rust-libp2p/core/tests/transport_upgrade.rs

129 lines
4.7 KiB
Rust
Raw Normal View History

Rework the transport upgrade API. (#1240) * Rework the transport upgrade API. ALthough transport upgrades must follow a specific pattern in order fot the resulting transport to be usable with a `Network` or `Swarm`, that pattern is currently not well reflected in the transport upgrade API. Rather, transport upgrades are rather laborious and involve non-trivial code duplication. This commit introduces a `transport::upgrade::Builder` that is obtained from `Transport::upgrade`. The `Builder` encodes the previously implicit rules for transport upgrades: 1. Authentication upgrades must happen first. 2. Any number of upgrades may follow. 3. A multiplexer upgrade must happen last. Since multiplexing is the last (regular) transport upgrade (because that upgrade yields a `StreamMuxer` which is no longer a `AsyncRead` / `AsyncWrite` resource, which the upgrade process is based on), the upgrade starts with `Transport::upgrade` and ends with `Builder::multiplex`, which drops back down to the `Transport`, providing a fluent API. Authentication and multiplexer upgrades must furthermore adhere to a minimal contract w.r.t their outputs: 1. An authentication upgrade is given an (async) I/O resource `C` and must produce a pair `(I, D)` where `I: ConnectionInfo` and `D` is a new (async) I/O resource `D`. 2. A multiplexer upgrade is given an (async) I/O resource `C` and must produce a `M: StreamMuxer`. To that end, two changes to the `secio` and `noise` protocols have been made: 1. The `secio` upgrade now outputs a pair of `(PeerId, SecioOutput)`. The former implements `ConnectionInfo` and the latter `AsyncRead` / `AsyncWrite`, fulfilling the `Builder` contract. 2. A new `NoiseAuthenticated` upgrade has been added that wraps around any noise upgrade (i.e. `NoiseConfig`) and has an output of `(PeerId, NoiseOutput)`, i.e. it checks if the `RemoteIdentity` from the handshake output is an `IdentityKey`, failing if that is not the case. This is the standard upgrade procedure one wants for integrating noise with libp2p-core/swarm. * Cleanup * Add a new integration test. * Add missing license.
2019-09-10 15:42:45 +02:00
// 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.
mod util;
use futures::future::Future;
use futures::stream::Stream;
use libp2p_core::identity;
use libp2p_core::transport::{Transport, MemoryTransport, ListenerEvent};
use libp2p_core::upgrade::{self, UpgradeInfo, Negotiated, InboundUpgrade, OutboundUpgrade};
Rework the transport upgrade API. (#1240) * Rework the transport upgrade API. ALthough transport upgrades must follow a specific pattern in order fot the resulting transport to be usable with a `Network` or `Swarm`, that pattern is currently not well reflected in the transport upgrade API. Rather, transport upgrades are rather laborious and involve non-trivial code duplication. This commit introduces a `transport::upgrade::Builder` that is obtained from `Transport::upgrade`. The `Builder` encodes the previously implicit rules for transport upgrades: 1. Authentication upgrades must happen first. 2. Any number of upgrades may follow. 3. A multiplexer upgrade must happen last. Since multiplexing is the last (regular) transport upgrade (because that upgrade yields a `StreamMuxer` which is no longer a `AsyncRead` / `AsyncWrite` resource, which the upgrade process is based on), the upgrade starts with `Transport::upgrade` and ends with `Builder::multiplex`, which drops back down to the `Transport`, providing a fluent API. Authentication and multiplexer upgrades must furthermore adhere to a minimal contract w.r.t their outputs: 1. An authentication upgrade is given an (async) I/O resource `C` and must produce a pair `(I, D)` where `I: ConnectionInfo` and `D` is a new (async) I/O resource `D`. 2. A multiplexer upgrade is given an (async) I/O resource `C` and must produce a `M: StreamMuxer`. To that end, two changes to the `secio` and `noise` protocols have been made: 1. The `secio` upgrade now outputs a pair of `(PeerId, SecioOutput)`. The former implements `ConnectionInfo` and the latter `AsyncRead` / `AsyncWrite`, fulfilling the `Builder` contract. 2. A new `NoiseAuthenticated` upgrade has been added that wraps around any noise upgrade (i.e. `NoiseConfig`) and has an output of `(PeerId, NoiseOutput)`, i.e. it checks if the `RemoteIdentity` from the handshake output is an `IdentityKey`, failing if that is not the case. This is the standard upgrade procedure one wants for integrating noise with libp2p-core/swarm. * Cleanup * Add a new integration test. * Add missing license.
2019-09-10 15:42:45 +02:00
use libp2p_mplex::MplexConfig;
use libp2p_secio::SecioConfig;
use multiaddr::Multiaddr;
use rand::random;
use std::io;
use tokio_io::{io as nio, AsyncWrite, AsyncRead};
#[derive(Clone)]
struct HelloUpgrade {}
impl UpgradeInfo for HelloUpgrade {
type Info = &'static str;
type InfoIter = std::iter::Once<Self::Info>;
fn protocol_info(&self) -> Self::InfoIter {
std::iter::once("/hello/1")
}
}
impl<C> InboundUpgrade<C> for HelloUpgrade
where
C: AsyncRead + AsyncWrite + Send + 'static
{
type Output = Negotiated<C>;
type Error = io::Error;
type Future = Box<dyn Future<Item = Self::Output, Error = Self::Error> + Send>;
fn upgrade_inbound(self, socket: Negotiated<C>, _: Self::Info) -> Self::Future {
Box::new(nio::read_exact(socket, [0u8; 5]).map(|(io, buf)| {
assert_eq!(&buf[..], "hello".as_bytes());
io
}))
}
}
impl<C> OutboundUpgrade<C> for HelloUpgrade
where
C: AsyncWrite + AsyncRead + Send + 'static,
{
type Output = Negotiated<C>;
type Error = io::Error;
type Future = Box<dyn Future<Item = Self::Output, Error = Self::Error> + Send>;
fn upgrade_outbound(self, socket: Negotiated<C>, _: Self::Info) -> Self::Future {
Box::new(nio::write_all(socket, "hello").map(|(io, _)| io))
}
}
#[test]
fn upgrade_pipeline() {
let listener_keys = identity::Keypair::generate_ed25519();
let listener_id = listener_keys.public().into_peer_id();
let listener_transport = MemoryTransport::default()
.upgrade(upgrade::Version::V1)
Rework the transport upgrade API. (#1240) * Rework the transport upgrade API. ALthough transport upgrades must follow a specific pattern in order fot the resulting transport to be usable with a `Network` or `Swarm`, that pattern is currently not well reflected in the transport upgrade API. Rather, transport upgrades are rather laborious and involve non-trivial code duplication. This commit introduces a `transport::upgrade::Builder` that is obtained from `Transport::upgrade`. The `Builder` encodes the previously implicit rules for transport upgrades: 1. Authentication upgrades must happen first. 2. Any number of upgrades may follow. 3. A multiplexer upgrade must happen last. Since multiplexing is the last (regular) transport upgrade (because that upgrade yields a `StreamMuxer` which is no longer a `AsyncRead` / `AsyncWrite` resource, which the upgrade process is based on), the upgrade starts with `Transport::upgrade` and ends with `Builder::multiplex`, which drops back down to the `Transport`, providing a fluent API. Authentication and multiplexer upgrades must furthermore adhere to a minimal contract w.r.t their outputs: 1. An authentication upgrade is given an (async) I/O resource `C` and must produce a pair `(I, D)` where `I: ConnectionInfo` and `D` is a new (async) I/O resource `D`. 2. A multiplexer upgrade is given an (async) I/O resource `C` and must produce a `M: StreamMuxer`. To that end, two changes to the `secio` and `noise` protocols have been made: 1. The `secio` upgrade now outputs a pair of `(PeerId, SecioOutput)`. The former implements `ConnectionInfo` and the latter `AsyncRead` / `AsyncWrite`, fulfilling the `Builder` contract. 2. A new `NoiseAuthenticated` upgrade has been added that wraps around any noise upgrade (i.e. `NoiseConfig`) and has an output of `(PeerId, NoiseOutput)`, i.e. it checks if the `RemoteIdentity` from the handshake output is an `IdentityKey`, failing if that is not the case. This is the standard upgrade procedure one wants for integrating noise with libp2p-core/swarm. * Cleanup * Add a new integration test. * Add missing license.
2019-09-10 15:42:45 +02:00
.authenticate(SecioConfig::new(listener_keys))
.apply(HelloUpgrade {})
.apply(HelloUpgrade {})
.apply(HelloUpgrade {})
.multiplex(MplexConfig::default())
.and_then(|(peer, mplex), _| {
// Gracefully close the connection to allow protocol
// negotiation to complete.
util::CloseMuxer::new(mplex).map(move |mplex| (peer, mplex))
});
let dialer_keys = identity::Keypair::generate_ed25519();
let dialer_id = dialer_keys.public().into_peer_id();
let dialer_transport = MemoryTransport::default()
.upgrade(upgrade::Version::V1)
Rework the transport upgrade API. (#1240) * Rework the transport upgrade API. ALthough transport upgrades must follow a specific pattern in order fot the resulting transport to be usable with a `Network` or `Swarm`, that pattern is currently not well reflected in the transport upgrade API. Rather, transport upgrades are rather laborious and involve non-trivial code duplication. This commit introduces a `transport::upgrade::Builder` that is obtained from `Transport::upgrade`. The `Builder` encodes the previously implicit rules for transport upgrades: 1. Authentication upgrades must happen first. 2. Any number of upgrades may follow. 3. A multiplexer upgrade must happen last. Since multiplexing is the last (regular) transport upgrade (because that upgrade yields a `StreamMuxer` which is no longer a `AsyncRead` / `AsyncWrite` resource, which the upgrade process is based on), the upgrade starts with `Transport::upgrade` and ends with `Builder::multiplex`, which drops back down to the `Transport`, providing a fluent API. Authentication and multiplexer upgrades must furthermore adhere to a minimal contract w.r.t their outputs: 1. An authentication upgrade is given an (async) I/O resource `C` and must produce a pair `(I, D)` where `I: ConnectionInfo` and `D` is a new (async) I/O resource `D`. 2. A multiplexer upgrade is given an (async) I/O resource `C` and must produce a `M: StreamMuxer`. To that end, two changes to the `secio` and `noise` protocols have been made: 1. The `secio` upgrade now outputs a pair of `(PeerId, SecioOutput)`. The former implements `ConnectionInfo` and the latter `AsyncRead` / `AsyncWrite`, fulfilling the `Builder` contract. 2. A new `NoiseAuthenticated` upgrade has been added that wraps around any noise upgrade (i.e. `NoiseConfig`) and has an output of `(PeerId, NoiseOutput)`, i.e. it checks if the `RemoteIdentity` from the handshake output is an `IdentityKey`, failing if that is not the case. This is the standard upgrade procedure one wants for integrating noise with libp2p-core/swarm. * Cleanup * Add a new integration test. * Add missing license.
2019-09-10 15:42:45 +02:00
.authenticate(SecioConfig::new(dialer_keys))
.apply(HelloUpgrade {})
.apply(HelloUpgrade {})
.apply(HelloUpgrade {})
.multiplex(MplexConfig::default())
.and_then(|(peer, mplex), _| {
// Gracefully close the connection to allow protocol
// negotiation to complete.
util::CloseMuxer::new(mplex).map(move |mplex| (peer, mplex))
});
let listen_addr: Multiaddr = format!("/memory/{}", random::<u64>()).parse().unwrap();
let listener = listener_transport.listen_on(listen_addr.clone()).unwrap()
.filter_map(ListenerEvent::into_upgrade)
.for_each(move |(upgrade, _remote_addr)| {
let dialer = dialer_id.clone();
upgrade.map(move |(peer, _mplex)| {
assert_eq!(peer, dialer)
})
})
.map_err(|e| panic!("Listener error: {}", e));
let dialer = dialer_transport.dial(listen_addr).unwrap()
.map(move |(peer, _mplex)| {
assert_eq!(peer, listener_id)
});
let mut rt = tokio::runtime::Runtime::new().unwrap();
rt.spawn(listener);
rt.block_on(dialer).unwrap()
}