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};
|
2019-09-23 12:04:39 +02:00
|
|
|
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()
|
2019-09-23 12:04:39 +02:00
|
|
|
.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()
|
2019-09-23 12:04:39 +02:00
|
|
|
.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()
|
|
|
|
}
|
|
|
|
|