multistream-select-0.13.0/.cargo_vcs_info.json0000644000000001650000000000100147420ustar { "git": { "sha1": "42566869b2ab3d1903d164c84da3308e7e890b8b" }, "path_in_vcs": "misc/multistream-select" }multistream-select-0.13.0/CHANGELOG.md000064400000000000000000000063271046102023000153510ustar 00000000000000## 0.13.0 - Don't wait for negotiation on `::poll_close`. This can save one round-trip for protocols that use stream closing as an operation in ones protocol, e.g. using stream closing to signal the end of a request. See [PR 4019] for details. - Raise MSRV to 1.65. See [PR 3715]. [PR 4019]: https://github.com/libp2p/rust-libp2p/pull/4019 [PR 3715]: https://github.com/libp2p/rust-libp2p/pull/3715 ## 0.12.1 - Update `rust-version` to reflect the actual MSRV: 1.60.0. See [PR 3090]. [PR 3090]: https://github.com/libp2p/rust-libp2p/pull/3090 ## 0.12.0 - Remove parallel dialing optimization, to avoid requiring the use of the `ls` command. See [PR 2934]. [PR 2934]: https://github.com/libp2p/rust-libp2p/pull/2934 ## 0.11.0 [2022-01-27] - Migrate to Rust edition 2021 (see [PR 2339]). [PR 2339]: https://github.com/libp2p/rust-libp2p/pull/2339 ## 0.10.4 [2021-11-01] - Implement `From for ProtocolError` instead of `Into`. [PR 2169](https://github.com/libp2p/rust-libp2p/pull/2169) ## 0.10.3 [2021-03-17] - Update dependencies. ## 0.10.2 [2021-03-01] - Re-enable "parallel negotiation" if the dialer has 3 or more alternative protocols. [PR 1934](https://github.com/libp2p/rust-libp2p/pull/1934) ## 0.10.1 [2021-02-15] - Update dependencies. ## 0.10.0 [2021-01-12] - Update dependencies. ## 0.9.1 [2020-12-02] - Ensure uniform outcomes for failed negotiations with both `V1` and `V1Lazy`. [PR 1871](https://github.com/libp2p/rust-libp2p/pull/1871) ## 0.9.0 [2020-11-25] - Make the `V1Lazy` upgrade strategy more interoperable with `V1`. Specifically, the listener now behaves identically with `V1` and `V1Lazy`. Furthermore, the multistream-select protocol header is now also identical, making `V1` and `V1Lazy` indistinguishable on the wire. The remaining central effect of `V1Lazy` is that the dialer, if it only supports a single protocol in a negotiation, optimistically settles on that protocol without immediately flushing the negotiation data (i.e. protocol proposal) and without waiting for the corresponding confirmation before it is able to start sending application data, expecting the used protocol to be confirmed with the response. - Fix the encoding and decoding of `ls` responses to be spec-compliant and interoperable with other implementations. For a clean upgrade, `0.8.4` must already be deployed. ## 0.8.5 [2020-11-09] - During negotiation do not interpret EOF error as an IO error, but instead as a negotiation error. See https://github.com/libp2p/rust-libp2p/pull/1823. ## 0.8.4 [2020-10-20] - Temporarily disable the internal selection of "parallel" protocol negotiation for the dialer to later change the response format of the "ls" message for spec compliance. See https://github.com/libp2p/rust-libp2p/issues/1795. ## 0.8.3 [2020-10-16] - Fix a regression resulting in a panic with the `V1Lazy` protocol. [PR 1783](https://github.com/libp2p/rust-libp2p/pull/1783). - Fix a potential deadlock during protocol negotiation due to a missing flush, potentially resulting in sporadic protocol upgrade timeouts. [PR 1781](https://github.com/libp2p/rust-libp2p/pull/1781). - Update dependencies. ## 0.8.2 [2020-06-22] - Updated dependencies. multistream-select-0.13.0/Cargo.toml0000644000000030050000000000100127340ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.65.0" name = "multistream-select" version = "0.13.0" authors = ["Parity Technologies "] description = "Multistream-select negotiation protocol for libp2p" keywords = [ "peer-to-peer", "libp2p", "networking", ] categories = [ "network-programming", "asynchronous", ] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" [package.metadata.docs.rs] all-features = true rustc-args = [ "--cfg", "docsrs", ] rustdoc-args = [ "--cfg", "docsrs", ] [dependencies.bytes] version = "1" [dependencies.futures] version = "0.3" [dependencies.log] version = "0.4" [dependencies.pin-project] version = "1.1.0" [dependencies.smallvec] version = "1.6.1" [dependencies.unsigned-varint] version = "0.7" [dev-dependencies.async-std] version = "1.6.2" features = ["attributes"] [dev-dependencies.env_logger] version = "0.10" [dev-dependencies.futures_ringbuf] version = "0.4.0" [dev-dependencies.rand] version = "0.8" [dev-dependencies.rw-stream-sink] version = "0.4.0" multistream-select-0.13.0/Cargo.toml.orig000064400000000000000000000017441046102023000164250ustar 00000000000000[package] name = "multistream-select" edition = "2021" rust-version = { workspace = true } description = "Multistream-select negotiation protocol for libp2p" version = "0.13.0" authors = ["Parity Technologies "] license = "MIT" repository = "https://github.com/libp2p/rust-libp2p" keywords = ["peer-to-peer", "libp2p", "networking"] categories = ["network-programming", "asynchronous"] [dependencies] bytes = "1" futures = "0.3" log = "0.4" pin-project = "1.1.0" smallvec = "1.6.1" unsigned-varint = "0.7" [dev-dependencies] async-std = { version = "1.6.2", features = ["attributes"] } env_logger = "0.10" futures_ringbuf = "0.4.0" quickcheck = { workspace = true } rand = "0.8" rw-stream-sink = { workspace = true } # Passing arguments to the docsrs builder in order to properly document cfg's. # More information: https://docs.rs/about/builds#cross-compiling [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] rustc-args = ["--cfg", "docsrs"] multistream-select-0.13.0/src/dialer_select.rs000064400000000000000000000216101046102023000174640ustar 00000000000000// 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. //! Protocol negotiation strategies for the peer acting as the dialer. use crate::protocol::{HeaderLine, Message, MessageIO, Protocol, ProtocolError}; use crate::{Negotiated, NegotiationError, Version}; use futures::prelude::*; use std::{ convert::TryFrom as _, iter, mem, pin::Pin, task::{Context, Poll}, }; /// Returns a `Future` that negotiates a protocol on the given I/O stream /// for a peer acting as the _dialer_ (or _initiator_). /// /// 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. /// /// Within the scope of this library, a dialer always commits to a specific /// multistream-select [`Version`], whereas a listener always supports /// all versions supported by this library. Frictionless multistream-select /// protocol upgrades may thus proceed by deployments with updated listeners, /// eventually followed by deployments of dialers choosing the newer protocol. pub fn dialer_select_proto( inner: R, protocols: I, version: Version, ) -> DialerSelectFuture where R: AsyncRead + AsyncWrite, I: IntoIterator, I::Item: AsRef, { let protocols = protocols.into_iter().peekable(); DialerSelectFuture { version, protocols, state: State::SendHeader { io: MessageIO::new(inner), }, } } /// A `Future` returned by [`dialer_select_proto`] which negotiates /// a protocol iteratively by considering one protocol after the other. #[pin_project::pin_project] pub struct DialerSelectFuture { // TODO: It would be nice if eventually N = I::Item = Protocol. protocols: iter::Peekable, state: State, version: Version, } enum State { SendHeader { io: MessageIO }, SendProtocol { io: MessageIO, protocol: N }, FlushProtocol { io: MessageIO, protocol: N }, AwaitProtocol { io: MessageIO, protocol: N }, Done, } impl Future for DialerSelectFuture where // The Unpin bound here is required because we produce a `Negotiated` as the output. // It also makes the implementation considerably easier to write. R: AsyncRead + AsyncWrite + Unpin, I: Iterator, I::Item: AsRef, { type Output = Result<(I::Item, Negotiated), NegotiationError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); loop { match mem::replace(this.state, State::Done) { State::SendHeader { mut io } => { match Pin::new(&mut io).poll_ready(cx)? { Poll::Ready(()) => {} Poll::Pending => { *this.state = State::SendHeader { io }; return Poll::Pending; } } let h = HeaderLine::from(*this.version); if let Err(err) = Pin::new(&mut io).start_send(Message::Header(h)) { return Poll::Ready(Err(From::from(err))); } let protocol = this.protocols.next().ok_or(NegotiationError::Failed)?; // The dialer always sends the header and the first protocol // proposal in one go for efficiency. *this.state = State::SendProtocol { io, protocol }; } State::SendProtocol { mut io, protocol } => { match Pin::new(&mut io).poll_ready(cx)? { Poll::Ready(()) => {} Poll::Pending => { *this.state = State::SendProtocol { io, protocol }; return Poll::Pending; } } let p = Protocol::try_from(protocol.as_ref())?; if let Err(err) = Pin::new(&mut io).start_send(Message::Protocol(p.clone())) { return Poll::Ready(Err(From::from(err))); } log::debug!("Dialer: Proposed protocol: {}", p); if this.protocols.peek().is_some() { *this.state = State::FlushProtocol { io, protocol } } else { match this.version { Version::V1 => *this.state = State::FlushProtocol { io, protocol }, // This is the only effect that `V1Lazy` has compared to `V1`: // Optimistically settling on the only protocol that // the dialer supports for this negotiation. Notably, // the dialer expects a regular `V1` response. Version::V1Lazy => { log::debug!("Dialer: Expecting proposed protocol: {}", p); let hl = HeaderLine::from(Version::V1Lazy); let io = Negotiated::expecting(io.into_reader(), p, Some(hl)); return Poll::Ready(Ok((protocol, io))); } } } } State::FlushProtocol { mut io, protocol } => { match Pin::new(&mut io).poll_flush(cx)? { Poll::Ready(()) => *this.state = State::AwaitProtocol { io, protocol }, Poll::Pending => { *this.state = State::FlushProtocol { io, protocol }; return Poll::Pending; } } } State::AwaitProtocol { mut io, protocol } => { let msg = match Pin::new(&mut io).poll_next(cx)? { Poll::Ready(Some(msg)) => msg, Poll::Pending => { *this.state = State::AwaitProtocol { io, protocol }; return Poll::Pending; } // Treat EOF error as [`NegotiationError::Failed`], not as // [`NegotiationError::ProtocolError`], allowing dropping or closing an I/O // stream as a permissible way to "gracefully" fail a negotiation. Poll::Ready(None) => return Poll::Ready(Err(NegotiationError::Failed)), }; match msg { Message::Header(v) if v == HeaderLine::from(*this.version) => { *this.state = State::AwaitProtocol { io, protocol }; } Message::Protocol(ref p) if p.as_ref() == protocol.as_ref() => { log::debug!("Dialer: Received confirmation for protocol: {}", p); let io = Negotiated::completed(io.into_inner()); return Poll::Ready(Ok((protocol, io))); } Message::NotAvailable => { log::debug!( "Dialer: Received rejection of protocol: {}", protocol.as_ref() ); let protocol = this.protocols.next().ok_or(NegotiationError::Failed)?; *this.state = State::SendProtocol { io, protocol } } _ => return Poll::Ready(Err(ProtocolError::InvalidMessage.into())), } } State::Done => panic!("State::poll called after completion"), } } } } multistream-select-0.13.0/src/length_delimited.rs000064400000000000000000000461341046102023000201760ustar 00000000000000// 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. use bytes::{Buf as _, BufMut as _, Bytes, BytesMut}; use futures::{io::IoSlice, prelude::*}; use std::{ convert::TryFrom as _, io, pin::Pin, task::{Context, Poll}, u16, }; 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; /// 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 /// unlikely to be more than 16KiB long. #[pin_project::pin_project] #[derive(Debug)] pub(crate) struct LengthDelimited { /// The inner I/O resource. #[pin] inner: R, /// Read buffer for a single incoming unsigned-varint length-delimited frame. read_buffer: BytesMut, /// 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. read_state: ReadState, } #[derive(Debug, Copy, Clone, PartialEq, Eq)] 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 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(crate) fn new(inner: R) -> LengthDelimited { LengthDelimited { inner, read_state: ReadState::default(), read_buffer: BytesMut::with_capacity(DEFAULT_BUFFER_SIZE), write_buffer: BytesMut::with_capacity(DEFAULT_BUFFER_SIZE + MAX_LEN_BYTES as usize), } } /// Drops the [`LengthDelimited`] resource, yielding 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 `Bytes` frame. The write buffer is guaranteed to be empty after /// flushing. pub(crate) fn into_inner(self) -> R { assert!(self.read_buffer.is_empty()); assert!(self.write_buffer.is_empty()); self.inner } /// 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(crate) 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 `Poll::Ready`, the write buffer of frames /// submitted to the `Sink` is guaranteed to be empty. fn poll_write_buffer(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> where R: AsyncWrite, { let mut this = self.project(); while !this.write_buffer.is_empty() { match this.inner.as_mut().poll_write(cx, this.write_buffer) { Poll::Pending => return Poll::Pending, Poll::Ready(Ok(0)) => { return Poll::Ready(Err(io::Error::new( io::ErrorKind::WriteZero, "Failed to write buffered frame.", ))) } Poll::Ready(Ok(n)) => this.write_buffer.advance(n), Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), } } Poll::Ready(Ok(())) } } impl Stream for LengthDelimited where R: AsyncRead, { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); loop { match this.read_state { ReadState::ReadLength { buf, pos } => { match this.inner.as_mut().poll_read(cx, &mut buf[*pos..*pos + 1]) { Poll::Ready(Ok(0)) => { if *pos == 0 { return Poll::Ready(None); } else { return Poll::Ready(Some(Err(io::ErrorKind::UnexpectedEof.into()))); } } Poll::Ready(Ok(n)) => { debug_assert_eq!(n, 1); *pos += n; } Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(err))), Poll::Pending => return Poll::Pending, }; if (buf[*pos - 1] & 0x80) == 0 { // MSB is not set, indicating the end of the length prefix. let (len, _) = unsigned_varint::decode::u16(buf).map_err(|e| { log::debug!("invalid length prefix: {}", e); io::Error::new(io::ErrorKind::InvalidData, "invalid length prefix") })?; if len >= 1 { *this.read_state = ReadState::ReadData { len, pos: 0 }; this.read_buffer.resize(len as usize, 0); } else { debug_assert_eq!(len, 0); *this.read_state = ReadState::default(); return Poll::Ready(Some(Ok(Bytes::new()))); } } 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 Poll::Ready(Some(Err(io::Error::new( io::ErrorKind::InvalidData, "Maximum frame length exceeded", )))); } } ReadState::ReadData { len, pos } => { match this .inner .as_mut() .poll_read(cx, &mut this.read_buffer[*pos..]) { Poll::Ready(Ok(0)) => { return Poll::Ready(Some(Err(io::ErrorKind::UnexpectedEof.into()))) } Poll::Ready(Ok(n)) => *pos += n, Poll::Pending => return Poll::Pending, Poll::Ready(Err(err)) => return Poll::Ready(Some(Err(err))), }; if *pos == *len as usize { // Finished reading the frame. let frame = this.read_buffer.split_off(0).freeze(); *this.read_state = ReadState::default(); return Poll::Ready(Some(Ok(frame))); } } } } } } impl Sink for LengthDelimited where R: AsyncWrite, { type Error = io::Error; fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // 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.as_mut().project().write_buffer.len() >= MAX_FRAME_SIZE as usize { match self.as_mut().poll_write_buffer(cx) { Poll::Ready(Ok(())) => {} Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), Poll::Pending => return Poll::Pending, } debug_assert!(self.as_mut().project().write_buffer.is_empty()); } Poll::Ready(Ok(())) } fn start_send(self: Pin<&mut Self>, item: Bytes) -> Result<(), Self::Error> { let this = self.project(); let len = match u16::try_from(item.len()) { Ok(len) if len <= MAX_FRAME_SIZE => len, _ => { return Err(io::Error::new( io::ErrorKind::InvalidData, "Maximum frame size exceeded.", )) } }; let mut uvi_buf = unsigned_varint::encode::u16_buffer(); let uvi_len = unsigned_varint::encode::u16(len, &mut uvi_buf); this.write_buffer.reserve(len as usize + uvi_len.len()); this.write_buffer.put(uvi_len); this.write_buffer.put(item); Ok(()) } fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // Write all buffered frame data to the underlying I/O stream. match LengthDelimited::poll_write_buffer(self.as_mut(), cx) { Poll::Ready(Ok(())) => {} Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), Poll::Pending => return Poll::Pending, } let this = self.project(); debug_assert!(this.write_buffer.is_empty()); // Flush the underlying I/O stream. this.inner.poll_flush(cx) } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // Write all buffered frame data to the underlying I/O stream. match LengthDelimited::poll_write_buffer(self.as_mut(), cx) { Poll::Ready(Ok(())) => {} Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), Poll::Pending => return Poll::Pending, } let this = self.project(); debug_assert!(this.write_buffer.is_empty()); // Close the underlying I/O stream. this.inner.poll_close(cx) } } /// A `LengthDelimitedReader` implements a `Stream` of uvi-length-delimited /// frames on an underlying I/O resource combined with direct `AsyncWrite` access. #[pin_project::pin_project] #[derive(Debug)] pub(crate) struct LengthDelimitedReader { #[pin] inner: LengthDelimited, } impl LengthDelimitedReader { /// 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_next`] /// yield a new `Message`. The write buffer is guaranteed to be empty whenever /// [`LengthDelimited::poll_write_buffer`] yields [`Poll::Ready`] or after /// the [`Sink`] has been completely flushed via [`Sink::poll_flush`]. pub(crate) fn into_inner(self) -> R { self.inner.into_inner() } } impl Stream for LengthDelimitedReader where R: AsyncRead, { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_next(cx) } } impl AsyncWrite for LengthDelimitedReader where R: AsyncWrite, { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { // `this` here designates the `LengthDelimited`. let mut this = self.project().inner; // We need to flush any data previously written with the `LengthDelimited`. match LengthDelimited::poll_write_buffer(this.as_mut(), cx) { Poll::Ready(Ok(())) => {} Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), Poll::Pending => return Poll::Pending, } debug_assert!(this.write_buffer.is_empty()); this.project().inner.poll_write(cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_close(cx) } fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { // `this` here designates the `LengthDelimited`. let mut this = self.project().inner; // We need to flush any data previously written with the `LengthDelimited`. match LengthDelimited::poll_write_buffer(this.as_mut(), cx) { Poll::Ready(Ok(())) => {} Poll::Ready(Err(err)) => return Poll::Ready(Err(err)), Poll::Pending => return Poll::Pending, } debug_assert!(this.write_buffer.is_empty()); this.project().inner.poll_write_vectored(cx, bufs) } } #[cfg(test)] mod tests { use crate::length_delimited::LengthDelimited; use futures::{io::Cursor, prelude::*}; use quickcheck::*; use std::io::ErrorKind; #[test] fn basic_read() { let data = vec![6, 9, 8, 7, 6, 5, 4]; let framed = LengthDelimited::new(Cursor::new(data)); let recved = futures::executor::block_on(framed.try_collect::>()).unwrap(); assert_eq!(recved, vec![vec![9, 8, 7, 6, 5, 4]]); } #[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)); let recved = futures::executor::block_on(framed.try_collect::>()).unwrap(); assert_eq!(recved, vec![vec![9, 8, 7, 6, 5, 4], vec![9, 8, 7]]); } #[test] fn two_bytes_long_packet() { let len = 5000u16; assert!(len < (1 << 15)); 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 mut framed = LengthDelimited::new(Cursor::new(data)); let recved = futures::executor::block_on(async move { framed.next().await }).unwrap(); assert_eq!(recved.unwrap(), frame); } #[test] fn packet_len_too_long() { let mut data = vec![0x81, 0x81, 0x1]; data.extend((0..16513).map(|_| 0)); let mut framed = LengthDelimited::new(Cursor::new(data)); let recved = futures::executor::block_on(async move { framed.next().await.unwrap() }); if let Err(io_err) = recved { assert_eq!(io_err.kind(), ErrorKind::InvalidData) } else { panic!() } } #[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)); let recved = futures::executor::block_on(framed.try_collect::>()).unwrap(); assert_eq!( recved, vec![ vec![], vec![], vec![9, 8, 7, 6, 5, 4], vec![], vec![9, 8, 7], ] ); } #[test] fn unexpected_eof_in_len() { let data = vec![0x89]; let framed = LengthDelimited::new(Cursor::new(data)); let recved = futures::executor::block_on(framed.try_collect::>()); if let Err(io_err) = recved { assert_eq!(io_err.kind(), ErrorKind::UnexpectedEof) } else { panic!() } } #[test] fn unexpected_eof_in_data() { let data = vec![5]; let framed = LengthDelimited::new(Cursor::new(data)); let recved = futures::executor::block_on(framed.try_collect::>()); if let Err(io_err) = recved { assert_eq!(io_err.kind(), ErrorKind::UnexpectedEof) } else { panic!() } } #[test] fn unexpected_eof_in_data2() { let data = vec![5, 9, 8, 7]; let framed = LengthDelimited::new(Cursor::new(data)); let recved = futures::executor::block_on(framed.try_collect::>()); if let Err(io_err) = recved { assert_eq!(io_err.kind(), ErrorKind::UnexpectedEof) } else { panic!() } } #[test] fn writing_reading() { fn prop(frames: Vec>) -> TestResult { let (client_connection, server_connection) = futures_ringbuf::Endpoint::pair(100, 100); async_std::task::block_on(async move { let expected_frames = frames.clone(); let server = async_std::task::spawn(async move { let mut connec = rw_stream_sink::RwStreamSink::new(LengthDelimited::new(server_connection)); let mut buf = vec![0u8; 0]; for expected in expected_frames { if expected.is_empty() { continue; } if buf.len() < expected.len() { buf.resize(expected.len(), 0); } let n = connec.read(&mut buf).await.unwrap(); assert_eq!(&buf[..n], &expected[..]); } }); let client = async_std::task::spawn(async move { let mut connec = LengthDelimited::new(client_connection); for frame in frames { connec.send(From::from(frame)).await.unwrap(); } }); server.await; client.await; }); TestResult::passed() } quickcheck(prop as fn(_) -> _) } } multistream-select-0.13.0/src/lib.rs000064400000000000000000000164361046102023000154450ustar 00000000000000// 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 Negotiation //! //! 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 primarily by core components of *libp2p* and it //! > is usually not used directly on its own. //! //! ## Roles //! //! Two peers using the multistream-select negotiation protocol on an I/O stream //! 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. //! 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). //! //! ## [`Negotiated`](self::Negotiated) //! //! A `Negotiated` represents an I/O stream that has settled on a protocol //! to use. By default, with [`Version::V1`], protocol negotiation is always //! at least one dedicated round-trip message exchange, before application //! data for the negotiated protocol can be sent by the dialer. There is //! a variant [`Version::V1Lazy`] that permits 0-RTT negotiation if the //! dialer only supports a single protocol. In that case, when a dialer //! settles on a protocol to use, the [`DialerSelectFuture`] yields a //! [`Negotiated`](self::Negotiated) I/O stream before the negotiation //! data has been flushed. It is then expecting confirmation for that protocol //! as the first messages read from the stream. This behaviour allows the dialer //! to immediately send data relating to the negotiated protocol together with the //! remaining negotiation message(s). 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) and the documentation //! of [`Version::V1Lazy`] for further details. //! //! ## Examples //! //! For a dialer: //! //! ```no_run //! use async_std::net::TcpStream; //! use multistream_select::{dialer_select_proto, Version}; //! use futures::prelude::*; //! //! async_std::task::block_on(async move { //! let socket = TcpStream::connect("127.0.0.1:10333").await.unwrap(); //! //! let protos = vec!["/echo/1.0.0", "/echo/2.5.0"]; //! let (protocol, _io) = dialer_select_proto(socket, protos, Version::V1).await.unwrap(); //! //! println!("Negotiated protocol: {:?}", protocol); //! // You can now use `_io` to communicate with the remote. //! }); //! ``` //! #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] mod dialer_select; mod length_delimited; mod listener_select; mod negotiated; mod protocol; pub use self::dialer_select::{dialer_select_proto, DialerSelectFuture}; pub use self::listener_select::{listener_select_proto, ListenerSelectFuture}; pub use self::negotiated::{Negotiated, NegotiatedComplete, NegotiationError}; pub use self::protocol::ProtocolError; /// Supported multistream-select versions. #[derive(Clone, Copy, Debug, PartialEq, Eq, Default)] pub enum Version { /// Version 1 of the multistream-select protocol. See [1] and [2]. /// /// [1]: https://github.com/libp2p/specs/blob/master/connections/README.md#protocol-negotiation /// [2]: https://github.com/multiformats/multistream-select #[default] V1, /// A "lazy" variant of version 1 that is identical on the wire but whereby /// the dialer delays flushing protocol negotiation data in order to combine /// it with initial application data, thus performing 0-RTT negotiation. /// /// This strategy is only applicable for the node with the role of "dialer" /// in the negotiation and only if the dialer supports just a single /// application protocol. In that case the dialer immedidately "settles" /// on that protocol, buffering the negotiation messages to be sent /// with the first round of application protocol data (or an attempt /// is made to read from the `Negotiated` I/O stream). /// /// A listener will behave identically to `V1`. This ensures interoperability with `V1`. /// Notably, it will immediately send the multistream header as well as the protocol /// confirmation, resulting in multiple frames being sent on the underlying transport. /// Nevertheless, if the listener supports the protocol that the dialer optimistically /// settled on, it can be a 0-RTT negotiation. /// /// > **Note**: `V1Lazy` is specific to `rust-libp2p`. The wire protocol is identical to `V1` /// > and generally interoperable with peers only supporting `V1`. Nevertheless, there is a /// > pitfall that is rarely encountered: When nesting multiple protocol negotiations, the /// > listener should either be known to support all of the dialer's optimistically chosen /// > protocols or there is must be no intermediate protocol without a payload and none of /// > the protocol payloads must have the potential for being mistaken for a multistream-select /// > protocol message. This avoids rare edge-cases whereby the listener may not recognize /// > upgrade boundaries and erroneously process a request despite not supporting one of /// > the intermediate protocols that the dialer committed to. See [1] and [2]. /// /// [1]: https://github.com/multiformats/go-multistream/issues/20 /// [2]: https://github.com/libp2p/rust-libp2p/pull/1212 V1Lazy, // Draft: https://github.com/libp2p/specs/pull/95 // V2, } multistream-select-0.13.0/src/listener_select.rs000064400000000000000000000324551046102023000200620ustar 00000000000000// 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. //! Protocol negotiation strategies for the peer acting as the listener //! in a multistream-select protocol negotiation. use crate::protocol::{HeaderLine, Message, MessageIO, Protocol, ProtocolError}; use crate::{Negotiated, NegotiationError}; use futures::prelude::*; use smallvec::SmallVec; use std::{ convert::TryFrom as _, iter::FromIterator, mem, pin::Pin, task::{Context, Poll}, }; /// Returns a `Future` that negotiates a protocol on the given I/O stream /// for a peer acting as the _listener_ (or _responder_). /// /// 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, I: IntoIterator, I::Item: AsRef, { let protocols = protocols .into_iter() .filter_map(|n| match Protocol::try_from(n.as_ref()) { Ok(p) => Some((n, p)), Err(e) => { log::warn!( "Listener: Ignoring invalid protocol: {} due to {}", n.as_ref(), e ); None } }); ListenerSelectFuture { protocols: SmallVec::from_iter(protocols), state: State::RecvHeader { io: MessageIO::new(inner), }, last_sent_na: false, } } /// The `Future` returned by [`listener_select_proto`] that performs a /// multistream-select protocol negotiation on an underlying I/O stream. #[pin_project::pin_project] pub struct ListenerSelectFuture { // 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, /// Whether the last message sent was a protocol rejection (i.e. `na\n`). /// /// If the listener reads garbage or EOF after such a rejection, /// the dialer is likely using `V1Lazy` and negotiation must be /// considered failed, but not with a protocol violation or I/O /// error. last_sent_na: bool, } enum State { RecvHeader { io: MessageIO, }, SendHeader { io: MessageIO, }, RecvMessage { io: MessageIO, }, SendMessage { io: MessageIO, message: Message, protocol: Option, }, Flush { io: MessageIO, protocol: Option, }, Done, } impl Future for ListenerSelectFuture where // The Unpin bound here is required because we produce a `Negotiated` as the output. // It also makes the implementation considerably easier to write. R: AsyncRead + AsyncWrite + Unpin, N: AsRef + Clone, { type Output = Result<(N, Negotiated), NegotiationError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); loop { match mem::replace(this.state, State::Done) { State::RecvHeader { mut io } => { match io.poll_next_unpin(cx) { Poll::Ready(Some(Ok(Message::Header(h)))) => match h { HeaderLine::V1 => *this.state = State::SendHeader { io }, }, Poll::Ready(Some(Ok(_))) => { return Poll::Ready(Err(ProtocolError::InvalidMessage.into())) } Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(From::from(err))), // Treat EOF error as [`NegotiationError::Failed`], not as // [`NegotiationError::ProtocolError`], allowing dropping or closing an I/O // stream as a permissible way to "gracefully" fail a negotiation. Poll::Ready(None) => return Poll::Ready(Err(NegotiationError::Failed)), Poll::Pending => { *this.state = State::RecvHeader { io }; return Poll::Pending; } } } State::SendHeader { mut io } => { match Pin::new(&mut io).poll_ready(cx) { Poll::Pending => { *this.state = State::SendHeader { io }; return Poll::Pending; } Poll::Ready(Ok(())) => {} Poll::Ready(Err(err)) => return Poll::Ready(Err(From::from(err))), } let msg = Message::Header(HeaderLine::V1); if let Err(err) = Pin::new(&mut io).start_send(msg) { return Poll::Ready(Err(From::from(err))); } *this.state = State::Flush { io, protocol: None }; } State::RecvMessage { mut io } => { let msg = match Pin::new(&mut io).poll_next(cx) { Poll::Ready(Some(Ok(msg))) => msg, // Treat EOF error as [`NegotiationError::Failed`], not as // [`NegotiationError::ProtocolError`], allowing dropping or closing an I/O // stream as a permissible way to "gracefully" fail a negotiation. // // This is e.g. important when a listener rejects a protocol with // [`Message::NotAvailable`] and the dialer does not have alternative // protocols to propose. Then the dialer will stop the negotiation and drop // the corresponding stream. As a listener this EOF should be interpreted as // a failed negotiation. Poll::Ready(None) => return Poll::Ready(Err(NegotiationError::Failed)), Poll::Pending => { *this.state = State::RecvMessage { io }; return Poll::Pending; } Poll::Ready(Some(Err(err))) => { if *this.last_sent_na { // When we read garbage or EOF after having already rejected a // protocol, the dialer is most likely using `V1Lazy` and has // optimistically settled on this protocol, so this is really a // failed negotiation, not a protocol violation. In this case // the dialer also raises `NegotiationError::Failed` when finally // reading the `N/A` response. if let ProtocolError::InvalidMessage = &err { log::trace!( "Listener: Negotiation failed with invalid \ message after protocol rejection." ); return Poll::Ready(Err(NegotiationError::Failed)); } if let ProtocolError::IoError(e) = &err { if e.kind() == std::io::ErrorKind::UnexpectedEof { log::trace!( "Listener: Negotiation failed with EOF \ after protocol rejection." ); return Poll::Ready(Err(NegotiationError::Failed)); } } } return Poll::Ready(Err(From::from(err))); } }; match msg { Message::ListProtocols => { let supported = this.protocols.iter().map(|(_, p)| p).cloned().collect(); let message = Message::Protocols(supported); *this.state = State::SendMessage { io, message, protocol: None, } } Message::Protocol(p) => { let protocol = this.protocols.iter().find_map(|(name, proto)| { if &p == proto { Some(name.clone()) } else { None } }); let message = if protocol.is_some() { log::debug!("Listener: confirming protocol: {}", p); Message::Protocol(p.clone()) } else { log::debug!("Listener: rejecting protocol: {}", p.as_ref()); Message::NotAvailable }; *this.state = State::SendMessage { io, message, protocol, }; } _ => return Poll::Ready(Err(ProtocolError::InvalidMessage.into())), } } State::SendMessage { mut io, message, protocol, } => { match Pin::new(&mut io).poll_ready(cx) { Poll::Pending => { *this.state = State::SendMessage { io, message, protocol, }; return Poll::Pending; } Poll::Ready(Ok(())) => {} Poll::Ready(Err(err)) => return Poll::Ready(Err(From::from(err))), } if let Message::NotAvailable = &message { *this.last_sent_na = true; } else { *this.last_sent_na = false; } if let Err(err) = Pin::new(&mut io).start_send(message) { return Poll::Ready(Err(From::from(err))); } *this.state = State::Flush { io, protocol }; } State::Flush { mut io, protocol } => { match Pin::new(&mut io).poll_flush(cx) { Poll::Pending => { *this.state = State::Flush { io, protocol }; return Poll::Pending; } Poll::Ready(Ok(())) => { // If a protocol has been selected, finish negotiation. // Otherwise expect to receive another message. match protocol { Some(protocol) => { log::debug!( "Listener: sent confirmed protocol: {}", protocol.as_ref() ); let io = Negotiated::completed(io.into_inner()); return Poll::Ready(Ok((protocol, io))); } None => *this.state = State::RecvMessage { io }, } } Poll::Ready(Err(err)) => return Poll::Ready(Err(From::from(err))), } } State::Done => panic!("State::poll called after completion"), } } } } multistream-select-0.13.0/src/negotiated.rs000064400000000000000000000330251046102023000170130ustar 00000000000000// 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 crate::protocol::{HeaderLine, Message, MessageReader, Protocol, ProtocolError}; use futures::{ io::{IoSlice, IoSliceMut}, prelude::*, ready, }; use pin_project::pin_project; use std::{ error::Error, fmt, io, mem, pin::Pin, task::{Context, Poll}, }; /// 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 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. /// /// Reading from a `Negotiated` I/O stream that still has pending negotiation /// protocol data to send implicitly triggers flushing of all yet unsent data. #[pin_project] #[derive(Debug)] pub struct Negotiated { #[pin] state: State, } /// A `Future` that waits on the completion of protocol negotiation. #[derive(Debug)] pub struct NegotiatedComplete { inner: Option>, } impl Future for NegotiatedComplete where // `Unpin` is required not because of implementation details but because we produce the // `Negotiated` as the output of the future. TInner: AsyncRead + AsyncWrite + Unpin, { type Output = Result, NegotiationError>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut io = self .inner .take() .expect("NegotiatedFuture called after completion."); match Negotiated::poll(Pin::new(&mut io), cx) { Poll::Pending => { self.inner = Some(io); Poll::Pending } Poll::Ready(Ok(())) => Poll::Ready(Ok(io)), Poll::Ready(Err(err)) => { self.inner = Some(io); Poll::Ready(Err(err)) } } } } impl Negotiated { /// Creates a `Negotiated` in state [`State::Completed`]. pub(crate) fn completed(io: TInner) -> Self { Negotiated { state: State::Completed { io }, } } /// Creates a `Negotiated` in state [`State::Expecting`] that is still /// expecting confirmation of the given `protocol`. pub(crate) fn expecting( io: MessageReader, protocol: Protocol, header: Option, ) -> Self { Negotiated { state: State::Expecting { io, protocol, header, }, } } /// Polls the `Negotiated` for completion. fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> where TInner: AsyncRead + AsyncWrite + Unpin, { // Flush any pending negotiation data. match self.as_mut().poll_flush(cx) { Poll::Ready(Ok(())) => {} Poll::Pending => return Poll::Pending, Poll::Ready(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 Poll::Ready(Err(e.into())); } } } let mut this = self.project(); if let StateProj::Completed { .. } = this.state.as_mut().project() { return Poll::Ready(Ok(())); } // Read outstanding protocol negotiation messages. loop { match mem::replace(&mut *this.state, State::Invalid) { State::Expecting { mut io, header, protocol, } => { let msg = match Pin::new(&mut io).poll_next(cx)? { Poll::Ready(Some(msg)) => msg, Poll::Pending => { *this.state = State::Expecting { io, header, protocol, }; return Poll::Pending; } Poll::Ready(None) => { return Poll::Ready(Err(ProtocolError::IoError( io::ErrorKind::UnexpectedEof.into(), ) .into())); } }; if let Message::Header(h) = &msg { if Some(h) == header.as_ref() { *this.state = State::Expecting { io, protocol, header: None, }; continue; } } if let Message::Protocol(p) = &msg { if p.as_ref() == protocol.as_ref() { log::debug!("Negotiated: Received confirmation for protocol: {}", p); *this.state = State::Completed { io: io.into_inner(), }; return Poll::Ready(Ok(())); } } return Poll::Ready(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. #[pin_project(project = StateProj)] #[derive(Debug)] enum State { /// In this state, a `Negotiated` is still expecting to /// receive confirmation of the protocol it has optimistically /// settled on. Expecting { /// The underlying I/O stream. #[pin] io: MessageReader, /// The expected negotiation header/preamble (i.e. multistream-select version), /// if one is still expected to be received. header: Option, /// The expected application protocol (i.e. name and version). protocol: Protocol, }, /// In this state, a protocol has been agreed upon and I/O /// on the underlying stream can commence. Completed { #[pin] io: R, }, /// Temporary state while moving the `io` resource from /// `Expecting` to `Completed`. Invalid, } impl AsyncRead for Negotiated where TInner: AsyncRead + AsyncWrite + Unpin, { fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { loop { if let StateProj::Completed { io } = self.as_mut().project().state.project() { // If protocol negotiation is complete, commence with reading. return io.poll_read(cx, buf); } // Poll the `Negotiated`, driving protocol negotiation to completion, // including flushing of any remaining data. match self.as_mut().poll(cx) { Poll::Ready(Ok(())) => {} Poll::Pending => return Poll::Pending, Poll::Ready(Err(err)) => return Poll::Ready(Err(From::from(err))), } } } // TODO: implement once method is stabilized in the futures crate /*unsafe fn initializer(&self) -> Initializer { match &self.state { State::Completed { io, .. } => io.initializer(), State::Expecting { io, .. } => io.inner_ref().initializer(), State::Invalid => panic!("Negotiated: Invalid state"), } }*/ fn poll_read_vectored( mut self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>], ) -> Poll> { loop { if let StateProj::Completed { io } = self.as_mut().project().state.project() { // If protocol negotiation is complete, commence with reading. return io.poll_read_vectored(cx, bufs); } // Poll the `Negotiated`, driving protocol negotiation to completion, // including flushing of any remaining data. match self.as_mut().poll(cx) { Poll::Ready(Ok(())) => {} Poll::Pending => return Poll::Pending, Poll::Ready(Err(err)) => return Poll::Ready(Err(From::from(err))), } } } } impl AsyncWrite for Negotiated where TInner: AsyncWrite + AsyncRead + Unpin, { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { match self.project().state.project() { StateProj::Completed { io } => io.poll_write(cx, buf), StateProj::Expecting { io, .. } => io.poll_write(cx, buf), StateProj::Invalid => panic!("Negotiated: Invalid state"), } } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match self.project().state.project() { StateProj::Completed { io } => io.poll_flush(cx), StateProj::Expecting { io, .. } => io.poll_flush(cx), StateProj::Invalid => panic!("Negotiated: Invalid state"), } } fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // Ensure all data has been flushed, including optimistic multistream-select messages. ready!(self .as_mut() .poll_flush(cx) .map_err(Into::::into)?); // Continue with the shutdown of the underlying I/O stream. match self.project().state.project() { StateProj::Completed { io, .. } => io.poll_close(cx), StateProj::Expecting { io, .. } => { let close_poll = io.poll_close(cx); if let Poll::Ready(Ok(())) = close_poll { log::debug!("Stream closed. Confirmation from remote for optimstic protocol negotiation still pending.") } close_poll } StateProj::Invalid => panic!("Negotiated: Invalid state"), } } fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { match self.project().state.project() { StateProj::Completed { io } => io.poll_write_vectored(cx, bufs), StateProj::Expecting { io, .. } => io.poll_write_vectored(cx, bufs), StateProj::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 From for io::Error { fn from(err: NegotiationError) -> io::Error { if let NegotiationError::ProtocolError(e) = err { return e.into(); } io::Error::new(io::ErrorKind::Other, err) } } 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> { match self { NegotiationError::ProtocolError(p) => { fmt.write_fmt(format_args!("Protocol error: {p}")) } NegotiationError::Failed => fmt.write_str("Protocol negotiation failed."), } } } multistream-select-0.13.0/src/protocol.rs000064400000000000000000000403171046102023000165330ustar 00000000000000// 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 crate::length_delimited::{LengthDelimited, LengthDelimitedReader}; use crate::Version; use bytes::{BufMut, Bytes, BytesMut}; use futures::{io::IoSlice, prelude::*, ready}; use std::{ convert::TryFrom, error::Error, fmt, io, pin::Pin, task::{Context, Poll}, }; use unsigned_varint as uvi; /// The maximum number of supported protocols that can be processed. const MAX_PROTOCOLS: usize = 1000; /// The encoded form of a multistream-select 1.0.0 header message. const MSG_MULTISTREAM_1_0: &[u8] = b"/multistream/1.0.0\n"; /// The encoded form of a multistream-select '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 multistream-select header lines preceeding negotiation. /// /// Every [`Version`] has a corresponding header line. #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum HeaderLine { /// The `/multistream/1.0.0` header line. V1, } impl From for HeaderLine { fn from(v: Version) -> HeaderLine { match v { Version::V1 | Version::V1Lazy => HeaderLine::V1, } } } /// A protocol (name) exchanged during protocol negotiation. #[derive(Clone, Debug, PartialEq, Eq)] pub(crate) struct Protocol(String); impl AsRef for Protocol { fn as_ref(&self) -> &str { self.0.as_ref() } } impl TryFrom for Protocol { type Error = ProtocolError; fn try_from(value: Bytes) -> Result { if !value.as_ref().starts_with(b"/") { return Err(ProtocolError::InvalidProtocol); } let protocol_as_string = String::from_utf8(value.to_vec()).map_err(|_| ProtocolError::InvalidProtocol)?; Ok(Protocol(protocol_as_string)) } } impl TryFrom<&[u8]> for Protocol { type Error = ProtocolError; fn try_from(value: &[u8]) -> Result { Self::try_from(Bytes::copy_from_slice(value)) } } impl TryFrom<&str> for Protocol { type Error = ProtocolError; fn try_from(value: &str) -> Result { if !value.starts_with('/') { return Err(ProtocolError::InvalidProtocol); } Ok(Protocol(value.to_owned())) } } impl fmt::Display for Protocol { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", 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(crate) enum Message { /// A header message identifies the multistream-select protocol /// that the sender wishes to speak. Header(HeaderLine), /// 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. fn encode(&self, dest: &mut BytesMut) -> Result<(), ProtocolError> { match self { Message::Header(HeaderLine::V1) => { dest.reserve(MSG_MULTISTREAM_1_0.len()); dest.put(MSG_MULTISTREAM_1_0); Ok(()) } Message::Protocol(p) => { let len = p.as_ref().len() + 1; // + 1 for \n dest.reserve(len); dest.put(p.0.as_ref()); dest.put_u8(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 encoded = Vec::with_capacity(ps.len()); for p in ps { encoded.extend(uvi::encode::usize(p.as_ref().len() + 1, &mut buf)); // +1 for '\n' encoded.extend_from_slice(p.0.as_ref()); encoded.push(b'\n') } encoded.push(b'\n'); dest.reserve(encoded.len()); dest.put(encoded.as_ref()); Ok(()) } Message::NotAvailable => { dest.reserve(MSG_PROTOCOL_NA.len()); dest.put(MSG_PROTOCOL_NA); Ok(()) } } } /// Decodes a `Message` from its byte representation. fn decode(mut msg: Bytes) -> Result { if msg == MSG_MULTISTREAM_1_0 { return Ok(Message::Header(HeaderLine::V1)); } if msg == MSG_PROTOCOL_NA { return Ok(Message::NotAvailable); } if msg == MSG_LS { return Ok(Message::ListProtocols); } // If it starts with a `/`, ends with a line feed without any // other line feeds in-between, it must be a protocol name. if msg.first() == Some(&b'/') && msg.last() == Some(&b'\n') && !msg[..msg.len() - 1].contains(&b'\n') { let p = Protocol::try_from(msg.split_to(msg.len() - 1))?; return Ok(Message::Protocol(p)); } // At this point, it must be an `ls` response, i.e. one or more // length-prefixed, newline-delimited protocol names. let mut protocols = Vec::new(); let mut remaining: &[u8] = &msg; loop { // A well-formed message must be terminated with a newline. if remaining == [b'\n'] { break; } else if protocols.len() == MAX_PROTOCOLS { return Err(ProtocolError::TooManyProtocols); } // Decode the length of the next protocol name and check that // it ends with a line feed. let (len, tail) = uvi::decode::usize(remaining)?; if len == 0 || len > tail.len() || tail[len - 1] != b'\n' { return Err(ProtocolError::InvalidMessage); } // Parse the protocol name. let p = Protocol::try_from(Bytes::copy_from_slice(&tail[..len - 1]))?; protocols.push(p); // Skip ahead to the next protocol. remaining = &tail[len..]; } Ok(Message::Protocols(protocols)) } } /// A `MessageIO` implements a [`Stream`] and [`Sink`] of [`Message`]s. #[pin_project::pin_project] pub(crate) struct MessageIO { #[pin] inner: LengthDelimited, } impl MessageIO { /// Constructs a new `MessageIO` resource wrapping the given I/O stream. pub(crate) 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(crate) fn into_reader(self) -> MessageReader { MessageReader { inner: self.inner.into_reader(), } } /// Drops the [`MessageIO`] resource, yielding the underlying I/O stream. /// /// # Panics /// /// Panics if the read buffer or write buffer is not empty, meaning that an incoming /// protocol negotiation frame has been partially read or an outgoing frame /// has not yet been flushed. The read buffer is guaranteed to be empty whenever /// `MessageIO::poll` returned a message. The write buffer is guaranteed to be empty /// when the sink has been flushed. pub(crate) fn into_inner(self) -> R { self.inner.into_inner() } } impl Sink for MessageIO where R: AsyncWrite, { type Error = ProtocolError; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_ready(cx).map_err(From::from) } fn start_send(self: Pin<&mut Self>, item: Message) -> Result<(), Self::Error> { let mut buf = BytesMut::new(); item.encode(&mut buf)?; self.project() .inner .start_send(buf.freeze()) .map_err(From::from) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_flush(cx).map_err(From::from) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_close(cx).map_err(From::from) } } impl Stream for MessageIO where R: AsyncRead, { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match poll_stream(self.project().inner, cx) { Poll::Pending => Poll::Pending, Poll::Ready(None) => Poll::Ready(None), Poll::Ready(Some(Ok(m))) => Poll::Ready(Some(Ok(m))), Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err))), } } } /// A `MessageReader` implements a `Stream` of `Message`s on an underlying /// I/O resource combined with direct `AsyncWrite` access. #[pin_project::pin_project] #[derive(Debug)] pub(crate) struct MessageReader { #[pin] 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. /// /// # Panics /// /// Panics if the read buffer or write buffer is not empty, meaning that either /// an incoming protocol negotiation frame has been partially read, or an /// outgoing frame has not yet been flushed. The read buffer is guaranteed to /// be empty whenever `MessageReader::poll` returned a message. The write /// buffer is guaranteed to be empty whenever the sink has been flushed. pub(crate) fn into_inner(self) -> R { self.inner.into_inner() } } impl Stream for MessageReader where R: AsyncRead, { type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { poll_stream(self.project().inner, cx) } } impl AsyncWrite for MessageReader where TInner: AsyncWrite, { fn poll_write( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { self.project().inner.poll_write(cx, buf) } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_flush(cx) } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_close(cx) } fn poll_write_vectored( self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { self.project().inner.poll_write_vectored(cx, bufs) } } fn poll_stream( stream: Pin<&mut S>, cx: &mut Context<'_>, ) -> Poll>> where S: Stream>, { let msg = if let Some(msg) = ready!(stream.poll_next(cx)?) { match Message::decode(msg) { Ok(m) => m, Err(err) => return Poll::Ready(Some(Err(err))), } } else { return Poll::Ready(None); }; log::trace!("Received message: {:?}", msg); Poll::Ready(Some(Ok(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 From for io::Error { fn from(err: ProtocolError) -> Self { if let ProtocolError::IoError(e) = err { return e; } 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 std::iter; impl Arbitrary for Protocol { fn arbitrary(g: &mut Gen) -> Protocol { let n = g.gen_range(1..g.size()); let p: String = iter::repeat(()) .map(|()| char::arbitrary(g)) .filter(|&c| c.is_ascii_alphanumeric()) .take(n) .collect(); Protocol(format!("/{p}")) } } impl Arbitrary for Message { fn arbitrary(g: &mut Gen) -> Message { match g.gen_range(0..5u8) { 0 => Message::Header(HeaderLine::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) .unwrap_or_else(|_| panic!("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(_)) } } multistream-select-0.13.0/tests/dialer_select.rs000064400000000000000000000167071046102023000200520ustar 00000000000000// 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. //! Integration tests for protocol negotiation. use futures::prelude::*; use multistream_select::{dialer_select_proto, listener_select_proto, NegotiationError, Version}; use std::time::Duration; #[test] fn select_proto_basic() { async fn run(version: Version) { let (client_connection, server_connection) = futures_ringbuf::Endpoint::pair(100, 100); let server = async_std::task::spawn(async move { let protos = vec!["/proto1", "/proto2"]; let (proto, mut io) = listener_select_proto(server_connection, protos) .await .unwrap(); assert_eq!(proto, "/proto2"); let mut out = vec![0; 32]; let n = io.read(&mut out).await.unwrap(); out.truncate(n); assert_eq!(out, b"ping"); io.write_all(b"pong").await.unwrap(); io.flush().await.unwrap(); }); let client = async_std::task::spawn(async move { let protos = vec!["/proto3", "/proto2"]; let (proto, mut io) = dialer_select_proto(client_connection, protos.into_iter(), version) .await .unwrap(); assert_eq!(proto, "/proto2"); io.write_all(b"ping").await.unwrap(); io.flush().await.unwrap(); let mut out = vec![0; 32]; let n = io.read(&mut out).await.unwrap(); out.truncate(n); assert_eq!(out, b"pong"); }); server.await; client.await; } async_std::task::block_on(run(Version::V1)); async_std::task::block_on(run(Version::V1Lazy)); } /// Tests the expected behaviour of failed negotiations. #[test] fn negotiation_failed() { let _ = env_logger::try_init(); async fn run( Test { version, listen_protos, dial_protos, dial_payload, }: Test, ) { let (client_connection, server_connection) = futures_ringbuf::Endpoint::pair(100, 100); let server = async_std::task::spawn(async move { let io = match listener_select_proto(server_connection, listen_protos).await { Ok((_, io)) => io, Err(NegotiationError::Failed) => return, Err(NegotiationError::ProtocolError(e)) => { panic!("Unexpected protocol error {e}") } }; match io.complete().await { Err(NegotiationError::Failed) => {} _ => panic!(), } }); let client = async_std::task::spawn(async move { let mut io = match dialer_select_proto(client_connection, dial_protos.into_iter(), version) .await { Err(NegotiationError::Failed) => return, Ok((_, io)) => io, Err(_) => panic!(), }; // The dialer may write a payload that is even sent before it // got confirmation of the last proposed protocol, when `V1Lazy` // is used. io.write_all(&dial_payload).await.unwrap(); match io.complete().await { Err(NegotiationError::Failed) => {} _ => panic!(), } }); server.await; client.await; } /// Parameters for a single test run. #[derive(Clone)] struct Test { version: Version, listen_protos: Vec<&'static str>, dial_protos: Vec<&'static str>, dial_payload: Vec, } // Disjunct combinations of listen and dial protocols to test. // // The choices here cover the main distinction between a single // and multiple protocols. let protos = vec![ (vec!["/proto1"], vec!["/proto2"]), (vec!["/proto1", "/proto2"], vec!["/proto3", "/proto4"]), ]; // The payloads that the dialer sends after "successful" negotiation, // which may be sent even before the dialer got protocol confirmation // when `V1Lazy` is used. // // The choices here cover the specific situations that can arise with // `V1Lazy` and which must nevertheless behave identically to `V1` w.r.t. // the outcome of the negotiation. let payloads = vec![ // No payload, in which case all versions should behave identically // in any case, i.e. the baseline test. vec![], // With this payload and `V1Lazy`, the listener interprets the first // `1` as a message length and encounters an invalid message (the // second `1`). The listener is nevertheless expected to fail // negotiation normally, just like with `V1`. vec![1, 1], // With this payload and `V1Lazy`, the listener interprets the first // `42` as a message length and encounters unexpected EOF trying to // read a message of that length. The listener is nevertheless expected // to fail negotiation normally, just like with `V1` vec![42, 1], ]; for (listen_protos, dial_protos) in protos { for dial_payload in payloads.clone() { for &version in &[Version::V1, Version::V1Lazy] { async_std::task::block_on(run(Test { version, listen_protos: listen_protos.clone(), dial_protos: dial_protos.clone(), dial_payload: dial_payload.clone(), })) } } } } #[async_std::test] async fn v1_lazy_do_not_wait_for_negotiation_on_poll_close() { let (client_connection, _server_connection) = futures_ringbuf::Endpoint::pair(1024 * 1024, 1); let client = async_std::task::spawn(async move { // Single protocol to allow for lazy (or optimistic) protocol negotiation. let protos = vec!["/proto1"]; let (proto, mut io) = dialer_select_proto(client_connection, protos.into_iter(), Version::V1Lazy) .await .unwrap(); assert_eq!(proto, "/proto1"); // client can close the connection even though protocol negotiation is not yet done, i.e. // `_server_connection` had been untouched. io.close().await.unwrap(); }); async_std::future::timeout(Duration::from_secs(10), client) .await .unwrap(); }