From e94e21ba39ed14851cd37262bcadfd42c9fd1ef8 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Tue, 16 Jul 2019 10:46:47 +0200 Subject: [PATCH 01/14] Remove tokio-codec dependency from multistream-select. In preparation for the eventual switch from tokio to std futures. Includes some initial refactoring in preparation for further work in the context of https://github.com/libp2p/rust-libp2p/issues/659. --- misc/multistream-select/Cargo.toml | 3 +- misc/multistream-select/src/dialer_select.rs | 43 ++-- misc/multistream-select/src/error.rs | 19 +- .../src/length_delimited.rs | 239 +++++++++--------- misc/multistream-select/src/lib.rs | 11 +- .../multistream-select/src/listener_select.rs | 16 +- .../multistream-select/src/protocol/dialer.rs | 134 +++++----- misc/multistream-select/src/protocol/error.rs | 28 +- .../src/protocol/listener.rs | 140 +++++----- misc/multistream-select/src/protocol/mod.rs | 22 +- misc/multistream-select/src/tests.rs | 12 +- 11 files changed, 323 insertions(+), 344 deletions(-) diff --git a/misc/multistream-select/Cargo.toml b/misc/multistream-select/Cargo.toml index bfa652f03c0..83de968e89d 100644 --- a/misc/multistream-select/Cargo.toml +++ b/misc/multistream-select/Cargo.toml @@ -14,9 +14,8 @@ bytes = "0.4" futures = { version = "0.1" } log = "0.4" smallvec = "0.6" -tokio-codec = "0.1" tokio-io = "0.1" -unsigned-varint = { version = "0.2.1", features = ["codec"] } +unsigned-varint = { version = "0.2.2" } [dev-dependencies] tokio = "0.1" diff --git a/misc/multistream-select/src/dialer_select.rs b/misc/multistream-select/src/dialer_select.rs index bbd40c1a200..59bda8ad396 100644 --- a/misc/multistream-select/src/dialer_select.rs +++ b/misc/multistream-select/src/dialer_select.rs @@ -22,19 +22,14 @@ //! `multistream-select` for the dialer. use futures::{future::Either, prelude::*, stream::StreamFuture}; -use crate::protocol::{ - Dialer, - DialerFuture, - DialerToListenerMessage, - ListenerToDialerMessage -}; +use crate::protocol::{Dialer, DialerFuture, Request, Response}; use log::trace; use std::mem; use tokio_io::{AsyncRead, AsyncWrite}; use crate::{Negotiated, ProtocolChoiceError}; /// Future, returned by `dialer_select_proto`, which selects a protocol and dialer -/// either sequentially of by considering all protocols in parallel. +/// either sequentially or by considering all protocols in parallel. pub type DialerSelectFuture = Either, DialerSelectPar>; /// Helps selecting a protocol amongst the ones supported. @@ -75,7 +70,10 @@ where { let protocols = protocols.into_iter(); DialerSelectSeq { - inner: DialerSelectSeqState::AwaitDialer { dialer_fut: Dialer::dial(inner), protocols } + inner: DialerSelectSeqState::AwaitDialer { + dialer_fut: Dialer::dial(inner), + protocols + } } } @@ -148,9 +146,7 @@ where } DialerSelectSeqState::NextProtocol { mut dialer, protocols, proto_name } => { trace!("sending {:?}", proto_name.as_ref()); - let req = DialerToListenerMessage::ProtocolRequest { - name: proto_name.clone() - }; + let req = Request::Protocol { name: proto_name.clone() }; match dialer.start_send(req)? { AsyncSink::Ready => { self.inner = DialerSelectSeqState::FlushProtocol { @@ -204,12 +200,12 @@ where }; trace!("received {:?}", m); match m.ok_or(ProtocolChoiceError::UnexpectedMessage)? { - ListenerToDialerMessage::ProtocolAck { ref name } + Response::Protocol { ref name } if name.as_ref() == proto_name.as_ref() => { return Ok(Async::Ready((proto_name, Negotiated(r.into_inner())))) } - ListenerToDialerMessage::NotAvailable => { + Response::ProtocolNotAvailable => { let proto_name = protocols.next() .ok_or(ProtocolChoiceError::NoProtocolFound)?; self.inner = DialerSelectSeqState::NextProtocol { @@ -244,9 +240,8 @@ where } } - /// Future, returned by `dialer_select_proto_parallel`, which selects a protocol and dialer in -/// parellel, by first requesting the liste of protocols supported by the remote endpoint and +/// parallel, by first requesting the list of protocols supported by the remote endpoint and /// then selecting the most appropriate one by applying a match predicate to the result. pub struct DialerSelectPar where @@ -319,7 +314,7 @@ where } DialerSelectParState::ProtocolList { mut dialer, protocols } => { trace!("requesting protocols list"); - match dialer.start_send(DialerToListenerMessage::ProtocolsListRequest)? { + match dialer.start_send(Request::ListProtocols)? { AsyncSink::Ready => { self.inner = DialerSelectParState::FlushListRequest { dialer, @@ -359,15 +354,15 @@ where Err((e, _)) => return Err(ProtocolChoiceError::from(e)) }; trace!("protocols list response: {:?}", resp); - let list = - if let Some(ListenerToDialerMessage::ProtocolsListResponse { list }) = resp { - list + let supported = + if let Some(Response::SupportedProtocols { protocols }) = resp { + protocols } else { return Err(ProtocolChoiceError::UnexpectedMessage) }; let mut found = None; for local_name in protocols { - for remote_name in &list { + for remote_name in &supported { if remote_name.as_ref() == local_name.as_ref() { found = Some(local_name); break; @@ -381,10 +376,8 @@ where self.inner = DialerSelectParState::Protocol { dialer, proto_name } } DialerSelectParState::Protocol { mut dialer, proto_name } => { - trace!("requesting protocol: {:?}", proto_name.as_ref()); - let req = DialerToListenerMessage::ProtocolRequest { - name: proto_name.clone() - }; + trace!("Requesting protocol: {:?}", proto_name.as_ref()); + let req = Request::Protocol { name: proto_name.clone() }; match dialer.start_send(req)? { AsyncSink::Ready => { self.inner = DialerSelectParState::FlushProtocol { dialer, proto_name } @@ -420,7 +413,7 @@ where }; trace!("received {:?}", resp); match resp { - Some(ListenerToDialerMessage::ProtocolAck { ref name }) + Some(Response::Protocol { ref name }) if name.as_ref() == proto_name.as_ref() => { return Ok(Async::Ready((proto_name, Negotiated(dialer.into_inner())))) diff --git a/misc/multistream-select/src/error.rs b/misc/multistream-select/src/error.rs index 62b540ec502..1f72b5c0c8a 100644 --- a/misc/multistream-select/src/error.rs +++ b/misc/multistream-select/src/error.rs @@ -21,9 +21,8 @@ //! Main `ProtocolChoiceError` error. use crate::protocol::MultistreamSelectError; -use std::error; -use std::fmt; -use std::io::Error as IoError; +use std::error::Error; +use std::{fmt, io}; /// Error that can happen when negotiating a protocol with the remote. #[derive(Debug)] @@ -39,21 +38,18 @@ pub enum ProtocolChoiceError { } impl From for ProtocolChoiceError { - #[inline] fn from(err: MultistreamSelectError) -> ProtocolChoiceError { ProtocolChoiceError::MultistreamSelectError(err) } } -impl From for ProtocolChoiceError { - #[inline] - fn from(err: IoError) -> ProtocolChoiceError { +impl From for ProtocolChoiceError { + fn from(err: io::Error) -> ProtocolChoiceError { MultistreamSelectError::from(err).into() } } -impl error::Error for ProtocolChoiceError { - #[inline] +impl Error for ProtocolChoiceError { fn description(&self) -> &str { match *self { ProtocolChoiceError::MultistreamSelectError(_) => "error in the protocol", @@ -66,7 +62,7 @@ impl error::Error for ProtocolChoiceError { } } - fn cause(&self) -> Option<&dyn error::Error> { + fn source(&self) -> Option<&(dyn Error + 'static)> { match *self { ProtocolChoiceError::MultistreamSelectError(ref err) => Some(err), _ => None, @@ -75,8 +71,7 @@ impl error::Error for ProtocolChoiceError { } impl fmt::Display for ProtocolChoiceError { - #[inline] fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(fmt, "{}", error::Error::description(self)) + write!(fmt, "{}", Error::description(self)) } } diff --git a/misc/multistream-select/src/length_delimited.rs b/misc/multistream-select/src/length_delimited.rs index 72256b83980..44a8ff36090 100644 --- a/misc/multistream-select/src/length_delimited.rs +++ b/misc/multistream-select/src/length_delimited.rs @@ -18,55 +18,59 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use bytes::Bytes; -use futures::{Async, Poll, Sink, StartSend, Stream}; -use smallvec::SmallVec; +use bytes::{Bytes, BytesMut, BufMut}; +use futures::{try_ready, Async, Poll, Sink, StartSend, Stream, AsyncSink}; use std::{io, u16}; -use tokio_codec::{Encoder, FramedWrite}; use tokio_io::{AsyncRead, AsyncWrite}; -use unsigned_varint::decode; +use unsigned_varint as uvi; -/// `Stream` and `Sink` wrapping some `AsyncRead + AsyncWrite` object to read +const MAX_LEN_BYTES: u16 = 2; +const MAX_FRAME_SIZE: u16 = (1 << (MAX_LEN_BYTES * 8 - MAX_LEN_BYTES)) - 1; + +/// `Stream` and `Sink` wrapping some `AsyncRead + AsyncWrite` resource to read /// and write unsigned-varint prefixed frames. /// -/// We purposely only support a frame length of under 64kiB. Frames mostly consist -/// in a short protocol name, which is highly unlikely to be more than 64kiB long. -pub struct LengthDelimited { - // The inner socket where data is pulled from. - inner: FramedWrite, - // Intermediary buffer where we put either the length of the next frame of data, or the frame - // of data itself before it is returned. - // Must always contain enough space to read data from `inner`. - internal_buffer: SmallVec<[u8; 64]>, - // Number of bytes within `internal_buffer` that contain valid data. - internal_buffer_pos: usize, - // State of the decoder. - state: State +/// We purposely only support a frame sizes up to 16KiB (2 bytes unsigned varint +/// frame length). Frames mostly consist in a short protocol name, which is highly +/// unlikely to be more than 16KiB long. +pub struct LengthDelimited { + /// The inner I/O resource. + inner: R, + /// Read buffer for a single unsigned-varint length-delimited frame. + read_buffer: BytesMut, + /// Write buffer for a single unsigned-varint length-delimited frame. + write_buffer: BytesMut, + /// The current read state, alternating between reading a frame + /// length and reading a frame payload. + read_state: ReadState, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] -enum State { - // We are currently reading the length of the next frame of data. - ReadingLength, - // We are currently reading the frame of data itself. - ReadingData { frame_len: u16 }, +enum ReadState { + /// We are currently reading the length of the next frame of data. + ReadLength { buf: [u8; MAX_LEN_BYTES as usize], pos: usize }, + /// We are currently reading the frame of data itself. + ReadData { len: u16, pos: usize }, } -impl LengthDelimited -where - R: AsyncWrite, - C: Encoder -{ - pub fn new(inner: R, codec: C) -> LengthDelimited { +impl Default for ReadState { + fn default() -> Self { + ReadState::ReadLength { + buf: [0; MAX_LEN_BYTES as usize], + pos: 0 + } + } +} + +impl LengthDelimited { + /// Creates a new I/O resource for reading and writing unsigned-varint + /// length delimited frames. + pub fn new(inner: R) -> LengthDelimited { LengthDelimited { - inner: FramedWrite::new(inner, codec), - internal_buffer: { - let mut v = SmallVec::new(); - v.push(0); - v - }, - internal_buffer_pos: 0, - state: State::ReadingLength + inner, + read_state: ReadState::default(), + read_buffer: BytesMut::with_capacity(MAX_FRAME_SIZE as usize), + write_buffer: BytesMut::with_capacity((MAX_FRAME_SIZE + MAX_LEN_BYTES) as usize), } } @@ -81,15 +85,14 @@ where /// you call `poll()` manually**. Using this struct as it is intended to be used (i.e. through /// the modifiers provided by the `futures` crate) will always leave the object in a state in /// which `into_inner()` will not panic. - #[inline] pub fn into_inner(self) -> R { - assert_eq!(self.state, State::ReadingLength); - assert_eq!(self.internal_buffer_pos, 0); - self.inner.into_inner() + assert!(self.write_buffer.is_empty()); + assert!(self.read_buffer.is_empty()); + self.inner } } -impl Stream for LengthDelimited +impl Stream for LengthDelimited where R: AsyncRead { @@ -98,16 +101,11 @@ where fn poll(&mut self) -> Poll, Self::Error> { loop { - debug_assert!(!self.internal_buffer.is_empty()); - debug_assert!(self.internal_buffer_pos < self.internal_buffer.len()); - - match self.state { - State::ReadingLength => { - let slice = &mut self.internal_buffer[self.internal_buffer_pos..]; - match self.inner.get_mut().read(slice) { + match &mut self.read_state { + ReadState::ReadLength { buf, pos } => { + match self.inner.read(&mut buf[*pos .. *pos + 1]) { Ok(0) => { - // EOF - if self.internal_buffer_pos == 0 { + if *pos == 0 { return Ok(Async::Ready(None)); } else { return Err(io::ErrorKind::UnexpectedEof.into()); @@ -115,7 +113,7 @@ where } Ok(n) => { debug_assert_eq!(n, 1); - self.internal_buffer_pos += n; + *pos += n; } Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => { return Ok(Async::NotReady); @@ -125,56 +123,45 @@ where } }; - debug_assert_eq!(self.internal_buffer.len(), self.internal_buffer_pos); - - if (*self.internal_buffer.last().unwrap_or(&0) & 0x80) == 0 { - // End of length prefix. Most of the time we will switch to reading data, - // but we need to handle a few corner cases first. - let (frame_len, _) = decode::u16(&self.internal_buffer).map_err(|e| { + if (buf[*pos - 1] & 0x80) == 0 { + // MSB is not set, indicating the end of the length prefix. + let (len, _) = uvi::decode::u16(buf).map_err(|e| { log::debug!("invalid length prefix: {}", e); io::Error::new(io::ErrorKind::InvalidData, "invalid length prefix") })?; - if frame_len >= 1 { - self.state = State::ReadingData { frame_len }; - self.internal_buffer.clear(); - self.internal_buffer.reserve(frame_len as usize); - self.internal_buffer.extend((0..frame_len).map(|_| 0)); - self.internal_buffer_pos = 0; + if len >= 1 { + self.read_state = ReadState::ReadData { len, pos: 0 }; + self.read_buffer.resize(len as usize, 0); } else { - debug_assert_eq!(frame_len, 0); - self.state = State::ReadingLength; - self.internal_buffer.clear(); - self.internal_buffer.push(0); - self.internal_buffer_pos = 0; - return Ok(Async::Ready(Some(From::from(&[][..])))); + debug_assert_eq!(len, 0); + self.read_state = ReadState::default(); + return Ok(Async::Ready(Some(Bytes::new()))); } - } else if self.internal_buffer_pos >= 2 { - // Length prefix is too long. See module doc for info about max frame len. - return Err(io::Error::new(io::ErrorKind::InvalidData, "frame length too long")); - } else { - // Prepare for next read. - self.internal_buffer.push(0); + } else if *pos == MAX_LEN_BYTES as usize { + // MSB signals more length bytes but we have already read the maximum. + // See the module documentation about the max frame len. + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Maximum frame length exceeded")); } } - State::ReadingData { frame_len } => { - let slice = &mut self.internal_buffer[self.internal_buffer_pos..]; - match self.inner.get_mut().read(slice) { + ReadState::ReadData { len, pos } => { + match self.inner.read(&mut self.read_buffer[*pos..]) { Ok(0) => return Err(io::ErrorKind::UnexpectedEof.into()), - Ok(n) => self.internal_buffer_pos += n, - Err(ref err) if err.kind() == io::ErrorKind::WouldBlock => { - return Ok(Async::NotReady) - } - Err(err) => return Err(err) + Ok(n) => *pos += n, + Err(err) => + if err.kind() == io::ErrorKind::WouldBlock { + return Ok(Async::NotReady) + } else { + return Err(err) + } }; - if self.internal_buffer_pos >= frame_len as usize { - // Finished reading the frame of data. - self.state = State::ReadingLength; - let out_data = From::from(&self.internal_buffer[..]); - self.internal_buffer.clear(); - self.internal_buffer.push(0); - self.internal_buffer_pos = 0; - return Ok(Async::Ready(Some(out_data))); + if *pos == *len as usize { + // Finished reading the frame. + let frame = self.read_buffer.split_off(0).freeze(); + self.read_state = ReadState::default(); + return Ok(Async::Ready(Some(frame))); } } } @@ -182,27 +169,54 @@ where } } -impl Sink for LengthDelimited +impl Sink for LengthDelimited where R: AsyncWrite, - C: Encoder { - type SinkItem = as Sink>::SinkItem; - type SinkError = as Sink>::SinkError; + type SinkItem = Bytes; + type SinkError = io::Error; + + fn start_send(&mut self, msg: Self::SinkItem) -> StartSend { + if !self.write_buffer.is_empty() { + self.poll_complete()?; + if !self.write_buffer.is_empty() { + return Ok(AsyncSink::NotReady(msg)) + } + } + + let len = msg.len() as u16; + if len > MAX_FRAME_SIZE { + return Err(io::Error::new( + io::ErrorKind::InvalidData, + "Maximum frame size exceeded.")) + } - #[inline] - fn start_send(&mut self, item: Self::SinkItem) -> StartSend { - self.inner.start_send(item) + self.write_buffer.put_slice(uvi::encode::u16(len, &mut [0; 3])); + self.write_buffer.extend(msg); + + Ok(AsyncSink::Ready) } - #[inline] fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - self.inner.poll_complete() + while !self.write_buffer.is_empty() { + let n = try_ready!(self.inner.poll_write(&self.write_buffer)); + + if n == 0 { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "Failed to write buffered frame.")) + } + + let _ = self.write_buffer.split_to(n); + } + + try_ready!(self.inner.poll_flush()); + return Ok(Async::Ready(())); } - #[inline] fn close(&mut self) -> Poll<(), Self::SinkError> { - self.inner.close() + try_ready!(self.poll_complete()); + Ok(self.inner.shutdown()?) } } @@ -211,12 +225,11 @@ mod tests { use futures::{Future, Stream}; use crate::length_delimited::LengthDelimited; use std::io::{Cursor, ErrorKind}; - use unsigned_varint::codec::UviBytes; #[test] fn basic_read() { let data = vec![6, 9, 8, 7, 6, 5, 4]; - let framed = LengthDelimited::new(Cursor::new(data), UviBytes::>::default()); + let framed = LengthDelimited::new(Cursor::new(data)); let recved = framed.collect().wait().unwrap(); assert_eq!(recved, vec![vec![9, 8, 7, 6, 5, 4]]); } @@ -224,7 +237,7 @@ mod tests { #[test] fn basic_read_two() { let data = vec![6, 9, 8, 7, 6, 5, 4, 3, 9, 8, 7]; - let framed = LengthDelimited::new(Cursor::new(data), UviBytes::>::default()); + let framed = LengthDelimited::new(Cursor::new(data)); let recved = framed.collect().wait().unwrap(); assert_eq!(recved, vec![vec![9, 8, 7, 6, 5, 4], vec![9, 8, 7]]); } @@ -236,7 +249,7 @@ mod tests { let frame = (0..len).map(|n| (n & 0xff) as u8).collect::>(); let mut data = vec![(len & 0x7f) as u8 | 0x80, (len >> 7) as u8]; data.extend(frame.clone().into_iter()); - let framed = LengthDelimited::new(Cursor::new(data), UviBytes::>::default()); + let framed = LengthDelimited::new(Cursor::new(data)); let recved = framed .into_future() .map(|(m, _)| m) @@ -250,7 +263,7 @@ mod tests { fn packet_len_too_long() { let mut data = vec![0x81, 0x81, 0x1]; data.extend((0..16513).map(|_| 0)); - let framed = LengthDelimited::new(Cursor::new(data), UviBytes::>::default()); + let framed = LengthDelimited::new(Cursor::new(data)); let recved = framed .into_future() .map(|(m, _)| m) @@ -267,7 +280,7 @@ mod tests { #[test] fn empty_frames() { let data = vec![0, 0, 6, 9, 8, 7, 6, 5, 4, 0, 3, 9, 8, 7]; - let framed = LengthDelimited::new(Cursor::new(data), UviBytes::>::default()); + let framed = LengthDelimited::new(Cursor::new(data)); let recved = framed.collect().wait().unwrap(); assert_eq!( recved, @@ -284,7 +297,7 @@ mod tests { #[test] fn unexpected_eof_in_len() { let data = vec![0x89]; - let framed = LengthDelimited::new(Cursor::new(data), UviBytes::>::default()); + let framed = LengthDelimited::new(Cursor::new(data)); let recved = framed.collect().wait(); if let Err(io_err) = recved { assert_eq!(io_err.kind(), ErrorKind::UnexpectedEof) @@ -296,7 +309,7 @@ mod tests { #[test] fn unexpected_eof_in_data() { let data = vec![5]; - let framed = LengthDelimited::new(Cursor::new(data), UviBytes::>::default()); + let framed = LengthDelimited::new(Cursor::new(data)); let recved = framed.collect().wait(); if let Err(io_err) = recved { assert_eq!(io_err.kind(), ErrorKind::UnexpectedEof) @@ -308,7 +321,7 @@ mod tests { #[test] fn unexpected_eof_in_data2() { let data = vec![5, 9, 8, 7]; - let framed = LengthDelimited::new(Cursor::new(data), UviBytes::>::default()); + let framed = LengthDelimited::new(Cursor::new(data)); let recved = framed.collect().wait(); if let Err(io_err) = recved { assert_eq!(io_err.kind(), ErrorKind::UnexpectedEof) diff --git a/misc/multistream-select/src/lib.rs b/misc/multistream-select/src/lib.rs index 746f167608c..b2bc054bfff 100644 --- a/misc/multistream-select/src/lib.rs +++ b/misc/multistream-select/src/lib.rs @@ -21,7 +21,7 @@ //! # Multistream-select //! //! This crate implements the `multistream-select` protocol, which is the protocol used by libp2p -//! to negotiate which protocol to use with the remote. +//! to negotiate which protocol to use with the remote on a connection or substream. //! //! > **Note**: This crate is used by the internals of *libp2p*, and it is not required to //! > understand it in order to use *libp2p*. @@ -76,6 +76,7 @@ mod protocol; use futures::prelude::*; use std::io; +use tokio_io::{AsyncRead, AsyncWrite}; pub use self::dialer_select::{dialer_select_proto, DialerSelectFuture}; pub use self::error::ProtocolChoiceError; @@ -93,9 +94,9 @@ where } } -impl tokio_io::AsyncRead for Negotiated +impl AsyncRead for Negotiated where - TInner: tokio_io::AsyncRead + TInner: AsyncRead { unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { self.0.prepare_uninitialized_buffer(buf) @@ -119,9 +120,9 @@ where } } -impl tokio_io::AsyncWrite for Negotiated +impl AsyncWrite for Negotiated where - TInner: tokio_io::AsyncWrite + TInner: AsyncWrite { fn shutdown(&mut self) -> Poll<(), io::Error> { self.0.shutdown() diff --git a/misc/multistream-select/src/listener_select.rs b/misc/multistream-select/src/listener_select.rs index 59492dc0722..40ed92d057e 100644 --- a/misc/multistream-select/src/listener_select.rs +++ b/misc/multistream-select/src/listener_select.rs @@ -23,10 +23,10 @@ use futures::{prelude::*, sink, stream::StreamFuture}; use crate::protocol::{ - DialerToListenerMessage, + Request, + Response, Listener, ListenerFuture, - ListenerToDialerMessage }; use log::{debug, trace}; use std::mem; @@ -126,13 +126,13 @@ where Err((e, _)) => return Err(ProtocolChoiceError::from(e)) }; match msg { - Some(DialerToListenerMessage::ProtocolsListRequest) => { + Some(Request::ListProtocols) => { trace!("protocols list response: {:?}", protocols .into_iter() .map(|p| p.as_ref().into()) .collect::>>()); - let list = protocols.into_iter().collect(); - let msg = ListenerToDialerMessage::ProtocolsListResponse { list }; + let supported = protocols.into_iter().collect(); + let msg = Response::SupportedProtocols { protocols: supported }; let sender = listener.send(msg); self.inner = ListenerSelectState::Outgoing { sender, @@ -140,12 +140,12 @@ where outcome: None } } - Some(DialerToListenerMessage::ProtocolRequest { name }) => { + Some(Request::Protocol { name }) => { let mut outcome = None; - let mut send_back = ListenerToDialerMessage::NotAvailable; + let mut send_back = Response::ProtocolNotAvailable; for supported in &protocols { if name.as_ref() == supported.as_ref() { - send_back = ListenerToDialerMessage::ProtocolAck { + send_back = Response::Protocol { name: supported.clone() }; outcome = Some(supported); diff --git a/misc/multistream-select/src/protocol/dialer.rs b/misc/multistream-select/src/protocol/dialer.rs index 71c9d21077f..d2d732a46ac 100644 --- a/misc/multistream-select/src/protocol/dialer.rs +++ b/misc/multistream-select/src/protocol/dialer.rs @@ -20,23 +20,25 @@ //! Contains the `Dialer` wrapper, which allows raw communications with a listener. +use super::*; + use bytes::{BufMut, Bytes, BytesMut}; use crate::length_delimited::LengthDelimited; -use crate::protocol::DialerToListenerMessage; -use crate::protocol::ListenerToDialerMessage; -use crate::protocol::MultistreamSelectError; -use crate::protocol::MULTISTREAM_PROTOCOL_WITH_LF; +use crate::protocol::{Request, Response, MultistreamSelectError}; use futures::{prelude::*, sink, Async, StartSend, try_ready}; -use std::io; -use tokio_codec::Encoder; use tokio_io::{AsyncRead, AsyncWrite}; -use unsigned_varint::{decode, codec::Uvi}; +use std::marker; +use unsigned_varint as uvi; + +/// The maximum number of supported protocols that can be processed. +const MAX_PROTOCOLS: usize = 1000; /// Wraps around a `AsyncRead+AsyncWrite`. /// Assumes that we're on the dialer's side. Produces and accepts messages. pub struct Dialer { - inner: LengthDelimited>, - handshake_finished: bool + inner: LengthDelimited, + handshake_finished: bool, + _protocol_name: marker::PhantomData, } impl Dialer @@ -45,15 +47,16 @@ where N: AsRef<[u8]> { pub fn dial(inner: R) -> DialerFuture { - let codec = MessageEncoder(std::marker::PhantomData); - let sender = LengthDelimited::new(inner, codec); + let sender = LengthDelimited::new(inner); + let mut buf = BytesMut::new(); + let _ = Message::::Header.encode(&mut buf); DialerFuture { - inner: sender.send(Message::Header) + inner: sender.send(buf.freeze()), + _protocol_name: marker::PhantomData, } } /// Grants back the socket. Typically used after a `ProtocolAck` has been received. - #[inline] pub fn into_inner(self) -> R { self.inner.into_inner() } @@ -64,24 +67,22 @@ where R: AsyncRead + AsyncWrite, N: AsRef<[u8]> { - type SinkItem = DialerToListenerMessage; + type SinkItem = Request; type SinkError = MultistreamSelectError; - #[inline] fn start_send(&mut self, item: Self::SinkItem) -> StartSend { - match self.inner.start_send(Message::Body(item))? { - AsyncSink::NotReady(Message::Body(item)) => Ok(AsyncSink::NotReady(item)), - AsyncSink::NotReady(Message::Header) => unreachable!(), - AsyncSink::Ready => Ok(AsyncSink::Ready) + let mut msg = BytesMut::new(); + Message::Body(&item).encode(&mut msg)?; + match self.inner.start_send(msg.freeze())? { + AsyncSink::NotReady(_) => Ok(AsyncSink::NotReady(item)), + AsyncSink::Ready => Ok(AsyncSink::Ready), } } - #[inline] fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { Ok(self.inner.poll_complete()?) } - #[inline] fn close(&mut self) -> Poll<(), Self::SinkError> { Ok(self.inner.close()?) } @@ -91,20 +92,20 @@ impl Stream for Dialer where R: AsyncRead + AsyncWrite { - type Item = ListenerToDialerMessage; + type Item = Response; type Error = MultistreamSelectError; fn poll(&mut self) -> Poll, Self::Error> { loop { - let mut frame = match self.inner.poll() { - Ok(Async::Ready(Some(frame))) => frame, + let mut msg = match self.inner.poll() { + Ok(Async::Ready(Some(msg))) => msg, Ok(Async::Ready(None)) => return Ok(Async::Ready(None)), Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => return Err(err.into()), }; if !self.handshake_finished { - if frame == MULTISTREAM_PROTOCOL_WITH_LF { + if msg == MSG_MULTISTREAM_1_0 { self.handshake_finished = true; continue; } else { @@ -112,31 +113,31 @@ where } } - if frame.get(0) == Some(&b'/') && frame.last() == Some(&b'\n') { - let frame_len = frame.len(); - let protocol = frame.split_to(frame_len - 1); - return Ok(Async::Ready(Some(ListenerToDialerMessage::ProtocolAck { - name: protocol - }))); - } else if frame == b"na\n"[..] { - return Ok(Async::Ready(Some(ListenerToDialerMessage::NotAvailable))); + if msg.get(0) == Some(&b'/') && msg.last() == Some(&b'\n') { + let len = msg.len(); + let name = msg.split_to(len - 1); + return Ok(Async::Ready(Some( + Response::Protocol { name } + ))); + } else if msg == MSG_PROTOCOL_NA { + return Ok(Async::Ready(Some(Response::ProtocolNotAvailable))); } else { // A varint number of protocols - let (num_protocols, mut remaining) = decode::usize(&frame)?; - if num_protocols > 1000 { // TODO: configurable limit - return Err(MultistreamSelectError::VarintParseError("too many protocols".into())) + let (num_protocols, mut remaining) = uvi::decode::usize(&msg)?; + if num_protocols > MAX_PROTOCOLS { // TODO: configurable limit + return Err(MultistreamSelectError::TooManyProtocols) } - let mut out = Vec::with_capacity(num_protocols); + let mut protocols = Vec::with_capacity(num_protocols); for _ in 0 .. num_protocols { - let (len, rem) = decode::usize(remaining)?; + let (len, rem) = uvi::decode::usize(remaining)?; if len == 0 || len > rem.len() || rem[len - 1] != b'\n' { return Err(MultistreamSelectError::UnknownMessage) } - out.push(Bytes::from(&rem[.. len - 1])); + protocols.push(Bytes::from(&rem[.. len - 1])); remaining = &rem[len ..] } return Ok(Async::Ready(Some( - ListenerToDialerMessage::ProtocolsListResponse { list: out }, + Response::SupportedProtocols { protocols }, ))); } } @@ -145,7 +146,8 @@ where /// Future, returned by `Dialer::new`, which send the handshake and returns the actual `Dialer`. pub struct DialerFuture> { - inner: sink::Send>> + inner: sink::Send>, + _protocol_name: marker::PhantomData, } impl> Future for DialerFuture { @@ -154,48 +156,40 @@ impl> Future for DialerFuture { fn poll(&mut self) -> Poll { let inner = try_ready!(self.inner.poll()); - Ok(Async::Ready(Dialer { inner, handshake_finished: false })) + Ok(Async::Ready(Dialer { + inner, + handshake_finished: false, + _protocol_name: marker::PhantomData, + })) } } -/// tokio-codec `Encoder` handling `DialerToListenerMessage` values. -struct MessageEncoder(std::marker::PhantomData); - -enum Message { +enum Message<'a, N> { Header, - Body(DialerToListenerMessage) + Body(&'a Request) } -impl> Encoder for MessageEncoder { - type Item = Message; - type Error = MultistreamSelectError; - - fn encode(&mut self, item: Self::Item, dest: &mut BytesMut) -> Result<(), Self::Error> { - match item { +impl> Message<'_, N> { + fn encode(&self, dest: &mut BytesMut) -> Result<(), MultistreamSelectError> { + match self { Message::Header => { - Uvi::::default().encode(MULTISTREAM_PROTOCOL_WITH_LF.len(), dest)?; - dest.reserve(MULTISTREAM_PROTOCOL_WITH_LF.len()); - dest.put(MULTISTREAM_PROTOCOL_WITH_LF); + dest.reserve(MSG_MULTISTREAM_1_0.len()); + dest.put(MSG_MULTISTREAM_1_0); Ok(()) } - Message::Body(DialerToListenerMessage::ProtocolRequest { name }) => { + Message::Body(Request::Protocol { name }) => { if !name.as_ref().starts_with(b"/") { - return Err(MultistreamSelectError::WrongProtocolName) + return Err(MultistreamSelectError::InvalidProtocolName) } let len = name.as_ref().len() + 1; // + 1 for \n - if len > std::u16::MAX as usize { - return Err(io::Error::new(io::ErrorKind::InvalidData, "name too long").into()) - } - Uvi::::default().encode(len, dest)?; dest.reserve(len); dest.put(name.as_ref()); dest.put(&b"\n"[..]); Ok(()) } - Message::Body(DialerToListenerMessage::ProtocolsListRequest) => { - Uvi::::default().encode(3, dest)?; - dest.reserve(3); - dest.put(&b"ls\n"[..]); + Message::Body(Request::ListProtocols) => { + dest.reserve(MSG_LS.len()); + dest.put(MSG_LS); Ok(()) } } @@ -204,7 +198,7 @@ impl> Encoder for MessageEncoder { #[cfg(test)] mod tests { - use crate::protocol::{Dialer, DialerToListenerMessage, MultistreamSelectError}; + use super::*; use tokio::runtime::current_thread::Runtime; use tokio_tcp::{TcpListener, TcpStream}; use futures::Future; @@ -225,13 +219,13 @@ mod tests { .from_err() .and_then(move |stream| Dialer::dial(stream)) .and_then(move |dialer| { - let p = b"invalid_name"; - dialer.send(DialerToListenerMessage::ProtocolRequest { name: p }) + let name = b"invalid_name"; + dialer.send(Request::Protocol { name }) }); let mut rt = Runtime::new().unwrap(); match rt.block_on(server.join(client)) { - Err(MultistreamSelectError::WrongProtocolName) => (), + Err(MultistreamSelectError::InvalidProtocolName) => (), _ => panic!(), } } diff --git a/misc/multistream-select/src/protocol/error.rs b/misc/multistream-select/src/protocol/error.rs index f3b859f15da..f6686ee9fbd 100644 --- a/misc/multistream-select/src/protocol/error.rs +++ b/misc/multistream-select/src/protocol/error.rs @@ -20,7 +20,7 @@ //! Contains the error structs for the low-level protocol handling. -use std::error; +use std::error::Error; use std::fmt; use std::io; use unsigned_varint::decode; @@ -38,29 +38,25 @@ pub enum MultistreamSelectError { UnknownMessage, /// Protocol names must always start with `/`, otherwise this error is returned. - WrongProtocolName, + InvalidProtocolName, - /// Failure to parse variable-length integer. - // TODO: we don't include the actual error, because that would remove Send from the enum - VarintParseError(String), + /// Too many protocols have been returned by the remote. + TooManyProtocols, } impl From for MultistreamSelectError { - #[inline] fn from(err: io::Error) -> MultistreamSelectError { MultistreamSelectError::IoError(err) } } impl From for MultistreamSelectError { - #[inline] fn from(err: decode::Error) -> MultistreamSelectError { - MultistreamSelectError::VarintParseError(err.to_string()) + Self::from(io::Error::new(io::ErrorKind::InvalidData, err.to_string())) } } -impl error::Error for MultistreamSelectError { - #[inline] +impl Error for MultistreamSelectError { fn description(&self) -> &str { match *self { MultistreamSelectError::IoError(_) => "I/O error", @@ -68,16 +64,15 @@ impl error::Error for MultistreamSelectError { "the remote doesn't use the same multistream-select protocol as we do" } MultistreamSelectError::UnknownMessage => "received an unknown message from the remote", - MultistreamSelectError::WrongProtocolName => { + MultistreamSelectError::InvalidProtocolName => { "protocol names must always start with `/`, otherwise this error is returned" } - MultistreamSelectError::VarintParseError(_) => { - "failure to parse variable-length integer" - } + MultistreamSelectError::TooManyProtocols => + "Too many protocols." } } - fn cause(&self) -> Option<&dyn error::Error> { + fn source(&self) -> Option<&(dyn Error + 'static)> { match *self { MultistreamSelectError::IoError(ref err) => Some(err), _ => None, @@ -86,8 +81,7 @@ impl error::Error for MultistreamSelectError { } impl fmt::Display for MultistreamSelectError { - #[inline] fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(fmt, "{}", error::Error::description(self)) + write!(fmt, "{}", Error::description(self)) } } diff --git a/misc/multistream-select/src/protocol/listener.rs b/misc/multistream-select/src/protocol/listener.rs index 7616e8eac8f..ef1a962a3ea 100644 --- a/misc/multistream-select/src/protocol/listener.rs +++ b/misc/multistream-select/src/protocol/listener.rs @@ -20,23 +20,22 @@ //! Contains the `Listener` wrapper, which allows raw communications with a dialer. +use super::*; + use bytes::{BufMut, Bytes, BytesMut}; use crate::length_delimited::LengthDelimited; -use crate::protocol::DialerToListenerMessage; -use crate::protocol::ListenerToDialerMessage; -use crate::protocol::MultistreamSelectError; -use crate::protocol::MULTISTREAM_PROTOCOL_WITH_LF; +use crate::protocol::{Request, Response, MultistreamSelectError}; use futures::{prelude::*, sink, stream::StreamFuture}; use log::{debug, trace}; -use std::{io, mem}; -use tokio_codec::Encoder; +use std::{marker, mem}; use tokio_io::{AsyncRead, AsyncWrite}; -use unsigned_varint::{encode, codec::Uvi}; +use unsigned_varint as uvi; /// Wraps around a `AsyncRead+AsyncWrite`. Assumes that we're on the listener's side. Produces and /// accepts messages. pub struct Listener { - inner: LengthDelimited> + inner: LengthDelimited, + _protocol_name: marker::PhantomData, } impl Listener @@ -47,10 +46,10 @@ where /// Takes ownership of a socket and starts the handshake. If the handshake succeeds, the /// future returns a `Listener`. pub fn listen(inner: R) -> ListenerFuture { - let codec = MessageEncoder(std::marker::PhantomData); - let inner = LengthDelimited::new(inner, codec); + let inner = LengthDelimited::new(inner); ListenerFuture { - inner: ListenerFutureState::Await { inner: inner.into_future() } + inner: ListenerFutureState::Await { inner: inner.into_future() }, + _protocol_name: marker::PhantomData, } } @@ -67,24 +66,22 @@ where R: AsyncRead + AsyncWrite, N: AsRef<[u8]> { - type SinkItem = ListenerToDialerMessage; + type SinkItem = Response; type SinkError = MultistreamSelectError; - #[inline] fn start_send(&mut self, item: Self::SinkItem) -> StartSend { - match self.inner.start_send(Message::Body(item))? { - AsyncSink::NotReady(Message::Body(item)) => Ok(AsyncSink::NotReady(item)), - AsyncSink::NotReady(Message::Header) => unreachable!(), + let mut msg = BytesMut::new(); + Message::Body(&item).encode(&mut msg)?; + match self.inner.start_send(msg.freeze())? { + AsyncSink::NotReady(_) => Ok(AsyncSink::NotReady(item)), AsyncSink::Ready => Ok(AsyncSink::Ready) } } - #[inline] fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { Ok(self.inner.poll_complete()?) } - #[inline] fn close(&mut self) -> Poll<(), Self::SinkError> { Ok(self.inner.close()?) } @@ -94,26 +91,26 @@ impl Stream for Listener where R: AsyncRead + AsyncWrite, { - type Item = DialerToListenerMessage; + type Item = Request; type Error = MultistreamSelectError; fn poll(&mut self) -> Poll, Self::Error> { - let mut frame = match self.inner.poll() { - Ok(Async::Ready(Some(frame))) => frame, + let mut msg = match self.inner.poll() { + Ok(Async::Ready(Some(msg))) => msg, Ok(Async::Ready(None)) => return Ok(Async::Ready(None)), Ok(Async::NotReady) => return Ok(Async::NotReady), Err(err) => return Err(err.into()), }; - if frame.get(0) == Some(&b'/') && frame.last() == Some(&b'\n') { - let frame_len = frame.len(); - let protocol = frame.split_to(frame_len - 1); + if msg.get(0) == Some(&b'/') && msg.last() == Some(&b'\n') { + let len = msg.len(); + let name = msg.split_to(len - 1); Ok(Async::Ready(Some( - DialerToListenerMessage::ProtocolRequest { name: protocol }, + Request::Protocol { name }, ))) - } else if frame == b"ls\n"[..] { + } else if msg == MSG_LS { Ok(Async::Ready(Some( - DialerToListenerMessage::ProtocolsListRequest, + Request::ListProtocols, ))) } else { Err(MultistreamSelectError::UnknownMessage) @@ -124,16 +121,17 @@ where /// Future, returned by `Listener::new` which performs the handshake and returns /// the `Listener` if successful. -pub struct ListenerFuture> { - inner: ListenerFutureState +pub struct ListenerFuture { + inner: ListenerFutureState, + _protocol_name: marker::PhantomData, } -enum ListenerFutureState> { +enum ListenerFutureState { Await { - inner: StreamFuture>> + inner: StreamFuture> }, Reply { - sender: sink::Send>> + sender: sink::Send> }, Undefined } @@ -155,12 +153,14 @@ impl> Future for ListenerFuture } Err((e, _)) => return Err(MultistreamSelectError::from(e)) }; - if msg.as_ref().map(|b| &b[..]) != Some(MULTISTREAM_PROTOCOL_WITH_LF) { - debug!("failed handshake; received: {:?}", msg); + if msg.as_ref().map(|b| &b[..]) != Some(MSG_MULTISTREAM_1_0) { + debug!("Unexpected message: {:?}", msg); return Err(MultistreamSelectError::FailedHandshake) } trace!("sending back /multistream/ to finish the handshake"); - let sender = socket.send(Message::Header); + let mut frame = BytesMut::new(); + Message::::Header.encode(&mut frame)?; + let sender = socket.send(frame.freeze()); self.inner = ListenerFutureState::Reply { sender } } ListenerFutureState::Reply { mut sender } => { @@ -171,69 +171,57 @@ impl> Future for ListenerFuture return Ok(Async::NotReady) } }; - return Ok(Async::Ready(Listener { inner: listener })) + return Ok(Async::Ready(Listener { + inner: listener, + _protocol_name: marker::PhantomData + })) } - ListenerFutureState::Undefined => panic!("ListenerFutureState::poll called after completion") + ListenerFutureState::Undefined => + panic!("ListenerFutureState::poll called after completion") } } } } -/// tokio-codec `Encoder` handling `ListenerToDialerMessage` values. -struct MessageEncoder(std::marker::PhantomData); - -enum Message { +enum Message<'a, N> { Header, - Body(ListenerToDialerMessage) + Body(&'a Response) } -impl> Encoder for MessageEncoder { - type Item = Message; - type Error = MultistreamSelectError; +impl> Message<'_, N> { - fn encode(&mut self, item: Self::Item, dest: &mut BytesMut) -> Result<(), Self::Error> { - match item { + fn encode(&self, dest: &mut BytesMut) -> Result<(), MultistreamSelectError> { + match self { Message::Header => { - Uvi::::default().encode(MULTISTREAM_PROTOCOL_WITH_LF.len(), dest)?; - dest.reserve(MULTISTREAM_PROTOCOL_WITH_LF.len()); - dest.put(MULTISTREAM_PROTOCOL_WITH_LF); + dest.reserve(MSG_MULTISTREAM_1_0.len()); + dest.put(MSG_MULTISTREAM_1_0); Ok(()) } - Message::Body(ListenerToDialerMessage::ProtocolAck { name }) => { + Message::Body(Response::Protocol { name }) => { if !name.as_ref().starts_with(b"/") { - return Err(MultistreamSelectError::WrongProtocolName) + return Err(MultistreamSelectError::InvalidProtocolName) } let len = name.as_ref().len() + 1; // + 1 for \n - if len > std::u16::MAX as usize { - return Err(io::Error::new(io::ErrorKind::InvalidData, "name too long").into()) - } - Uvi::::default().encode(len, dest)?; dest.reserve(len); dest.put(name.as_ref()); dest.put(&b"\n"[..]); Ok(()) } - Message::Body(ListenerToDialerMessage::ProtocolsListResponse { list }) => { - let mut buf = encode::usize_buffer(); - let mut out_msg = Vec::from(encode::usize(list.len(), &mut buf)); - for e in &list { - if e.as_ref().len() + 1 > std::u16::MAX as usize { - return Err(io::Error::new(io::ErrorKind::InvalidData, "name too long").into()) - } - out_msg.extend(encode::usize(e.as_ref().len() + 1, &mut buf)); // +1 for '\n' - out_msg.extend_from_slice(e.as_ref()); + Message::Body(Response::SupportedProtocols { protocols }) => { + let mut buf = uvi::encode::usize_buffer(); + let mut out_msg = Vec::from(uvi::encode::usize(protocols.len(), &mut buf)); + for p in protocols { + out_msg.extend(uvi::encode::usize(p.as_ref().len() + 1, &mut buf)); // +1 for '\n' + out_msg.extend_from_slice(p.as_ref()); out_msg.push(b'\n') } - let len = encode::usize(out_msg.len(), &mut buf); - dest.reserve(len.len() + out_msg.len()); - dest.put(len); + dest.reserve(out_msg.len()); dest.put(out_msg); Ok(()) } - Message::Body(ListenerToDialerMessage::NotAvailable) => { - Uvi::::default().encode(3, dest)?; - dest.reserve(3); - dest.put(&b"na\n"[..]); + Message::Body(Response::ProtocolNotAvailable) => { + dest.reserve(MSG_PROTOCOL_NA.len()); + dest.put(MSG_PROTOCOL_NA); Ok(()) } } @@ -242,12 +230,12 @@ impl> Encoder for MessageEncoder { #[cfg(test)] mod tests { + use super::*; use tokio::runtime::current_thread::Runtime; use tokio_tcp::{TcpListener, TcpStream}; use bytes::Bytes; use futures::Future; use futures::{Sink, Stream}; - use crate::protocol::{Dialer, Listener, ListenerToDialerMessage, MultistreamSelectError}; #[test] fn wrong_proto_name() { @@ -260,8 +248,8 @@ mod tests { .map_err(|(e, _)| e.into()) .and_then(move |(connec, _)| Listener::listen(connec.unwrap())) .and_then(|listener| { - let proto_name = Bytes::from("invalid-proto"); - listener.send(ListenerToDialerMessage::ProtocolAck { name: proto_name }) + let name = Bytes::from("invalid-proto"); + listener.send(Response::Protocol { name }) }); let client = TcpStream::connect(&listener_addr) @@ -270,7 +258,7 @@ mod tests { let mut rt = Runtime::new().unwrap(); match rt.block_on(server.join(client)) { - Err(MultistreamSelectError::WrongProtocolName) => (), + Err(MultistreamSelectError::InvalidProtocolName) => (), _ => panic!(), } } diff --git a/misc/multistream-select/src/protocol/mod.rs b/misc/multistream-select/src/protocol/mod.rs index 7e840b31c33..738f8872234 100644 --- a/misc/multistream-select/src/protocol/mod.rs +++ b/misc/multistream-select/src/protocol/mod.rs @@ -20,47 +20,49 @@ //! Contains lower-level structs to handle the multistream protocol. +const MSG_MULTISTREAM_1_0: &[u8] = b"/multistream/1.0.0\n"; +const MSG_PROTOCOL_NA: &[u8] = b"na\n"; +const MSG_LS: &[u8] = b"ls\n"; + mod dialer; mod error; mod listener; -const MULTISTREAM_PROTOCOL_WITH_LF: &[u8] = b"/multistream/1.0.0\n"; - pub use self::dialer::{Dialer, DialerFuture}; pub use self::error::MultistreamSelectError; pub use self::listener::{Listener, ListenerFuture}; /// Message sent from the dialer to the listener. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum DialerToListenerMessage { +pub enum Request { /// The dialer wants us to use a protocol. /// /// If this is accepted (by receiving back a `ProtocolAck`), then we immediately start /// communicating in the new protocol. - ProtocolRequest { + Protocol { /// Name of the protocol. name: N }, /// The dialer requested the list of protocols that the listener supports. - ProtocolsListRequest, + ListProtocols, } /// Message sent from the listener to the dialer. #[derive(Debug, Clone, PartialEq, Eq)] -pub enum ListenerToDialerMessage { +pub enum Response { /// The protocol requested by the dialer is accepted. The socket immediately starts using the /// new protocol. - ProtocolAck { name: N }, + Protocol { name: N }, /// The protocol requested by the dialer is not supported or available. - NotAvailable, + ProtocolNotAvailable, /// Response to the request for the list of protocols. - ProtocolsListResponse { + SupportedProtocols { /// The list of protocols. // TODO: use some sort of iterator - list: Vec, + protocols: Vec, }, } diff --git a/misc/multistream-select/src/tests.rs b/misc/multistream-select/src/tests.rs index 0feba85469b..dbfc0588d7e 100644 --- a/misc/multistream-select/src/tests.rs +++ b/misc/multistream-select/src/tests.rs @@ -24,7 +24,7 @@ use crate::ProtocolChoiceError; use crate::dialer_select::{dialer_select_proto_parallel, dialer_select_proto_serial}; -use crate::protocol::{Dialer, DialerToListenerMessage, Listener, ListenerToDialerMessage}; +use crate::protocol::{Dialer, Request, Listener, Response}; use crate::{dialer_select_proto, listener_select_proto}; use futures::prelude::*; use tokio::runtime::current_thread::Runtime; @@ -56,23 +56,23 @@ fn negotiate_with_self_succeeds() { .and_then(|l| l.into_future().map_err(|(e, _)| e)) .and_then(|(msg, rest)| { let proto = match msg { - Some(DialerToListenerMessage::ProtocolRequest { name }) => name, + Some(Request::Protocol { name }) => name, _ => panic!(), }; - rest.send(ListenerToDialerMessage::ProtocolAck { name: proto }) + rest.send(Response::Protocol { name: proto }) }); let client = TcpStream::connect(&listener_addr) .from_err() .and_then(move |stream| Dialer::dial(stream)) .and_then(move |dialer| { - let p = b"/hello/1.0.0"; - dialer.send(DialerToListenerMessage::ProtocolRequest { name: p }) + let name = b"/hello/1.0.0"; + dialer.send(Request::Protocol { name }) }) .and_then(move |dialer| dialer.into_future().map_err(|(e, _)| e)) .and_then(move |(msg, _)| { let proto = match msg { - Some(ListenerToDialerMessage::ProtocolAck { name }) => name, + Some(Response::Protocol { name }) => name, _ => panic!(), }; assert_eq!(proto, "/hello/1.0.0"); From d292aacb7beefebb3774e807d5bd3114f1a36b7a Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Tue, 16 Jul 2019 16:32:42 +0200 Subject: [PATCH 02/14] Reduce default buffer sizes. --- misc/multistream-select/src/length_delimited.rs | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/misc/multistream-select/src/length_delimited.rs b/misc/multistream-select/src/length_delimited.rs index 44a8ff36090..45caaecbc2d 100644 --- a/misc/multistream-select/src/length_delimited.rs +++ b/misc/multistream-select/src/length_delimited.rs @@ -26,6 +26,7 @@ use unsigned_varint as uvi; const MAX_LEN_BYTES: u16 = 2; const MAX_FRAME_SIZE: u16 = (1 << (MAX_LEN_BYTES * 8 - MAX_LEN_BYTES)) - 1; +const DEFAULT_BUFFER_SIZE: usize = 64; /// `Stream` and `Sink` wrapping some `AsyncRead + AsyncWrite` resource to read /// and write unsigned-varint prefixed frames. @@ -69,8 +70,8 @@ impl LengthDelimited { LengthDelimited { inner, read_state: ReadState::default(), - read_buffer: BytesMut::with_capacity(MAX_FRAME_SIZE as usize), - write_buffer: BytesMut::with_capacity((MAX_FRAME_SIZE + MAX_LEN_BYTES) as usize), + read_buffer: BytesMut::with_capacity(DEFAULT_BUFFER_SIZE), + write_buffer: BytesMut::with_capacity(DEFAULT_BUFFER_SIZE + MAX_LEN_BYTES as usize), } } @@ -191,8 +192,11 @@ where "Maximum frame size exceeded.")) } - self.write_buffer.put_slice(uvi::encode::u16(len, &mut [0; 3])); - self.write_buffer.extend(msg); + let mut uvi_buf = uvi::encode::u16_buffer(); + let uvi_len = uvi::encode::u16(len, &mut uvi_buf); + self.write_buffer.reserve(len as usize + uvi_len.len()); + self.write_buffer.put(uvi_len); + self.write_buffer.put(msg); Ok(AsyncSink::Ready) } From 402f1a25cfbee7b5a3ea7b25016073a093654994 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Tue, 16 Jul 2019 16:54:20 +0200 Subject: [PATCH 03/14] Allow more than one frame to be buffered for sending. --- misc/multistream-select/src/length_delimited.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/misc/multistream-select/src/length_delimited.rs b/misc/multistream-select/src/length_delimited.rs index 45caaecbc2d..51493c33888 100644 --- a/misc/multistream-select/src/length_delimited.rs +++ b/misc/multistream-select/src/length_delimited.rs @@ -178,9 +178,12 @@ where type SinkError = io::Error; fn start_send(&mut self, msg: Self::SinkItem) -> StartSend { - if !self.write_buffer.is_empty() { + // Use the maximum frame length also as a (soft) upper limit + // for the entire write buffer. The actual (hard) limit is thus + // implied to be roughly 2 * MAX_FRAME_SIZE. + if self.write_buffer.len() >= MAX_FRAME_SIZE as usize { self.poll_complete()?; - if !self.write_buffer.is_empty() { + if self.write_buffer.len() >= MAX_FRAME_SIZE as usize { return Ok(AsyncSink::NotReady(msg)) } } From ae2f5099d4a7b6ea01c6355f9f7bc0cbbb248a7a Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Tue, 16 Jul 2019 17:07:37 +0200 Subject: [PATCH 04/14] Doc tweaks. --- misc/multistream-select/src/length_delimited.rs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/misc/multistream-select/src/length_delimited.rs b/misc/multistream-select/src/length_delimited.rs index 51493c33888..4d4f2c02bb4 100644 --- a/misc/multistream-select/src/length_delimited.rs +++ b/misc/multistream-select/src/length_delimited.rs @@ -37,9 +37,9 @@ const DEFAULT_BUFFER_SIZE: usize = 64; pub struct LengthDelimited { /// The inner I/O resource. inner: R, - /// Read buffer for a single unsigned-varint length-delimited frame. + /// Read buffer for a single incoming unsigned-varint length-delimited frame. read_buffer: BytesMut, - /// Write buffer for a single unsigned-varint length-delimited frame. + /// Write buffer for outgoing unsigned-varint length-delimited frames. write_buffer: BytesMut, /// The current read state, alternating between reading a frame /// length and reading a frame payload. @@ -77,15 +77,14 @@ impl LengthDelimited { /// Destroys the `LengthDelimited` and returns the underlying socket. /// - /// Contrary to its equivalent `tokio_io::codec::length_delimited::FramedRead`, this method is - /// guaranteed not to skip any data from the socket. + /// This method is guaranteed not to skip any data from the socket. /// /// # Panic /// - /// Will panic if called while there is data inside the buffer. **This can only happen if - /// you call `poll()` manually**. Using this struct as it is intended to be used (i.e. through - /// the modifiers provided by the `futures` crate) will always leave the object in a state in - /// which `into_inner()` will not panic. + /// Will panic if called while there is data inside the read or write buffer. + /// **This can only happen if you call `poll()` manually**. Using this struct + /// as it is intended to be used (i.e. through the high-level `futures` API) + /// will always leave the object in a state in which `into_inner()` will not panic. pub fn into_inner(self) -> R { assert!(self.write_buffer.is_empty()); assert!(self.read_buffer.is_empty()); From 05e33338ba1a81444e1770114cdae9002010237e Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Wed, 17 Jul 2019 15:35:25 +0200 Subject: [PATCH 05/14] Remove superfluous (duplicated) Message types. --- .../multistream-select/src/protocol/dialer.rs | 46 ++--------- .../src/protocol/listener.rs | 57 ++------------ misc/multistream-select/src/protocol/mod.rs | 76 +++++++++++++++++++ 3 files changed, 88 insertions(+), 91 deletions(-) diff --git a/misc/multistream-select/src/protocol/dialer.rs b/misc/multistream-select/src/protocol/dialer.rs index d2d732a46ac..28da191a490 100644 --- a/misc/multistream-select/src/protocol/dialer.rs +++ b/misc/multistream-select/src/protocol/dialer.rs @@ -22,7 +22,7 @@ use super::*; -use bytes::{BufMut, Bytes, BytesMut}; +use bytes::{Bytes, BytesMut}; use crate::length_delimited::LengthDelimited; use crate::protocol::{Request, Response, MultistreamSelectError}; use futures::{prelude::*, sink, Async, StartSend, try_ready}; @@ -47,11 +47,11 @@ where N: AsRef<[u8]> { pub fn dial(inner: R) -> DialerFuture { - let sender = LengthDelimited::new(inner); + let io = LengthDelimited::new(inner); let mut buf = BytesMut::new(); - let _ = Message::::Header.encode(&mut buf); + Header::Multistream10.encode(&mut buf); DialerFuture { - inner: sender.send(buf.freeze()), + inner: io.send(buf.freeze()), _protocol_name: marker::PhantomData, } } @@ -70,11 +70,11 @@ where type SinkItem = Request; type SinkError = MultistreamSelectError; - fn start_send(&mut self, item: Self::SinkItem) -> StartSend { + fn start_send(&mut self, request: Self::SinkItem) -> StartSend { let mut msg = BytesMut::new(); - Message::Body(&item).encode(&mut msg)?; + request.encode(&mut msg)?; match self.inner.start_send(msg.freeze())? { - AsyncSink::NotReady(_) => Ok(AsyncSink::NotReady(item)), + AsyncSink::NotReady(_) => Ok(AsyncSink::NotReady(request)), AsyncSink::Ready => Ok(AsyncSink::Ready), } } @@ -164,38 +164,6 @@ impl> Future for DialerFuture { } } -enum Message<'a, N> { - Header, - Body(&'a Request) -} - -impl> Message<'_, N> { - fn encode(&self, dest: &mut BytesMut) -> Result<(), MultistreamSelectError> { - match self { - Message::Header => { - dest.reserve(MSG_MULTISTREAM_1_0.len()); - dest.put(MSG_MULTISTREAM_1_0); - Ok(()) - } - Message::Body(Request::Protocol { name }) => { - if !name.as_ref().starts_with(b"/") { - return Err(MultistreamSelectError::InvalidProtocolName) - } - let len = name.as_ref().len() + 1; // + 1 for \n - dest.reserve(len); - dest.put(name.as_ref()); - dest.put(&b"\n"[..]); - Ok(()) - } - Message::Body(Request::ListProtocols) => { - dest.reserve(MSG_LS.len()); - dest.put(MSG_LS); - Ok(()) - } - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/misc/multistream-select/src/protocol/listener.rs b/misc/multistream-select/src/protocol/listener.rs index ef1a962a3ea..243304edcff 100644 --- a/misc/multistream-select/src/protocol/listener.rs +++ b/misc/multistream-select/src/protocol/listener.rs @@ -22,14 +22,13 @@ use super::*; -use bytes::{BufMut, Bytes, BytesMut}; +use bytes::{Bytes, BytesMut}; use crate::length_delimited::LengthDelimited; use crate::protocol::{Request, Response, MultistreamSelectError}; use futures::{prelude::*, sink, stream::StreamFuture}; use log::{debug, trace}; use std::{marker, mem}; use tokio_io::{AsyncRead, AsyncWrite}; -use unsigned_varint as uvi; /// Wraps around a `AsyncRead+AsyncWrite`. Assumes that we're on the listener's side. Produces and /// accepts messages. @@ -55,7 +54,6 @@ where /// Grants back the socket. Typically used after a `ProtocolRequest` has been received and a /// `ProtocolAck` has been sent back. - #[inline] pub fn into_inner(self) -> R { self.inner.into_inner() } @@ -69,11 +67,11 @@ where type SinkItem = Response; type SinkError = MultistreamSelectError; - fn start_send(&mut self, item: Self::SinkItem) -> StartSend { + fn start_send(&mut self, response: Self::SinkItem) -> StartSend { let mut msg = BytesMut::new(); - Message::Body(&item).encode(&mut msg)?; + response.encode(&mut msg)?; match self.inner.start_send(msg.freeze())? { - AsyncSink::NotReady(_) => Ok(AsyncSink::NotReady(item)), + AsyncSink::NotReady(_) => Ok(AsyncSink::NotReady(response)), AsyncSink::Ready => Ok(AsyncSink::Ready) } } @@ -159,7 +157,7 @@ impl> Future for ListenerFuture } trace!("sending back /multistream/ to finish the handshake"); let mut frame = BytesMut::new(); - Message::::Header.encode(&mut frame)?; + Header::Multistream10.encode(&mut frame); let sender = socket.send(frame.freeze()); self.inner = ListenerFutureState::Reply { sender } } @@ -183,51 +181,6 @@ impl> Future for ListenerFuture } } -enum Message<'a, N> { - Header, - Body(&'a Response) -} - -impl> Message<'_, N> { - - fn encode(&self, dest: &mut BytesMut) -> Result<(), MultistreamSelectError> { - match self { - Message::Header => { - dest.reserve(MSG_MULTISTREAM_1_0.len()); - dest.put(MSG_MULTISTREAM_1_0); - Ok(()) - } - Message::Body(Response::Protocol { name }) => { - if !name.as_ref().starts_with(b"/") { - return Err(MultistreamSelectError::InvalidProtocolName) - } - let len = name.as_ref().len() + 1; // + 1 for \n - dest.reserve(len); - dest.put(name.as_ref()); - dest.put(&b"\n"[..]); - Ok(()) - } - Message::Body(Response::SupportedProtocols { protocols }) => { - let mut buf = uvi::encode::usize_buffer(); - let mut out_msg = Vec::from(uvi::encode::usize(protocols.len(), &mut buf)); - for p in protocols { - out_msg.extend(uvi::encode::usize(p.as_ref().len() + 1, &mut buf)); // +1 for '\n' - out_msg.extend_from_slice(p.as_ref()); - out_msg.push(b'\n') - } - dest.reserve(out_msg.len()); - dest.put(out_msg); - Ok(()) - } - Message::Body(Response::ProtocolNotAvailable) => { - dest.reserve(MSG_PROTOCOL_NA.len()); - dest.put(MSG_PROTOCOL_NA); - Ok(()) - } - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/misc/multistream-select/src/protocol/mod.rs b/misc/multistream-select/src/protocol/mod.rs index 738f8872234..5b1fca7153b 100644 --- a/misc/multistream-select/src/protocol/mod.rs +++ b/misc/multistream-select/src/protocol/mod.rs @@ -32,6 +32,24 @@ pub use self::dialer::{Dialer, DialerFuture}; pub use self::error::MultistreamSelectError; pub use self::listener::{Listener, ListenerFuture}; +use bytes::{BytesMut, BufMut}; +use unsigned_varint as uvi; + +pub enum Header { + Multistream10 +} + +impl Header { + fn encode(&self, dest: &mut BytesMut) { + match self { + Header::Multistream10 => { + dest.reserve(MSG_MULTISTREAM_1_0.len()); + dest.put(MSG_MULTISTREAM_1_0); + } + } + } +} + /// Message sent from the dialer to the listener. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Request { @@ -48,6 +66,29 @@ pub enum Request { ListProtocols, } +impl> Request { + fn encode(&self, dest: &mut BytesMut) -> Result<(), MultistreamSelectError> { + match self { + Request::Protocol { name } => { + if !name.as_ref().starts_with(b"/") { + return Err(MultistreamSelectError::InvalidProtocolName) + } + let len = name.as_ref().len() + 1; // + 1 for \n + dest.reserve(len); + dest.put(name.as_ref()); + dest.put(&b"\n"[..]); + Ok(()) + } + Request::ListProtocols => { + dest.reserve(MSG_LS.len()); + dest.put(MSG_LS); + Ok(()) + } + } + } +} + + /// Message sent from the listener to the dialer. #[derive(Debug, Clone, PartialEq, Eq)] pub enum Response { @@ -66,3 +107,38 @@ pub enum Response { }, } +impl> Response { + fn encode(&self, dest: &mut BytesMut) -> Result<(), MultistreamSelectError> { + match self { + Response::Protocol { name } => { + if !name.as_ref().starts_with(b"/") { + return Err(MultistreamSelectError::InvalidProtocolName) + } + let len = name.as_ref().len() + 1; // + 1 for \n + dest.reserve(len); + dest.put(name.as_ref()); + dest.put(&b"\n"[..]); + Ok(()) + } + Response::SupportedProtocols { protocols } => { + let mut buf = uvi::encode::usize_buffer(); + let mut out_msg = Vec::from(uvi::encode::usize(protocols.len(), &mut buf)); + for p in protocols { + out_msg.extend(uvi::encode::usize(p.as_ref().len() + 1, &mut buf)); // +1 for '\n' + out_msg.extend_from_slice(p.as_ref()); + out_msg.push(b'\n') + } + dest.reserve(out_msg.len()); + dest.put(out_msg); + Ok(()) + } + Response::ProtocolNotAvailable => { + dest.reserve(MSG_PROTOCOL_NA.len()); + dest.put(MSG_PROTOCOL_NA); + Ok(()) + } + } + } +} + + From 9855c8525fc621f365bf35ace0e2d54825a25207 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Fri, 19 Jul 2019 20:31:52 +0200 Subject: [PATCH 06/14] Reduce roundtrips in multistream-select negotiation. 1. Enable 0-RTT: If the dialer only supports a single protocol, it can send protocol data (e.g. the actual application request) together with the multistream-select header and protocol proposal. Similarly, if the listener supports a proposed protocol, it can send protocol data (e.g. the actual application response) together with the multistream-select header and protocol confirmation. 2. In general, the dialer "settles on" an expected protocol as soon as it runs out of alternatives. Furthermore, both dialer and listener do not immediately flush the final protocol confirmation, allowing it to be sent together with application protocol data. Attempts to read from the negotiated I/O stream implicitly flushes any pending data. 3. A clean / graceful shutdown of an I/O stream always completes protocol negotiation. The publich API of multistream-select changed slightly, requiring both AsyncRead and AsyncWrite bounds for async reading and writing due to the implicit buffering and "lazy" negotiation. The error types have also been changed, but they were not previously fully exported. Includes some general refactoring with simplifications and some more tests, e.g. there was an edge case relating to a possible ambiguity when parsing multistream-select protocol messages. --- core/Cargo.toml | 1 + core/src/upgrade/apply.rs | 39 +- core/src/upgrade/error.rs | 8 +- core/src/upgrade/mod.rs | 2 +- core/tests/network_dial_error.rs | 13 +- core/tests/network_simult.rs | 16 +- core/tests/util.rs | 47 ++ misc/multistream-select/Cargo.toml | 2 + misc/multistream-select/src/dialer_select.rs | 478 +++++++---------- misc/multistream-select/src/error.rs | 54 +- .../src/length_delimited.rs | 165 +++++- misc/multistream-select/src/lib.rs | 103 ++-- .../multistream-select/src/listener_select.rs | 257 +++++---- misc/multistream-select/src/negotiated.rs | 362 +++++++++++++ misc/multistream-select/src/protocol.rs | 486 ++++++++++++++++++ .../multistream-select/src/protocol/dialer.rs | 200 ------- misc/multistream-select/src/protocol/error.rs | 87 ---- .../src/protocol/listener.rs | 218 -------- misc/multistream-select/src/protocol/mod.rs | 144 ------ misc/multistream-select/src/tests.rs | 111 ++-- muxers/mplex/src/lib.rs | 2 +- protocols/floodsub/src/protocol.rs | 4 +- protocols/kad/src/handler.rs | 11 +- 23 files changed, 1495 insertions(+), 1315 deletions(-) create mode 100644 core/tests/util.rs create mode 100644 misc/multistream-select/src/negotiated.rs create mode 100644 misc/multistream-select/src/protocol.rs delete mode 100644 misc/multistream-select/src/protocol/dialer.rs delete mode 100644 misc/multistream-select/src/protocol/error.rs delete mode 100644 misc/multistream-select/src/protocol/listener.rs delete mode 100644 misc/multistream-select/src/protocol/mod.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index 1c47945c8c5..bf34750a5a3 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -45,6 +45,7 @@ untrusted = { version = "0.6" } libp2p-swarm = { version = "0.1.0", path = "../swarm" } libp2p-tcp = { version = "0.11.0", path = "../transports/tcp" } libp2p-mplex = { version = "0.11.0", path = "../muxers/mplex" } +libp2p-yamux = { version = "0.11.0", path = "../muxers/yamux" } libp2p-secio = { version = "0.11.0", path = "../protocols/secio" } rand = "0.6" quickcheck = "0.8" diff --git a/core/src/upgrade/apply.rs b/core/src/upgrade/apply.rs index d9a3e7a1fe2..982cd0295d5 100644 --- a/core/src/upgrade/apply.rs +++ b/core/src/upgrade/apply.rs @@ -19,11 +19,11 @@ // DEALINGS IN THE SOFTWARE. use crate::ConnectedPoint; -use crate::upgrade::{UpgradeInfo, InboundUpgrade, OutboundUpgrade, UpgradeError, ProtocolName}; +use crate::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeError, ProtocolName}; use futures::{future::Either, prelude::*}; use log::debug; use multistream_select::{self, DialerSelectFuture, ListenerSelectFuture}; -use std::mem; +use std::{iter, mem}; use tokio_io::{AsyncRead, AsyncWrite}; /// Applies an upgrade to the inbound and outbound direction of a connection or substream. @@ -46,10 +46,10 @@ where C: AsyncRead + AsyncWrite, U: InboundUpgrade, { - let iter = UpgradeInfoIterWrap(up); + let iter = up.protocol_info().into_iter().map(NameWrap as fn(_) -> NameWrap<_>); let future = multistream_select::listener_select_proto(conn, iter); InboundUpgradeApply { - inner: InboundUpgradeApplyState::Init { future } + inner: InboundUpgradeApplyState::Init { future, upgrade: up } } } @@ -78,10 +78,11 @@ where enum InboundUpgradeApplyState where C: AsyncRead + AsyncWrite, - U: InboundUpgrade + U: InboundUpgrade, { Init { - future: ListenerSelectFuture, NameWrap>, + future: ListenerSelectFuture>, + upgrade: U, }, Upgrade { future: U::Future @@ -100,16 +101,16 @@ where fn poll(&mut self) -> Poll { loop { match mem::replace(&mut self.inner, InboundUpgradeApplyState::Undefined) { - InboundUpgradeApplyState::Init { mut future } => { - let (info, connection, upgrade) = match future.poll()? { + InboundUpgradeApplyState::Init { mut future, upgrade } => { + let (info, connection) = match future.poll()? { Async::Ready(x) => x, Async::NotReady => { - self.inner = InboundUpgradeApplyState::Init { future }; + self.inner = InboundUpgradeApplyState::Init { future, upgrade }; return Ok(Async::NotReady) } }; self.inner = InboundUpgradeApplyState::Upgrade { - future: upgrade.0.upgrade_inbound(connection, info.0) + future: upgrade.upgrade_inbound(connection, info.0) }; } InboundUpgradeApplyState::Upgrade { mut future } => { @@ -205,23 +206,7 @@ where } } -/// Wraps around a `UpgradeInfo` and satisfies the requirement of `listener_select_proto`. -struct UpgradeInfoIterWrap(U); - -impl<'a, U> IntoIterator for &'a UpgradeInfoIterWrap -where - U: UpgradeInfo -{ - type Item = NameWrap; - type IntoIter = NameWrapIter<::IntoIter>; - - fn into_iter(self) -> Self::IntoIter { - self.0.protocol_info().into_iter().map(NameWrap) - } -} - -type NameWrapIter = - std::iter::Map::Item) -> NameWrap<::Item>>; +type NameWrapIter = iter::Map::Item) -> NameWrap<::Item>>; /// Wrapper type to expose an `AsRef<[u8]>` impl for all types implementing `ProtocolName`. #[derive(Clone)] diff --git a/core/src/upgrade/error.rs b/core/src/upgrade/error.rs index 6dd3082e0cb..de0ecadbd51 100644 --- a/core/src/upgrade/error.rs +++ b/core/src/upgrade/error.rs @@ -18,14 +18,14 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use multistream_select::ProtocolChoiceError; +use multistream_select::NegotiationError; use std::fmt; /// Error that can happen when upgrading a connection or substream to use a protocol. #[derive(Debug)] pub enum UpgradeError { /// Error during the negotiation process. - Select(ProtocolChoiceError), + Select(NegotiationError), /// Error during the post-negotiation handshake. Apply(E), } @@ -73,8 +73,8 @@ where } } -impl From for UpgradeError { - fn from(e: ProtocolChoiceError) -> Self { +impl From for UpgradeError { + fn from(e: NegotiationError) -> Self { UpgradeError::Select(e) } } diff --git a/core/src/upgrade/mod.rs b/core/src/upgrade/mod.rs index 6a40d211969..4d26dbe9a3f 100644 --- a/core/src/upgrade/mod.rs +++ b/core/src/upgrade/mod.rs @@ -68,7 +68,7 @@ mod transfer; use futures::future::Future; -pub use multistream_select::Negotiated; +pub use multistream_select::{Negotiated, NegotiationError, ProtocolError}; pub use self::{ apply::{apply, apply_inbound, apply_outbound, InboundUpgradeApply, OutboundUpgradeApply}, denied::DeniedUpgrade, diff --git a/core/tests/network_dial_error.rs b/core/tests/network_dial_error.rs index 5484168d1d1..e6e3db0ca1c 100644 --- a/core/tests/network_dial_error.rs +++ b/core/tests/network_dial_error.rs @@ -18,6 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +mod util; + use futures::{future, prelude::*}; use libp2p_core::identity; use libp2p_core::multiaddr::multiaddr; @@ -167,6 +169,7 @@ fn deny_incoming_connec() { #[test] fn dial_self() { + // Check whether dialing ourselves correctly fails. // // Dialing the same address we're listening should result in three events: @@ -191,7 +194,13 @@ fn dial_self() { .map_outbound(move |muxer| (peer_id, muxer)) .map_inbound(move |muxer| (peer_id2, muxer)); upgrade::apply(out.stream, upgrade, endpoint) + }) + .and_then(|(peer, mplex), _| { + // Gracefully close the connection to allow protocol + // negotiation to complete. + util::CloseMuxer::new(mplex).map(move |mplex| (peer, mplex)) }); + Network::new(transport, local_public_key.into()) }; @@ -243,7 +252,9 @@ fn dial_self() { assert_eq!(*inc.listen_addr(), address); inc.accept(TestHandler::default().into_node_handler_builder()); }, - Async::Ready(ev) => unreachable!("{:?}", ev), + Async::Ready(ev) => { + panic!("Unexpected event: {:?}", ev) + } Async::NotReady => break Ok(Async::NotReady), } } diff --git a/core/tests/network_simult.rs b/core/tests/network_simult.rs index cc9ebdfec80..525b6ccf29b 100644 --- a/core/tests/network_simult.rs +++ b/core/tests/network_simult.rs @@ -18,6 +18,8 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. +mod util; + use futures::{future, prelude::*}; use libp2p_core::identity; use libp2p_core::nodes::network::{Network, NetworkEvent, IncomingError}; @@ -118,6 +120,11 @@ fn raw_swarm_simultaneous_connect() { .map_outbound(move |muxer| (peer_id, muxer)) .map_inbound(move |muxer| (peer_id2, muxer)); upgrade::apply(out.stream, upgrade, endpoint) + }) + .and_then(|(peer, mplex), _| { + // Gracefully close the connection to allow protocol + // negotiation to complete. + util::CloseMuxer::new(mplex).map(move |mplex| (peer, mplex)) }); Network::new(transport, local_public_key.into_peer_id()) }; @@ -134,6 +141,11 @@ fn raw_swarm_simultaneous_connect() { .map_outbound(move |muxer| (peer_id, muxer)) .map_inbound(move |muxer| (peer_id2, muxer)); upgrade::apply(out.stream, upgrade, endpoint) + }) + .and_then(|(peer, mplex), _| { + // Gracefully close the connection to allow protocol + // negotiation to complete. + util::CloseMuxer::new(mplex).map(move |mplex| (peer, mplex)) }); Network::new(transport, local_public_key.into_peer_id()) }; @@ -224,7 +236,7 @@ fn raw_swarm_simultaneous_connect() { Async::Ready(NetworkEvent::IncomingConnection(inc)) => { inc.accept(TestHandler::default().into_node_handler_builder()); }, - Async::Ready(_) => unreachable!(), + Async::Ready(ev) => panic!("swarm1: unexpected event: {:?}", ev), Async::NotReady => swarm1_not_ready = true, } } @@ -248,7 +260,7 @@ fn raw_swarm_simultaneous_connect() { Async::Ready(NetworkEvent::IncomingConnection(inc)) => { inc.accept(TestHandler::default().into_node_handler_builder()); }, - Async::Ready(_) => unreachable!(), + Async::Ready(ev) => panic!("swarm2: unexpected event: {:?}", ev), Async::NotReady => swarm2_not_ready = true, } } diff --git a/core/tests/util.rs b/core/tests/util.rs new file mode 100644 index 00000000000..b43442822cb --- /dev/null +++ b/core/tests/util.rs @@ -0,0 +1,47 @@ + +#![allow(dead_code)] + +use futures::prelude::*; +use libp2p_core::muxing::StreamMuxer; + +pub struct CloseMuxer { + state: CloseMuxerState, +} + +impl CloseMuxer { + pub fn new(m: M) -> CloseMuxer { + CloseMuxer { + state: CloseMuxerState::Close(m) + } + } +} + +pub enum CloseMuxerState { + Close(M), + Done, +} + +impl Future for CloseMuxer +where + M: StreamMuxer, + M::Error: From +{ + type Item = M; + type Error = M::Error; + + fn poll(&mut self) -> Poll { + loop { + match std::mem::replace(&mut self.state, CloseMuxerState::Done) { + CloseMuxerState::Close(muxer) => { + if muxer.close()?.is_not_ready() { + self.state = CloseMuxerState::Close(muxer); + return Ok(Async::NotReady) + } + return Ok(Async::Ready(muxer)) + } + CloseMuxerState::Done => panic!() + } + } + } +} + diff --git a/misc/multistream-select/Cargo.toml b/misc/multistream-select/Cargo.toml index 83de968e89d..b7a9ab0cafe 100644 --- a/misc/multistream-select/Cargo.toml +++ b/misc/multistream-select/Cargo.toml @@ -20,3 +20,5 @@ unsigned-varint = { version = "0.2.2" } [dev-dependencies] tokio = "0.1" tokio-tcp = "0.1" +quickcheck = "0.8" +rand = "0.6" diff --git a/misc/multistream-select/src/dialer_select.rs b/misc/multistream-select/src/dialer_select.rs index 59bda8ad396..991e57b681f 100644 --- a/misc/multistream-select/src/dialer_select.rs +++ b/misc/multistream-select/src/dialer_select.rs @@ -18,31 +18,29 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Contains the `dialer_select_proto` code, which allows selecting a protocol thanks to -//! `multistream-select` for the dialer. +//! Protocol negotiation strategies for the peer acting as the dialer. -use futures::{future::Either, prelude::*, stream::StreamFuture}; -use crate::protocol::{Dialer, DialerFuture, Request, Response}; -use log::trace; -use std::mem; +use crate::protocol::{Protocol, ProtocolError, MessageIO, Message, Version}; +use futures::{future::Either, prelude::*}; +use log::debug; +use std::{io, iter, mem, convert::TryFrom}; use tokio_io::{AsyncRead, AsyncWrite}; -use crate::{Negotiated, ProtocolChoiceError}; +use crate::{Negotiated, NegotiationError}; -/// Future, returned by `dialer_select_proto`, which selects a protocol and dialer -/// either sequentially or by considering all protocols in parallel. -pub type DialerSelectFuture = Either, DialerSelectPar>; - -/// Helps selecting a protocol amongst the ones supported. +/// Returns a `Future` that negotiates a protocol on the given I/O stream +/// for a peer acting as the _dialer_ (or _initiator_). /// -/// This function expects a socket and a list of protocols. It uses the `multistream-select` -/// protocol to choose with the remote a protocol amongst the ones produced by the iterator. +/// This function is given an I/O stream and a list of protocols and returns a +/// computation that performs the protocol negotiation with the remote. The +/// returned `Future` resolves with the name of the negotiated protocol and +/// a [`Negotiated`] I/O stream. /// -/// The iterator must produce a tuple of a protocol name advertised to the remote, a function that -/// checks whether a protocol name matches the protocol, and a protocol "identifier" of type `P` -/// (you decide what `P` is). The parameters of the match function are the name proposed by the -/// remote, and the protocol name that we passed (so that you don't have to clone the name). On -/// success, the function returns the identifier (of type `P`), plus the socket which now uses that -/// chosen protocol. +/// The chosen message flow for protocol negotiation depends on the numbers +/// of supported protocols given. That is, this function delegates to +/// [`dialer_select_proto_serial`] or [`dialer_select_proto_parallel`] +/// based on the number of protocols given. The number of protocols is +/// determined through the `size_hint` of the given iterator and thus +/// an inaccurate size estimate may result in a suboptimal choice. pub fn dialer_select_proto(inner: R, protocols: I) -> DialerSelectFuture where R: AsyncRead + AsyncWrite, @@ -58,371 +56,261 @@ where } } -/// Helps selecting a protocol amongst the ones supported. +/// Future, returned by `dialer_select_proto`, which selects a protocol and dialer +/// either trying protocols in-order, or by requesting all protocols supported +/// by the remote upfront, from which the first protocol found in the dialer's +/// list of protocols is selected. +pub type DialerSelectFuture = Either, DialerSelectPar>; + +/// Returns a `Future` that negotiates a protocol on the given I/O stream. /// -/// Same as `dialer_select_proto`. Tries protocols one by one. The iterator doesn't need to produce -/// match functions, because it's not needed. +/// Just like [`dialer_select_proto`] but always using an iterative message flow, +/// trying the given list of supported protocols one-by-one. +/// +/// This strategy is preferable if the dialer only supports a few protocols. pub fn dialer_select_proto_serial(inner: R, protocols: I) -> DialerSelectSeq where R: AsyncRead + AsyncWrite, I: IntoIterator, I::Item: AsRef<[u8]> { - let protocols = protocols.into_iter(); + let protocols = protocols.into_iter().peekable(); DialerSelectSeq { - inner: DialerSelectSeqState::AwaitDialer { - dialer_fut: Dialer::dial(inner), - protocols + protocols, + state: SeqState::SendHeader { + io: MessageIO::new(inner) } } } +/// Returns a `Future` that negotiates a protocol on the given I/O stream. +/// +/// Just like [`dialer_select_proto`] but always using a message flow that first +/// requests all supported protocols from the remote, selecting the first +/// protocol from the given list of supported protocols that is supported +/// by the remote. +/// +/// This strategy may be beneficial if the dialer supports many protocols +/// and it is unclear whether the remote supports one of the first few. +pub fn dialer_select_proto_parallel(inner: R, protocols: I) -> DialerSelectPar +where + R: AsyncRead + AsyncWrite, + I: IntoIterator, + I::Item: AsRef<[u8]> +{ + let protocols = protocols.into_iter(); + DialerSelectPar { + protocols, + state: ParState::SendHeader { + io: MessageIO::new(inner) + } + } +} -/// Future, returned by `dialer_select_proto_serial` which selects a protocol -/// and dialer sequentially. +/// A `Future` returned by [`dialer_select_proto_serial`] which negotiates +/// a protocol iteratively by considering one protocol after the other. pub struct DialerSelectSeq where R: AsyncRead + AsyncWrite, I: Iterator, I::Item: AsRef<[u8]> { - inner: DialerSelectSeqState + // TODO: It would be nice if eventually N = I::Item = Protocol. + protocols: iter::Peekable, + state: SeqState } -enum DialerSelectSeqState +enum SeqState where R: AsyncRead + AsyncWrite, - I: Iterator, - I::Item: AsRef<[u8]> + N: AsRef<[u8]> { - AwaitDialer { - dialer_fut: DialerFuture, - protocols: I - }, - NextProtocol { - dialer: Dialer, - proto_name: I::Item, - protocols: I - }, - FlushProtocol { - dialer: Dialer, - proto_name: I::Item, - protocols: I - }, - AwaitProtocol { - stream: StreamFuture>, - proto_name: I::Item, - protocols: I - }, - Undefined + SendHeader { io: MessageIO, }, + SendProtocol { io: MessageIO, protocol: N }, + FlushProtocol { io: MessageIO, protocol: N }, + AwaitProtocol { io: MessageIO, protocol: N }, + Done } impl Future for DialerSelectSeq where R: AsyncRead + AsyncWrite, I: Iterator, - I::Item: AsRef<[u8]> + Clone + I::Item: AsRef<[u8]> { type Item = (I::Item, Negotiated); - type Error = ProtocolChoiceError; + type Error = NegotiationError; fn poll(&mut self) -> Poll { loop { - match mem::replace(&mut self.inner, DialerSelectSeqState::Undefined) { - DialerSelectSeqState::AwaitDialer { mut dialer_fut, mut protocols } => { - let dialer = match dialer_fut.poll()? { - Async::Ready(d) => d, - Async::NotReady => { - self.inner = DialerSelectSeqState::AwaitDialer { dialer_fut, protocols }; - return Ok(Async::NotReady) - } - }; - let proto_name = protocols.next().ok_or(ProtocolChoiceError::NoProtocolFound)?; - self.inner = DialerSelectSeqState::NextProtocol { - dialer, - protocols, - proto_name + match mem::replace(&mut self.state, SeqState::Done) { + SeqState::SendHeader { mut io } => { + if io.start_send(Message::Header(Version::V1))?.is_not_ready() { + self.state = SeqState::SendHeader { io }; + return Ok(Async::NotReady) } + let protocol = self.protocols.next().ok_or(NegotiationError::Failed)?; + self.state = SeqState::SendProtocol { io, protocol }; } - DialerSelectSeqState::NextProtocol { mut dialer, protocols, proto_name } => { - trace!("sending {:?}", proto_name.as_ref()); - let req = Request::Protocol { name: proto_name.clone() }; - match dialer.start_send(req)? { - AsyncSink::Ready => { - self.inner = DialerSelectSeqState::FlushProtocol { - dialer, - proto_name, - protocols - } - } - AsyncSink::NotReady(_) => { - self.inner = DialerSelectSeqState::NextProtocol { - dialer, - protocols, - proto_name - }; - return Ok(Async::NotReady) - } + SeqState::SendProtocol { mut io, protocol } => { + let p = Protocol::try_from(protocol.as_ref())?; + if io.start_send(Message::Protocol(p.clone()))?.is_not_ready() { + self.state = SeqState::SendProtocol { io, protocol }; + return Ok(Async::NotReady) + } + debug!("Dialer: Proposed protocol: {}", p); + if self.protocols.peek().is_some() { + self.state = SeqState::FlushProtocol { io, protocol } + } else { + debug!("Dialer: Expecting proposed protocol: {}", p); + let io = Negotiated::expecting(io.into_reader(), p); + return Ok(Async::Ready((protocol, io))) } } - DialerSelectSeqState::FlushProtocol { mut dialer, proto_name, protocols } => { - match dialer.poll_complete()? { - Async::Ready(()) => { - let stream = dialer.into_future(); - self.inner = DialerSelectSeqState::AwaitProtocol { - stream, - proto_name, - protocols - } - } - Async::NotReady => { - self.inner = DialerSelectSeqState::FlushProtocol { - dialer, - proto_name, - protocols - }; - return Ok(Async::NotReady) - } + SeqState::FlushProtocol { mut io, protocol } => { + if io.poll_complete()?.is_not_ready() { + self.state = SeqState::FlushProtocol { io, protocol }; + return Ok(Async::NotReady) } + self.state = SeqState::AwaitProtocol { io, protocol } } - DialerSelectSeqState::AwaitProtocol { mut stream, proto_name, mut protocols } => { - let (m, r) = match stream.poll() { - Ok(Async::Ready(x)) => x, - Ok(Async::NotReady) => { - self.inner = DialerSelectSeqState::AwaitProtocol { - stream, - proto_name, - protocols - }; + SeqState::AwaitProtocol { mut io, protocol } => { + let msg = match io.poll()? { + Async::NotReady => { + self.state = SeqState::AwaitProtocol { io, protocol }; return Ok(Async::NotReady) } - Err((e, _)) => return Err(ProtocolChoiceError::from(e)) + Async::Ready(None) => + return Err(NegotiationError::from( + io::Error::from(io::ErrorKind::UnexpectedEof))), + Async::Ready(Some(msg)) => msg, }; - trace!("received {:?}", m); - match m.ok_or(ProtocolChoiceError::UnexpectedMessage)? { - Response::Protocol { ref name } - if name.as_ref() == proto_name.as_ref() => - { - return Ok(Async::Ready((proto_name, Negotiated(r.into_inner())))) + + match msg { + Message::Header(Version::V1) => { + self.state = SeqState::AwaitProtocol { io, protocol }; + } + Message::Protocol(ref p) if p.as_ref() == protocol.as_ref() => { + debug!("Dialer: Received confirmation for protocol: {}", p); + let (io, remaining) = io.into_inner(); + let io = Negotiated::completed(io, remaining); + return Ok(Async::Ready((protocol, io))) } - Response::ProtocolNotAvailable => { - let proto_name = protocols.next() - .ok_or(ProtocolChoiceError::NoProtocolFound)?; - self.inner = DialerSelectSeqState::NextProtocol { - dialer: r, - protocols, - proto_name - } + Message::NotAvailable => { + debug!("Dialer: Received rejection of protocol: {}", + String::from_utf8_lossy(protocol.as_ref())); + let protocol = self.protocols.next() + .ok_or(NegotiationError::Failed)?; + self.state = SeqState::SendProtocol { io, protocol } } - _ => return Err(ProtocolChoiceError::UnexpectedMessage) + _ => return Err(ProtocolError::InvalidMessage.into()) } } - DialerSelectSeqState::Undefined => - panic!("DialerSelectSeqState::poll called after completion") + SeqState::Done => panic!("SeqState::poll called after completion") } } } } -/// Helps selecting a protocol amongst the ones supported. -/// -/// Same as `dialer_select_proto`. Queries the list of supported protocols from the remote, then -/// chooses the most appropriate one. -pub fn dialer_select_proto_parallel(inner: R, protocols: I) -> DialerSelectPar -where - R: AsyncRead + AsyncWrite, - I: IntoIterator, - I::Item: AsRef<[u8]> -{ - let protocols = protocols.into_iter(); - DialerSelectPar { - inner: DialerSelectParState::AwaitDialer { dialer_fut: Dialer::dial(inner), protocols } - } -} - -/// Future, returned by `dialer_select_proto_parallel`, which selects a protocol and dialer in -/// parallel, by first requesting the list of protocols supported by the remote endpoint and -/// then selecting the most appropriate one by applying a match predicate to the result. +/// A `Future` returned by [`dialer_select_proto_parallel`] which negotiates +/// a protocol selectively by considering all supported protocols of the remote +/// "in parallel". pub struct DialerSelectPar where R: AsyncRead + AsyncWrite, I: Iterator, I::Item: AsRef<[u8]> { - inner: DialerSelectParState + protocols: I, + state: ParState } -enum DialerSelectParState +enum ParState where R: AsyncRead + AsyncWrite, - I: Iterator, - I::Item: AsRef<[u8]> + N: AsRef<[u8]> { - AwaitDialer { - dialer_fut: DialerFuture, - protocols: I - }, - ProtocolList { - dialer: Dialer, - protocols: I - }, - FlushListRequest { - dialer: Dialer, - protocols: I - }, - AwaitListResponse { - stream: StreamFuture>, - protocols: I, - }, - Protocol { - dialer: Dialer, - proto_name: I::Item - }, - FlushProtocol { - dialer: Dialer, - proto_name: I::Item - }, - AwaitProtocol { - stream: StreamFuture>, - proto_name: I::Item - }, - Undefined + SendHeader { io: MessageIO }, + SendProtocolsRequest { io: MessageIO }, + Flush { io: MessageIO }, + RecvProtocols { io: MessageIO }, + SendProtocol { io: MessageIO, protocol: N }, + Done } impl Future for DialerSelectPar where R: AsyncRead + AsyncWrite, I: Iterator, - I::Item: AsRef<[u8]> + Clone + I::Item: AsRef<[u8]> { type Item = (I::Item, Negotiated); - type Error = ProtocolChoiceError; + type Error = NegotiationError; fn poll(&mut self) -> Poll { loop { - match mem::replace(&mut self.inner, DialerSelectParState::Undefined) { - DialerSelectParState::AwaitDialer { mut dialer_fut, protocols } => { - match dialer_fut.poll()? { - Async::Ready(dialer) => { - self.inner = DialerSelectParState::ProtocolList { dialer, protocols } - } - Async::NotReady => { - self.inner = DialerSelectParState::AwaitDialer { dialer_fut, protocols }; - return Ok(Async::NotReady) - } + match mem::replace(&mut self.state, ParState::Done) { + ParState::SendHeader { mut io } => { + if io.start_send(Message::Header(Version::V1))?.is_not_ready() { + self.state = ParState::SendHeader { io }; + return Ok(Async::NotReady) } + self.state = ParState::SendProtocolsRequest { io }; } - DialerSelectParState::ProtocolList { mut dialer, protocols } => { - trace!("requesting protocols list"); - match dialer.start_send(Request::ListProtocols)? { - AsyncSink::Ready => { - self.inner = DialerSelectParState::FlushListRequest { - dialer, - protocols - } - } - AsyncSink::NotReady(_) => { - self.inner = DialerSelectParState::ProtocolList { dialer, protocols }; - return Ok(Async::NotReady) - } + ParState::SendProtocolsRequest { mut io } => { + if io.start_send(Message::ListProtocols)?.is_not_ready() { + self.state = ParState::SendProtocolsRequest { io }; + return Ok(Async::NotReady) } + debug!("Dialer: Requested supported protocols."); + self.state = ParState::Flush { io } } - DialerSelectParState::FlushListRequest { mut dialer, protocols } => { - match dialer.poll_complete()? { - Async::Ready(()) => { - self.inner = DialerSelectParState::AwaitListResponse { - stream: dialer.into_future(), - protocols - } - } - Async::NotReady => { - self.inner = DialerSelectParState::FlushListRequest { - dialer, - protocols - }; - return Ok(Async::NotReady) - } + ParState::Flush { mut io } => { + if io.poll_complete()?.is_not_ready() { + self.state = ParState::Flush { io }; + return Ok(Async::NotReady) } + self.state = ParState::RecvProtocols { io } } - DialerSelectParState::AwaitListResponse { mut stream, protocols } => { - let (resp, dialer) = match stream.poll() { - Ok(Async::Ready(x)) => x, - Ok(Async::NotReady) => { - self.inner = DialerSelectParState::AwaitListResponse { stream, protocols }; + ParState::RecvProtocols { mut io } => { + let msg = match io.poll()? { + Async::NotReady => { + self.state = ParState::RecvProtocols { io }; return Ok(Async::NotReady) } - Err((e, _)) => return Err(ProtocolChoiceError::from(e)) + Async::Ready(None) => + return Err(NegotiationError::from( + io::Error::from(io::ErrorKind::UnexpectedEof))), + Async::Ready(Some(msg)) => msg, }; - trace!("protocols list response: {:?}", resp); - let supported = - if let Some(Response::SupportedProtocols { protocols }) = resp { - protocols - } else { - return Err(ProtocolChoiceError::UnexpectedMessage) - }; - let mut found = None; - for local_name in protocols { - for remote_name in &supported { - if remote_name.as_ref() == local_name.as_ref() { - found = Some(local_name); - break; - } - } - if found.is_some() { - break; - } - } - let proto_name = found.ok_or(ProtocolChoiceError::NoProtocolFound)?; - self.inner = DialerSelectParState::Protocol { dialer, proto_name } - } - DialerSelectParState::Protocol { mut dialer, proto_name } => { - trace!("Requesting protocol: {:?}", proto_name.as_ref()); - let req = Request::Protocol { name: proto_name.clone() }; - match dialer.start_send(req)? { - AsyncSink::Ready => { - self.inner = DialerSelectParState::FlushProtocol { dialer, proto_name } - } - AsyncSink::NotReady(_) => { - self.inner = DialerSelectParState::Protocol { dialer, proto_name }; - return Ok(Async::NotReady) - } - } - } - DialerSelectParState::FlushProtocol { mut dialer, proto_name } => { - match dialer.poll_complete()? { - Async::Ready(()) => { - self.inner = DialerSelectParState::AwaitProtocol { - stream: dialer.into_future(), - proto_name - } + + match &msg { + Message::Header(Version::V1) => { + self.state = ParState::RecvProtocols { io } } - Async::NotReady => { - self.inner = DialerSelectParState::FlushProtocol { dialer, proto_name }; - return Ok(Async::NotReady) + Message::Protocols(supported) => { + let protocol = self.protocols.by_ref() + .find(|p| supported.iter().any(|s| + s.as_ref() == p.as_ref())) + .ok_or(NegotiationError::Failed)?; + debug!("Dialer: Found supported protocol: {}", + String::from_utf8_lossy(protocol.as_ref())); + self.state = ParState::SendProtocol { io, protocol }; } + _ => return Err(ProtocolError::InvalidMessage.into()) } } - DialerSelectParState::AwaitProtocol { mut stream, proto_name } => { - let (resp, dialer) = match stream.poll() { - Ok(Async::Ready(x)) => x, - Ok(Async::NotReady) => { - self.inner = DialerSelectParState::AwaitProtocol { stream, proto_name }; - return Ok(Async::NotReady) - } - Err((e, _)) => return Err(ProtocolChoiceError::from(e)) - }; - trace!("received {:?}", resp); - match resp { - Some(Response::Protocol { ref name }) - if name.as_ref() == proto_name.as_ref() => - { - return Ok(Async::Ready((proto_name, Negotiated(dialer.into_inner())))) - } - _ => return Err(ProtocolChoiceError::UnexpectedMessage) + ParState::SendProtocol { mut io, protocol } => { + let p = Protocol::try_from(protocol.as_ref())?; + if io.start_send(Message::Protocol(p.clone()))?.is_not_ready() { + self.state = ParState::SendProtocol { io, protocol }; + return Ok(Async::NotReady) } + debug!("Dialer: Expecting proposed protocol: {}", p); + let io = Negotiated::expecting(io.into_reader(), p); + return Ok(Async::Ready((protocol, io))) } - DialerSelectParState::Undefined => - panic!("DialerSelectParState::poll called after completion") + ParState::Done => panic!("ParState::poll called after completion") } } } diff --git a/misc/multistream-select/src/error.rs b/misc/multistream-select/src/error.rs index 1f72b5c0c8a..4d948de4490 100644 --- a/misc/multistream-select/src/error.rs +++ b/misc/multistream-select/src/error.rs @@ -20,58 +20,8 @@ //! Main `ProtocolChoiceError` error. -use crate::protocol::MultistreamSelectError; +pub use crate::protocol::ProtocolError; + use std::error::Error; use std::{fmt, io}; -/// Error that can happen when negotiating a protocol with the remote. -#[derive(Debug)] -pub enum ProtocolChoiceError { - /// Error in the protocol. - MultistreamSelectError(MultistreamSelectError), - - /// Received a message from the remote that makes no sense in the current context. - UnexpectedMessage, - - /// We don't support any protocol in common with the remote. - NoProtocolFound, -} - -impl From for ProtocolChoiceError { - fn from(err: MultistreamSelectError) -> ProtocolChoiceError { - ProtocolChoiceError::MultistreamSelectError(err) - } -} - -impl From for ProtocolChoiceError { - fn from(err: io::Error) -> ProtocolChoiceError { - MultistreamSelectError::from(err).into() - } -} - -impl Error for ProtocolChoiceError { - fn description(&self) -> &str { - match *self { - ProtocolChoiceError::MultistreamSelectError(_) => "error in the protocol", - ProtocolChoiceError::UnexpectedMessage => { - "received a message from the remote that makes no sense in the current context" - } - ProtocolChoiceError::NoProtocolFound => { - "we don't support any protocol in common with the remote" - } - } - } - - fn source(&self) -> Option<&(dyn Error + 'static)> { - match *self { - ProtocolChoiceError::MultistreamSelectError(ref err) => Some(err), - _ => None, - } - } -} - -impl fmt::Display for ProtocolChoiceError { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(fmt, "{}", Error::description(self)) - } -} diff --git a/misc/multistream-select/src/length_delimited.rs b/misc/multistream-select/src/length_delimited.rs index 4d4f2c02bb4..0e1dfc85efe 100644 --- a/misc/multistream-select/src/length_delimited.rs +++ b/misc/multistream-select/src/length_delimited.rs @@ -28,8 +28,8 @@ const MAX_LEN_BYTES: u16 = 2; const MAX_FRAME_SIZE: u16 = (1 << (MAX_LEN_BYTES * 8 - MAX_LEN_BYTES)) - 1; const DEFAULT_BUFFER_SIZE: usize = 64; -/// `Stream` and `Sink` wrapping some `AsyncRead + AsyncWrite` resource to read -/// and write unsigned-varint prefixed frames. +/// A `Stream` and `Sink` for unsigned-varint length-delimited frames, +/// wrapping an underlying `AsyncRead + AsyncWrite` I/O resource. /// /// We purposely only support a frame sizes up to 16KiB (2 bytes unsigned varint /// frame length). Frames mostly consist in a short protocol name, which is highly @@ -75,20 +75,64 @@ impl LengthDelimited { } } - /// Destroys the `LengthDelimited` and returns the underlying socket. + /// Returns a reference to the underlying I/O stream. + pub fn inner_ref(&self) -> &R { + &self.inner + } + + /// Returns a mutable reference to the underlying I/O stream. + /// + /// > **Note**: Care should be taken to not tamper with the underlying stream of data + /// > coming in, as it may corrupt the stream of frames. + pub fn inner_mut(&mut self) -> &mut R { + &mut self.inner + } + + /// Destroys the `LengthDelimited` and returns the underlying I/O stream. /// - /// This method is guaranteed not to skip any data from the socket. + /// This method is guaranteed not to drop any data read from or not yet + /// submitted to the underlying I/O stream. /// /// # Panic /// - /// Will panic if called while there is data inside the read or write buffer. - /// **This can only happen if you call `poll()` manually**. Using this struct - /// as it is intended to be used (i.e. through the high-level `futures` API) - /// will always leave the object in a state in which `into_inner()` will not panic. - pub fn into_inner(self) -> R { - assert!(self.write_buffer.is_empty()); + /// Will panic if called while there is data in the read or write buffer. + /// The read buffer is guaranteed to be empty whenever `Stream::poll` yields + /// a new `Message`. The write buffer is guaranteed to be empty whenever + /// [`poll_write_buffer`] yields `Async::Ready` or after the `Sink` has been + /// completely flushed via [`Sink::poll_complete`]. + pub fn into_inner(self) -> (R, BytesMut) { + // assert!(self.write_buffer.is_empty()); assert!(self.read_buffer.is_empty()); - self.inner + (self.inner, self.write_buffer) + } + + /// TODO + pub fn into_reader(self) -> LengthDelimitedReader { + LengthDelimitedReader { inner: self } + } + + /// Writes all buffered frame data to the underlying I/O stream, + /// _without flushing it_. + /// + /// After this method returns `Async::Ready`, the write buffer of frames + /// submitted to the `Sink` is guaranteed to be empty. + pub fn poll_write_buffer(&mut self) -> Poll<(), io::Error> + where + R: AsyncWrite + { + while !self.write_buffer.is_empty() { + let n = try_ready!(self.inner.poll_write(&self.write_buffer)); + + if n == 0 { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "Failed to write buffered frame.")) + } + + self.write_buffer.split_to(n); + } + + Ok(Async::Ready(())) } } @@ -204,18 +248,9 @@ where } fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - while !self.write_buffer.is_empty() { - let n = try_ready!(self.inner.poll_write(&self.write_buffer)); - - if n == 0 { - return Err(io::Error::new( - io::ErrorKind::WriteZero, - "Failed to write buffered frame.")) - } - - let _ = self.write_buffer.split_to(n); - } - + // Write all buffered frame data to the underlying I/O stream. + try_ready!(self.poll_write_buffer()); + // Flush the underlying I/O stream. try_ready!(self.inner.poll_flush()); return Ok(Async::Ready(())); } @@ -226,6 +261,90 @@ where } } +/// TODO +pub struct LengthDelimitedReader { + inner: LengthDelimited +} + +impl LengthDelimitedReader { + /// TODO + pub fn into_inner(self) -> (R, BytesMut) { + self.inner.into_inner() + } + + /// Returns a reference to the underlying I/O stream. + pub fn inner_ref(&self) -> &R { + self.inner.inner_ref() + } + + /// Returns a mutable reference to the underlying I/O stream. + /// + /// > **Note**: Care should be taken to not tamper with the underlying stream of data + /// > coming in, as it may corrupt the stream of frames. + pub fn inner_mut(&mut self) -> &mut R { + self.inner.inner_mut() + } +} + +impl Stream for LengthDelimitedReader +where + R: AsyncRead +{ + type Item = Bytes; + type Error = io::Error; + + fn poll(&mut self) -> Poll, Self::Error> { + self.inner.poll() + } +} + +impl io::Write for LengthDelimitedReader +where + R: AsyncWrite +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + // Try to drain the write buffer together with writing `buf`. + if !self.inner.write_buffer.is_empty() { + let n = self.inner.write_buffer.len(); + self.inner.write_buffer.extend_from_slice(buf); + let result = self.inner.poll_write_buffer(); + let written = n - self.inner.write_buffer.len(); + if written == 0 { + if let Err(e) = result { + return Err(e) + } + return Err(io::ErrorKind::WouldBlock.into()) + } + if written < buf.len() { + if self.inner.write_buffer.len() > n { + self.inner.write_buffer.split_off(n); // Never grow the buffer. + } + return Ok(written) + } + return Ok(buf.len()) + } + + self.inner_mut().write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + match self.inner.poll_complete()? { + Async::Ready(()) => Ok(()), + Async::NotReady => Err(io::ErrorKind::WouldBlock.into()) + } + } +} + +impl AsyncWrite for LengthDelimitedReader +where + R: AsyncWrite +{ + fn shutdown(&mut self) -> Poll<(), io::Error> { + try_ready!(self.inner.poll_complete()); + self.inner_mut().shutdown() + } +} + #[cfg(test)] mod tests { use futures::{Future, Stream}; diff --git a/misc/multistream-select/src/lib.rs b/misc/multistream-select/src/lib.rs index b2bc054bfff..c160afc45f4 100644 --- a/misc/multistream-select/src/lib.rs +++ b/misc/multistream-select/src/lib.rs @@ -18,24 +18,32 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! # Multistream-select +//! # Multistream-select Protocol Negotiation //! -//! This crate implements the `multistream-select` protocol, which is the protocol used by libp2p -//! to negotiate which protocol to use with the remote on a connection or substream. +//! This crate implements the `multistream-select` protocol, which is the protocol +//! used by libp2p to negotiate which application-layer protocol to use with the +//! remote on a connection or substream. //! -//! > **Note**: This crate is used by the internals of *libp2p*, and it is not required to -//! > understand it in order to use *libp2p*. +//! > **Note**: This crate is used primarily by core components of *libp2p* and it +//! > is usually not used directly on its own. //! -//! Whenever a new connection or a new multiplexed substream is opened, libp2p uses -//! `multistream-select` to negotiate with the remote which protocol to use. After a protocol has -//! been successfully negotiated, the stream (i.e. the connection or the multiplexed substream) -//! immediately stops using `multistream-select` and starts using the negotiated protocol. +//! ## Roles //! -//! ## Protocol explanation +//! Two peers using the multistream-select negotiation protocol on an I/O stream +//! are distinguished by their role as a _dialer_ or a _listener_. Thereby the dialer +//! (or _initiator_) plays the active part, driving the protocol, whereas the listener +//! (or _responder_) reacts to the messages received. //! -//! The dialer has two options available: either request the list of protocols that the listener -//! supports, or suggest a protocol. If a protocol is suggested, the listener can either accept (by -//! answering with the same protocol name) or refuse the choice (by answering "not available"). +//! The dialer has two options: it can either pick a protocol from the complete list +//! of protocols that the listener supports, or it can directly suggest a protocol. +//! Either way, a selected protocol is sent to the listener who can either accept (by +//! echoing the same protocol) or reject (by responding with a message stating +//! "not available"). If a suggested protocol is not available, the dialer may +//! suggest another protocol. This process continues until a protocol is agreed upon, +//! yielding a [`Negotiated`](self::Negotiated) stream, or the dialer has run out of alternatives. +//! +//! See [`dialer_select_proto`](self::dialer_select_proto) and +//! [`listener_select_proto`](self::listener_select_proto). //! //! ## Examples //! @@ -54,77 +62,28 @@ //! //! let client = TcpStream::connect(&"127.0.0.1:10333".parse().unwrap()) //! .from_err() -//! .and_then(move |connec| { +//! .and_then(move |io| { //! let protos = vec![b"/echo/1.0.0", b"/echo/2.5.0"]; -//! dialer_select_proto(connec, protos).map(|r| r.0) -//! }); +//! dialer_select_proto(io, protos) // .map(|r| r.0) +//! }) +//! .map(|(protocol, _io)| protocol); //! //! let mut rt = Runtime::new().unwrap(); -//! let negotiated_protocol = rt.block_on(client).expect("failed to find a protocol"); -//! println!("negotiated: {:?}", negotiated_protocol); +//! let protocol = rt.block_on(client).expect("failed to find a protocol"); +//! println!("Negotiated protocol: {:?}", protocol); //! # } //! ``` //! mod dialer_select; -mod error; mod length_delimited; mod listener_select; -mod tests; - +mod negotiated; mod protocol; +mod tests; -use futures::prelude::*; -use std::io; -use tokio_io::{AsyncRead, AsyncWrite}; - +pub use self::negotiated::{Negotiated, NegotiationError}; +pub use self::protocol::ProtocolError; pub use self::dialer_select::{dialer_select_proto, DialerSelectFuture}; -pub use self::error::ProtocolChoiceError; pub use self::listener_select::{listener_select_proto, ListenerSelectFuture}; -/// A stream after it has been negotiated. -pub struct Negotiated(pub(crate) TInner); - -impl io::Read for Negotiated -where - TInner: io::Read -{ - fn read(&mut self, buf: &mut [u8]) -> io::Result { - self.0.read(buf) - } -} - -impl AsyncRead for Negotiated -where - TInner: AsyncRead -{ - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - self.0.prepare_uninitialized_buffer(buf) - } - - fn read_buf(&mut self, buf: &mut B) -> Poll { - self.0.read_buf(buf) - } -} - -impl io::Write for Negotiated -where - TInner: io::Write -{ - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.write(buf) - } - - fn flush(&mut self) -> io::Result<()> { - self.0.flush() - } -} - -impl AsyncWrite for Negotiated -where - TInner: AsyncWrite -{ - fn shutdown(&mut self) -> Poll<(), io::Error> { - self.0.shutdown() - } -} diff --git a/misc/multistream-select/src/listener_select.rs b/misc/multistream-select/src/listener_select.rs index 40ed92d057e..a62581158dd 100644 --- a/misc/multistream-select/src/listener_select.rs +++ b/misc/multistream-select/src/listener_select.rs @@ -18,167 +18,196 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -//! Contains the `listener_select_proto` code, which allows selecting a protocol thanks to -//! `multistream-select` for the listener. +//! Protocol negotiation strategies for the peer acting as the listener +//! in a multistream-select protocol negotiation. -use futures::{prelude::*, sink, stream::StreamFuture}; -use crate::protocol::{ - Request, - Response, - Listener, - ListenerFuture, -}; -use log::{debug, trace}; -use std::mem; +use futures::prelude::*; +use crate::protocol::{Protocol, ProtocolError, MessageIO, Message, Version}; +use log::{debug, warn}; +use smallvec::SmallVec; +use std::{io, iter::FromIterator, mem, convert::TryFrom}; use tokio_io::{AsyncRead, AsyncWrite}; -use crate::{Negotiated, ProtocolChoiceError}; +use crate::{Negotiated, NegotiationError}; -/// Helps selecting a protocol amongst the ones supported. +/// Returns a `Future` that negotiates a protocol on the given I/O stream +/// for a peer acting as the _listener_ (or _responder_). /// -/// This function expects a socket and an iterator of the list of supported protocols. The iterator -/// must be clonable (i.e. iterable multiple times), because the list may need to be accessed -/// multiple times. -/// -/// The iterator must produce tuples of the name of the protocol that is advertised to the remote, -/// a function that will check whether a remote protocol matches ours, and an identifier for the -/// protocol of type `P` (you decide what `P` is). The parameters of the function are the name -/// proposed by the remote, and the protocol name that we passed (so that you don't have to clone -/// the name). -/// -/// On success, returns the socket and the identifier of the chosen protocol (of type `P`). The -/// socket now uses this protocol. -pub fn listener_select_proto(inner: R, protocols: I) -> ListenerSelectFuture +/// This function is given an I/O stream and a list of protocols and returns a +/// computation that performs the protocol negotiation with the remote. The +/// returned `Future` resolves with the name of the negotiated protocol and +/// a [`Negotiated`] I/O stream. +pub fn listener_select_proto(inner: R, protocols: I) -> ListenerSelectFuture where R: AsyncRead + AsyncWrite, - for<'r> &'r I: IntoIterator, - X: AsRef<[u8]> + I: IntoIterator, + I::Item: AsRef<[u8]> { + let protocols = protocols.into_iter().filter_map(|n| + match Protocol::try_from(n.as_ref()) { + Ok(p) => Some((n, p)), + Err(e) => { + warn!("Listener: Ignoring invalid protocol: {} due to {}", + String::from_utf8_lossy(n.as_ref()), e); + None + } + }); ListenerSelectFuture { - inner: ListenerSelectState::AwaitListener { - listener_fut: Listener::listen(inner), - protocols + protocols: SmallVec::from_iter(protocols), + state: State::RecvHeader { + io: MessageIO::new(inner) } } } -/// Future, returned by `listener_select_proto` which selects a protocol among the ones supported. -pub struct ListenerSelectFuture +/// The `Future` returned by [`listener_select_proto`] that performs a +/// multistream-select protocol negotiation on an underlying I/O stream. +pub struct ListenerSelectFuture where R: AsyncRead + AsyncWrite, - for<'a> &'a I: IntoIterator, - X: AsRef<[u8]> + N: AsRef<[u8]> { - inner: ListenerSelectState + // TODO: It would be nice if eventually N = Protocol, which has a + // few more implications on the API. + protocols: SmallVec<[(N, Protocol); 8]>, + state: State } -enum ListenerSelectState +enum State where R: AsyncRead + AsyncWrite, - for<'a> &'a I: IntoIterator, - X: AsRef<[u8]> + N: AsRef<[u8]> { - AwaitListener { - listener_fut: ListenerFuture, - protocols: I + RecvHeader { io: MessageIO }, + SendHeader { io: MessageIO }, + RecvMessage { io: MessageIO }, + SendMessage { + io: MessageIO, + message: Message, + protocol: Option }, - Incoming { - stream: StreamFuture>, - protocols: I - }, - Outgoing { - sender: sink::Send>, - protocols: I, - outcome: Option - }, - Undefined + Flush { io: MessageIO }, + Done } -impl Future for ListenerSelectFuture +impl Future for ListenerSelectFuture where R: AsyncRead + AsyncWrite, - for<'a> &'a I: IntoIterator, - X: AsRef<[u8]> + Clone + N: AsRef<[u8]> + Clone { - type Item = (X, Negotiated, I); - type Error = ProtocolChoiceError; + type Item = (N, Negotiated); + type Error = NegotiationError; fn poll(&mut self) -> Poll { loop { - match mem::replace(&mut self.inner, ListenerSelectState::Undefined) { - ListenerSelectState::AwaitListener { mut listener_fut, protocols } => { - let listener = match listener_fut.poll()? { - Async::Ready(l) => l, + match mem::replace(&mut self.state, State::Done) { + State::RecvHeader { mut io } => { + match io.poll()? { + Async::Ready(Some(Message::Header(Version::V1))) => { + self.state = State::SendHeader { io } + } + Async::Ready(Some(Message::Header(Version::V2))) => { + // The V2 protocol is not yet supported and not even + // yet fully specified or implemented anywhere. For + // now we just return 'na' to force any dialer to + // fall back to V1, according to the current plans + // for the "transition period". + // + // See: https://github.com/libp2p/specs/pull/95. + self.state = State::SendMessage { + io, + message: Message::NotAvailable, + protocol: None, + } + } + Async::Ready(Some(_)) => { + return Err(ProtocolError::InvalidMessage.into()) + } + Async::Ready(None) => + return Err(NegotiationError::from( + ProtocolError::IoError( + io::ErrorKind::UnexpectedEof.into()))), Async::NotReady => { - self.inner = ListenerSelectState::AwaitListener { listener_fut, protocols }; + self.state = State::RecvHeader { io }; return Ok(Async::NotReady) } - }; - let stream = listener.into_future(); - self.inner = ListenerSelectState::Incoming { stream, protocols }; + } } - ListenerSelectState::Incoming { mut stream, protocols } => { - let (msg, listener) = match stream.poll() { - Ok(Async::Ready(x)) => x, + State::SendHeader { mut io } => { + if io.start_send(Message::Header(Version::V1))?.is_not_ready() { + return Ok(Async::NotReady) + } + self.state = State::RecvMessage { io }; + } + State::RecvMessage { mut io } => { + let msg = match io.poll() { + Ok(Async::Ready(Some(msg))) => msg, + Ok(Async::Ready(None)) => + return Err(NegotiationError::from( + ProtocolError::IoError( + io::ErrorKind::UnexpectedEof.into()))), Ok(Async::NotReady) => { - self.inner = ListenerSelectState::Incoming { stream, protocols }; + self.state = State::RecvMessage { io }; return Ok(Async::NotReady) } - Err((e, _)) => return Err(ProtocolChoiceError::from(e)) + Err(e) => return Err(e.into()) }; + match msg { - Some(Request::ListProtocols) => { - trace!("protocols list response: {:?}", protocols - .into_iter() - .map(|p| p.as_ref().into()) - .collect::>>()); - let supported = protocols.into_iter().collect(); - let msg = Response::SupportedProtocols { protocols: supported }; - let sender = listener.send(msg); - self.inner = ListenerSelectState::Outgoing { - sender, - protocols, - outcome: None - } + Message::ListProtocols => { + let supported = self.protocols.iter().map(|(_,p)| p).cloned().collect(); + let message = Message::Protocols(supported); + self.state = State::SendMessage { io, message, protocol: None } } - Some(Request::Protocol { name }) => { - let mut outcome = None; - let mut send_back = Response::ProtocolNotAvailable; - for supported in &protocols { - if name.as_ref() == supported.as_ref() { - send_back = Response::Protocol { - name: supported.clone() - }; - outcome = Some(supported); - break; + Message::Protocol(p) => { + let protocol = self.protocols.iter().find_map(|(name, proto)| { + if &p == proto { + Some(name.clone()) + } else { + None } - } - trace!("requested: {:?}, supported: {}", name, outcome.is_some()); - let sender = listener.send(send_back); - self.inner = ListenerSelectState::Outgoing { sender, protocols, outcome } - } - None => { - debug!("no protocol request received"); - return Err(ProtocolChoiceError::NoProtocolFound) + }); + + let message = if protocol.is_some() { + debug!("Listener: confirming protocol: {}", p); + Message::Protocol(p.clone()) + } else { + debug!("Listener: rejecting protocol: {}", + String::from_utf8_lossy(p.as_ref())); + Message::NotAvailable + }; + + self.state = State::SendMessage { io, message, protocol }; } + _ => return Err(ProtocolError::InvalidMessage.into()) } } - ListenerSelectState::Outgoing { mut sender, protocols, outcome } => { - let listener = match sender.poll()? { - Async::Ready(l) => l, - Async::NotReady => { - self.inner = ListenerSelectState::Outgoing { sender, protocols, outcome }; - return Ok(Async::NotReady) + State::SendMessage { mut io, message, protocol } => { + if let AsyncSink::NotReady(message) = io.start_send(message)? { + self.state = State::SendMessage { io, message, protocol }; + return Ok(Async::NotReady) + }; + // If a protocol has been selected, finish negotiation. + // Otherwise flush the sink and expect to receive another + // message. + self.state = match protocol { + Some(protocol) => { + debug!("Listener: sent confirmed protocol: {}", + String::from_utf8_lossy(protocol.as_ref())); + let (io, remaining) = io.into_inner(); + let io = Negotiated::completed(io, remaining); + return Ok(Async::Ready((protocol, io))) } + None => State::Flush { io } }; - if let Some(p) = outcome { - return Ok(Async::Ready((p, Negotiated(listener.into_inner()), protocols))) - } else { - let stream = listener.into_future(); - self.inner = ListenerSelectState::Incoming { stream, protocols } + } + State::Flush { mut io } => { + if io.poll_complete()?.is_not_ready() { + self.state = State::Flush { io }; + return Ok(Async::NotReady) } + self.state = State::RecvMessage { io } } - ListenerSelectState::Undefined => - panic!("ListenerSelectState::poll called after completion") + State::Done => panic!("State::poll called after completion") } } } diff --git a/misc/multistream-select/src/negotiated.rs b/misc/multistream-select/src/negotiated.rs new file mode 100644 index 00000000000..94c3a5375a6 --- /dev/null +++ b/misc/multistream-select/src/negotiated.rs @@ -0,0 +1,362 @@ +// Copyright 2019 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use bytes::BytesMut; +use crate::protocol::{Protocol, MessageReader, Message, Version, ProtocolError}; +use futures::{prelude::*, Async, try_ready}; +use log::debug; +use tokio_io::{AsyncRead, AsyncWrite}; +use std::{mem, io, fmt, error::Error}; + +/// An I/O stream that has settled on an (application-layer) protocol to use. +/// +/// A `Negotiated` represents an I/O stream that has _settled_ on a protocol +/// to use. In particular, it does not imply that all of the protocol negotiation +/// frames have been sent and / or received, just that the selected protocol +/// is fully determined. This is to allow the last protocol negotiation frames +/// sent by a peer to be combined in a single write, possibly piggy-backing +/// data from the negotiated protocol on top. +/// +/// Specifically that means: +/// +/// * If a `Negotiated` is obtained by the peer with the role of the dialer in +/// the protocol negotiation, not a single negotiation message may yet have +/// been sent, if the dialer only supports a single protocol. In that case, +/// the dialer "settles" on that protocol immediately and expects it to +/// be confirmed by the remote, as it has no alternatives. Once the +/// `Negotiated` I/O resource is flushed, possibly after writing additional +/// data related to the negotiated protocol, all of the buffered frames relating to +/// protocol selection are sent together with that data. The dialer still expects +/// to receive acknowledgment of the protocol before it can continue reading data +/// from the remote related to the negotiated protocol. +/// The `Negotiated` stream may ultimately still fail protocol negotiation, if +/// the protocol that the dialer has settled on is not actually supported +/// by the listener, but having settled on that protocol the dialer has by +/// definition no more alternatives and hence such a failed negotiation is +/// usually equivalent to a failed request made using the desired protocol. +/// If an application wishes to only start using the `Negotiated` stream +/// once protocol negotiation fully completed, it may wait on completion +/// of the `Future` obtained from [`Negotiated::complete`]. +/// +/// * If a `Negotiated` is obtained by the peer with the role of the listener in +/// the protocol negotiation, the final confirmation message for the remote's +/// selected protocol may not yet have been sent. Once the `Negotiated` I/O +/// resource is flushed, possibly after writing additional data related to the +/// negotiated protocol, e.g. a response, the buffered frames relating to protocol +/// acknowledgement are sent together with that data. +/// +pub struct Negotiated { + state: State +} + +/// A `Future` that waits on the completion of protocol negotiation. +pub struct NegotiatedComplete { + inner: Option> +} + +impl Future for NegotiatedComplete { + type Item = Negotiated; + type Error = NegotiationError; + + fn poll(&mut self) -> Poll { + try_ready!(self.inner.as_mut() + .expect("NegotiatedFuture called after completion.") + .poll()); + Ok(Async::Ready(self.inner.take().expect(""))) + } +} + +impl Negotiated { + /// Creates a `Negotiated` in state [`State::Complete`], possibly + /// with `remaining` data to be sent. + pub(crate) fn completed(io: TInner, remaining: BytesMut) -> Self { + Negotiated { state: State::Completed { io, remaining } } + } + + /// Creates a `Negotiated` in state [`State::Expecting`] that is still + /// expecting confirmation of the given `protocol`. + pub(crate) fn expecting(io: MessageReader, protocol: Protocol) -> Self { + Negotiated { state: State::Expecting { io, protocol } } + } + + /// Polls the `Negotiated` for completion. + fn poll(&mut self) -> Poll<(), NegotiationError> + where + TInner: AsyncRead + AsyncWrite + { + // Flush any pending negotiation data. + match self.poll_flush() { + Ok(Async::Ready(())) => {}, + Ok(Async::NotReady) => return Ok(Async::NotReady), + Err(e) => { + // If the remote closed the stream, it is important to still + // continue reading the data that was sent, if any. + if e.kind() != io::ErrorKind::WriteZero { + return Err(e.into()) + } + } + } + + if let State::Completed { remaining, .. } = &mut self.state { + let _ = remaining.take(); // Drop remaining data flushed above. + return Ok(Async::Ready(())) + } + + // Read outstanding protocol negotiation messages. + loop { + match mem::replace(&mut self.state, State::Invalid) { + State::Expecting { mut io, protocol } => { + let msg = match io.poll() { + Ok(Async::Ready(Some(msg))) => msg, + Ok(Async::NotReady) => { + self.state = State::Expecting { io, protocol }; + return Ok(Async::NotReady) + } + Ok(Async::Ready(None)) => { + self.state = State::Expecting { io, protocol }; + return Err(ProtocolError::IoError( + io::ErrorKind::UnexpectedEof.into()).into()) + } + Err(err) => { + self.state = State::Expecting { io, protocol }; + return Err(err.into()) + } + }; + + if let Message::Header(Version::V1) = &msg { + self.state = State::Expecting { io, protocol }; + continue + } + + if let Message::Protocol(p) = &msg { + if p.as_ref() == protocol.as_ref() { + debug!("Negotiated: Received confirmation for protocol: {}", p); + let (io, remaining) = io.into_inner(); + self.state = State::Completed { io, remaining }; + return Ok(Async::Ready(())) + } + } + + return Err(NegotiationError::Failed) + } + + _ => panic!("Negotiated: Invalid state") + } + } + } + + /// Returns a `NegotiatedComplete` future that waits for protocol + /// negotiation to complete. + pub fn complete(self) -> NegotiatedComplete { + NegotiatedComplete { inner: Some(self) } + } +} + +/// The states of a `Negotiated` I/O stream. +enum State { + /// In this state, a `Negotiated` is still expecting to + /// receive confirmation of the protocol it as settled on. + Expecting { io: MessageReader, protocol: Protocol }, + + /// In this state, a protocol has been agreed upon and may + /// only be pending the sending of the final acknowledgement, + /// which is prepended to / combined with the next write for + /// efficiency. + Completed { io: R, remaining: BytesMut }, + + /// Temporary state while moving the `io` resource from + /// `Expecting` to `Completed`. + Invalid, +} + +impl io::Read for Negotiated +where + R: AsyncRead + AsyncWrite +{ + fn read(&mut self, buf: &mut [u8]) -> io::Result { + loop { + if let State::Completed { io, remaining } = &mut self.state { + // If protocol negotiation is complete and there is no + // remaining data to be flushed, commence with reading. + if remaining.is_empty() { + return io.read(buf) + } + } + + // Poll the `Future`, driving protocol negotiation to completion, + // including flushing of any remaining data. + let result = self.poll(); + + // There is still remaining data to be sent before data relating + // to the negotiated protocol can be read. + if let Ok(Async::NotReady) = result { + return Err(io::ErrorKind::WouldBlock.into()) + } + + if let Err(err) = result { + return Err(err.into()) + } + } + } +} + +impl AsyncRead for Negotiated +where + TInner: AsyncRead + AsyncWrite +{ + unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { + match &self.state { + State::Completed { io, .. } => + io.prepare_uninitialized_buffer(buf), + State::Expecting { io, .. } => + io.inner_ref().prepare_uninitialized_buffer(buf), + State::Invalid => panic!("Negotiated: Invalid state") + } + } +} + +impl io::Write for Negotiated +where + TInner: AsyncWrite +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + match &mut self.state { + State::Completed { io, ref mut remaining } => { + if !remaining.is_empty() { + // Try to write `buf` together with `remaining` for efficiency, + // regardless of whether the underlying I/O stream is buffered. + // Every call to `write` may imply a syscall and separate + // network packet. + let remaining_len = remaining.len(); + remaining.extend_from_slice(buf); + match io.write(&remaining) { + Err(e) => { + remaining.split_off(buf.len()); + debug_assert_eq!(remaining.len(), remaining_len); + Err(e) + } + Ok(n) => { + remaining.split_to(n); + if !remaining.is_empty() { + let written = if n < buf.len() { + remaining.split_off(remaining_len); + n + } else { + buf.len() + }; + debug_assert!(remaining.len() <= remaining_len); + Ok(written) + } else { + Ok(buf.len()) + } + } + } + } else { + io.write(buf) + } + }, + State::Expecting { io, .. } => io.write(buf), + State::Invalid => panic!("Negotiated: Invalid state") + } + } + + fn flush(&mut self) -> io::Result<()> { + match &mut self.state { + State::Completed { io, ref mut remaining } => { + while !remaining.is_empty() { + let n = io.write(remaining)?; + if n == 0 { + return Err(io::Error::new( + io::ErrorKind::WriteZero, + "Failed to write remaining buffer.")) + } + remaining.split_to(n); + } + io.flush() + }, + State::Expecting { io, .. } => io.flush(), + State::Invalid => panic!("Negotiated: Invalid state") + } + } +} + +impl AsyncWrite for Negotiated +where + TInner: AsyncWrite + AsyncRead +{ + fn shutdown(&mut self) -> Poll<(), io::Error> { + // Ensure all data has been flushed and expected negotiation messages + // have been received. + try_ready!(self.poll().map_err(Into::::into)); + // Continue with the shutdown of the underlying I/O stream. + match &mut self.state { + State::Completed { io, .. } => io.shutdown(), + State::Expecting { io, .. } => io.shutdown(), + State::Invalid => panic!("Negotiated: Invalid state") + } + } +} + +/// Error that can happen when negotiating a protocol with the remote. +#[derive(Debug)] +pub enum NegotiationError { + /// A protocol error occurred during the negotiation. + ProtocolError(ProtocolError), + + /// Protocol negotiation failed because no protocol could be agreed upon. + Failed, +} + +impl From for NegotiationError { + fn from(err: ProtocolError) -> NegotiationError { + NegotiationError::ProtocolError(err) + } +} + +impl From for NegotiationError { + fn from(err: io::Error) -> NegotiationError { + ProtocolError::from(err).into() + } +} + +impl Into for NegotiationError { + fn into(self) -> io::Error { + if let NegotiationError::ProtocolError(e) = self { + return e.into() + } + io::Error::new(io::ErrorKind::Other, self) + } +} + +impl Error for NegotiationError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + NegotiationError::ProtocolError(err) => Some(err), + _ => None, + } + } +} + +impl fmt::Display for NegotiationError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + write!(fmt, "{}", Error::description(self)) + } +} + diff --git a/misc/multistream-select/src/protocol.rs b/misc/multistream-select/src/protocol.rs new file mode 100644 index 00000000000..4af91917510 --- /dev/null +++ b/misc/multistream-select/src/protocol.rs @@ -0,0 +1,486 @@ +// Copyright 2017 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Multistream-select protocol messages an I/O operations for +//! constructing protocol negotiation flows. +//! +//! A protocol negotiation flow is constructed by using the +//! `Stream` and `Sink` implementations of `MessageIO` and +//! `MessageReader`. + +use bytes::{Bytes, BytesMut, BufMut}; +use crate::length_delimited::{LengthDelimited, LengthDelimitedReader}; +use futures::{prelude::*, try_ready}; +use log::trace; +use std::{io, fmt, error::Error, convert::TryFrom}; +use tokio_io::{AsyncRead, AsyncWrite}; +use unsigned_varint as uvi; + +/// The maximum number of supported protocols that can be processed. +const MAX_PROTOCOLS: usize = 1000; + +/// The maximum length (in bytes) of a protocol name. +/// +/// This limit is necessary in order to be able to unambiguously parse +/// response messages without knowledge of the corresponding request. +/// 140 comes about from 3 * 47 = 141, where 47 is the ascii/utf8 +/// encoding of the `/` character and an encoded protocol name is +/// at least 3 bytes long (uvi-length followed by `/` and `\n`). +/// Hence a protocol list response message with 47 protocols is at least +/// 141 bytes long and thus such a response cannot be mistaken for a +/// single protocol response. See `Message::decode`. +const MAX_PROTOCOL_LEN: usize = 140; + +/// 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 2.0.0 header message. +const MSG_MULTISTREAM_2_0: &[u8] = b"/multistream/2.0.0\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"; + +/// The known multistream-select protocol versions. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Version { + /// The first and currently still the only deployed version + /// of multistream-select. + V1, + /// Draft: https://github.com/libp2p/specs/pull/95 + V2, +} + +/// A protocol (name) exchanged during protocol negotiation. +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Protocol(Bytes); + +impl AsRef<[u8]> for Protocol { + fn as_ref(&self) -> &[u8] { + self.0.as_ref() + } +} + +impl TryFrom for Protocol { + type Error = ProtocolError; + + fn try_from(value: Bytes) -> Result { + if !value.as_ref().starts_with(b"/") || value.len() > MAX_PROTOCOL_LEN { + return Err(ProtocolError::InvalidProtocol) + } + Ok(Protocol(value)) + } +} + +impl TryFrom<&[u8]> for Protocol { + type Error = ProtocolError; + + fn try_from(value: &[u8]) -> Result { + Self::try_from(Bytes::from(value)) + } +} + +impl fmt::Display for Protocol { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", String::from_utf8_lossy(&self.0)) + } +} + +/// A multistream-select protocol message. +/// +/// Multistream-select protocol messages are exchanged with the goal +/// of agreeing on a application-layer protocol to use on an I/O stream. +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum Message { + /// A header message identifies the multistream-select protocol + /// that the sender wishes to speak. + Header(Version), + /// A protocol message identifies a protocol request or acknowledgement. + Protocol(Protocol), + /// A message through which a peer requests the complete list of + /// supported protocols from the remote. + ListProtocols, + /// A message listing all supported protocols of a peer. + Protocols(Vec), + /// A message signaling that a requested protocol is not available. + NotAvailable, +} + +impl Message { + /// Encodes a `Message` into its byte representation. + pub fn encode(&self, dest: &mut BytesMut) -> Result<(), ProtocolError> { + match self { + Message::Header(Version::V1) => { + dest.reserve(MSG_MULTISTREAM_1_0.len()); + dest.put(MSG_MULTISTREAM_1_0); + Ok(()) + } + Message::Header(Version::V2) => { + dest.reserve(MSG_MULTISTREAM_2_0.len()); + dest.put(MSG_MULTISTREAM_2_0); + Ok(()) + } + Message::Protocol(p) => { + let len = p.0.as_ref().len() + 1; // + 1 for \n + dest.reserve(len); + dest.put(p.0.as_ref()); + dest.put(&b"\n"[..]); + Ok(()) + } + Message::ListProtocols => { + dest.reserve(MSG_LS.len()); + dest.put(MSG_LS); + Ok(()) + } + Message::Protocols(ps) => { + let mut buf = uvi::encode::usize_buffer(); + let mut out_msg = Vec::from(uvi::encode::usize(ps.len(), &mut buf)); + for p in ps { + out_msg.extend(uvi::encode::usize(p.0.as_ref().len() + 1, &mut buf)); // +1 for '\n' + out_msg.extend_from_slice(p.0.as_ref()); + out_msg.push(b'\n') + } + dest.reserve(out_msg.len()); + dest.put(out_msg); + Ok(()) + } + Message::NotAvailable => { + dest.reserve(MSG_PROTOCOL_NA.len()); + dest.put(MSG_PROTOCOL_NA); + Ok(()) + } + } + } + + /// Decodes a `Message` from its byte representation. + pub fn decode(mut msg: Bytes) -> Result { + if msg == MSG_MULTISTREAM_1_0 { + return Ok(Message::Header(Version::V1)) + } + + if msg == MSG_MULTISTREAM_2_0 { + return Ok(Message::Header(Version::V2)) + } + + if msg.get(0) == Some(&b'/') && msg.last() == Some(&b'\n') && msg.len() <= MAX_PROTOCOL_LEN { + let p = Protocol::try_from(msg.split_to(msg.len() - 1))?; + return Ok(Message::Protocol(p)); + } + + if msg == MSG_PROTOCOL_NA { + return Ok(Message::NotAvailable); + } + + if msg == MSG_LS { + return Ok(Message::ListProtocols) + } + + // At this point, it must be a varint number of protocols, i.e. + // a `Protocols` message. + let (num_protocols, mut remaining) = uvi::decode::usize(&msg)?; + if num_protocols > MAX_PROTOCOLS { + return Err(ProtocolError::TooManyProtocols) + } + let mut protocols = Vec::with_capacity(num_protocols); + for _ in 0 .. num_protocols { + let (len, rem) = uvi::decode::usize(remaining)?; + if len == 0 || len > rem.len() || rem[len - 1] != b'\n' { + return Err(ProtocolError::InvalidMessage) + } + let p = Protocol::try_from(Bytes::from(&rem[.. len - 1]))?; + protocols.push(p); + remaining = &rem[len ..] + } + + return Ok(Message::Protocols(protocols)); + } +} + +/// A `MessageIO` implements a [`Stream`] and [`Sink`] of [`Message`]s. +pub struct MessageIO { + inner: LengthDelimited, +} + +impl MessageIO { + /// Constructs a new `MessageIO` resource wrapping the given I/O stream. + pub fn new(inner: R) -> MessageIO + where + R: AsyncRead + AsyncWrite + { + Self { inner: LengthDelimited::new(inner) } + } + + /// Converts the `MessageIO` into a `MessageReader`, dropping the + /// `Message`-oriented `Sink` in favour of direct `AsyncWrite` access + /// to the underlying I/O stream. + /// + /// This is typically done if further negotiation messages are expected to be + /// received but no more messages are written, allowing the writing of + /// follow-up protocol data to commence. + pub fn into_reader(self) -> MessageReader { + MessageReader { inner: self.inner.into_reader() } + } + + /// Drops the `MessageIO` resource, yielding the underlying I/O stream + /// together with the remaining write buffer containing the protocol + /// negotiation frame data that has not yet been written to the I/O stream. + /// + /// The returned remaining write buffer may be prepended to follow-up + /// protocol data to send with a single `write`. Either way, if non-empty, + /// the write buffer _must_ eventually be written to the I/O stream + /// _before_ any follow-up data, in order for protocol negotiation to + /// complete cleanly. + /// + /// # Panics + /// + /// Panics if the read buffer is not empty, meaning that an incoming + /// protocol negotiation frame has been partially read. The read buffer + /// is guaranteed to be empty whenever [`MessageIO::poll`] returned + /// a message. + pub fn into_inner(self) -> (R, BytesMut) { + self.inner.into_inner() + } +} + +impl Sink for MessageIO +where + R: AsyncWrite, +{ + type SinkItem = Message; + type SinkError = ProtocolError; + + fn start_send(&mut self, msg: Self::SinkItem) -> StartSend { + let mut buf = BytesMut::new(); + msg.encode(&mut buf)?; + match self.inner.start_send(buf.freeze())? { + AsyncSink::NotReady(_) => Ok(AsyncSink::NotReady(msg)), + AsyncSink::Ready => Ok(AsyncSink::Ready), + } + } + + fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { + Ok(self.inner.poll_complete()?) + } + + fn close(&mut self) -> Poll<(), Self::SinkError> { + Ok(self.inner.close()?) + } +} + +impl Stream for MessageIO +where + R: AsyncRead +{ + type Item = Message; + type Error = ProtocolError; + + fn poll(&mut self) -> Poll, Self::Error> { + poll_stream(&mut self.inner) + } +} + +/// A `MessageReader` implements a `Stream` of `Message`s on an underlying +/// I/O resource combined with direct `AsyncWrite` access. +pub struct MessageReader { + inner: LengthDelimitedReader +} + +impl MessageReader { + /// Drops the `MessageReader` resource, yielding the underlying I/O stream + /// together with the remaining write buffer containing the protocol + /// negotiation frame data that has not yet been written to the I/O stream. + /// + /// The returned remaining write buffer may be prepended to follow-up + /// protocol data to send with a single `write`. Either way, if non-empty, + /// the write buffer _must_ eventually be written to the I/O stream + /// _before_ any follow-up data, in order for protocol negotiation to + /// complete cleanly. + /// + /// # Panics + /// + /// Panics if the read buffer is not empty, meaning that an incoming + /// protocol negotiation frame has been partially read. The read buffer + /// is guaranteed to be empty whenever [`MessageReader::poll`] returned + /// a message. + pub fn into_inner(self) -> (R, BytesMut) { + self.inner.into_inner() + } + + /// Returns a reference to the underlying I/O stream. + pub fn inner_ref(&self) -> &R { + self.inner.inner_ref() + } +} + +impl Stream for MessageReader +where + R: AsyncRead +{ + type Item = Message; + type Error = ProtocolError; + + fn poll(&mut self) -> Poll, Self::Error> { + poll_stream(&mut self.inner) + } +} + +impl io::Write for MessageReader +where + R: AsyncWrite +{ + fn write(&mut self, buf: &[u8]) -> io::Result { + self.inner.write(buf) + } + + fn flush(&mut self) -> io::Result<()> { + self.inner.flush() + } +} + +impl AsyncWrite for MessageReader +where + TInner: AsyncWrite +{ + fn shutdown(&mut self) -> Poll<(), io::Error> { + self.inner.shutdown() + } +} + +fn poll_stream(stream: &mut S) -> Poll, ProtocolError> +where + S: Stream, +{ + let msg = if let Some(msg) = try_ready!(stream.poll()) { + Message::decode(msg)? + } else { + return Ok(Async::Ready(None)) + }; + + trace!("Received message: {:?}", msg); + + Ok(Async::Ready(Some(msg))) +} + +/// A protocol error. +#[derive(Debug)] +pub enum ProtocolError { + /// I/O error. + IoError(io::Error), + + /// Received an invalid message from the remote. + InvalidMessage, + + /// A protocol (name) is invalid. + InvalidProtocol, + + /// Too many protocols have been returned by the remote. + TooManyProtocols, +} + +impl From for ProtocolError { + fn from(err: io::Error) -> ProtocolError { + ProtocolError::IoError(err) + } +} + +impl Into for ProtocolError { + fn into(self) -> io::Error { + if let ProtocolError::IoError(e) = self { + return e + } + return io::ErrorKind::InvalidData.into() + } +} + +impl From for ProtocolError { + fn from(err: uvi::decode::Error) -> ProtocolError { + Self::from(io::Error::new(io::ErrorKind::InvalidData, err.to_string())) + } +} + +impl Error for ProtocolError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match *self { + ProtocolError::IoError(ref err) => Some(err), + _ => None, + } + } +} + +impl fmt::Display for ProtocolError { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + match self { + ProtocolError::IoError(e) => + write!(fmt, "I/O error: {}", e), + ProtocolError::InvalidMessage => + write!(fmt, "Received an invalid message."), + ProtocolError::InvalidProtocol => + write!(fmt, "A protocol (name) is invalid."), + ProtocolError::TooManyProtocols => + write!(fmt, "Too many protocols received.") + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use quickcheck::*; + use rand::Rng; + use rand::distributions::Alphanumeric; + use std::iter; + + impl Arbitrary for Protocol { + fn arbitrary(g: &mut G) -> Protocol { + let n = g.gen_range(1, g.size()); + let p: String = iter::repeat(()) + .map(|()| g.sample(Alphanumeric)) + .take(n) + .collect(); + Protocol(Bytes::from(format!("/{}", p))) + } + } + + impl Arbitrary for Message { + fn arbitrary(g: &mut G) -> Message { + match g.gen_range(0, 5) { + 0 => Message::Header(Version::V1), + 1 => Message::NotAvailable, + 2 => Message::ListProtocols, + 3 => Message::Protocol(Protocol::arbitrary(g)), + 4 => Message::Protocols(Vec::arbitrary(g)), + _ => panic!() + } + } + } + + #[test] + fn encode_decode_message() { + fn prop(msg: Message) { + let mut buf = BytesMut::new(); + msg.encode(&mut buf).expect(&format!("Encoding message failed: {:?}", msg)); + match Message::decode(buf.freeze()) { + Ok(m) => assert_eq!(m, msg), + Err(e) => panic!("Decoding failed: {:?}", e) + } + } + quickcheck(prop as fn(_)) + } +} + diff --git a/misc/multistream-select/src/protocol/dialer.rs b/misc/multistream-select/src/protocol/dialer.rs deleted file mode 100644 index 28da191a490..00000000000 --- a/misc/multistream-select/src/protocol/dialer.rs +++ /dev/null @@ -1,200 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Contains the `Dialer` wrapper, which allows raw communications with a listener. - -use super::*; - -use bytes::{Bytes, BytesMut}; -use crate::length_delimited::LengthDelimited; -use crate::protocol::{Request, Response, MultistreamSelectError}; -use futures::{prelude::*, sink, Async, StartSend, try_ready}; -use tokio_io::{AsyncRead, AsyncWrite}; -use std::marker; -use unsigned_varint as uvi; - -/// The maximum number of supported protocols that can be processed. -const MAX_PROTOCOLS: usize = 1000; - -/// Wraps around a `AsyncRead+AsyncWrite`. -/// Assumes that we're on the dialer's side. Produces and accepts messages. -pub struct Dialer { - inner: LengthDelimited, - handshake_finished: bool, - _protocol_name: marker::PhantomData, -} - -impl Dialer -where - R: AsyncRead + AsyncWrite, - N: AsRef<[u8]> -{ - pub fn dial(inner: R) -> DialerFuture { - let io = LengthDelimited::new(inner); - let mut buf = BytesMut::new(); - Header::Multistream10.encode(&mut buf); - DialerFuture { - inner: io.send(buf.freeze()), - _protocol_name: marker::PhantomData, - } - } - - /// Grants back the socket. Typically used after a `ProtocolAck` has been received. - pub fn into_inner(self) -> R { - self.inner.into_inner() - } -} - -impl Sink for Dialer -where - R: AsyncRead + AsyncWrite, - N: AsRef<[u8]> -{ - type SinkItem = Request; - type SinkError = MultistreamSelectError; - - fn start_send(&mut self, request: Self::SinkItem) -> StartSend { - let mut msg = BytesMut::new(); - request.encode(&mut msg)?; - match self.inner.start_send(msg.freeze())? { - AsyncSink::NotReady(_) => Ok(AsyncSink::NotReady(request)), - AsyncSink::Ready => Ok(AsyncSink::Ready), - } - } - - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - Ok(self.inner.poll_complete()?) - } - - fn close(&mut self) -> Poll<(), Self::SinkError> { - Ok(self.inner.close()?) - } -} - -impl Stream for Dialer -where - R: AsyncRead + AsyncWrite -{ - type Item = Response; - type Error = MultistreamSelectError; - - fn poll(&mut self) -> Poll, Self::Error> { - loop { - let mut msg = match self.inner.poll() { - Ok(Async::Ready(Some(msg))) => msg, - Ok(Async::Ready(None)) => return Ok(Async::Ready(None)), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(err.into()), - }; - - if !self.handshake_finished { - if msg == MSG_MULTISTREAM_1_0 { - self.handshake_finished = true; - continue; - } else { - return Err(MultistreamSelectError::FailedHandshake); - } - } - - if msg.get(0) == Some(&b'/') && msg.last() == Some(&b'\n') { - let len = msg.len(); - let name = msg.split_to(len - 1); - return Ok(Async::Ready(Some( - Response::Protocol { name } - ))); - } else if msg == MSG_PROTOCOL_NA { - return Ok(Async::Ready(Some(Response::ProtocolNotAvailable))); - } else { - // A varint number of protocols - let (num_protocols, mut remaining) = uvi::decode::usize(&msg)?; - if num_protocols > MAX_PROTOCOLS { // TODO: configurable limit - return Err(MultistreamSelectError::TooManyProtocols) - } - let mut protocols = Vec::with_capacity(num_protocols); - for _ in 0 .. num_protocols { - let (len, rem) = uvi::decode::usize(remaining)?; - if len == 0 || len > rem.len() || rem[len - 1] != b'\n' { - return Err(MultistreamSelectError::UnknownMessage) - } - protocols.push(Bytes::from(&rem[.. len - 1])); - remaining = &rem[len ..] - } - return Ok(Async::Ready(Some( - Response::SupportedProtocols { protocols }, - ))); - } - } - } -} - -/// Future, returned by `Dialer::new`, which send the handshake and returns the actual `Dialer`. -pub struct DialerFuture> { - inner: sink::Send>, - _protocol_name: marker::PhantomData, -} - -impl> Future for DialerFuture { - type Item = Dialer; - type Error = MultistreamSelectError; - - fn poll(&mut self) -> Poll { - let inner = try_ready!(self.inner.poll()); - Ok(Async::Ready(Dialer { - inner, - handshake_finished: false, - _protocol_name: marker::PhantomData, - })) - } -} - -#[cfg(test)] -mod tests { - use super::*; - use tokio::runtime::current_thread::Runtime; - use tokio_tcp::{TcpListener, TcpStream}; - use futures::Future; - use futures::{Sink, Stream}; - - #[test] - fn wrong_proto_name() { - let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); - let listener_addr = listener.local_addr().unwrap(); - - let server = listener - .incoming() - .into_future() - .map(|_| ()) - .map_err(|(e, _)| e.into()); - - let client = TcpStream::connect(&listener_addr) - .from_err() - .and_then(move |stream| Dialer::dial(stream)) - .and_then(move |dialer| { - let name = b"invalid_name"; - dialer.send(Request::Protocol { name }) - }); - - let mut rt = Runtime::new().unwrap(); - match rt.block_on(server.join(client)) { - Err(MultistreamSelectError::InvalidProtocolName) => (), - _ => panic!(), - } - } -} diff --git a/misc/multistream-select/src/protocol/error.rs b/misc/multistream-select/src/protocol/error.rs deleted file mode 100644 index f6686ee9fbd..00000000000 --- a/misc/multistream-select/src/protocol/error.rs +++ /dev/null @@ -1,87 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Contains the error structs for the low-level protocol handling. - -use std::error::Error; -use std::fmt; -use std::io; -use unsigned_varint::decode; - -/// Error at the multistream-select layer of communication. -#[derive(Debug)] -pub enum MultistreamSelectError { - /// I/O error. - IoError(io::Error), - - /// The remote doesn't use the same multistream-select protocol as we do. - FailedHandshake, - - /// Received an unknown message from the remote. - UnknownMessage, - - /// Protocol names must always start with `/`, otherwise this error is returned. - InvalidProtocolName, - - /// Too many protocols have been returned by the remote. - TooManyProtocols, -} - -impl From for MultistreamSelectError { - fn from(err: io::Error) -> MultistreamSelectError { - MultistreamSelectError::IoError(err) - } -} - -impl From for MultistreamSelectError { - fn from(err: decode::Error) -> MultistreamSelectError { - Self::from(io::Error::new(io::ErrorKind::InvalidData, err.to_string())) - } -} - -impl Error for MultistreamSelectError { - fn description(&self) -> &str { - match *self { - MultistreamSelectError::IoError(_) => "I/O error", - MultistreamSelectError::FailedHandshake => { - "the remote doesn't use the same multistream-select protocol as we do" - } - MultistreamSelectError::UnknownMessage => "received an unknown message from the remote", - MultistreamSelectError::InvalidProtocolName => { - "protocol names must always start with `/`, otherwise this error is returned" - } - MultistreamSelectError::TooManyProtocols => - "Too many protocols." - } - } - - fn source(&self) -> Option<&(dyn Error + 'static)> { - match *self { - MultistreamSelectError::IoError(ref err) => Some(err), - _ => None, - } - } -} - -impl fmt::Display for MultistreamSelectError { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { - write!(fmt, "{}", Error::description(self)) - } -} diff --git a/misc/multistream-select/src/protocol/listener.rs b/misc/multistream-select/src/protocol/listener.rs deleted file mode 100644 index 243304edcff..00000000000 --- a/misc/multistream-select/src/protocol/listener.rs +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Contains the `Listener` wrapper, which allows raw communications with a dialer. - -use super::*; - -use bytes::{Bytes, BytesMut}; -use crate::length_delimited::LengthDelimited; -use crate::protocol::{Request, Response, MultistreamSelectError}; -use futures::{prelude::*, sink, stream::StreamFuture}; -use log::{debug, trace}; -use std::{marker, mem}; -use tokio_io::{AsyncRead, AsyncWrite}; - -/// Wraps around a `AsyncRead+AsyncWrite`. Assumes that we're on the listener's side. Produces and -/// accepts messages. -pub struct Listener { - inner: LengthDelimited, - _protocol_name: marker::PhantomData, -} - -impl Listener -where - R: AsyncRead + AsyncWrite, - N: AsRef<[u8]> -{ - /// Takes ownership of a socket and starts the handshake. If the handshake succeeds, the - /// future returns a `Listener`. - pub fn listen(inner: R) -> ListenerFuture { - let inner = LengthDelimited::new(inner); - ListenerFuture { - inner: ListenerFutureState::Await { inner: inner.into_future() }, - _protocol_name: marker::PhantomData, - } - } - - /// Grants back the socket. Typically used after a `ProtocolRequest` has been received and a - /// `ProtocolAck` has been sent back. - pub fn into_inner(self) -> R { - self.inner.into_inner() - } -} - -impl Sink for Listener -where - R: AsyncRead + AsyncWrite, - N: AsRef<[u8]> -{ - type SinkItem = Response; - type SinkError = MultistreamSelectError; - - fn start_send(&mut self, response: Self::SinkItem) -> StartSend { - let mut msg = BytesMut::new(); - response.encode(&mut msg)?; - match self.inner.start_send(msg.freeze())? { - AsyncSink::NotReady(_) => Ok(AsyncSink::NotReady(response)), - AsyncSink::Ready => Ok(AsyncSink::Ready) - } - } - - fn poll_complete(&mut self) -> Poll<(), Self::SinkError> { - Ok(self.inner.poll_complete()?) - } - - fn close(&mut self) -> Poll<(), Self::SinkError> { - Ok(self.inner.close()?) - } -} - -impl Stream for Listener -where - R: AsyncRead + AsyncWrite, -{ - type Item = Request; - type Error = MultistreamSelectError; - - fn poll(&mut self) -> Poll, Self::Error> { - let mut msg = match self.inner.poll() { - Ok(Async::Ready(Some(msg))) => msg, - Ok(Async::Ready(None)) => return Ok(Async::Ready(None)), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(err.into()), - }; - - if msg.get(0) == Some(&b'/') && msg.last() == Some(&b'\n') { - let len = msg.len(); - let name = msg.split_to(len - 1); - Ok(Async::Ready(Some( - Request::Protocol { name }, - ))) - } else if msg == MSG_LS { - Ok(Async::Ready(Some( - Request::ListProtocols, - ))) - } else { - Err(MultistreamSelectError::UnknownMessage) - } - } -} - - -/// Future, returned by `Listener::new` which performs the handshake and returns -/// the `Listener` if successful. -pub struct ListenerFuture { - inner: ListenerFutureState, - _protocol_name: marker::PhantomData, -} - -enum ListenerFutureState { - Await { - inner: StreamFuture> - }, - Reply { - sender: sink::Send> - }, - Undefined -} - -impl> Future for ListenerFuture { - type Item = Listener; - type Error = MultistreamSelectError; - - fn poll(&mut self) -> Poll { - loop { - match mem::replace(&mut self.inner, ListenerFutureState::Undefined) { - ListenerFutureState::Await { mut inner } => { - let (msg, socket) = - match inner.poll() { - Ok(Async::Ready(x)) => x, - Ok(Async::NotReady) => { - self.inner = ListenerFutureState::Await { inner }; - return Ok(Async::NotReady) - } - Err((e, _)) => return Err(MultistreamSelectError::from(e)) - }; - if msg.as_ref().map(|b| &b[..]) != Some(MSG_MULTISTREAM_1_0) { - debug!("Unexpected message: {:?}", msg); - return Err(MultistreamSelectError::FailedHandshake) - } - trace!("sending back /multistream/ to finish the handshake"); - let mut frame = BytesMut::new(); - Header::Multistream10.encode(&mut frame); - let sender = socket.send(frame.freeze()); - self.inner = ListenerFutureState::Reply { sender } - } - ListenerFutureState::Reply { mut sender } => { - let listener = match sender.poll()? { - Async::Ready(x) => x, - Async::NotReady => { - self.inner = ListenerFutureState::Reply { sender }; - return Ok(Async::NotReady) - } - }; - return Ok(Async::Ready(Listener { - inner: listener, - _protocol_name: marker::PhantomData - })) - } - ListenerFutureState::Undefined => - panic!("ListenerFutureState::poll called after completion") - } - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use tokio::runtime::current_thread::Runtime; - use tokio_tcp::{TcpListener, TcpStream}; - use bytes::Bytes; - use futures::Future; - use futures::{Sink, Stream}; - - #[test] - fn wrong_proto_name() { - let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); - let listener_addr = listener.local_addr().unwrap(); - - let server = listener - .incoming() - .into_future() - .map_err(|(e, _)| e.into()) - .and_then(move |(connec, _)| Listener::listen(connec.unwrap())) - .and_then(|listener| { - let name = Bytes::from("invalid-proto"); - listener.send(Response::Protocol { name }) - }); - - let client = TcpStream::connect(&listener_addr) - .from_err() - .and_then(move |stream| Dialer::<_, Bytes>::dial(stream)); - - let mut rt = Runtime::new().unwrap(); - match rt.block_on(server.join(client)) { - Err(MultistreamSelectError::InvalidProtocolName) => (), - _ => panic!(), - } - } -} diff --git a/misc/multistream-select/src/protocol/mod.rs b/misc/multistream-select/src/protocol/mod.rs deleted file mode 100644 index 5b1fca7153b..00000000000 --- a/misc/multistream-select/src/protocol/mod.rs +++ /dev/null @@ -1,144 +0,0 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -//! Contains lower-level structs to handle the multistream protocol. - -const MSG_MULTISTREAM_1_0: &[u8] = b"/multistream/1.0.0\n"; -const MSG_PROTOCOL_NA: &[u8] = b"na\n"; -const MSG_LS: &[u8] = b"ls\n"; - -mod dialer; -mod error; -mod listener; - -pub use self::dialer::{Dialer, DialerFuture}; -pub use self::error::MultistreamSelectError; -pub use self::listener::{Listener, ListenerFuture}; - -use bytes::{BytesMut, BufMut}; -use unsigned_varint as uvi; - -pub enum Header { - Multistream10 -} - -impl Header { - fn encode(&self, dest: &mut BytesMut) { - match self { - Header::Multistream10 => { - dest.reserve(MSG_MULTISTREAM_1_0.len()); - dest.put(MSG_MULTISTREAM_1_0); - } - } - } -} - -/// Message sent from the dialer to the listener. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Request { - /// The dialer wants us to use a protocol. - /// - /// If this is accepted (by receiving back a `ProtocolAck`), then we immediately start - /// communicating in the new protocol. - Protocol { - /// Name of the protocol. - name: N - }, - - /// The dialer requested the list of protocols that the listener supports. - ListProtocols, -} - -impl> Request { - fn encode(&self, dest: &mut BytesMut) -> Result<(), MultistreamSelectError> { - match self { - Request::Protocol { name } => { - if !name.as_ref().starts_with(b"/") { - return Err(MultistreamSelectError::InvalidProtocolName) - } - let len = name.as_ref().len() + 1; // + 1 for \n - dest.reserve(len); - dest.put(name.as_ref()); - dest.put(&b"\n"[..]); - Ok(()) - } - Request::ListProtocols => { - dest.reserve(MSG_LS.len()); - dest.put(MSG_LS); - Ok(()) - } - } - } -} - - -/// Message sent from the listener to the dialer. -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum Response { - /// The protocol requested by the dialer is accepted. The socket immediately starts using the - /// new protocol. - Protocol { name: N }, - - /// The protocol requested by the dialer is not supported or available. - ProtocolNotAvailable, - - /// Response to the request for the list of protocols. - SupportedProtocols { - /// The list of protocols. - // TODO: use some sort of iterator - protocols: Vec, - }, -} - -impl> Response { - fn encode(&self, dest: &mut BytesMut) -> Result<(), MultistreamSelectError> { - match self { - Response::Protocol { name } => { - if !name.as_ref().starts_with(b"/") { - return Err(MultistreamSelectError::InvalidProtocolName) - } - let len = name.as_ref().len() + 1; // + 1 for \n - dest.reserve(len); - dest.put(name.as_ref()); - dest.put(&b"\n"[..]); - Ok(()) - } - Response::SupportedProtocols { protocols } => { - let mut buf = uvi::encode::usize_buffer(); - let mut out_msg = Vec::from(uvi::encode::usize(protocols.len(), &mut buf)); - for p in protocols { - out_msg.extend(uvi::encode::usize(p.as_ref().len() + 1, &mut buf)); // +1 for '\n' - out_msg.extend_from_slice(p.as_ref()); - out_msg.push(b'\n') - } - dest.reserve(out_msg.len()); - dest.put(out_msg); - Ok(()) - } - Response::ProtocolNotAvailable => { - dest.reserve(MSG_PROTOCOL_NA.len()); - dest.put(MSG_PROTOCOL_NA); - Ok(()) - } - } - } -} - - diff --git a/misc/multistream-select/src/tests.rs b/misc/multistream-select/src/tests.rs index dbfc0588d7e..95e7c151849 100644 --- a/misc/multistream-select/src/tests.rs +++ b/misc/multistream-select/src/tests.rs @@ -22,65 +22,13 @@ #![cfg(test)] -use crate::ProtocolChoiceError; +use crate::NegotiationError; use crate::dialer_select::{dialer_select_proto_parallel, dialer_select_proto_serial}; -use crate::protocol::{Dialer, Request, Listener, Response}; use crate::{dialer_select_proto, listener_select_proto}; use futures::prelude::*; use tokio::runtime::current_thread::Runtime; use tokio_tcp::{TcpListener, TcpStream}; - -/// Holds a `Vec` and satifies the iterator requirements of `listener_select_proto`. -struct VecRefIntoIter(Vec); - -impl<'a, T> IntoIterator for &'a VecRefIntoIter -where T: Clone -{ - type Item = T; - type IntoIter = std::vec::IntoIter; - fn into_iter(self) -> Self::IntoIter { - self.0.clone().into_iter() - } -} - -#[test] -fn negotiate_with_self_succeeds() { - let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); - let listener_addr = listener.local_addr().unwrap(); - - let server = listener - .incoming() - .into_future() - .map_err(|(e, _)| e.into()) - .and_then(move |(connec, _)| Listener::listen(connec.unwrap())) - .and_then(|l| l.into_future().map_err(|(e, _)| e)) - .and_then(|(msg, rest)| { - let proto = match msg { - Some(Request::Protocol { name }) => name, - _ => panic!(), - }; - rest.send(Response::Protocol { name: proto }) - }); - - let client = TcpStream::connect(&listener_addr) - .from_err() - .and_then(move |stream| Dialer::dial(stream)) - .and_then(move |dialer| { - let name = b"/hello/1.0.0"; - dialer.send(Request::Protocol { name }) - }) - .and_then(move |dialer| dialer.into_future().map_err(|(e, _)| e)) - .and_then(move |(msg, _)| { - let proto = match msg { - Some(Response::Protocol { name }) => name, - _ => panic!(), - }; - assert_eq!(proto, "/hello/1.0.0"); - Ok(()) - }); - let mut rt = Runtime::new().unwrap(); - let _ = rt.block_on(server.join(client)).unwrap(); -} +use tokio_io::io as nio; #[test] fn select_proto_basic() { @@ -94,18 +42,32 @@ fn select_proto_basic() { .map_err(|(e, _)| e.into()) .and_then(move |connec| { let protos = vec![b"/proto1", b"/proto2"]; - listener_select_proto(connec, VecRefIntoIter(protos)).map(|r| r.0) + listener_select_proto(connec, protos) + }) + .and_then(|(proto, io)| { + nio::write_all(io, b"pong").from_err().map(move |_| proto) }); let client = TcpStream::connect(&listener_addr) .from_err() .and_then(move |connec| { let protos = vec![b"/proto3", b"/proto2"]; - dialer_select_proto(connec, protos).map(|r| r.0) + dialer_select_proto(connec, protos) + }) + .and_then(|(proto, io)| { + nio::write_all(io, b"ping").from_err().map(move |(io, _)| (proto, io)) + }) + .and_then(|(proto, io)| { + nio::read_exact(io, [0; 4]).from_err().map(move |(_, msg)| { + assert_eq!(&msg, b"pong"); + proto + }) }); + let mut rt = Runtime::new().unwrap(); let (dialer_chosen, listener_chosen) = rt.block_on(client.join(server)).unwrap(); + assert_eq!(dialer_chosen, b"/proto2"); assert_eq!(listener_chosen, b"/proto2"); } @@ -122,19 +84,22 @@ fn no_protocol_found() { .map_err(|(e, _)| e.into()) .and_then(move |connec| { let protos = vec![b"/proto1", b"/proto2"]; - listener_select_proto(connec, VecRefIntoIter(protos)).map(|r| r.0) - }); + listener_select_proto(connec, protos) + }) + .and_then(|(proto, io)| io.complete().map(move |_| proto)); let client = TcpStream::connect(&listener_addr) .from_err() .and_then(move |connec| { let protos = vec![b"/proto3", b"/proto4"]; - dialer_select_proto(connec, protos).map(|r| r.0) - }); + dialer_select_proto(connec, protos) + }) + .and_then(|(proto, io)| io.complete().map(move |_| proto)); + let mut rt = Runtime::new().unwrap(); match rt.block_on(client.join(server)) { - Err(ProtocolChoiceError::NoProtocolFound) => (), - _ => panic!(), + Err(NegotiationError::Failed) => (), + e => panic!("{:?}", e), } } @@ -150,19 +115,22 @@ fn select_proto_parallel() { .map_err(|(e, _)| e.into()) .and_then(move |connec| { let protos = vec![b"/proto1", b"/proto2"]; - listener_select_proto(connec, VecRefIntoIter(protos)).map(|r| r.0) - }); + listener_select_proto(connec, protos) + }) + .and_then(|(proto, io)| io.complete().map(move |_| proto)); let client = TcpStream::connect(&listener_addr) .from_err() .and_then(move |connec| { let protos = vec![b"/proto3", b"/proto2"]; - dialer_select_proto_parallel(connec, protos.into_iter()).map(|r| r.0) - }); + dialer_select_proto_parallel(connec, protos.into_iter()) + }) + .and_then(|(proto, io)| io.complete().map(move |_| proto)); let mut rt = Runtime::new().unwrap(); let (dialer_chosen, listener_chosen) = rt.block_on(client.join(server)).unwrap(); + assert_eq!(dialer_chosen, b"/proto2"); assert_eq!(listener_chosen, b"/proto2"); } @@ -179,19 +147,22 @@ fn select_proto_serial() { .map_err(|(e, _)| e.into()) .and_then(move |connec| { let protos = vec![b"/proto1", b"/proto2"]; - listener_select_proto(connec, VecRefIntoIter(protos)).map(|r| r.0) - }); + listener_select_proto(connec, protos) + }) + .and_then(|(proto, io)| io.complete().map(move |_| proto)); let client = TcpStream::connect(&listener_addr) .from_err() .and_then(move |connec| { let protos = vec![b"/proto3", b"/proto2"]; - dialer_select_proto_serial(connec, protos.into_iter()).map(|r| r.0) - }); + dialer_select_proto_serial(connec, protos.into_iter()) + }) + .and_then(|(proto, io)| io.complete().map(move |_| proto)); let mut rt = Runtime::new().unwrap(); let (dialer_chosen, listener_chosen) = rt.block_on(client.join(server)).unwrap(); + assert_eq!(dialer_chosen, b"/proto2"); assert_eq!(listener_chosen, b"/proto2"); } diff --git a/muxers/mplex/src/lib.rs b/muxers/mplex/src/lib.rs index a1fe1636d6d..8806b031551 100644 --- a/muxers/mplex/src/lib.rs +++ b/muxers/mplex/src/lib.rs @@ -27,7 +27,7 @@ use bytes::Bytes; use libp2p_core::{ Endpoint, StreamMuxer, - upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo, Negotiated} + upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo, Negotiated}, }; use log::{debug, trace}; use parking_lot::Mutex; diff --git a/protocols/floodsub/src/protocol.rs b/protocols/floodsub/src/protocol.rs index 532a0f88f80..e6951321dc5 100644 --- a/protocols/floodsub/src/protocol.rs +++ b/protocols/floodsub/src/protocol.rs @@ -49,7 +49,7 @@ impl UpgradeInfo for FloodsubConfig { impl InboundUpgrade for FloodsubConfig where - TSocket: AsyncRead, + TSocket: AsyncRead + AsyncWrite, { type Output = FloodsubRpc; type Error = FloodsubDecodeError; @@ -164,7 +164,7 @@ impl UpgradeInfo for FloodsubRpc { impl OutboundUpgrade for FloodsubRpc where - TSocket: AsyncWrite, + TSocket: AsyncWrite + AsyncRead, { type Output = (); type Error = io::Error; diff --git a/protocols/kad/src/handler.rs b/protocols/kad/src/handler.rs index 320483cb81a..71e5ab7ef20 100644 --- a/protocols/kad/src/handler.rs +++ b/protocols/kad/src/handler.rs @@ -35,6 +35,7 @@ use libp2p_core::{ either::EitherOutput, upgrade::{self, InboundUpgrade, OutboundUpgrade, Negotiated} }; +use log::trace; use multihash::Multihash; use std::{borrow::Cow, error, fmt, io, time::Duration}; use tokio_io::{AsyncRead, AsyncWrite}; @@ -80,7 +81,6 @@ where KadRequestMsg, Option, ), - /// Waiting to send a message to the remote. /// Waiting to flush the substream so that the data arrives to the remote. OutPendingFlush(KadOutStreamSink, Option), /// Waiting for an answer back from the remote. @@ -830,7 +830,14 @@ where None, false, ), - Ok(Async::Ready(None)) | Err(_) => (None, None, false), + Ok(Async::Ready(None)) => { + trace!("Inbound substream: EOF"); + (None, None, false) + } + Err(e) => { + trace!("Inbound substream error: {:?}", e); + (None, None, false) + }, }, SubstreamState::InWaitingUser(id, substream) => ( Some(SubstreamState::InWaitingUser(id, substream)), From a358b92e64944d5aec83ea30ed29355110aae9ec Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Fri, 26 Jul 2019 12:21:20 +0200 Subject: [PATCH 07/14] Further missing commentary. --- .../src/length_delimited.rs | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/misc/multistream-select/src/length_delimited.rs b/misc/multistream-select/src/length_delimited.rs index 0e1dfc85efe..44dbfe95789 100644 --- a/misc/multistream-select/src/length_delimited.rs +++ b/misc/multistream-select/src/length_delimited.rs @@ -88,25 +88,31 @@ impl LengthDelimited { &mut self.inner } - /// Destroys the `LengthDelimited` and returns the underlying I/O stream. + /// Drops the `LengthDelimited` resource, yielding the underlying I/O stream + /// together with the remaining write buffer containing the uvi-framed data + /// that has not yet been written to the underlying I/O stream. /// - /// This method is guaranteed not to drop any data read from or not yet - /// submitted to the underlying I/O stream. + /// The returned remaining write buffer may be prepended to follow-up + /// protocol data to send with a single `write`. Either way, if non-empty, + /// the write buffer _must_ eventually be written to the I/O stream + /// _before_ any follow-up data, in order to maintain a correct data stream. /// /// # Panic /// - /// Will panic if called while there is data in the read or write buffer. - /// The read buffer is guaranteed to be empty whenever `Stream::poll` yields - /// a new `Message`. The write buffer is guaranteed to be empty whenever - /// [`poll_write_buffer`] yields `Async::Ready` or after the `Sink` has been - /// completely flushed via [`Sink::poll_complete`]. + /// Will panic if called while there is data in the read buffer. The read buffer is + /// guaranteed to be empty whenever `Stream::poll` yields a new `Bytes` frame. pub fn into_inner(self) -> (R, BytesMut) { - // assert!(self.write_buffer.is_empty()); assert!(self.read_buffer.is_empty()); (self.inner, self.write_buffer) } - /// TODO + /// Converts the `LengthDelimited` into a `LengthDelimitedReader`, dropping the + /// uvi-framed `Sink` in favour of direct `AsyncWrite` access to the underlying + /// I/O stream. + /// + /// This is typically done if further uvi-framed messages are expected to be + /// received but no more such messages are written, allowing the writing of + /// follow-up protocol data to commence. pub fn into_reader(self) -> LengthDelimitedReader { LengthDelimitedReader { inner: self } } @@ -261,13 +267,25 @@ where } } -/// TODO +/// A `LengthDelimitedReader` implements a `Stream` of uvi-length-delimited +/// frames on an underlying I/O resource combined with direct `AsyncWrite` access. pub struct LengthDelimitedReader { inner: LengthDelimited } impl LengthDelimitedReader { - /// TODO + /// Destroys the `LengthDelimitedReader` and returns the underlying I/O stream. + /// + /// This method is guaranteed not to drop any data read from or not yet + /// submitted to the underlying I/O stream. + /// + /// # Panic + /// + /// Will panic if called while there is data in the read or write buffer. + /// The read buffer is guaranteed to be empty whenever `Stream::poll` yields + /// a new `Message`. The write buffer is guaranteed to be empty whenever + /// [`poll_write_buffer`] yields `Async::Ready` or after the `Sink` has been + /// completely flushed via [`Sink::poll_complete`]. pub fn into_inner(self) -> (R, BytesMut) { self.inner.into_inner() } From d42084092e76536c178e59463bf902a6b9306974 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Fri, 26 Jul 2019 14:19:08 +0200 Subject: [PATCH 08/14] Remove unused test dependency. --- core/Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/core/Cargo.toml b/core/Cargo.toml index bf34750a5a3..1c47945c8c5 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -45,7 +45,6 @@ untrusted = { version = "0.6" } libp2p-swarm = { version = "0.1.0", path = "../swarm" } libp2p-tcp = { version = "0.11.0", path = "../transports/tcp" } libp2p-mplex = { version = "0.11.0", path = "../muxers/mplex" } -libp2p-yamux = { version = "0.11.0", path = "../muxers/yamux" } libp2p-secio = { version = "0.11.0", path = "../protocols/secio" } rand = "0.6" quickcheck = "0.8" From 5f03d2238fe60f51f2c2b1dbce84de2c92b67da9 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Fri, 26 Jul 2019 14:22:43 +0200 Subject: [PATCH 09/14] Adjust commentary. --- misc/multistream-select/src/negotiated.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/misc/multistream-select/src/negotiated.rs b/misc/multistream-select/src/negotiated.rs index 94c3a5375a6..58b92790d57 100644 --- a/misc/multistream-select/src/negotiated.rs +++ b/misc/multistream-select/src/negotiated.rs @@ -200,7 +200,7 @@ where } } - // Poll the `Future`, driving protocol negotiation to completion, + // Poll the `Negotiated`, driving protocol negotiation to completion, // including flushing of any remaining data. let result = self.poll(); From d978987b191622d5ef196b54cb827555bd5d79dc Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Fri, 26 Jul 2019 17:08:38 +0200 Subject: [PATCH 10/14] Cleanup NegotiatedComplete::poll() --- misc/multistream-select/src/negotiated.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/misc/multistream-select/src/negotiated.rs b/misc/multistream-select/src/negotiated.rs index 58b92790d57..9bb87513a07 100644 --- a/misc/multistream-select/src/negotiated.rs +++ b/misc/multistream-select/src/negotiated.rs @@ -76,10 +76,12 @@ impl Future for NegotiatedComplete { type Error = NegotiationError; fn poll(&mut self) -> Poll { - try_ready!(self.inner.as_mut() - .expect("NegotiatedFuture called after completion.") - .poll()); - Ok(Async::Ready(self.inner.take().expect(""))) + let mut io = self.inner.take().expect("NegotiatedFuture called after completion."); + if io.poll()?.is_not_ready() { + self.inner = Some(io); + return Ok(Async::NotReady) + } + return Ok(Async::Ready(io)) } } From d1473aa377fec3318667fc15333f7242bf574d9c Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Fri, 26 Jul 2019 17:25:01 +0200 Subject: [PATCH 11/14] Fix deflate protocol tests. --- protocols/deflate/tests/test.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/protocols/deflate/tests/test.rs b/protocols/deflate/tests/test.rs index 0abed2857d6..7ea9b116570 100644 --- a/protocols/deflate/tests/test.rs +++ b/protocols/deflate/tests/test.rs @@ -85,7 +85,7 @@ where .unwrap() .map_err(|e| panic!("client error: {}", e)) .and_then(move |server| { - io::write_all(server, message2).and_then(|(client, _)| io::flush(client)) + io::write_all(server, message2).and_then(|(client, _)| io::shutdown(client)) }) .map(|_| ()); From 6f74bef8997d24abbb67df92742b2fa86b33c03f Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Mon, 29 Jul 2019 09:07:40 +0200 Subject: [PATCH 12/14] Stabilise network_simult test. The test implicitly relied on "slow" connection establishment in order to have a sufficient probability of passing. With the removal of roundtrips in multistream-select, it is now more likely that within the up to 50ms duration between swarm1 and swarm2 dialing, the connection is already established, causing the expectation of step == 1 to fail when receiving a Connected event, since the step may then still be 0. This commit aims to avoid these spurious errors by detecting runs during which a connection is established "too quickly", repeating the test run. It still seems theoretically possible that, if connections are always established "too quickly", the test runs forever. However, given that the delta between swarm1 and swarm2 dialing is 0-50ms and that the TCP transport is used, that seems probabilistically unlikely. Nevertheless, the purpose of the artificial dialing delay between swarm1 and swarm2 should be re-evaluated and possibly at least the maximum delay further reduced. --- core/tests/network_simult.rs | 59 +++++++++++++++++++++++++++--------- 1 file changed, 44 insertions(+), 15 deletions(-) diff --git a/core/tests/network_simult.rs b/core/tests/network_simult.rs index 525b6ccf29b..0b5c23839d6 100644 --- a/core/tests/network_simult.rs +++ b/core/tests/network_simult.rs @@ -22,7 +22,8 @@ mod util; use futures::{future, prelude::*}; use libp2p_core::identity; -use libp2p_core::nodes::network::{Network, NetworkEvent, IncomingError}; +use libp2p_core::nodes::{Network, NetworkEvent, Peer}; +use libp2p_core::nodes::network::IncomingError; use libp2p_core::{Transport, upgrade, upgrade::OutboundUpgradeExt, upgrade::InboundUpgradeExt}; use libp2p_swarm::{ ProtocolsHandler, @@ -176,14 +177,14 @@ fn raw_swarm_simultaneous_connect() { let mut reactor = tokio::runtime::current_thread::Runtime::new().unwrap(); - for _ in 0 .. 10 { + loop { let mut swarm1_step = 0; let mut swarm2_step = 0; let mut swarm1_dial_start = Delay::new(Instant::now() + Duration::new(0, rand::random::() % 50_000_000)); let mut swarm2_dial_start = Delay::new(Instant::now() + Duration::new(0, rand::random::() % 50_000_000)); - let future = future::poll_fn(|| -> Poll<(), io::Error> { + let future = future::poll_fn(|| -> Poll { loop { let mut swarm1_not_ready = false; let mut swarm2_not_ready = false; @@ -195,10 +196,11 @@ fn raw_swarm_simultaneous_connect() { match swarm1_dial_start.poll().unwrap() { Async::Ready(_) => { let handler = TestHandler::default().into_node_handler_builder(); - swarm1.peer(swarm2.local_peer_id().clone()).into_not_connected().unwrap() + swarm1.peer(swarm2.local_peer_id().clone()) + .into_not_connected() + .unwrap() .connect(swarm2_listen_addr.clone(), handler); swarm1_step = 1; - swarm1_not_ready = false; }, Async::NotReady => swarm1_not_ready = true, } @@ -208,10 +210,11 @@ fn raw_swarm_simultaneous_connect() { match swarm2_dial_start.poll().unwrap() { Async::Ready(_) => { let handler = TestHandler::default().into_node_handler_builder(); - swarm2.peer(swarm1.local_peer_id().clone()).into_not_connected().unwrap() + swarm2.peer(swarm1.local_peer_id().clone()) + .into_not_connected() + .unwrap() .connect(swarm1_listen_addr.clone(), handler); swarm2_step = 1; - swarm2_not_ready = false; }, Async::NotReady => swarm2_not_ready = true, } @@ -219,12 +222,19 @@ fn raw_swarm_simultaneous_connect() { if rand::random::() < 0.1 { match swarm1.poll() { - Async::Ready(NetworkEvent::IncomingConnectionError { error: IncomingError::DeniedLowerPriority, .. }) => { + Async::Ready(NetworkEvent::IncomingConnectionError { + error: IncomingError::DeniedLowerPriority, .. + }) => { assert_eq!(swarm1_step, 2); swarm1_step = 3; }, Async::Ready(NetworkEvent::Connected { conn_info, .. }) => { assert_eq!(conn_info, *swarm2.local_peer_id()); + if swarm1_step == 0 { + // The connection was established before + // swarm1 started dialing; discard the test run. + return Ok(Async::Ready(false)) + } assert_eq!(swarm1_step, 1); swarm1_step = 2; }, @@ -243,12 +253,19 @@ fn raw_swarm_simultaneous_connect() { if rand::random::() < 0.1 { match swarm2.poll() { - Async::Ready(NetworkEvent::IncomingConnectionError { error: IncomingError::DeniedLowerPriority, .. }) => { + Async::Ready(NetworkEvent::IncomingConnectionError { + error: IncomingError::DeniedLowerPriority, .. + }) => { assert_eq!(swarm2_step, 2); swarm2_step = 3; }, Async::Ready(NetworkEvent::Connected { conn_info, .. }) => { assert_eq!(conn_info, *swarm1.local_peer_id()); + if swarm2_step == 0 { + // The connection was established before + // swarm2 started dialing; discard the test run. + return Ok(Async::Ready(false)) + } assert_eq!(swarm2_step, 1); swarm2_step = 2; }, @@ -267,7 +284,7 @@ fn raw_swarm_simultaneous_connect() { // TODO: make sure that >= 5 is correct if swarm1_step + swarm2_step >= 5 { - return Ok(Async::Ready(())); + return Ok(Async::Ready(true)); } if swarm1_not_ready && swarm2_not_ready { @@ -276,11 +293,23 @@ fn raw_swarm_simultaneous_connect() { } }); - reactor.block_on(future).unwrap(); - - // We now disconnect them again. - swarm1.peer(swarm2.local_peer_id().clone()).into_connected().unwrap().close(); - swarm2.peer(swarm1.local_peer_id().clone()).into_connected().unwrap().close(); + if reactor.block_on(future).unwrap() { + // The test exercised what we wanted to exercise: a simultaneous connect. + break + } else { + // The test did not trigger a simultaneous connect; ensure the nodes + // are disconnected and re-run the test. + match swarm1.peer(swarm2.local_peer_id().clone()) { + Peer::Connected(p) => p.close(), + Peer::PendingConnect(p) => p.interrupt(), + x => panic!("Unexpected state for swarm1: {:?}", x) + } + match swarm2.peer(swarm1.local_peer_id().clone()) { + Peer::Connected(p) => p.close(), + Peer::PendingConnect(p) => p.interrupt(), + x => panic!("Unexpected state for swarm2: {:?}", x) + } + } } } } From 0172a49a058f28bd65ab1c3769b372c393944849 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Mon, 29 Jul 2019 22:46:21 +0200 Subject: [PATCH 13/14] Complete negotiation between upgrades in libp2p-core. While multistream-select, as a standalone library and providing an API at the granularity of a single negotiation, supports lazy negotiation (and in particular 0-RTT negotiation), in the context of libp2p-core where any number of negotiations are composed generically within the concept of composable "upgrades", it is necessary to wait for protocol negotiation between upgrades to complete. --- core/src/upgrade/apply.rs | 49 +++++++++++++++++-- core/src/upgrade/mod.rs | 2 +- misc/multistream-select/src/lib.rs | 58 +++++++++++++++++++++-- misc/multistream-select/src/negotiated.rs | 23 +++++---- 4 files changed, 111 insertions(+), 21 deletions(-) diff --git a/core/src/upgrade/apply.rs b/core/src/upgrade/apply.rs index 982cd0295d5..bebc9492de2 100644 --- a/core/src/upgrade/apply.rs +++ b/core/src/upgrade/apply.rs @@ -19,7 +19,8 @@ // DEALINGS IN THE SOFTWARE. use crate::ConnectedPoint; -use crate::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeError, ProtocolName}; +use crate::upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeError}; +use crate::upgrade::{ProtocolName, NegotiatedComplete}; use futures::{future::Either, prelude::*}; use log::debug; use multistream_select::{self, DialerSelectFuture, ListenerSelectFuture}; @@ -84,6 +85,11 @@ where future: ListenerSelectFuture>, upgrade: U, }, + AwaitNegotiated { + io: NegotiatedComplete, + protocol: U::Info, + upgrade: U + }, Upgrade { future: U::Future }, @@ -109,8 +115,24 @@ where return Ok(Async::NotReady) } }; + self.inner = InboundUpgradeApplyState::AwaitNegotiated { + io: connection.complete(), + protocol: info.0, + upgrade + }; + } + InboundUpgradeApplyState::AwaitNegotiated { mut io, protocol, upgrade } => { + let io = match io.poll()? { + Async::NotReady => { + self.inner = InboundUpgradeApplyState::AwaitNegotiated { + io, protocol, upgrade + }; + return Ok(Async::NotReady) + } + Async::Ready(io) => io + }; self.inner = InboundUpgradeApplyState::Upgrade { - future: upgrade.upgrade_inbound(connection, info.0) + future: upgrade.upgrade_inbound(io, protocol) }; } InboundUpgradeApplyState::Upgrade { mut future } => { @@ -154,6 +176,11 @@ where future: DialerSelectFuture::IntoIter>>, upgrade: U }, + AwaitNegotiated { + io: NegotiatedComplete, + upgrade: U, + protocol: U::Info + }, Upgrade { future: U::Future }, @@ -179,8 +206,24 @@ where return Ok(Async::NotReady) } }; + self.inner = OutboundUpgradeApplyState::AwaitNegotiated { + io: connection.complete(), + protocol: info.0, + upgrade + }; + } + OutboundUpgradeApplyState::AwaitNegotiated { mut io, protocol, upgrade } => { + let io = match io.poll()? { + Async::NotReady => { + self.inner = OutboundUpgradeApplyState::AwaitNegotiated { + io, protocol, upgrade + }; + return Ok(Async::NotReady) + } + Async::Ready(io) => io + }; self.inner = OutboundUpgradeApplyState::Upgrade { - future: upgrade.upgrade_outbound(connection, info.0) + future: upgrade.upgrade_outbound(io, protocol) }; } OutboundUpgradeApplyState::Upgrade { mut future } => { diff --git a/core/src/upgrade/mod.rs b/core/src/upgrade/mod.rs index 4d26dbe9a3f..7403655f513 100644 --- a/core/src/upgrade/mod.rs +++ b/core/src/upgrade/mod.rs @@ -68,7 +68,7 @@ mod transfer; use futures::future::Future; -pub use multistream_select::{Negotiated, NegotiationError, ProtocolError}; +pub use multistream_select::{Negotiated, NegotiatedComplete, NegotiationError, ProtocolError}; pub use self::{ apply::{apply, apply_inbound, apply_outbound, InboundUpgradeApply, OutboundUpgradeApply}, denied::DeniedUpgrade, diff --git a/misc/multistream-select/src/lib.rs b/misc/multistream-select/src/lib.rs index c160afc45f4..c41821a6dc2 100644 --- a/misc/multistream-select/src/lib.rs +++ b/misc/multistream-select/src/lib.rs @@ -30,9 +30,9 @@ //! ## Roles //! //! Two peers using the multistream-select negotiation protocol on an I/O stream -//! are distinguished by their role as a _dialer_ or a _listener_. Thereby the dialer -//! (or _initiator_) plays the active part, driving the protocol, whereas the listener -//! (or _responder_) reacts to the messages received. +//! are distinguished by their role as a _dialer_ (or _initiator_) or as a _listener_ +//! (or _responder_). Thereby the dialer plays the active part, driving the protocol, +//! whereas the listener reacts to the messages received. //! //! The dialer has two options: it can either pick a protocol from the complete list //! of protocols that the listener supports, or it can directly suggest a protocol. @@ -40,11 +40,59 @@ //! echoing the same protocol) or reject (by responding with a message stating //! "not available"). If a suggested protocol is not available, the dialer may //! suggest another protocol. This process continues until a protocol is agreed upon, -//! yielding a [`Negotiated`](self::Negotiated) stream, or the dialer has run out of alternatives. +//! yielding a [`Negotiated`](self::Negotiated) stream, or the dialer has run out of +//! alternatives. //! //! See [`dialer_select_proto`](self::dialer_select_proto) and //! [`listener_select_proto`](self::listener_select_proto). //! +//! ## [`Negotiated`](self::Negotiated) +//! +//! When a dialer or listener participating in a negotiation settles +//! on a protocol to use, the [`DialerSelectFuture`] respectively +//! [`ListenerSelectFuture`] yields a [`Negotiated`](self::Negotiated) +//! I/O stream. +//! +//! Notably, when a `DialerSelectFuture` resolves to a `Negotiated`, it may not yet +//! have written the last negotiation message to the underlying I/O stream and may +//! still be expecting confirmation for that protocol, despite having settled on +//! a protocol to use. +//! +//! Similarly, when a `ListenerSelectFuture` resolves to a `Negotiated`, it may not +//! yet have sent the last negotiation message despite having settled on a protocol +//! proposed by the dialer that it supports. +//! +//! This behaviour allows both the dialer and the listener to send data +//! relating to the negotiated protocol together with the last negotiation +//! message(s), which, in the case of the dialer only supporting a single +//! protocol, results in 0-RTT negotiation. +//! +//! However, to avoid pitfalls, the following rules should be observed: +//! +//! 1. When a dialer nests multiple `DialerSelectFuture`s, i.e. performs +//! multiple nested protocol negotiations, it should ensure completion +//! of the previous negotiation before starting the next negotiation, +//! which can be accomplished by waiting for the future returned by +//! [`Negotiated::complete`] of the previous negotiation to resolve. +//! This avoids problematic cases like \[[1]\] whereby the listener may +//! erroneously process a request for which the dialer considers the +//! negotiation to have failed. +//! +//! 2. When a listener cannot assume that a dialer will always sent request +//! data together with its last protocol proposal, i.e. the dialer may wait for +//! protocol confirmation before sending request data, the listener should +//! always flush its negotiation responses before processing request data +//! and any sending of response data. Otherwise a dialer may be waiting for +//! protocol confirmation from the listener before sending a request while +//! the listener waits for the request before sending the response together with the +//! protocol confirmation. Just like for the dialer, this can be accomplished +//! by waiting for the future returned by [`Negotiated::complete`] +//! to resolve before continuing to process (and possibly wait for) the +//! request data, followed by sending any response data. +//! +//! [1]: https://github.com/multiformats/go-multistream/issues/20 +//! [`Negotiated::complete`]: self::Negotiated::complete +//! //! ## Examples //! //! For a dialer: @@ -82,7 +130,7 @@ mod negotiated; mod protocol; mod tests; -pub use self::negotiated::{Negotiated, NegotiationError}; +pub use self::negotiated::{Negotiated, NegotiatedComplete, NegotiationError}; pub use self::protocol::ProtocolError; pub use self::dialer_select::{dialer_select_proto, DialerSelectFuture}; pub use self::listener_select::{listener_select_proto, ListenerSelectFuture}; diff --git a/misc/multistream-select/src/negotiated.rs b/misc/multistream-select/src/negotiated.rs index 9bb87513a07..6269807b506 100644 --- a/misc/multistream-select/src/negotiated.rs +++ b/misc/multistream-select/src/negotiated.rs @@ -28,8 +28,8 @@ use std::{mem, io, fmt, error::Error}; /// An I/O stream that has settled on an (application-layer) protocol to use. /// /// A `Negotiated` represents an I/O stream that has _settled_ on a protocol -/// to use. In particular, it does not imply that all of the protocol negotiation -/// frames have been sent and / or received, just that the selected protocol +/// to use. In particular, it is not implied that all of the protocol negotiation +/// frames have yet been sent and / or received, just that the selected protocol /// is fully determined. This is to allow the last protocol negotiation frames /// sent by a peer to be combined in a single write, possibly piggy-backing /// data from the negotiated protocol on top. @@ -40,16 +40,13 @@ use std::{mem, io, fmt, error::Error}; /// the protocol negotiation, not a single negotiation message may yet have /// been sent, if the dialer only supports a single protocol. In that case, /// the dialer "settles" on that protocol immediately and expects it to -/// be confirmed by the remote, as it has no alternatives. Once the -/// `Negotiated` I/O resource is flushed, possibly after writing additional -/// data related to the negotiated protocol, all of the buffered frames relating to -/// protocol selection are sent together with that data. The dialer still expects -/// to receive acknowledgment of the protocol before it can continue reading data -/// from the remote related to the negotiated protocol. -/// The `Negotiated` stream may ultimately still fail protocol negotiation, if -/// the protocol that the dialer has settled on is not actually supported +/// be confirmed by the remote. Once the `Negotiated` I/O stream is flushed, +/// possibly after writing additional data related to the negotiated protocol, +/// all of the buffered frames relating to protocol selection are sent together +/// with that data. The `Negotiated` stream may ultimately still fail protocol +/// negotiation, if the protocol that the dialer has settled on is not actually supported /// by the listener, but having settled on that protocol the dialer has by -/// definition no more alternatives and hence such a failed negotiation is +/// definition no further alternatives and hence such a failed negotiation is /// usually equivalent to a failed request made using the desired protocol. /// If an application wishes to only start using the `Negotiated` stream /// once protocol negotiation fully completed, it may wait on completion @@ -58,10 +55,12 @@ use std::{mem, io, fmt, error::Error}; /// * If a `Negotiated` is obtained by the peer with the role of the listener in /// the protocol negotiation, the final confirmation message for the remote's /// selected protocol may not yet have been sent. Once the `Negotiated` I/O -/// resource is flushed, possibly after writing additional data related to the +/// stream is flushed, possibly after writing additional data related to the /// negotiated protocol, e.g. a response, the buffered frames relating to protocol /// acknowledgement are sent together with that data. /// +/// See also the [crate documentation](crate) for details about lazy protocol +/// negotiation. pub struct Negotiated { state: State } From c1f99b9c1c09dfdcb824007aca28f8e319056f03 Mon Sep 17 00:00:00 2001 From: "Roman S. Borschel" Date: Tue, 30 Jul 2019 09:24:24 +0200 Subject: [PATCH 14/14] Clarify docs. Simplify listener upgrades. Since reading from a Negotiated I/O stream implicitly flushes any pending negotiation data, there is no pitfall involved in not waiting for completion. --- core/src/upgrade/apply.rs | 25 ++------------- misc/multistream-select/src/dialer_select.rs | 12 +++++++ misc/multistream-select/src/lib.rs | 33 ++++---------------- misc/multistream-select/src/negotiated.rs | 29 ++--------------- 4 files changed, 22 insertions(+), 77 deletions(-) diff --git a/core/src/upgrade/apply.rs b/core/src/upgrade/apply.rs index bebc9492de2..787ec4c4574 100644 --- a/core/src/upgrade/apply.rs +++ b/core/src/upgrade/apply.rs @@ -85,11 +85,6 @@ where future: ListenerSelectFuture>, upgrade: U, }, - AwaitNegotiated { - io: NegotiatedComplete, - protocol: U::Info, - upgrade: U - }, Upgrade { future: U::Future }, @@ -108,31 +103,15 @@ where loop { match mem::replace(&mut self.inner, InboundUpgradeApplyState::Undefined) { InboundUpgradeApplyState::Init { mut future, upgrade } => { - let (info, connection) = match future.poll()? { + let (info, io) = match future.poll()? { Async::Ready(x) => x, Async::NotReady => { self.inner = InboundUpgradeApplyState::Init { future, upgrade }; return Ok(Async::NotReady) } }; - self.inner = InboundUpgradeApplyState::AwaitNegotiated { - io: connection.complete(), - protocol: info.0, - upgrade - }; - } - InboundUpgradeApplyState::AwaitNegotiated { mut io, protocol, upgrade } => { - let io = match io.poll()? { - Async::NotReady => { - self.inner = InboundUpgradeApplyState::AwaitNegotiated { - io, protocol, upgrade - }; - return Ok(Async::NotReady) - } - Async::Ready(io) => io - }; self.inner = InboundUpgradeApplyState::Upgrade { - future: upgrade.upgrade_inbound(io, protocol) + future: upgrade.upgrade_inbound(io, info.0) }; } InboundUpgradeApplyState::Upgrade { mut future } => { diff --git a/misc/multistream-select/src/dialer_select.rs b/misc/multistream-select/src/dialer_select.rs index 991e57b681f..dc39f753230 100644 --- a/misc/multistream-select/src/dialer_select.rs +++ b/misc/multistream-select/src/dialer_select.rs @@ -41,6 +41,18 @@ use crate::{Negotiated, NegotiationError}; /// based on the number of protocols given. The number of protocols is /// determined through the `size_hint` of the given iterator and thus /// an inaccurate size estimate may result in a suboptimal choice. +/// +/// > **Note**: When multiple `DialerSelectFuture`s are composed, i.e. a +/// > dialer performs multiple, nested protocol negotiations with just a +/// > single supported protocol (0-RTT negotiations), a listener that +/// > does not support one of the intermediate protocols may still process +/// > the request data associated with a supported follow-up protocol. +/// > See \[[1]\]. To avoid this behaviour, a dialer should ensure completion +/// > of the previous negotiation before starting the next negotiation, +/// > which can be accomplished by waiting for the future returned by +/// > [`Negotiated::complete`] to resolve. +/// +/// [1]: https://github.com/multiformats/go-multistream/issues/20 pub fn dialer_select_proto(inner: R, protocols: I) -> DialerSelectFuture where R: AsyncRead + AsyncWrite, diff --git a/misc/multistream-select/src/lib.rs b/misc/multistream-select/src/lib.rs index c41821a6dc2..9dd89e3cbe2 100644 --- a/misc/multistream-select/src/lib.rs +++ b/misc/multistream-select/src/lib.rs @@ -62,36 +62,15 @@ //! yet have sent the last negotiation message despite having settled on a protocol //! proposed by the dialer that it supports. //! +//! //! This behaviour allows both the dialer and the listener to send data //! relating to the negotiated protocol together with the last negotiation //! message(s), which, in the case of the dialer only supporting a single -//! protocol, results in 0-RTT negotiation. -//! -//! However, to avoid pitfalls, the following rules should be observed: -//! -//! 1. When a dialer nests multiple `DialerSelectFuture`s, i.e. performs -//! multiple nested protocol negotiations, it should ensure completion -//! of the previous negotiation before starting the next negotiation, -//! which can be accomplished by waiting for the future returned by -//! [`Negotiated::complete`] of the previous negotiation to resolve. -//! This avoids problematic cases like \[[1]\] whereby the listener may -//! erroneously process a request for which the dialer considers the -//! negotiation to have failed. -//! -//! 2. When a listener cannot assume that a dialer will always sent request -//! data together with its last protocol proposal, i.e. the dialer may wait for -//! protocol confirmation before sending request data, the listener should -//! always flush its negotiation responses before processing request data -//! and any sending of response data. Otherwise a dialer may be waiting for -//! protocol confirmation from the listener before sending a request while -//! the listener waits for the request before sending the response together with the -//! protocol confirmation. Just like for the dialer, this can be accomplished -//! by waiting for the future returned by [`Negotiated::complete`] -//! to resolve before continuing to process (and possibly wait for) the -//! request data, followed by sending any response data. -//! -//! [1]: https://github.com/multiformats/go-multistream/issues/20 -//! [`Negotiated::complete`]: self::Negotiated::complete +//! protocol, results in 0-RTT negotiation. Note, however, that a dialer +//! that performs multiple 0-RTT negotiations in sequence for different +//! protocols layered on top of each other may trigger undesirable behaviour +//! for a listener not supporting one of the intermediate protocols. +//! See [`dialer_select_proto`](self::dialer_select_proto). //! //! ## Examples //! diff --git a/misc/multistream-select/src/negotiated.rs b/misc/multistream-select/src/negotiated.rs index 6269807b506..cb63adcf685 100644 --- a/misc/multistream-select/src/negotiated.rs +++ b/misc/multistream-select/src/negotiated.rs @@ -34,33 +34,8 @@ use std::{mem, io, fmt, error::Error}; /// sent by a peer to be combined in a single write, possibly piggy-backing /// data from the negotiated protocol on top. /// -/// Specifically that means: -/// -/// * If a `Negotiated` is obtained by the peer with the role of the dialer in -/// the protocol negotiation, not a single negotiation message may yet have -/// been sent, if the dialer only supports a single protocol. In that case, -/// the dialer "settles" on that protocol immediately and expects it to -/// be confirmed by the remote. Once the `Negotiated` I/O stream is flushed, -/// possibly after writing additional data related to the negotiated protocol, -/// all of the buffered frames relating to protocol selection are sent together -/// with that data. The `Negotiated` stream may ultimately still fail protocol -/// negotiation, if the protocol that the dialer has settled on is not actually supported -/// by the listener, but having settled on that protocol the dialer has by -/// definition no further alternatives and hence such a failed negotiation is -/// usually equivalent to a failed request made using the desired protocol. -/// If an application wishes to only start using the `Negotiated` stream -/// once protocol negotiation fully completed, it may wait on completion -/// of the `Future` obtained from [`Negotiated::complete`]. -/// -/// * If a `Negotiated` is obtained by the peer with the role of the listener in -/// the protocol negotiation, the final confirmation message for the remote's -/// selected protocol may not yet have been sent. Once the `Negotiated` I/O -/// stream is flushed, possibly after writing additional data related to the -/// negotiated protocol, e.g. a response, the buffered frames relating to protocol -/// acknowledgement are sent together with that data. -/// -/// See also the [crate documentation](crate) for details about lazy protocol -/// negotiation. +/// Reading from a `Negotiated` I/O stream that still has pending negotiation +/// protocol data to send implicitly triggers flushing of all yet unsent data. pub struct Negotiated { state: State }