mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-06-18 20:41:25 +00:00
[multistream] Make the lazy variant more interoperable. (#1855)
* Make the lazy variant interoperable. The remaining optimisation for `V1Lazy` for a listener in the negotiation, whereby the listener delays flushing of the multistream version header, is hereby removed. The remaining effect of `V1Lazy` is only on the side of the dialer, which delays flushing of its singular protocol proposal in order to send it together with the first application data (or an attempt is made to read from the negotiated stream, which similarly triggers a flush of the protocol proposal). This permits `V1Lazy` dialers to be interoperable with `V1` listeners. The remaining theoretical pitfall whereby application data gets misinterpreted as another protocol proposal by a listener remains, however unlikely. `V1` remains the default, but we may eventually risk just making this lazy dialer flush a part of the default `V1` implementation, removing the dedicated `V1Lazy` version identifier. * Update CHANGELOG * Separate versions from mere header lines. Every multistream-select version maps to a specific header line, but there may be different variants of the same multistream-select version using the same header line, i.e. the same wire protocol. * Cleanup * Update misc/multistream-select/CHANGELOG.md
This commit is contained in:
@ -25,6 +25,7 @@
|
||||
//! `Stream` and `Sink` implementations of `MessageIO` and
|
||||
//! `MessageReader`.
|
||||
|
||||
use crate::Version;
|
||||
use crate::length_delimited::{LengthDelimited, LengthDelimitedReader};
|
||||
|
||||
use bytes::{Bytes, BytesMut, BufMut};
|
||||
@ -37,71 +38,25 @@ const MAX_PROTOCOLS: usize = 1000;
|
||||
|
||||
/// The encoded form of a multistream-select 1.0.0 header message.
|
||||
const MSG_MULTISTREAM_1_0: &[u8] = b"/multistream/1.0.0\n";
|
||||
/// The encoded form of a multistream-select 1.0.0 header message.
|
||||
const MSG_MULTISTREAM_1_0_LAZY: &[u8] = b"/multistream-lazy/1\n";
|
||||
/// The encoded form of a multistream-select 'na' message.
|
||||
const MSG_PROTOCOL_NA: &[u8] = b"na\n";
|
||||
/// The encoded form of a multistream-select 'ls' message.
|
||||
const MSG_LS: &[u8] = b"ls\n";
|
||||
|
||||
/// Supported multistream-select protocol versions.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Version {
|
||||
/// Version 1 of the multistream-select protocol. See [1] and [2].
|
||||
///
|
||||
/// [1]: https://github.com/libp2p/specs/blob/master/connections/README.md#protocol-negotiation
|
||||
/// [2]: https://github.com/multiformats/multistream-select
|
||||
/// The multistream-select header lines preceeding negotiation.
|
||||
///
|
||||
/// Every [`Version`] has a corresponding header line.
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
|
||||
pub enum HeaderLine {
|
||||
/// The `/multistream/1.0.0` header line.
|
||||
V1,
|
||||
/// A lazy variant of version 1 that is identical on the wire but delays
|
||||
/// sending of protocol negotiation data as much as possible.
|
||||
///
|
||||
/// Delaying the sending of protocol negotiation data can result in
|
||||
/// significantly fewer network roundtrips used for the negotiation,
|
||||
/// up to 0-RTT negotiation.
|
||||
///
|
||||
/// 0-RTT negotiation is achieved if the dialer supports only a single
|
||||
/// application protocol. In that case the dialer immedidately settles
|
||||
/// on that protocol, buffering the negotiation messages to be sent
|
||||
/// with the first round of application protocol data (or an attempt
|
||||
/// is made to read from the `Negotiated` I/O stream).
|
||||
///
|
||||
/// A listener receiving a `V1Lazy` header will similarly delay sending
|
||||
/// of the protocol confirmation. Though typically the listener will need
|
||||
/// to read the request data before sending its response, thus triggering
|
||||
/// sending of the protocol confirmation, which, in absence of additional
|
||||
/// buffering on lower layers will result in at least two response frames
|
||||
/// to be sent.
|
||||
///
|
||||
/// `V1Lazy` is specific to `rust-libp2p`: While the wire protocol
|
||||
/// is identical to `V1`, delayed sending of protocol negotiation frames
|
||||
/// is only safe under the following assumptions:
|
||||
///
|
||||
/// 1. The dialer is assumed to always send the first multistream-select
|
||||
/// protocol message immediately after the multistream header, without
|
||||
/// first waiting for confirmation of that header. Since the listener
|
||||
/// delays sending the protocol confirmation, a deadlock situation may
|
||||
/// otherwise occurs that is only resolved by a timeout. This assumption
|
||||
/// is trivially satisfied if both peers support and use `V1Lazy`.
|
||||
///
|
||||
/// 2. When nesting multiple protocol negotiations, the listener is either
|
||||
/// known to support all of the dialer's optimistically chosen protocols
|
||||
/// or there is no intermediate protocol without a payload and none of
|
||||
/// the protocol payloads has the potential for being mistaken for a
|
||||
/// multistream-select protocol message. This avoids rare edge-cases whereby
|
||||
/// the listener may not recognize upgrade boundaries and erroneously
|
||||
/// process a request despite not supporting one of the intermediate
|
||||
/// protocols that the dialer committed to. See [1] and [2].
|
||||
///
|
||||
/// [1]: https://github.com/multiformats/go-multistream/issues/20
|
||||
/// [2]: https://github.com/libp2p/rust-libp2p/pull/1212
|
||||
V1Lazy,
|
||||
// Draft: https://github.com/libp2p/specs/pull/95
|
||||
// V2,
|
||||
}
|
||||
|
||||
impl Default for Version {
|
||||
fn default() -> Self {
|
||||
Version::V1
|
||||
impl From<Version> for HeaderLine {
|
||||
fn from(v: Version) -> HeaderLine {
|
||||
match v {
|
||||
Version::V1 | Version::V1Lazy => HeaderLine::V1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -148,7 +103,7 @@ impl fmt::Display for Protocol {
|
||||
pub enum Message {
|
||||
/// A header message identifies the multistream-select protocol
|
||||
/// that the sender wishes to speak.
|
||||
Header(Version),
|
||||
Header(HeaderLine),
|
||||
/// A protocol message identifies a protocol request or acknowledgement.
|
||||
Protocol(Protocol),
|
||||
/// A message through which a peer requests the complete list of
|
||||
@ -164,16 +119,11 @@ impl Message {
|
||||
/// Encodes a `Message` into its byte representation.
|
||||
pub fn encode(&self, dest: &mut BytesMut) -> Result<(), ProtocolError> {
|
||||
match self {
|
||||
Message::Header(Version::V1) => {
|
||||
Message::Header(HeaderLine::V1) => {
|
||||
dest.reserve(MSG_MULTISTREAM_1_0.len());
|
||||
dest.put(MSG_MULTISTREAM_1_0);
|
||||
Ok(())
|
||||
}
|
||||
Message::Header(Version::V1Lazy) => {
|
||||
dest.reserve(MSG_MULTISTREAM_1_0_LAZY.len());
|
||||
dest.put(MSG_MULTISTREAM_1_0_LAZY);
|
||||
Ok(())
|
||||
}
|
||||
Message::Protocol(p) => {
|
||||
let len = p.0.as_ref().len() + 1; // + 1 for \n
|
||||
dest.reserve(len);
|
||||
@ -209,12 +159,8 @@ impl Message {
|
||||
|
||||
/// Decodes a `Message` from its byte representation.
|
||||
pub fn decode(mut msg: Bytes) -> Result<Message, ProtocolError> {
|
||||
if msg == MSG_MULTISTREAM_1_0_LAZY {
|
||||
return Ok(Message::Header(Version::V1Lazy))
|
||||
}
|
||||
|
||||
if msg == MSG_MULTISTREAM_1_0 {
|
||||
return Ok(Message::Header(Version::V1))
|
||||
return Ok(Message::Header(HeaderLine::V1))
|
||||
}
|
||||
|
||||
if msg == MSG_PROTOCOL_NA {
|
||||
@ -506,7 +452,7 @@ mod tests {
|
||||
impl Arbitrary for Message {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> Message {
|
||||
match g.gen_range(0, 5) {
|
||||
0 => Message::Header(Version::V1),
|
||||
0 => Message::Header(HeaderLine::V1),
|
||||
1 => Message::NotAvailable,
|
||||
2 => Message::ListProtocols,
|
||||
3 => Message::Protocol(Protocol::arbitrary(g)),
|
||||
|
Reference in New Issue
Block a user