Clean up directory structure (#426)

* Remove unused circular-buffer crate
* Move transports into subdirectory
* Move misc into subdirectory
* Move stores into subdirectory
* Move multiplexers
* Move protocols
* Move libp2p top layer
* Fix Test: skip doctest if secio isn't enabled
This commit is contained in:
Benjamin Kampmann
2018-08-29 11:24:44 +02:00
committed by GitHub
parent f5ce93c730
commit 2ea49718f3
131 changed files with 146 additions and 1023 deletions

View File

@ -0,0 +1,190 @@
// 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_select_proto` code, which allows selecting a protocol thanks to
//! `multistream-select` for the dialer.
use bytes::Bytes;
use futures::future::{loop_fn, result, Loop, Either};
use futures::{Future, Sink, Stream};
use ProtocolChoiceError;
use protocol::Dialer;
use protocol::DialerToListenerMessage;
use protocol::ListenerToDialerMessage;
use tokio_io::{AsyncRead, AsyncWrite};
/// Helps selecting a protocol amongst the ones supported.
///
/// 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.
///
/// 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.
#[inline]
pub fn dialer_select_proto<R, I, M, P>(
inner: R,
protocols: I,
) -> impl Future<Item = (P, R), Error = ProtocolChoiceError>
where
R: AsyncRead + AsyncWrite,
I: Iterator<Item = (Bytes, M, P)>,
M: FnMut(&Bytes, &Bytes) -> bool,
{
// We choose between the "serial" and "parallel" strategies based on the number of protocols.
if protocols.size_hint().1.map(|n| n <= 3).unwrap_or(false) {
let fut = dialer_select_proto_serial(inner, protocols.map(|(n, _, id)| (n, id)));
Either::A(fut)
} else {
let fut = dialer_select_proto_parallel(inner, protocols);
Either::B(fut)
}
}
/// Helps selecting a protocol amongst the ones supported.
///
/// Same as `dialer_select_proto`. Tries protocols one by one. The iterator doesn't need to produce
/// match functions, because it's not needed.
pub fn dialer_select_proto_serial<R, I, P>(
inner: R,
mut protocols: I,
) -> impl Future<Item = (P, R), Error = ProtocolChoiceError>
where
R: AsyncRead + AsyncWrite,
I: Iterator<Item = (Bytes, P)>,
{
Dialer::new(inner).from_err().and_then(move |dialer| {
// Similar to a `loop` keyword.
loop_fn(dialer, move |dialer| {
result(protocols.next().ok_or(ProtocolChoiceError::NoProtocolFound))
// If the `protocols` iterator produced an element, send it to the dialer
.and_then(|(proto_name, proto_value)| {
let req = DialerToListenerMessage::ProtocolRequest {
name: proto_name.clone()
};
trace!("sending {:?}", req);
dialer.send(req)
.map(|d| (d, proto_name, proto_value))
.from_err()
})
// Once sent, read one element from `dialer`.
.and_then(|(dialer, proto_name, proto_value)| {
dialer
.into_future()
.map(|(msg, rest)| (msg, rest, proto_name, proto_value))
.map_err(|(e, _)| e.into())
})
// Once read, analyze the response.
.and_then(|(message, rest, proto_name, proto_value)| {
trace!("received {:?}", message);
let message = message.ok_or(ProtocolChoiceError::UnexpectedMessage)?;
match message {
ListenerToDialerMessage::ProtocolAck { ref name }
if name == &proto_name =>
{
// Satisfactory response, break the loop.
Ok(Loop::Break((proto_value, rest.into_inner())))
},
ListenerToDialerMessage::NotAvailable => {
Ok(Loop::Continue(rest))
},
_ => Err(ProtocolChoiceError::UnexpectedMessage),
}
})
})
})
}
/// 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<R, I, M, P>(
inner: R,
protocols: I,
) -> impl Future<Item = (P, R), Error = ProtocolChoiceError>
where
R: AsyncRead + AsyncWrite,
I: Iterator<Item = (Bytes, M, P)>,
M: FnMut(&Bytes, &Bytes) -> bool,
{
Dialer::new(inner)
.from_err()
.and_then(move |dialer| {
trace!("requesting protocols list");
dialer
.send(DialerToListenerMessage::ProtocolsListRequest)
.from_err()
})
.and_then(move |dialer| dialer.into_future().map_err(|(e, _)| e.into()))
.and_then(move |(msg, dialer)| {
trace!("protocols list response: {:?}", msg);
let list = match msg {
Some(ListenerToDialerMessage::ProtocolsListResponse { list }) => list,
_ => return Err(ProtocolChoiceError::UnexpectedMessage),
};
let mut found = None;
for (local_name, mut match_fn, ident) in protocols {
for remote_name in &list {
if match_fn(remote_name, &local_name) {
found = Some((remote_name.clone(), ident));
break;
}
}
if found.is_some() {
break;
}
}
let (proto_name, proto_val) = found.ok_or(ProtocolChoiceError::NoProtocolFound)?;
Ok((proto_name, proto_val, dialer))
})
.and_then(|(proto_name, proto_val, dialer)| {
trace!("sending {:?}", proto_name);
dialer
.send(DialerToListenerMessage::ProtocolRequest {
name: proto_name.clone(),
})
.from_err()
.map(|dialer| (proto_name, proto_val, dialer))
})
.and_then(|(proto_name, proto_val, dialer)| {
dialer
.into_future()
.map(|(msg, rest)| (proto_name, proto_val, msg, rest))
.map_err(|(err, _)| err.into())
})
.and_then(|(proto_name, proto_val, msg, dialer)| {
trace!("received {:?}", msg);
match msg {
Some(ListenerToDialerMessage::ProtocolAck { ref name }) if name == &proto_name => {
Ok((proto_val, dialer.into_inner()))
}
_ => Err(ProtocolChoiceError::UnexpectedMessage),
}
})
}

View File

@ -0,0 +1,82 @@
// 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.
//! Main `ProtocolChoiceError` error.
use protocol::MultistreamSelectError;
use std::error;
use std::fmt;
use std::io::Error as IoError;
/// 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<MultistreamSelectError> for ProtocolChoiceError {
#[inline]
fn from(err: MultistreamSelectError) -> ProtocolChoiceError {
ProtocolChoiceError::MultistreamSelectError(err)
}
}
impl From<IoError> for ProtocolChoiceError {
#[inline]
fn from(err: IoError) -> ProtocolChoiceError {
MultistreamSelectError::from(err).into()
}
}
impl error::Error for ProtocolChoiceError {
#[inline]
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 cause(&self) -> Option<&error::Error> {
match *self {
ProtocolChoiceError::MultistreamSelectError(ref err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for ProtocolChoiceError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "{}", error::Error::description(self))
}
}

View File

@ -0,0 +1,344 @@
// 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.
//! Alternative implementation for `tokio_io::codec::length_delimited::FramedRead` with an
//! additional property: the `into_inner()` method is guarateed not to drop any data.
//!
//! Also has the length field length hardcoded.
//!
//! We purposely only support a frame length of under 64kiB. Frames most consist in a short
//! protocol name, which is highly unlikely to be more than 64kiB long.
use futures::{Async, Poll, Sink, StartSend, Stream};
use smallvec::SmallVec;
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
use std::marker::PhantomData;
use tokio_io::AsyncRead;
/// Wraps around a `AsyncRead` and implements `Stream`.
///
/// Also implements `Sink` if the inner object implements `Sink`, for convenience.
///
/// The `I` generic indicates the type of data that needs to be produced by the `Stream`.
pub struct LengthDelimitedFramedRead<I, S> {
// The inner socket where data is pulled from.
inner: S,
// 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,
marker: PhantomData<I>,
}
#[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 },
}
impl<I, S> LengthDelimitedFramedRead<I, S> {
pub fn new(inner: S) -> LengthDelimitedFramedRead<I, S> {
LengthDelimitedFramedRead {
inner,
internal_buffer: {
let mut v = SmallVec::new();
v.push(0);
v
},
internal_buffer_pos: 0,
state: State::ReadingLength,
marker: PhantomData,
}
}
/// Destroys the `LengthDelimitedFramedRead` 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.
///
/// # 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 (ie. 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) -> S {
assert_eq!(self.state, State::ReadingLength);
assert_eq!(self.internal_buffer_pos, 0);
self.inner
}
}
impl<I, S> Stream for LengthDelimitedFramedRead<I, S>
where
S: AsyncRead,
I: for<'r> From<&'r [u8]>,
{
type Item = I;
type Error = IoError;
fn poll(&mut self) -> Poll<Option<Self::Item>, 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 => {
match self.inner
.read(&mut self.internal_buffer[self.internal_buffer_pos..])
{
Ok(0) => {
// EOF
if self.internal_buffer_pos == 0 {
return Ok(Async::Ready(None));
} else {
return Err(IoError::new(IoErrorKind::BrokenPipe, "unexpected eof"));
}
}
Ok(n) => {
debug_assert_eq!(n, 1);
self.internal_buffer_pos += n;
}
Err(ref err) if err.kind() == IoErrorKind::WouldBlock => {
return Ok(Async::NotReady);
}
Err(err) => {
return Err(err);
}
};
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_length_prefix(&self.internal_buffer);
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;
} 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(&[][..]))));
}
} else if self.internal_buffer_pos >= 2 {
// Length prefix is too long. See module doc for info about max frame len.
return Err(IoError::new(
IoErrorKind::InvalidData,
"frame length too long",
));
} else {
// Prepare for next read.
self.internal_buffer.push(0);
}
}
State::ReadingData { frame_len } => {
match self.inner
.read(&mut self.internal_buffer[self.internal_buffer_pos..])
{
Ok(0) => {
return Err(IoError::new(IoErrorKind::BrokenPipe, "unexpected eof"));
}
Ok(n) => self.internal_buffer_pos += n,
Err(ref err) if err.kind() == IoErrorKind::WouldBlock => {
return Ok(Async::NotReady);
}
Err(err) => {
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)));
}
}
}
}
}
}
impl<I, S> Sink for LengthDelimitedFramedRead<I, S>
where
S: Sink,
{
type SinkItem = S::SinkItem;
type SinkError = S::SinkError;
#[inline]
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
self.inner.start_send(item)
}
#[inline]
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
self.inner.poll_complete()
}
}
fn decode_length_prefix(buf: &[u8]) -> u16 {
debug_assert!(buf.len() <= 2);
let mut sum = 0u16;
for &byte in buf.iter().rev() {
let byte = byte & 0x7f;
sum <<= 7;
debug_assert!(sum.checked_add(u16::from(byte)).is_some());
sum += u16::from(byte);
}
sum
}
#[cfg(test)]
mod tests {
use futures::{Future, Stream};
use length_delimited::LengthDelimitedFramedRead;
use std::io::Cursor;
use std::io::ErrorKind;
#[test]
fn basic_read() {
let data = vec![6, 9, 8, 7, 6, 5, 4];
let framed = LengthDelimitedFramedRead::<Vec<u8>, _>::new(Cursor::new(data));
let recved = framed.collect().wait().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 = LengthDelimitedFramedRead::<Vec<u8>, _>::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]]);
}
#[test]
fn two_bytes_long_packet() {
let len = 5000u16;
assert!(len < (1 << 15));
let frame = (0..len).map(|n| (n & 0xff) as u8).collect::<Vec<_>>();
let mut data = vec![(len & 0x7f) as u8 | 0x80, (len >> 7) as u8];
data.extend(frame.clone().into_iter());
let framed = LengthDelimitedFramedRead::<Vec<u8>, _>::new(Cursor::new(data));
let recved = framed
.into_future()
.map(|(m, _)| m)
.map_err(|_| ())
.wait()
.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 framed = LengthDelimitedFramedRead::<Vec<u8>, _>::new(Cursor::new(data));
let recved = framed
.into_future()
.map(|(m, _)| m)
.map_err(|(err, _)| err)
.wait();
match recved {
Err(io_err) => assert_eq!(io_err.kind(), ErrorKind::InvalidData),
_ => panic!(),
}
}
#[test]
fn empty_frames() {
let data = vec![0, 0, 6, 9, 8, 7, 6, 5, 4, 0, 3, 9, 8, 7];
let framed = LengthDelimitedFramedRead::<Vec<u8>, _>::new(Cursor::new(data));
let recved = framed.collect().wait().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 = LengthDelimitedFramedRead::<Vec<u8>, _>::new(Cursor::new(data));
let recved = framed.collect().wait();
match recved {
Err(io_err) => assert_eq!(io_err.kind(), ErrorKind::BrokenPipe),
_ => panic!(),
}
}
#[test]
fn unexpected_eof_in_data() {
let data = vec![5];
let framed = LengthDelimitedFramedRead::<Vec<u8>, _>::new(Cursor::new(data));
let recved = framed.collect().wait();
match recved {
Err(io_err) => assert_eq!(io_err.kind(), ErrorKind::BrokenPipe),
_ => panic!(),
}
}
#[test]
fn unexpected_eof_in_data2() {
let data = vec![5, 9, 8, 7];
let framed = LengthDelimitedFramedRead::<Vec<u8>, _>::new(Cursor::new(data));
let recved = framed.collect().wait();
match recved {
Err(io_err) => assert_eq!(io_err.kind(), ErrorKind::BrokenPipe),
_ => panic!(),
}
}
}

View File

@ -0,0 +1,133 @@
// 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
//!
//! This crate implements the `multistream-select` protocol, which is the protocol used by libp2p
//! to negotiate which protocol to use with the remote.
//!
//! > **Note**: This crate is used by the internals of *libp2p*, and it is not required to
//! > understand it in order to use *libp2p*.
//!
//! 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 (ie. the connection or the multiplexed substream)
//! immediately stops using `multistream-select` and starts using the negotiated protocol.
//!
//! ## Protocol explanation
//!
//! 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").
//!
//! ## Examples
//!
//! For a dialer:
//!
//! ```no_run
//! extern crate bytes;
//! extern crate futures;
//! extern crate multistream_select;
//! extern crate tokio_current_thread;
//! extern crate tokio_tcp;
//!
//! # fn main() {
//! use bytes::Bytes;
//! use multistream_select::dialer_select_proto;
//! use futures::{Future, Sink, Stream};
//! use tokio_tcp::TcpStream;
//!
//! #[derive(Debug, Copy, Clone)]
//! enum MyProto { Echo, Hello }
//!
//! let client = TcpStream::connect(&"127.0.0.1:10333".parse().unwrap())
//! .from_err()
//! .and_then(move |connec| {
//! let protos = vec![
//! (Bytes::from("/echo/1.0.0"), <Bytes as PartialEq>::eq, MyProto::Echo),
//! (Bytes::from("/hello/2.5.0"), <Bytes as PartialEq>::eq, MyProto::Hello),
//! ]
//! .into_iter();
//! dialer_select_proto(connec, protos).map(|r| r.0)
//! });
//!
//! let negotiated_protocol: MyProto = tokio_current_thread::block_on_all(client)
//! .expect("failed to find a protocol");
//! println!("negotiated: {:?}", negotiated_protocol);
//! # }
//! ```
//!
//! For a listener:
//!
//! ```no_run
//! extern crate bytes;
//! extern crate futures;
//! extern crate multistream_select;
//! extern crate tokio_current_thread;
//! extern crate tokio_tcp;
//!
//! # fn main() {
//! use bytes::Bytes;
//! use multistream_select::listener_select_proto;
//! use futures::{Future, Sink, Stream};
//! use tokio_tcp::TcpListener;
//!
//! #[derive(Debug, Copy, Clone)]
//! enum MyProto { Echo, Hello }
//!
//! let server = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap()
//! .incoming()
//! .from_err()
//! .and_then(move |connec| {
//! let protos = vec![
//! (Bytes::from("/echo/1.0.0"), <Bytes as PartialEq>::eq, MyProto::Echo),
//! (Bytes::from("/hello/2.5.0"), <Bytes as PartialEq>::eq, MyProto::Hello),
//! ]
//! .into_iter();
//! listener_select_proto(connec, protos)
//! })
//! .for_each(|(proto, _connec)| {
//! println!("new remote with {:?} negotiated", proto);
//! Ok(())
//! });
//!
//! tokio_current_thread::block_on_all(server).expect("failed to run server");
//! # }
//! ```
extern crate bytes;
extern crate futures;
#[macro_use]
extern crate log;
extern crate smallvec;
extern crate tokio_io;
extern crate unsigned_varint;
mod dialer_select;
mod error;
mod length_delimited;
mod listener_select;
mod tests;
pub mod protocol;
pub use self::dialer_select::dialer_select_proto;
pub use self::error::ProtocolChoiceError;
pub use self::listener_select::listener_select_proto;

View File

@ -0,0 +1,105 @@
// 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_select_proto` code, which allows selecting a protocol thanks to
//! `multistream-select` for the listener.
use bytes::Bytes;
use futures::future::{err, loop_fn, Loop, Either};
use futures::{Future, Sink, Stream};
use ProtocolChoiceError;
use protocol::DialerToListenerMessage;
use protocol::Listener;
use protocol::ListenerToDialerMessage;
use tokio_io::{AsyncRead, AsyncWrite};
/// Helps selecting a protocol amongst the ones supported.
///
/// This function expects a socket and an iterator of the list of supported protocols. The iterator
/// must be clonable (ie. 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<R, I, M, P>(
inner: R,
protocols: I,
) -> impl Future<Item = (P, R), Error = ProtocolChoiceError>
where
R: AsyncRead + AsyncWrite,
I: Iterator<Item = (Bytes, M, P)> + Clone,
M: FnMut(&Bytes, &Bytes) -> bool,
{
Listener::new(inner).from_err().and_then(move |listener| {
loop_fn(listener, move |listener| {
let protocols = protocols.clone();
listener
.into_future()
.map_err(|(e, _)| e.into())
.and_then(move |(message, listener)| match message {
Some(DialerToListenerMessage::ProtocolsListRequest) => {
let msg = ListenerToDialerMessage::ProtocolsListResponse {
list: protocols.map(|(p, _, _)| p).collect(),
};
trace!("protocols list response: {:?}", msg);
let fut = listener
.send(msg)
.from_err()
.map(move |listener| (None, listener));
Either::A(Either::A(fut))
}
Some(DialerToListenerMessage::ProtocolRequest { name }) => {
let mut outcome = None;
let mut send_back = ListenerToDialerMessage::NotAvailable;
for (supported, mut matches, value) in protocols {
if matches(&name, &supported) {
send_back =
ListenerToDialerMessage::ProtocolAck { name: name.clone() };
outcome = Some(value);
break;
}
}
trace!("requested: {:?}, response: {:?}", name, send_back);
let fut = listener
.send(send_back)
.from_err()
.map(move |listener| (outcome, listener));
Either::A(Either::B(fut))
}
None => {
debug!("no protocol request received");
Either::B(err(ProtocolChoiceError::NoProtocolFound))
}
})
.map(|(outcome, listener): (_, Listener<R>)| match outcome {
Some(outcome) => Loop::Break((outcome, listener.into_inner())),
None => Loop::Continue(listener),
})
})
})
}

View File

@ -0,0 +1,220 @@
// 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 bytes::{Bytes, BytesMut};
use futures::{Async, AsyncSink, Future, Poll, Sink, StartSend, Stream};
use length_delimited::LengthDelimitedFramedRead;
use protocol::DialerToListenerMessage;
use protocol::ListenerToDialerMessage;
use protocol::MultistreamSelectError;
use protocol::MULTISTREAM_PROTOCOL_WITH_LF;
use std::io::{BufRead, Cursor};
use tokio_io::codec::length_delimited::Builder as LengthDelimitedBuilder;
use tokio_io::codec::length_delimited::FramedWrite as LengthDelimitedFramedWrite;
use tokio_io::{AsyncRead, AsyncWrite};
use unsigned_varint::decode;
/// Wraps around a `AsyncRead+AsyncWrite`. Assumes that we're on the dialer's side. Produces and
/// accepts messages.
pub struct Dialer<R> {
inner: LengthDelimitedFramedRead<Bytes, LengthDelimitedFramedWrite<R, BytesMut>>,
handshake_finished: bool,
}
impl<R> Dialer<R>
where
R: AsyncRead + AsyncWrite,
{
/// Takes ownership of a socket and starts the handshake. If the handshake succeeds, the
/// future returns a `Dialer`.
pub fn new(inner: R) -> impl Future<Item = Dialer<R>, Error = MultistreamSelectError> {
let write = LengthDelimitedBuilder::new()
.length_field_length(1)
.new_write(inner);
let inner = LengthDelimitedFramedRead::new(write);
inner
.send(BytesMut::from(MULTISTREAM_PROTOCOL_WITH_LF))
.from_err()
.map(|inner| Dialer {
inner,
handshake_finished: false,
})
}
/// Grants back the socket. Typically used after a `ProtocolAck` has been received.
#[inline]
pub fn into_inner(self) -> R {
self.inner.into_inner().into_inner()
}
}
impl<R> Sink for Dialer<R>
where
R: AsyncRead + AsyncWrite,
{
type SinkItem = DialerToListenerMessage;
type SinkError = MultistreamSelectError;
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
match item {
DialerToListenerMessage::ProtocolRequest { name } => {
if !name.starts_with(b"/") {
return Err(MultistreamSelectError::WrongProtocolName);
}
let mut protocol = BytesMut::from(name);
protocol.extend_from_slice(&[b'\n']);
match self.inner.start_send(protocol) {
Ok(AsyncSink::Ready) => Ok(AsyncSink::Ready),
Ok(AsyncSink::NotReady(mut protocol)) => {
let protocol_len = protocol.len();
protocol.truncate(protocol_len - 1);
let protocol = protocol.freeze();
Ok(AsyncSink::NotReady(
DialerToListenerMessage::ProtocolRequest { name: protocol },
))
}
Err(err) => Err(err.into()),
}
}
DialerToListenerMessage::ProtocolsListRequest => {
match self.inner.start_send(BytesMut::from(&b"ls\n"[..])) {
Ok(AsyncSink::Ready) => Ok(AsyncSink::Ready),
Ok(AsyncSink::NotReady(_)) => Ok(AsyncSink::NotReady(
DialerToListenerMessage::ProtocolsListRequest,
)),
Err(err) => Err(err.into()),
}
}
}
}
#[inline]
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
Ok(self.inner.poll_complete()?)
}
}
impl<R> Stream for Dialer<R>
where
R: AsyncRead + AsyncWrite,
{
type Item = ListenerToDialerMessage;
type Error = MultistreamSelectError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
loop {
let mut frame = match self.inner.poll() {
Ok(Async::Ready(Some(frame))) => frame,
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 {
self.handshake_finished = true;
continue;
} else {
return Err(MultistreamSelectError::FailedHandshake);
}
}
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)));
} else {
// A varint number of protocols
let (num_protocols, remaining) = decode::usize(&frame)?;
let reader = Cursor::new(remaining);
let mut iter = BufRead::split(reader, b'\r');
if !iter.next()
.ok_or(MultistreamSelectError::UnknownMessage)??
.is_empty()
{
return Err(MultistreamSelectError::UnknownMessage);
}
let mut out = Vec::with_capacity(num_protocols);
for proto in iter.by_ref().take(num_protocols) {
let mut proto = proto?;
let poped = proto.pop(); // Pop the `\n`
if poped != Some(b'\n') {
return Err(MultistreamSelectError::UnknownMessage);
}
out.push(Bytes::from(proto));
}
// Making sure that the number of protocols was correct.
if iter.next().is_some() || out.len() != num_protocols {
return Err(MultistreamSelectError::UnknownMessage);
}
return Ok(Async::Ready(Some(
ListenerToDialerMessage::ProtocolsListResponse { list: out },
)));
}
}
}
}
#[cfg(test)]
mod tests {
extern crate tokio_current_thread;
extern crate tokio_tcp;
use self::tokio_tcp::{TcpListener, TcpStream};
use bytes::Bytes;
use futures::Future;
use futures::{Sink, Stream};
use protocol::{Dialer, DialerToListenerMessage, MultistreamSelectError};
#[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::new(stream))
.and_then(move |dialer| {
let p = Bytes::from("invalid_name");
dialer.send(DialerToListenerMessage::ProtocolRequest { name: p })
});
match tokio_current_thread::block_on_all(server.join(client)) {
Err(MultistreamSelectError::WrongProtocolName) => (),
_ => panic!(),
}
}
}

View File

@ -0,0 +1,93 @@
// 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;
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.
WrongProtocolName,
/// Failure to parse variable-length integer.
// TODO: we don't include the actual error, because that would remove Send from the enum
VarintParseError(String),
}
impl From<io::Error> for MultistreamSelectError {
#[inline]
fn from(err: io::Error) -> MultistreamSelectError {
MultistreamSelectError::IoError(err)
}
}
impl From<decode::Error> for MultistreamSelectError {
#[inline]
fn from(err: decode::Error) -> MultistreamSelectError {
MultistreamSelectError::VarintParseError(err.to_string())
}
}
impl error::Error for MultistreamSelectError {
#[inline]
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::WrongProtocolName => {
"protocol names must always start with `/`, otherwise this error is returned"
}
MultistreamSelectError::VarintParseError(_) => {
"failure to parse variable-length integer"
}
}
}
fn cause(&self) -> Option<&error::Error> {
match *self {
MultistreamSelectError::IoError(ref err) => Some(err),
_ => None,
}
}
}
impl fmt::Display for MultistreamSelectError {
#[inline]
fn fmt(&self, fmt: &mut fmt::Formatter) -> Result<(), fmt::Error> {
write!(fmt, "{}", error::Error::description(self))
}
}

View File

@ -0,0 +1,215 @@
// 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 bytes::{Bytes, BytesMut};
use futures::{Async, AsyncSink, Future, Poll, Sink, StartSend, Stream};
use length_delimited::LengthDelimitedFramedRead;
use protocol::DialerToListenerMessage;
use protocol::ListenerToDialerMessage;
use protocol::MultistreamSelectError;
use protocol::MULTISTREAM_PROTOCOL_WITH_LF;
use tokio_io::codec::length_delimited::Builder as LengthDelimitedBuilder;
use tokio_io::codec::length_delimited::FramedWrite as LengthDelimitedFramedWrite;
use tokio_io::{AsyncRead, AsyncWrite};
use unsigned_varint::encode;
/// Wraps around a `AsyncRead+AsyncWrite`. Assumes that we're on the listener's side. Produces and
/// accepts messages.
pub struct Listener<R> {
inner: LengthDelimitedFramedRead<Bytes, LengthDelimitedFramedWrite<R, BytesMut>>,
}
impl<R> Listener<R>
where
R: AsyncRead + AsyncWrite,
{
/// Takes ownership of a socket and starts the handshake. If the handshake succeeds, the
/// future returns a `Listener`.
pub fn new(inner: R) -> impl Future<Item = Listener<R>, Error = MultistreamSelectError> {
let write = LengthDelimitedBuilder::new()
.length_field_length(1)
.new_write(inner);
let inner = LengthDelimitedFramedRead::<Bytes, _>::new(write);
inner
.into_future()
.map_err(|(e, _)| e.into())
.and_then(|(msg, rest)| {
if msg.as_ref().map(|b| &b[..]) != Some(MULTISTREAM_PROTOCOL_WITH_LF) {
debug!("failed handshake; received: {:?}", msg);
return Err(MultistreamSelectError::FailedHandshake);
}
Ok(rest)
})
.and_then(|socket| {
trace!("sending back /multistream/<version> to finish the handshake");
socket
.send(BytesMut::from(MULTISTREAM_PROTOCOL_WITH_LF))
.from_err()
})
.map(|inner| Listener { inner })
}
/// 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().into_inner()
}
}
impl<R> Sink for Listener<R>
where
R: AsyncRead + AsyncWrite,
{
type SinkItem = ListenerToDialerMessage;
type SinkError = MultistreamSelectError;
#[inline]
fn start_send(&mut self, item: Self::SinkItem) -> StartSend<Self::SinkItem, Self::SinkError> {
match item {
ListenerToDialerMessage::ProtocolAck { name } => {
if !name.starts_with(b"/") {
debug!("invalid protocol name {:?}", name);
return Err(MultistreamSelectError::WrongProtocolName);
}
let mut protocol = BytesMut::from(name);
protocol.extend_from_slice(&[b'\n']);
match self.inner.start_send(protocol) {
Ok(AsyncSink::Ready) => Ok(AsyncSink::Ready),
Ok(AsyncSink::NotReady(mut protocol)) => {
let protocol_len = protocol.len();
protocol.truncate(protocol_len - 1);
let protocol = protocol.freeze();
Ok(AsyncSink::NotReady(ListenerToDialerMessage::ProtocolAck {
name: protocol,
}))
}
Err(err) => Err(err.into()),
}
}
ListenerToDialerMessage::NotAvailable => {
match self.inner.start_send(BytesMut::from(&b"na\n"[..])) {
Ok(AsyncSink::Ready) => Ok(AsyncSink::Ready),
Ok(AsyncSink::NotReady(_)) => {
Ok(AsyncSink::NotReady(ListenerToDialerMessage::NotAvailable))
}
Err(err) => Err(err.into()),
}
}
ListenerToDialerMessage::ProtocolsListResponse { list } => {
use std::iter;
let mut buf = encode::usize_buffer();
let mut out_msg = Vec::from(encode::usize(list.len(), &mut buf));
for elem in &list {
out_msg.extend(iter::once(b'\r'));
out_msg.extend_from_slice(elem);
out_msg.extend(iter::once(b'\n'));
}
match self.inner.start_send(BytesMut::from(out_msg)) {
Ok(AsyncSink::Ready) => Ok(AsyncSink::Ready),
Ok(AsyncSink::NotReady(_)) => {
let m = ListenerToDialerMessage::ProtocolsListResponse { list };
Ok(AsyncSink::NotReady(m))
}
Err(err) => Err(err.into()),
}
}
}
}
#[inline]
fn poll_complete(&mut self) -> Poll<(), Self::SinkError> {
Ok(self.inner.poll_complete()?)
}
}
impl<R> Stream for Listener<R>
where
R: AsyncRead + AsyncWrite,
{
type Item = DialerToListenerMessage;
type Error = MultistreamSelectError;
fn poll(&mut self) -> Poll<Option<Self::Item>, Self::Error> {
let mut frame = match self.inner.poll() {
Ok(Async::Ready(Some(frame))) => frame,
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);
Ok(Async::Ready(Some(
DialerToListenerMessage::ProtocolRequest { name: protocol },
)))
} else if frame == b"ls\n"[..] {
Ok(Async::Ready(Some(
DialerToListenerMessage::ProtocolsListRequest,
)))
} else {
Err(MultistreamSelectError::UnknownMessage)
}
}
}
#[cfg(test)]
mod tests {
extern crate tokio_current_thread;
extern crate tokio_tcp;
use self::tokio_tcp::{TcpListener, TcpStream};
use bytes::Bytes;
use futures::Future;
use futures::{Sink, Stream};
use protocol::{Dialer, Listener, ListenerToDialerMessage, MultistreamSelectError};
#[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::new(connec.unwrap()))
.and_then(|listener| {
let proto_name = Bytes::from("invalid-proto");
listener.send(ListenerToDialerMessage::ProtocolAck { name: proto_name })
});
let client = TcpStream::connect(&listener_addr)
.from_err()
.and_then(move |stream| Dialer::new(stream));
match tokio_current_thread::block_on_all(server.join(client)) {
Err(MultistreamSelectError::WrongProtocolName) => (),
_ => panic!(),
}
}
}

View File

@ -0,0 +1,67 @@
// 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.
use bytes::Bytes;
mod dialer;
mod error;
mod listener;
const MULTISTREAM_PROTOCOL_WITH_LF: &[u8] = b"/multistream/1.0.0\n";
pub use self::dialer::Dialer;
pub use self::error::MultistreamSelectError;
pub use self::listener::Listener;
/// Message sent from the dialer to the listener.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum DialerToListenerMessage {
/// 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 {
/// Name of the protocol.
name: Bytes,
},
/// The dialer requested the list of protocols that the listener supports.
ProtocolsListRequest,
}
/// Message sent from the listener to the dialer.
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ListenerToDialerMessage {
/// The protocol requested by the dialer is accepted. The socket immediately starts using the
/// new protocol.
ProtocolAck { name: Bytes },
/// The protocol requested by the dialer is not supported or available.
NotAvailable,
/// Response to the request for the list of protocols.
ProtocolsListResponse {
/// The list of protocols.
// TODO: use some sort of iterator
list: Vec<Bytes>,
},
}

View File

@ -0,0 +1,207 @@
// 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 unit tests of the library.
#![cfg(test)]
extern crate tokio_current_thread;
extern crate tokio_tcp;
use self::tokio_tcp::{TcpListener, TcpStream};
use bytes::Bytes;
use dialer_select::{dialer_select_proto_parallel, dialer_select_proto_serial};
use futures::Future;
use futures::{Sink, Stream};
use protocol::{Dialer, DialerToListenerMessage, Listener, ListenerToDialerMessage};
use ProtocolChoiceError;
use {dialer_select_proto, listener_select_proto};
#[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::new(connec.unwrap()))
.and_then(|l| l.into_future().map_err(|(e, _)| e))
.and_then(|(msg, rest)| {
let proto = match msg {
Some(DialerToListenerMessage::ProtocolRequest { name }) => name,
_ => panic!(),
};
rest.send(ListenerToDialerMessage::ProtocolAck { name: proto })
});
let client = TcpStream::connect(&listener_addr)
.from_err()
.and_then(move |stream| Dialer::new(stream))
.and_then(move |dialer| {
let p = Bytes::from("/hello/1.0.0");
dialer.send(DialerToListenerMessage::ProtocolRequest { name: p })
})
.and_then(move |dialer| dialer.into_future().map_err(|(e, _)| e))
.and_then(move |(msg, _)| {
let proto = match msg {
Some(ListenerToDialerMessage::ProtocolAck { name }) => name,
_ => panic!(),
};
assert_eq!(proto, "/hello/1.0.0");
Ok(())
});
tokio_current_thread::block_on_all(server.join(client)).unwrap();
}
#[test]
fn select_proto_basic() {
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(|s| s.0.unwrap())
.map_err(|(e, _)| e.into())
.and_then(move |connec| {
let protos = vec![
(Bytes::from("/proto1"), <Bytes as PartialEq>::eq, 0),
(Bytes::from("/proto2"), <Bytes as PartialEq>::eq, 1),
].into_iter();
listener_select_proto(connec, protos).map(|r| r.0)
});
let client = TcpStream::connect(&listener_addr)
.from_err()
.and_then(move |connec| {
let protos = vec![
(Bytes::from("/proto3"), <Bytes as PartialEq>::eq, 2),
(Bytes::from("/proto2"), <Bytes as PartialEq>::eq, 3),
].into_iter();
dialer_select_proto(connec, protos).map(|r| r.0)
});
let (dialer_chosen, listener_chosen) =
tokio_current_thread::block_on_all(client.join(server)).unwrap();
assert_eq!(dialer_chosen, 3);
assert_eq!(listener_chosen, 1);
}
#[test]
fn no_protocol_found() {
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(|s| s.0.unwrap())
.map_err(|(e, _)| e.into())
.and_then(move |connec| {
let protos = vec![
(Bytes::from("/proto1"), <Bytes as PartialEq>::eq, 1),
(Bytes::from("/proto2"), <Bytes as PartialEq>::eq, 2),
].into_iter();
listener_select_proto(connec, protos).map(|r| r.0)
});
let client = TcpStream::connect(&listener_addr)
.from_err()
.and_then(move |connec| {
let protos = vec![
(Bytes::from("/proto3"), <Bytes as PartialEq>::eq, 3),
(Bytes::from("/proto4"), <Bytes as PartialEq>::eq, 4),
].into_iter();
dialer_select_proto(connec, protos).map(|r| r.0)
});
match tokio_current_thread::block_on_all(client.join(server)) {
Err(ProtocolChoiceError::NoProtocolFound) => (),
_ => panic!(),
}
}
#[test]
fn select_proto_parallel() {
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(|s| s.0.unwrap())
.map_err(|(e, _)| e.into())
.and_then(move |connec| {
let protos = vec![
(Bytes::from("/proto1"), <Bytes as PartialEq>::eq, 0),
(Bytes::from("/proto2"), <Bytes as PartialEq>::eq, 1),
].into_iter();
listener_select_proto(connec, protos).map(|r| r.0)
});
let client = TcpStream::connect(&listener_addr)
.from_err()
.and_then(move |connec| {
let protos = vec![
(Bytes::from("/proto3"), <Bytes as PartialEq>::eq, 2),
(Bytes::from("/proto2"), <Bytes as PartialEq>::eq, 3),
].into_iter();
dialer_select_proto_parallel(connec, protos).map(|r| r.0)
});
let (dialer_chosen, listener_chosen) =
tokio_current_thread::block_on_all(client.join(server)).unwrap();
assert_eq!(dialer_chosen, 3);
assert_eq!(listener_chosen, 1);
}
#[test]
fn select_proto_serial() {
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(|s| s.0.unwrap())
.map_err(|(e, _)| e.into())
.and_then(move |connec| {
let protos = vec![
(Bytes::from("/proto1"), <Bytes as PartialEq>::eq, 0),
(Bytes::from("/proto2"), <Bytes as PartialEq>::eq, 1),
].into_iter();
listener_select_proto(connec, protos).map(|r| r.0)
});
let client = TcpStream::connect(&listener_addr)
.from_err()
.and_then(move |connec| {
let protos = vec![(Bytes::from("/proto3"), 2), (Bytes::from("/proto2"), 3)].into_iter();
dialer_select_proto_serial(connec, protos).map(|r| r.0)
});
let (dialer_chosen, listener_chosen) =
tokio_current_thread::block_on_all(client.join(server)).unwrap();
assert_eq!(dialer_chosen, 3);
assert_eq!(listener_chosen, 1);
}