mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-04-25 03:02:12 +00:00
Merge branch 'rust_master' into fluence_master
# Conflicts: # examples/distributed-key-value-store.rs # examples/ipfs-kad.rs # protocols/kad/src/behaviour.rs # protocols/kad/src/behaviour/test.rs # protocols/kad/src/lib.rs # protocols/kad/src/query/peers/closest.rs
This commit is contained in:
commit
8bc0c9db51
6
.github/workflows/ci.yml
vendored
6
.github/workflows/ci.yml
vendored
@ -38,7 +38,7 @@ jobs:
|
||||
container:
|
||||
image: rust
|
||||
env:
|
||||
CC: clang-9
|
||||
CC: clang-10
|
||||
steps:
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install Rust
|
||||
@ -50,9 +50,9 @@ jobs:
|
||||
- name: Install a recent version of clang
|
||||
run: |
|
||||
wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | apt-key add -
|
||||
echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-9 main" >> /etc/apt/sources.list
|
||||
echo "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-10 main" >> /etc/apt/sources.list
|
||||
apt-get update
|
||||
apt-get install -y clang-9
|
||||
apt-get install -y clang-10
|
||||
- name: Install CMake
|
||||
run: apt-get install -y cmake
|
||||
- name: Cache cargo registry
|
||||
|
54
CHANGELOG.md
54
CHANGELOG.md
@ -1,10 +1,64 @@
|
||||
# Version ???
|
||||
|
||||
|
||||
# Version 0.19.0 (2020-05-18)
|
||||
|
||||
- `libp2p-core`, `libp2p-swarm`: Added support for multiple dialing
|
||||
attempts per peer, with a configurable limit.
|
||||
[PR 1506](https://github.com/libp2p/rust-libp2p/pull/1506)
|
||||
|
||||
- `libp2p-core`: `PeerId`s that use the identity hashing will now be properly
|
||||
displayed using the string representation of an identity multihash, rather
|
||||
than the canonical SHA 256 representation.
|
||||
[PR 1576](https://github.com/libp2p/rust-libp2p/pull/1576)
|
||||
|
||||
- `libp2p-core`: Updated to multihash 0.11.0.
|
||||
[PR 1566](https://github.com/libp2p/rust-libp2p/pull/1566)
|
||||
|
||||
- `libp2p-core`: Make the number of events buffered to/from tasks configurable.
|
||||
[PR 1574](https://github.com/libp2p/rust-libp2p/pull/1574)
|
||||
|
||||
- `libp2p-dns`, `parity-multiaddr`: Added support for the `/dns` multiaddr
|
||||
protocol. Additionally, the `multiaddr::from_url` function will now use
|
||||
`/dns` instead of `/dns4`.
|
||||
[PR 1575](https://github.com/libp2p/rust-libp2p/pull/1575)
|
||||
|
||||
- `libp2p-noise`: Added the `X25519Spec` protocol suite which uses
|
||||
libp2p-noise-spec compliant signatures on static keys as well as the
|
||||
`/noise` protocol upgrade, hence providing a libp2p-noise-spec compliant
|
||||
`XX` handshake. `IK` and `IX` are still supported with `X25519Spec`
|
||||
though not guaranteed to be interoperable with other libp2p
|
||||
implementations as these handshake patterns are not currently
|
||||
included in the libp2p-noise-spec. The `X25519Spec` implementation
|
||||
will eventually replace the current `X25519` implementation, with
|
||||
the former being removed. To upgrade without interruptions, you may
|
||||
temporarily include `NoiseConfig`s for both implementations as
|
||||
alternatives in your transport upgrade pipeline.
|
||||
|
||||
- `libp2p-kad`: Consider fixed (K_VALUE) amount of peers at closest query
|
||||
initialization. Unless `KademliaConfig::set_replication_factor` is used change
|
||||
has no effect.
|
||||
[PR 1536](https://github.com/libp2p/rust-libp2p/pull/1536)
|
||||
|
||||
- `libp2p-kad`: Provide more insight into, and control of, the execution of
|
||||
queries. All query results are now wrapped in `KademliaEvent::QueryResult`.
|
||||
As a side-effect of these changes and for as long as the record storage
|
||||
API is not asynchronous, local storage errors on `put_record` are reported
|
||||
synchronously in a `Result`, instead of being reported asynchronously by
|
||||
an event.
|
||||
[PR 1567](https://github.com/libp2p/rust-libp2p/pull/1567)
|
||||
|
||||
- `libp2p-tcp`, `libp2p`: Made the `libp2p-tcp/async-std` feature flag
|
||||
disabled by default, and split the `libp2p/tcp` feature in two:
|
||||
`tcp-async-std` and `tcp-tokio`. `tcp-async-std` is still enabled by default.
|
||||
[PR 1471](https://github.com/libp2p/rust-libp2p/pull/1471)
|
||||
|
||||
- `libp2p-tcp`: On listeners started with an IPv6 multi-address the socket
|
||||
option `IPV6_V6ONLY` is set to true. Instead of relying on IPv4-mapped IPv6
|
||||
address support, two listeners can be started if IPv4 and IPv6 should both
|
||||
be supported. IPv4 listener addresses are not affected by this change.
|
||||
[PR 1555](https://github.com/libp2p/rust-libp2p/pull/1555)
|
||||
|
||||
# Version 0.18.1 (2020-04-17)
|
||||
|
||||
- `libp2p-swarm`: Make sure inject_dial_failure is called in all situations.
|
||||
|
53
Cargo.toml
53
Cargo.toml
@ -2,7 +2,7 @@
|
||||
name = "libp2p"
|
||||
edition = "2018"
|
||||
description = "Peer-to-peer networking library"
|
||||
version = "0.18.1"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -25,7 +25,7 @@ default = [
|
||||
"pnet",
|
||||
"secio",
|
||||
"secp256k1",
|
||||
"tcp",
|
||||
"tcp-async-std",
|
||||
"uds",
|
||||
"wasm-ext",
|
||||
"websocket",
|
||||
@ -44,7 +44,8 @@ ping = ["libp2p-ping"]
|
||||
plaintext = ["libp2p-plaintext"]
|
||||
pnet = ["libp2p-pnet"]
|
||||
secio = ["libp2p-secio"]
|
||||
tcp = ["libp2p-tcp"]
|
||||
tcp-async-std = ["libp2p-tcp", "libp2p-tcp/async-std"]
|
||||
tcp-tokio = ["libp2p-tcp", "libp2p-tcp/tokio"]
|
||||
uds = ["libp2p-uds"]
|
||||
wasm-ext = ["libp2p-wasm-ext"]
|
||||
websocket = ["libp2p-websocket"]
|
||||
@ -54,36 +55,36 @@ secp256k1 = ["libp2p-core/secp256k1", "libp2p-secio/secp256k1"]
|
||||
[dependencies]
|
||||
bytes = "0.5"
|
||||
futures = "0.3.1"
|
||||
multiaddr = { package = "parity-multiaddr", version = "0.8.0", path = "misc/multiaddr" }
|
||||
multihash = "0.10"
|
||||
multiaddr = { package = "parity-multiaddr", version = "0.9.0", path = "misc/multiaddr" }
|
||||
multihash = "0.11.0"
|
||||
lazy_static = "1.2"
|
||||
libp2p-mplex = { version = "0.18.0", path = "muxers/mplex", optional = true }
|
||||
libp2p-identify = { version = "0.18.0", path = "protocols/identify", optional = true }
|
||||
libp2p-kad = { version = "0.18.0", path = "protocols/kad", optional = true }
|
||||
libp2p-floodsub = { version = "0.18.0", path = "protocols/floodsub", optional = true }
|
||||
libp2p-gossipsub = { version = "0.18.0", path = "./protocols/gossipsub", optional = true }
|
||||
libp2p-ping = { version = "0.18.0", path = "protocols/ping", optional = true }
|
||||
libp2p-plaintext = { version = "0.18.0", path = "protocols/plaintext", optional = true }
|
||||
libp2p-pnet = { version = "0.18.0", path = "protocols/pnet", optional = true }
|
||||
libp2p-core = { version = "0.18.0", path = "core" }
|
||||
libp2p-core-derive = { version = "0.18.0", path = "misc/core-derive" }
|
||||
libp2p-secio = { version = "0.18.0", path = "protocols/secio", default-features = false, optional = true }
|
||||
libp2p-swarm = { version = "0.18.1", path = "swarm" }
|
||||
libp2p-uds = { version = "0.18.0", path = "transports/uds", optional = true }
|
||||
libp2p-wasm-ext = { version = "0.18.0", path = "transports/wasm-ext", optional = true }
|
||||
libp2p-yamux = { version = "0.18.0", path = "muxers/yamux", optional = true }
|
||||
libp2p-noise = { version = "0.18.0", path = "protocols/noise", optional = true }
|
||||
libp2p-mplex = { version = "0.19.0", path = "muxers/mplex", optional = true }
|
||||
libp2p-identify = { version = "0.19.0", path = "protocols/identify", optional = true }
|
||||
libp2p-kad = { version = "0.19.0", path = "protocols/kad", optional = true }
|
||||
libp2p-floodsub = { version = "0.19.0", path = "protocols/floodsub", optional = true }
|
||||
libp2p-gossipsub = { version = "0.19.0", path = "./protocols/gossipsub", optional = true }
|
||||
libp2p-ping = { version = "0.19.0", path = "protocols/ping", optional = true }
|
||||
libp2p-plaintext = { version = "0.19.0", path = "protocols/plaintext", optional = true }
|
||||
libp2p-pnet = { version = "0.19.0", path = "protocols/pnet", optional = true }
|
||||
libp2p-core = { version = "0.19.0", path = "core" }
|
||||
libp2p-core-derive = { version = "0.19.0", path = "misc/core-derive" }
|
||||
libp2p-secio = { version = "0.19.0", path = "protocols/secio", default-features = false, optional = true }
|
||||
libp2p-swarm = { version = "0.19.0", path = "swarm" }
|
||||
libp2p-uds = { version = "0.19.0", path = "transports/uds", optional = true }
|
||||
libp2p-wasm-ext = { version = "0.19.0", path = "transports/wasm-ext", optional = true }
|
||||
libp2p-yamux = { version = "0.19.0", path = "muxers/yamux", optional = true }
|
||||
libp2p-noise = { version = "0.19.0", path = "protocols/noise", optional = true }
|
||||
parking_lot = "0.10.0"
|
||||
pin-project = "0.4.6"
|
||||
smallvec = "1.0"
|
||||
wasm-timer = "0.2.4"
|
||||
|
||||
[target.'cfg(not(any(target_os = "emscripten", target_os = "unknown")))'.dependencies]
|
||||
libp2p-deflate = { version = "0.18.0", path = "protocols/deflate", optional = true }
|
||||
libp2p-dns = { version = "0.18.0", path = "transports/dns", optional = true }
|
||||
libp2p-mdns = { version = "0.18.0", path = "protocols/mdns", optional = true }
|
||||
libp2p-tcp = { version = "0.18.0", path = "transports/tcp", optional = true }
|
||||
libp2p-websocket = { version = "0.18.0", path = "transports/websocket", optional = true }
|
||||
libp2p-deflate = { version = "0.19.0", path = "protocols/deflate", optional = true }
|
||||
libp2p-dns = { version = "0.19.0", path = "transports/dns", optional = true }
|
||||
libp2p-mdns = { version = "0.19.0", path = "protocols/mdns", optional = true }
|
||||
libp2p-tcp = { version = "0.19.0", path = "transports/tcp", optional = true }
|
||||
libp2p-websocket = { version = "0.19.0", path = "transports/websocket", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
async-std = "1.0"
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-core"
|
||||
edition = "2018"
|
||||
description = "Core traits and structs of libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -20,8 +20,8 @@ futures-timer = "3"
|
||||
lazy_static = "1.2"
|
||||
libsecp256k1 = { version = "0.3.1", optional = true }
|
||||
log = "0.4"
|
||||
multiaddr = { package = "parity-multiaddr", version = "0.8.0", path = "../misc/multiaddr" }
|
||||
multihash = "0.10"
|
||||
multiaddr = { package = "parity-multiaddr", version = "0.9.0", path = "../misc/multiaddr" }
|
||||
multihash = "0.11.0"
|
||||
multistream-select = { version = "0.8.0", path = "../misc/multistream-select" }
|
||||
parking_lot = "0.10.0"
|
||||
pin-project = "0.4.6"
|
||||
@ -40,9 +40,9 @@ ring = { version = "0.16.9", features = ["alloc", "std"], default-features = fal
|
||||
|
||||
[dev-dependencies]
|
||||
async-std = "1.0"
|
||||
libp2p-mplex = { version = "0.18.0", path = "../muxers/mplex" }
|
||||
libp2p-secio = { version = "0.18.0", path = "../protocols/secio" }
|
||||
libp2p-tcp = { version = "0.18.0", path = "../transports/tcp" }
|
||||
libp2p-mplex = { version = "0.19.0", path = "../muxers/mplex" }
|
||||
libp2p-secio = { version = "0.19.0", path = "../protocols/secio" }
|
||||
libp2p-tcp = { version = "0.19.0", path = "../transports/tcp" }
|
||||
quickcheck = "0.9.0"
|
||||
wasm-timer = "0.2"
|
||||
|
||||
|
@ -99,6 +99,9 @@ pub struct Manager<I, O, H, E, HE, C> {
|
||||
/// Next available identifier for a new connection / task.
|
||||
next_task_id: TaskId,
|
||||
|
||||
/// Size of the task command buffer (per task).
|
||||
task_command_buffer_size: usize,
|
||||
|
||||
/// The executor to use for running the background tasks. If `None`,
|
||||
/// the tasks are kept in `local_spawns` instead and polled on the
|
||||
/// current thread when the manager is polled for new events.
|
||||
@ -127,6 +130,32 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Configuration options when creating a [`Manager`].
|
||||
///
|
||||
/// The default configuration specifies no dedicated task executor, a
|
||||
/// task event buffer size of 32, and a task command buffer size of 7.
|
||||
#[non_exhaustive]
|
||||
pub struct ManagerConfig {
|
||||
/// Executor to use to spawn tasks.
|
||||
pub executor: Option<Box<dyn Executor + Send>>,
|
||||
|
||||
/// Size of the task command buffer (per task).
|
||||
pub task_command_buffer_size: usize,
|
||||
|
||||
/// Size of the task event buffer (for all tasks).
|
||||
pub task_event_buffer_size: usize,
|
||||
}
|
||||
|
||||
impl Default for ManagerConfig {
|
||||
fn default() -> Self {
|
||||
ManagerConfig {
|
||||
executor: None,
|
||||
task_event_buffer_size: 32,
|
||||
task_command_buffer_size: 7,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Internal information about a running task.
|
||||
///
|
||||
/// Contains the sender to deliver event messages to the task, and
|
||||
@ -196,12 +225,13 @@ pub enum Event<'a, I, O, H, TE, HE, C> {
|
||||
|
||||
impl<I, O, H, TE, HE, C> Manager<I, O, H, TE, HE, C> {
|
||||
/// Creates a new connection manager.
|
||||
pub fn new(executor: Option<Box<dyn Executor + Send>>) -> Self {
|
||||
let (tx, rx) = mpsc::channel(1);
|
||||
pub fn new(config: ManagerConfig) -> Self {
|
||||
let (tx, rx) = mpsc::channel(config.task_event_buffer_size);
|
||||
Self {
|
||||
tasks: FnvHashMap::default(),
|
||||
next_task_id: TaskId(0),
|
||||
executor,
|
||||
task_command_buffer_size: config.task_command_buffer_size,
|
||||
executor: config.executor,
|
||||
local_spawns: FuturesUnordered::new(),
|
||||
events_tx: tx,
|
||||
events_rx: rx
|
||||
@ -234,7 +264,7 @@ impl<I, O, H, TE, HE, C> Manager<I, O, H, TE, HE, C> {
|
||||
let task_id = self.next_task_id;
|
||||
self.next_task_id.0 += 1;
|
||||
|
||||
let (tx, rx) = mpsc::channel(4);
|
||||
let (tx, rx) = mpsc::channel(self.task_command_buffer_size);
|
||||
self.tasks.insert(task_id, TaskInfo { sender: tx, state: TaskState::Pending });
|
||||
|
||||
let task = Box::pin(Task::pending(task_id, self.events_tx.clone(), rx, future, handler));
|
||||
@ -269,7 +299,7 @@ impl<I, O, H, TE, HE, C> Manager<I, O, H, TE, HE, C> {
|
||||
let task_id = self.next_task_id;
|
||||
self.next_task_id.0 += 1;
|
||||
|
||||
let (tx, rx) = mpsc::channel(4);
|
||||
let (tx, rx) = mpsc::channel(self.task_command_buffer_size);
|
||||
self.tasks.insert(task_id, TaskInfo {
|
||||
sender: tx, state: TaskState::Established(info)
|
||||
});
|
||||
|
@ -19,7 +19,6 @@
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use crate::{
|
||||
Executor,
|
||||
ConnectedPoint,
|
||||
PeerId,
|
||||
connection::{
|
||||
@ -36,7 +35,7 @@ use crate::{
|
||||
OutgoingInfo,
|
||||
Substream,
|
||||
PendingConnectionError,
|
||||
manager::{self, Manager},
|
||||
manager::{self, Manager, ManagerConfig},
|
||||
},
|
||||
muxing::StreamMuxer,
|
||||
};
|
||||
@ -175,13 +174,13 @@ where
|
||||
/// Creates a new empty `Pool`.
|
||||
pub fn new(
|
||||
local_id: TPeerId,
|
||||
executor: Option<Box<dyn Executor + Send>>,
|
||||
manager_config: ManagerConfig,
|
||||
limits: PoolLimits
|
||||
) -> Self {
|
||||
Pool {
|
||||
local_id,
|
||||
limits,
|
||||
manager: Manager::new(executor),
|
||||
manager: Manager::new(manager_config),
|
||||
established: Default::default(),
|
||||
pending: Default::default(),
|
||||
}
|
||||
@ -225,12 +224,7 @@ where
|
||||
TPeerId: Clone + Send + 'static,
|
||||
{
|
||||
let endpoint = info.to_connected_point();
|
||||
if let Some(limit) = self.limits.max_incoming {
|
||||
let current = self.iter_pending_incoming().count();
|
||||
if current >= limit {
|
||||
return Err(ConnectionLimit { limit, current })
|
||||
}
|
||||
}
|
||||
self.limits.check_incoming(|| self.iter_pending_incoming().count())?;
|
||||
Ok(self.add_pending(future, handler, endpoint, None))
|
||||
}
|
||||
|
||||
@ -267,6 +261,11 @@ where
|
||||
TPeerId: Clone + Send + 'static,
|
||||
{
|
||||
self.limits.check_outgoing(|| self.iter_pending_outgoing().count())?;
|
||||
|
||||
if let Some(peer) = &info.peer_id {
|
||||
self.limits.check_outgoing_per_peer(|| self.num_peer_outgoing(peer))?;
|
||||
}
|
||||
|
||||
let endpoint = info.to_connected_point();
|
||||
Ok(self.add_pending(future, handler, endpoint, info.peer_id.cloned()))
|
||||
}
|
||||
@ -465,6 +464,13 @@ where
|
||||
self.established.get(peer).map_or(0, |conns| conns.len())
|
||||
}
|
||||
|
||||
/// Counts the number of pending outgoing connections to the given peer.
|
||||
pub fn num_peer_outgoing(&self, peer: &TPeerId) -> usize {
|
||||
self.iter_pending_outgoing()
|
||||
.filter(|info| info.peer_id == Some(peer))
|
||||
.count()
|
||||
}
|
||||
|
||||
/// Returns an iterator over all established connections of `peer`.
|
||||
pub fn iter_peer_established<'a>(&'a mut self, peer: &TPeerId)
|
||||
-> EstablishedConnectionIter<'a,
|
||||
@ -837,6 +843,7 @@ pub struct PoolLimits {
|
||||
pub max_outgoing: Option<usize>,
|
||||
pub max_incoming: Option<usize>,
|
||||
pub max_established_per_peer: Option<usize>,
|
||||
pub max_outgoing_per_peer: Option<usize>,
|
||||
}
|
||||
|
||||
impl PoolLimits {
|
||||
@ -854,6 +861,20 @@ impl PoolLimits {
|
||||
Self::check(current, self.max_outgoing)
|
||||
}
|
||||
|
||||
fn check_incoming<F>(&self, current: F) -> Result<(), ConnectionLimit>
|
||||
where
|
||||
F: FnOnce() -> usize
|
||||
{
|
||||
Self::check(current, self.max_incoming)
|
||||
}
|
||||
|
||||
fn check_outgoing_per_peer<F>(&self, current: F) -> Result<(), ConnectionLimit>
|
||||
where
|
||||
F: FnOnce() -> usize
|
||||
{
|
||||
Self::check(current, self.max_outgoing_per_peer)
|
||||
}
|
||||
|
||||
fn check<F>(current: F, limit: Option<usize>) -> Result<(), ConnectionLimit>
|
||||
where
|
||||
F: FnOnce() -> usize
|
||||
|
@ -43,6 +43,7 @@ use crate::{
|
||||
ListenersStream,
|
||||
PendingConnectionError,
|
||||
Substream,
|
||||
manager::ManagerConfig,
|
||||
pool::{Pool, PoolEvent, PoolLimits},
|
||||
},
|
||||
muxing::StreamMuxer,
|
||||
@ -50,12 +51,14 @@ use crate::{
|
||||
};
|
||||
use fnv::{FnvHashMap};
|
||||
use futures::{prelude::*, future};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
collections::hash_map,
|
||||
convert::TryFrom as _,
|
||||
error,
|
||||
fmt,
|
||||
hash::Hash,
|
||||
num::NonZeroUsize,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
@ -78,21 +81,17 @@ where
|
||||
|
||||
/// The ongoing dialing attempts.
|
||||
///
|
||||
/// The `Network` enforces a single ongoing dialing attempt per peer,
|
||||
/// even if multiple (established) connections per peer are allowed.
|
||||
/// However, a single dialing attempt operates on a list of addresses
|
||||
/// to connect to, which can be extended with new addresses while
|
||||
/// the connection attempt is still in progress. Thereby each
|
||||
/// dialing attempt is associated with a new connection and hence a new
|
||||
/// connection ID.
|
||||
/// There may be multiple ongoing dialing attempts to the same peer.
|
||||
/// Each dialing attempt is associated with a new connection and hence
|
||||
/// a new connection ID.
|
||||
///
|
||||
/// > **Note**: `dialing` must be consistent with the pending outgoing
|
||||
/// > connections in `pool`. That is, for every entry in `dialing`
|
||||
/// > there must exist a pending outgoing connection in `pool` with
|
||||
/// > the same connection ID. This is ensured by the implementation of
|
||||
/// > `Network` (see `dial_peer_impl` and `on_connection_failed`)
|
||||
/// > together with the implementation of `DialingConnection::abort`.
|
||||
dialing: FnvHashMap<TPeerId, peer::DialingAttempt>,
|
||||
/// > together with the implementation of `DialingAttempt::abort`.
|
||||
dialing: FnvHashMap<TPeerId, SmallVec<[peer::DialingState; 10]>>,
|
||||
}
|
||||
|
||||
impl<TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId> fmt::Debug for
|
||||
@ -157,7 +156,7 @@ where
|
||||
Network {
|
||||
local_peer_id,
|
||||
listeners: ListenersStream::new(transport),
|
||||
pool: Pool::new(pool_local_id, config.executor, config.pool_limits),
|
||||
pool: Pool::new(pool_local_id, config.manager_config, config.pool_limits),
|
||||
dialing: Default::default(),
|
||||
}
|
||||
}
|
||||
@ -381,8 +380,11 @@ where
|
||||
Poll::Pending => return Poll::Pending,
|
||||
Poll::Ready(PoolEvent::ConnectionEstablished { connection, num_established }) => {
|
||||
match self.dialing.entry(connection.peer_id().clone()) {
|
||||
hash_map::Entry::Occupied(e) if e.get().id == connection.id() => {
|
||||
e.remove();
|
||||
hash_map::Entry::Occupied(mut e) => {
|
||||
e.get_mut().retain(|s| s.current.0 != connection.id());
|
||||
if e.get().is_empty() {
|
||||
e.remove();
|
||||
}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
@ -453,7 +455,7 @@ fn dial_peer_impl<TMuxer, TInEvent, TOutEvent, THandler, TTrans, TConnInfo, TPee
|
||||
transport: TTrans,
|
||||
pool: &mut Pool<TInEvent, TOutEvent, THandler, TTrans::Error,
|
||||
<THandler::Handler as ConnectionHandler>::Error, TConnInfo, TPeerId>,
|
||||
dialing: &mut FnvHashMap<TPeerId, peer::DialingAttempt>,
|
||||
dialing: &mut FnvHashMap<TPeerId, SmallVec<[peer::DialingState; 10]>>,
|
||||
opts: DialingOpts<TPeerId, THandler>
|
||||
) -> Result<ConnectionId, ConnectionLimit>
|
||||
where
|
||||
@ -489,14 +491,12 @@ where
|
||||
};
|
||||
|
||||
if let Ok(id) = &result {
|
||||
let former = dialing.insert(opts.peer,
|
||||
peer::DialingAttempt {
|
||||
id: *id,
|
||||
current: opts.address,
|
||||
next: opts.remaining,
|
||||
dialing.entry(opts.peer).or_default().push(
|
||||
peer::DialingState {
|
||||
current: (*id, opts.address),
|
||||
remaining: opts.remaining,
|
||||
},
|
||||
);
|
||||
debug_assert!(former.is_none());
|
||||
}
|
||||
|
||||
result
|
||||
@ -508,7 +508,7 @@ where
|
||||
/// If the failed connection attempt was a dialing attempt and there
|
||||
/// are more addresses to try, new `DialingOpts` are returned.
|
||||
fn on_connection_failed<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>(
|
||||
dialing: &mut FnvHashMap<TPeerId, peer::DialingAttempt>,
|
||||
dialing: &mut FnvHashMap<TPeerId, SmallVec<[peer::DialingState; 10]>>,
|
||||
id: ConnectionId,
|
||||
endpoint: ConnectedPoint,
|
||||
error: PendingConnectionError<TTrans::Error>,
|
||||
@ -521,27 +521,34 @@ where
|
||||
TPeerId: Eq + Hash + Clone,
|
||||
{
|
||||
// Check if the failed connection is associated with a dialing attempt.
|
||||
// TODO: could be more optimal than iterating over everything
|
||||
let dialing_peer = dialing.iter() // (1)
|
||||
.find(|(_, a)| a.id == id)
|
||||
.map(|(p, _)| p.clone());
|
||||
let dialing_failed = dialing.iter_mut()
|
||||
.find_map(|(peer, attempts)| {
|
||||
if let Some(pos) = attempts.iter().position(|s| s.current.0 == id) {
|
||||
let attempt = attempts.remove(pos);
|
||||
let last = attempts.is_empty();
|
||||
Some((peer.clone(), attempt, last))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(peer_id) = dialing_peer {
|
||||
// A pending outgoing connection to a known peer failed.
|
||||
let mut attempt = dialing.remove(&peer_id).expect("by (1)");
|
||||
if let Some((peer_id, mut attempt, last)) = dialing_failed {
|
||||
if last {
|
||||
dialing.remove(&peer_id);
|
||||
}
|
||||
|
||||
let num_remain = u32::try_from(attempt.next.len()).unwrap();
|
||||
let failed_addr = attempt.current.clone();
|
||||
let num_remain = u32::try_from(attempt.remaining.len()).unwrap();
|
||||
let failed_addr = attempt.current.1.clone();
|
||||
|
||||
let (opts, attempts_remaining) =
|
||||
if num_remain > 0 {
|
||||
if let Some(handler) = handler {
|
||||
let next_attempt = attempt.next.remove(0);
|
||||
let next_attempt = attempt.remaining.remove(0);
|
||||
let opts = DialingOpts {
|
||||
peer: peer_id.clone(),
|
||||
handler,
|
||||
address: next_attempt,
|
||||
remaining: attempt.next
|
||||
remaining: attempt.remaining
|
||||
};
|
||||
(Some(opts), num_remain)
|
||||
} else {
|
||||
@ -581,25 +588,33 @@ where
|
||||
/// Information about the network obtained by [`Network::info()`].
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct NetworkInfo {
|
||||
/// The total number of connected peers.
|
||||
pub num_peers: usize,
|
||||
/// The total number of connections, both established and pending.
|
||||
pub num_connections: usize,
|
||||
/// The total number of pending connections, both incoming and outgoing.
|
||||
pub num_connections_pending: usize,
|
||||
/// The total number of established connections.
|
||||
pub num_connections_established: usize,
|
||||
}
|
||||
|
||||
/// The (optional) configuration for a [`Network`].
|
||||
///
|
||||
/// The default configuration specifies no dedicated task executor
|
||||
/// and no connection limits.
|
||||
/// The default configuration specifies no dedicated task executor, no
|
||||
/// connection limits, a connection event buffer size of 32, and a
|
||||
/// `notify_handler` buffer size of 8.
|
||||
#[derive(Default)]
|
||||
pub struct NetworkConfig {
|
||||
executor: Option<Box<dyn Executor + Send>>,
|
||||
/// Note that the `ManagerConfig`s task command buffer always provides
|
||||
/// one "free" slot per task. Thus the given total `notify_handler_buffer_size`
|
||||
/// exposed for configuration on the `Network` is reduced by one.
|
||||
manager_config: ManagerConfig,
|
||||
pool_limits: PoolLimits,
|
||||
}
|
||||
|
||||
impl NetworkConfig {
|
||||
pub fn set_executor(&mut self, e: Box<dyn Executor + Send>) -> &mut Self {
|
||||
self.executor = Some(e);
|
||||
self.manager_config.executor = Some(e);
|
||||
self
|
||||
}
|
||||
|
||||
@ -616,7 +631,30 @@ impl NetworkConfig {
|
||||
}
|
||||
|
||||
pub fn executor(&self) -> Option<&Box<dyn Executor + Send>> {
|
||||
self.executor.as_ref()
|
||||
self.manager_config.executor.as_ref()
|
||||
}
|
||||
|
||||
/// Sets the maximum number of events sent to a connection's background task
|
||||
/// that may be buffered, if the task cannot keep up with their consumption and
|
||||
/// delivery to the connection handler.
|
||||
///
|
||||
/// When the buffer for a particular connection is full, `notify_handler` will no
|
||||
/// longer be able to deliver events to the associated `ConnectionHandler`,
|
||||
/// thus exerting back-pressure on the connection and peer API.
|
||||
pub fn set_notify_handler_buffer_size(&mut self, n: NonZeroUsize) -> &mut Self {
|
||||
self.manager_config.task_command_buffer_size = n.get() - 1;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum number of buffered connection events (beyond a guaranteed
|
||||
/// buffer of 1 event per connection).
|
||||
///
|
||||
/// When the buffer is full, the background tasks of all connections will stall.
|
||||
/// In this way, the consumers of network events exert back-pressure on
|
||||
/// the network connection I/O.
|
||||
pub fn set_connection_event_buffer_size(&mut self, n: usize) -> &mut Self {
|
||||
self.manager_config.task_event_buffer_size = n;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_incoming_limit(&mut self, n: usize) -> &mut Self {
|
||||
@ -633,4 +671,9 @@ impl NetworkConfig {
|
||||
self.pool_limits.max_established_per_peer = Some(n);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_outgoing_per_peer_limit(&mut self, n: usize) -> &mut Self {
|
||||
self.pool_limits.max_outgoing_per_peer = Some(n);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
@ -35,8 +35,11 @@ use crate::{
|
||||
IntoConnectionHandler,
|
||||
PendingConnection,
|
||||
Substream,
|
||||
pool::Pool,
|
||||
},
|
||||
};
|
||||
use fnv::FnvHashMap;
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
collections::hash_map,
|
||||
error,
|
||||
@ -47,6 +50,10 @@ use super::{Network, DialingOpts};
|
||||
|
||||
/// The possible representations of a peer in a [`Network`], as
|
||||
/// seen by the local node.
|
||||
///
|
||||
/// > **Note**: In any state there may always be a pending incoming
|
||||
/// > connection attempt from the peer, however, the remote identity
|
||||
/// > of a peer is only known once a connection is fully established.
|
||||
pub enum Peer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>
|
||||
where
|
||||
TTrans: Transport,
|
||||
@ -63,10 +70,6 @@ where
|
||||
/// There exists no established connection to the peer and there is
|
||||
/// currently no ongoing dialing (i.e. outgoing connection) attempt
|
||||
/// in progress.
|
||||
///
|
||||
/// > **Note**: In this state there may always be a pending incoming
|
||||
/// > connection attempt from the peer, however, the remote identity
|
||||
/// > of a peer is only known once a connection is fully established.
|
||||
Disconnected(DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>),
|
||||
|
||||
/// The peer represents the local node.
|
||||
@ -82,20 +85,20 @@ where
|
||||
TPeerId: fmt::Debug + Eq + Hash,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
match *self {
|
||||
Peer::Connected(ConnectedPeer { ref peer_id, .. }) => {
|
||||
match self {
|
||||
Peer::Connected(p) => {
|
||||
f.debug_struct("Connected")
|
||||
.field("peer_id", peer_id)
|
||||
.field("peer", &p)
|
||||
.finish()
|
||||
}
|
||||
Peer::Dialing(DialingPeer { ref peer_id, .. } ) => {
|
||||
f.debug_struct("DialingPeer")
|
||||
.field("peer_id", peer_id)
|
||||
Peer::Dialing(p) => {
|
||||
f.debug_struct("Dialing")
|
||||
.field("peer", &p)
|
||||
.finish()
|
||||
}
|
||||
Peer::Disconnected(DisconnectedPeer { ref peer_id, .. }) => {
|
||||
Peer::Disconnected(p) => {
|
||||
f.debug_struct("Disconnected")
|
||||
.field("peer_id", peer_id)
|
||||
.field("peer", &p)
|
||||
.finish()
|
||||
}
|
||||
Peer::Local => {
|
||||
@ -164,12 +167,11 @@ where
|
||||
TTrans::Dial: Send + 'static,
|
||||
TMuxer: StreamMuxer + Send + Sync + 'static,
|
||||
TMuxer::OutboundSubstream: Send,
|
||||
TMuxer::Substream: Send,
|
||||
TInEvent: Send + 'static,
|
||||
TOutEvent: Send + 'static,
|
||||
THandler: IntoConnectionHandler<TConnInfo> + Send + 'static,
|
||||
THandler::Handler: ConnectionHandler<Substream = Substream<TMuxer>, InEvent = TInEvent, OutEvent = TOutEvent> + Send + 'static,
|
||||
<THandler::Handler as ConnectionHandler>::OutboundOpenInfo: Send + 'static, // TODO: shouldn't be necessary
|
||||
THandler::Handler: ConnectionHandler<Substream = Substream<TMuxer>, InEvent = TInEvent, OutEvent = TOutEvent> + Send,
|
||||
<THandler::Handler as ConnectionHandler>::OutboundOpenInfo: Send,
|
||||
<THandler::Handler as ConnectionHandler>::Error: error::Error + Send + 'static,
|
||||
TConnInfo: fmt::Debug + ConnectionInfo<PeerId = TPeerId> + Send + 'static,
|
||||
TPeerId: Eq + Hash + Clone + Send + 'static,
|
||||
@ -208,7 +210,41 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the peer into a `ConnectedPeer`, if there an established connection exists.
|
||||
/// Initiates a new dialing attempt to this peer using the given addresses.
|
||||
///
|
||||
/// The connection ID of the first connection attempt, i.e. to `address`,
|
||||
/// is returned, together with a [`DialingPeer`] for further use. The
|
||||
/// `remaining` addresses are tried in order in subsequent connection
|
||||
/// attempts in the context of the same dialing attempt, if the connection
|
||||
/// attempt to the first address fails.
|
||||
pub fn dial<I>(self, address: Multiaddr, remaining: I, handler: THandler)
|
||||
-> Result<
|
||||
(ConnectionId, DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>),
|
||||
ConnectionLimit
|
||||
>
|
||||
where
|
||||
I: IntoIterator<Item = Multiaddr>,
|
||||
{
|
||||
let (peer_id, network) = match self {
|
||||
Peer::Connected(p) => (p.peer_id, p.network),
|
||||
Peer::Dialing(p) => (p.peer_id, p.network),
|
||||
Peer::Disconnected(p) => (p.peer_id, p.network),
|
||||
Peer::Local => return Err(ConnectionLimit { current: 0, limit: 0 })
|
||||
};
|
||||
|
||||
let id = network.dial_peer(DialingOpts {
|
||||
peer: peer_id.clone(),
|
||||
handler,
|
||||
address,
|
||||
remaining: remaining.into_iter().collect(),
|
||||
})?;
|
||||
|
||||
Ok((id, DialingPeer { network, peer_id }))
|
||||
}
|
||||
|
||||
/// Converts the peer into a `ConnectedPeer`, if an established connection exists.
|
||||
///
|
||||
/// Succeeds if the there is at least one established connection to the peer.
|
||||
pub fn into_connected(self) -> Option<
|
||||
ConnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>
|
||||
> {
|
||||
@ -221,6 +257,8 @@ where
|
||||
}
|
||||
|
||||
/// Converts the peer into a `DialingPeer`, if a dialing attempt exists.
|
||||
///
|
||||
/// Succeeds if the there is at least one pending outgoing connection to the peer.
|
||||
pub fn into_dialing(self) -> Option<
|
||||
DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>
|
||||
> {
|
||||
@ -245,7 +283,8 @@ where
|
||||
}
|
||||
|
||||
/// The representation of a peer in a [`Network`] to whom at least
|
||||
/// one established connection exists.
|
||||
/// one established connection exists. There may also be additional ongoing
|
||||
/// dialing attempts to the peer.
|
||||
pub struct ConnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>
|
||||
where
|
||||
TTrans: Transport,
|
||||
@ -267,57 +306,12 @@ where
|
||||
&self.peer_id
|
||||
}
|
||||
|
||||
/// Attempts to establish a new connection to this peer using the given addresses,
|
||||
/// if there is currently no ongoing dialing attempt.
|
||||
///
|
||||
/// Existing established connections are not affected.
|
||||
///
|
||||
/// > **Note**: If there is an ongoing dialing attempt, a `DialingPeer`
|
||||
/// > is returned with the given addresses and handler being ignored.
|
||||
/// > You may want to check [`ConnectedPeer::is_dialing`] first.
|
||||
pub fn connect<I, TMuxer>(self, address: Multiaddr, remaining: I, handler: THandler)
|
||||
-> Result<DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>,
|
||||
ConnectionLimit>
|
||||
where
|
||||
I: IntoIterator<Item = Multiaddr>,
|
||||
THandler: Send + 'static,
|
||||
THandler::Handler: Send,
|
||||
<THandler::Handler as ConnectionHandler>::Error: error::Error + Send,
|
||||
<THandler::Handler as ConnectionHandler>::OutboundOpenInfo: Send,
|
||||
THandler::Handler: ConnectionHandler<Substream = Substream<TMuxer>, InEvent = TInEvent, OutEvent = TOutEvent> + Send,
|
||||
TTrans: Transport<Output = (TConnInfo, TMuxer)> + Clone,
|
||||
TTrans::Error: Send + 'static,
|
||||
TTrans::Dial: Send + 'static,
|
||||
TMuxer: StreamMuxer + Send + Sync + 'static,
|
||||
TMuxer::OutboundSubstream: Send,
|
||||
TMuxer::Substream: Send,
|
||||
TConnInfo: fmt::Debug + Send + 'static,
|
||||
TPeerId: Eq + Hash + Clone + Send + 'static,
|
||||
TInEvent: Send + 'static,
|
||||
TOutEvent: Send + 'static,
|
||||
|
||||
{
|
||||
if self.network.dialing.contains_key(&self.peer_id) {
|
||||
let peer = DialingPeer {
|
||||
network: self.network,
|
||||
peer_id: self.peer_id
|
||||
};
|
||||
Ok(peer)
|
||||
} else {
|
||||
self.network.dial_peer(DialingOpts {
|
||||
peer: self.peer_id.clone(),
|
||||
handler,
|
||||
address,
|
||||
remaining: remaining.into_iter().collect(),
|
||||
})?;
|
||||
Ok(DialingPeer {
|
||||
network: self.network,
|
||||
peer_id: self.peer_id,
|
||||
})
|
||||
}
|
||||
/// Returns the `ConnectedPeer` into a `Peer`.
|
||||
pub fn into_peer(self) -> Peer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId> {
|
||||
Peer::Connected(self)
|
||||
}
|
||||
|
||||
/// Obtains an existing connection to the peer.
|
||||
/// Obtains an established connection to the peer by ID.
|
||||
pub fn connection<'b>(&'b mut self, id: ConnectionId)
|
||||
-> Option<EstablishedConnection<'b, TInEvent, TConnInfo, TPeerId>>
|
||||
{
|
||||
@ -348,7 +342,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets an iterator over all established connections of the peer.
|
||||
/// Gets an iterator over all established connections to the peer.
|
||||
pub fn connections<'b>(&'b mut self) ->
|
||||
EstablishedConnectionIter<'b,
|
||||
impl Iterator<Item = ConnectionId>,
|
||||
@ -386,11 +380,13 @@ impl<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId> fmt::Debug f
|
||||
where
|
||||
TTrans: Transport,
|
||||
THandler: IntoConnectionHandler<TConnInfo>,
|
||||
TPeerId: fmt::Debug,
|
||||
TPeerId: Eq + Hash + fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("ConnectedPeer")
|
||||
.field("peer_id", &self.peer_id)
|
||||
.field("established", &self.network.pool.iter_peer_established_info(&self.peer_id))
|
||||
.field("attempts", &self.network.dialing.get(&self.peer_id))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@ -419,8 +415,16 @@ where
|
||||
&self.peer_id
|
||||
}
|
||||
|
||||
/// Disconnects from this peer, closing all pending connections.
|
||||
pub fn disconnect(self) -> DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId> {
|
||||
/// Returns the `DialingPeer` into a `Peer`.
|
||||
pub fn into_peer(self) -> Peer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId> {
|
||||
Peer::Dialing(self)
|
||||
}
|
||||
|
||||
/// Disconnects from this peer, closing all established connections and
|
||||
/// aborting all dialing attempts.
|
||||
pub fn disconnect(self)
|
||||
-> DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>
|
||||
{
|
||||
self.network.disconnect(&self.peer_id);
|
||||
DisconnectedPeer { network: self.network, peer_id: self.peer_id }
|
||||
}
|
||||
@ -443,20 +447,50 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Obtains the connection that is currently being established.
|
||||
pub fn connection<'b>(&'b mut self) -> DialingConnection<'b, TInEvent, TConnInfo, TPeerId> {
|
||||
let attempt = match self.network.dialing.entry(self.peer_id.clone()) {
|
||||
hash_map::Entry::Occupied(e) => e,
|
||||
_ => unreachable!("By `Peer::new` and the definition of `DialingPeer`.")
|
||||
};
|
||||
|
||||
let inner = self.network.pool
|
||||
.get_outgoing(attempt.get().id)
|
||||
.expect("By consistency of `network.pool` with `network.dialing`.");
|
||||
|
||||
DialingConnection {
|
||||
inner, dialing: attempt, peer_id: &self.peer_id
|
||||
/// Obtains a dialing attempt to the peer by connection ID of
|
||||
/// the current connection attempt.
|
||||
pub fn attempt<'b>(&'b mut self, id: ConnectionId)
|
||||
-> Option<DialingAttempt<'b, TInEvent, TConnInfo, TPeerId>>
|
||||
{
|
||||
if let hash_map::Entry::Occupied(attempts) = self.network.dialing.entry(self.peer_id.clone()) {
|
||||
if let Some(pos) = attempts.get().iter().position(|s| s.current.0 == id) {
|
||||
if let Some(inner) = self.network.pool.get_outgoing(id) {
|
||||
return Some(DialingAttempt { pos, inner, attempts })
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// The number of ongoing dialing attempts, i.e. pending outgoing connections
|
||||
/// to this peer.
|
||||
pub fn num_attempts(&self) -> usize {
|
||||
self.network.pool.num_peer_outgoing(&self.peer_id)
|
||||
}
|
||||
|
||||
/// Gets an iterator over all dialing (i.e. pending outgoing) connections to the peer.
|
||||
pub fn attempts<'b>(&'b mut self)
|
||||
-> DialingAttemptIter<'b,
|
||||
TInEvent,
|
||||
TOutEvent,
|
||||
THandler,
|
||||
TTrans::Error,
|
||||
<THandler::Handler as ConnectionHandler>::Error,
|
||||
TConnInfo,
|
||||
TPeerId>
|
||||
{
|
||||
DialingAttemptIter::new(&self.peer_id, &mut self.network.pool, &mut self.network.dialing)
|
||||
}
|
||||
|
||||
/// Obtains some dialing connection to the peer.
|
||||
///
|
||||
/// At least one dialing connection is guaranteed to exist on a `DialingPeer`.
|
||||
pub fn some_attempt<'b>(&'b mut self)
|
||||
-> DialingAttempt<'b, TInEvent, TConnInfo, TPeerId>
|
||||
{
|
||||
self.attempts()
|
||||
.into_first()
|
||||
.expect("By `Peer::new` and the definition of `DialingPeer`.")
|
||||
}
|
||||
}
|
||||
|
||||
@ -465,11 +499,13 @@ impl<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId> fmt::Debug f
|
||||
where
|
||||
TTrans: Transport,
|
||||
THandler: IntoConnectionHandler<TConnInfo>,
|
||||
TPeerId: fmt::Debug,
|
||||
TPeerId: Eq + Hash + fmt::Debug,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
|
||||
f.debug_struct("DialingPeer")
|
||||
.field("peer_id", &self.peer_id)
|
||||
.field("established", &self.network.pool.iter_peer_established_info(&self.peer_id))
|
||||
.field("attempts", &self.network.dialing.get(&self.peer_id))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@ -500,46 +536,19 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, TTrans, TInEvent, TOutEvent, TMuxer, THandler, TConnInfo, TPeerId>
|
||||
impl<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>
|
||||
DisconnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>
|
||||
where
|
||||
TTrans: Transport<Output = (TConnInfo, TMuxer)> + Clone,
|
||||
TTrans::Error: Send + 'static,
|
||||
TTrans::Dial: Send + 'static,
|
||||
TMuxer: StreamMuxer + Send + Sync + 'static,
|
||||
TMuxer::OutboundSubstream: Send,
|
||||
TMuxer::Substream: Send,
|
||||
THandler: IntoConnectionHandler<TConnInfo> + Send + 'static,
|
||||
THandler::Handler: ConnectionHandler<Substream = Substream<TMuxer>, InEvent = TInEvent, OutEvent = TOutEvent> + Send,
|
||||
<THandler::Handler as ConnectionHandler>::OutboundOpenInfo: Send,
|
||||
<THandler::Handler as ConnectionHandler>::Error: error::Error + Send,
|
||||
TInEvent: Send + 'static,
|
||||
TOutEvent: Send + 'static,
|
||||
TTrans: Transport,
|
||||
THandler: IntoConnectionHandler<TConnInfo>,
|
||||
{
|
||||
pub fn id(&self) -> &TPeerId {
|
||||
&self.peer_id
|
||||
}
|
||||
|
||||
/// Attempts to connect to this peer using the given addresses.
|
||||
pub fn connect<TIter>(self, first: Multiaddr, rest: TIter, handler: THandler)
|
||||
-> Result<DialingPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>,
|
||||
ConnectionLimit>
|
||||
where
|
||||
TIter: IntoIterator<Item = Multiaddr>,
|
||||
TConnInfo: fmt::Debug + ConnectionInfo<PeerId = TPeerId> + Send + 'static,
|
||||
TPeerId: Eq + Hash + Clone + Send + 'static,
|
||||
{
|
||||
self.network.dial_peer(DialingOpts {
|
||||
peer: self.peer_id.clone(),
|
||||
handler,
|
||||
address: first,
|
||||
remaining: rest.into_iter().collect(),
|
||||
})?;
|
||||
Ok(DialingPeer {
|
||||
network: self.network,
|
||||
peer_id: self.peer_id,
|
||||
})
|
||||
|
||||
/// Returns the `DisconnectedPeer` into a `Peer`.
|
||||
pub fn into_peer(self) -> Peer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId> {
|
||||
Peer::Disconnected(self)
|
||||
}
|
||||
|
||||
/// Moves the peer into a connected state by supplying an existing
|
||||
@ -550,8 +559,7 @@ where
|
||||
/// # Panics
|
||||
///
|
||||
/// Panics if `connected.peer_id()` does not identify the current peer.
|
||||
///
|
||||
pub fn set_connected(
|
||||
pub fn set_connected<TMuxer>(
|
||||
self,
|
||||
connected: Connected<TConnInfo>,
|
||||
connection: Connection<TMuxer, THandler::Handler>,
|
||||
@ -559,8 +567,17 @@ where
|
||||
ConnectedPeer<'a, TTrans, TInEvent, TOutEvent, THandler, TConnInfo, TPeerId>,
|
||||
ConnectionLimit
|
||||
> where
|
||||
TInEvent: Send + 'static,
|
||||
TOutEvent: Send + 'static,
|
||||
THandler: Send + 'static,
|
||||
TTrans::Error: Send + 'static,
|
||||
THandler::Handler: ConnectionHandler<Substream = Substream<TMuxer>, InEvent = TInEvent, OutEvent = TOutEvent> + Send,
|
||||
<THandler::Handler as ConnectionHandler>::OutboundOpenInfo: Send,
|
||||
<THandler::Handler as ConnectionHandler>::Error: error::Error + Send + 'static,
|
||||
TConnInfo: fmt::Debug + ConnectionInfo<PeerId = TPeerId> + Clone + Send + 'static,
|
||||
TPeerId: Eq + Hash + Clone + fmt::Debug,
|
||||
TPeerId: Eq + Hash + Clone + Send + fmt::Debug + 'static,
|
||||
TMuxer: StreamMuxer + Send + Sync + 'static,
|
||||
TMuxer::OutboundSubstream: Send,
|
||||
{
|
||||
if connected.peer_id() != &self.peer_id {
|
||||
panic!("Invalid peer ID given: {:?}. Expected: {:?}", connected.peer_id(), self.peer_id)
|
||||
@ -574,71 +591,142 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to reach a peer.
|
||||
/// The (internal) state of a `DialingAttempt`, tracking the
|
||||
/// current connection attempt as well as remaining addresses.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(super) struct DialingAttempt {
|
||||
/// Identifier for the reach attempt.
|
||||
pub(super) id: ConnectionId,
|
||||
/// Multiaddr currently being attempted.
|
||||
pub(super) current: Multiaddr,
|
||||
pub(super) struct DialingState {
|
||||
/// The ID and (remote) address of the current connection attempt.
|
||||
pub(super) current: (ConnectionId, Multiaddr),
|
||||
/// Multiaddresses to attempt if the current one fails.
|
||||
pub(super) next: Vec<Multiaddr>,
|
||||
pub(super) remaining: Vec<Multiaddr>,
|
||||
}
|
||||
|
||||
/// A `DialingConnection` is a [`PendingConnection`] where the local peer
|
||||
/// has the role of the dialer (i.e. initiator) and the (expected) remote
|
||||
/// peer ID is known.
|
||||
pub struct DialingConnection<'a, TInEvent, TConnInfo, TPeerId> {
|
||||
peer_id: &'a TPeerId,
|
||||
/// A `DialingAttempt` is an ongoing outgoing connection attempt to
|
||||
/// a known / expected remote peer ID and a list of alternative addresses
|
||||
/// to connect to, if the current connection attempt fails.
|
||||
pub struct DialingAttempt<'a, TInEvent, TConnInfo, TPeerId> {
|
||||
/// The underlying pending connection in the `Pool`.
|
||||
inner: PendingConnection<'a, TInEvent, TConnInfo, TPeerId>,
|
||||
dialing: hash_map::OccupiedEntry<'a, TPeerId, DialingAttempt>,
|
||||
/// All current dialing attempts of the peer.
|
||||
attempts: hash_map::OccupiedEntry<'a, TPeerId, SmallVec<[DialingState; 10]>>,
|
||||
/// The position of the current `DialingState` of this connection in the `attempts`.
|
||||
pos: usize,
|
||||
}
|
||||
|
||||
impl<'a, TInEvent, TConnInfo, TPeerId>
|
||||
DialingConnection<'a, TInEvent, TConnInfo, TPeerId>
|
||||
DialingAttempt<'a, TInEvent, TConnInfo, TPeerId>
|
||||
{
|
||||
/// Returns the local connection ID.
|
||||
/// Returns the ID of the current connection attempt.
|
||||
pub fn id(&self) -> ConnectionId {
|
||||
self.inner.id()
|
||||
}
|
||||
|
||||
/// Returns the (expected) peer ID of the ongoing connection attempt.
|
||||
/// Returns the (expected) peer ID of the dialing attempt.
|
||||
pub fn peer_id(&self) -> &TPeerId {
|
||||
self.peer_id
|
||||
self.attempts.key()
|
||||
}
|
||||
|
||||
/// Returns information about this endpoint of the connection attempt.
|
||||
pub fn endpoint(&self) -> &ConnectedPoint {
|
||||
self.inner.endpoint()
|
||||
}
|
||||
|
||||
/// Aborts the connection attempt.
|
||||
pub fn abort(self)
|
||||
where
|
||||
TPeerId: Eq + Hash + Clone,
|
||||
{
|
||||
self.dialing.remove();
|
||||
self.inner.abort();
|
||||
}
|
||||
|
||||
/// Adds new candidate addresses to the end of the addresses used
|
||||
/// in the ongoing dialing process.
|
||||
///
|
||||
/// Duplicates are ignored.
|
||||
pub fn add_addresses(&mut self, addrs: impl IntoIterator<Item = Multiaddr>) {
|
||||
for addr in addrs {
|
||||
self.add_address(addr);
|
||||
/// Returns the remote address of the current connection attempt.
|
||||
pub fn address(&self) -> &Multiaddr {
|
||||
match self.inner.endpoint() {
|
||||
ConnectedPoint::Dialer { address } => address,
|
||||
ConnectedPoint::Listener { .. } => unreachable!("by definition of a `DialingAttempt`.")
|
||||
}
|
||||
}
|
||||
|
||||
/// Adds an address to the end of the addresses used in the ongoing
|
||||
/// dialing process.
|
||||
/// Aborts the dialing attempt.
|
||||
///
|
||||
/// Duplicates are ignored.
|
||||
/// Aborting a dialing attempt involves aborting the current connection
|
||||
/// attempt and dropping any remaining addresses given to [`Peer::dial()`]
|
||||
/// that have not yet been tried.
|
||||
pub fn abort(mut self) {
|
||||
self.attempts.get_mut().remove(self.pos);
|
||||
if self.attempts.get().is_empty() {
|
||||
self.attempts.remove();
|
||||
}
|
||||
self.inner.abort();
|
||||
}
|
||||
|
||||
/// Adds an address to the end of the remaining addresses
|
||||
/// for this dialing attempt. Duplicates are ignored.
|
||||
pub fn add_address(&mut self, addr: Multiaddr) {
|
||||
if self.dialing.get().next.iter().all(|a| a != &addr) {
|
||||
self.dialing.get_mut().next.push(addr);
|
||||
let remaining = &mut self.attempts.get_mut()[self.pos].remaining;
|
||||
if remaining.iter().all(|a| a != &addr) {
|
||||
remaining.push(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// An iterator over the ongoing dialing attempts to a peer.
|
||||
pub struct DialingAttemptIter<'a, TInEvent, TOutEvent, THandler, TTransErr, THandlerErr, TConnInfo, TPeerId> {
|
||||
/// The peer whose dialing attempts are being iterated.
|
||||
peer_id: &'a TPeerId,
|
||||
/// The underlying connection `Pool` of the `Network`.
|
||||
pool: &'a mut Pool<TInEvent, TOutEvent, THandler, TTransErr, THandlerErr, TConnInfo, TPeerId>,
|
||||
/// The state of all current dialing attempts known to the `Network`.
|
||||
///
|
||||
/// Ownership of the `OccupiedEntry` for `peer_id` containing all attempts must be
|
||||
/// borrowed to each `DialingAttempt` in order for it to remove the entry if the
|
||||
/// last dialing attempt is aborted.
|
||||
dialing: &'a mut FnvHashMap<TPeerId, SmallVec<[DialingState; 10]>>,
|
||||
/// The current position of the iterator in `dialing[peer_id]`.
|
||||
pos: usize,
|
||||
/// The total number of elements in `dialing[peer_id]` to iterate over.
|
||||
end: usize,
|
||||
}
|
||||
|
||||
// Note: Ideally this would be an implementation of `Iterator`, but that
|
||||
// requires GATs (cf. https://github.com/rust-lang/rust/issues/44265) and
|
||||
// a different definition of `Iterator`.
|
||||
impl<'a, TInEvent, TOutEvent, THandler, TTransErr, THandlerErr, TConnInfo, TPeerId>
|
||||
DialingAttemptIter<'a, TInEvent, TOutEvent, THandler, TTransErr, THandlerErr, TConnInfo, TPeerId>
|
||||
where
|
||||
TConnInfo: ConnectionInfo<PeerId = TPeerId>,
|
||||
TPeerId: Eq + Hash + Clone,
|
||||
{
|
||||
fn new(
|
||||
peer_id: &'a TPeerId,
|
||||
pool: &'a mut Pool<TInEvent, TOutEvent, THandler, TTransErr, THandlerErr, TConnInfo, TPeerId>,
|
||||
dialing: &'a mut FnvHashMap<TPeerId, SmallVec<[DialingState; 10]>>,
|
||||
) -> Self {
|
||||
let end = dialing.get(peer_id).map_or(0, |conns| conns.len());
|
||||
Self { pos: 0, end, pool, dialing, peer_id }
|
||||
}
|
||||
|
||||
/// Obtains the next dialing connection, if any.
|
||||
pub fn next<'b>(&'b mut self) -> Option<DialingAttempt<'b, TInEvent, TConnInfo, TPeerId>> {
|
||||
if self.pos == self.end {
|
||||
return None
|
||||
}
|
||||
|
||||
if let hash_map::Entry::Occupied(attempts) = self.dialing.entry(self.peer_id.clone()) {
|
||||
let id = attempts.get()[self.pos].current.0;
|
||||
if let Some(inner) = self.pool.get_outgoing(id) {
|
||||
let conn = DialingAttempt { pos: self.pos, inner, attempts };
|
||||
self.pos += 1;
|
||||
return Some(conn)
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
/// Returns the first connection, if any, consuming the iterator.
|
||||
pub fn into_first<'b>(self)
|
||||
-> Option<DialingAttempt<'b, TInEvent, TConnInfo, TPeerId>>
|
||||
where 'a: 'b
|
||||
{
|
||||
if self.pos == self.end {
|
||||
return None
|
||||
}
|
||||
|
||||
if let hash_map::Entry::Occupied(attempts) = self.dialing.entry(self.peer_id.clone()) {
|
||||
let id = attempts.get()[self.pos].current.0;
|
||||
if let Some(inner) = self.pool.get_outgoing(id) {
|
||||
return Some(DialingAttempt { pos: self.pos, inner, attempts })
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
}
|
||||
|
@ -77,10 +77,9 @@ impl PeerId {
|
||||
};
|
||||
|
||||
let canonical = canonical_algorithm.map(|alg|
|
||||
alg.hasher().expect("SHA2-256 hasher is always supported").digest(&key_enc));
|
||||
alg.digest(&key_enc));
|
||||
|
||||
let multihash = hash_algorithm.hasher()
|
||||
.expect("Identity and SHA-256 hasher are always supported").digest(&key_enc);
|
||||
let multihash = hash_algorithm.digest(&key_enc);
|
||||
|
||||
PeerId { multihash, canonical }
|
||||
}
|
||||
@ -148,7 +147,7 @@ impl PeerId {
|
||||
|
||||
/// Returns a base-58 encoded string of this `PeerId`.
|
||||
pub fn to_base58(&self) -> String {
|
||||
bs58::encode(self.borrow() as &[u8]).into_string()
|
||||
bs58::encode(self.as_bytes()).into_string()
|
||||
}
|
||||
|
||||
/// Checks whether the public key passed as parameter matches the public key of this `PeerId`.
|
||||
@ -158,7 +157,7 @@ impl PeerId {
|
||||
pub fn is_public_key(&self, public_key: &PublicKey) -> Option<bool> {
|
||||
let alg = self.multihash.algorithm();
|
||||
let enc = public_key.clone().into_protobuf_encoding();
|
||||
Some(alg.hasher()?.digest(&enc) == self.multihash)
|
||||
Some(alg.digest(&enc) == self.multihash)
|
||||
}
|
||||
|
||||
/// Returns public key if it was inlined in this `PeerId`.
|
||||
@ -336,8 +335,8 @@ mod tests {
|
||||
}
|
||||
|
||||
fn property(data: Vec<u8>, algo1: HashAlgo, algo2: HashAlgo) -> bool {
|
||||
let a = PeerId::try_from(algo1.0.hasher().unwrap().digest(&data)).unwrap();
|
||||
let b = PeerId::try_from(algo2.0.hasher().unwrap().digest(&data)).unwrap();
|
||||
let a = PeerId::try_from(algo1.0.digest(&data)).unwrap();
|
||||
let b = PeerId::try_from(algo2.0.digest(&data)).unwrap();
|
||||
|
||||
if algo1 == algo2 || algo1.0 == Code::Identity || algo2.0 == Code::Identity {
|
||||
a == b
|
||||
|
@ -37,9 +37,14 @@ use multiaddr::{Multiaddr, Protocol};
|
||||
/// If the first [`Protocol`]s are not IP addresses, `None` is returned instead.
|
||||
pub fn address_translation(original: &Multiaddr, observed: &Multiaddr) -> Option<Multiaddr> {
|
||||
original.replace(0, move |proto| match proto {
|
||||
Protocol::Ip4(_) | Protocol::Ip6(_) | Protocol::Dns4(_) | Protocol::Dns6(_) => match observed.iter().next() {
|
||||
Protocol::Ip4(_)
|
||||
| Protocol::Ip6(_)
|
||||
| Protocol::Dns(_)
|
||||
| Protocol::Dns4(_)
|
||||
| Protocol::Dns6(_) => match observed.iter().next() {
|
||||
x @ Some(Protocol::Ip4(_)) => x,
|
||||
x @ Some(Protocol::Ip6(_)) => x,
|
||||
x @ Some(Protocol::Dns(_)) => x,
|
||||
x @ Some(Protocol::Dns4(_)) => x,
|
||||
x @ Some(Protocol::Dns6(_)) => x,
|
||||
_ => None,
|
||||
|
@ -22,47 +22,60 @@ mod util;
|
||||
|
||||
use futures::prelude::*;
|
||||
use libp2p_core::identity;
|
||||
use libp2p_core::multiaddr::multiaddr;
|
||||
use libp2p_core::multiaddr::{multiaddr, Multiaddr};
|
||||
use libp2p_core::{
|
||||
Network,
|
||||
PeerId,
|
||||
Transport,
|
||||
connection::PendingConnectionError,
|
||||
muxing::StreamMuxerBox,
|
||||
network::NetworkEvent,
|
||||
network::{NetworkEvent, NetworkConfig},
|
||||
transport,
|
||||
upgrade,
|
||||
};
|
||||
use rand::Rng;
|
||||
use rand::seq::SliceRandom;
|
||||
use std::{io, task::Poll};
|
||||
use std::{io, error::Error, fmt, task::Poll};
|
||||
use util::TestHandler;
|
||||
|
||||
type TestNetwork<TTrans> = Network<TTrans, (), (), TestHandler>;
|
||||
type TestNetwork = Network<TestTransport, (), (), TestHandler>;
|
||||
type TestTransport = transport::boxed::Boxed<(PeerId, StreamMuxerBox), BoxError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct BoxError(Box<dyn Error + Send + 'static>);
|
||||
|
||||
impl Error for BoxError {}
|
||||
|
||||
impl fmt::Display for BoxError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
write!(f, "Transport error: {}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
fn new_network(cfg: NetworkConfig) -> TestNetwork {
|
||||
let local_key = identity::Keypair::generate_ed25519();
|
||||
let local_public_key = local_key.public();
|
||||
let transport: TestTransport = libp2p_tcp::TcpConfig::new()
|
||||
.upgrade(upgrade::Version::V1)
|
||||
.authenticate(libp2p_secio::SecioConfig::new(local_key))
|
||||
.multiplex(libp2p_mplex::MplexConfig::new())
|
||||
.map(|(conn_info, muxer), _| (conn_info, StreamMuxerBox::new(muxer)))
|
||||
.and_then(|(peer, mplex), _| {
|
||||
// Gracefully close the connection to allow protocol
|
||||
// negotiation to complete.
|
||||
util::CloseMuxer::new(mplex).map_ok(move |mplex| (peer, mplex))
|
||||
})
|
||||
.map_err(|e| BoxError(Box::new(e)))
|
||||
.boxed();
|
||||
TestNetwork::new(transport, local_public_key.into(), cfg)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deny_incoming_connec() {
|
||||
// Checks whether refusing an incoming connection on a swarm triggers the correct events.
|
||||
|
||||
let mut swarm1 = {
|
||||
let local_key = identity::Keypair::generate_ed25519();
|
||||
let local_public_key = local_key.public();
|
||||
let transport = libp2p_tcp::TcpConfig::new()
|
||||
.upgrade(upgrade::Version::V1)
|
||||
.authenticate(libp2p_secio::SecioConfig::new(local_key))
|
||||
.multiplex(libp2p_mplex::MplexConfig::new())
|
||||
.map(|(conn_info, muxer), _| (conn_info, StreamMuxerBox::new(muxer)));
|
||||
TestNetwork::new(transport, local_public_key.into(), Default::default())
|
||||
};
|
||||
|
||||
let mut swarm2 = {
|
||||
let local_key = identity::Keypair::generate_ed25519();
|
||||
let local_public_key = local_key.public();
|
||||
let transport = libp2p_tcp::TcpConfig::new()
|
||||
.upgrade(upgrade::Version::V1)
|
||||
.authenticate(libp2p_secio::SecioConfig::new(local_key))
|
||||
.multiplex(libp2p_mplex::MplexConfig::new())
|
||||
.map(|(conn_info, muxer), _| (conn_info, StreamMuxerBox::new(muxer)));
|
||||
TestNetwork::new(transport, local_public_key.into(), Default::default())
|
||||
};
|
||||
let mut swarm1 = new_network(NetworkConfig::default());
|
||||
let mut swarm2 = new_network(NetworkConfig::default());
|
||||
|
||||
swarm1.listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap()).unwrap();
|
||||
|
||||
@ -76,8 +89,7 @@ fn deny_incoming_connec() {
|
||||
|
||||
swarm2
|
||||
.peer(swarm1.local_peer_id().clone())
|
||||
.into_disconnected().unwrap()
|
||||
.connect(address.clone(), Vec::new(), TestHandler())
|
||||
.dial(address.clone(), Vec::new(), TestHandler())
|
||||
.unwrap();
|
||||
|
||||
async_std::task::block_on(future::poll_fn(|cx| -> Poll<Result<(), io::Error>> {
|
||||
@ -119,22 +131,7 @@ fn dial_self() {
|
||||
//
|
||||
// The last two can happen in any order.
|
||||
|
||||
let mut swarm = {
|
||||
let local_key = identity::Keypair::generate_ed25519();
|
||||
let local_public_key = local_key.public();
|
||||
let transport = libp2p_tcp::TcpConfig::new()
|
||||
.upgrade(upgrade::Version::V1)
|
||||
.authenticate(libp2p_secio::SecioConfig::new(local_key))
|
||||
.multiplex(libp2p_mplex::MplexConfig::new())
|
||||
.and_then(|(peer, mplex), _| {
|
||||
// Gracefully close the connection to allow protocol
|
||||
// negotiation to complete.
|
||||
util::CloseMuxer::new(mplex).map_ok(move |mplex| (peer, mplex))
|
||||
})
|
||||
.map(|(conn_info, muxer), _| (conn_info, StreamMuxerBox::new(muxer)));
|
||||
TestNetwork::new(transport, local_public_key.into(), Default::default())
|
||||
};
|
||||
|
||||
let mut swarm = new_network(NetworkConfig::default());
|
||||
swarm.listen_on("/ip4/127.0.0.1/tcp/0".parse().unwrap()).unwrap();
|
||||
|
||||
let (local_address, mut swarm) = async_std::task::block_on(
|
||||
@ -193,36 +190,16 @@ fn dial_self() {
|
||||
fn dial_self_by_id() {
|
||||
// Trying to dial self by passing the same `PeerId` shouldn't even be possible in the first
|
||||
// place.
|
||||
|
||||
let mut swarm = {
|
||||
let local_key = identity::Keypair::generate_ed25519();
|
||||
let local_public_key = local_key.public();
|
||||
let transport = libp2p_tcp::TcpConfig::new()
|
||||
.upgrade(upgrade::Version::V1)
|
||||
.authenticate(libp2p_secio::SecioConfig::new(local_key))
|
||||
.multiplex(libp2p_mplex::MplexConfig::new())
|
||||
.map(|(conn_info, muxer), _| (conn_info, StreamMuxerBox::new(muxer)));
|
||||
TestNetwork::new(transport, local_public_key.into(), Default::default())
|
||||
};
|
||||
|
||||
let mut swarm = new_network(NetworkConfig::default());
|
||||
let peer_id = swarm.local_peer_id().clone();
|
||||
assert!(swarm.peer(peer_id).into_disconnected().is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiple_addresses_err() {
|
||||
// Tries dialing multiple addresses, and makes sure there's one dialing error per addresses.
|
||||
// Tries dialing multiple addresses, and makes sure there's one dialing error per address.
|
||||
|
||||
let mut swarm = {
|
||||
let local_key = identity::Keypair::generate_ed25519();
|
||||
let local_public_key = local_key.public();
|
||||
let transport = libp2p_tcp::TcpConfig::new()
|
||||
.upgrade(upgrade::Version::V1)
|
||||
.authenticate(libp2p_secio::SecioConfig::new(local_key))
|
||||
.multiplex(libp2p_mplex::MplexConfig::new())
|
||||
.map(|(conn_info, muxer), _| (conn_info, StreamMuxerBox::new(muxer)));
|
||||
TestNetwork::new(transport, local_public_key.into(), Default::default())
|
||||
};
|
||||
let mut swarm = new_network(NetworkConfig::default());
|
||||
|
||||
let mut addresses = Vec::new();
|
||||
for _ in 0 .. 3 {
|
||||
@ -238,8 +215,7 @@ fn multiple_addresses_err() {
|
||||
|
||||
let target = PeerId::random();
|
||||
swarm.peer(target.clone())
|
||||
.into_disconnected().unwrap()
|
||||
.connect(first, rest, TestHandler())
|
||||
.dial(first, rest, TestHandler())
|
||||
.unwrap();
|
||||
|
||||
async_std::task::block_on(future::poll_fn(|cx| -> Poll<Result<(), io::Error>> {
|
||||
@ -267,3 +243,44 @@ fn multiple_addresses_err() {
|
||||
}
|
||||
})).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn connection_limit() {
|
||||
let outgoing_per_peer_limit = rand::thread_rng().gen_range(1, 10);
|
||||
let outgoing_limit = 2 * outgoing_per_peer_limit;
|
||||
|
||||
let mut cfg = NetworkConfig::default();
|
||||
cfg.set_outgoing_per_peer_limit(outgoing_per_peer_limit);
|
||||
cfg.set_outgoing_limit(outgoing_limit);
|
||||
let mut network = new_network(cfg);
|
||||
|
||||
let target = PeerId::random();
|
||||
for _ in 0 .. outgoing_per_peer_limit {
|
||||
network.peer(target.clone())
|
||||
.dial(Multiaddr::empty(), Vec::new(), TestHandler())
|
||||
.ok()
|
||||
.expect("Unexpected connection limit.");
|
||||
}
|
||||
|
||||
let err = network.peer(target)
|
||||
.dial(Multiaddr::empty(), Vec::new(), TestHandler())
|
||||
.expect_err("Unexpected dialing success.");
|
||||
|
||||
assert_eq!(err.current, outgoing_per_peer_limit);
|
||||
assert_eq!(err.limit, outgoing_per_peer_limit);
|
||||
|
||||
let target2 = PeerId::random();
|
||||
for _ in outgoing_per_peer_limit .. outgoing_limit {
|
||||
network.peer(target2.clone())
|
||||
.dial(Multiaddr::empty(), Vec::new(), TestHandler())
|
||||
.ok()
|
||||
.expect("Unexpected connection limit.");
|
||||
}
|
||||
|
||||
let err = network.peer(target2)
|
||||
.dial(Multiaddr::empty(), Vec::new(), TestHandler())
|
||||
.expect_err("Unexpected dialing success.");
|
||||
|
||||
assert_eq!(err.current, outgoing_limit);
|
||||
assert_eq!(err.limit, outgoing_limit);
|
||||
}
|
||||
|
@ -1,201 +1,208 @@
|
||||
// // Copyright 20l9 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.
|
||||
// Copyright 20l9 Parity Technologies (UK) Ltd.
|
||||
//
|
||||
// //! A basic key value store demonstrating libp2p and the mDNS and Kademlia protocols.
|
||||
// //!
|
||||
// //! 1. Using two terminal windows, start two instances. If you local network
|
||||
// //! allows mDNS, they will automatically connect.
|
||||
// //!
|
||||
// //! 2. Type `PUT my-key my-value` in terminal one and hit return.
|
||||
// //!
|
||||
// //! 3. Type `GET my-key` in terminal two and hit return.
|
||||
// //!
|
||||
// //! 4. Close with Ctrl-c.
|
||||
// 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:
|
||||
//
|
||||
// use async_std::{io, task};
|
||||
// use futures::prelude::*;
|
||||
// use libp2p::identity::Keypair;
|
||||
// use libp2p::kad::record::store::MemoryStore;
|
||||
// use libp2p::kad::{record::Key, Kademlia, KademliaEvent, PutRecordOk, Quorum, Record};
|
||||
// use libp2p::{
|
||||
// build_development_transport, identity,
|
||||
// mdns::{Mdns, MdnsEvent},
|
||||
// swarm::NetworkBehaviourEventProcess,
|
||||
// NetworkBehaviour, PeerId, Swarm,
|
||||
// };
|
||||
// use std::{
|
||||
// error::Error,
|
||||
// task::{Context, Poll},
|
||||
// };
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// fn main() -> Result<(), Box<dyn Error>> {
|
||||
// env_logger::init();
|
||||
//
|
||||
// // Create a random key for ourselves.
|
||||
// let local_key = identity::Keypair::generate_ed25519();
|
||||
// let local_peer_id = PeerId::from(local_key.public());
|
||||
//
|
||||
// // Set up a an encrypted DNS-enabled TCP Transport over the Mplex protocol.
|
||||
// let transport = build_development_transport(local_key.clone())?;
|
||||
//
|
||||
// // We create a custom network behaviour that combines Kademlia and mDNS.
|
||||
// #[derive(NetworkBehaviour)]
|
||||
// struct MyBehaviour {
|
||||
// kademlia: Kademlia<MemoryStore>,
|
||||
// mdns: Mdns,
|
||||
// }
|
||||
//
|
||||
// impl NetworkBehaviourEventProcess<MdnsEvent> for MyBehaviour {
|
||||
// // Called when `mdns` produces an event.
|
||||
// fn inject_event(&mut self, event: MdnsEvent) {
|
||||
// if let MdnsEvent::Discovered(list) = event {
|
||||
// for (peer_id, multiaddr) in list {
|
||||
// self.kademlia.add_address(&peer_id, multiaddr);
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// impl NetworkBehaviourEventProcess<KademliaEvent> for MyBehaviour {
|
||||
// // Called when `kademlia` produces an event.
|
||||
// fn inject_event(&mut self, message: KademliaEvent) {
|
||||
// match message {
|
||||
// KademliaEvent::GetRecordResult(Ok(result)) => {
|
||||
// for Record { key, value, .. } in result.records {
|
||||
// println!(
|
||||
// "Got record {:?} {:?}",
|
||||
// std::str::from_utf8(key.as_ref()).unwrap(),
|
||||
// std::str::from_utf8(&value).unwrap(),
|
||||
// );
|
||||
// }
|
||||
// }
|
||||
// KademliaEvent::GetRecordResult(Err(err)) => {
|
||||
// eprintln!("Failed to get record: {:?}", err);
|
||||
// }
|
||||
// KademliaEvent::PutRecordResult(Ok(PutRecordOk { key })) => {
|
||||
// println!(
|
||||
// "Successfully put record {:?}",
|
||||
// std::str::from_utf8(key.as_ref()).unwrap()
|
||||
// );
|
||||
// }
|
||||
// KademliaEvent::PutRecordResult(Err(err)) => {
|
||||
// eprintln!("Failed to put record: {:?}", err);
|
||||
// }
|
||||
// _ => {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// // Create a swarm to manage peers and events.
|
||||
// let mut swarm = {
|
||||
// // Create a Kademlia behaviour.
|
||||
// let store = MemoryStore::new(local_peer_id.clone());
|
||||
// let Keypair::Ed25519(local_key) = local_key;
|
||||
// let kademlia = Kademlia::new(local_key, local_peer_id.clone(), store);
|
||||
// let mdns = Mdns::new()?;
|
||||
// let behaviour = MyBehaviour { kademlia, mdns };
|
||||
// Swarm::new(transport, behaviour, local_peer_id)
|
||||
// };
|
||||
//
|
||||
// // Read full lines from stdin
|
||||
// let mut stdin = io::BufReader::new(io::stdin()).lines();
|
||||
//
|
||||
// // Listen on all interfaces and whatever port the OS assigns.
|
||||
// Swarm::listen_on(&mut swarm, "/ip4/0.0.0.0/tcp/0".parse()?)?;
|
||||
//
|
||||
// // Kick it off.
|
||||
// let mut listening = false;
|
||||
// task::block_on(future::poll_fn(move |cx: &mut Context| {
|
||||
// loop {
|
||||
// match stdin.try_poll_next_unpin(cx)? {
|
||||
// Poll::Ready(Some(line)) => handle_input_line(&mut swarm.kademlia, line),
|
||||
// Poll::Ready(None) => panic!("Stdin closed"),
|
||||
// Poll::Pending => break,
|
||||
// }
|
||||
// }
|
||||
// loop {
|
||||
// match swarm.poll_next_unpin(cx) {
|
||||
// Poll::Ready(Some(event)) => println!("{:?}", event),
|
||||
// Poll::Ready(None) => return Poll::Ready(Ok(())),
|
||||
// Poll::Pending => {
|
||||
// if !listening {
|
||||
// if let Some(a) = Swarm::listeners(&swarm).next() {
|
||||
// println!("Listening on {:?}", a);
|
||||
// listening = true;
|
||||
// }
|
||||
// }
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// Poll::Pending
|
||||
// }))
|
||||
// }
|
||||
//
|
||||
// fn handle_input_line(kademlia: &mut Kademlia<MemoryStore>, line: String) {
|
||||
// let mut args = line.split(" ");
|
||||
//
|
||||
// match args.next() {
|
||||
// Some("GET") => {
|
||||
// let key = {
|
||||
// match args.next() {
|
||||
// Some(key) => Key::new(&key),
|
||||
// None => {
|
||||
// eprintln!("Expected key");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// kademlia.get_record(&key, Quorum::One);
|
||||
// }
|
||||
// Some("PUT") => {
|
||||
// let key = {
|
||||
// match args.next() {
|
||||
// Some(key) => Key::new(&key),
|
||||
// None => {
|
||||
// eprintln!("Expected key");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// let value = {
|
||||
// match args.next() {
|
||||
// Some(value) => value.as_bytes().to_vec(),
|
||||
// None => {
|
||||
// eprintln!("Expected value");
|
||||
// return;
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
// let record = Record {
|
||||
// key,
|
||||
// value,
|
||||
// publisher: None,
|
||||
// expires: None,
|
||||
// };
|
||||
// kademlia.put_record(record, Quorum::One);
|
||||
// }
|
||||
// _ => {
|
||||
// eprintln!("expected GET or PUT");
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// 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.
|
||||
|
||||
fn main() {}
|
||||
//! A basic key value store demonstrating libp2p and the mDNS and Kademlia protocols.
|
||||
//!
|
||||
//! 1. Using two terminal windows, start two instances. If you local network
|
||||
//! allows mDNS, they will automatically connect.
|
||||
//!
|
||||
//! 2. Type `PUT my-key my-value` in terminal one and hit return.
|
||||
//!
|
||||
//! 3. Type `GET my-key` in terminal two and hit return.
|
||||
//!
|
||||
//! 4. Close with Ctrl-c.
|
||||
|
||||
use async_std::{io, task};
|
||||
use futures::prelude::*;
|
||||
use libp2p::kad::record::store::MemoryStore;
|
||||
use libp2p::kad::{
|
||||
record::Key,
|
||||
Kademlia,
|
||||
KademliaEvent,
|
||||
PutRecordOk,
|
||||
QueryResult,
|
||||
Quorum,
|
||||
Record
|
||||
};
|
||||
use libp2p::{
|
||||
NetworkBehaviour,
|
||||
PeerId,
|
||||
Swarm,
|
||||
build_development_transport,
|
||||
identity,
|
||||
mdns::{Mdns, MdnsEvent},
|
||||
swarm::NetworkBehaviourEventProcess
|
||||
};
|
||||
use std::{error::Error, task::{Context, Poll}};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
env_logger::init();
|
||||
|
||||
// Create a random key for ourselves.
|
||||
let local_key = identity::Keypair::generate_ed25519();
|
||||
let local_peer_id = PeerId::from(local_key.public());
|
||||
|
||||
// Set up a an encrypted DNS-enabled TCP Transport over the Mplex protocol.
|
||||
let transport = build_development_transport(local_key)?;
|
||||
|
||||
// We create a custom network behaviour that combines Kademlia and mDNS.
|
||||
#[derive(NetworkBehaviour)]
|
||||
struct MyBehaviour {
|
||||
kademlia: Kademlia<MemoryStore>,
|
||||
mdns: Mdns
|
||||
}
|
||||
|
||||
impl NetworkBehaviourEventProcess<MdnsEvent> for MyBehaviour {
|
||||
// Called when `mdns` produces an event.
|
||||
fn inject_event(&mut self, event: MdnsEvent) {
|
||||
if let MdnsEvent::Discovered(list) = event {
|
||||
for (peer_id, multiaddr) in list {
|
||||
self.kademlia.add_address(&peer_id, multiaddr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NetworkBehaviourEventProcess<KademliaEvent> for MyBehaviour {
|
||||
// Called when `kademlia` produces an event.
|
||||
fn inject_event(&mut self, message: KademliaEvent) {
|
||||
match message {
|
||||
KademliaEvent::QueryResult { result, .. } => match result {
|
||||
QueryResult::GetRecord(Ok(ok)) => {
|
||||
for Record { key, value, .. } in ok.records {
|
||||
println!(
|
||||
"Got record {:?} {:?}",
|
||||
std::str::from_utf8(key.as_ref()).unwrap(),
|
||||
std::str::from_utf8(&value).unwrap(),
|
||||
);
|
||||
}
|
||||
}
|
||||
QueryResult::GetRecord(Err(err)) => {
|
||||
eprintln!("Failed to get record: {:?}", err);
|
||||
}
|
||||
QueryResult::PutRecord(Ok(PutRecordOk { key })) => {
|
||||
println!(
|
||||
"Successfully put record {:?}",
|
||||
std::str::from_utf8(key.as_ref()).unwrap()
|
||||
);
|
||||
}
|
||||
QueryResult::PutRecord(Err(err)) => {
|
||||
eprintln!("Failed to put record: {:?}", err);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Create a swarm to manage peers and events.
|
||||
let mut swarm = {
|
||||
// Create a Kademlia behaviour.
|
||||
let store = MemoryStore::new(local_peer_id.clone());
|
||||
let kademlia = Kademlia::new(local_peer_id.clone(), store);
|
||||
let mdns = Mdns::new()?;
|
||||
let behaviour = MyBehaviour { kademlia, mdns };
|
||||
Swarm::new(transport, behaviour, local_peer_id)
|
||||
};
|
||||
|
||||
// Read full lines from stdin
|
||||
let mut stdin = io::BufReader::new(io::stdin()).lines();
|
||||
|
||||
// Listen on all interfaces and whatever port the OS assigns.
|
||||
Swarm::listen_on(&mut swarm, "/ip4/0.0.0.0/tcp/0".parse()?)?;
|
||||
|
||||
// Kick it off.
|
||||
let mut listening = false;
|
||||
task::block_on(future::poll_fn(move |cx: &mut Context| {
|
||||
loop {
|
||||
match stdin.try_poll_next_unpin(cx)? {
|
||||
Poll::Ready(Some(line)) => handle_input_line(&mut swarm.kademlia, line),
|
||||
Poll::Ready(None) => panic!("Stdin closed"),
|
||||
Poll::Pending => break
|
||||
}
|
||||
}
|
||||
loop {
|
||||
match swarm.poll_next_unpin(cx) {
|
||||
Poll::Ready(Some(event)) => println!("{:?}", event),
|
||||
Poll::Ready(None) => return Poll::Ready(Ok(())),
|
||||
Poll::Pending => {
|
||||
if !listening {
|
||||
if let Some(a) = Swarm::listeners(&swarm).next() {
|
||||
println!("Listening on {:?}", a);
|
||||
listening = true;
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
Poll::Pending
|
||||
}))
|
||||
}
|
||||
|
||||
fn handle_input_line(kademlia: &mut Kademlia<MemoryStore>, line: String) {
|
||||
let mut args = line.split(" ");
|
||||
|
||||
match args.next() {
|
||||
Some("GET") => {
|
||||
let key = {
|
||||
match args.next() {
|
||||
Some(key) => Key::new(&key),
|
||||
None => {
|
||||
eprintln!("Expected key");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
kademlia.get_record(&key, Quorum::One);
|
||||
}
|
||||
Some("PUT") => {
|
||||
let key = {
|
||||
match args.next() {
|
||||
Some(key) => Key::new(&key),
|
||||
None => {
|
||||
eprintln!("Expected key");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
let value = {
|
||||
match args.next() {
|
||||
Some(value) => value.as_bytes().to_vec(),
|
||||
None => {
|
||||
eprintln!("Expected value");
|
||||
return;
|
||||
}
|
||||
}
|
||||
};
|
||||
let record = Record {
|
||||
key,
|
||||
value,
|
||||
publisher: None,
|
||||
expires: None,
|
||||
};
|
||||
kademlia.put_record(record, Quorum::One).expect("Failed to store record locally.");
|
||||
}
|
||||
_ => {
|
||||
eprintln!("expected GET or PUT");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,123 +1,129 @@
|
||||
// // Copyright 2018 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.
|
||||
// Copyright 2018 Parity Technologies (UK) Ltd.
|
||||
//
|
||||
// //! Demonstrates how to perform Kademlia queries on the IPFS network.
|
||||
// //!
|
||||
// //! You can pass as parameter a base58 peer ID to search for. If you don't pass any parameter, a
|
||||
// //! peer ID will be generated randomly.
|
||||
// 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:
|
||||
//
|
||||
// use async_std::task;
|
||||
// use libp2p::identity::Keypair;
|
||||
// use libp2p::kad::record::store::MemoryStore;
|
||||
// use libp2p::kad::{GetClosestPeersError, Kademlia, KademliaConfig, KademliaEvent};
|
||||
// use libp2p::{build_development_transport, identity, PeerId, Swarm};
|
||||
// use std::{env, error::Error, time::Duration};
|
||||
// The above copyright notice and this permission notice shall be included in
|
||||
// all copies or substantial portions of the Software.
|
||||
//
|
||||
// fn main() -> Result<(), Box<dyn Error>> {
|
||||
// env_logger::init();
|
||||
//
|
||||
// // Create a random key for ourselves.
|
||||
// let local_key = identity::Keypair::generate_ed25519();
|
||||
// let local_peer_id = PeerId::from(local_key.public());
|
||||
//
|
||||
// // Set up a an encrypted DNS-enabled TCP Transport over the Mplex protocol
|
||||
// let transport = build_development_transport(local_key.clone())?;
|
||||
//
|
||||
// // Create a swarm to manage peers and events.
|
||||
// let mut swarm = {
|
||||
// // Create a Kademlia behaviour.
|
||||
// let mut cfg = KademliaConfig::default();
|
||||
// cfg.set_query_timeout(Duration::from_secs(5 * 60));
|
||||
// let store = MemoryStore::new(local_peer_id.clone());
|
||||
// let mut behaviour = Kademlia::with_config(local_key, local_peer_id.clone(), store, cfg);
|
||||
//
|
||||
// // TODO: the /dnsaddr/ scheme is not supported (https://github.com/libp2p/rust-libp2p/issues/967)
|
||||
// /*behaviour.add_address(&"QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN".parse().unwrap(), "/dnsaddr/bootstrap.libp2p.io".parse().unwrap());
|
||||
// behaviour.add_address(&"QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa".parse().unwrap(), "/dnsaddr/bootstrap.libp2p.io".parse().unwrap());
|
||||
// behaviour.add_address(&"QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb".parse().unwrap(), "/dnsaddr/bootstrap.libp2p.io".parse().unwrap());
|
||||
// behaviour.add_address(&"QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt".parse().unwrap(), "/dnsaddr/bootstrap.libp2p.io".parse().unwrap());*/
|
||||
//
|
||||
// // The only address that currently works.
|
||||
// behaviour.add_address(
|
||||
// &"QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ".parse()?,
|
||||
// "/ip4/104.131.131.82/tcp/4001".parse()?,
|
||||
// );
|
||||
//
|
||||
// // The following addresses always fail signature verification, possibly due to
|
||||
// // RSA keys with < 2048 bits.
|
||||
// // behaviour.add_address(&"QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM".parse().unwrap(), "/ip4/104.236.179.241/tcp/4001".parse().unwrap());
|
||||
// // behaviour.add_address(&"QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu".parse().unwrap(), "/ip4/128.199.219.111/tcp/4001".parse().unwrap());
|
||||
// // behaviour.add_address(&"QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64".parse().unwrap(), "/ip4/104.236.76.40/tcp/4001".parse().unwrap());
|
||||
// // behaviour.add_address(&"QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd".parse().unwrap(), "/ip4/178.62.158.247/tcp/4001".parse().unwrap());
|
||||
//
|
||||
// // The following addresses are permanently unreachable:
|
||||
// // Other(Other(A(Transport(A(Underlying(Os { code: 101, kind: Other, message: "Network is unreachable" }))))))
|
||||
// // behaviour.add_address(&"QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM".parse().unwrap(), "/ip6/2604:a880:1:20::203:d001/tcp/4001".parse().unwrap());
|
||||
// // behaviour.add_address(&"QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu".parse().unwrap(), "/ip6/2400:6180:0:d0::151:6001/tcp/4001".parse().unwrap());
|
||||
// // behaviour.add_address(&"QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64".parse().unwrap(), "/ip6/2604:a880:800:10::4a:5001/tcp/4001".parse().unwrap());
|
||||
// // behaviour.add_address(&"QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd".parse().unwrap(), "/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001".parse().unwrap());
|
||||
// Swarm::new(transport, behaviour, local_peer_id)
|
||||
// };
|
||||
//
|
||||
// // Order Kademlia to search for a peer.
|
||||
// let to_search: PeerId = if let Some(peer_id) = env::args().nth(1) {
|
||||
// peer_id.parse()?
|
||||
// } else {
|
||||
// identity::Keypair::generate_ed25519().public().into()
|
||||
// };
|
||||
//
|
||||
// println!("Searching for the closest peers to {:?}", to_search);
|
||||
// swarm.get_closest_peers(to_search);
|
||||
//
|
||||
// // Kick it off!
|
||||
// task::block_on(async move {
|
||||
// loop {
|
||||
// let event = swarm.next().await;
|
||||
// if let KademliaEvent::GetClosestPeersResult(result) = event {
|
||||
// match result {
|
||||
// Ok(ok) => {
|
||||
// if !ok.peers.is_empty() {
|
||||
// println!("Query finished with closest peers: {:#?}", ok.peers)
|
||||
// } else {
|
||||
// // The example is considered failed as there
|
||||
// // should always be at least 1 reachable peer.
|
||||
// println!("Query finished with no closest peers.")
|
||||
// }
|
||||
// }
|
||||
// Err(GetClosestPeersError::Timeout { peers, .. }) => {
|
||||
// if !peers.is_empty() {
|
||||
// println!("Query timed out with closest peers: {:#?}", peers)
|
||||
// } else {
|
||||
// // The example is considered failed as there
|
||||
// // should always be at least 1 reachable peer.
|
||||
// println!("Query timed out with no closest peers.");
|
||||
// }
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// Ok(())
|
||||
// })
|
||||
// }
|
||||
// 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.
|
||||
|
||||
fn main() {}
|
||||
//! Demonstrates how to perform Kademlia queries on the IPFS network.
|
||||
//!
|
||||
//! You can pass as parameter a base58 peer ID to search for. If you don't pass any parameter, a
|
||||
//! peer ID will be generated randomly.
|
||||
|
||||
use async_std::task;
|
||||
use libp2p::{
|
||||
Swarm,
|
||||
PeerId,
|
||||
identity,
|
||||
build_development_transport
|
||||
};
|
||||
use libp2p::kad::{
|
||||
Kademlia,
|
||||
KademliaConfig,
|
||||
KademliaEvent,
|
||||
GetClosestPeersError,
|
||||
QueryResult,
|
||||
};
|
||||
use libp2p::kad::record::store::MemoryStore;
|
||||
use std::{env, error::Error, time::Duration};
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
env_logger::init();
|
||||
|
||||
// Create a random key for ourselves.
|
||||
let local_key = identity::Keypair::generate_ed25519();
|
||||
let local_peer_id = PeerId::from(local_key.public());
|
||||
|
||||
// Set up a an encrypted DNS-enabled TCP Transport over the Mplex protocol
|
||||
let transport = build_development_transport(local_key)?;
|
||||
|
||||
// Create a swarm to manage peers and events.
|
||||
let mut swarm = {
|
||||
// Create a Kademlia behaviour.
|
||||
let mut cfg = KademliaConfig::default();
|
||||
cfg.set_query_timeout(Duration::from_secs(5 * 60));
|
||||
let store = MemoryStore::new(local_peer_id.clone());
|
||||
let mut behaviour = Kademlia::with_config(local_peer_id.clone(), store, cfg);
|
||||
|
||||
// TODO: the /dnsaddr/ scheme is not supported (https://github.com/libp2p/rust-libp2p/issues/967)
|
||||
/*behaviour.add_address(&"QmNnooDu7bfjPFoTZYxMNLWUQJyrVwtbZg5gBMjTezGAJN".parse().unwrap(), "/dnsaddr/bootstrap.libp2p.io".parse().unwrap());
|
||||
behaviour.add_address(&"QmQCU2EcMqAqQPR2i9bChDtGNJchTbq5TbXJJ16u19uLTa".parse().unwrap(), "/dnsaddr/bootstrap.libp2p.io".parse().unwrap());
|
||||
behaviour.add_address(&"QmbLHAnMoJPWSCR5Zhtx6BHJX9KiKNN6tpvbUcqanj75Nb".parse().unwrap(), "/dnsaddr/bootstrap.libp2p.io".parse().unwrap());
|
||||
behaviour.add_address(&"QmcZf59bWwK5XFi76CZX8cbJ4BhTzzA3gU1ZjYZcYW3dwt".parse().unwrap(), "/dnsaddr/bootstrap.libp2p.io".parse().unwrap());*/
|
||||
|
||||
// The only address that currently works.
|
||||
behaviour.add_address(&"QmaCpDMGvV2BGHeYERUEnRQAwe3N8SzbUtfsmvsqQLuvuJ".parse()?, "/ip4/104.131.131.82/tcp/4001".parse()?);
|
||||
|
||||
// The following addresses always fail signature verification, possibly due to
|
||||
// RSA keys with < 2048 bits.
|
||||
// behaviour.add_address(&"QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM".parse().unwrap(), "/ip4/104.236.179.241/tcp/4001".parse().unwrap());
|
||||
// behaviour.add_address(&"QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu".parse().unwrap(), "/ip4/128.199.219.111/tcp/4001".parse().unwrap());
|
||||
// behaviour.add_address(&"QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64".parse().unwrap(), "/ip4/104.236.76.40/tcp/4001".parse().unwrap());
|
||||
// behaviour.add_address(&"QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd".parse().unwrap(), "/ip4/178.62.158.247/tcp/4001".parse().unwrap());
|
||||
|
||||
// The following addresses are permanently unreachable:
|
||||
// Other(Other(A(Transport(A(Underlying(Os { code: 101, kind: Other, message: "Network is unreachable" }))))))
|
||||
// behaviour.add_address(&"QmSoLPppuBtQSGwKDZT2M73ULpjvfd3aZ6ha4oFGL1KrGM".parse().unwrap(), "/ip6/2604:a880:1:20::203:d001/tcp/4001".parse().unwrap());
|
||||
// behaviour.add_address(&"QmSoLSafTMBsPKadTEgaXctDQVcqN88CNLHXMkTNwMKPnu".parse().unwrap(), "/ip6/2400:6180:0:d0::151:6001/tcp/4001".parse().unwrap());
|
||||
// behaviour.add_address(&"QmSoLV4Bbm51jM9C4gDYZQ9Cy3U6aXMJDAbzgu2fzaDs64".parse().unwrap(), "/ip6/2604:a880:800:10::4a:5001/tcp/4001".parse().unwrap());
|
||||
// behaviour.add_address(&"QmSoLer265NRgSp2LA3dPaeykiS1J6DifTC88f5uVQKNAd".parse().unwrap(), "/ip6/2a03:b0c0:0:1010::23:1001/tcp/4001".parse().unwrap());
|
||||
Swarm::new(transport, behaviour, local_peer_id)
|
||||
};
|
||||
|
||||
// Order Kademlia to search for a peer.
|
||||
let to_search: PeerId = if let Some(peer_id) = env::args().nth(1) {
|
||||
peer_id.parse()?
|
||||
} else {
|
||||
identity::Keypair::generate_ed25519().public().into()
|
||||
};
|
||||
|
||||
println!("Searching for the closest peers to {:?}", to_search);
|
||||
swarm.get_closest_peers(to_search);
|
||||
|
||||
// Kick it off!
|
||||
task::block_on(async move {
|
||||
loop {
|
||||
let event = swarm.next().await;
|
||||
if let KademliaEvent::QueryResult {
|
||||
result: QueryResult::GetClosestPeers(result),
|
||||
..
|
||||
} = event {
|
||||
match result {
|
||||
Ok(ok) =>
|
||||
if !ok.peers.is_empty() {
|
||||
println!("Query finished with closest peers: {:#?}", ok.peers)
|
||||
} else {
|
||||
// The example is considered failed as there
|
||||
// should always be at least 1 reachable peer.
|
||||
println!("Query finished with no closest peers.")
|
||||
}
|
||||
Err(GetClosestPeersError::Timeout { peers, .. }) =>
|
||||
if !peers.is_empty() {
|
||||
println!("Query timed out with closest peers: {:#?}", peers)
|
||||
} else {
|
||||
// The example is considered failed as there
|
||||
// should always be at least 1 reachable peer.
|
||||
println!("Query timed out with no closest peers.");
|
||||
}
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-core-derive"
|
||||
edition = "2018"
|
||||
description = "Procedural macros of libp2p-core"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -17,4 +17,4 @@ syn = { version = "1.0.8", default-features = false, features = ["clone-impls",
|
||||
quote = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
libp2p = { version = "0.18.0", path = "../.." }
|
||||
libp2p = { version = "0.19.0", path = "../.." }
|
||||
|
@ -288,7 +288,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
|
||||
quote!{ ev }
|
||||
};
|
||||
|
||||
for _ in 0 .. data_struct.fields.iter().filter(|f| !is_ignored(f)).count() - 1 - field_n {
|
||||
for _ in 0 .. data_struct.fields.iter().filter(|f| !is_ignored(f)).count() - 1 - enum_n {
|
||||
elem = quote!{ #either_ident::First(#elem) };
|
||||
}
|
||||
|
||||
@ -378,7 +378,7 @@ fn build_struct(ast: &DeriveInput, data_struct: &DataStruct) -> TokenStream {
|
||||
} else {
|
||||
quote!{ event }
|
||||
};
|
||||
for _ in 0 .. data_struct.fields.iter().filter(|f| !is_ignored(f)).count() - 1 - field_n {
|
||||
for _ in 0 .. data_struct.fields.iter().filter(|f| !is_ignored(f)).count() - 1 - enum_n {
|
||||
wrapped_event = quote!{ #either_ident::First(#wrapped_event) };
|
||||
}
|
||||
|
||||
|
@ -109,6 +109,33 @@ fn three_fields() {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn three_fields_non_last_ignored() {
|
||||
#[allow(dead_code)]
|
||||
#[derive(NetworkBehaviour)]
|
||||
struct Foo {
|
||||
ping: libp2p::ping::Ping,
|
||||
#[behaviour(ignore)]
|
||||
identify: String,
|
||||
kad: libp2p::kad::Kademlia<libp2p::kad::record::store::MemoryStore>,
|
||||
}
|
||||
|
||||
impl libp2p::swarm::NetworkBehaviourEventProcess<libp2p::ping::PingEvent> for Foo {
|
||||
fn inject_event(&mut self, _: libp2p::ping::PingEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
impl libp2p::swarm::NetworkBehaviourEventProcess<libp2p::kad::KademliaEvent> for Foo {
|
||||
fn inject_event(&mut self, _: libp2p::kad::KademliaEvent) {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
fn foo() {
|
||||
require_net_behaviour::<Foo>();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn custom_polling() {
|
||||
#[allow(dead_code)]
|
||||
|
@ -6,14 +6,14 @@ description = "Implementation of the multiaddr format"
|
||||
homepage = "https://github.com/libp2p/rust-libp2p"
|
||||
keywords = ["multiaddr", "ipfs"]
|
||||
license = "MIT"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
|
||||
[dependencies]
|
||||
arrayref = "0.3"
|
||||
bs58 = "0.3.0"
|
||||
byteorder = "1.3.1"
|
||||
data-encoding = "2.1"
|
||||
multihash = "0.10"
|
||||
multihash = "0.11.0"
|
||||
percent-encoding = "2.1.0"
|
||||
serde = "1.0.70"
|
||||
static_assertions = "1.1"
|
||||
|
@ -70,7 +70,7 @@ fn from_url_inner_http_ws(url: url::Url, lossy: bool) -> std::result::Result<Mul
|
||||
if let Ok(ip) = hostname.parse::<IpAddr>() {
|
||||
Protocol::from(ip)
|
||||
} else {
|
||||
Protocol::Dns4(hostname.into())
|
||||
Protocol::Dns(hostname.into())
|
||||
}
|
||||
} else {
|
||||
return Err(FromUrlErr::BadUrl);
|
||||
@ -185,31 +185,31 @@ mod tests {
|
||||
#[test]
|
||||
fn dns_addr_ws() {
|
||||
let addr = from_url("ws://example.com").unwrap();
|
||||
assert_eq!(addr, "/dns4/example.com/tcp/80/ws".parse().unwrap());
|
||||
assert_eq!(addr, "/dns/example.com/tcp/80/ws".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dns_addr_http() {
|
||||
let addr = from_url("http://example.com").unwrap();
|
||||
assert_eq!(addr, "/dns4/example.com/tcp/80/http".parse().unwrap());
|
||||
assert_eq!(addr, "/dns/example.com/tcp/80/http".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dns_addr_wss() {
|
||||
let addr = from_url("wss://example.com").unwrap();
|
||||
assert_eq!(addr, "/dns4/example.com/tcp/443/wss".parse().unwrap());
|
||||
assert_eq!(addr, "/dns/example.com/tcp/443/wss".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dns_addr_https() {
|
||||
let addr = from_url("https://example.com").unwrap();
|
||||
assert_eq!(addr, "/dns4/example.com/tcp/443/https".parse().unwrap());
|
||||
assert_eq!(addr, "/dns/example.com/tcp/443/https".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bad_hostname() {
|
||||
let addr = from_url("wss://127.0.0.1x").unwrap();
|
||||
assert_eq!(addr, "/dns4/127.0.0.1x/tcp/443/wss".parse().unwrap());
|
||||
assert_eq!(addr, "/dns/127.0.0.1x/tcp/443/wss".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -223,7 +223,7 @@ mod tests {
|
||||
#[test]
|
||||
fn dns_and_port() {
|
||||
let addr = from_url("http://example.com:1000").unwrap();
|
||||
assert_eq!(addr, "/dns4/example.com/tcp/1000/http".parse().unwrap());
|
||||
assert_eq!(addr, "/dns/example.com/tcp/1000/http".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -17,6 +17,7 @@ use unsigned_varint::{encode, decode};
|
||||
use crate::onion_addr::Onion3Addr;
|
||||
|
||||
const DCCP: u32 = 33;
|
||||
const DNS: u32 = 53;
|
||||
const DNS4: u32 = 54;
|
||||
const DNS6: u32 = 55;
|
||||
const DNSADDR: u32 = 56;
|
||||
@ -66,6 +67,7 @@ const PATH_SEGMENT_ENCODE_SET: &percent_encoding::AsciiSet = &percent_encoding::
|
||||
#[derive(PartialEq, Eq, Clone, Debug)]
|
||||
pub enum Protocol<'a> {
|
||||
Dccp(u16),
|
||||
Dns(Cow<'a, str>),
|
||||
Dns4(Cow<'a, str>),
|
||||
Dns6(Cow<'a, str>),
|
||||
Dnsaddr(Cow<'a, str>),
|
||||
@ -125,6 +127,10 @@ impl<'a> Protocol<'a> {
|
||||
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
|
||||
Ok(Protocol::Ip6(Ipv6Addr::from_str(s)?))
|
||||
}
|
||||
"dns" => {
|
||||
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
|
||||
Ok(Protocol::Dns(Cow::Borrowed(s)))
|
||||
}
|
||||
"dns4" => {
|
||||
let s = iter.next().ok_or(Error::InvalidProtocolString)?;
|
||||
Ok(Protocol::Dns4(Cow::Borrowed(s)))
|
||||
@ -206,6 +212,11 @@ impl<'a> Protocol<'a> {
|
||||
let num = rdr.read_u16::<BigEndian>()?;
|
||||
Ok((Protocol::Dccp(num), rest))
|
||||
}
|
||||
DNS => {
|
||||
let (n, input) = decode::usize(input)?;
|
||||
let (data, rest) = split_at(n, input)?;
|
||||
Ok((Protocol::Dns(Cow::Borrowed(str::from_utf8(data)?)), rest))
|
||||
}
|
||||
DNS4 => {
|
||||
let (n, input) = decode::usize(input)?;
|
||||
let (data, rest) = split_at(n, input)?;
|
||||
@ -345,6 +356,12 @@ impl<'a> Protocol<'a> {
|
||||
w.write_all(encode::u32(SCTP, &mut buf))?;
|
||||
w.write_u16::<BigEndian>(*port)?
|
||||
}
|
||||
Protocol::Dns(s) => {
|
||||
w.write_all(encode::u32(DNS, &mut buf))?;
|
||||
let bytes = s.as_bytes();
|
||||
w.write_all(encode::usize(bytes.len(), &mut encode::usize_buffer()))?;
|
||||
w.write_all(&bytes)?
|
||||
}
|
||||
Protocol::Dns4(s) => {
|
||||
w.write_all(encode::u32(DNS4, &mut buf))?;
|
||||
let bytes = s.as_bytes();
|
||||
@ -421,6 +438,7 @@ impl<'a> Protocol<'a> {
|
||||
use self::Protocol::*;
|
||||
match self {
|
||||
Dccp(a) => Dccp(a),
|
||||
Dns(cow) => Dns(Cow::Owned(cow.into_owned())),
|
||||
Dns4(cow) => Dns4(Cow::Owned(cow.into_owned())),
|
||||
Dns6(cow) => Dns6(Cow::Owned(cow.into_owned())),
|
||||
Dnsaddr(cow) => Dnsaddr(Cow::Owned(cow.into_owned())),
|
||||
@ -454,6 +472,7 @@ impl<'a> fmt::Display for Protocol<'a> {
|
||||
use self::Protocol::*;
|
||||
match self {
|
||||
Dccp(port) => write!(f, "/dccp/{}", port),
|
||||
Dns(s) => write!(f, "/dns/{}", s),
|
||||
Dns4(s) => write!(f, "/dns4/{}", s),
|
||||
Dns6(s) => write!(f, "/dns6/{}", s),
|
||||
Dnsaddr(s) => write!(f, "/dnsaddr/{}", s),
|
||||
|
@ -76,36 +76,37 @@ struct Proto(Protocol<'static>);
|
||||
impl Arbitrary for Proto {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> Self {
|
||||
use Protocol::*;
|
||||
match g.gen_range(0, 24) { // TODO: Add Protocol::Quic
|
||||
match g.gen_range(0, 25) { // TODO: Add Protocol::Quic
|
||||
0 => Proto(Dccp(g.gen())),
|
||||
1 => Proto(Dns4(Cow::Owned(SubString::arbitrary(g).0))),
|
||||
2 => Proto(Dns6(Cow::Owned(SubString::arbitrary(g).0))),
|
||||
3 => Proto(Http),
|
||||
4 => Proto(Https),
|
||||
5 => Proto(Ip4(Ipv4Addr::arbitrary(g))),
|
||||
6 => Proto(Ip6(Ipv6Addr::arbitrary(g))),
|
||||
7 => Proto(P2pWebRtcDirect),
|
||||
8 => Proto(P2pWebRtcStar),
|
||||
9 => Proto(P2pWebSocketStar),
|
||||
10 => Proto(Memory(g.gen())),
|
||||
1 => Proto(Dns(Cow::Owned(SubString::arbitrary(g).0))),
|
||||
2 => Proto(Dns4(Cow::Owned(SubString::arbitrary(g).0))),
|
||||
3 => Proto(Dns6(Cow::Owned(SubString::arbitrary(g).0))),
|
||||
4 => Proto(Http),
|
||||
5 => Proto(Https),
|
||||
6 => Proto(Ip4(Ipv4Addr::arbitrary(g))),
|
||||
7 => Proto(Ip6(Ipv6Addr::arbitrary(g))),
|
||||
8 => Proto(P2pWebRtcDirect),
|
||||
9 => Proto(P2pWebRtcStar),
|
||||
10 => Proto(P2pWebSocketStar),
|
||||
11 => Proto(Memory(g.gen())),
|
||||
// TODO: impl Arbitrary for Multihash:
|
||||
11 => Proto(P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))),
|
||||
12 => Proto(P2pCircuit),
|
||||
13 => Proto(Quic),
|
||||
14 => Proto(Sctp(g.gen())),
|
||||
15 => Proto(Tcp(g.gen())),
|
||||
16 => Proto(Udp(g.gen())),
|
||||
17 => Proto(Udt),
|
||||
18 => Proto(Unix(Cow::Owned(SubString::arbitrary(g).0))),
|
||||
19 => Proto(Utp),
|
||||
20 => Proto(Ws("/".into())),
|
||||
21 => Proto(Wss("/".into())),
|
||||
22 => {
|
||||
12 => Proto(P2p(multihash("QmcgpsyWgH8Y8ajJz1Cu72KnS5uo2Aa2LpzU7kinSupNKC"))),
|
||||
13 => Proto(P2pCircuit),
|
||||
14 => Proto(Quic),
|
||||
15 => Proto(Sctp(g.gen())),
|
||||
16 => Proto(Tcp(g.gen())),
|
||||
17 => Proto(Udp(g.gen())),
|
||||
18 => Proto(Udt),
|
||||
19 => Proto(Unix(Cow::Owned(SubString::arbitrary(g).0))),
|
||||
20 => Proto(Utp),
|
||||
21 => Proto(Ws("/".into())),
|
||||
22 => Proto(Wss("/".into())),
|
||||
23 => {
|
||||
let mut a = [0; 10];
|
||||
g.fill(&mut a);
|
||||
Proto(Onion(Cow::Owned(a), g.gen_range(1, std::u16::MAX)))
|
||||
},
|
||||
23 => {
|
||||
24 => {
|
||||
let mut a = [0; 35];
|
||||
g.fill_bytes(&mut a);
|
||||
Proto(Onion3((a, g.gen_range(1, std::u16::MAX)).into()))
|
||||
|
@ -11,5 +11,5 @@ categories = ["network-programming", "asynchronous"]
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
num_cpus = "1.8"
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-mplex"
|
||||
edition = "2018"
|
||||
description = "Mplex multiplexing protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -14,11 +14,11 @@ bytes = "0.5"
|
||||
fnv = "1.0"
|
||||
futures = "0.3.1"
|
||||
futures_codec = "0.3.4"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
log = "0.4"
|
||||
parking_lot = "0.10"
|
||||
unsigned-varint = { version = "0.3", features = ["futures-codec"] }
|
||||
|
||||
[dev-dependencies]
|
||||
async-std = "1.0"
|
||||
libp2p-tcp = { version = "0.18.0", path = "../../transports/tcp" }
|
||||
libp2p-tcp = { version = "0.19.0", path = "../../transports/tcp" }
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-yamux"
|
||||
edition = "2018"
|
||||
description = "Yamux multiplexing protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -11,7 +11,7 @@ categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.1"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
parking_lot = "0.10"
|
||||
thiserror = "1.0"
|
||||
yamux = "0.4.5"
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-deflate"
|
||||
edition = "2018"
|
||||
description = "Deflate encryption protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -11,11 +11,11 @@ categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.1"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
flate2 = "1.0"
|
||||
|
||||
[dev-dependencies]
|
||||
async-std = "1.0"
|
||||
libp2p-tcp = { version = "0.18.0", path = "../../transports/tcp" }
|
||||
libp2p-tcp = { version = "0.19.0", path = "../../transports/tcp" }
|
||||
rand = "0.7"
|
||||
quickcheck = "0.9"
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-floodsub"
|
||||
edition = "2018"
|
||||
description = "Floodsub protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -13,8 +13,8 @@ categories = ["network-programming", "asynchronous"]
|
||||
cuckoofilter = "0.3.2"
|
||||
fnv = "1.0"
|
||||
futures = "0.3.1"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.18.0", path = "../../swarm" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.19.0", path = "../../swarm" }
|
||||
prost = "0.6.1"
|
||||
rand = "0.7"
|
||||
smallvec = "1.0"
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-gossipsub"
|
||||
edition = "2018"
|
||||
description = "Gossipsub protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Age Manning <Age@AgeManning.com>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -10,8 +10,8 @@ keywords = ["peer-to-peer", "libp2p", "networking"]
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
libp2p-swarm = { version = "0.18.0", path = "../../swarm" }
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.19.0", path = "../../swarm" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
bytes = "0.5.4"
|
||||
byteorder = "1.3.2"
|
||||
fnv = "1.0.6"
|
||||
@ -30,8 +30,8 @@ prost = "0.6.1"
|
||||
[dev-dependencies]
|
||||
async-std = "1.4.0"
|
||||
env_logger = "0.7.1"
|
||||
libp2p-plaintext = { version = "0.18.0", path = "../plaintext" }
|
||||
libp2p-yamux = { version = "0.18.0", path = "../../muxers/yamux" }
|
||||
libp2p-plaintext = { version = "0.19.0", path = "../plaintext" }
|
||||
libp2p-yamux = { version = "0.19.0", path = "../../muxers/yamux" }
|
||||
quickcheck = "0.9.2"
|
||||
|
||||
[build-dependencies]
|
||||
|
@ -27,7 +27,7 @@ use libp2p_swarm::protocols_handler::{
|
||||
KeepAlive, ProtocolsHandler, ProtocolsHandlerEvent, ProtocolsHandlerUpgrErr, SubstreamProtocol,
|
||||
};
|
||||
use libp2p_swarm::NegotiatedSubstream;
|
||||
use log::{debug, trace, warn};
|
||||
use log::{debug, error, trace, warn};
|
||||
use smallvec::SmallVec;
|
||||
use std::{
|
||||
borrow::Cow,
|
||||
@ -274,7 +274,13 @@ impl ProtocolsHandler for GossipsubHandler {
|
||||
Some(OutboundSubstreamState::PendingFlush(substream))
|
||||
}
|
||||
Err(e) => {
|
||||
return Poll::Ready(ProtocolsHandlerEvent::Close(e));
|
||||
if let io::ErrorKind::PermissionDenied = e.kind() {
|
||||
error!("Message over the maximum transmission limit was not sent.");
|
||||
self.outbound_substream =
|
||||
Some(OutboundSubstreamState::WaitingOutput(substream));
|
||||
} else {
|
||||
return Poll::Ready(ProtocolsHandlerEvent::Close(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-identify"
|
||||
edition = "2018"
|
||||
description = "Nodes identifcation protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -11,8 +11,8 @@ categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.1"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.18.0", path = "../../swarm" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.19.0", path = "../../swarm" }
|
||||
log = "0.4.1"
|
||||
prost = "0.6.1"
|
||||
smallvec = "1.0"
|
||||
@ -20,9 +20,9 @@ wasm-timer = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
async-std = "1.0"
|
||||
libp2p-mplex = { version = "0.18.0", path = "../../muxers/mplex" }
|
||||
libp2p-secio = { version = "0.18.0", path = "../../protocols/secio" }
|
||||
libp2p-tcp = { version = "0.18.0", path = "../../transports/tcp" }
|
||||
libp2p-mplex = { version = "0.19.0", path = "../../muxers/mplex" }
|
||||
libp2p-secio = { version = "0.19.0", path = "../../protocols/secio" }
|
||||
libp2p-tcp = { version = "0.19.0", path = "../../transports/tcp" }
|
||||
|
||||
[build-dependencies]
|
||||
prost-build = "0.6"
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-kad"
|
||||
edition = "2018"
|
||||
description = "Kademlia protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -17,9 +17,9 @@ fnv = "1.0"
|
||||
futures_codec = "0.3.4"
|
||||
futures = "0.3.1"
|
||||
log = "0.4"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.18.0", path = "../../swarm" }
|
||||
multihash = "0.10"
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.19.0", path = "../../swarm" }
|
||||
multihash = "0.11.0"
|
||||
prost = "0.6.1"
|
||||
rand = "0.7.2"
|
||||
sha2 = "0.8.0"
|
||||
@ -34,8 +34,8 @@ derivative = "2.0.2"
|
||||
trust-graph = { git = "ssh://git@github.com/fluencelabs/arqada.git", branch = "memory_store_set" }
|
||||
|
||||
[dev-dependencies]
|
||||
libp2p-secio = { version = "0.18.0", path = "../secio" }
|
||||
libp2p-yamux = { version = "0.18.0", path = "../../muxers/yamux" }
|
||||
libp2p-secio = { version = "0.19.0", path = "../secio" }
|
||||
libp2p-yamux = { version = "0.19.0", path = "../../muxers/yamux" }
|
||||
quickcheck = "0.9.0"
|
||||
|
||||
[build-dependencies]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -152,10 +152,11 @@ fn bootstrap() {
|
||||
.collect::<Vec<_>>();
|
||||
let swarm_ids: Vec<_> = swarms.iter().map(Swarm::local_peer_id).cloned().collect();
|
||||
|
||||
swarms[0].bootstrap();
|
||||
let qid = swarms[0].bootstrap().unwrap();
|
||||
|
||||
// Expected known peers
|
||||
let expected_known = swarm_ids.iter().skip(1).cloned().collect::<HashSet<_>>();
|
||||
let mut first = true;
|
||||
|
||||
// Run test
|
||||
block_on(
|
||||
@ -163,14 +164,23 @@ fn bootstrap() {
|
||||
for (i, swarm) in swarms.iter_mut().enumerate() {
|
||||
loop {
|
||||
match swarm.poll_next_unpin(ctx) {
|
||||
Poll::Ready(Some(KademliaEvent::BootstrapResult(Ok(ok)))) => {
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
id, result: QueryResult::Bootstrap(Ok(ok)), ..
|
||||
})) => {
|
||||
assert_eq!(id, qid);
|
||||
assert_eq!(i, 0);
|
||||
assert_eq!(ok.peer, swarm_ids[0]);
|
||||
let known = swarm.kbuckets.iter()
|
||||
.map(|e| e.node.key.preimage().clone())
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(expected_known, known);
|
||||
return Poll::Ready(())
|
||||
if first {
|
||||
// Bootstrapping must start with a self-lookup.
|
||||
assert_eq!(ok.peer, swarm_ids[0]);
|
||||
}
|
||||
first = false;
|
||||
if ok.num_remaining == 0 {
|
||||
let known = swarm.kbuckets.iter()
|
||||
.map(|e| e.node.key.preimage().clone())
|
||||
.collect::<HashSet<_>>();
|
||||
assert_eq!(expected_known, known);
|
||||
return Poll::Ready(())
|
||||
}
|
||||
}
|
||||
// Ignore any other event.
|
||||
Poll::Ready(Some(_)) => (),
|
||||
@ -210,7 +220,17 @@ fn query_iter() {
|
||||
// propagate forwards through the list of peers.
|
||||
let search_target = PeerId::random();
|
||||
let search_target_key = kbucket::Key::new(search_target.clone());
|
||||
swarms[0].get_closest_peers(search_target.clone());
|
||||
let qid = swarms[0].get_closest_peers(search_target.clone());
|
||||
|
||||
match swarms[0].query(&qid) {
|
||||
Some(q) => match q.info() {
|
||||
QueryInfo::GetClosestPeers { key } => {
|
||||
assert_eq!(&key[..], search_target.borrow() as &[u8])
|
||||
},
|
||||
i => panic!("Unexpected query info: {:?}", i)
|
||||
}
|
||||
None => panic!("Query not found: {:?}", qid)
|
||||
}
|
||||
|
||||
// Set up expectations.
|
||||
let expected_swarm_id = swarm_ids[0].clone();
|
||||
@ -224,7 +244,10 @@ fn query_iter() {
|
||||
for (i, swarm) in swarms.iter_mut().enumerate() {
|
||||
loop {
|
||||
match swarm.poll_next_unpin(ctx) {
|
||||
Poll::Ready(Some(KademliaEvent::GetClosestPeersResult(Ok(ok)))) => {
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
id, result: QueryResult::GetClosestPeers(Ok(ok)), ..
|
||||
})) => {
|
||||
assert_eq!(id, qid);
|
||||
assert_eq!(&ok.key[..], search_target.as_bytes());
|
||||
assert_eq!(swarm_ids[i], expected_swarm_id);
|
||||
assert_eq!(swarm.queries.size(), 0);
|
||||
@ -275,7 +298,9 @@ fn unresponsive_not_returned_direct() {
|
||||
for (_, swarm) in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll_next_unpin(ctx) {
|
||||
Poll::Ready(Some(KademliaEvent::GetClosestPeersResult(Ok(ok)))) => {
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
result: QueryResult::GetClosestPeers(Ok(ok)), ..
|
||||
})) => {
|
||||
assert_eq!(&ok.key[..], search_target.as_bytes());
|
||||
assert_eq!(ok.peers.len(), 0);
|
||||
return Poll::Ready(());
|
||||
@ -325,7 +350,9 @@ fn unresponsive_not_returned_indirect() {
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll_next_unpin(ctx) {
|
||||
Poll::Ready(Some(KademliaEvent::GetClosestPeersResult(Ok(ok)))) => {
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
result: QueryResult::GetClosestPeers(Ok(ok)), ..
|
||||
})) => {
|
||||
assert_eq!(&ok.key[..], search_target.as_bytes());
|
||||
assert_eq!(ok.peers.len(), 1);
|
||||
assert_eq!(ok.peers[0], first_peer_id);
|
||||
@ -363,14 +390,17 @@ fn get_record_not_found() {
|
||||
let mut swarms = swarms.into_iter().map(|(_, _addr, swarm)| swarm).collect::<Vec<_>>();
|
||||
|
||||
let target_key = record::Key::from(random_multihash());
|
||||
swarms[0].get_record(&target_key, Quorum::One);
|
||||
let qid = swarms[0].get_record(&target_key, Quorum::One);
|
||||
|
||||
block_on(
|
||||
poll_fn(move |ctx| {
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll_next_unpin(ctx) {
|
||||
Poll::Ready(Some(KademliaEvent::GetRecordResult(Err(e)))) => {
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
id, result: QueryResult::GetRecord(Err(e)), ..
|
||||
})) => {
|
||||
assert_eq!(id, qid);
|
||||
if let GetRecordError::NotFound { key, closest_peers, } = e {
|
||||
assert_eq!(key, target_key);
|
||||
assert_eq!(closest_peers.len(), 2);
|
||||
@ -436,8 +466,23 @@ fn put_record() {
|
||||
})
|
||||
.collect::<HashMap<_,_>>();
|
||||
|
||||
// Initiate put_record queries.
|
||||
let mut qids = HashSet::new();
|
||||
for r in records.values() {
|
||||
swarms[0].put_record(r.clone(), Quorum::All);
|
||||
let qid = swarms[0].put_record(r.clone(), Quorum::All).unwrap();
|
||||
match swarms[0].query(&qid) {
|
||||
Some(q) => match q.info() {
|
||||
QueryInfo::PutRecord { phase, record, .. } => {
|
||||
assert_eq!(phase, &PutRecordPhase::GetClosestPeers);
|
||||
assert_eq!(record.key, r.key);
|
||||
assert_eq!(record.value, r.value);
|
||||
assert!(record.expires.is_some());
|
||||
qids.insert(qid);
|
||||
},
|
||||
i => panic!("Unexpected query info: {:?}", i)
|
||||
}
|
||||
None => panic!("Query not found: {:?}", qid)
|
||||
}
|
||||
}
|
||||
|
||||
// Each test run republishes all records once.
|
||||
@ -451,8 +496,17 @@ fn put_record() {
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll_next_unpin(ctx) {
|
||||
Poll::Ready(Some(KademliaEvent::PutRecordResult(res))) |
|
||||
Poll::Ready(Some(KademliaEvent::RepublishRecordResult(res))) => {
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
id, result: QueryResult::PutRecord(res), stats
|
||||
})) |
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
id, result: QueryResult::RepublishRecord(res), stats
|
||||
})) => {
|
||||
assert!(qids.is_empty() || qids.remove(&id));
|
||||
assert!(stats.duration().is_some());
|
||||
assert!(stats.num_successes() >= replication_factor.get() as u32);
|
||||
assert!(stats.num_requests() >= stats.num_successes());
|
||||
assert_eq!(stats.num_failures(), 0);
|
||||
match res {
|
||||
Err(e) => panic!("{:?}", e),
|
||||
Ok(ok) => {
|
||||
@ -551,7 +605,7 @@ fn put_record() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_value() {
|
||||
fn get_record() {
|
||||
let mut swarms = build_nodes(3);
|
||||
|
||||
// Let first peer know of second peer and second peer know of third peer.
|
||||
@ -566,14 +620,17 @@ fn get_value() {
|
||||
let record = Record::new(random_multihash(), vec![4,5,6]);
|
||||
|
||||
swarms[1].1.store.put(record.clone()).unwrap();
|
||||
swarms[0].1.get_record(&record.key, Quorum::One);
|
||||
let qid = swarms[0].1.get_record(&record.key, Quorum::One);
|
||||
|
||||
block_on(
|
||||
poll_fn(move |ctx| {
|
||||
for (_, swarm) in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll_next_unpin(ctx) {
|
||||
Poll::Ready(Some(KademliaEvent::GetRecordResult(Ok(ok)))) => {
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
id, result: QueryResult::GetRecord(Ok(ok)), ..
|
||||
})) => {
|
||||
assert_eq!(id, qid);
|
||||
assert_eq!(ok.records.len(), 1);
|
||||
assert_eq!(ok.records.first(), Some(&record));
|
||||
return Poll::Ready(());
|
||||
@ -592,7 +649,7 @@ fn get_value() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_value_many() {
|
||||
fn get_record_many() {
|
||||
// TODO: Randomise
|
||||
let num_nodes = 12;
|
||||
let mut swarms = build_connected_nodes(num_nodes, 3).into_iter()
|
||||
@ -607,14 +664,17 @@ fn get_value_many() {
|
||||
}
|
||||
|
||||
let quorum = Quorum::N(NonZeroUsize::new(num_results).unwrap());
|
||||
swarms[0].get_record(&record.key, quorum);
|
||||
let qid = swarms[0].get_record(&record.key, quorum);
|
||||
|
||||
block_on(
|
||||
poll_fn(move |ctx| {
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll_next_unpin(ctx) {
|
||||
Poll::Ready(Some(KademliaEvent::GetRecordResult(Ok(ok)))) => {
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
id, result: QueryResult::GetRecord(Ok(ok)), ..
|
||||
})) => {
|
||||
assert_eq!(id, qid);
|
||||
assert_eq!(ok.records.len(), num_results);
|
||||
assert_eq!(ok.records.first(), Some(&record));
|
||||
return Poll::Ready(());
|
||||
@ -672,8 +732,10 @@ fn add_provider() {
|
||||
let mut results = Vec::new();
|
||||
|
||||
// Initiate the first round of publishing.
|
||||
let mut qids = HashSet::new();
|
||||
for k in &keys {
|
||||
swarms[0].start_providing(k.clone());
|
||||
let qid = swarms[0].start_providing(k.clone()).unwrap();
|
||||
qids.insert(qid);
|
||||
}
|
||||
|
||||
block_on(
|
||||
@ -682,8 +744,13 @@ fn add_provider() {
|
||||
for swarm in &mut swarms {
|
||||
loop {
|
||||
match swarm.poll_next_unpin(ctx) {
|
||||
Poll::Ready(Some(KademliaEvent::StartProvidingResult(res))) |
|
||||
Poll::Ready(Some(KademliaEvent::RepublishProviderResult(res))) => {
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
id, result: QueryResult::StartProviding(res), ..
|
||||
})) |
|
||||
Poll::Ready(Some(KademliaEvent::QueryResult {
|
||||
id, result: QueryResult::RepublishProvider(res), ..
|
||||
})) => {
|
||||
assert!(qids.is_empty() || qids.remove(&id));
|
||||
match res {
|
||||
Err(e) => panic!(e),
|
||||
Ok(ok) => {
|
||||
@ -784,7 +851,7 @@ fn exceed_jobs_max_queries() {
|
||||
let (_, _addr, mut swarm) = build_node();
|
||||
let num = JOBS_MAX_QUERIES + 1;
|
||||
for _ in 0 .. num {
|
||||
swarm.bootstrap();
|
||||
swarm.get_closest_peers(PeerId::random());
|
||||
}
|
||||
|
||||
assert_eq!(swarm.queries.size(), num);
|
||||
@ -794,8 +861,10 @@ fn exceed_jobs_max_queries() {
|
||||
for _ in 0 .. num {
|
||||
// There are no other nodes, so the queries finish instantly.
|
||||
if let Poll::Ready(Some(e)) = swarm.poll_next_unpin(ctx) {
|
||||
if let KademliaEvent::BootstrapResult(r) = e {
|
||||
assert!(r.is_ok(), "Unexpected error")
|
||||
if let KademliaEvent::QueryResult {
|
||||
result: QueryResult::GetClosestPeers(Ok(r)), ..
|
||||
} = e {
|
||||
assert!(r.peers.is_empty())
|
||||
} else {
|
||||
panic!("Unexpected event: {:?}", e)
|
||||
}
|
||||
|
@ -41,10 +41,33 @@ mod dht_proto {
|
||||
|
||||
pub use addresses::Addresses;
|
||||
pub use behaviour::{
|
||||
AddProviderError, AddProviderOk, AddProviderResult, BootstrapError, BootstrapOk,
|
||||
BootstrapResult, GetClosestPeersError, GetClosestPeersOk, GetClosestPeersResult,
|
||||
GetProvidersError, GetProvidersOk, GetProvidersResult, GetRecordError, GetRecordOk,
|
||||
GetRecordResult, PutRecordError, PutRecordOk, PutRecordResult,
|
||||
QueryResult,
|
||||
QueryInfo,
|
||||
QueryStats,
|
||||
|
||||
BootstrapResult,
|
||||
BootstrapOk,
|
||||
BootstrapError,
|
||||
|
||||
GetRecordResult,
|
||||
GetRecordOk,
|
||||
GetRecordError,
|
||||
|
||||
PutRecordResult,
|
||||
PutRecordOk,
|
||||
PutRecordError,
|
||||
|
||||
GetClosestPeersResult,
|
||||
GetClosestPeersOk,
|
||||
GetClosestPeersError,
|
||||
|
||||
AddProviderResult,
|
||||
AddProviderOk,
|
||||
AddProviderError,
|
||||
|
||||
GetProvidersResult,
|
||||
GetProvidersOk,
|
||||
GetProvidersError,
|
||||
};
|
||||
pub use behaviour::{Kademlia, KademliaConfig, KademliaEvent, Quorum};
|
||||
pub use protocol::KadConnectionType;
|
||||
|
@ -89,16 +89,40 @@ impl<TInner> QueryPool<TInner> {
|
||||
/// Adds a query to the pool that contacts a fixed set of peers.
|
||||
pub fn add_fixed<I>(&mut self, peers: I, inner: TInner) -> QueryId
|
||||
where
|
||||
I: IntoIterator<Item = Key<PeerId>>
|
||||
I: IntoIterator<Item = PeerId>
|
||||
{
|
||||
let peers = peers.into_iter().map(|k| k.into_preimage()).collect::<Vec<_>>();
|
||||
let id = self.next_query_id();
|
||||
self.continue_fixed(id, peers, inner);
|
||||
id
|
||||
}
|
||||
|
||||
/// Continues an earlier query with a fixed set of peers, reusing
|
||||
/// the given query ID, which must be from a query that finished
|
||||
/// earlier.
|
||||
pub fn continue_fixed<I>(&mut self, id: QueryId, peers: I, inner: TInner)
|
||||
where
|
||||
I: IntoIterator<Item = PeerId>
|
||||
{
|
||||
assert!(!self.queries.contains_key(&id));
|
||||
let parallelism = self.config.replication_factor.get();
|
||||
let peer_iter = QueryPeerIter::Fixed(FixedPeersIter::new(peers, parallelism));
|
||||
self.add(peer_iter, inner)
|
||||
let query = Query::new(id, peer_iter, inner);
|
||||
self.queries.insert(id, query);
|
||||
}
|
||||
|
||||
/// Adds a query to the pool that iterates towards the closest peers to the target.
|
||||
pub fn add_iter_closest<T, I>(&mut self, target: T, peers: I, inner: TInner) -> QueryId
|
||||
where
|
||||
T: Into<KeyBytes>,
|
||||
I: IntoIterator<Item = Key<PeerId>>
|
||||
{
|
||||
let id = self.next_query_id();
|
||||
self.continue_iter_closest(id, target, peers, inner);
|
||||
id
|
||||
}
|
||||
|
||||
/// Adds a query to the pool that iterates towards the closest peers to the target.
|
||||
pub fn continue_iter_closest<T, I>(&mut self, id: QueryId, target: T, peers: I, inner: TInner)
|
||||
where
|
||||
T: Into<KeyBytes>,
|
||||
I: IntoIterator<Item = Key<PeerId>>
|
||||
@ -108,14 +132,13 @@ impl<TInner> QueryPool<TInner> {
|
||||
.. ClosestPeersIterConfig::default()
|
||||
};
|
||||
let peer_iter = QueryPeerIter::Closest(ClosestPeersIter::with_config(cfg, target, peers));
|
||||
self.add(peer_iter, inner)
|
||||
}
|
||||
|
||||
fn add(&mut self, peer_iter: QueryPeerIter, inner: TInner) -> QueryId {
|
||||
let id = QueryId(self.next_id);
|
||||
self.next_id = self.next_id.wrapping_add(1);
|
||||
let query = Query::new(id, peer_iter, inner);
|
||||
self.queries.insert(id, query);
|
||||
}
|
||||
|
||||
fn next_query_id(&mut self) -> QueryId {
|
||||
let id = QueryId(self.next_id);
|
||||
self.next_id = self.next_id.wrapping_add(1);
|
||||
id
|
||||
}
|
||||
|
||||
@ -136,7 +159,7 @@ impl<TInner> QueryPool<TInner> {
|
||||
let mut waiting = None;
|
||||
|
||||
for (&query_id, query) in self.queries.iter_mut() {
|
||||
query.started = query.started.or(Some(now));
|
||||
query.stats.start = query.stats.start.or(Some(now));
|
||||
match query.next(now) {
|
||||
PeersIterState::Finished => {
|
||||
finished = Some(query_id);
|
||||
@ -148,7 +171,7 @@ impl<TInner> QueryPool<TInner> {
|
||||
break
|
||||
}
|
||||
PeersIterState::Waiting(None) | PeersIterState::WaitingAtCapacity => {
|
||||
let elapsed = now - query.started.unwrap_or(now);
|
||||
let elapsed = now - query.stats.start.unwrap_or(now);
|
||||
if elapsed >= self.config.timeout {
|
||||
timeout = Some(query_id);
|
||||
break
|
||||
@ -163,12 +186,14 @@ impl<TInner> QueryPool<TInner> {
|
||||
}
|
||||
|
||||
if let Some(query_id) = finished {
|
||||
let query = self.queries.remove(&query_id).expect("s.a.");
|
||||
let mut query = self.queries.remove(&query_id).expect("s.a.");
|
||||
query.stats.end = Some(now);
|
||||
return QueryPoolState::Finished(query)
|
||||
}
|
||||
|
||||
if let Some(query_id) = timeout {
|
||||
let query = self.queries.remove(&query_id).expect("s.a.");
|
||||
let mut query = self.queries.remove(&query_id).expect("s.a.");
|
||||
query.stats.end = Some(now);
|
||||
return QueryPoolState::Timeout(query)
|
||||
}
|
||||
|
||||
@ -206,9 +231,8 @@ pub struct Query<TInner> {
|
||||
id: QueryId,
|
||||
/// The peer iterator that drives the query state.
|
||||
peer_iter: QueryPeerIter,
|
||||
/// The instant when the query started (i.e. began waiting for the first
|
||||
/// result from a peer).
|
||||
started: Option<Instant>,
|
||||
/// Execution statistics of the query.
|
||||
stats: QueryStats,
|
||||
/// The opaque inner query state.
|
||||
pub inner: TInner,
|
||||
}
|
||||
@ -222,7 +246,7 @@ enum QueryPeerIter {
|
||||
impl<TInner> Query<TInner> {
|
||||
/// Creates a new query without starting it.
|
||||
fn new(id: QueryId, peer_iter: QueryPeerIter, inner: TInner) -> Self {
|
||||
Query { id, inner, peer_iter, started: None }
|
||||
Query { id, inner, peer_iter, stats: QueryStats::empty() }
|
||||
}
|
||||
|
||||
/// Gets the unique ID of the query.
|
||||
@ -230,11 +254,19 @@ impl<TInner> Query<TInner> {
|
||||
self.id
|
||||
}
|
||||
|
||||
/// Gets the current execution statistics of the query.
|
||||
pub fn stats(&self) -> &QueryStats {
|
||||
&self.stats
|
||||
}
|
||||
|
||||
/// Informs the query that the attempt to contact `peer` failed.
|
||||
pub fn on_failure(&mut self, peer: &PeerId) {
|
||||
match &mut self.peer_iter {
|
||||
let updated = match &mut self.peer_iter {
|
||||
QueryPeerIter::Closest(iter) => iter.on_failure(peer),
|
||||
QueryPeerIter::Fixed(iter) => iter.on_failure(peer)
|
||||
};
|
||||
if updated {
|
||||
self.stats.failure += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,9 +277,12 @@ impl<TInner> Query<TInner> {
|
||||
where
|
||||
I: IntoIterator<Item = PeerId>
|
||||
{
|
||||
match &mut self.peer_iter {
|
||||
let updated = match &mut self.peer_iter {
|
||||
QueryPeerIter::Closest(iter) => iter.on_success(peer, new_peers),
|
||||
QueryPeerIter::Fixed(iter) => iter.on_success(peer)
|
||||
};
|
||||
if updated {
|
||||
self.stats.success += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -261,10 +296,16 @@ impl<TInner> Query<TInner> {
|
||||
|
||||
/// Advances the state of the underlying peer iterator.
|
||||
fn next(&mut self, now: Instant) -> PeersIterState {
|
||||
match &mut self.peer_iter {
|
||||
let state = match &mut self.peer_iter {
|
||||
QueryPeerIter::Closest(iter) => iter.next(now),
|
||||
QueryPeerIter::Fixed(iter) => iter.next()
|
||||
};
|
||||
|
||||
if let PeersIterState::Waiting(Some(_)) = state {
|
||||
self.stats.requests += 1;
|
||||
}
|
||||
|
||||
state
|
||||
}
|
||||
|
||||
/// Finishes the query prematurely.
|
||||
@ -278,13 +319,24 @@ impl<TInner> Query<TInner> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether the query has finished.
|
||||
///
|
||||
/// A finished query is eventually reported by `QueryPool::next()` and
|
||||
/// removed from the pool.
|
||||
pub fn is_finished(&self) -> bool {
|
||||
match &self.peer_iter {
|
||||
QueryPeerIter::Closest(iter) => iter.is_finished(),
|
||||
QueryPeerIter::Fixed(iter) => iter.is_finished()
|
||||
}
|
||||
}
|
||||
|
||||
/// Consumes the query, producing the final `QueryResult`.
|
||||
pub fn into_result(self) -> QueryResult<TInner, impl Iterator<Item = PeerId>> {
|
||||
let peers = match self.peer_iter {
|
||||
QueryPeerIter::Closest(iter) => Either::Left(iter.into_result()),
|
||||
QueryPeerIter::Fixed(iter) => Either::Right(iter.into_result())
|
||||
};
|
||||
QueryResult { inner: self.inner, peers }
|
||||
QueryResult { peers, inner: self.inner, stats: self.stats }
|
||||
}
|
||||
}
|
||||
|
||||
@ -293,6 +345,90 @@ pub struct QueryResult<TInner, TPeers> {
|
||||
/// The opaque inner query state.
|
||||
pub inner: TInner,
|
||||
/// The successfully contacted peers.
|
||||
pub peers: TPeers
|
||||
pub peers: TPeers,
|
||||
/// The collected query statistics.
|
||||
pub stats: QueryStats
|
||||
}
|
||||
|
||||
/// Execution statistics of a query.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct QueryStats {
|
||||
requests: u32,
|
||||
success: u32,
|
||||
failure: u32,
|
||||
start: Option<Instant>,
|
||||
end: Option<Instant>
|
||||
}
|
||||
|
||||
impl QueryStats {
|
||||
pub fn empty() -> Self {
|
||||
QueryStats {
|
||||
requests: 0,
|
||||
success: 0,
|
||||
failure: 0,
|
||||
start: None,
|
||||
end: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the total number of requests initiated by the query.
|
||||
pub fn num_requests(&self) -> u32 {
|
||||
self.requests
|
||||
}
|
||||
|
||||
/// Gets the number of successful requests.
|
||||
pub fn num_successes(&self) -> u32 {
|
||||
self.success
|
||||
}
|
||||
|
||||
/// Gets the number of failed requests.
|
||||
pub fn num_failures(&self) -> u32 {
|
||||
self.failure
|
||||
}
|
||||
|
||||
/// Gets the number of pending requests.
|
||||
///
|
||||
/// > **Note**: A query can finish while still having pending
|
||||
/// > requests, if the termination conditions are already met.
|
||||
pub fn num_pending(&self) -> u32 {
|
||||
self.requests - (self.success + self.failure)
|
||||
}
|
||||
|
||||
/// Gets the duration of the query.
|
||||
///
|
||||
/// If the query has not yet finished, the duration is measured from the
|
||||
/// start of the query to the current instant.
|
||||
///
|
||||
/// If the query did not yet start (i.e. yield the first peer to contact),
|
||||
/// `None` is returned.
|
||||
pub fn duration(&self) -> Option<Duration> {
|
||||
if let Some(s) = self.start {
|
||||
if let Some(e) = self.end {
|
||||
Some(e - s)
|
||||
} else {
|
||||
Some(Instant::now() - s)
|
||||
}
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Merges these stats with the given stats of another query,
|
||||
/// e.g. to accumulate statistics from a multi-phase query.
|
||||
///
|
||||
/// Counters are merged cumulatively while the instants for
|
||||
/// start and end of the queries are taken as the minimum and
|
||||
/// maximum, respectively.
|
||||
pub fn merge(self, other: QueryStats) -> Self {
|
||||
QueryStats {
|
||||
requests: self.requests + other.requests,
|
||||
success: self.success + other.success,
|
||||
failure: self.failure + other.failure,
|
||||
start: match (self.start, other.start) {
|
||||
(Some(a), Some(b)) => Some(std::cmp::min(a, b)),
|
||||
(a, b) => a.or(b)
|
||||
},
|
||||
end: std::cmp::max(self.end, other.end)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -132,8 +132,7 @@ impl ClosestPeersIter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Callback for delivering the result of a successful request to a peer
|
||||
/// that the iterator is waiting on.
|
||||
/// Callback for delivering the result of a successful request to a peer.
|
||||
///
|
||||
/// Delivering results of requests back to the iterator allows the iterator to make
|
||||
/// progress. The iterator is said to make progress either when the given
|
||||
@ -141,18 +140,20 @@ impl ClosestPeersIter {
|
||||
/// or when the iterator did not yet accumulate `num_results` closest peers and
|
||||
/// `closer_peers` contains a new peer, regardless of its distance to the target.
|
||||
///
|
||||
/// After calling this function, `next` should eventually be called again
|
||||
/// to advance the state of the iterator.
|
||||
/// If the iterator is currently waiting for a result from `peer`,
|
||||
/// the iterator state is updated and `true` is returned. In that
|
||||
/// case, after calling this function, `next` should eventually be
|
||||
/// called again to obtain the new state of the iterator.
|
||||
///
|
||||
/// If the iterator is finished, it is not currently waiting for a
|
||||
/// result from `peer`, or a result for `peer` has already been reported,
|
||||
/// calling this function has no effect.
|
||||
pub fn on_success<I>(&mut self, peer: &PeerId, closer_peers: I)
|
||||
/// calling this function has no effect and `false` is returned.
|
||||
pub fn on_success<I>(&mut self, peer: &PeerId, closer_peers: I) -> bool
|
||||
where
|
||||
I: IntoIterator<Item = PeerId>
|
||||
{
|
||||
if let State::Finished = self.state {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
let key = Key::from(peer.clone());
|
||||
@ -160,7 +161,7 @@ impl ClosestPeersIter {
|
||||
|
||||
// Mark the peer as succeeded.
|
||||
match self.closest_peers.entry(distance) {
|
||||
Entry::Vacant(..) => return,
|
||||
Entry::Vacant(..) => return false,
|
||||
Entry::Occupied(mut e) => match e.get().state {
|
||||
PeerState::Waiting(..) => {
|
||||
debug_assert!(self.num_waiting > 0);
|
||||
@ -172,7 +173,7 @@ impl ClosestPeersIter {
|
||||
}
|
||||
PeerState::NotContacted
|
||||
| PeerState::Failed
|
||||
| PeerState::Succeeded => return
|
||||
| PeerState::Succeeded => return false
|
||||
}
|
||||
}
|
||||
|
||||
@ -209,28 +210,31 @@ impl ClosestPeersIter {
|
||||
State::Stalled
|
||||
}
|
||||
State::Finished => State::Finished
|
||||
}
|
||||
};
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Callback for informing the iterator about a failed request to a peer
|
||||
/// that the iterator is waiting on.
|
||||
/// Callback for informing the iterator about a failed request to a peer.
|
||||
///
|
||||
/// After calling this function, `next` should eventually be called again
|
||||
/// to advance the state of the iterator.
|
||||
/// If the iterator is currently waiting for a result from `peer`,
|
||||
/// the iterator state is updated and `true` is returned. In that
|
||||
/// case, after calling this function, `next` should eventually be
|
||||
/// called again to obtain the new state of the iterator.
|
||||
///
|
||||
/// If the iterator is finished, it is not currently waiting for a
|
||||
/// result from `peer`, or a result for `peer` has already been reported,
|
||||
/// calling this function has no effect.
|
||||
pub fn on_failure(&mut self, peer: &PeerId) {
|
||||
/// calling this function has no effect and `false` is returned.
|
||||
pub fn on_failure(&mut self, peer: &PeerId) -> bool {
|
||||
if let State::Finished = self.state {
|
||||
return
|
||||
return false
|
||||
}
|
||||
|
||||
let key = Key::from(peer.clone());
|
||||
let distance = key.distance(&self.target);
|
||||
|
||||
match self.closest_peers.entry(distance) {
|
||||
Entry::Vacant(_) => return,
|
||||
Entry::Vacant(_) => return false,
|
||||
Entry::Occupied(mut e) => match e.get().state {
|
||||
PeerState::Waiting(_) => {
|
||||
debug_assert!(self.num_waiting > 0);
|
||||
@ -240,9 +244,13 @@ impl ClosestPeersIter {
|
||||
PeerState::Unresponsive => {
|
||||
e.get_mut().set_state(PeerState::Failed);
|
||||
}
|
||||
_ => {}
|
||||
PeerState::NotContacted
|
||||
| PeerState::Failed
|
||||
| PeerState::Succeeded => return false
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
|
||||
/// Returns the list of peers for which the iterator is currently waiting
|
||||
@ -398,7 +406,7 @@ impl ClosestPeersIter {
|
||||
}
|
||||
|
||||
/// Checks whether the iterator has finished.
|
||||
pub fn finished(&self) -> bool {
|
||||
pub fn is_finished(&self) -> bool {
|
||||
self.state == State::Finished
|
||||
}
|
||||
|
||||
@ -523,25 +531,16 @@ mod tests {
|
||||
|
||||
use multihash::Multihash;
|
||||
use quickcheck::*;
|
||||
use rand::{Rng, thread_rng};
|
||||
use rand::{Rng, rngs::StdRng, SeedableRng};
|
||||
|
||||
use libp2p_core::PeerId;
|
||||
|
||||
use super::*;
|
||||
|
||||
fn random_peers(n: usize) -> impl Iterator<Item = PeerId> + Clone {
|
||||
(0 .. n).map(|_| PeerId::random())
|
||||
}
|
||||
|
||||
fn random_iter<G: Rng>(g: &mut G) -> ClosestPeersIter {
|
||||
let known_closest_peers = random_peers(g.gen_range(1, 60)).map(Key::from);
|
||||
let target = Key::from(Into::<Multihash>::into(PeerId::random()));
|
||||
let config = ClosestPeersIterConfig {
|
||||
parallelism: g.gen_range(1, 10),
|
||||
num_results: g.gen_range(1, 25),
|
||||
peer_timeout: Duration::from_secs(g.gen_range(10, 30)),
|
||||
};
|
||||
ClosestPeersIter::with_config(config, target, known_closest_peers)
|
||||
fn random_peers<R: Rng>(n: usize, g: &mut R) -> Vec<PeerId> {
|
||||
(0 .. n).map(|_| PeerId::from_multihash(
|
||||
multihash::wrap(multihash::Code::Sha2_256, &g.gen::<[u8; 32]>())
|
||||
).unwrap()).collect()
|
||||
}
|
||||
|
||||
fn sorted<T: AsRef<KeyBytes>>(target: &T, peers: &Vec<Key<PeerId>>) -> bool {
|
||||
@ -550,42 +549,63 @@ mod tests {
|
||||
|
||||
impl Arbitrary for ClosestPeersIter {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> ClosestPeersIter {
|
||||
random_iter(g)
|
||||
let known_closest_peers = random_peers(g.gen_range(1, 60), g)
|
||||
.into_iter()
|
||||
.map(Key::from);
|
||||
let target = Key::from(Into::<Multihash>::into(PeerId::random()));
|
||||
let config = ClosestPeersIterConfig {
|
||||
parallelism: g.gen_range(1, 10),
|
||||
num_results: g.gen_range(1, 25),
|
||||
peer_timeout: Duration::from_secs(g.gen_range(10, 30)),
|
||||
};
|
||||
ClosestPeersIter::with_config(config, target, known_closest_peers)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct Seed([u8; 32]);
|
||||
|
||||
impl Arbitrary for Seed {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> Seed {
|
||||
Seed(g.gen())
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_iter() {
|
||||
let iter = random_iter(&mut thread_rng());
|
||||
let target = iter.target.clone();
|
||||
fn prop(iter: ClosestPeersIter) {
|
||||
let target = iter.target.clone();
|
||||
|
||||
let (keys, states): (Vec<_>, Vec<_>) = iter.closest_peers
|
||||
.values()
|
||||
.map(|e| (e.key.clone(), &e.state))
|
||||
.unzip();
|
||||
let (keys, states): (Vec<_>, Vec<_>) = iter.closest_peers
|
||||
.values()
|
||||
.map(|e| (e.key.clone(), &e.state))
|
||||
.unzip();
|
||||
|
||||
let none_contacted = states
|
||||
.iter()
|
||||
.all(|s| match s {
|
||||
PeerState::NotContacted => true,
|
||||
_ => false
|
||||
});
|
||||
let none_contacted = states
|
||||
.iter()
|
||||
.all(|s| match s {
|
||||
PeerState::NotContacted => true,
|
||||
_ => false
|
||||
});
|
||||
|
||||
assert!(none_contacted,
|
||||
"Unexpected peer state in new iterator.");
|
||||
assert!(sorted(&target, &keys),
|
||||
"Closest peers in new iterator not sorted by distance to target.");
|
||||
assert_eq!(iter.num_waiting(), 0,
|
||||
"Unexpected peers in progress in new iterator.");
|
||||
assert_eq!(iter.into_result().count(), 0,
|
||||
"Unexpected closest peers in new iterator");
|
||||
assert!(none_contacted,
|
||||
"Unexpected peer state in new iterator.");
|
||||
assert!(sorted(&target, &keys),
|
||||
"Closest peers in new iterator not sorted by distance to target.");
|
||||
assert_eq!(iter.num_waiting(), 0,
|
||||
"Unexpected peers in progress in new iterator.");
|
||||
assert_eq!(iter.into_result().count(), 0,
|
||||
"Unexpected closest peers in new iterator");
|
||||
}
|
||||
|
||||
QuickCheck::new().tests(10).quickcheck(prop as fn(_) -> _)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn termination_and_parallelism() {
|
||||
fn prop(mut iter: ClosestPeersIter) {
|
||||
fn prop(mut iter: ClosestPeersIter, seed: Seed) {
|
||||
let now = Instant::now();
|
||||
let mut rng = thread_rng();
|
||||
let mut rng = StdRng::from_seed(seed.0);
|
||||
|
||||
let mut expected = iter.closest_peers
|
||||
.values()
|
||||
@ -632,7 +652,7 @@ mod tests {
|
||||
for (i, k) in expected.iter().enumerate() {
|
||||
if rng.gen_bool(0.75) {
|
||||
let num_closer = rng.gen_range(0, iter.config.num_results + 1);
|
||||
let closer_peers = random_peers(num_closer).collect::<Vec<_>>();
|
||||
let closer_peers = random_peers(num_closer, &mut rng);
|
||||
remaining.extend(closer_peers.iter().cloned().map(Key::from));
|
||||
iter.on_success(k.preimage(), closer_peers);
|
||||
} else {
|
||||
@ -680,14 +700,16 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
QuickCheck::new().tests(10).quickcheck(prop as fn(_) -> _)
|
||||
QuickCheck::new().tests(10).quickcheck(prop as fn(_, _) -> _)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_duplicates() {
|
||||
fn prop(mut iter: ClosestPeersIter) -> bool {
|
||||
fn prop(mut iter: ClosestPeersIter, seed: Seed) -> bool {
|
||||
let now = Instant::now();
|
||||
let closer = random_peers(1).collect::<Vec<_>>();
|
||||
let mut rng = StdRng::from_seed(seed.0);
|
||||
|
||||
let closer = random_peers(1, &mut rng);
|
||||
|
||||
// A first peer reports a "closer" peer.
|
||||
let peer1 = match iter.next(now) {
|
||||
@ -702,7 +724,7 @@ mod tests {
|
||||
match iter.next(now) {
|
||||
PeersIterState::Waiting(Some(p)) => {
|
||||
let peer2 = p.into_owned();
|
||||
iter.on_success(&peer2, closer.clone())
|
||||
assert!(iter.on_success(&peer2, closer.clone()))
|
||||
}
|
||||
PeersIterState::Finished => {}
|
||||
_ => panic!("Unexpectedly iter state."),
|
||||
@ -715,7 +737,7 @@ mod tests {
|
||||
true
|
||||
}
|
||||
|
||||
QuickCheck::new().tests(10).quickcheck(prop as fn(_) -> _)
|
||||
QuickCheck::new().tests(10).quickcheck(prop as fn(_, _) -> _)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -742,7 +764,7 @@ mod tests {
|
||||
Peer { state, .. } => panic!("Unexpected peer state: {:?}", state)
|
||||
}
|
||||
|
||||
let finished = iter.finished();
|
||||
let finished = iter.is_finished();
|
||||
iter.on_success(&peer, iter::empty());
|
||||
let closest = iter.into_result().collect::<Vec<_>>();
|
||||
|
||||
|
@ -39,6 +39,7 @@ pub struct FixedPeersIter {
|
||||
state: State,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
enum State {
|
||||
Waiting { num_waiting: usize },
|
||||
Finished
|
||||
@ -57,7 +58,12 @@ enum PeerState {
|
||||
}
|
||||
|
||||
impl FixedPeersIter {
|
||||
pub fn new(peers: Vec<PeerId>, parallelism: usize) -> Self {
|
||||
pub fn new<I>(peers: I, parallelism: usize) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = PeerId>
|
||||
{
|
||||
let peers = peers.into_iter().collect::<Vec<_>>();
|
||||
|
||||
Self {
|
||||
parallelism,
|
||||
peers: FnvHashMap::default(),
|
||||
@ -66,21 +72,46 @@ impl FixedPeersIter {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn on_success(&mut self, peer: &PeerId) {
|
||||
/// Callback for delivering the result of a successful request to a peer.
|
||||
///
|
||||
/// If the iterator is currently waiting for a result from `peer`,
|
||||
/// the iterator state is updated and `true` is returned. In that
|
||||
/// case, after calling this function, `next` should eventually be
|
||||
/// called again to obtain the new state of the iterator.
|
||||
///
|
||||
/// If the iterator is finished, it is not currently waiting for a
|
||||
/// result from `peer`, or a result for `peer` has already been reported,
|
||||
/// calling this function has no effect and `false` is returned.
|
||||
pub fn on_success(&mut self, peer: &PeerId) -> bool {
|
||||
if let State::Waiting { num_waiting } = &mut self.state {
|
||||
if let Some(state @ PeerState::Waiting) = self.peers.get_mut(peer) {
|
||||
*state = PeerState::Succeeded;
|
||||
*num_waiting -= 1;
|
||||
return true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn on_failure(&mut self, peer: &PeerId) {
|
||||
if let State::Waiting { .. } = &self.state {
|
||||
/// Callback for informing the iterator about a failed request to a peer.
|
||||
///
|
||||
/// If the iterator is currently waiting for a result from `peer`,
|
||||
/// the iterator state is updated and `true` is returned. In that
|
||||
/// case, after calling this function, `next` should eventually be
|
||||
/// called again to obtain the new state of the iterator.
|
||||
///
|
||||
/// If the iterator is finished, it is not currently waiting for a
|
||||
/// result from `peer`, or a result for `peer` has already been reported,
|
||||
/// calling this function has no effect and `false` is returned.
|
||||
pub fn on_failure(&mut self, peer: &PeerId) -> bool {
|
||||
if let State::Waiting { num_waiting } = &mut self.state {
|
||||
if let Some(state @ PeerState::Waiting) = self.peers.get_mut(peer) {
|
||||
*state = PeerState::Failed;
|
||||
*num_waiting -= 1;
|
||||
return true
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
pub fn is_waiting(&self, peer: &PeerId) -> bool {
|
||||
@ -93,6 +124,11 @@ impl FixedPeersIter {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks whether the iterator has finished.
|
||||
pub fn is_finished(&self) -> bool {
|
||||
self.state == State::Finished
|
||||
}
|
||||
|
||||
pub fn next(&mut self) -> PeersIterState {
|
||||
match &mut self.state {
|
||||
State::Finished => return PeersIterState::Finished,
|
||||
@ -133,3 +169,30 @@ impl FixedPeersIter {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn decrease_num_waiting_on_failure() {
|
||||
let mut iter = FixedPeersIter::new(vec![PeerId::random(), PeerId::random()], 1);
|
||||
|
||||
match iter.next() {
|
||||
PeersIterState::Waiting(Some(peer)) => {
|
||||
let peer = peer.into_owned();
|
||||
iter.on_failure(&peer);
|
||||
},
|
||||
_ => panic!("Expected iterator to yield peer."),
|
||||
}
|
||||
|
||||
match iter.next() {
|
||||
PeersIterState::Waiting(Some(_)) => {},
|
||||
PeersIterState::WaitingAtCapacity => panic!(
|
||||
"Expected iterator to return another peer given that the \
|
||||
previous `on_failure` call should have allowed another peer \
|
||||
to be queried.",
|
||||
),
|
||||
_ => panic!("Expected iterator to yield peer."),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "libp2p-mdns"
|
||||
edition = "2018"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
description = "Implementation of the libp2p mDNS discovery method"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
@ -16,8 +16,8 @@ dns-parser = "0.8"
|
||||
either = "1.5.3"
|
||||
futures = "0.3.1"
|
||||
lazy_static = "1.2"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.18.0", path = "../../swarm" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.19.0", path = "../../swarm" }
|
||||
log = "0.4"
|
||||
net2 = "0.2"
|
||||
rand = "0.7"
|
||||
|
@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "libp2p-noise"
|
||||
description = "Cryptographic handshake protocol using the noise framework."
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -11,7 +11,7 @@ edition = "2018"
|
||||
curve25519-dalek = "2.0.0"
|
||||
futures = "0.3.1"
|
||||
lazy_static = "1.2"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
log = "0.4"
|
||||
prost = "0.6.1"
|
||||
rand = "0.7.2"
|
||||
@ -28,7 +28,7 @@ snow = { version = "0.6.1", features = ["default-resolver"], default-features =
|
||||
|
||||
[dev-dependencies]
|
||||
env_logger = "0.7.1"
|
||||
libp2p-tcp = { version = "0.18.0", path = "../../transports/tcp" }
|
||||
libp2p-tcp = { version = "0.19.0", path = "../../transports/tcp" }
|
||||
quickcheck = "0.9.0"
|
||||
sodiumoxide = "^0.2.5"
|
||||
|
||||
|
@ -365,10 +365,10 @@ where
|
||||
|
||||
let mut payload_buf = vec![0; len];
|
||||
state.io.read_exact(&mut payload_buf).await?;
|
||||
let pb = payload_proto::Identity::decode(&payload_buf[..])?;
|
||||
let pb = payload_proto::NoiseHandshakePayload::decode(&payload_buf[..])?;
|
||||
|
||||
if !pb.pubkey.is_empty() {
|
||||
let pk = identity::PublicKey::from_protobuf_encoding(&pb.pubkey)
|
||||
if !pb.identity_key.is_empty() {
|
||||
let pk = identity::PublicKey::from_protobuf_encoding(&pb.identity_key)
|
||||
.map_err(|_| NoiseError::InvalidKey)?;
|
||||
if let Some(ref k) = state.id_remote_pubkey {
|
||||
if k != &pk {
|
||||
@ -377,8 +377,8 @@ where
|
||||
}
|
||||
state.id_remote_pubkey = Some(pk);
|
||||
}
|
||||
if !pb.signature.is_empty() {
|
||||
state.dh_remote_pubkey_sig = Some(pb.signature);
|
||||
if !pb.identity_sig.is_empty() {
|
||||
state.dh_remote_pubkey_sig = Some(pb.identity_sig);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
@ -389,12 +389,12 @@ async fn send_identity<T>(state: &mut State<T>) -> Result<(), NoiseError>
|
||||
where
|
||||
T: AsyncWrite + Unpin,
|
||||
{
|
||||
let mut pb = payload_proto::Identity::default();
|
||||
let mut pb = payload_proto::NoiseHandshakePayload::default();
|
||||
if state.send_identity {
|
||||
pb.pubkey = state.identity.public.clone().into_protobuf_encoding()
|
||||
pb.identity_key = state.identity.public.clone().into_protobuf_encoding()
|
||||
}
|
||||
if let Some(ref sig) = state.identity.signature {
|
||||
pb.signature = sig.clone()
|
||||
pb.identity_sig = sig.clone()
|
||||
}
|
||||
let mut buf = Vec::with_capacity(pb.encoded_len());
|
||||
pb.encode(&mut buf).expect("Vec<u8> provides capacity as needed");
|
||||
|
@ -4,8 +4,8 @@ package payload.proto;
|
||||
|
||||
// Payloads for Noise handshake messages.
|
||||
|
||||
message Identity {
|
||||
bytes pubkey = 1;
|
||||
bytes signature = 2;
|
||||
message NoiseHandshakePayload {
|
||||
bytes identity_key = 1;
|
||||
bytes identity_sig = 2;
|
||||
bytes data = 3;
|
||||
}
|
||||
|
||||
|
@ -27,6 +27,9 @@
|
||||
//! implementations for various noise handshake patterns (currently `IK`, `IX`, and `XX`)
|
||||
//! over a particular choice of Diffie–Hellman key agreement (currently only X25519).
|
||||
//!
|
||||
//! > **Note**: Only the `XX` handshake pattern is currently guaranteed to provide
|
||||
//! > interoperability with other libp2p implementations.
|
||||
//!
|
||||
//! All upgrades produce as output a pair, consisting of the remote's static public key
|
||||
//! and a `NoiseOutput` which represents the established cryptographic session with the
|
||||
//! remote, implementing `futures::io::AsyncRead` and `futures::io::AsyncWrite`.
|
||||
@ -38,11 +41,11 @@
|
||||
//! ```
|
||||
//! use libp2p_core::{identity, Transport, upgrade};
|
||||
//! use libp2p_tcp::TcpConfig;
|
||||
//! use libp2p_noise::{Keypair, X25519, NoiseConfig};
|
||||
//! use libp2p_noise::{Keypair, X25519Spec, NoiseConfig};
|
||||
//!
|
||||
//! # fn main() {
|
||||
//! let id_keys = identity::Keypair::generate_ed25519();
|
||||
//! let dh_keys = Keypair::<X25519>::new().into_authentic(&id_keys).unwrap();
|
||||
//! let dh_keys = Keypair::<X25519Spec>::new().into_authentic(&id_keys).unwrap();
|
||||
//! let noise = NoiseConfig::xx(dh_keys).into_authenticated();
|
||||
//! let builder = TcpConfig::new().upgrade(upgrade::Version::V1).authenticate(noise);
|
||||
//! // let transport = builder.multiplex(...);
|
||||
@ -60,7 +63,8 @@ pub use io::NoiseOutput;
|
||||
pub use io::handshake;
|
||||
pub use io::handshake::{Handshake, RemoteIdentity, IdentityExchange};
|
||||
pub use protocol::{Keypair, AuthenticKeypair, KeypairIdentity, PublicKey, SecretKey};
|
||||
pub use protocol::{Protocol, ProtocolParams, x25519::X25519, IX, IK, XX};
|
||||
pub use protocol::{Protocol, ProtocolParams, IX, IK, XX};
|
||||
pub use protocol::{x25519::X25519, x25519_spec::X25519Spec};
|
||||
|
||||
use futures::prelude::*;
|
||||
use libp2p_core::{identity, PeerId, UpgradeInfo, InboundUpgrade, OutboundUpgrade};
|
||||
|
@ -21,6 +21,7 @@
|
||||
//! Components of a Noise protocol.
|
||||
|
||||
pub mod x25519;
|
||||
pub mod x25519_spec;
|
||||
|
||||
use crate::NoiseError;
|
||||
use libp2p_core::identity;
|
||||
@ -71,6 +72,7 @@ pub trait Protocol<C> {
|
||||
///
|
||||
/// The trivial case is when the keys are byte for byte identical.
|
||||
#[allow(unused_variables)]
|
||||
#[deprecated]
|
||||
fn linked(id_pk: &identity::PublicKey, dh_pk: &PublicKey<C>) -> bool {
|
||||
false
|
||||
}
|
||||
@ -87,6 +89,7 @@ pub trait Protocol<C> {
|
||||
/// without a signature, otherwise a signature over the static DH public key
|
||||
/// must be given and is verified with the public identity key, establishing
|
||||
/// the authenticity of the static DH public key w.r.t. the public identity key.
|
||||
#[allow(deprecated)]
|
||||
fn verify(id_pk: &identity::PublicKey, dh_pk: &PublicKey<C>, sig: &Option<Vec<u8>>) -> bool
|
||||
where
|
||||
C: AsRef<[u8]>
|
||||
@ -95,6 +98,13 @@ pub trait Protocol<C> {
|
||||
||
|
||||
sig.as_ref().map_or(false, |s| id_pk.verify(dh_pk.as_ref(), s))
|
||||
}
|
||||
|
||||
fn sign(id_keys: &identity::Keypair, dh_pk: &PublicKey<C>) -> Result<Vec<u8>, NoiseError>
|
||||
where
|
||||
C: AsRef<[u8]>
|
||||
{
|
||||
Ok(id_keys.sign(dh_pk.as_ref())?)
|
||||
}
|
||||
}
|
||||
|
||||
/// DH keypair.
|
||||
@ -151,9 +161,10 @@ impl<T: Zeroize> Keypair<T> {
|
||||
/// is authentic w.r.t. the given identity keypair, by signing the DH public key.
|
||||
pub fn into_authentic(self, id_keys: &identity::Keypair) -> Result<AuthenticKeypair<T>, NoiseError>
|
||||
where
|
||||
T: AsRef<[u8]>
|
||||
T: AsRef<[u8]>,
|
||||
T: Protocol<T>
|
||||
{
|
||||
let sig = id_keys.sign(self.public.as_ref())?;
|
||||
let sig = T::sign(id_keys, &self.public)?;
|
||||
|
||||
let identity = KeypairIdentity {
|
||||
public: id_keys.public(),
|
||||
|
@ -18,7 +18,10 @@
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
//! Noise protocols based on X25519.
|
||||
//! Legacy Noise protocols based on X25519.
|
||||
//!
|
||||
//! **Note**: This set of protocols is not interoperable with other
|
||||
//! libp2p implementations.
|
||||
|
||||
use crate::{NoiseConfig, NoiseError, Protocol, ProtocolParams};
|
||||
use curve25519_dalek::edwards::CompressedEdwardsY;
|
||||
@ -92,7 +95,11 @@ impl<R> UpgradeInfo for NoiseConfig<IK, X25519, R> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Noise protocols for X25519.
|
||||
/// Legacy Noise protocol for X25519.
|
||||
///
|
||||
/// **Note**: This `Protocol` provides no configuration that
|
||||
/// is interoperable with other libp2p implementations.
|
||||
/// See [`crate::X25519Spec`] instead.
|
||||
impl Protocol<X25519> for X25519 {
|
||||
fn params_ik() -> ProtocolParams {
|
||||
PARAMS_IK.clone()
|
||||
|
150
protocols/noise/src/protocol/x25519_spec.rs
Normal file
150
protocols/noise/src/protocol/x25519_spec.rs
Normal file
@ -0,0 +1,150 @@
|
||||
// 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.
|
||||
|
||||
//! [libp2p-noise-spec] compliant Noise protocols based on X25519.
|
||||
//!
|
||||
//! [libp2p-noise-spec]: https://github.com/libp2p/specs/tree/master/noise
|
||||
|
||||
use crate::{NoiseConfig, NoiseError, Protocol, ProtocolParams};
|
||||
use libp2p_core::UpgradeInfo;
|
||||
use libp2p_core::identity;
|
||||
use rand::Rng;
|
||||
use x25519_dalek::{X25519_BASEPOINT_BYTES, x25519};
|
||||
use zeroize::Zeroize;
|
||||
|
||||
use super::{*, x25519::X25519};
|
||||
|
||||
/// Prefix of static key signatures for domain separation.
|
||||
const STATIC_KEY_DOMAIN: &str = "noise-libp2p-static-key:";
|
||||
|
||||
/// A X25519 key.
|
||||
#[derive(Clone)]
|
||||
pub struct X25519Spec([u8; 32]);
|
||||
|
||||
impl AsRef<[u8]> for X25519Spec {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
self.0.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
impl Zeroize for X25519Spec {
|
||||
fn zeroize(&mut self) {
|
||||
self.0.zeroize()
|
||||
}
|
||||
}
|
||||
|
||||
impl Keypair<X25519Spec> {
|
||||
/// Create a new X25519 keypair.
|
||||
pub fn new() -> Keypair<X25519Spec> {
|
||||
let mut sk_bytes = [0u8; 32];
|
||||
rand::thread_rng().fill(&mut sk_bytes);
|
||||
let sk = SecretKey(X25519Spec(sk_bytes)); // Copy
|
||||
sk_bytes.zeroize();
|
||||
Self::from(sk)
|
||||
}
|
||||
}
|
||||
|
||||
/// Promote a X25519 secret key into a keypair.
|
||||
impl From<SecretKey<X25519Spec>> for Keypair<X25519Spec> {
|
||||
fn from(secret: SecretKey<X25519Spec>) -> Keypair<X25519Spec> {
|
||||
let public = PublicKey(X25519Spec(x25519((secret.0).0, X25519_BASEPOINT_BYTES)));
|
||||
Keypair { secret, public }
|
||||
}
|
||||
}
|
||||
|
||||
impl UpgradeInfo for NoiseConfig<XX, X25519Spec> {
|
||||
type Info = &'static [u8];
|
||||
type InfoIter = std::iter::Once<Self::Info>;
|
||||
|
||||
fn protocol_info(&self) -> Self::InfoIter {
|
||||
std::iter::once(b"/noise")
|
||||
}
|
||||
}
|
||||
|
||||
/// Noise protocols for X25519 with libp2p-spec compliant signatures.
|
||||
///
|
||||
/// **Note**: Only the XX handshake pattern is currently guaranteed to be
|
||||
/// interoperable with other libp2p implementations.
|
||||
impl Protocol<X25519Spec> for X25519Spec {
|
||||
fn params_ik() -> ProtocolParams {
|
||||
X25519::params_ik()
|
||||
}
|
||||
|
||||
fn params_ix() -> ProtocolParams {
|
||||
X25519::params_ix()
|
||||
}
|
||||
|
||||
fn params_xx() -> ProtocolParams {
|
||||
X25519::params_xx()
|
||||
}
|
||||
|
||||
fn public_from_bytes(bytes: &[u8]) -> Result<PublicKey<X25519Spec>, NoiseError> {
|
||||
if bytes.len() != 32 {
|
||||
return Err(NoiseError::InvalidKey)
|
||||
}
|
||||
let mut pk = [0u8; 32];
|
||||
pk.copy_from_slice(bytes);
|
||||
Ok(PublicKey(X25519Spec(pk)))
|
||||
}
|
||||
|
||||
fn verify(id_pk: &identity::PublicKey, dh_pk: &PublicKey<X25519Spec>, sig: &Option<Vec<u8>>) -> bool
|
||||
{
|
||||
sig.as_ref().map_or(false, |s| {
|
||||
id_pk.verify(&[STATIC_KEY_DOMAIN.as_bytes(), dh_pk.as_ref()].concat(), s)
|
||||
})
|
||||
}
|
||||
|
||||
fn sign(id_keys: &identity::Keypair, dh_pk: &PublicKey<X25519Spec>) -> Result<Vec<u8>, NoiseError> {
|
||||
Ok(id_keys.sign(&[STATIC_KEY_DOMAIN.as_bytes(), dh_pk.as_ref()].concat())?)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl snow::types::Dh for Keypair<X25519Spec> {
|
||||
fn name(&self) -> &'static str { "25519" }
|
||||
fn pub_len(&self) -> usize { 32 }
|
||||
fn priv_len(&self) -> usize { 32 }
|
||||
fn pubkey(&self) -> &[u8] { self.public.as_ref() }
|
||||
fn privkey(&self) -> &[u8] { self.secret.as_ref() }
|
||||
|
||||
fn set(&mut self, sk: &[u8]) {
|
||||
let mut secret = [0u8; 32];
|
||||
secret.copy_from_slice(&sk[..]);
|
||||
self.secret = SecretKey(X25519Spec(secret)); // Copy
|
||||
self.public = PublicKey(X25519Spec(x25519(secret, X25519_BASEPOINT_BYTES)));
|
||||
secret.zeroize();
|
||||
}
|
||||
|
||||
fn generate(&mut self, rng: &mut dyn snow::types::Random) {
|
||||
let mut secret = [0u8; 32];
|
||||
rng.fill_bytes(&mut secret);
|
||||
self.secret = SecretKey(X25519Spec(secret)); // Copy
|
||||
self.public = PublicKey(X25519Spec(x25519(secret, X25519_BASEPOINT_BYTES)));
|
||||
secret.zeroize();
|
||||
}
|
||||
|
||||
fn dh(&self, pk: &[u8], shared_secret: &mut [u8]) -> Result<(), ()> {
|
||||
let mut p = [0; 32];
|
||||
p.copy_from_slice(&pk[.. 32]);
|
||||
let ss = x25519((self.secret.0).0, p);
|
||||
shared_secret[.. 32].copy_from_slice(&ss[..]);
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ use futures::{future::{self, Either}, prelude::*};
|
||||
use libp2p_core::identity;
|
||||
use libp2p_core::upgrade::{self, Negotiated, apply_inbound, apply_outbound};
|
||||
use libp2p_core::transport::{Transport, ListenerEvent};
|
||||
use libp2p_noise::{Keypair, X25519, NoiseConfig, RemoteIdentity, NoiseError, NoiseOutput};
|
||||
use libp2p_noise::{Keypair, X25519, X25519Spec, NoiseConfig, RemoteIdentity, NoiseError, NoiseOutput};
|
||||
use libp2p_tcp::{TcpConfig, TcpTransStream};
|
||||
use log::info;
|
||||
use quickcheck::QuickCheck;
|
||||
@ -38,6 +38,37 @@ fn core_upgrade_compat() {
|
||||
let _ = TcpConfig::new().upgrade(upgrade::Version::V1).authenticate(noise);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xx_spec() {
|
||||
let _ = env_logger::try_init();
|
||||
fn prop(mut messages: Vec<Message>) -> bool {
|
||||
messages.truncate(5);
|
||||
let server_id = identity::Keypair::generate_ed25519();
|
||||
let client_id = identity::Keypair::generate_ed25519();
|
||||
|
||||
let server_id_public = server_id.public();
|
||||
let client_id_public = client_id.public();
|
||||
|
||||
let server_dh = Keypair::<X25519Spec>::new().into_authentic(&server_id).unwrap();
|
||||
let server_transport = TcpConfig::new()
|
||||
.and_then(move |output, endpoint| {
|
||||
upgrade::apply(output, NoiseConfig::xx(server_dh), endpoint, upgrade::Version::V1)
|
||||
})
|
||||
.and_then(move |out, _| expect_identity(out, &client_id_public));
|
||||
|
||||
let client_dh = Keypair::<X25519Spec>::new().into_authentic(&client_id).unwrap();
|
||||
let client_transport = TcpConfig::new()
|
||||
.and_then(move |output, endpoint| {
|
||||
upgrade::apply(output, NoiseConfig::xx(client_dh), endpoint, upgrade::Version::V1)
|
||||
})
|
||||
.and_then(move |out, _| expect_identity(out, &server_id_public));
|
||||
|
||||
run(server_transport, client_transport, messages);
|
||||
true
|
||||
}
|
||||
QuickCheck::new().max_tests(30).quickcheck(prop as fn(Vec<Message>) -> bool)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xx() {
|
||||
let _ = env_logger::try_init();
|
||||
@ -144,15 +175,15 @@ fn ik_xx() {
|
||||
QuickCheck::new().max_tests(30).quickcheck(prop as fn(Vec<Message>) -> bool)
|
||||
}
|
||||
|
||||
type Output = (RemoteIdentity<X25519>, NoiseOutput<Negotiated<TcpTransStream>>);
|
||||
type Output<C> = (RemoteIdentity<C>, NoiseOutput<Negotiated<TcpTransStream>>);
|
||||
|
||||
fn run<T, U, I>(server_transport: T, client_transport: U, messages: I)
|
||||
fn run<T, U, I, C>(server_transport: T, client_transport: U, messages: I)
|
||||
where
|
||||
T: Transport<Output = Output>,
|
||||
T: Transport<Output = Output<C>>,
|
||||
T::Dial: Send + 'static,
|
||||
T::Listener: Send + Unpin + 'static,
|
||||
T::ListenerUpgrade: Send + 'static,
|
||||
U: Transport<Output = Output>,
|
||||
U: Transport<Output = Output<C>>,
|
||||
U::Dial: Send + 'static,
|
||||
U::Listener: Send + 'static,
|
||||
U::ListenerUpgrade: Send + 'static,
|
||||
@ -218,8 +249,8 @@ where
|
||||
})
|
||||
}
|
||||
|
||||
fn expect_identity(output: Output, pk: &identity::PublicKey)
|
||||
-> impl Future<Output = Result<Output, NoiseError>>
|
||||
fn expect_identity<C>(output: Output<C>, pk: &identity::PublicKey)
|
||||
-> impl Future<Output = Result<Output<C>, NoiseError>>
|
||||
{
|
||||
match output.0 {
|
||||
RemoteIdentity::IdentityKey(ref k) if k == pk => future::ok(output),
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-ping"
|
||||
edition = "2018"
|
||||
description = "Ping protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -11,8 +11,8 @@ categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.1"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.18.0", path = "../../swarm" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
libp2p-swarm = { version = "0.19.0", path = "../../swarm" }
|
||||
log = "0.4.1"
|
||||
rand = "0.7.2"
|
||||
void = "1.0"
|
||||
@ -20,7 +20,7 @@ wasm-timer = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
async-std = "1.0"
|
||||
libp2p-tcp = { version = "0.18.0", path = "../../transports/tcp" }
|
||||
libp2p-secio = { version = "0.18.0", path = "../../protocols/secio" }
|
||||
libp2p-yamux = { version = "0.18.0", path = "../../muxers/yamux" }
|
||||
libp2p-tcp = { version = "0.19.0", path = "../../transports/tcp" }
|
||||
libp2p-secio = { version = "0.19.0", path = "../../protocols/secio" }
|
||||
libp2p-yamux = { version = "0.19.0", path = "../../muxers/yamux" }
|
||||
quickcheck = "0.9.0"
|
||||
|
@ -83,7 +83,7 @@ where
|
||||
type Future = BoxFuture<'static, Result<Duration, io::Error>>;
|
||||
|
||||
fn upgrade_outbound(self, mut socket: TSocket, _: Self::Info) -> Self::Future {
|
||||
let payload: [u8; 32] = thread_rng().sample(distributions::Standard);
|
||||
let payload: [u8; PING_SIZE] = thread_rng().sample(distributions::Standard);
|
||||
debug!("Preparing ping payload {:?}", payload);
|
||||
async move {
|
||||
socket.write_all(&payload).await?;
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-plaintext"
|
||||
edition = "2018"
|
||||
description = "Plaintext encryption dummy protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -13,7 +13,7 @@ categories = ["network-programming", "asynchronous"]
|
||||
bytes = "0.5"
|
||||
futures = "0.3.1"
|
||||
futures_codec = "0.3.4"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
log = "0.4.8"
|
||||
prost = "0.6.1"
|
||||
rw-stream-sink = "0.2.0"
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-pnet"
|
||||
edition = "2018"
|
||||
description = "Private swarm support for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
|
@ -55,6 +55,11 @@ const FINGERPRINT_SIZE: usize = 16;
|
||||
pub struct PreSharedKey([u8; KEY_SIZE]);
|
||||
|
||||
impl PreSharedKey {
|
||||
/// Create a new pre shared key from raw bytes
|
||||
pub fn new(data: [u8; KEY_SIZE]) -> Self {
|
||||
Self(data)
|
||||
}
|
||||
|
||||
/// Compute PreSharedKey fingerprint identical to the go-libp2p fingerprint.
|
||||
/// The computation of the fingerprint is not specified in the spec.
|
||||
///
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-secio"
|
||||
edition = "2018"
|
||||
description = "Secio encryption protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -16,7 +16,7 @@ ctr = "0.3"
|
||||
futures = "0.3.1"
|
||||
hmac = "0.7.0"
|
||||
lazy_static = "1.2.0"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
log = "0.4.6"
|
||||
prost = "0.6.1"
|
||||
pin-project = "0.4.6"
|
||||
@ -48,8 +48,8 @@ aes-all = ["aesni"]
|
||||
[dev-dependencies]
|
||||
async-std = "1.0"
|
||||
criterion = "0.3"
|
||||
libp2p-mplex = { version = "0.18.0", path = "../../muxers/mplex" }
|
||||
libp2p-tcp = { version = "0.18.0", path = "../../transports/tcp" }
|
||||
libp2p-mplex = { version = "0.19.0", path = "../../muxers/mplex" }
|
||||
libp2p-tcp = { version = "0.19.0", path = "../../transports/tcp" }
|
||||
|
||||
[[bench]]
|
||||
name = "bench"
|
||||
|
30
src/lib.rs
30
src/lib.rs
@ -85,7 +85,7 @@
|
||||
//! Example ([`secio`] + [`yamux`] Protocol Upgrade):
|
||||
//!
|
||||
//! ```rust
|
||||
//! # #[cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp", feature = "secio", feature = "yamux"))] {
|
||||
//! # #[cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp-async-std", feature = "secio", feature = "yamux"))] {
|
||||
//! use libp2p::{Transport, core::upgrade, tcp::TcpConfig, secio::SecioConfig, identity::Keypair, yamux};
|
||||
//! let tcp = TcpConfig::new();
|
||||
//! let secio = SecioConfig::new(Keypair::generate_ed25519());
|
||||
@ -217,8 +217,8 @@ pub use libp2p_plaintext as plaintext;
|
||||
pub use libp2p_secio as secio;
|
||||
#[doc(inline)]
|
||||
pub use libp2p_swarm as swarm;
|
||||
#[cfg(feature = "tcp")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "tcp")))]
|
||||
#[cfg(any(feature = "tcp-async-std", feature = "tcp-tokio-std"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "tcp-async-std", feature = "tcp-tokio-std"))))]
|
||||
#[cfg(not(any(target_os = "emscripten", target_os = "unknown")))]
|
||||
#[doc(inline)]
|
||||
pub use libp2p_tcp as tcp;
|
||||
@ -262,16 +262,14 @@ pub use self::simple::SimpleProtocol;
|
||||
pub use self::swarm::Swarm;
|
||||
pub use self::transport_ext::TransportExt;
|
||||
|
||||
use std::{error, io, time::Duration};
|
||||
|
||||
/// Builds a `Transport` that supports the most commonly-used protocols that libp2p supports.
|
||||
///
|
||||
/// > **Note**: This `Transport` is not suitable for production usage, as its implementation
|
||||
/// > reserves the right to support additional protocols or remove deprecated protocols.
|
||||
#[cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))))]
|
||||
#[cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp-async-std", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp-async-std", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))))]
|
||||
pub fn build_development_transport(keypair: identity::Keypair)
|
||||
-> io::Result<impl Transport<Output = (PeerId, impl core::muxing::StreamMuxer<OutboundSubstream = impl Send, Substream = impl Send, Error = impl Into<io::Error>> + Send + Sync), Error = impl error::Error + Send, Listener = impl Send, Dial = impl Send, ListenerUpgrade = impl Send> + Clone>
|
||||
-> std::io::Result<impl Transport<Output = (PeerId, impl core::muxing::StreamMuxer<OutboundSubstream = impl Send, Substream = impl Send, Error = impl Into<std::io::Error>> + Send + Sync), Error = impl std::error::Error + Send, Listener = impl Send, Dial = impl Send, ListenerUpgrade = impl Send> + Clone>
|
||||
{
|
||||
build_tcp_ws_secio_mplex_yamux(keypair)
|
||||
}
|
||||
@ -282,10 +280,10 @@ pub fn build_development_transport(keypair: identity::Keypair)
|
||||
/// and mplex or yamux as the multiplexing layer.
|
||||
///
|
||||
/// > **Note**: If you ever need to express the type of this `Transport`.
|
||||
#[cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))))]
|
||||
#[cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp-async-std", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp-async-std", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux"))))]
|
||||
pub fn build_tcp_ws_secio_mplex_yamux(keypair: identity::Keypair)
|
||||
-> io::Result<impl Transport<Output = (PeerId, impl core::muxing::StreamMuxer<OutboundSubstream = impl Send, Substream = impl Send, Error = impl Into<io::Error>> + Send + Sync), Error = impl error::Error + Send, Listener = impl Send, Dial = impl Send, ListenerUpgrade = impl Send> + Clone>
|
||||
-> std::io::Result<impl Transport<Output = (PeerId, impl core::muxing::StreamMuxer<OutboundSubstream = impl Send, Substream = impl Send, Error = impl Into<std::io::Error>> + Send + Sync), Error = impl std::error::Error + Send, Listener = impl Send, Dial = impl Send, ListenerUpgrade = impl Send> + Clone>
|
||||
{
|
||||
let transport = {
|
||||
let tcp = tcp::TcpConfig::new().nodelay(true);
|
||||
@ -299,7 +297,7 @@ pub fn build_tcp_ws_secio_mplex_yamux(keypair: identity::Keypair)
|
||||
.authenticate(secio::SecioConfig::new(keypair))
|
||||
.multiplex(core::upgrade::SelectUpgrade::new(yamux::Config::default(), mplex::MplexConfig::new()))
|
||||
.map(|(peer, muxer), _| (peer, core::muxing::StreamMuxerBox::new(muxer)))
|
||||
.timeout(Duration::from_secs(20)))
|
||||
.timeout(std::time::Duration::from_secs(20)))
|
||||
}
|
||||
|
||||
/// Builds an implementation of `Transport` that is suitable for usage with the `Swarm`.
|
||||
@ -308,10 +306,10 @@ pub fn build_tcp_ws_secio_mplex_yamux(keypair: identity::Keypair)
|
||||
/// and mplex or yamux as the multiplexing layer.
|
||||
///
|
||||
/// > **Note**: If you ever need to express the type of this `Transport`.
|
||||
#[cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux", feature = "pnet"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux", feature = "pnet"))))]
|
||||
#[cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp-async-std", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux", feature = "pnet"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(not(any(target_os = "emscripten", target_os = "unknown")), feature = "tcp-async-std", feature = "websocket", feature = "secio", feature = "mplex", feature = "yamux", feature = "pnet"))))]
|
||||
pub fn build_tcp_ws_pnet_secio_mplex_yamux(keypair: identity::Keypair, psk: PreSharedKey)
|
||||
-> io::Result<impl Transport<Output = (PeerId, impl core::muxing::StreamMuxer<OutboundSubstream = impl Send, Substream = impl Send, Error = impl Into<io::Error>> + Send + Sync), Error = impl error::Error + Send, Listener = impl Send, Dial = impl Send, ListenerUpgrade = impl Send> + Clone>
|
||||
-> std::io::Result<impl Transport<Output = (PeerId, impl core::muxing::StreamMuxer<OutboundSubstream = impl Send, Substream = impl Send, Error = impl Into<std::io::Error>> + Send + Sync), Error = impl std::error::Error + Send, Listener = impl Send, Dial = impl Send, ListenerUpgrade = impl Send> + Clone>
|
||||
{
|
||||
let transport = {
|
||||
let tcp = tcp::TcpConfig::new().nodelay(true);
|
||||
@ -326,5 +324,5 @@ pub fn build_tcp_ws_pnet_secio_mplex_yamux(keypair: identity::Keypair, psk: PreS
|
||||
.authenticate(secio::SecioConfig::new(keypair))
|
||||
.multiplex(core::upgrade::SelectUpgrade::new(yamux::Config::default(), mplex::MplexConfig::new()))
|
||||
.map(|(peer, muxer), _| (peer, core::muxing::StreamMuxerBox::new(muxer)))
|
||||
.timeout(Duration::from_secs(20)))
|
||||
.timeout(std::time::Duration::from_secs(20)))
|
||||
}
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-swarm"
|
||||
edition = "2018"
|
||||
description = "The libp2p swarm"
|
||||
version = "0.18.1"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -11,7 +11,7 @@ categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
futures = "0.3.1"
|
||||
libp2p-core = { version = "0.18.0", path = "../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../core" }
|
||||
log = "0.4"
|
||||
rand = "0.7"
|
||||
smallvec = "1.0"
|
||||
@ -19,6 +19,6 @@ wasm-timer = "0.2"
|
||||
void = "1"
|
||||
|
||||
[dev-dependencies]
|
||||
libp2p-mplex = { version = "0.18.0", path = "../muxers/mplex" }
|
||||
libp2p-mplex = { version = "0.19.0", path = "../muxers/mplex" }
|
||||
quickcheck = "0.9.0"
|
||||
rand = "0.7.2"
|
||||
|
@ -291,12 +291,10 @@ pub enum DialPeerCondition {
|
||||
/// If there is an ongoing dialing attempt, the addresses reported by
|
||||
/// [`NetworkBehaviour::addresses_of_peer`] are added to the ongoing
|
||||
/// dialing attempt, ignoring duplicates.
|
||||
///
|
||||
/// This condition implies [`DialPeerCondition::Disconnected`].
|
||||
NotDialing,
|
||||
// TODO: Once multiple dialing attempts per peer are permitted.
|
||||
// See https://github.com/libp2p/rust-libp2p/pull/1506.
|
||||
// Always,
|
||||
/// A new dialing attempt is always initiated, only subject to the
|
||||
/// configured connection limits.
|
||||
Always,
|
||||
}
|
||||
|
||||
impl Default for DialPeerCondition {
|
||||
|
167
swarm/src/lib.rs
167
swarm/src/lib.rs
@ -115,7 +115,6 @@ use libp2p_core::{
|
||||
NetworkInfo,
|
||||
NetworkEvent,
|
||||
NetworkConfig,
|
||||
Peer,
|
||||
peer::ConnectedPeer,
|
||||
},
|
||||
upgrade::ProtocolName,
|
||||
@ -124,7 +123,7 @@ use registry::{Addresses, AddressIntoIter};
|
||||
use smallvec::SmallVec;
|
||||
use std::{error, fmt, hash::Hash, io, ops::{Deref, DerefMut}, pin::Pin, task::{Context, Poll}};
|
||||
use std::collections::HashSet;
|
||||
use std::num::NonZeroU32;
|
||||
use std::num::{NonZeroU32, NonZeroUsize};
|
||||
use upgrade::UpgradeInfoSend as _;
|
||||
|
||||
/// Contains the state of the network, plus the way it should behave.
|
||||
@ -379,70 +378,31 @@ where TBehaviour: NetworkBehaviour<ProtocolsHandler = THandler>,
|
||||
///
|
||||
/// If a new dialing attempt has been initiated, `Ok(true)` is returned.
|
||||
///
|
||||
/// If there is an ongoing dialing attempt, the current addresses of the
|
||||
/// peer, as reported by [`NetworkBehaviour::addresses_of_peer`] are added
|
||||
/// to the ongoing dialing attempt, ignoring duplicates. In this case no
|
||||
/// new dialing attempt is initiated.
|
||||
///
|
||||
/// If no new dialing attempt has been initiated, meaning there is an ongoing
|
||||
/// dialing attempt or `addresses_of_peer` reports no addresses, `Ok(false)`
|
||||
/// is returned.
|
||||
pub fn dial(me: &mut Self, peer_id: &PeerId) -> Result<bool, ConnectionLimit> {
|
||||
pub fn dial(me: &mut Self, peer_id: &PeerId) -> Result<(), DialError> {
|
||||
let mut addrs = me.behaviour.addresses_of_peer(peer_id).into_iter();
|
||||
match me.network.peer(peer_id.clone()) {
|
||||
Peer::Disconnected(peer) => {
|
||||
if let Some(first) = addrs.next() {
|
||||
let handler = me.behaviour.new_handler().into_node_handler_builder();
|
||||
match peer.connect(first, addrs, handler) {
|
||||
Ok(_) => return Ok(true),
|
||||
Err(error) => {
|
||||
log::debug!(
|
||||
"New dialing attempt to disconnected peer {:?} failed: {:?}.",
|
||||
peer_id, error);
|
||||
me.behaviour.inject_dial_failure(&peer_id);
|
||||
return Err(error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!(
|
||||
"New dialing attempt to disconnected peer {:?} failed: no address.",
|
||||
peer_id
|
||||
);
|
||||
me.behaviour.inject_dial_failure(&peer_id);
|
||||
}
|
||||
Ok(false)
|
||||
},
|
||||
Peer::Connected(peer) => {
|
||||
if let Some(first) = addrs.next() {
|
||||
let handler = me.behaviour.new_handler().into_node_handler_builder();
|
||||
match peer.connect(first, addrs, handler) {
|
||||
Ok(_) => return Ok(true),
|
||||
Err(error) => {
|
||||
log::debug!(
|
||||
"New dialing attempt to connected peer {:?} failed: {:?}.",
|
||||
peer_id, error);
|
||||
me.behaviour.inject_dial_failure(&peer_id);
|
||||
return Err(error)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log::debug!(
|
||||
"New dialing attempt to disconnected peer {:?} failed: no address.",
|
||||
peer_id
|
||||
);
|
||||
me.behaviour.inject_dial_failure(&peer_id);
|
||||
}
|
||||
Ok(false)
|
||||
}
|
||||
Peer::Dialing(mut peer) => {
|
||||
peer.connection().add_addresses(addrs);
|
||||
Ok(false)
|
||||
},
|
||||
Peer::Local => {
|
||||
me.behaviour.inject_dial_failure(&peer_id);
|
||||
Err(ConnectionLimit { current: 0, limit: 0 })
|
||||
}
|
||||
let peer = me.network.peer(peer_id.clone());
|
||||
|
||||
let result =
|
||||
if let Some(first) = addrs.next() {
|
||||
let handler = me.behaviour.new_handler().into_node_handler_builder();
|
||||
peer.dial(first, addrs, handler)
|
||||
.map(|_| ())
|
||||
.map_err(DialError::ConnectionLimit)
|
||||
} else {
|
||||
Err(DialError::NoAddresses)
|
||||
};
|
||||
|
||||
if let Err(error) = &result {
|
||||
log::debug!(
|
||||
"New dialing attempt to peer {:?} failed: {:?}.",
|
||||
peer_id, error);
|
||||
me.behaviour.inject_dial_failure(&peer_id);
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Returns an iterator that produces the list of addresses we're listening on.
|
||||
@ -721,18 +681,22 @@ where TBehaviour: NetworkBehaviour<ProtocolsHandler = THandler>,
|
||||
if !this.network.is_dialing(&peer_id) => true,
|
||||
_ => false
|
||||
};
|
||||
|
||||
if condition_matched {
|
||||
if let Ok(true) = ExpandedSwarm::dial(this, &peer_id) {
|
||||
return Poll::Ready(SwarmEvent::Dialing(peer_id));
|
||||
if ExpandedSwarm::dial(this, &peer_id).is_ok() {
|
||||
return Poll::Ready(SwarmEvent::Dialing(peer_id))
|
||||
}
|
||||
|
||||
} else {
|
||||
// Even if the condition for a _new_ dialing attempt is not met,
|
||||
// we always add any potentially new addresses of the peer to an
|
||||
// ongoing dialing attempt, if there is one.
|
||||
log::trace!("Condition for new dialing attempt to {:?} not met: {:?}",
|
||||
peer_id, condition);
|
||||
if let Some(mut peer) = this.network.peer(peer_id.clone()).into_dialing() {
|
||||
let addrs = this.behaviour.addresses_of_peer(peer.id());
|
||||
peer.connection().add_addresses(addrs);
|
||||
let mut attempt = peer.some_attempt();
|
||||
for addr in addrs {
|
||||
attempt.add_address(addr);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1038,6 +1002,48 @@ where TBehaviour: NetworkBehaviour,
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the number of events from the [`NetworkBehaviour`] in
|
||||
/// destination to the [`ProtocolsHandler`] that can be buffered before
|
||||
/// the [`Swarm`] has to wait. An individual buffer with this number of
|
||||
/// events exists for each individual connection.
|
||||
///
|
||||
/// The ideal value depends on the executor used, the CPU speed, and the
|
||||
/// volume of events. If this value is too low, then the [`Swarm`] will
|
||||
/// be sleeping more often than necessary. Increasing this value increases
|
||||
/// the overall memory usage.
|
||||
pub fn notify_handler_buffer_size(mut self, n: NonZeroUsize) -> Self {
|
||||
self.network_config.set_notify_handler_buffer_size(n);
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures the number of extra events from the [`ProtocolsHandler`] in
|
||||
/// destination to the [`NetworkBehaviour`] that can be buffered before
|
||||
/// the [`ProtocolsHandler`] has to go to sleep.
|
||||
///
|
||||
/// There exists a buffer of events received from [`ProtocolsHandler`]s
|
||||
/// that the [`NetworkBehaviour`] has yet to process. This buffer is
|
||||
/// shared between all instances of [`ProtocolsHandler`]. Each instance of
|
||||
/// [`ProtocolsHandler`] is guaranteed one slot in this buffer, meaning
|
||||
/// that delivering an event for the first time is guaranteed to be
|
||||
/// instantaneous. Any extra event delivery, however, must wait for that
|
||||
/// first event to be delivered or for an "extra slot" to be available.
|
||||
///
|
||||
/// This option configures the number of such "extra slots" in this
|
||||
/// shared buffer. These extra slots are assigned in a first-come,
|
||||
/// first-served basis.
|
||||
///
|
||||
/// The ideal value depends on the executor used, the CPU speed, the
|
||||
/// average number of connections, and the volume of events. If this value
|
||||
/// is too low, then the [`ProtocolsHandler`]s will be sleeping more often
|
||||
/// than necessary. Increasing this value increases the overall memory
|
||||
/// usage, and more importantly the latency between the moment when an
|
||||
/// event is emitted and the moment when it is received by the
|
||||
/// [`NetworkBehaviour`].
|
||||
pub fn connection_event_buffer_size(mut self, n: usize) -> Self {
|
||||
self.network_config.set_connection_event_buffer_size(n);
|
||||
self
|
||||
}
|
||||
|
||||
/// Configures a limit for the number of simultaneous incoming
|
||||
/// connection attempts.
|
||||
pub fn incoming_connection_limit(mut self, n: usize) -> Self {
|
||||
@ -1104,6 +1110,35 @@ where TBehaviour: NetworkBehaviour,
|
||||
}
|
||||
}
|
||||
|
||||
/// The possible failures of [`ExpandedSwarm::dial`].
|
||||
#[derive(Debug)]
|
||||
pub enum DialError {
|
||||
/// The configured limit for simultaneous outgoing connections
|
||||
/// has been reached.
|
||||
ConnectionLimit(ConnectionLimit),
|
||||
/// [`NetworkBehaviour::addresses_of_peer`] returned no addresses
|
||||
/// for the peer to dial.
|
||||
NoAddresses
|
||||
}
|
||||
|
||||
impl fmt::Display for DialError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
DialError::ConnectionLimit(err) => write!(f, "Dial error: {}", err),
|
||||
DialError::NoAddresses => write!(f, "Dial error: no addresses for peer.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl error::Error for DialError {
|
||||
fn source(&self) -> Option<&(dyn error::Error + 'static)> {
|
||||
match self {
|
||||
DialError::ConnectionLimit(err) => Some(err),
|
||||
DialError::NoAddresses => None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Dummy implementation of [`NetworkBehaviour`] that doesn't do anything.
|
||||
#[derive(Clone, Default)]
|
||||
pub struct DummyBehaviour {
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-dns"
|
||||
edition = "2018"
|
||||
description = "DNS transport implementation for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -10,6 +10,6 @@ keywords = ["peer-to-peer", "libp2p", "networking"]
|
||||
categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[dependencies]
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
log = "0.4.1"
|
||||
futures = "0.3.1"
|
||||
|
@ -124,6 +124,7 @@ where
|
||||
// As an optimization, we immediately pass through if no component of the address contain
|
||||
// a DNS protocol.
|
||||
let contains_dns = addr.iter().any(|cmp| match cmp {
|
||||
Protocol::Dns(_) => true,
|
||||
Protocol::Dns4(_) => true,
|
||||
Protocol::Dns6(_) => true,
|
||||
_ => false,
|
||||
@ -139,7 +140,7 @@ where
|
||||
trace!("Dialing address with DNS: {}", addr);
|
||||
let resolve_futs = addr.iter()
|
||||
.map(|cmp| match cmp {
|
||||
Protocol::Dns4(ref name) | Protocol::Dns6(ref name) => {
|
||||
Protocol::Dns(ref name) | Protocol::Dns4(ref name) | Protocol::Dns6(ref name) => {
|
||||
let name = name.to_string();
|
||||
let to_resolve = format!("{}:0", name);
|
||||
let (tx, rx) = oneshot::channel();
|
||||
@ -151,7 +152,12 @@ where
|
||||
});
|
||||
});
|
||||
|
||||
let is_dns4 = if let Protocol::Dns4(_) = cmp { true } else { false };
|
||||
let (dns4, dns6) = match cmp {
|
||||
Protocol::Dns(_) => (true, true),
|
||||
Protocol::Dns4(_) => (true, false),
|
||||
Protocol::Dns6(_) => (false, true),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
async move {
|
||||
let list = rx.await
|
||||
@ -166,7 +172,7 @@ where
|
||||
|
||||
list.into_iter()
|
||||
.filter_map(|addr| {
|
||||
if (is_dns4 && addr.is_ipv4()) || (!is_dns4 && addr.is_ipv6()) {
|
||||
if (dns4 && addr.is_ipv4()) || (dns6 && addr.is_ipv6()) {
|
||||
Some(Protocol::from(addr))
|
||||
} else {
|
||||
None
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-tcp"
|
||||
edition = "2018"
|
||||
description = "TCP/IP transport protocol for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -15,9 +15,7 @@ futures = "0.3.1"
|
||||
futures-timer = "3.0"
|
||||
get_if_addrs = "0.5.3"
|
||||
ipnet = "2.0.0"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
log = "0.4.1"
|
||||
socket2 = "0.3.12"
|
||||
tokio = { version = "0.2", default-features = false, features = ["tcp"], optional = true }
|
||||
|
||||
[features]
|
||||
default = ["async-std"]
|
||||
|
@ -39,8 +39,10 @@ use libp2p_core::{
|
||||
transport::{ListenerEvent, TransportError}
|
||||
};
|
||||
use log::{debug, trace};
|
||||
use socket2::{Socket, Domain, Type};
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
convert::TryFrom,
|
||||
io,
|
||||
iter::{self, FromIterator},
|
||||
net::{IpAddr, SocketAddr},
|
||||
@ -108,7 +110,22 @@ impl Transport for $tcp_config {
|
||||
async fn do_listen(cfg: $tcp_config, socket_addr: SocketAddr)
|
||||
-> Result<impl Stream<Item = Result<ListenerEvent<Ready<Result<$tcp_trans_stream, io::Error>>, io::Error>, io::Error>>, io::Error>
|
||||
{
|
||||
let listener = <$tcp_listener>::bind(&socket_addr).await?;
|
||||
let socket = if socket_addr.is_ipv4() {
|
||||
Socket::new(Domain::ipv4(), Type::stream(), Some(socket2::Protocol::tcp()))?
|
||||
} else {
|
||||
let s = Socket::new(Domain::ipv6(), Type::stream(), Some(socket2::Protocol::tcp()))?;
|
||||
s.set_only_v6(true)?;
|
||||
s
|
||||
};
|
||||
if cfg!(target_family = "unix") {
|
||||
socket.set_reuse_address(true)?;
|
||||
}
|
||||
socket.bind(&socket_addr.into())?;
|
||||
socket.listen(1024)?; // we may want to make this configurable
|
||||
|
||||
let listener = <$tcp_listener>::try_from(socket.into_tcp_listener())
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?;
|
||||
|
||||
let local_addr = listener.local_addr()?;
|
||||
let port = local_addr.port();
|
||||
|
||||
@ -485,42 +502,45 @@ mod tests {
|
||||
#[test]
|
||||
#[cfg(feature = "async-std")]
|
||||
fn wildcard_expansion() {
|
||||
let mut listener = TcpConfig::new()
|
||||
.listen_on("/ip4/0.0.0.0/tcp/0".parse().unwrap())
|
||||
.expect("listener");
|
||||
fn test(addr: Multiaddr) {
|
||||
let mut listener = TcpConfig::new().listen_on(addr).expect("listener");
|
||||
|
||||
// Get the first address.
|
||||
let addr = futures::executor::block_on_stream(listener.by_ref())
|
||||
.next()
|
||||
.expect("some event")
|
||||
.expect("no error")
|
||||
.into_new_address()
|
||||
.expect("listen address");
|
||||
// Get the first address.
|
||||
let addr = futures::executor::block_on_stream(listener.by_ref())
|
||||
.next()
|
||||
.expect("some event")
|
||||
.expect("no error")
|
||||
.into_new_address()
|
||||
.expect("listen address");
|
||||
|
||||
// Process all initial `NewAddress` events and make sure they
|
||||
// do not contain wildcard address or port.
|
||||
let server = listener
|
||||
.take_while(|event| match event.as_ref().unwrap() {
|
||||
ListenerEvent::NewAddress(a) => {
|
||||
let mut iter = a.iter();
|
||||
match iter.next().expect("ip address") {
|
||||
Protocol::Ip4(ip) => assert!(!ip.is_unspecified()),
|
||||
Protocol::Ip6(ip) => assert!(!ip.is_unspecified()),
|
||||
other => panic!("Unexpected protocol: {}", other)
|
||||
// Process all initial `NewAddress` events and make sure they
|
||||
// do not contain wildcard address or port.
|
||||
let server = listener
|
||||
.take_while(|event| match event.as_ref().unwrap() {
|
||||
ListenerEvent::NewAddress(a) => {
|
||||
let mut iter = a.iter();
|
||||
match iter.next().expect("ip address") {
|
||||
Protocol::Ip4(ip) => assert!(!ip.is_unspecified()),
|
||||
Protocol::Ip6(ip) => assert!(!ip.is_unspecified()),
|
||||
other => panic!("Unexpected protocol: {}", other)
|
||||
}
|
||||
if let Protocol::Tcp(port) = iter.next().expect("port") {
|
||||
assert_ne!(0, port)
|
||||
} else {
|
||||
panic!("No TCP port in address: {}", a)
|
||||
}
|
||||
futures::future::ready(true)
|
||||
}
|
||||
if let Protocol::Tcp(port) = iter.next().expect("port") {
|
||||
assert_ne!(0, port)
|
||||
} else {
|
||||
panic!("No TCP port in address: {}", a)
|
||||
}
|
||||
futures::future::ready(true)
|
||||
}
|
||||
_ => futures::future::ready(false)
|
||||
})
|
||||
.for_each(|_| futures::future::ready(()));
|
||||
_ => futures::future::ready(false)
|
||||
})
|
||||
.for_each(|_| futures::future::ready(()));
|
||||
|
||||
let client = TcpConfig::new().dial(addr).expect("dialer");
|
||||
async_std::task::block_on(futures::future::join(server, client)).1.unwrap();
|
||||
let client = TcpConfig::new().dial(addr).expect("dialer");
|
||||
async_std::task::block_on(futures::future::join(server, client)).1.unwrap();
|
||||
}
|
||||
|
||||
test("/ip4/0.0.0.0/tcp/0".parse().unwrap());
|
||||
test("/ip6/::1/tcp/0".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -575,43 +595,47 @@ mod tests {
|
||||
#[test]
|
||||
#[cfg(feature = "async-std")]
|
||||
fn communicating_between_dialer_and_listener() {
|
||||
let (ready_tx, ready_rx) = futures::channel::oneshot::channel();
|
||||
let mut ready_tx = Some(ready_tx);
|
||||
fn test(addr: Multiaddr) {
|
||||
let (ready_tx, ready_rx) = futures::channel::oneshot::channel();
|
||||
let mut ready_tx = Some(ready_tx);
|
||||
|
||||
async_std::task::spawn(async move {
|
||||
let addr = "/ip4/127.0.0.1/tcp/0".parse::<Multiaddr>().unwrap();
|
||||
let tcp = TcpConfig::new();
|
||||
let mut listener = tcp.listen_on(addr).unwrap();
|
||||
async_std::task::spawn(async move {
|
||||
let tcp = TcpConfig::new();
|
||||
let mut listener = tcp.listen_on(addr).unwrap();
|
||||
|
||||
loop {
|
||||
match listener.next().await.unwrap().unwrap() {
|
||||
ListenerEvent::NewAddress(listen_addr) => {
|
||||
ready_tx.take().unwrap().send(listen_addr).unwrap();
|
||||
},
|
||||
ListenerEvent::Upgrade { upgrade, .. } => {
|
||||
let mut upgrade = upgrade.await.unwrap();
|
||||
let mut buf = [0u8; 3];
|
||||
upgrade.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, [1, 2, 3]);
|
||||
upgrade.write_all(&[4, 5, 6]).await.unwrap();
|
||||
},
|
||||
_ => unreachable!()
|
||||
loop {
|
||||
match listener.next().await.unwrap().unwrap() {
|
||||
ListenerEvent::NewAddress(listen_addr) => {
|
||||
ready_tx.take().unwrap().send(listen_addr).unwrap();
|
||||
},
|
||||
ListenerEvent::Upgrade { upgrade, .. } => {
|
||||
let mut upgrade = upgrade.await.unwrap();
|
||||
let mut buf = [0u8; 3];
|
||||
upgrade.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, [1, 2, 3]);
|
||||
upgrade.write_all(&[4, 5, 6]).await.unwrap();
|
||||
},
|
||||
_ => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
async_std::task::block_on(async move {
|
||||
let addr = ready_rx.await.unwrap();
|
||||
let tcp = TcpConfig::new();
|
||||
async_std::task::block_on(async move {
|
||||
let addr = ready_rx.await.unwrap();
|
||||
let tcp = TcpConfig::new();
|
||||
|
||||
// Obtain a future socket through dialing
|
||||
let mut socket = tcp.dial(addr.clone()).unwrap().await.unwrap();
|
||||
socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap();
|
||||
// Obtain a future socket through dialing
|
||||
let mut socket = tcp.dial(addr.clone()).unwrap().await.unwrap();
|
||||
socket.write_all(&[0x1, 0x2, 0x3]).await.unwrap();
|
||||
|
||||
let mut buf = [0u8; 3];
|
||||
socket.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, [4, 5, 6]);
|
||||
});
|
||||
let mut buf = [0u8; 3];
|
||||
socket.read_exact(&mut buf).await.unwrap();
|
||||
assert_eq!(buf, [4, 5, 6]);
|
||||
});
|
||||
}
|
||||
|
||||
test("/ip4/127.0.0.1/tcp/0".parse().unwrap());
|
||||
test("/ip6/::1/tcp/0".parse().unwrap());
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-uds"
|
||||
edition = "2018"
|
||||
description = "Unix domain sockets transport for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -11,7 +11,7 @@ categories = ["network-programming", "asynchronous"]
|
||||
|
||||
[target.'cfg(all(unix, not(any(target_os = "emscripten", target_os = "unknown"))))'.dependencies]
|
||||
async-std = { version = "1.0", optional = true }
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
log = "0.4.1"
|
||||
futures = "0.3.1"
|
||||
tokio = { version = "0.2", default-features = false, features = ["uds"], optional = true }
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "libp2p-wasm-ext"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Pierre Krieger <pierre.krieger1708@gmail.com>"]
|
||||
edition = "2018"
|
||||
description = "Allows passing in an external transport in a WASM environment"
|
||||
@ -12,7 +12,7 @@ categories = ["network-programming", "asynchronous"]
|
||||
[dependencies]
|
||||
futures = "0.3.1"
|
||||
js-sys = "0.3.19"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
parity-send-wrapper = "0.1.0"
|
||||
wasm-bindgen = "0.2.42"
|
||||
wasm-bindgen-futures = "0.4.4"
|
||||
|
@ -2,7 +2,7 @@
|
||||
name = "libp2p-websocket"
|
||||
edition = "2018"
|
||||
description = "WebSocket transport for libp2p"
|
||||
version = "0.18.0"
|
||||
version = "0.19.0"
|
||||
authors = ["Parity Technologies <admin@parity.io>"]
|
||||
license = "MIT"
|
||||
repository = "https://github.com/libp2p/rust-libp2p"
|
||||
@ -14,7 +14,7 @@ async-tls = "0.7.0"
|
||||
bytes = "0.5"
|
||||
either = "1.5.3"
|
||||
futures = "0.3.1"
|
||||
libp2p-core = { version = "0.18.0", path = "../../core" }
|
||||
libp2p-core = { version = "0.19.0", path = "../../core" }
|
||||
log = "0.4.8"
|
||||
quicksink = "0.1"
|
||||
rustls = "0.17.0"
|
||||
@ -25,4 +25,4 @@ webpki = "0.21"
|
||||
webpki-roots = "0.18"
|
||||
|
||||
[dev-dependencies]
|
||||
libp2p-tcp = { version = "0.18.0", path = "../tcp" }
|
||||
libp2p-tcp = { version = "0.19.0", path = "../tcp" }
|
||||
|
@ -353,6 +353,8 @@ fn host_and_dnsname<T>(addr: &Multiaddr) -> Result<(String, Option<webpki::DNSNa
|
||||
Ok((format!("{}:{}", ip, port), None)),
|
||||
(Some(Protocol::Ip6(ip)), Some(Protocol::Tcp(port))) =>
|
||||
Ok((format!("{}:{}", ip, port), None)),
|
||||
(Some(Protocol::Dns(h)), Some(Protocol::Tcp(port))) =>
|
||||
Ok((format!("{}:{}", &h, port), Some(tls::dns_name_ref(&h)?.to_owned()))),
|
||||
(Some(Protocol::Dns4(h)), Some(Protocol::Tcp(port))) =>
|
||||
Ok((format!("{}:{}", &h, port), Some(tls::dns_name_ref(&h)?.to_owned()))),
|
||||
(Some(Protocol::Dns6(h)), Some(Protocol::Tcp(port))) =>
|
||||
@ -371,7 +373,7 @@ fn location_to_multiaddr<T>(location: &str) -> Result<Multiaddr, Error<T>> {
|
||||
let mut a = Multiaddr::empty();
|
||||
match url.host() {
|
||||
Some(url::Host::Domain(h)) => {
|
||||
a.push(Protocol::Dns4(h.into()))
|
||||
a.push(Protocol::Dns(h.into()))
|
||||
}
|
||||
Some(url::Host::Ipv4(ip)) => {
|
||||
a.push(Protocol::Ip4(ip))
|
||||
|
Loading…
x
Reference in New Issue
Block a user