From 2c580d91423d6d62c91bbacc0062d6af81516eb5 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 24 Aug 2022 12:55:58 +0100 Subject: [PATCH 01/34] Add `ReadyUpgrade` --- core/src/upgrade.rs | 2 ++ core/src/upgrade/ready.rs | 75 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 77 insertions(+) create mode 100644 core/src/upgrade/ready.rs diff --git a/core/src/upgrade.rs b/core/src/upgrade.rs index 34a27cdf77a..de9ef765e16 100644 --- a/core/src/upgrade.rs +++ b/core/src/upgrade.rs @@ -65,6 +65,7 @@ mod from_fn; mod map; mod optional; mod pending; +mod ready; mod select; mod transfer; @@ -79,6 +80,7 @@ pub use self::{ map::{MapInboundUpgrade, MapInboundUpgradeErr, MapOutboundUpgrade, MapOutboundUpgradeErr}, optional::OptionalUpgrade, pending::PendingUpgrade, + ready::ReadyUpgrade, select::SelectUpgrade, transfer::{read_length_prefixed, read_varint, write_length_prefixed, write_varint}, }; diff --git a/core/src/upgrade/ready.rs b/core/src/upgrade/ready.rs new file mode 100644 index 00000000000..16a9b2867f4 --- /dev/null +++ b/core/src/upgrade/ready.rs @@ -0,0 +1,75 @@ +// Copyright 2022 Protocol Labs. +// Copyright 2017-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. + +use crate::upgrade::{InboundUpgrade, OutboundUpgrade, ProtocolName, UpgradeInfo}; +use futures::future; +use std::iter; +use void::Void; + +/// Implementation of [`UpgradeInfo`], [`InboundUpgrade`] and [`OutboundUpgrade`] that directly yields the substream. +#[derive(Debug, Copy, Clone)] +pub struct ReadyUpgrade

{ + protocol_name: P, +} + +impl

ReadyUpgrade

{ + pub fn new(protocol_name: P) -> Self { + Self { protocol_name } + } +} + +impl

UpgradeInfo for ReadyUpgrade

+where + P: ProtocolName + Clone, +{ + type Info = P; + type InfoIter = iter::Once

; + + fn protocol_info(&self) -> Self::InfoIter { + iter::once(self.protocol_name.clone()) + } +} + +impl InboundUpgrade for ReadyUpgrade

+where + P: ProtocolName + Clone, +{ + type Output = C; + type Error = Void; + type Future = future::Ready>; + + fn upgrade_inbound(self, stream: C, _: Self::Info) -> Self::Future { + future::ready(Ok(stream)) + } +} + +impl OutboundUpgrade for ReadyUpgrade

+where + P: ProtocolName + Clone, +{ + type Output = C; + type Error = Void; + type Future = future::Ready>; + + fn upgrade_outbound(self, stream: C, _: Self::Info) -> Self::Future { + future::ready(Ok(stream)) + } +} From b2d1c055cbaa7763035352990d6fe53350008a36 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 24 Aug 2022 12:56:26 +0100 Subject: [PATCH 02/34] Add basic `FromFn` `ConnectionHandler` --- swarm/src/handler.rs | 1 + swarm/src/handler/from_fn.rs | 255 +++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 swarm/src/handler/from_fn.rs diff --git a/swarm/src/handler.rs b/swarm/src/handler.rs index c6125f277b1..4088f91b85b 100644 --- a/swarm/src/handler.rs +++ b/swarm/src/handler.rs @@ -40,6 +40,7 @@ mod dummy; pub mod either; +mod from_fn; mod map_in; mod map_out; pub mod multi; diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs new file mode 100644 index 00000000000..42ae5ddcf36 --- /dev/null +++ b/swarm/src/handler/from_fn.rs @@ -0,0 +1,255 @@ +use crate::handler::{InboundUpgradeSend, OutboundUpgradeSend}; +use crate::{ + ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive, + NegotiatedSubstream, SubstreamProtocol, +}; +use futures::future::BoxFuture; +use futures::future::FutureExt; +use futures::stream::FuturesUnordered; +use futures::StreamExt; +use libp2p_core::upgrade::{NegotiationError, ReadyUpgrade}; +use libp2p_core::UpgradeError; +use std::collections::VecDeque; +use std::fmt; +use std::future::Future; +use std::task::{Context, Poll, Waker}; +use void::Void; + +pub fn from_fn( + protocol: &'static str, + on_new_inbound: impl Fn(NegotiatedSubstream) -> TInboundFuture + Send + 'static, + on_new_outbound: impl Fn(NegotiatedSubstream, TOutboundOpenInfo) -> TOutboundFuture + Send + 'static, +) -> FromFn +where + TInboundFuture: Future + Send + 'static, + TOutboundFuture: Future + Send + 'static, +{ + FromFn { + protocol, + inbound_streams: FuturesUnordered::default(), + outbound_streams: FuturesUnordered::default(), + on_new_inbound: Box::new(move |stream| on_new_inbound(stream).boxed()), + on_new_outbound: Box::new(move |stream, info| on_new_outbound(stream, info).boxed()), + idle_waker: None, + pending_dials: VecDeque::default(), + failed_open: VecDeque::default(), + } +} + +#[derive(Debug)] +pub enum OutEvent { + InboundFinished(I), + OutboundFinished(O), + FailedToOpen(OpenError), +} + +// TODO: Impl std::error::Error +#[derive(Debug)] +pub enum OpenError { + Timeout(OpenInfo), + NegotiationFailed(OpenInfo, NegotiationError), +} + +#[derive(Default, Debug)] +pub struct NewOutbound(pub I); + +pub struct FromFn { + protocol: &'static str, + + inbound_streams: FuturesUnordered>, + outbound_streams: FuturesUnordered>, + + on_new_inbound: Box BoxFuture<'static, TInbound> + Send>, + on_new_outbound: + Box BoxFuture<'static, TOutbound> + Send>, + + idle_waker: Option, + + pending_dials: VecDeque, + + failed_open: VecDeque>, +} + +impl ConnectionHandler + for FromFn +where + TOutboundInfo: fmt::Debug + Send + 'static, + TInbound: fmt::Debug + Send + 'static, + TOutbound: fmt::Debug + Send + 'static, +{ + type InEvent = NewOutbound; + type OutEvent = OutEvent; + type Error = Void; + type InboundProtocol = ReadyUpgrade<&'static str>; + type OutboundProtocol = ReadyUpgrade<&'static str>; + type InboundOpenInfo = (); + type OutboundOpenInfo = TOutboundInfo; + + fn listen_protocol(&self) -> SubstreamProtocol { + SubstreamProtocol::new(ReadyUpgrade::new(self.protocol), ()) + } + + fn inject_fully_negotiated_inbound( + &mut self, + protocol: ::Output, + _: Self::InboundOpenInfo, + ) { + self.inbound_streams.push((self.on_new_inbound)(protocol)); + + if let Some(waker) = self.idle_waker.take() { + waker.wake(); + } + } + + fn inject_fully_negotiated_outbound( + &mut self, + protocol: ::Output, + info: Self::OutboundOpenInfo, + ) { + self.outbound_streams + .push((self.on_new_outbound)(protocol, info)); + + if let Some(waker) = self.idle_waker.take() { + waker.wake(); + } + } + + fn inject_event(&mut self, event: Self::InEvent) { + self.pending_dials.push_back(event.0); + } + + fn inject_dial_upgrade_error( + &mut self, + info: Self::OutboundOpenInfo, + error: ConnectionHandlerUpgrErr<::Error>, + ) { + match error { + ConnectionHandlerUpgrErr::Timeout => { + self.failed_open.push_back(OpenError::Timeout(info)) + } + ConnectionHandlerUpgrErr::Timer => {} + ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Select(negotiation)) => self + .failed_open + .push_back(OpenError::NegotiationFailed(info, negotiation)), + ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Apply(apply)) => { + void::unreachable(apply) + } + } + } + + fn connection_keep_alive(&self) -> KeepAlive { + if self.inbound_streams.is_empty() + && self.outbound_streams.is_empty() + && self.pending_dials.is_empty() + { + return KeepAlive::No; + } + + KeepAlive::Yes + } + + fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll< + ConnectionHandlerEvent< + Self::OutboundProtocol, + Self::OutboundOpenInfo, + Self::OutEvent, + Self::Error, + >, + > { + if let Some(error) = self.failed_open.pop_front() { + return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::FailedToOpen( + error, + ))); + } + + if let Some(outbound_open_info) = self.pending_dials.pop_front() { + return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new( + ReadyUpgrade::new(self.protocol), + outbound_open_info, + ), + }); + } + + match self.outbound_streams.poll_next_unpin(cx) { + Poll::Ready(Some(outbound_done)) => { + return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::OutboundFinished( + outbound_done, + ))); + } + Poll::Ready(None) => { + self.idle_waker = Some(cx.waker().clone()); + } + Poll::Pending => {} + }; + + match self.inbound_streams.poll_next_unpin(cx) { + Poll::Ready(Some(inbound_done)) => { + return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::InboundFinished( + inbound_done, + ))); + } + Poll::Ready(None) => { + self.idle_waker = Some(cx.waker().clone()); + } + Poll::Pending => {} + }; + + Poll::Pending + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{ + IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, + PollParameters, + }; + use libp2p_core::connection::ConnectionId; + use libp2p_core::PeerId; + + struct MyBehaviour {} + + impl NetworkBehaviour for MyBehaviour { + type ConnectionHandler = FromFn<(), (), ()>; + type OutEvent = (); + + fn new_handler(&mut self) -> Self::ConnectionHandler { + from_fn( + "/foo/bar/1.0.0", + |_stream| async move {}, + |_stream, ()| async move {}, + ) + } + + fn inject_event( + &mut self, + _peer_id: PeerId, + _connection: ConnectionId, + event: <::Handler as ConnectionHandler>::OutEvent, + ) { + match event { + OutEvent::InboundFinished(()) => {} + OutEvent::OutboundFinished(()) => {} + OutEvent::FailedToOpen(OpenError::Timeout(())) => {} + OutEvent::FailedToOpen(OpenError::NegotiationFailed((), neg_error)) => {} + } + } + + fn poll( + &mut self, + _cx: &mut Context<'_>, + _params: &mut impl PollParameters, + ) -> Poll> { + Poll::Ready(NetworkBehaviourAction::NotifyHandler { + peer_id: PeerId::random(), + handler: NotifyHandler::Any, + event: NewOutbound::default(), + }) + } + } +} From 230fabbe24032772f4afb44a1683b16aa244a6c2 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sun, 28 Aug 2022 22:04:33 +0200 Subject: [PATCH 03/34] Add `TState` abstraction --- swarm/src/handler/from_fn.rs | 105 ++++++++++++++++++++++++----------- 1 file changed, 73 insertions(+), 32 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 42ae5ddcf36..30241b3732d 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -15,24 +15,53 @@ use std::future::Future; use std::task::{Context, Poll, Waker}; use void::Void; -pub fn from_fn( +/// A low-level building block for protocols that can be expressed as async functions. +/// +/// An async function in Rust is executed within an executor and thus only has access to its local state +/// and by extension, the state that was available when the [`Future`] was constructed. +/// +/// This [`ConnectionHandler`] aims to reduce the boilerplate for protocols which can be expressed as +/// a static sequence of reads and writes to a socket where the response can be generated with limited +/// "local" knowledge or in other words, the local state within the [`Future`]. +/// +/// For outbound substreams, arbitrary data can be supplied via [`InEvent::NewOutbound`] which will be +/// made available to the callback once the stream is fully negotiated. +/// +/// Inbound substreams may be opened at any time by the remote. To facilitate this one and more usecases, +/// the supplied callbacks for inbound and outbound substream are given access to the handler's `state` +/// field. This `state` field can contain arbitrary data and can be updated by the [`NetworkBehaviour`] +/// via [`InEvent::UpdateState`]. +/// +/// The design of this [`ConnectionHandler`] trades boilerplate (you don't have to write your own handler) +/// and simplicity (small API surface) for eventual consistency, depending on your protocol design: +/// +/// Most likely, the [`NetworkBehaviour`] is the authoritive source of `TState` but updates to it have +/// to be manually performed via [`InEvent::UpdateState`]. Thus, the state given to newly created +/// substreams, may be outdated and only eventually-consistent. +pub fn from_fn( protocol: &'static str, - on_new_inbound: impl Fn(NegotiatedSubstream) -> TInboundFuture + Send + 'static, - on_new_outbound: impl Fn(NegotiatedSubstream, TOutboundOpenInfo) -> TOutboundFuture + Send + 'static, -) -> FromFn + on_new_inbound: impl Fn(NegotiatedSubstream, &mut TState) -> TInboundFuture + Send + 'static, + on_new_outbound: impl Fn(NegotiatedSubstream, &mut TState, TOutboundOpenInfo) -> TOutboundFuture + + Send + + 'static, +) -> FromFn where TInboundFuture: Future + Send + 'static, TOutboundFuture: Future + Send + 'static, + TState: Default, { FromFn { protocol, inbound_streams: FuturesUnordered::default(), outbound_streams: FuturesUnordered::default(), - on_new_inbound: Box::new(move |stream| on_new_inbound(stream).boxed()), - on_new_outbound: Box::new(move |stream, info| on_new_outbound(stream, info).boxed()), + on_new_inbound: Box::new(move |stream, state| on_new_inbound(stream, state).boxed()), + on_new_outbound: Box::new(move |stream, state, info| { + on_new_outbound(stream, state, info).boxed() + }), idle_waker: None, pending_dials: VecDeque::default(), failed_open: VecDeque::default(), + state: TState::default(), } } @@ -50,34 +79,44 @@ pub enum OpenError { NegotiationFailed(OpenInfo, NegotiationError), } -#[derive(Default, Debug)] -pub struct NewOutbound(pub I); +#[derive(Debug)] +pub enum InEvent { + UpdateState(TState), + NewOutbound(TOutboundOpenInfo), +} -pub struct FromFn { +// TODO: Implement limit for max incoming streams +pub struct FromFn { protocol: &'static str, inbound_streams: FuturesUnordered>, outbound_streams: FuturesUnordered>, - on_new_inbound: Box BoxFuture<'static, TInbound> + Send>, - on_new_outbound: - Box BoxFuture<'static, TOutbound> + Send>, + on_new_inbound: + Box BoxFuture<'static, TInbound> + Send>, + on_new_outbound: Box< + dyn Fn(NegotiatedSubstream, &mut TState, TOutboundInfo) -> BoxFuture<'static, TOutbound> + + Send, + >, idle_waker: Option, pending_dials: VecDeque, failed_open: VecDeque>, + + state: TState, } -impl ConnectionHandler - for FromFn +impl ConnectionHandler + for FromFn where TOutboundInfo: fmt::Debug + Send + 'static, TInbound: fmt::Debug + Send + 'static, TOutbound: fmt::Debug + Send + 'static, + TState: fmt::Debug + Send + 'static, { - type InEvent = NewOutbound; + type InEvent = InEvent; type OutEvent = OutEvent; type Error = Void; type InboundProtocol = ReadyUpgrade<&'static str>; @@ -94,7 +133,8 @@ where protocol: ::Output, _: Self::InboundOpenInfo, ) { - self.inbound_streams.push((self.on_new_inbound)(protocol)); + let inbound_future = (self.on_new_inbound)(protocol, &mut self.state); + self.inbound_streams.push(inbound_future); if let Some(waker) = self.idle_waker.take() { waker.wake(); @@ -106,8 +146,8 @@ where protocol: ::Output, info: Self::OutboundOpenInfo, ) { - self.outbound_streams - .push((self.on_new_outbound)(protocol, info)); + let outbound_future = (self.on_new_outbound)(protocol, &mut self.state, info); + self.outbound_streams.push(outbound_future); if let Some(waker) = self.idle_waker.take() { waker.wake(); @@ -115,7 +155,10 @@ where } fn inject_event(&mut self, event: Self::InEvent) { - self.pending_dials.push_back(event.0); + match event { + InEvent::UpdateState(new_state) => self.state = new_state, + InEvent::NewOutbound(open_info) => self.pending_dials.push_back(open_info), + } } fn inject_dial_upgrade_error( @@ -205,24 +248,26 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{ - IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, - PollParameters, - }; + use crate::{IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters}; use libp2p_core::connection::ConnectionId; use libp2p_core::PeerId; struct MyBehaviour {} + #[derive(Debug, Default)] + struct ConnectionState { + _foo: (), + } + impl NetworkBehaviour for MyBehaviour { - type ConnectionHandler = FromFn<(), (), ()>; + type ConnectionHandler = FromFn<(), (), (), ConnectionState>; type OutEvent = (); fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn( "/foo/bar/1.0.0", - |_stream| async move {}, - |_stream, ()| async move {}, + |_stream, _state| async move {}, + |_stream, _state, ()| async move {}, ) } @@ -236,7 +281,7 @@ mod tests { OutEvent::InboundFinished(()) => {} OutEvent::OutboundFinished(()) => {} OutEvent::FailedToOpen(OpenError::Timeout(())) => {} - OutEvent::FailedToOpen(OpenError::NegotiationFailed((), neg_error)) => {} + OutEvent::FailedToOpen(OpenError::NegotiationFailed((), _neg_error)) => {} } } @@ -245,11 +290,7 @@ mod tests { _cx: &mut Context<'_>, _params: &mut impl PollParameters, ) -> Poll> { - Poll::Ready(NetworkBehaviourAction::NotifyHandler { - peer_id: PeerId::random(), - handler: NotifyHandler::Any, - event: NewOutbound::default(), - }) + Poll::Pending } } } From 58fe45b150d45b9d662860b5dee1950a718eecbe Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sun, 28 Aug 2022 22:16:45 +0200 Subject: [PATCH 04/34] Publicly expose types --- swarm/src/handler.rs | 2 +- swarm/src/lib.rs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/swarm/src/handler.rs b/swarm/src/handler.rs index 4088f91b85b..2401869c26c 100644 --- a/swarm/src/handler.rs +++ b/swarm/src/handler.rs @@ -40,7 +40,7 @@ mod dummy; pub mod either; -mod from_fn; +pub mod from_fn; mod map_in; mod map_out; pub mod multi; diff --git a/swarm/src/lib.rs b/swarm/src/lib.rs index 38df855cff5..6f5c0cb54de 100644 --- a/swarm/src/lib.rs +++ b/swarm/src/lib.rs @@ -73,9 +73,9 @@ pub use connection::{ PendingInboundConnectionError, PendingOutboundConnectionError, }; pub use handler::{ - ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerSelect, ConnectionHandlerUpgrErr, - IntoConnectionHandler, IntoConnectionHandlerSelect, KeepAlive, OneShotHandler, - OneShotHandlerConfig, SubstreamProtocol, + from_fn::from_fn, ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerSelect, + ConnectionHandlerUpgrErr, IntoConnectionHandler, IntoConnectionHandlerSelect, KeepAlive, + OneShotHandler, OneShotHandlerConfig, SubstreamProtocol, }; pub use registry::{AddAddressResult, AddressRecord, AddressScore}; From ff1db42197ca231dc7326c57b65d4c181b515482 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sun, 28 Aug 2022 22:18:39 +0200 Subject: [PATCH 05/34] Fix docs --- swarm/src/handler/from_fn.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 30241b3732d..727217f295a 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -38,6 +38,8 @@ use void::Void; /// Most likely, the [`NetworkBehaviour`] is the authoritive source of `TState` but updates to it have /// to be manually performed via [`InEvent::UpdateState`]. Thus, the state given to newly created /// substreams, may be outdated and only eventually-consistent. +/// +/// [`NetworkBehaviour`]: crate::NetworkBehaviour pub fn from_fn( protocol: &'static str, on_new_inbound: impl Fn(NegotiatedSubstream, &mut TState) -> TInboundFuture + Send + 'static, From 1b175d2b62715ee1dd88addbce2551fc96be2d5c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 30 Aug 2022 11:33:18 +0200 Subject: [PATCH 06/34] Implement limit for max inbound streams and pending dials --- swarm/src/handler/from_fn.rs | 29 +++++++++++++++++++++++++++-- 1 file changed, 27 insertions(+), 2 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 727217f295a..3acf0205a5d 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -42,6 +42,8 @@ use void::Void; /// [`NetworkBehaviour`]: crate::NetworkBehaviour pub fn from_fn( protocol: &'static str, + inbound_streams_limit: usize, + pending_dial_limit: usize, on_new_inbound: impl Fn(NegotiatedSubstream, &mut TState) -> TInboundFuture + Send + 'static, on_new_outbound: impl Fn(NegotiatedSubstream, &mut TState, TOutboundOpenInfo) -> TOutboundFuture + Send @@ -54,6 +56,8 @@ where { FromFn { protocol, + inbound_streams_limit, + pending_dial_limit, inbound_streams: FuturesUnordered::default(), outbound_streams: FuturesUnordered::default(), on_new_inbound: Box::new(move |stream, state| on_new_inbound(stream, state).boxed()), @@ -78,6 +82,7 @@ pub enum OutEvent { #[derive(Debug)] pub enum OpenError { Timeout(OpenInfo), + LimitExceeded(OpenInfo), NegotiationFailed(OpenInfo, NegotiationError), } @@ -87,7 +92,6 @@ pub enum InEvent { NewOutbound(TOutboundOpenInfo), } -// TODO: Implement limit for max incoming streams pub struct FromFn { protocol: &'static str, @@ -103,7 +107,10 @@ pub struct FromFn { idle_waker: Option, + inbound_streams_limit: usize, + pending_dials: VecDeque, + pending_dial_limit: usize, failed_open: VecDeque>, @@ -135,6 +142,14 @@ where protocol: ::Output, _: Self::InboundOpenInfo, ) { + if self.inbound_streams.len() >= self.inbound_streams_limit { + log::debug!( + "Dropping inbound substream because limit ({}) would be exceeded", + self.inbound_streams_limit + ); + return; + } + let inbound_future = (self.on_new_inbound)(protocol, &mut self.state); self.inbound_streams.push(inbound_future); @@ -159,7 +174,14 @@ where fn inject_event(&mut self, event: Self::InEvent) { match event { InEvent::UpdateState(new_state) => self.state = new_state, - InEvent::NewOutbound(open_info) => self.pending_dials.push_back(open_info), + InEvent::NewOutbound(open_info) => { + if self.pending_dials.len() >= self.pending_dial_limit { + self.failed_open + .push_back(OpenError::LimitExceeded(open_info)); + } else { + self.pending_dials.push_back(open_info); + } + } } } @@ -268,6 +290,8 @@ mod tests { fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn( "/foo/bar/1.0.0", + 5, + 5, |_stream, _state| async move {}, |_stream, _state, ()| async move {}, ) @@ -284,6 +308,7 @@ mod tests { OutEvent::OutboundFinished(()) => {} OutEvent::FailedToOpen(OpenError::Timeout(())) => {} OutEvent::FailedToOpen(OpenError::NegotiationFailed((), _neg_error)) => {} + OutEvent::FailedToOpen(OpenError::LimitExceeded(_)) => {} } } From 69fa94fd90ad81c884a2dbdf63abdd9c72f76b1e Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Tue, 30 Aug 2022 11:42:07 +0200 Subject: [PATCH 07/34] Implement std::error::Error for `OpenError` --- swarm/src/handler/from_fn.rs | 28 +++++++++++++++++++++++++++- 1 file changed, 27 insertions(+), 1 deletion(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 3acf0205a5d..143b1ed833d 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -10,6 +10,7 @@ use futures::StreamExt; use libp2p_core::upgrade::{NegotiationError, ReadyUpgrade}; use libp2p_core::UpgradeError; use std::collections::VecDeque; +use std::error::Error; use std::fmt; use std::future::Future; use std::task::{Context, Poll, Waker}; @@ -78,7 +79,6 @@ pub enum OutEvent { FailedToOpen(OpenError), } -// TODO: Impl std::error::Error #[derive(Debug)] pub enum OpenError { Timeout(OpenInfo), @@ -86,6 +86,29 @@ pub enum OpenError { NegotiationFailed(OpenInfo, NegotiationError), } +impl fmt::Display for OpenError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + OpenError::Timeout(_) => write!(f, "opening new substream timed out"), + OpenError::LimitExceeded(_) => write!(f, "limit for pending dials exceeded"), + OpenError::NegotiationFailed(_, _) => Ok(()), // Don't print anything to avoid double printing of error. + } + } +} + +impl Error for OpenError +where + OpenInfo: fmt::Debug, +{ + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + OpenError::Timeout(_) => None, + OpenError::LimitExceeded(_) => None, + OpenError::NegotiationFailed(_, source) => Some(source), + } + } +} + #[derive(Debug)] pub enum InEvent { UpdateState(TState), @@ -320,4 +343,7 @@ mod tests { Poll::Pending } } + + // TODO: Add test for max pending dials + // TODO: Add test for max inbound streams } From 54e64378ed2261d487b12bc0e82cb1207d1cf9a6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 5 Sep 2022 18:57:07 +0200 Subject: [PATCH 08/34] Avoid dial terminology for streams --- swarm/src/handler/from_fn.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 143b1ed833d..d8394c73913 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -58,7 +58,7 @@ where FromFn { protocol, inbound_streams_limit, - pending_dial_limit, + pending_outbound_streams_limit: pending_dial_limit, inbound_streams: FuturesUnordered::default(), outbound_streams: FuturesUnordered::default(), on_new_inbound: Box::new(move |stream, state| on_new_inbound(stream, state).boxed()), @@ -66,7 +66,7 @@ where on_new_outbound(stream, state, info).boxed() }), idle_waker: None, - pending_dials: VecDeque::default(), + pending_outbound_streams: VecDeque::default(), failed_open: VecDeque::default(), state: TState::default(), } @@ -132,8 +132,8 @@ pub struct FromFn { inbound_streams_limit: usize, - pending_dials: VecDeque, - pending_dial_limit: usize, + pending_outbound_streams: VecDeque, + pending_outbound_streams_limit: usize, failed_open: VecDeque>, @@ -198,11 +198,11 @@ where match event { InEvent::UpdateState(new_state) => self.state = new_state, InEvent::NewOutbound(open_info) => { - if self.pending_dials.len() >= self.pending_dial_limit { + if self.pending_outbound_streams.len() >= self.pending_outbound_streams_limit { self.failed_open .push_back(OpenError::LimitExceeded(open_info)); } else { - self.pending_dials.push_back(open_info); + self.pending_outbound_streams.push_back(open_info); } } } @@ -230,7 +230,7 @@ where fn connection_keep_alive(&self) -> KeepAlive { if self.inbound_streams.is_empty() && self.outbound_streams.is_empty() - && self.pending_dials.is_empty() + && self.pending_outbound_streams.is_empty() { return KeepAlive::No; } @@ -255,7 +255,7 @@ where ))); } - if let Some(outbound_open_info) = self.pending_dials.pop_front() { + if let Some(outbound_open_info) = self.pending_outbound_streams.pop_front() { return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { protocol: SubstreamProtocol::new( ReadyUpgrade::new(self.protocol), From 635730aa2bff3dddca5940f44d9ec4f8bc242ef7 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 5 Sep 2022 18:59:28 +0200 Subject: [PATCH 09/34] Remove `idle_waker` --- swarm/src/handler/from_fn.rs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index d8394c73913..e0fbc9f37a4 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -13,7 +13,7 @@ use std::collections::VecDeque; use std::error::Error; use std::fmt; use std::future::Future; -use std::task::{Context, Poll, Waker}; +use std::task::{Context, Poll}; use void::Void; /// A low-level building block for protocols that can be expressed as async functions. @@ -65,7 +65,6 @@ where on_new_outbound: Box::new(move |stream, state, info| { on_new_outbound(stream, state, info).boxed() }), - idle_waker: None, pending_outbound_streams: VecDeque::default(), failed_open: VecDeque::default(), state: TState::default(), @@ -128,8 +127,6 @@ pub struct FromFn { + Send, >, - idle_waker: Option, - inbound_streams_limit: usize, pending_outbound_streams: VecDeque, @@ -175,10 +172,6 @@ where let inbound_future = (self.on_new_inbound)(protocol, &mut self.state); self.inbound_streams.push(inbound_future); - - if let Some(waker) = self.idle_waker.take() { - waker.wake(); - } } fn inject_fully_negotiated_outbound( @@ -188,10 +181,6 @@ where ) { let outbound_future = (self.on_new_outbound)(protocol, &mut self.state, info); self.outbound_streams.push(outbound_future); - - if let Some(waker) = self.idle_waker.take() { - waker.wake(); - } } fn inject_event(&mut self, event: Self::InEvent) { @@ -271,7 +260,8 @@ where ))); } Poll::Ready(None) => { - self.idle_waker = Some(cx.waker().clone()); + // Normally, we'd register a waker here but `Connection` polls us anyway again + // after calling `inject` on us which is where we'd use the waker. } Poll::Pending => {} }; @@ -283,7 +273,8 @@ where ))); } Poll::Ready(None) => { - self.idle_waker = Some(cx.waker().clone()); + // Normally, we'd register a waker here but `Connection` polls us anyway again + // after calling `inject` on us which is where we'd use the waker. } Poll::Pending => {} }; From b37fa5c2d0fce9500cb105ac7092cf55f472a2ef Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 5 Sep 2022 19:00:41 +0200 Subject: [PATCH 10/34] Make `TState` configurable --- swarm/src/handler/from_fn.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index e0fbc9f37a4..5b19b409662 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -43,6 +43,7 @@ use void::Void; /// [`NetworkBehaviour`]: crate::NetworkBehaviour pub fn from_fn( protocol: &'static str, + state: TState, inbound_streams_limit: usize, pending_dial_limit: usize, on_new_inbound: impl Fn(NegotiatedSubstream, &mut TState) -> TInboundFuture + Send + 'static, @@ -53,7 +54,6 @@ pub fn from_fn + Send + 'static, TOutboundFuture: Future + Send + 'static, - TState: Default, { FromFn { protocol, @@ -67,7 +67,7 @@ where }), pending_outbound_streams: VecDeque::default(), failed_open: VecDeque::default(), - state: TState::default(), + state, } } @@ -304,6 +304,7 @@ mod tests { fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn( "/foo/bar/1.0.0", + ConnectionState::default(), 5, 5, |_stream, _state| async move {}, From d4079bf95a0ce3a9510d26a81077442d686a6daa Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 5 Sep 2022 19:02:14 +0200 Subject: [PATCH 11/34] Finish local work before producing new work --- swarm/src/handler/from_fn.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 5b19b409662..df51bf4077e 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -244,15 +244,6 @@ where ))); } - if let Some(outbound_open_info) = self.pending_outbound_streams.pop_front() { - return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { - protocol: SubstreamProtocol::new( - ReadyUpgrade::new(self.protocol), - outbound_open_info, - ), - }); - } - match self.outbound_streams.poll_next_unpin(cx) { Poll::Ready(Some(outbound_done)) => { return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::OutboundFinished( @@ -279,6 +270,15 @@ where Poll::Pending => {} }; + if let Some(outbound_open_info) = self.pending_outbound_streams.pop_front() { + return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { + protocol: SubstreamProtocol::new( + ReadyUpgrade::new(self.protocol), + outbound_open_info, + ), + }); + } + Poll::Pending } } From bc361eca0336151fe9bdc6a0147d40a913796ec1 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 5 Sep 2022 19:09:15 +0200 Subject: [PATCH 12/34] Introduce `FromFnProto` This allows us to capture the `remote_peer_id` and `connection_point` of the connection. --- swarm/src/handler/from_fn.rs | 70 ++++++++++++++++++++++++++++++------ 1 file changed, 60 insertions(+), 10 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index df51bf4077e..1804b824c14 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -1,14 +1,14 @@ use crate::handler::{InboundUpgradeSend, OutboundUpgradeSend}; use crate::{ - ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive, - NegotiatedSubstream, SubstreamProtocol, + ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, IntoConnectionHandler, + KeepAlive, NegotiatedSubstream, SubstreamProtocol, }; use futures::future::BoxFuture; use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::StreamExt; use libp2p_core::upgrade::{NegotiationError, ReadyUpgrade}; -use libp2p_core::UpgradeError; +use libp2p_core::{ConnectedPoint, PeerId, UpgradeError}; use std::collections::VecDeque; use std::error::Error; use std::fmt; @@ -50,23 +50,19 @@ pub fn from_fn TOutboundFuture + Send + 'static, -) -> FromFn +) -> FromFnProto where TInboundFuture: Future + Send + 'static, TOutboundFuture: Future + Send + 'static, { - FromFn { + FromFnProto { protocol, inbound_streams_limit, pending_outbound_streams_limit: pending_dial_limit, - inbound_streams: FuturesUnordered::default(), - outbound_streams: FuturesUnordered::default(), on_new_inbound: Box::new(move |stream, state| on_new_inbound(stream, state).boxed()), on_new_outbound: Box::new(move |stream, state, info| { on_new_outbound(stream, state, info).boxed() }), - pending_outbound_streams: VecDeque::default(), - failed_open: VecDeque::default(), state, } } @@ -114,8 +110,62 @@ pub enum InEvent { NewOutbound(TOutboundOpenInfo), } +pub struct FromFnProto { + protocol: &'static str, + + on_new_inbound: + Box BoxFuture<'static, TInbound> + Send>, + on_new_outbound: Box< + dyn Fn(NegotiatedSubstream, &mut TState, TOutboundOpenInfo) -> BoxFuture<'static, TOutbound> + + Send, + >, + + inbound_streams_limit: usize, + pending_outbound_streams_limit: usize, + + state: TState, +} + +impl IntoConnectionHandler + for FromFnProto +where + TInbound: fmt::Debug + Send + 'static, + TOutbound: fmt::Debug + Send + 'static, + TOutboundOpenInfo: fmt::Debug + Send + 'static, + TState: fmt::Debug + Send + 'static, +{ + type Handler = FromFn; + + fn into_handler( + self, + remote_peer_id: &PeerId, + connected_point: &ConnectedPoint, + ) -> Self::Handler { + FromFn { + protocol: self.protocol, + remote_peer_id: *remote_peer_id, + connected_point: connected_point.clone(), + inbound_streams: FuturesUnordered::default(), + outbound_streams: FuturesUnordered::default(), + on_new_inbound: self.on_new_inbound, + on_new_outbound: self.on_new_outbound, + inbound_streams_limit: self.inbound_streams_limit, + pending_outbound_streams: VecDeque::default(), + pending_outbound_streams_limit: self.pending_outbound_streams_limit, + failed_open: VecDeque::default(), + state: self.state, + } + } + + fn inbound_protocol(&self) -> ::InboundProtocol { + ReadyUpgrade::new(self.protocol) + } +} + pub struct FromFn { protocol: &'static str, + remote_peer_id: PeerId, + connected_point: ConnectedPoint, inbound_streams: FuturesUnordered>, outbound_streams: FuturesUnordered>, @@ -298,7 +348,7 @@ mod tests { } impl NetworkBehaviour for MyBehaviour { - type ConnectionHandler = FromFn<(), (), (), ConnectionState>; + type ConnectionHandler = FromFnProto<(), (), (), ConnectionState>; type OutEvent = (); fn new_handler(&mut self) -> Self::ConnectionHandler { From 712180b2745c90b7e2473a232d92b640f33718b2 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Mon, 5 Sep 2022 19:11:31 +0200 Subject: [PATCH 13/34] Expose `remote_peer_id` and `connected_point` to closures --- swarm/src/handler/from_fn.rs | 79 +++++++++++++++++++++++++++++------- 1 file changed, 64 insertions(+), 15 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 1804b824c14..fd4b16c8f88 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -46,8 +46,16 @@ pub fn from_fn TInboundFuture + Send + 'static, - on_new_outbound: impl Fn(NegotiatedSubstream, &mut TState, TOutboundOpenInfo) -> TOutboundFuture + on_new_inbound: impl Fn(NegotiatedSubstream, PeerId, &ConnectedPoint, &mut TState) -> TInboundFuture + + Send + + 'static, + on_new_outbound: impl Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &mut TState, + TOutboundOpenInfo, + ) -> TOutboundFuture + Send + 'static, ) -> FromFnProto @@ -59,10 +67,14 @@ where protocol, inbound_streams_limit, pending_outbound_streams_limit: pending_dial_limit, - on_new_inbound: Box::new(move |stream, state| on_new_inbound(stream, state).boxed()), - on_new_outbound: Box::new(move |stream, state, info| { - on_new_outbound(stream, state, info).boxed() + on_new_inbound: Box::new(move |stream, remote_peer_id, connected_point, state| { + on_new_inbound(stream, remote_peer_id, connected_point, state).boxed() }), + on_new_outbound: Box::new( + move |stream, remote_peer_id, connected_point, state, info| { + on_new_outbound(stream, remote_peer_id, connected_point, state, info).boxed() + }, + ), state, } } @@ -113,10 +125,23 @@ pub enum InEvent { pub struct FromFnProto { protocol: &'static str, - on_new_inbound: - Box BoxFuture<'static, TInbound> + Send>, + on_new_inbound: Box< + dyn Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &mut TState, + ) -> BoxFuture<'static, TInbound> + + Send, + >, on_new_outbound: Box< - dyn Fn(NegotiatedSubstream, &mut TState, TOutboundOpenInfo) -> BoxFuture<'static, TOutbound> + dyn Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &mut TState, + TOutboundOpenInfo, + ) -> BoxFuture<'static, TOutbound> + Send, >, @@ -170,10 +195,23 @@ pub struct FromFn { inbound_streams: FuturesUnordered>, outbound_streams: FuturesUnordered>, - on_new_inbound: - Box BoxFuture<'static, TInbound> + Send>, + on_new_inbound: Box< + dyn Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &mut TState, + ) -> BoxFuture<'static, TInbound> + + Send, + >, on_new_outbound: Box< - dyn Fn(NegotiatedSubstream, &mut TState, TOutboundInfo) -> BoxFuture<'static, TOutbound> + dyn Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &mut TState, + TOutboundInfo, + ) -> BoxFuture<'static, TOutbound> + Send, >, @@ -220,7 +258,12 @@ where return; } - let inbound_future = (self.on_new_inbound)(protocol, &mut self.state); + let inbound_future = (self.on_new_inbound)( + protocol, + self.remote_peer_id, + &self.connected_point, + &mut self.state, + ); self.inbound_streams.push(inbound_future); } @@ -229,7 +272,13 @@ where protocol: ::Output, info: Self::OutboundOpenInfo, ) { - let outbound_future = (self.on_new_outbound)(protocol, &mut self.state, info); + let outbound_future = (self.on_new_outbound)( + protocol, + self.remote_peer_id, + &self.connected_point, + &mut self.state, + info, + ); self.outbound_streams.push(outbound_future); } @@ -357,8 +406,8 @@ mod tests { ConnectionState::default(), 5, 5, - |_stream, _state| async move {}, - |_stream, _state, ()| async move {}, + |_stream, _remote_peer_id, _connected_point, _state| async move {}, + |_stream, _remote_peer_id, _connected_point, _state, ()| async move {}, ) } From 5d7f0bd9db84a55ee8adb80fd1a2e25287fa2bb8 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 2 Nov 2022 19:09:20 +1100 Subject: [PATCH 14/34] Implement `Shared` abstraction to automatically share state --- swarm/src/handler/from_fn.rs | 149 ++++++++++++++++++++++++++++++++--- 1 file changed, 136 insertions(+), 13 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index fd4b16c8f88..3a4afc6bd19 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -1,19 +1,21 @@ use crate::handler::{InboundUpgradeSend, OutboundUpgradeSend}; use crate::{ ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, IntoConnectionHandler, - KeepAlive, NegotiatedSubstream, SubstreamProtocol, + KeepAlive, NegotiatedSubstream, NetworkBehaviourAction, NotifyHandler, SubstreamProtocol, }; use futures::future::BoxFuture; use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::StreamExt; +use libp2p_core::connection::ConnectionId; use libp2p_core::upgrade::{NegotiationError, ReadyUpgrade}; use libp2p_core::{ConnectedPoint, PeerId, UpgradeError}; -use std::collections::VecDeque; +use std::collections::{HashSet, VecDeque}; use std::error::Error; use std::fmt; use std::future::Future; -use std::task::{Context, Poll}; +use std::ops::{Deref, DerefMut}; +use std::task::{Context, Poll, Waker}; use void::Void; /// A low-level building block for protocols that can be expressed as async functions. @@ -93,6 +95,93 @@ pub enum OpenError { NegotiationFailed(OpenInfo, NegotiationError), } +/// A wrapper for state that is shared across all connections. +/// +/// Any update to the state will "automatically" be relayed to all connections, assuming this struct +/// is correctly wired into your [`NetworkBehaviour`](crate::swarm::NetworkBehaviour). +/// +/// This struct implements an observer pattern. All registered connections will receive updates that +/// are made to the state. +pub struct Shared { + inner: T, + + dirty: bool, + waker: Option, + connections: HashSet<(PeerId, ConnectionId)>, + pending_update_events: VecDeque<(PeerId, ConnectionId, T)>, +} + +impl Shared +where + T: Clone, +{ + pub fn new(state: T) -> Self { + Self { + inner: state, + dirty: false, + waker: None, + connections: HashSet::default(), + pending_update_events: VecDeque::default(), + } + } + + pub fn register_connection(&mut self, peer_id: PeerId, id: ConnectionId) { + self.connections.insert((peer_id, id)); + } + + pub fn unregister_connection(&mut self, peer_id: PeerId, id: ConnectionId) { + self.connections.remove(&(peer_id, id)); + } + + pub fn poll( + &mut self, + cx: &mut Context<'_>, + ) -> Poll>> + where + THandler: IntoConnectionHandler, + { + if self.dirty { + self.pending_update_events = self + .connections + .iter() + .map(|(peer_id, conn_id)| (*peer_id, *conn_id, self.inner.clone())) + .collect(); + + self.dirty = false; + } + + if let Some((peer_id, conn_id, state)) = self.pending_update_events.pop_front() { + return Poll::Ready(NetworkBehaviourAction::NotifyHandler { + peer_id, + handler: NotifyHandler::One(conn_id), + event: InEvent::UpdateState(state), + }); + } + + self.waker = Some(cx.waker().clone()); + Poll::Pending + } +} + +impl Deref for Shared { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +impl DerefMut for Shared { + fn deref_mut(&mut self) -> &mut Self::Target { + self.dirty = true; + if let Some(waker) = self.waker.take() { + waker.wake(); + } + + &mut self.inner + } +} + impl fmt::Display for OpenError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { @@ -387,30 +476,60 @@ mod tests { use super::*; use crate::{IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters}; use libp2p_core::connection::ConnectionId; - use libp2p_core::PeerId; + use libp2p_core::{Multiaddr, PeerId}; - struct MyBehaviour {} + struct MyBehaviour { + state: Shared, + } - #[derive(Debug, Default)] - struct ConnectionState { - _foo: (), + #[derive(Debug, Default, Clone)] + struct State { + foo: String, + } + + impl MyBehaviour { + fn set_new_state(&mut self, foo: String) { + self.state.foo = foo; + } } impl NetworkBehaviour for MyBehaviour { - type ConnectionHandler = FromFnProto<(), (), (), ConnectionState>; + type ConnectionHandler = FromFnProto<(), (), (), State>; type OutEvent = (); fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn( "/foo/bar/1.0.0", - ConnectionState::default(), + State::default(), 5, 5, - |_stream, _remote_peer_id, _connected_point, _state| async move {}, - |_stream, _remote_peer_id, _connected_point, _state, ()| async move {}, + |stream, _, _, state| async move {}, + |stream, _, _, state, ()| async move {}, ) } + fn inject_connection_established( + &mut self, + peer_id: &PeerId, + connection_id: &ConnectionId, + _endpoint: &ConnectedPoint, + _failed_addresses: Option<&Vec>, + _other_established: usize, + ) { + self.state.register_connection(*peer_id, *connection_id); + } + + fn inject_connection_closed( + &mut self, + peer_id: &PeerId, + connection_id: &ConnectionId, + _: &ConnectedPoint, + _: ::Handler, + _remaining_established: usize, + ) { + self.state.unregister_connection(*peer_id, *connection_id); + } + fn inject_event( &mut self, _peer_id: PeerId, @@ -428,9 +547,13 @@ mod tests { fn poll( &mut self, - _cx: &mut Context<'_>, + cx: &mut Context<'_>, _params: &mut impl PollParameters, ) -> Poll> { + if let Poll::Ready(action) = self.state.poll(cx) { + return Poll::Ready(action); + } + Poll::Pending } } From 8ee59fdced41d858eb88d5e7757b1236440d4f4c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 2 Nov 2022 19:25:09 +1100 Subject: [PATCH 15/34] Don't allow ConnectionHandlers to modify the state It is impossible to implement a general algorithm for how to resolve conflicts without going down the path of CRDTs. --- swarm/src/handler/from_fn.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 3a4afc6bd19..28c048cc817 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -48,14 +48,14 @@ pub fn from_fn TInboundFuture + on_new_inbound: impl Fn(NegotiatedSubstream, PeerId, &ConnectedPoint, &TState) -> TInboundFuture + Send + 'static, on_new_outbound: impl Fn( NegotiatedSubstream, PeerId, &ConnectedPoint, - &mut TState, + &TState, TOutboundOpenInfo, ) -> TOutboundFuture + Send @@ -219,7 +219,7 @@ pub struct FromFnProto { NegotiatedSubstream, PeerId, &ConnectedPoint, - &mut TState, + &TState, ) -> BoxFuture<'static, TInbound> + Send, >, @@ -228,7 +228,7 @@ pub struct FromFnProto { NegotiatedSubstream, PeerId, &ConnectedPoint, - &mut TState, + &TState, TOutboundOpenInfo, ) -> BoxFuture<'static, TOutbound> + Send, @@ -289,7 +289,7 @@ pub struct FromFn { NegotiatedSubstream, PeerId, &ConnectedPoint, - &mut TState, + &TState, ) -> BoxFuture<'static, TInbound> + Send, >, @@ -298,7 +298,7 @@ pub struct FromFn { NegotiatedSubstream, PeerId, &ConnectedPoint, - &mut TState, + &TState, TOutboundInfo, ) -> BoxFuture<'static, TOutbound> + Send, From 7fb438712db0928b2d8b7b52858ab25ca86b1522 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 2 Nov 2022 20:45:20 +1100 Subject: [PATCH 16/34] Implement test --- swarm/src/handler/from_fn.rs | 253 +++++++++++++++++++++++++++++++---- 1 file changed, 225 insertions(+), 28 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 28c048cc817..3b4cccf08d8 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -16,6 +16,7 @@ use std::fmt; use std::future::Future; use std::ops::{Deref, DerefMut}; use std::task::{Context, Poll, Waker}; +use std::time::{Duration, Instant}; use void::Void; /// A low-level building block for protocols that can be expressed as async functions. @@ -266,8 +267,10 @@ where inbound_streams_limit: self.inbound_streams_limit, pending_outbound_streams: VecDeque::default(), pending_outbound_streams_limit: self.pending_outbound_streams_limit, + pending_outbound_stream_waker: None, failed_open: VecDeque::default(), state: self.state, + keep_alive: KeepAlive::Yes, } } @@ -308,10 +311,13 @@ pub struct FromFn { pending_outbound_streams: VecDeque, pending_outbound_streams_limit: usize, + pending_outbound_stream_waker: Option, failed_open: VecDeque>, state: TState, + + keep_alive: KeepAlive, } impl ConnectionHandler @@ -380,6 +386,10 @@ where .push_back(OpenError::LimitExceeded(open_info)); } else { self.pending_outbound_streams.push_back(open_info); + + if let Some(waker) = self.pending_outbound_stream_waker.take() { + waker.wake(); + } } } } @@ -405,14 +415,7 @@ where } fn connection_keep_alive(&self) -> KeepAlive { - if self.inbound_streams.is_empty() - && self.outbound_streams.is_empty() - && self.pending_outbound_streams.is_empty() - { - return KeepAlive::No; - } - - KeepAlive::Yes + self.keep_alive } fn poll( @@ -465,6 +468,20 @@ where outbound_open_info, ), }); + } else { + self.pending_outbound_stream_waker = Some(cx.waker().clone()); + } + + if self.inbound_streams.is_empty() + && self.outbound_streams.is_empty() + && self.pending_outbound_streams.is_empty() + { + if self.keep_alive.is_yes() { + // TODO: Make configurable + self.keep_alive = KeepAlive::Until(Instant::now() + Duration::from_secs(10)) + } + } else { + self.keep_alive = KeepAlive::Yes } Poll::Pending @@ -474,37 +491,195 @@ where #[cfg(test)] mod tests { use super::*; - use crate::{IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters}; + use crate::{ + IntoConnectionHandler, NetworkBehaviour, NetworkBehaviourAction, PollParameters, Swarm, + SwarmEvent, + }; + use futures::{AsyncReadExt, AsyncWriteExt}; + use libp2p::plaintext::PlainText2Config; + use libp2p::yamux; use libp2p_core::connection::ConnectionId; - use libp2p_core::{Multiaddr, PeerId}; + use libp2p_core::transport::MemoryTransport; + use libp2p_core::upgrade::Version; + use libp2p_core::{identity, Multiaddr, PeerId, Transport}; + use std::collections::HashMap; + use std::io; + use std::ops::AddAssign; + + #[async_std::test] + async fn greetings() { + let _ = env_logger::try_init(); + + let mut alice = make_swarm("Alice"); + let mut bob = make_swarm("Bob"); + + let bob_peer_id = *bob.local_peer_id(); + + let listen_id = alice.listen_on("/memory/0".parse().unwrap()).unwrap(); + + let alice_listen_addr = loop { + if let SwarmEvent::NewListenAddr { + address, + listener_id, + } = alice.select_next_some().await + { + if listener_id == listen_id { + break address; + } + } + }; + bob.dial(alice_listen_addr).unwrap(); + + futures::future::join( + async { + loop { + if let SwarmEvent::ConnectionEstablished { .. } = alice.select_next_some().await + { + break; + } + } + }, + async { + loop { + if let SwarmEvent::ConnectionEstablished { .. } = bob.select_next_some().await { + break; + } + } + }, + ) + .await; + + futures::future::join( + async { + alice.behaviour_mut().say_hello(bob_peer_id); + + loop { + if let SwarmEvent::Behaviour(greetings) = alice.select_next_some().await { + assert_eq!(*greetings.get(&Name("Bob".to_owned())).unwrap(), 1); + break; + } + } + }, + async { + loop { + if let SwarmEvent::Behaviour(greetings) = bob.select_next_some().await { + assert_eq!(*greetings.get(&Name("Alice".to_owned())).unwrap(), 1); + break; + } + } + }, + ) + .await; + + alice.behaviour_mut().state.name = Name("Carol".to_owned()); + bob.behaviour_mut().state.name = Name("Steve".to_owned()); + + futures::future::join( + async { + alice.behaviour_mut().say_hello(bob_peer_id); + + loop { + if let SwarmEvent::Behaviour(greetings) = alice.select_next_some().await { + assert_eq!(*greetings.get(&Name("Bob".to_owned())).unwrap(), 1); + assert_eq!(*greetings.get(&Name("Steve".to_owned())).unwrap(), 1); + break; + } + } + }, + async { + loop { + if let SwarmEvent::Behaviour(greetings) = bob.select_next_some().await { + assert_eq!(*greetings.get(&Name("Alice".to_owned())).unwrap(), 1); + assert_eq!(*greetings.get(&Name("Carol".to_owned())).unwrap(), 1); + break; + } + } + }, + ) + .await; + } - struct MyBehaviour { + fn make_swarm(name: &'static str) -> Swarm { + let identity = identity::Keypair::generate_ed25519(); + + let transport = MemoryTransport::new() + .upgrade(Version::V1) + .authenticate(PlainText2Config { + local_public_key: identity.public(), + }) + .multiplex(yamux::YamuxConfig::default()) + .boxed(); + + let swarm = Swarm::new( + transport, + HelloBehaviour { + state: Shared::new(State { + name: Name(name.to_owned()), + }), + pending_messages: Default::default(), + pending_events: Default::default(), + greeting_count: Default::default(), + }, + identity.public().to_peer_id(), + ); + swarm + } + + struct HelloBehaviour { state: Shared, + pending_messages: VecDeque<(PeerId, Name)>, + pending_events: VecDeque>, + greeting_count: HashMap, } - #[derive(Debug, Default, Clone)] + #[derive(Debug, Clone)] struct State { - foo: String, + name: Name, } - impl MyBehaviour { - fn set_new_state(&mut self, foo: String) { - self.state.foo = foo; + #[derive(Debug, Clone, PartialEq, Hash, Eq)] + struct Name(String); + + impl HelloBehaviour { + fn say_hello(&mut self, to: PeerId) { + self.pending_messages + .push_back((to, self.state.name.clone())); } } - impl NetworkBehaviour for MyBehaviour { - type ConnectionHandler = FromFnProto<(), (), (), State>; - type OutEvent = (); + impl NetworkBehaviour for HelloBehaviour { + type ConnectionHandler = FromFnProto, io::Result, Name, State>; + type OutEvent = HashMap; fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn( - "/foo/bar/1.0.0", - State::default(), + "/hello-world/1.0.0", + self.state.clone(), 5, 5, - |stream, _, _, state| async move {}, - |stream, _, _, state, ()| async move {}, + |mut stream, _, _, state| { + let my_name = state.name.to_owned(); + + async move { + let mut received_name = Vec::new(); + stream.read_to_end(&mut received_name).await?; + + stream.write_all(&my_name.0.as_bytes()).await?; + stream.close().await?; + + Ok(Name(String::from_utf8(received_name).unwrap())) + } + }, + |mut stream, _, _, _, name: Name| async move { + stream.write_all(&name.0.as_bytes()).await?; + stream.flush().await?; + stream.close().await?; + + let mut received_name = Vec::new(); + stream.read_to_end(&mut received_name).await?; + + Ok(Name(String::from_utf8(received_name).unwrap())) + }, ) } @@ -537,10 +712,20 @@ mod tests { event: <::Handler as ConnectionHandler>::OutEvent, ) { match event { - OutEvent::InboundFinished(()) => {} - OutEvent::OutboundFinished(()) => {} - OutEvent::FailedToOpen(OpenError::Timeout(())) => {} - OutEvent::FailedToOpen(OpenError::NegotiationFailed((), _neg_error)) => {} + OutEvent::InboundFinished(Ok(name)) => { + self.greeting_count.entry(name).or_default().add_assign(1); + + self.pending_events.push_back(self.greeting_count.clone()) + } + OutEvent::OutboundFinished(Ok(name)) => { + self.greeting_count.entry(name).or_default().add_assign(1); + + self.pending_events.push_back(self.greeting_count.clone()) + } + OutEvent::InboundFinished(_) => {} + OutEvent::OutboundFinished(_) => {} + OutEvent::FailedToOpen(OpenError::Timeout(_)) => {} + OutEvent::FailedToOpen(OpenError::NegotiationFailed(_, _neg_error)) => {} OutEvent::FailedToOpen(OpenError::LimitExceeded(_)) => {} } } @@ -548,12 +733,24 @@ mod tests { fn poll( &mut self, cx: &mut Context<'_>, - _params: &mut impl PollParameters, + _: &mut impl PollParameters, ) -> Poll> { + if let Some(greeting_count) = self.pending_events.pop_front() { + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(greeting_count)); + } + if let Poll::Ready(action) = self.state.poll(cx) { return Poll::Ready(action); } + if let Some((to, name)) = self.pending_messages.pop_front() { + return Poll::Ready(NetworkBehaviourAction::NotifyHandler { + peer_id: to, + handler: NotifyHandler::Any, + event: InEvent::NewOutbound(name), + }); + } + Poll::Pending } } From ac5c236e0dd612bc1d4dcc895604a704ae3c3b0c Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 2 Nov 2022 20:51:03 +1100 Subject: [PATCH 17/34] Remove unnecessary waker --- swarm/src/handler/from_fn.rs | 113 ++++++++++++++++------------------- 1 file changed, 52 insertions(+), 61 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 3b4cccf08d8..a8032373f01 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -267,7 +267,6 @@ where inbound_streams_limit: self.inbound_streams_limit, pending_outbound_streams: VecDeque::default(), pending_outbound_streams_limit: self.pending_outbound_streams_limit, - pending_outbound_stream_waker: None, failed_open: VecDeque::default(), state: self.state, keep_alive: KeepAlive::Yes, @@ -311,7 +310,6 @@ pub struct FromFn { pending_outbound_streams: VecDeque, pending_outbound_streams_limit: usize, - pending_outbound_stream_waker: Option, failed_open: VecDeque>, @@ -386,10 +384,6 @@ where .push_back(OpenError::LimitExceeded(open_info)); } else { self.pending_outbound_streams.push_back(open_info); - - if let Some(waker) = self.pending_outbound_stream_waker.take() { - waker.wake(); - } } } } @@ -468,8 +462,6 @@ where outbound_open_info, ), }); - } else { - self.pending_outbound_stream_waker = Some(cx.waker().clone()); } if self.inbound_streams.is_empty() @@ -477,7 +469,7 @@ where && self.pending_outbound_streams.is_empty() { if self.keep_alive.is_yes() { - // TODO: Make configurable + // TODO: Make timeout configurable self.keep_alive = KeepAlive::Until(Instant::now() + Duration::from_secs(10)) } } else { @@ -514,7 +506,6 @@ mod tests { let mut bob = make_swarm("Bob"); let bob_peer_id = *bob.local_peer_id(); - let listen_id = alice.listen_on("/memory/0".parse().unwrap()).unwrap(); let alice_listen_addr = loop { @@ -532,19 +523,16 @@ mod tests { futures::future::join( async { - loop { - if let SwarmEvent::ConnectionEstablished { .. } = alice.select_next_some().await - { - break; - } - } + while !matches!( + alice.select_next_some().await, + SwarmEvent::ConnectionEstablished { .. } + ) {} }, async { - loop { - if let SwarmEvent::ConnectionEstablished { .. } = bob.select_next_some().await { - break; - } - } + while !matches!( + bob.select_next_some().await, + SwarmEvent::ConnectionEstablished { .. } + ) {} }, ) .await; @@ -599,35 +587,9 @@ mod tests { .await; } - fn make_swarm(name: &'static str) -> Swarm { - let identity = identity::Keypair::generate_ed25519(); - - let transport = MemoryTransport::new() - .upgrade(Version::V1) - .authenticate(PlainText2Config { - local_public_key: identity.public(), - }) - .multiplex(yamux::YamuxConfig::default()) - .boxed(); - - let swarm = Swarm::new( - transport, - HelloBehaviour { - state: Shared::new(State { - name: Name(name.to_owned()), - }), - pending_messages: Default::default(), - pending_events: Default::default(), - greeting_count: Default::default(), - }, - identity.public().to_peer_id(), - ); - swarm - } - struct HelloBehaviour { state: Shared, - pending_messages: VecDeque<(PeerId, Name)>, + pending_messages: VecDeque, pending_events: VecDeque>, greeting_count: HashMap, } @@ -642,18 +604,17 @@ mod tests { impl HelloBehaviour { fn say_hello(&mut self, to: PeerId) { - self.pending_messages - .push_back((to, self.state.name.clone())); + self.pending_messages.push_back(to); } } impl NetworkBehaviour for HelloBehaviour { - type ConnectionHandler = FromFnProto, io::Result, Name, State>; + type ConnectionHandler = FromFnProto, io::Result, (), State>; type OutEvent = HashMap; fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn( - "/hello-world/1.0.0", + "/hello/1.0.0", self.state.clone(), 5, 5, @@ -670,15 +631,19 @@ mod tests { Ok(Name(String::from_utf8(received_name).unwrap())) } }, - |mut stream, _, _, _, name: Name| async move { - stream.write_all(&name.0.as_bytes()).await?; - stream.flush().await?; - stream.close().await?; + |mut stream, _, _, state, _| { + let my_name = state.name.to_owned(); - let mut received_name = Vec::new(); - stream.read_to_end(&mut received_name).await?; + async move { + stream.write_all(&my_name.0.as_bytes()).await?; + stream.flush().await?; + stream.close().await?; - Ok(Name(String::from_utf8(received_name).unwrap())) + let mut received_name = Vec::new(); + stream.read_to_end(&mut received_name).await?; + + Ok(Name(String::from_utf8(received_name).unwrap())) + } }, ) } @@ -743,11 +708,11 @@ mod tests { return Poll::Ready(action); } - if let Some((to, name)) = self.pending_messages.pop_front() { + if let Some(to) = self.pending_messages.pop_front() { return Poll::Ready(NetworkBehaviourAction::NotifyHandler { peer_id: to, handler: NotifyHandler::Any, - event: InEvent::NewOutbound(name), + event: InEvent::NewOutbound(()), }); } @@ -755,6 +720,32 @@ mod tests { } } + fn make_swarm(name: &'static str) -> Swarm { + let identity = identity::Keypair::generate_ed25519(); + + let transport = MemoryTransport::new() + .upgrade(Version::V1) + .authenticate(PlainText2Config { + local_public_key: identity.public(), + }) + .multiplex(yamux::YamuxConfig::default()) + .boxed(); + + let swarm = Swarm::new( + transport, + HelloBehaviour { + state: Shared::new(State { + name: Name(name.to_owned()), + }), + pending_messages: Default::default(), + pending_events: Default::default(), + greeting_count: Default::default(), + }, + identity.public().to_peer_id(), + ); + swarm + } + // TODO: Add test for max pending dials // TODO: Add test for max inbound streams } From 3e81e72413479d4d1a7a3964f93ba46d0c85ea0a Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 2 Nov 2022 21:05:14 +1100 Subject: [PATCH 18/34] Separate clone-able registration data --- protocols/rendezvous/src/handler.rs | 2 +- protocols/rendezvous/src/server.rs | 171 ++++++++++++++++------------ 2 files changed, 99 insertions(+), 74 deletions(-) diff --git a/protocols/rendezvous/src/handler.rs b/protocols/rendezvous/src/handler.rs index d07bf4d248f..5ca4413d46c 100644 --- a/protocols/rendezvous/src/handler.rs +++ b/protocols/rendezvous/src/handler.rs @@ -22,7 +22,7 @@ use crate::codec; use crate::codec::Message; use void::Void; -const PROTOCOL_IDENT: &[u8] = b"/rendezvous/1.0.0"; +pub const PROTOCOL_IDENT: &[u8] = b"/rendezvous/1.0.0"; pub mod inbound; pub mod outbound; diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index dd4731d8a8d..1ba21e8d324 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -19,7 +19,7 @@ // DEALINGS IN THE SOFTWARE. use crate::codec::{Cookie, ErrorCode, Namespace, NewRegistration, Registration, Ttl}; -use crate::handler::inbound; +use crate::handler::{inbound, PROTOCOL_IDENT}; use crate::substream_handler::{InboundSubstreamId, SubstreamConnectionHandler}; use crate::{handler, MAX_TTL, MIN_TTL}; use bimap::BiMap; @@ -29,10 +29,13 @@ use futures::stream::FuturesUnordered; use futures::{FutureExt, StreamExt}; use libp2p_core::connection::ConnectionId; use libp2p_core::PeerId; +use libp2p_swarm::handler::from_fn::FromFnProto; use libp2p_swarm::{ - CloseConnection, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, + from_fn, CloseConnection, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, + PollParameters, }; use std::collections::{HashMap, HashSet, VecDeque}; +use std::io; use std::iter::FromIterator; use std::task::{Context, Poll}; use std::time::Duration; @@ -113,9 +116,13 @@ impl NetworkBehaviour for Behaviour { type OutEvent = Event; fn new_handler(&mut self) -> Self::ConnectionHandler { - let initial_keep_alive = Duration::from_secs(30); + // from_fn( + // PROTOCOL_IDENT, + // self.registrations.clone(), + // + // ) - SubstreamConnectionHandler::new_inbound_only(initial_keep_alive) + todo!() } fn inject_event( @@ -306,14 +313,83 @@ impl RegistrationId { struct ExpiredRegistration(Registration); pub struct Registrations { - registrations_for_peer: BiMap<(PeerId, Namespace), RegistrationId>, - registrations: HashMap, - cookies: HashMap>, + data: RegistrationData, min_ttl: Ttl, max_ttl: Ttl, next_expiry: FuturesUnordered>, } +#[derive(Clone, Default)] +struct RegistrationData { + registrations_for_peer: BiMap<(PeerId, Namespace), RegistrationId>, + registrations: HashMap, + cookies: HashMap>, +} + +impl RegistrationData { + pub fn get( + &mut self, + discover_namespace: Option, + cookie: Option, + limit: Option, + ) -> Result<(impl Iterator + '_, Cookie), CookieNamespaceMismatch> { + let cookie_namespace = cookie.as_ref().and_then(|cookie| cookie.namespace()); + + match (discover_namespace.as_ref(), cookie_namespace) { + // discover all namespace but cookie is specific to a namespace? => bad + (None, Some(_)) => return Err(CookieNamespaceMismatch), + // discover for a namespace but cookie is for a different namesapce? => bad + (Some(namespace), Some(cookie_namespace)) if namespace != cookie_namespace => { + return Err(CookieNamespaceMismatch) + } + // every other combination is fine + _ => {} + } + + let mut reggos_of_last_discover = cookie + .and_then(|cookie| self.cookies.get(&cookie)) + .cloned() + .unwrap_or_default(); + + let ids = self + .registrations_for_peer + .iter() + .filter_map({ + |((_, namespace), registration_id)| { + if reggos_of_last_discover.contains(registration_id) { + return None; + } + + match discover_namespace.as_ref() { + Some(discover_namespace) if discover_namespace == namespace => { + Some(registration_id) + } + Some(_) => None, + None => Some(registration_id), + } + } + }) + .take(limit.unwrap_or(u64::MAX) as usize) + .cloned() + .collect::>(); + + reggos_of_last_discover.extend(&ids); + + let new_cookie = discover_namespace + .map(Cookie::for_namespace) + .unwrap_or_else(Cookie::for_all_namespaces); + self.cookies + .insert(new_cookie.clone(), reggos_of_last_discover); + + let reggos = &self.registrations; + let registrations = ids + .into_iter() + .map(move |id| reggos.get(&id).expect("bad internal datastructure")); + + Ok((registrations, new_cookie)) + } +} + #[derive(Debug, thiserror::Error)] pub enum TtlOutOfRange { #[error("Requested TTL ({requested}s) is too long; max {bound}s")] @@ -331,11 +407,9 @@ impl Default for Registrations { impl Registrations { pub fn with_config(config: Config) -> Self { Self { - registrations_for_peer: Default::default(), - registrations: Default::default(), + data: RegistrationData::default(), min_ttl: config.min_ttl, max_ttl: config.max_ttl, - cookies: Default::default(), next_expiry: FuturesUnordered::from_iter(vec![futures::future::pending().boxed()]), } } @@ -362,13 +436,14 @@ impl Registrations { let registration_id = RegistrationId::new(); if let Some(old_registration) = self + .data .registrations_for_peer .get_by_left(&(new_registration.record.peer_id(), namespace.clone())) { - self.registrations.remove(old_registration); + self.data.registrations.remove(old_registration); } - self.registrations_for_peer.insert( + self.data.registrations_for_peer.insert( (new_registration.record.peer_id(), namespace.clone()), registration_id, ); @@ -378,7 +453,8 @@ impl Registrations { record: new_registration.record, ttl, }; - self.registrations + self.data + .registrations .insert(registration_id, registration.clone()); let next_expiry = futures_timer::Delay::new(Duration::from_secs(ttl as u64)) @@ -392,11 +468,12 @@ impl Registrations { pub fn remove(&mut self, namespace: Namespace, peer_id: PeerId) { let reggo_to_remove = self + .data .registrations_for_peer .remove_by_left(&(peer_id, namespace)); if let Some((_, reggo_to_remove)) = reggo_to_remove { - self.registrations.remove(®go_to_remove); + self.data.registrations.remove(®go_to_remove); } } @@ -406,60 +483,7 @@ impl Registrations { cookie: Option, limit: Option, ) -> Result<(impl Iterator + '_, Cookie), CookieNamespaceMismatch> { - let cookie_namespace = cookie.as_ref().and_then(|cookie| cookie.namespace()); - - match (discover_namespace.as_ref(), cookie_namespace) { - // discover all namespace but cookie is specific to a namespace? => bad - (None, Some(_)) => return Err(CookieNamespaceMismatch), - // discover for a namespace but cookie is for a different namesapce? => bad - (Some(namespace), Some(cookie_namespace)) if namespace != cookie_namespace => { - return Err(CookieNamespaceMismatch) - } - // every other combination is fine - _ => {} - } - - let mut reggos_of_last_discover = cookie - .and_then(|cookie| self.cookies.get(&cookie)) - .cloned() - .unwrap_or_default(); - - let ids = self - .registrations_for_peer - .iter() - .filter_map({ - |((_, namespace), registration_id)| { - if reggos_of_last_discover.contains(registration_id) { - return None; - } - - match discover_namespace.as_ref() { - Some(discover_namespace) if discover_namespace == namespace => { - Some(registration_id) - } - Some(_) => None, - None => Some(registration_id), - } - } - }) - .take(limit.unwrap_or(u64::MAX) as usize) - .cloned() - .collect::>(); - - reggos_of_last_discover.extend(&ids); - - let new_cookie = discover_namespace - .map(Cookie::for_namespace) - .unwrap_or_else(Cookie::for_all_namespaces); - self.cookies - .insert(new_cookie.clone(), reggos_of_last_discover); - - let reggos = &self.registrations; - let registrations = ids - .into_iter() - .map(move |id| reggos.get(&id).expect("bad internal datastructure")); - - Ok((registrations, new_cookie)) + self.data.get(discover_namespace, cookie, limit) } fn poll(&mut self, cx: &mut Context<'_>) -> Poll { @@ -468,16 +492,17 @@ impl Registrations { ); // clean up our cookies - self.cookies.retain(|_, registrations| { + self.data.cookies.retain(|_, registrations| { registrations.remove(&expired_registration); // retain all cookies where there are still registrations left !registrations.is_empty() }); - self.registrations_for_peer + self.data + .registrations_for_peer .remove_by_right(&expired_registration); - match self.registrations.remove(&expired_registration) { + match self.data.registrations.remove(&expired_registration) { None => self.poll(cx), Some(registration) => Poll::Ready(ExpiredRegistration(registration)), } @@ -680,11 +705,11 @@ mod tests { .unwrap(); let (_, _) = registrations.get(None, None, None).unwrap(); - assert_eq!(registrations.cookies.len(), 1); + assert_eq!(registrations.data.cookies.len(), 1); let _ = registrations.next_event_in_at_most(3).await; - assert_eq!(registrations.cookies.len(), 0); + assert_eq!(registrations.data.cookies.len(), 0); } #[test] From 49c6eb16f4187bfaaf774be4726815c894da2fc5 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Wed, 2 Nov 2022 22:15:08 +1100 Subject: [PATCH 19/34] WIP: Migrate `libp2p-rendezvous` to `from_fn` --- protocols/rendezvous/src/client.rs | 334 ++++--- protocols/rendezvous/src/handler.rs | 2 +- protocols/rendezvous/src/handler/inbound.rs | 192 ---- protocols/rendezvous/src/handler/outbound.rs | 134 --- protocols/rendezvous/src/lib.rs | 4 +- protocols/rendezvous/src/server.rs | 918 +++++++++--------- protocols/rendezvous/src/substream_handler.rs | 553 ----------- 7 files changed, 667 insertions(+), 1470 deletions(-) delete mode 100644 protocols/rendezvous/src/handler/inbound.rs delete mode 100644 protocols/rendezvous/src/handler/outbound.rs delete mode 100644 protocols/rendezvous/src/substream_handler.rs diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index 5ceb945a4f1..8be371222eb 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -18,24 +18,31 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::codec::{Cookie, ErrorCode, Namespace, NewRegistration, Registration, Ttl}; -use crate::handler; -use crate::handler::outbound; -use crate::handler::outbound::OpenInfo; -use crate::substream_handler::SubstreamConnectionHandler; +use crate::codec::{ + Cookie, Error, ErrorCode, Message, Namespace, NewRegistration, Registration, RendezvousCodec, + Ttl, +}; +use crate::PROTOCOL_IDENT; +use asynchronous_codec::Framed; use futures::future::BoxFuture; use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::stream::StreamExt; +use futures::SinkExt; use instant::Duration; use libp2p_core::connection::ConnectionId; use libp2p_core::identity::error::SigningError; use libp2p_core::identity::Keypair; -use libp2p_core::{Multiaddr, PeerId, PeerRecord}; +use libp2p_core::{ConnectedPoint, Multiaddr, PeerId, PeerRecord}; +use libp2p_swarm::handler::from_fn; +use libp2p_swarm::handler::from_fn::OutEvent; use libp2p_swarm::{ - CloseConnection, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, + CloseConnection, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, + PollParameters, }; use std::collections::{HashMap, VecDeque}; +use std::future::Future; +use std::io; use std::iter::FromIterator; use std::task::{Context, Poll}; @@ -43,7 +50,7 @@ pub struct Behaviour { events: VecDeque< NetworkBehaviourAction< Event, - SubstreamConnectionHandler, + from_fn::FromFnProto<(), Result, OpenInfo, ()>, >, >, keypair: Keypair, @@ -86,9 +93,7 @@ impl Behaviour { self.events .push_back(NetworkBehaviourAction::NotifyHandler { peer_id: rendezvous_node, - event: handler::OutboundInEvent::NewSubstream { - open_info: OpenInfo::UnregisterRequest(namespace), - }, + event: from_fn::InEvent::NewOutbound(OpenInfo::UnregisterRequest(namespace)), handler: NotifyHandler::Any, }); } @@ -110,13 +115,11 @@ impl Behaviour { self.events .push_back(NetworkBehaviourAction::NotifyHandler { peer_id: rendezvous_node, - event: handler::OutboundInEvent::NewSubstream { - open_info: OpenInfo::DiscoverRequest { - namespace: ns, - cookie, - limit, - }, - }, + event: from_fn::InEvent::NewOutbound(OpenInfo::DiscoverRequest { + namespace: ns, + cookie, + limit, + }), handler: NotifyHandler::Any, }); } @@ -163,15 +166,49 @@ pub enum Event { Expired { peer: PeerId }, } +#[derive(Debug, Clone)] +pub enum OutboundEvent { + Registered { + namespace: Namespace, + ttl: Ttl, + }, + RegisterFailed(Namespace, ErrorCode), + Discovered { + registrations: Vec, + cookie: Cookie, + }, + DiscoverFailed { + namespace: Option, + error: ErrorCode, + }, +} + +#[allow(clippy::large_enum_variant)] +#[allow(clippy::enum_variant_names)] +#[derive(Debug, Clone)] +pub enum OpenInfo { + RegisterRequest(NewRegistration), + UnregisterRequest(Namespace), + DiscoverRequest { + namespace: Option, + cookie: Option, + limit: Option, + }, +} + impl NetworkBehaviour for Behaviour { - type ConnectionHandler = - SubstreamConnectionHandler; + type ConnectionHandler = from_fn::FromFnProto<(), Result, OpenInfo, ()>; type OutEvent = Event; fn new_handler(&mut self) -> Self::ConnectionHandler { - let initial_keep_alive = Duration::from_secs(30); - - SubstreamConnectionHandler::new_outbound_only(initial_keep_alive) + from_fn::from_fn( + PROTOCOL_IDENT, + (), + 10, + 10, + |_, _, _, _| async {}, + outbound_stream_handler, + ) } fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { @@ -185,30 +222,42 @@ impl NetworkBehaviour for Behaviour { fn inject_event( &mut self, - peer_id: PeerId, - connection_id: ConnectionId, - event: handler::OutboundOutEvent, + __id: PeerId, + _: ConnectionId, + event: from_fn::OutEvent<(), Result, OpenInfo>, ) { - let new_events = match event { - handler::OutboundOutEvent::InboundEvent { message, .. } => void::unreachable(message), - handler::OutboundOutEvent::OutboundEvent { message, .. } => handle_outbound_event( - message, - peer_id, - &mut self.discovered_peers, - &mut self.expiring_registrations, - ), - handler::OutboundOutEvent::InboundError { error, .. } => void::unreachable(error), - handler::OutboundOutEvent::OutboundError { error, .. } => { - log::warn!("Connection with peer {} failed: {}", peer_id, error); - - vec![NetworkBehaviourAction::CloseConnection { - peer_id, - connection: CloseConnection::One(connection_id), - }] - } - }; - - self.events.extend(new_events); + match event { + OutEvent::InboundFinished(()) => {} + OutEvent::OutboundFinished(Ok(OutboundEvent::Discovered { .. })) => {} + OutEvent::OutboundFinished(Ok(OutboundEvent::Registered { .. })) => {} + OutEvent::OutboundFinished(Ok(OutboundEvent::DiscoverFailed { .. })) => {} + OutEvent::OutboundFinished(Ok(OutboundEvent::RegisterFailed(..))) => {} + OutEvent::OutboundFinished(Err(e)) => {} + OutEvent::FailedToOpen(from_fn::OpenError::Timeout(info)) => {} + OutEvent::FailedToOpen(from_fn::OpenError::NegotiationFailed(..)) => {} + OutEvent::FailedToOpen(from_fn::OpenError::LimitExceeded(..)) => {} + } + // + // let new_events = match event { + // handler::OutboundOutEvent::InboundEvent { message, .. } => void::unreachable(message), + // handler::OutboundOutEvent::OutboundEvent { message, .. } => handle_outbound_event( + // message, + // peer_id, + // &mut self.discovered_peers, + // &mut self.expiring_registrations, + // ), + // handler::OutboundOutEvent::InboundError { error, .. } => void::unreachable(error), + // handler::OutboundOutEvent::OutboundError { error, .. } => { + // log::warn!("Connection with peer {} failed: {}", peer_id, error); + // + // vec![NetworkBehaviourAction::CloseConnection { + // peer_id, + // connection: CloseConnection::One(connection_id), + // }] + // } + // }; + // + // self.events.extend(new_events); } fn poll( @@ -237,13 +286,13 @@ impl NetworkBehaviour for Behaviour { let action = match PeerRecord::new(&self.keypair, external_addresses) { Ok(peer_record) => NetworkBehaviourAction::NotifyHandler { peer_id: rendezvous_node, - event: handler::OutboundInEvent::NewSubstream { - open_info: OpenInfo::RegisterRequest(NewRegistration { + event: from_fn::InEvent::NewOutbound(OpenInfo::RegisterRequest( + NewRegistration { namespace, record: peer_record, ttl, - }), - }, + }, + )), handler: NotifyHandler::Any, }, Err(signing_error) => NetworkBehaviourAction::GenerateEvent(Event::RegisterFailed( @@ -267,70 +316,137 @@ impl NetworkBehaviour for Behaviour { } } -fn handle_outbound_event( - event: outbound::OutEvent, - peer_id: PeerId, - discovered_peers: &mut HashMap<(PeerId, Namespace), Vec>, - expiring_registrations: &mut FuturesUnordered>, -) -> Vec< - NetworkBehaviourAction< - Event, - SubstreamConnectionHandler, - >, -> { - match event { - outbound::OutEvent::Registered { namespace, ttl } => { - vec![NetworkBehaviourAction::GenerateEvent(Event::Registered { - rendezvous_node: peer_id, - ttl, - namespace, - })] - } - outbound::OutEvent::RegisterFailed(namespace, error) => { - vec![NetworkBehaviourAction::GenerateEvent( - Event::RegisterFailed(RegisterError::Remote { - rendezvous_node: peer_id, - namespace, - error, - }), - )] - } - outbound::OutEvent::Discovered { - registrations, - cookie, - } => { - discovered_peers.extend(registrations.iter().map(|registration| { - let peer_id = registration.record.peer_id(); - let namespace = registration.namespace.clone(); +// fn handle_outbound_event( +// event: outbound::OutEvent, +// peer_id: PeerId, +// discovered_peers: &mut HashMap<(PeerId, Namespace), Vec>, +// expiring_registrations: &mut FuturesUnordered>, +// ) -> Vec< +// NetworkBehaviourAction< +// Event, +// SubstreamConnectionHandler, +// >, +// > { +// match event { +// outbound::OutEvent::Registered { namespace, ttl } => { +// vec![NetworkBehaviourAction::GenerateEvent(Event::Registered { +// rendezvous_node: peer_id, +// ttl, +// namespace, +// })] +// } +// outbound::OutEvent::RegisterFailed(namespace, error) => { +// vec![NetworkBehaviourAction::GenerateEvent( +// Event::RegisterFailed(RegisterError::Remote { +// rendezvous_node: peer_id, +// namespace, +// error, +// }), +// )] +// } +// outbound::OutEvent::Discovered { +// registrations, +// cookie, +// } => { +// discovered_peers.extend(registrations.iter().map(|registration| { +// let peer_id = registration.record.peer_id(); +// let namespace = registration.namespace.clone(); +// +// let addresses = registration.record.addresses().to_vec(); +// +// ((peer_id, namespace), addresses) +// })); +// expiring_registrations.extend(registrations.iter().cloned().map(|registration| { +// async move { +// // if the timer errors we consider it expired +// futures_timer::Delay::new(Duration::from_secs(registration.ttl as u64)).await; +// +// (registration.record.peer_id(), registration.namespace) +// } +// .boxed() +// })); +// +// vec![NetworkBehaviourAction::GenerateEvent(Event::Discovered { +// rendezvous_node: peer_id, +// registrations, +// cookie, +// })] +// } +// outbound::OutEvent::DiscoverFailed { namespace, error } => { +// vec![NetworkBehaviourAction::GenerateEvent( +// Event::DiscoverFailed { +// rendezvous_node: peer_id, +// namespace, +// error, +// }, +// )] +// } +// } +// } - let addresses = registration.record.addresses().to_vec(); +fn outbound_stream_handler( + substream: NegotiatedSubstream, + _: PeerId, + _: &ConnectedPoint, + _: &(), + request: OpenInfo, +) -> impl Future> { + let mut substream = Framed::new(substream, RendezvousCodec::default()); - ((peer_id, namespace), addresses) - })); - expiring_registrations.extend(registrations.iter().cloned().map(|registration| { - async move { - // if the timer errors we consider it expired - futures_timer::Delay::new(Duration::from_secs(registration.ttl as u64)).await; + async move { + substream + .send(match request.clone() { + OpenInfo::RegisterRequest(new_registration) => Message::Register(new_registration), + OpenInfo::UnregisterRequest(namespace) => Message::Unregister(namespace), + OpenInfo::DiscoverRequest { + namespace, + cookie, + limit, + } => Message::Discover { + namespace, + cookie, + limit, + }, + }) + .await?; - (registration.record.peer_id(), registration.namespace) - } - .boxed() - })); + let response = substream.next().await.transpose()?; - vec![NetworkBehaviourAction::GenerateEvent(Event::Discovered { - rendezvous_node: peer_id, + let out_event = match (request, response) { + (OpenInfo::RegisterRequest(r), Some(Message::RegisterResponse(Ok(ttl)))) => { + OutboundEvent::Registered { + namespace: r.namespace, + ttl, + } + } + (OpenInfo::RegisterRequest(r), Some(Message::RegisterResponse(Err(e)))) => { + OutboundEvent::RegisterFailed(r.namespace, e) + } + ( + OpenInfo::DiscoverRequest { .. }, + Some(Message::DiscoverResponse(Ok((registrations, cookie)))), + ) => OutboundEvent::Discovered { registrations, cookie, - })] - } - outbound::OutEvent::DiscoverFailed { namespace, error } => { - vec![NetworkBehaviourAction::GenerateEvent( - Event::DiscoverFailed { - rendezvous_node: peer_id, - namespace, - error, - }, - )] - } + }, + ( + OpenInfo::DiscoverRequest { namespace, .. }, + Some(Message::DiscoverResponse(Err(error))), + ) => OutboundEvent::DiscoverFailed { namespace, error }, + (OpenInfo::UnregisterRequest(_), None) => { + // All good. + + todo!() + } + (_, None) => { + // EOF? + todo!() + } + _ => { + panic!("protocol violation") // TODO: Make two different codecs to avoid this? + } + }; + + Ok(out_event) } } diff --git a/protocols/rendezvous/src/handler.rs b/protocols/rendezvous/src/handler.rs index 5ca4413d46c..b7f06647aa3 100644 --- a/protocols/rendezvous/src/handler.rs +++ b/protocols/rendezvous/src/handler.rs @@ -22,7 +22,7 @@ use crate::codec; use crate::codec::Message; use void::Void; -pub const PROTOCOL_IDENT: &[u8] = b"/rendezvous/1.0.0"; +pub const PROTOCOL_IDENT: &str = "/rendezvous/1.0.0"; pub mod inbound; pub mod outbound; diff --git a/protocols/rendezvous/src/handler/inbound.rs b/protocols/rendezvous/src/handler/inbound.rs deleted file mode 100644 index 3f432bee6bd..00000000000 --- a/protocols/rendezvous/src/handler/inbound.rs +++ /dev/null @@ -1,192 +0,0 @@ -// Copyright 2021 COMIT Network. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::codec::{ - Cookie, ErrorCode, Message, Namespace, NewRegistration, Registration, RendezvousCodec, Ttl, -}; -use crate::handler::Error; -use crate::handler::PROTOCOL_IDENT; -use crate::substream_handler::{Next, PassthroughProtocol, SubstreamHandler}; -use asynchronous_codec::Framed; -use futures::{SinkExt, StreamExt}; -use libp2p_swarm::{NegotiatedSubstream, SubstreamProtocol}; -use std::fmt; -use std::task::{Context, Poll}; - -/// The state of an inbound substream (i.e. the remote node opened it). -#[allow(clippy::large_enum_variant)] -#[allow(clippy::enum_variant_names)] -pub enum Stream { - /// We are in the process of reading a message from the substream. - PendingRead(Framed), - /// We read a message, dispatched it to the behaviour and are waiting for the response. - PendingBehaviour(Framed), - /// We are in the process of sending a response. - PendingSend(Framed, Message), - /// We've sent the message and are now closing down the substream. - PendingClose(Framed), -} - -impl fmt::Debug for Stream { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Stream::PendingRead(_) => write!(f, "Inbound::PendingRead"), - Stream::PendingBehaviour(_) => write!(f, "Inbound::PendingBehaviour"), - Stream::PendingSend(_, _) => write!(f, "Inbound::PendingSend"), - Stream::PendingClose(_) => write!(f, "Inbound::PendingClose"), - } - } -} - -#[allow(clippy::large_enum_variant)] -#[allow(clippy::enum_variant_names)] -#[derive(Debug, Clone)] -pub enum OutEvent { - RegistrationRequested(NewRegistration), - UnregisterRequested(Namespace), - DiscoverRequested { - namespace: Option, - cookie: Option, - limit: Option, - }, -} - -#[derive(Debug)] -pub enum InEvent { - RegisterResponse { - ttl: Ttl, - }, - DeclineRegisterRequest(ErrorCode), - DiscoverResponse { - discovered: Vec, - cookie: Cookie, - }, - DeclineDiscoverRequest(ErrorCode), -} - -impl SubstreamHandler for Stream { - type InEvent = InEvent; - type OutEvent = OutEvent; - type Error = Error; - type OpenInfo = (); - - fn upgrade( - open_info: Self::OpenInfo, - ) -> SubstreamProtocol { - SubstreamProtocol::new(PassthroughProtocol::new(PROTOCOL_IDENT), open_info) - } - - fn new(substream: NegotiatedSubstream, _: Self::OpenInfo) -> Self { - Stream::PendingRead(Framed::new(substream, RendezvousCodec::default())) - } - - fn inject_event(self, event: Self::InEvent) -> Self { - match (event, self) { - (InEvent::RegisterResponse { ttl }, Stream::PendingBehaviour(substream)) => { - Stream::PendingSend(substream, Message::RegisterResponse(Ok(ttl))) - } - (InEvent::DeclineRegisterRequest(error), Stream::PendingBehaviour(substream)) => { - Stream::PendingSend(substream, Message::RegisterResponse(Err(error))) - } - ( - InEvent::DiscoverResponse { discovered, cookie }, - Stream::PendingBehaviour(substream), - ) => Stream::PendingSend( - substream, - Message::DiscoverResponse(Ok((discovered, cookie))), - ), - (InEvent::DeclineDiscoverRequest(error), Stream::PendingBehaviour(substream)) => { - Stream::PendingSend(substream, Message::DiscoverResponse(Err(error))) - } - (event, inbound) => { - debug_assert!(false, "{:?} cannot handle event {:?}", inbound, event); - - inbound - } - } - } - - fn advance(self, cx: &mut Context<'_>) -> Result, Self::Error> { - let next_state = match self { - Stream::PendingRead(mut substream) => { - match substream.poll_next_unpin(cx).map_err(Error::ReadMessage)? { - Poll::Ready(Some(msg)) => { - let event = match msg { - Message::Register(registration) => { - OutEvent::RegistrationRequested(registration) - } - Message::Discover { - cookie, - namespace, - limit, - } => OutEvent::DiscoverRequested { - cookie, - namespace, - limit, - }, - Message::Unregister(namespace) => { - OutEvent::UnregisterRequested(namespace) - } - other => return Err(Error::BadMessage(other)), - }; - - Next::EmitEvent { - event, - next_state: Stream::PendingBehaviour(substream), - } - } - Poll::Ready(None) => return Err(Error::UnexpectedEndOfStream), - Poll::Pending => Next::Pending { - next_state: Stream::PendingRead(substream), - }, - } - } - Stream::PendingBehaviour(substream) => Next::Pending { - next_state: Stream::PendingBehaviour(substream), - }, - Stream::PendingSend(mut substream, message) => match substream - .poll_ready_unpin(cx) - .map_err(Error::WriteMessage)? - { - Poll::Ready(()) => { - substream - .start_send_unpin(message) - .map_err(Error::WriteMessage)?; - - Next::Continue { - next_state: Stream::PendingClose(substream), - } - } - Poll::Pending => Next::Pending { - next_state: Stream::PendingSend(substream, message), - }, - }, - Stream::PendingClose(mut substream) => match substream.poll_close_unpin(cx) { - Poll::Ready(Ok(())) => Next::Done, - Poll::Ready(Err(_)) => Next::Done, // there is nothing we can do about an error during close - Poll::Pending => Next::Pending { - next_state: Stream::PendingClose(substream), - }, - }, - }; - - Ok(next_state) - } -} diff --git a/protocols/rendezvous/src/handler/outbound.rs b/protocols/rendezvous/src/handler/outbound.rs deleted file mode 100644 index d461e7c7294..00000000000 --- a/protocols/rendezvous/src/handler/outbound.rs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright 2021 COMIT Network. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::codec::{Cookie, Message, NewRegistration, RendezvousCodec}; -use crate::handler::Error; -use crate::handler::PROTOCOL_IDENT; -use crate::substream_handler::{FutureSubstream, Next, PassthroughProtocol, SubstreamHandler}; -use crate::{ErrorCode, Namespace, Registration, Ttl}; -use asynchronous_codec::Framed; -use futures::{SinkExt, TryFutureExt, TryStreamExt}; -use libp2p_swarm::{NegotiatedSubstream, SubstreamProtocol}; -use std::task::Context; -use void::Void; - -pub struct Stream(FutureSubstream); - -impl SubstreamHandler for Stream { - type InEvent = Void; - type OutEvent = OutEvent; - type Error = Error; - type OpenInfo = OpenInfo; - - fn upgrade( - open_info: Self::OpenInfo, - ) -> SubstreamProtocol { - SubstreamProtocol::new(PassthroughProtocol::new(PROTOCOL_IDENT), open_info) - } - - fn new(substream: NegotiatedSubstream, info: Self::OpenInfo) -> Self { - let mut stream = Framed::new(substream, RendezvousCodec::default()); - let sent_message = match info { - OpenInfo::RegisterRequest(new_registration) => Message::Register(new_registration), - OpenInfo::UnregisterRequest(namespace) => Message::Unregister(namespace), - OpenInfo::DiscoverRequest { - namespace, - cookie, - limit, - } => Message::Discover { - namespace, - cookie, - limit, - }, - }; - - Self(FutureSubstream::new(async move { - use Message::*; - use OutEvent::*; - - stream - .send(sent_message.clone()) - .map_err(Error::WriteMessage) - .await?; - let received_message = stream.try_next().map_err(Error::ReadMessage).await?; - let received_message = received_message.ok_or(Error::UnexpectedEndOfStream)?; - - let event = match (sent_message, received_message) { - (Register(registration), RegisterResponse(Ok(ttl))) => Registered { - namespace: registration.namespace, - ttl, - }, - (Register(registration), RegisterResponse(Err(error))) => { - RegisterFailed(registration.namespace, error) - } - (Discover { .. }, DiscoverResponse(Ok((registrations, cookie)))) => Discovered { - registrations, - cookie, - }, - (Discover { namespace, .. }, DiscoverResponse(Err(error))) => { - DiscoverFailed { namespace, error } - } - (.., other) => return Err(Error::BadMessage(other)), - }; - - stream.close().map_err(Error::WriteMessage).await?; - - Ok(event) - })) - } - - fn inject_event(self, event: Self::InEvent) -> Self { - void::unreachable(event) - } - - fn advance(self, cx: &mut Context<'_>) -> Result, Self::Error> { - Ok(self.0.advance(cx)?.map_state(Stream)) - } -} - -#[derive(Debug, Clone)] -pub enum OutEvent { - Registered { - namespace: Namespace, - ttl: Ttl, - }, - RegisterFailed(Namespace, ErrorCode), - Discovered { - registrations: Vec, - cookie: Cookie, - }, - DiscoverFailed { - namespace: Option, - error: ErrorCode, - }, -} - -#[allow(clippy::large_enum_variant)] -#[allow(clippy::enum_variant_names)] -#[derive(Debug)] -pub enum OpenInfo { - RegisterRequest(NewRegistration), - UnregisterRequest(Namespace), - DiscoverRequest { - namespace: Option, - cookie: Option, - limit: Option, - }, -} diff --git a/protocols/rendezvous/src/lib.rs b/protocols/rendezvous/src/lib.rs index 08aae950068..e3c61539b38 100644 --- a/protocols/rendezvous/src/lib.rs +++ b/protocols/rendezvous/src/lib.rs @@ -22,9 +22,9 @@ pub use self::codec::{Cookie, ErrorCode, Namespace, NamespaceTooLong, Registration, Ttl}; +const PROTOCOL_IDENT: &str = "/rendezvous/1.0.0"; + mod codec; -mod handler; -mod substream_handler; /// If unspecified, rendezvous nodes should assume a TTL of 2h. /// diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index 1ba21e8d324..d7e12402132 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -18,23 +18,26 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::codec::{Cookie, ErrorCode, Namespace, NewRegistration, Registration, Ttl}; -use crate::handler::{inbound, PROTOCOL_IDENT}; -use crate::substream_handler::{InboundSubstreamId, SubstreamConnectionHandler}; -use crate::{handler, MAX_TTL, MIN_TTL}; +use crate::codec::{ + Cookie, Error, ErrorCode, Message, Namespace, NewRegistration, Registration, RendezvousCodec, + Ttl, +}; +use crate::{MAX_TTL, MIN_TTL, PROTOCOL_IDENT}; +use asynchronous_codec::Framed; use bimap::BiMap; use futures::future::BoxFuture; -use futures::ready; use futures::stream::FuturesUnordered; +use futures::{ready, SinkExt}; use futures::{FutureExt, StreamExt}; use libp2p_core::connection::ConnectionId; -use libp2p_core::PeerId; -use libp2p_swarm::handler::from_fn::FromFnProto; +use libp2p_core::{ConnectedPoint, Multiaddr, PeerId}; +use libp2p_swarm::handler::from_fn; use libp2p_swarm::{ - from_fn, CloseConnection, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, - PollParameters, + from_fn, CloseConnection, IntoConnectionHandler, NegotiatedSubstream, NetworkBehaviour, + NetworkBehaviourAction, PollParameters, }; -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, HashSet}; +use std::future::Future; use std::io; use std::iter::FromIterator; use std::task::{Context, Poll}; @@ -42,10 +45,8 @@ use std::time::Duration; use void::Void; pub struct Behaviour { - events: VecDeque< - NetworkBehaviourAction>, - >, registrations: Registrations, + registration_data: from_fn::Shared, } pub struct Config { @@ -78,8 +79,8 @@ impl Behaviour { /// Create a new instance of the rendezvous [`NetworkBehaviour`]. pub fn new(config: Config) -> Self { Self { - events: Default::default(), registrations: Registrations::with_config(config), + registration_data: from_fn::Shared::new(RegistrationData::default()), } } } @@ -112,42 +113,73 @@ pub enum Event { } impl NetworkBehaviour for Behaviour { - type ConnectionHandler = SubstreamConnectionHandler; + type ConnectionHandler = + from_fn::FromFnProto, Error>, Void, Void, RegistrationData>; type OutEvent = Event; fn new_handler(&mut self) -> Self::ConnectionHandler { - // from_fn( - // PROTOCOL_IDENT, - // self.registrations.clone(), - // - // ) + from_fn( + PROTOCOL_IDENT, + self.registration_data.clone(), + 10, + 10, + inbound_stream_handler, + |_, _, _, _, never| async move { void::unreachable(never) }, + ) + } + + fn inject_connection_established( + &mut self, + peer_id: &PeerId, + connection_id: &ConnectionId, + _: &ConnectedPoint, + _: Option<&Vec>, + _: usize, + ) { + self.registration_data + .register_connection(*peer_id, *connection_id) + } - todo!() + fn inject_connection_closed( + &mut self, + peer_id: &PeerId, + connection_id: &ConnectionId, + _: &ConnectedPoint, + _: ::Handler, + _remaining_established: usize, + ) { + self.registration_data + .unregister_connection(*peer_id, *connection_id) } fn inject_event( &mut self, peer_id: PeerId, - connection: ConnectionId, - event: handler::InboundOutEvent, + _: ConnectionId, + event: from_fn::OutEvent, Error>, Void, Void>, ) { - let new_events = match event { - handler::InboundOutEvent::InboundEvent { id, message } => { - handle_inbound_event(message, peer_id, connection, id, &mut self.registrations) - } - handler::InboundOutEvent::OutboundEvent { message, .. } => void::unreachable(message), - handler::InboundOutEvent::InboundError { error, .. } => { - log::warn!("Connection with peer {} failed: {}", peer_id, error); - - vec![NetworkBehaviourAction::CloseConnection { - peer_id, - connection: CloseConnection::One(connection), - }] + match event { + from_fn::OutEvent::InboundFinished(Ok(Some(InboundOutEvent::NewRegistration( + new_registration, + )))) => self + .registrations + .add(new_registration, &mut self.registration_data), + from_fn::OutEvent::InboundFinished(Ok(Some(InboundOutEvent::Unregister( + namespace, + )))) => self + .registrations + .remove(namespace, peer_id, &mut self.registration_data), + from_fn::OutEvent::OutboundFinished(_) => {} + from_fn::OutEvent::FailedToOpen(never) => match never { + from_fn::OpenError::Timeout(never) => void::unreachable(never), + from_fn::OpenError::LimitExceeded(never) => void::unreachable(never), + from_fn::OpenError::NegotiationFailed(never, _) => void::unreachable(never), + }, + from_fn::OutEvent::InboundFinished(Err(error)) => { + log::debug!("Inbound stream from {peer_id} failed: {error}"); } - handler::InboundOutEvent::OutboundError { error, .. } => void::unreachable(error), - }; - - self.events.extend(new_events); + from_fn::OutEvent::InboundFinished(Ok(None)) => {} + } } fn poll( @@ -155,151 +187,95 @@ impl NetworkBehaviour for Behaviour { cx: &mut Context<'_>, _: &mut impl PollParameters, ) -> Poll> { - if let Poll::Ready(ExpiredRegistration(registration)) = self.registrations.poll(cx) { + if let Poll::Ready(ExpiredRegistration(registration)) = + self.registrations.poll(&mut self.registration_data, cx) + { return Poll::Ready(NetworkBehaviourAction::GenerateEvent( Event::RegistrationExpired(registration), )); } - if let Some(event) = self.events.pop_front() { - return Poll::Ready(event); + if let Poll::Ready(action) = self.registration_data.poll(cx) { + return Poll::Ready(action); } Poll::Pending } } -fn handle_inbound_event( - event: inbound::OutEvent, - peer_id: PeerId, - connection: ConnectionId, - id: InboundSubstreamId, - registrations: &mut Registrations, -) -> Vec>> { - match event { - // bad registration - inbound::OutEvent::RegistrationRequested(registration) - if registration.record.peer_id() != peer_id => - { - let error = ErrorCode::NotAuthorized; - - vec![ - NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::One(connection), - event: handler::InboundInEvent::NotifyInboundSubstream { - id, - message: inbound::InEvent::DeclineRegisterRequest(error), - }, - }, - NetworkBehaviourAction::GenerateEvent(Event::PeerNotRegistered { - peer: peer_id, - namespace: registration.namespace, - error, - }), - ] - } - inbound::OutEvent::RegistrationRequested(registration) => { - let namespace = registration.namespace.clone(); - - match registrations.add(registration) { - Ok(registration) => { - vec![ - NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::One(connection), - event: handler::InboundInEvent::NotifyInboundSubstream { - id, - message: inbound::InEvent::RegisterResponse { - ttl: registration.ttl, - }, - }, - }, - NetworkBehaviourAction::GenerateEvent(Event::PeerRegistered { - peer: peer_id, - registration, - }), - ] - } - Err(TtlOutOfRange::TooLong { .. }) | Err(TtlOutOfRange::TooShort { .. }) => { - let error = ErrorCode::InvalidTtl; - - vec![ - NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::One(connection), - event: handler::InboundInEvent::NotifyInboundSubstream { - id, - message: inbound::InEvent::DeclineRegisterRequest(error), - }, - }, - NetworkBehaviourAction::GenerateEvent(Event::PeerNotRegistered { - peer: peer_id, - namespace, - error, - }), - ] - } +fn inbound_stream_handler( + substream: NegotiatedSubstream, + _: PeerId, + _: &ConnectedPoint, + registrations: &RegistrationData, +) -> impl Future, Error>> { + let mut registrations = registrations.clone(); + let mut substream = Framed::new(substream, RendezvousCodec::default()); + + async move { + let message = substream + .next() + .await + .ok_or_else(|| Error::Io(io::ErrorKind::UnexpectedEof.into()))??; + + let out_event = match message { + Message::Register(new_registration) => { + // TODO: Validate registration + + substream + .send(Message::RegisterResponse(Ok( + new_registration.effective_ttl() + ))) + .await?; + substream.close().await?; + + Some(InboundOutEvent::NewRegistration(new_registration)) } - } - inbound::OutEvent::DiscoverRequested { - namespace, - cookie, - limit, - } => match registrations.get(namespace, cookie, limit) { - Ok((registrations, cookie)) => { - let discovered = registrations.cloned().collect::>(); - - vec![ - NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::One(connection), - event: handler::InboundInEvent::NotifyInboundSubstream { - id, - message: inbound::InEvent::DiscoverResponse { - discovered: discovered.clone(), - cookie, - }, - }, - }, - NetworkBehaviourAction::GenerateEvent(Event::DiscoverServed { - enquirer: peer_id, - registrations: discovered, - }), - ] + Message::Unregister(namespace) => { + substream.close().await?; + + Some(InboundOutEvent::Unregister(namespace)) } - Err(_) => { - let error = ErrorCode::InvalidCookie; - - vec![ - NetworkBehaviourAction::NotifyHandler { - peer_id, - handler: NotifyHandler::One(connection), - event: handler::InboundInEvent::NotifyInboundSubstream { - id, - message: inbound::InEvent::DeclineDiscoverRequest(error), - }, - }, - NetworkBehaviourAction::GenerateEvent(Event::DiscoverNotServed { - enquirer: peer_id, - error, - }), - ] + Message::Discover { + namespace, + cookie, + limit, + } => match registrations.get(namespace, cookie, limit) { + Ok((registrations, cookie)) => { + substream + .send(Message::DiscoverResponse(Ok(( + registrations.cloned().collect(), + cookie, + )))) + .await?; + substream.close().await?; + + None + } + Err(e) => { + substream + .send(Message::DiscoverResponse(Err(ErrorCode::InvalidCookie))) + .await?; + substream.close().await?; + + None + } + }, + Message::DiscoverResponse(_) | Message::RegisterResponse(_) => { + panic!("protocol violation") } - }, - inbound::OutEvent::UnregisterRequested(namespace) => { - registrations.remove(namespace.clone(), peer_id); - - vec![NetworkBehaviourAction::GenerateEvent( - Event::PeerUnregistered { - peer: peer_id, - namespace, - }, - )] - } + }; + + Ok(out_event) } } +#[derive(Debug)] +pub enum InboundOutEvent { + NewRegistration(NewRegistration), + Unregister(Namespace), +} + #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] struct RegistrationId(u64); @@ -313,14 +289,13 @@ impl RegistrationId { struct ExpiredRegistration(Registration); pub struct Registrations { - data: RegistrationData, min_ttl: Ttl, max_ttl: Ttl, next_expiry: FuturesUnordered>, } -#[derive(Clone, Default)] -struct RegistrationData { +#[derive(Debug, Clone, Default)] +pub struct RegistrationData { registrations_for_peer: BiMap<(PeerId, Namespace), RegistrationId>, registrations: HashMap, cookies: HashMap>, @@ -407,43 +382,39 @@ impl Default for Registrations { impl Registrations { pub fn with_config(config: Config) -> Self { Self { - data: RegistrationData::default(), min_ttl: config.min_ttl, max_ttl: config.max_ttl, next_expiry: FuturesUnordered::from_iter(vec![futures::future::pending().boxed()]), } } - pub fn add( - &mut self, - new_registration: NewRegistration, - ) -> Result { + pub fn add(&mut self, new_registration: NewRegistration, data: &mut RegistrationData) { + // TOOD: Assume validation has by done by newtype. let ttl = new_registration.effective_ttl(); - if ttl > self.max_ttl { - return Err(TtlOutOfRange::TooLong { - bound: self.max_ttl, - requested: ttl, - }); - } - if ttl < self.min_ttl { - return Err(TtlOutOfRange::TooShort { - bound: self.min_ttl, - requested: ttl, - }); - } + // if ttl > self.max_ttl { + // return Err(TtlOutOfRange::TooLong { + // bound: self.max_ttl, + // requested: ttl, + // }); + // } + // if ttl < self.min_ttl { + // return Err(TtlOutOfRange::TooShort { + // bound: self.min_ttl, + // requested: ttl, + // }); + // } let namespace = new_registration.namespace; let registration_id = RegistrationId::new(); - if let Some(old_registration) = self - .data + if let Some(old_registration) = data .registrations_for_peer .get_by_left(&(new_registration.record.peer_id(), namespace.clone())) { - self.data.registrations.remove(old_registration); + data.registrations.remove(old_registration); } - self.data.registrations_for_peer.insert( + data.registrations_for_peer.insert( (new_registration.record.peer_id(), namespace.clone()), registration_id, ); @@ -453,57 +424,46 @@ impl Registrations { record: new_registration.record, ttl, }; - self.data - .registrations - .insert(registration_id, registration.clone()); + data.registrations.insert(registration_id, registration); let next_expiry = futures_timer::Delay::new(Duration::from_secs(ttl as u64)) .map(move |_| registration_id) .boxed(); self.next_expiry.push(next_expiry); - - Ok(registration) } - pub fn remove(&mut self, namespace: Namespace, peer_id: PeerId) { - let reggo_to_remove = self - .data + pub fn remove(&self, namespace: Namespace, peer_id: PeerId, data: &mut RegistrationData) { + let reggo_to_remove = data .registrations_for_peer .remove_by_left(&(peer_id, namespace)); if let Some((_, reggo_to_remove)) = reggo_to_remove { - self.data.registrations.remove(®go_to_remove); + data.registrations.remove(®go_to_remove); } } - pub fn get( + fn poll( &mut self, - discover_namespace: Option, - cookie: Option, - limit: Option, - ) -> Result<(impl Iterator + '_, Cookie), CookieNamespaceMismatch> { - self.data.get(discover_namespace, cookie, limit) - } - - fn poll(&mut self, cx: &mut Context<'_>) -> Poll { + data: &mut RegistrationData, + cx: &mut Context<'_>, + ) -> Poll { let expired_registration = ready!(self.next_expiry.poll_next_unpin(cx)).expect( "This stream should never finish because it is initialised with a pending future", ); // clean up our cookies - self.data.cookies.retain(|_, registrations| { + data.cookies.retain(|_, registrations| { registrations.remove(&expired_registration); // retain all cookies where there are still registrations left !registrations.is_empty() }); - self.data - .registrations_for_peer + data.registrations_for_peer .remove_by_right(&expired_registration); - match self.data.registrations.remove(&expired_registration) { - None => self.poll(cx), + match data.registrations.remove(&expired_registration) { + None => self.poll(data, cx), Some(registration) => Poll::Ready(ExpiredRegistration(registration)), } } @@ -515,269 +475,269 @@ pub struct CookieNamespaceMismatch; #[cfg(test)] mod tests { - use instant::SystemTime; - use std::option::Option::None; - - use libp2p_core::{identity, PeerRecord}; - - use super::*; - - #[test] - fn given_cookie_from_discover_when_discover_again_then_only_get_diff() { - let mut registrations = Registrations::default(); - registrations.add(new_dummy_registration("foo")).unwrap(); - registrations.add(new_dummy_registration("foo")).unwrap(); - - let (initial_discover, cookie) = registrations.get(None, None, None).unwrap(); - assert_eq!(initial_discover.count(), 2); - - let (subsequent_discover, _) = registrations.get(None, Some(cookie), None).unwrap(); - assert_eq!(subsequent_discover.count(), 0); - } - - #[test] - fn given_registrations_when_discover_all_then_all_are_returned() { - let mut registrations = Registrations::default(); - registrations.add(new_dummy_registration("foo")).unwrap(); - registrations.add(new_dummy_registration("foo")).unwrap(); - - let (discover, _) = registrations.get(None, None, None).unwrap(); - - assert_eq!(discover.count(), 2); - } - - #[test] - fn given_registrations_when_discover_only_for_specific_namespace_then_only_those_are_returned() - { - let mut registrations = Registrations::default(); - registrations.add(new_dummy_registration("foo")).unwrap(); - registrations.add(new_dummy_registration("bar")).unwrap(); - - let (discover, _) = registrations - .get(Some(Namespace::from_static("foo")), None, None) - .unwrap(); - - assert_eq!( - discover.map(|r| &r.namespace).collect::>(), - vec!["foo"] - ); - } - - #[test] - fn given_reregistration_old_registration_is_discarded() { - let alice = identity::Keypair::generate_ed25519(); - let mut registrations = Registrations::default(); - registrations - .add(new_registration("foo", alice.clone(), None)) - .unwrap(); - registrations - .add(new_registration("foo", alice, None)) - .unwrap(); - - let (discover, _) = registrations - .get(Some(Namespace::from_static("foo")), None, None) - .unwrap(); - - assert_eq!( - discover.map(|r| &r.namespace).collect::>(), - vec!["foo"] - ); - } - - #[test] - fn given_cookie_from_2nd_discover_does_not_return_nodes_from_first_discover() { - let mut registrations = Registrations::default(); - registrations.add(new_dummy_registration("foo")).unwrap(); - registrations.add(new_dummy_registration("foo")).unwrap(); - - let (initial_discover, cookie1) = registrations.get(None, None, None).unwrap(); - assert_eq!(initial_discover.count(), 2); - - let (subsequent_discover, cookie2) = registrations.get(None, Some(cookie1), None).unwrap(); - assert_eq!(subsequent_discover.count(), 0); - - let (subsequent_discover, _) = registrations.get(None, Some(cookie2), None).unwrap(); - assert_eq!(subsequent_discover.count(), 0); - } - - #[test] - fn cookie_from_different_discover_request_is_not_valid() { - let mut registrations = Registrations::default(); - registrations.add(new_dummy_registration("foo")).unwrap(); - registrations.add(new_dummy_registration("bar")).unwrap(); - - let (_, foo_discover_cookie) = registrations - .get(Some(Namespace::from_static("foo")), None, None) - .unwrap(); - let result = registrations.get( - Some(Namespace::from_static("bar")), - Some(foo_discover_cookie), - None, - ); - - assert!(matches!(result, Err(CookieNamespaceMismatch))) - } - - #[tokio::test] - async fn given_two_registration_ttls_one_expires_one_lives() { - let mut registrations = Registrations::with_config(Config { - min_ttl: 0, - max_ttl: 4, - }); - - let start_time = SystemTime::now(); - - registrations - .add(new_dummy_registration_with_ttl("foo", 1)) - .unwrap(); - registrations - .add(new_dummy_registration_with_ttl("bar", 4)) - .unwrap(); - - let event = registrations.next_event().await; - - let elapsed = start_time.elapsed().unwrap(); - assert!(elapsed.as_secs() >= 1); - assert!(elapsed.as_secs() < 2); - - assert_eq!(event.0.namespace, Namespace::from_static("foo")); - - { - let (mut discovered_foo, _) = registrations - .get(Some(Namespace::from_static("foo")), None, None) - .unwrap(); - assert!(discovered_foo.next().is_none()); - } - let (mut discovered_bar, _) = registrations - .get(Some(Namespace::from_static("bar")), None, None) - .unwrap(); - assert!(discovered_bar.next().is_some()); - } - - #[tokio::test] - async fn given_peer_unregisters_before_expiry_do_not_emit_registration_expired() { - let mut registrations = Registrations::with_config(Config { - min_ttl: 1, - max_ttl: 10, - }); - let dummy_registration = new_dummy_registration_with_ttl("foo", 2); - let namespace = dummy_registration.namespace.clone(); - let peer_id = dummy_registration.record.peer_id(); - - registrations.add(dummy_registration).unwrap(); - registrations.no_event_for(1).await; - registrations.remove(namespace, peer_id); - - registrations.no_event_for(3).await - } - - /// FuturesUnordered stop polling for ready futures when poll_next() is called until a None - /// value is returned. To prevent the next_expiry future from going to "sleep", next_expiry - /// is initialised with a future that always returns pending. This test ensures that - /// FuturesUnordered does not stop polling for ready futures. - #[tokio::test] - async fn given_all_registrations_expired_then_successfully_handle_new_registration_and_expiry() - { - let mut registrations = Registrations::with_config(Config { - min_ttl: 0, - max_ttl: 10, - }); - let dummy_registration = new_dummy_registration_with_ttl("foo", 1); - - registrations.add(dummy_registration.clone()).unwrap(); - let _ = registrations.next_event_in_at_most(2).await; - - registrations.no_event_for(1).await; - - registrations.add(dummy_registration).unwrap(); - let _ = registrations.next_event_in_at_most(2).await; - } - - #[tokio::test] - async fn cookies_are_cleaned_up_if_registrations_expire() { - let mut registrations = Registrations::with_config(Config { - min_ttl: 1, - max_ttl: 10, - }); - - registrations - .add(new_dummy_registration_with_ttl("foo", 2)) - .unwrap(); - let (_, _) = registrations.get(None, None, None).unwrap(); - - assert_eq!(registrations.data.cookies.len(), 1); - - let _ = registrations.next_event_in_at_most(3).await; - - assert_eq!(registrations.data.cookies.len(), 0); - } - - #[test] - fn given_limit_discover_only_returns_n_results() { - let mut registrations = Registrations::default(); - registrations.add(new_dummy_registration("foo")).unwrap(); - registrations.add(new_dummy_registration("foo")).unwrap(); - - let (registrations, _) = registrations.get(None, None, Some(1)).unwrap(); - - assert_eq!(registrations.count(), 1); - } - - #[test] - fn given_limit_cookie_can_be_used_for_pagination() { - let mut registrations = Registrations::default(); - registrations.add(new_dummy_registration("foo")).unwrap(); - registrations.add(new_dummy_registration("foo")).unwrap(); - - let (discover1, cookie) = registrations.get(None, None, Some(1)).unwrap(); - assert_eq!(discover1.count(), 1); - - let (discover2, _) = registrations.get(None, Some(cookie), None).unwrap(); - assert_eq!(discover2.count(), 1); - } - - fn new_dummy_registration(namespace: &'static str) -> NewRegistration { - let identity = identity::Keypair::generate_ed25519(); - - new_registration(namespace, identity, None) - } - - fn new_dummy_registration_with_ttl(namespace: &'static str, ttl: Ttl) -> NewRegistration { - let identity = identity::Keypair::generate_ed25519(); - - new_registration(namespace, identity, Some(ttl)) - } - - fn new_registration( - namespace: &'static str, - identity: identity::Keypair, - ttl: Option, - ) -> NewRegistration { - NewRegistration::new( - Namespace::from_static(namespace), - PeerRecord::new(&identity, vec!["/ip4/127.0.0.1/tcp/1234".parse().unwrap()]).unwrap(), - ttl, - ) - } - - /// Defines utility functions that make the tests more readable. - impl Registrations { - async fn next_event(&mut self) -> ExpiredRegistration { - futures::future::poll_fn(|cx| self.poll(cx)).await - } - - /// Polls [`Registrations`] for `seconds` and panics if it returns a event during this time. - async fn no_event_for(&mut self, seconds: u64) { - tokio::time::timeout(Duration::from_secs(seconds), self.next_event()) - .await - .unwrap_err(); - } - - /// Polls [`Registrations`] for at most `seconds` and panics if doesn't return an event within that time. - async fn next_event_in_at_most(&mut self, seconds: u64) -> ExpiredRegistration { - tokio::time::timeout(Duration::from_secs(seconds), self.next_event()) - .await - .unwrap() - } - } + // use instant::SystemTime; + // use std::option::Option::None; + // + // use libp2p_core::{identity, PeerRecord}; + // + // use super::*; + // + // #[test] + // fn given_cookie_from_discover_when_discover_again_then_only_get_diff() { + // let mut registrations = Registrations::default(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // + // let (initial_discover, cookie) = registrations.get(None, None, None).unwrap(); + // assert_eq!(initial_discover.count(), 2); + // + // let (subsequent_discover, _) = registrations.get(None, Some(cookie), None).unwrap(); + // assert_eq!(subsequent_discover.count(), 0); + // } + // + // #[test] + // fn given_registrations_when_discover_all_then_all_are_returned() { + // let mut registrations = Registrations::default(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // + // let (discover, _) = registrations.get(None, None, None).unwrap(); + // + // assert_eq!(discover.count(), 2); + // } + // + // #[test] + // fn given_registrations_when_discover_only_for_specific_namespace_then_only_those_are_returned() + // { + // let mut registrations = Registrations::default(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // registrations.add(new_dummy_registration("bar")).unwrap(); + // + // let (discover, _) = registrations + // .get(Some(Namespace::from_static("foo")), None, None) + // .unwrap(); + // + // assert_eq!( + // discover.map(|r| &r.namespace).collect::>(), + // vec!["foo"] + // ); + // } + // + // #[test] + // fn given_reregistration_old_registration_is_discarded() { + // let alice = identity::Keypair::generate_ed25519(); + // let mut registrations = Registrations::default(); + // registrations + // .add(new_registration("foo", alice.clone(), None)) + // .unwrap(); + // registrations + // .add(new_registration("foo", alice, None)) + // .unwrap(); + // + // let (discover, _) = registrations + // .get(Some(Namespace::from_static("foo")), None, None) + // .unwrap(); + // + // assert_eq!( + // discover.map(|r| &r.namespace).collect::>(), + // vec!["foo"] + // ); + // } + // + // #[test] + // fn given_cookie_from_2nd_discover_does_not_return_nodes_from_first_discover() { + // let mut registrations = Registrations::default(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // + // let (initial_discover, cookie1) = registrations.get(None, None, None).unwrap(); + // assert_eq!(initial_discover.count(), 2); + // + // let (subsequent_discover, cookie2) = registrations.get(None, Some(cookie1), None).unwrap(); + // assert_eq!(subsequent_discover.count(), 0); + // + // let (subsequent_discover, _) = registrations.get(None, Some(cookie2), None).unwrap(); + // assert_eq!(subsequent_discover.count(), 0); + // } + // + // #[test] + // fn cookie_from_different_discover_request_is_not_valid() { + // let mut registrations = Registrations::default(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // registrations.add(new_dummy_registration("bar")).unwrap(); + // + // let (_, foo_discover_cookie) = registrations + // .get(Some(Namespace::from_static("foo")), None, None) + // .unwrap(); + // let result = registrations.get( + // Some(Namespace::from_static("bar")), + // Some(foo_discover_cookie), + // None, + // ); + // + // assert!(matches!(result, Err(CookieNamespaceMismatch))) + // } + // + // #[tokio::test] + // async fn given_two_registration_ttls_one_expires_one_lives() { + // let mut registrations = Registrations::with_config(Config { + // min_ttl: 0, + // max_ttl: 4, + // }); + // + // let start_time = SystemTime::now(); + // + // registrations + // .add(new_dummy_registration_with_ttl("foo", 1)) + // .unwrap(); + // registrations + // .add(new_dummy_registration_with_ttl("bar", 4)) + // .unwrap(); + // + // let event = registrations.next_event().await; + // + // let elapsed = start_time.elapsed().unwrap(); + // assert!(elapsed.as_secs() >= 1); + // assert!(elapsed.as_secs() < 2); + // + // assert_eq!(event.0.namespace, Namespace::from_static("foo")); + // + // { + // let (mut discovered_foo, _) = registrations + // .get(Some(Namespace::from_static("foo")), None, None) + // .unwrap(); + // assert!(discovered_foo.next().is_none()); + // } + // let (mut discovered_bar, _) = registrations + // .get(Some(Namespace::from_static("bar")), None, None) + // .unwrap(); + // assert!(discovered_bar.next().is_some()); + // } + // + // #[tokio::test] + // async fn given_peer_unregisters_before_expiry_do_not_emit_registration_expired() { + // let mut registrations = Registrations::with_config(Config { + // min_ttl: 1, + // max_ttl: 10, + // }); + // let dummy_registration = new_dummy_registration_with_ttl("foo", 2); + // let namespace = dummy_registration.namespace.clone(); + // let peer_id = dummy_registration.record.peer_id(); + // + // registrations.add(dummy_registration).unwrap(); + // registrations.no_event_for(1).await; + // registrations.remove(namespace, peer_id); + // + // registrations.no_event_for(3).await + // } + // + // /// FuturesUnordered stop polling for ready futures when poll_next() is called until a None + // /// value is returned. To prevent the next_expiry future from going to "sleep", next_expiry + // /// is initialised with a future that always returns pending. This test ensures that + // /// FuturesUnordered does not stop polling for ready futures. + // #[tokio::test] + // async fn given_all_registrations_expired_then_successfully_handle_new_registration_and_expiry() + // { + // let mut registrations = Registrations::with_config(Config { + // min_ttl: 0, + // max_ttl: 10, + // }); + // let dummy_registration = new_dummy_registration_with_ttl("foo", 1); + // + // registrations.add(dummy_registration.clone()).unwrap(); + // let _ = registrations.next_event_in_at_most(2).await; + // + // registrations.no_event_for(1).await; + // + // registrations.add(dummy_registration).unwrap(); + // let _ = registrations.next_event_in_at_most(2).await; + // } + // + // #[tokio::test] + // async fn cookies_are_cleaned_up_if_registrations_expire() { + // let mut registrations = Registrations::with_config(Config { + // min_ttl: 1, + // max_ttl: 10, + // }); + // + // registrations + // .add(new_dummy_registration_with_ttl("foo", 2)) + // .unwrap(); + // let (_, _) = registrations.get(None, None, None).unwrap(); + // + // assert_eq!(registrations.data.cookies.len(), 1); + // + // let _ = registrations.next_event_in_at_most(3).await; + // + // assert_eq!(registrations.data.cookies.len(), 0); + // } + // + // #[test] + // fn given_limit_discover_only_returns_n_results() { + // let mut registrations = Registrations::default(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // + // let (registrations, _) = registrations.get(None, None, Some(1)).unwrap(); + // + // assert_eq!(registrations.count(), 1); + // } + // + // #[test] + // fn given_limit_cookie_can_be_used_for_pagination() { + // let mut registrations = Registrations::default(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // registrations.add(new_dummy_registration("foo")).unwrap(); + // + // let (discover1, cookie) = registrations.get(None, None, Some(1)).unwrap(); + // assert_eq!(discover1.count(), 1); + // + // let (discover2, _) = registrations.get(None, Some(cookie), None).unwrap(); + // assert_eq!(discover2.count(), 1); + // } + // + // fn new_dummy_registration(namespace: &'static str) -> NewRegistration { + // let identity = identity::Keypair::generate_ed25519(); + // + // new_registration(namespace, identity, None) + // } + // + // fn new_dummy_registration_with_ttl(namespace: &'static str, ttl: Ttl) -> NewRegistration { + // let identity = identity::Keypair::generate_ed25519(); + // + // new_registration(namespace, identity, Some(ttl)) + // } + // + // fn new_registration( + // namespace: &'static str, + // identity: identity::Keypair, + // ttl: Option, + // ) -> NewRegistration { + // NewRegistration::new( + // Namespace::from_static(namespace), + // PeerRecord::new(&identity, vec!["/ip4/127.0.0.1/tcp/1234".parse().unwrap()]).unwrap(), + // ttl, + // ) + // } + // + // /// Defines utility functions that make the tests more readable. + // impl Registrations { + // async fn next_event(&mut self) -> ExpiredRegistration { + // futures::future::poll_fn(|cx| self.poll(cx)).await + // } + // + // /// Polls [`Registrations`] for `seconds` and panics if it returns a event during this time. + // async fn no_event_for(&mut self, seconds: u64) { + // tokio::time::timeout(Duration::from_secs(seconds), self.next_event()) + // .await + // .unwrap_err(); + // } + // + // /// Polls [`Registrations`] for at most `seconds` and panics if doesn't return an event within that time. + // async fn next_event_in_at_most(&mut self, seconds: u64) -> ExpiredRegistration { + // tokio::time::timeout(Duration::from_secs(seconds), self.next_event()) + // .await + // .unwrap() + // } + // } } diff --git a/protocols/rendezvous/src/substream_handler.rs b/protocols/rendezvous/src/substream_handler.rs deleted file mode 100644 index f57dfded6c9..00000000000 --- a/protocols/rendezvous/src/substream_handler.rs +++ /dev/null @@ -1,553 +0,0 @@ -// Copyright 2021 COMIT Network. -// -// 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. - -//! A generic [`ConnectionHandler`] that delegates the handling of substreams to [`SubstreamHandler`]s. -//! -//! This module is an attempt to simplify the implementation of protocols by freeing implementations from dealing with aspects such as concurrent substreams. -//! Particularly for outbound substreams, it greatly simplifies the definition of protocols through the [`FutureSubstream`] helper. -//! -//! At the moment, this module is an implementation detail of the rendezvous protocol but the intent is for it to be provided as a generic module that is accessible to other protocols as well. - -use futures::future::{self, BoxFuture, Fuse, FusedFuture}; -use futures::FutureExt; -use instant::Instant; -use libp2p_core::{InboundUpgrade, OutboundUpgrade, UpgradeInfo}; -use libp2p_swarm::handler::{InboundUpgradeSend, OutboundUpgradeSend}; -use libp2p_swarm::{ - ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive, - NegotiatedSubstream, SubstreamProtocol, -}; -use std::collections::{HashMap, VecDeque}; -use std::fmt; -use std::future::Future; -use std::hash::Hash; -use std::task::{Context, Poll}; -use std::time::Duration; -use void::Void; - -/// Handles a substream throughout its lifetime. -pub trait SubstreamHandler: Sized { - type InEvent; - type OutEvent; - type Error; - type OpenInfo; - - fn upgrade(open_info: Self::OpenInfo) - -> SubstreamProtocol; - fn new(substream: NegotiatedSubstream, info: Self::OpenInfo) -> Self; - fn inject_event(self, event: Self::InEvent) -> Self; - fn advance(self, cx: &mut Context<'_>) -> Result, Self::Error>; -} - -/// The result of advancing a [`SubstreamHandler`]. -pub enum Next { - /// Return the given event and set the handler into `next_state`. - EmitEvent { event: TEvent, next_state: TState }, - /// The handler currently cannot do any more work, set its state back into `next_state`. - Pending { next_state: TState }, - /// The handler performed some work and wants to continue in the given state. - /// - /// This variant is useful because it frees the handler from implementing a loop internally. - Continue { next_state: TState }, - /// The handler finished. - Done, -} - -impl Next { - pub fn map_state( - self, - map: impl FnOnce(TState) -> TNextState, - ) -> Next { - match self { - Next::EmitEvent { event, next_state } => Next::EmitEvent { - event, - next_state: map(next_state), - }, - Next::Pending { next_state } => Next::Pending { - next_state: map(next_state), - }, - Next::Continue { next_state } => Next::Pending { - next_state: map(next_state), - }, - Next::Done => Next::Done, - } - } -} - -#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] -pub struct InboundSubstreamId(u64); - -impl InboundSubstreamId { - fn fetch_and_increment(&mut self) -> Self { - let next_id = *self; - self.0 += 1; - - next_id - } -} - -impl fmt::Display for InboundSubstreamId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -#[derive(Debug, Hash, Eq, PartialEq, Clone, Copy)] -pub struct OutboundSubstreamId(u64); - -impl OutboundSubstreamId { - fn fetch_and_increment(&mut self) -> Self { - let next_id = *self; - self.0 += 1; - - next_id - } -} - -impl fmt::Display for OutboundSubstreamId { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.0) - } -} - -pub struct PassthroughProtocol { - ident: Option<&'static [u8]>, -} - -impl PassthroughProtocol { - pub fn new(ident: &'static [u8]) -> Self { - Self { ident: Some(ident) } - } -} - -impl UpgradeInfo for PassthroughProtocol { - type Info = &'static [u8]; - type InfoIter = std::option::IntoIter; - - fn protocol_info(&self) -> Self::InfoIter { - self.ident.into_iter() - } -} - -impl InboundUpgrade for PassthroughProtocol { - type Output = C; - type Error = Void; - type Future = BoxFuture<'static, Result>; - - fn upgrade_inbound(self, socket: C, _: Self::Info) -> Self::Future { - match self.ident { - Some(_) => future::ready(Ok(socket)).boxed(), - None => future::pending().boxed(), - } - } -} - -impl OutboundUpgrade for PassthroughProtocol { - type Output = C; - type Error = Void; - type Future = BoxFuture<'static, Result>; - - fn upgrade_outbound(self, socket: C, _: Self::Info) -> Self::Future { - match self.ident { - Some(_) => future::ready(Ok(socket)).boxed(), - None => future::pending().boxed(), - } - } -} - -/// An implementation of [`ConnectionHandler`] that delegates to individual [`SubstreamHandler`]s. -pub struct SubstreamConnectionHandler { - inbound_substreams: HashMap, - outbound_substreams: HashMap, - next_inbound_substream_id: InboundSubstreamId, - next_outbound_substream_id: OutboundSubstreamId, - - new_substreams: VecDeque, - - initial_keep_alive_deadline: Instant, -} - -impl - SubstreamConnectionHandler -{ - pub fn new(initial_keep_alive: Duration) -> Self { - Self { - inbound_substreams: Default::default(), - outbound_substreams: Default::default(), - next_inbound_substream_id: InboundSubstreamId(0), - next_outbound_substream_id: OutboundSubstreamId(0), - new_substreams: Default::default(), - initial_keep_alive_deadline: Instant::now() + initial_keep_alive, - } - } -} - -impl - SubstreamConnectionHandler -{ - pub fn new_outbound_only(initial_keep_alive: Duration) -> Self { - Self { - inbound_substreams: Default::default(), - outbound_substreams: Default::default(), - next_inbound_substream_id: InboundSubstreamId(0), - next_outbound_substream_id: OutboundSubstreamId(0), - new_substreams: Default::default(), - initial_keep_alive_deadline: Instant::now() + initial_keep_alive, - } - } -} - -impl - SubstreamConnectionHandler -{ - pub fn new_inbound_only(initial_keep_alive: Duration) -> Self { - Self { - inbound_substreams: Default::default(), - outbound_substreams: Default::default(), - next_inbound_substream_id: InboundSubstreamId(0), - next_outbound_substream_id: OutboundSubstreamId(0), - new_substreams: Default::default(), - initial_keep_alive_deadline: Instant::now() + initial_keep_alive, - } - } -} - -/// Poll all substreams within the given HashMap. -/// -/// This is defined as a separate function because we call it with two different fields stored within [`SubstreamConnectionHandler`]. -fn poll_substreams( - substreams: &mut HashMap, - cx: &mut Context<'_>, -) -> Poll> -where - TSubstream: SubstreamHandler, - TId: Copy + Eq + Hash + fmt::Display, -{ - let substream_ids = substreams.keys().copied().collect::>(); - - 'loop_substreams: for id in substream_ids { - let mut handler = substreams - .remove(&id) - .expect("we just got the key out of the map"); - - let (next_state, poll) = 'loop_handler: loop { - match handler.advance(cx) { - Ok(Next::EmitEvent { next_state, event }) => { - break (next_state, Poll::Ready(Ok((id, event)))) - } - Ok(Next::Pending { next_state }) => break (next_state, Poll::Pending), - Ok(Next::Continue { next_state }) => { - handler = next_state; - continue 'loop_handler; - } - Ok(Next::Done) => { - log::debug!("Substream handler {} finished", id); - continue 'loop_substreams; - } - Err(e) => return Poll::Ready(Err((id, e))), - } - }; - - substreams.insert(id, next_state); - - return poll; - } - - Poll::Pending -} - -/// Event sent from the [`libp2p_swarm::NetworkBehaviour`] to the [`SubstreamConnectionHandler`]. -#[allow(clippy::enum_variant_names)] -#[derive(Debug)] -pub enum InEvent { - /// Open a new substream using the provided `open_info`. - /// - /// For "client-server" protocols, this is typically the initial message to be sent to the other party. - NewSubstream { open_info: I }, - NotifyInboundSubstream { - id: InboundSubstreamId, - message: TInboundEvent, - }, - NotifyOutboundSubstream { - id: OutboundSubstreamId, - message: TOutboundEvent, - }, -} - -/// Event produced by the [`SubstreamConnectionHandler`] for the corresponding [`libp2p_swarm::NetworkBehaviour`]. -#[derive(Debug)] -pub enum OutEvent { - /// An inbound substream produced an event. - InboundEvent { - id: InboundSubstreamId, - message: TInbound, - }, - /// An outbound substream produced an event. - OutboundEvent { - id: OutboundSubstreamId, - message: TOutbound, - }, - /// An inbound substream errored irrecoverably. - InboundError { - id: InboundSubstreamId, - error: TInboundError, - }, - /// An outbound substream errored irrecoverably. - OutboundError { - id: OutboundSubstreamId, - error: TOutboundError, - }, -} - -impl< - TInboundInEvent, - TInboundOutEvent, - TOutboundInEvent, - TOutboundOutEvent, - TOutboundOpenInfo, - TInboundError, - TOutboundError, - TInboundSubstreamHandler, - TOutboundSubstreamHandler, - > ConnectionHandler - for SubstreamConnectionHandler< - TInboundSubstreamHandler, - TOutboundSubstreamHandler, - TOutboundOpenInfo, - > -where - TInboundSubstreamHandler: SubstreamHandler< - InEvent = TInboundInEvent, - OutEvent = TInboundOutEvent, - Error = TInboundError, - OpenInfo = (), - >, - TOutboundSubstreamHandler: SubstreamHandler< - InEvent = TOutboundInEvent, - OutEvent = TOutboundOutEvent, - Error = TOutboundError, - OpenInfo = TOutboundOpenInfo, - >, - TInboundInEvent: fmt::Debug + Send + 'static, - TInboundOutEvent: fmt::Debug + Send + 'static, - TOutboundInEvent: fmt::Debug + Send + 'static, - TOutboundOutEvent: fmt::Debug + Send + 'static, - TOutboundOpenInfo: fmt::Debug + Send + 'static, - TInboundError: fmt::Debug + Send + 'static, - TOutboundError: fmt::Debug + Send + 'static, - TInboundSubstreamHandler: Send + 'static, - TOutboundSubstreamHandler: Send + 'static, -{ - type InEvent = InEvent; - type OutEvent = OutEvent; - type Error = Void; - type InboundProtocol = PassthroughProtocol; - type OutboundProtocol = PassthroughProtocol; - type InboundOpenInfo = (); - type OutboundOpenInfo = TOutboundOpenInfo; - - fn listen_protocol(&self) -> SubstreamProtocol { - TInboundSubstreamHandler::upgrade(()) - } - - fn inject_fully_negotiated_inbound( - &mut self, - protocol: ::Output, - _: Self::InboundOpenInfo, - ) { - self.inbound_substreams.insert( - self.next_inbound_substream_id.fetch_and_increment(), - TInboundSubstreamHandler::new(protocol, ()), - ); - } - - fn inject_fully_negotiated_outbound( - &mut self, - protocol: ::Output, - info: Self::OutboundOpenInfo, - ) { - self.outbound_substreams.insert( - self.next_outbound_substream_id.fetch_and_increment(), - TOutboundSubstreamHandler::new(protocol, info), - ); - } - - fn inject_event(&mut self, event: Self::InEvent) { - match event { - InEvent::NewSubstream { open_info } => self.new_substreams.push_back(open_info), - InEvent::NotifyInboundSubstream { id, message } => { - match self.inbound_substreams.remove(&id) { - Some(handler) => { - let new_handler = handler.inject_event(message); - - self.inbound_substreams.insert(id, new_handler); - } - None => { - log::debug!("Substream with ID {} not found", id); - } - } - } - InEvent::NotifyOutboundSubstream { id, message } => { - match self.outbound_substreams.remove(&id) { - Some(handler) => { - let new_handler = handler.inject_event(message); - - self.outbound_substreams.insert(id, new_handler); - } - None => { - log::debug!("Substream with ID {} not found", id); - } - } - } - } - } - - fn inject_dial_upgrade_error( - &mut self, - _: Self::OutboundOpenInfo, - _: ConnectionHandlerUpgrErr, - ) { - // TODO: Handle upgrade errors properly - } - - fn connection_keep_alive(&self) -> KeepAlive { - // Rudimentary keep-alive handling, to be extended as needed as this abstraction is used more by other protocols. - - if Instant::now() < self.initial_keep_alive_deadline { - return KeepAlive::Yes; - } - - if self.inbound_substreams.is_empty() - && self.outbound_substreams.is_empty() - && self.new_substreams.is_empty() - { - return KeepAlive::No; - } - - KeepAlive::Yes - } - - fn poll( - &mut self, - cx: &mut Context<'_>, - ) -> Poll< - ConnectionHandlerEvent< - Self::OutboundProtocol, - Self::OutboundOpenInfo, - Self::OutEvent, - Self::Error, - >, - > { - if let Some(open_info) = self.new_substreams.pop_front() { - return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { - protocol: TOutboundSubstreamHandler::upgrade(open_info), - }); - } - - match poll_substreams(&mut self.inbound_substreams, cx) { - Poll::Ready(Ok((id, message))) => { - return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::InboundEvent { - id, - message, - })) - } - Poll::Ready(Err((id, error))) => { - return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::InboundError { - id, - error, - })) - } - Poll::Pending => {} - } - - match poll_substreams(&mut self.outbound_substreams, cx) { - Poll::Ready(Ok((id, message))) => { - return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::OutboundEvent { - id, - message, - })) - } - Poll::Ready(Err((id, error))) => { - return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::OutboundError { - id, - error, - })) - } - Poll::Pending => {} - } - - Poll::Pending - } -} - -/// A helper struct for substream handlers that can be implemented as async functions. -/// -/// This only works for substreams without an `InEvent` because - once constructed - the state of an inner future is opaque. -pub struct FutureSubstream { - future: Fuse>>, -} - -impl FutureSubstream { - pub fn new(future: impl Future> + Send + 'static) -> Self { - Self { - future: future.boxed().fuse(), - } - } - - pub fn advance(mut self, cx: &mut Context<'_>) -> Result, TError> { - if self.future.is_terminated() { - return Ok(Next::Done); - } - - match self.future.poll_unpin(cx) { - Poll::Ready(Ok(event)) => Ok(Next::EmitEvent { - event, - next_state: self, - }), - Poll::Ready(Err(error)) => Err(error), - Poll::Pending => Ok(Next::Pending { next_state: self }), - } - } -} - -impl SubstreamHandler for void::Void { - type InEvent = void::Void; - type OutEvent = void::Void; - type Error = void::Void; - type OpenInfo = (); - - fn new(_: NegotiatedSubstream, _: Self::OpenInfo) -> Self { - unreachable!("we should never yield a substream") - } - - fn inject_event(self, event: Self::InEvent) -> Self { - void::unreachable(event) - } - - fn advance(self, _: &mut Context<'_>) -> Result, Self::Error> { - void::unreachable(self) - } - - fn upgrade( - open_info: Self::OpenInfo, - ) -> SubstreamProtocol { - SubstreamProtocol::new(PassthroughProtocol { ident: None }, open_info) - } -} From 411c0d76bcf0f5cfe6bce4fb1270b07cf7f82e0e Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 11 Nov 2022 15:11:29 +1100 Subject: [PATCH 20/34] fixup! WIP: Migrate `libp2p-rendezvous` to `from_fn` --- protocols/rendezvous/src/client.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index 8be371222eb..04b6b2854f6 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -35,7 +35,6 @@ use libp2p_core::identity::error::SigningError; use libp2p_core::identity::Keypair; use libp2p_core::{ConnectedPoint, Multiaddr, PeerId, PeerRecord}; use libp2p_swarm::handler::from_fn; -use libp2p_swarm::handler::from_fn::OutEvent; use libp2p_swarm::{ CloseConnection, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, From 5e185d89c21d8e3ea5eed74dce24db114b9c2e46 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 11 Nov 2022 15:11:29 +1100 Subject: [PATCH 21/34] fixup! WIP: Migrate `libp2p-rendezvous` to `from_fn` --- protocols/rendezvous/src/client.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index 04b6b2854f6..72602750506 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -226,15 +226,15 @@ impl NetworkBehaviour for Behaviour { event: from_fn::OutEvent<(), Result, OpenInfo>, ) { match event { - OutEvent::InboundFinished(()) => {} - OutEvent::OutboundFinished(Ok(OutboundEvent::Discovered { .. })) => {} - OutEvent::OutboundFinished(Ok(OutboundEvent::Registered { .. })) => {} - OutEvent::OutboundFinished(Ok(OutboundEvent::DiscoverFailed { .. })) => {} - OutEvent::OutboundFinished(Ok(OutboundEvent::RegisterFailed(..))) => {} - OutEvent::OutboundFinished(Err(e)) => {} - OutEvent::FailedToOpen(from_fn::OpenError::Timeout(info)) => {} - OutEvent::FailedToOpen(from_fn::OpenError::NegotiationFailed(..)) => {} - OutEvent::FailedToOpen(from_fn::OpenError::LimitExceeded(..)) => {} + from_fn::OutEvent::InboundFinished(()) => {} + from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::Discovered { .. })) => {} + from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::Registered { .. })) => {} + from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::DiscoverFailed { .. })) => {} + from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::RegisterFailed(..))) => {} + from_fn::OutEvent::OutboundFinished(Err(e)) => {} + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::Timeout(info)) => {} + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::NegotiationFailed(..)) => {} + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::LimitExceeded(..)) => {} } // // let new_events = match event { From bdbeadc5d28bfc6090acfb23d138439cac5449af Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 11 Nov 2022 15:13:07 +1100 Subject: [PATCH 22/34] Enforce the use of `Shared` --- protocols/rendezvous/src/client.rs | 2 +- protocols/rendezvous/src/server.rs | 2 +- swarm/src/handler/from_fn.rs | 7 ++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index 72602750506..d62aece737b 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -202,7 +202,7 @@ impl NetworkBehaviour for Behaviour { fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn::from_fn( PROTOCOL_IDENT, - (), + &from_fn::Shared::new(()), 10, 10, |_, _, _, _| async {}, diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index d7e12402132..2c3a06dcc31 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -120,7 +120,7 @@ impl NetworkBehaviour for Behaviour { fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn( PROTOCOL_IDENT, - self.registration_data.clone(), + &self.registration_data, 10, 10, inbound_stream_handler, diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index a8032373f01..86199eed642 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -46,7 +46,7 @@ use void::Void; /// [`NetworkBehaviour`]: crate::NetworkBehaviour pub fn from_fn( protocol: &'static str, - state: TState, + state: &Shared, inbound_streams_limit: usize, pending_dial_limit: usize, on_new_inbound: impl Fn(NegotiatedSubstream, PeerId, &ConnectedPoint, &TState) -> TInboundFuture @@ -63,6 +63,7 @@ pub fn from_fn FromFnProto where + TState: Clone, TInboundFuture: Future + Send + 'static, TOutboundFuture: Future + Send + 'static, { @@ -78,7 +79,7 @@ where on_new_outbound(stream, remote_peer_id, connected_point, state, info).boxed() }, ), - state, + state: state.inner.clone(), } } @@ -615,7 +616,7 @@ mod tests { fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn( "/hello/1.0.0", - self.state.clone(), + &self.state, 5, 5, |mut stream, _, _, state| { From 660c0a332086277badc3f2f04aa1a11a85eadbe6 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 11 Nov 2022 15:25:58 +1100 Subject: [PATCH 23/34] Share state between connection handlers This reduces the memory usage. --- swarm/src/handler/from_fn.rs | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 86199eed642..6fd9fc6d5ed 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -15,6 +15,7 @@ use std::error::Error; use std::fmt; use std::future::Future; use std::ops::{Deref, DerefMut}; +use std::sync::Arc; use std::task::{Context, Poll, Waker}; use std::time::{Duration, Instant}; use void::Void; @@ -79,7 +80,7 @@ where on_new_outbound(stream, remote_peer_id, connected_point, state, info).boxed() }, ), - state: state.inner.clone(), + state: state.shared.clone(), } } @@ -107,10 +108,12 @@ pub enum OpenError { pub struct Shared { inner: T, + shared: Arc, + dirty: bool, waker: Option, connections: HashSet<(PeerId, ConnectionId)>, - pending_update_events: VecDeque<(PeerId, ConnectionId, T)>, + pending_update_events: VecDeque<(PeerId, ConnectionId, Arc)>, } impl Shared @@ -119,7 +122,8 @@ where { pub fn new(state: T) -> Self { Self { - inner: state, + inner: state.clone(), + shared: Arc::new(state), dirty: false, waker: None, connections: HashSet::default(), @@ -143,10 +147,12 @@ where THandler: IntoConnectionHandler, { if self.dirty { + self.shared = Arc::new(self.inner.clone()); + self.pending_update_events = self .connections .iter() - .map(|(peer_id, conn_id)| (*peer_id, *conn_id, self.inner.clone())) + .map(|(peer_id, conn_id)| (*peer_id, *conn_id, self.shared.clone())) .collect(); self.dirty = false; @@ -209,7 +215,7 @@ where #[derive(Debug)] pub enum InEvent { - UpdateState(TState), + UpdateState(Arc), NewOutbound(TOutboundOpenInfo), } @@ -239,7 +245,7 @@ pub struct FromFnProto { inbound_streams_limit: usize, pending_outbound_streams_limit: usize, - state: TState, + state: Arc, } impl IntoConnectionHandler @@ -248,7 +254,7 @@ where TInbound: fmt::Debug + Send + 'static, TOutbound: fmt::Debug + Send + 'static, TOutboundOpenInfo: fmt::Debug + Send + 'static, - TState: fmt::Debug + Send + 'static, + TState: fmt::Debug + Send + Sync + 'static, { type Handler = FromFn; @@ -314,7 +320,7 @@ pub struct FromFn { failed_open: VecDeque>, - state: TState, + state: Arc, keep_alive: KeepAlive, } @@ -325,7 +331,7 @@ where TOutboundInfo: fmt::Debug + Send + 'static, TInbound: fmt::Debug + Send + 'static, TOutbound: fmt::Debug + Send + 'static, - TState: fmt::Debug + Send + 'static, + TState: fmt::Debug + Send + Sync + 'static, { type InEvent = InEvent; type OutEvent = OutEvent; From f5a3c500e7f1707e751c3f0f80ceef010ba722f5 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 11 Nov 2022 15:56:29 +1100 Subject: [PATCH 24/34] Introduce builder pattern --- protocols/rendezvous/src/client.rs | 22 ++-- protocols/rendezvous/src/server.rs | 12 +- swarm/src/handler/from_fn.rs | 185 ++++++++++++++++++++++------- 3 files changed, 155 insertions(+), 64 deletions(-) diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index d62aece737b..c24d31f6c6f 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -35,6 +35,7 @@ use libp2p_core::identity::error::SigningError; use libp2p_core::identity::Keypair; use libp2p_core::{ConnectedPoint, Multiaddr, PeerId, PeerRecord}; use libp2p_swarm::handler::from_fn; +use libp2p_swarm::handler::from_fn::Dropped; use libp2p_swarm::{ CloseConnection, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, @@ -49,7 +50,7 @@ pub struct Behaviour { events: VecDeque< NetworkBehaviourAction< Event, - from_fn::FromFnProto<(), Result, OpenInfo, ()>, + from_fn::FromFnProto, OpenInfo, ()>, >, >, keypair: Keypair, @@ -196,18 +197,15 @@ pub enum OpenInfo { } impl NetworkBehaviour for Behaviour { - type ConnectionHandler = from_fn::FromFnProto<(), Result, OpenInfo, ()>; + type ConnectionHandler = + from_fn::FromFnProto, OpenInfo, ()>; type OutEvent = Event; fn new_handler(&mut self) -> Self::ConnectionHandler { - from_fn::from_fn( - PROTOCOL_IDENT, - &from_fn::Shared::new(()), - 10, - 10, - |_, _, _, _| async {}, - outbound_stream_handler, - ) + from_fn::from_fn(PROTOCOL_IDENT) + .without_state() + .without_inbound_handler() + .with_outbound_handler(10, outbound_stream_handler) } fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { @@ -223,10 +221,10 @@ impl NetworkBehaviour for Behaviour { &mut self, __id: PeerId, _: ConnectionId, - event: from_fn::OutEvent<(), Result, OpenInfo>, + event: from_fn::OutEvent, OpenInfo>, ) { match event { - from_fn::OutEvent::InboundFinished(()) => {} + from_fn::OutEvent::InboundFinished(Dropped { .. }) => {} from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::Discovered { .. })) => {} from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::Registered { .. })) => {} from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::DiscoverFailed { .. })) => {} diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index 2c3a06dcc31..303c2c14d06 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -118,14 +118,10 @@ impl NetworkBehaviour for Behaviour { type OutEvent = Event; fn new_handler(&mut self) -> Self::ConnectionHandler { - from_fn( - PROTOCOL_IDENT, - &self.registration_data, - 10, - 10, - inbound_stream_handler, - |_, _, _, _, never| async move { void::unreachable(never) }, - ) + from_fn(PROTOCOL_IDENT) + .with_state(&self.registration_data) + .with_inbound_handler(10, inbound_stream_handler) + .without_outbound_handler() } fn inject_connection_established( diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 6fd9fc6d5ed..68a4a71b5a6 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -45,45 +45,146 @@ use void::Void; /// substreams, may be outdated and only eventually-consistent. /// /// [`NetworkBehaviour`]: crate::NetworkBehaviour -pub fn from_fn( - protocol: &'static str, - state: &Shared, - inbound_streams_limit: usize, - pending_dial_limit: usize, - on_new_inbound: impl Fn(NegotiatedSubstream, PeerId, &ConnectedPoint, &TState) -> TInboundFuture - + Send - + 'static, - on_new_outbound: impl Fn( - NegotiatedSubstream, - PeerId, - &ConnectedPoint, - &TState, - TOutboundOpenInfo, - ) -> TOutboundFuture - + Send - + 'static, -) -> FromFnProto -where - TState: Clone, - TInboundFuture: Future + Send + 'static, - TOutboundFuture: Future + Send + 'static, -{ - FromFnProto { +pub fn from_fn(protocol: &'static str) -> Builder { + Builder { protocol, - inbound_streams_limit, - pending_outbound_streams_limit: pending_dial_limit, - on_new_inbound: Box::new(move |stream, remote_peer_id, connected_point, state| { - on_new_inbound(stream, remote_peer_id, connected_point, state).boxed() - }), - on_new_outbound: Box::new( - move |stream, remote_peer_id, connected_point, state, info| { - on_new_outbound(stream, remote_peer_id, connected_point, state, info).boxed() + phase: WantState {}, + } +} + +pub struct Builder { + protocol: &'static str, + phase: TPhase, +} + +pub struct WantState {} + +pub struct WantInboundHandler { + state: Arc, +} + +pub struct WantOutboundHandler { + state: Arc, + max_inbound: usize, + inbound_handler: Box< + dyn Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &TState, + ) -> BoxFuture<'static, TInbound> + + Send, + >, +} + +impl Builder { + pub fn with_state( + self, + shared: &Shared, + ) -> Builder> { + Builder { + protocol: self.protocol, + phase: WantInboundHandler { + state: shared.shared.clone(), }, - ), - state: state.shared.clone(), + } + } + + pub fn without_state(self) -> Builder> { + Builder { + protocol: self.protocol, + phase: WantInboundHandler { + state: Arc::new(()), + }, + } } } +impl Builder> { + pub fn with_inbound_handler( + self, + max_inbound: usize, + handler: impl Fn(NegotiatedSubstream, PeerId, &ConnectedPoint, &TState) -> TInboundFuture + + Send + + 'static, + ) -> Builder> + where + TInboundFuture: Future + Send + 'static, + { + Builder { + protocol: self.protocol, + phase: WantOutboundHandler { + state: self.phase.state, + max_inbound, + inbound_handler: Box::new(move |stream, peer, endpoint, state| { + handler(stream, peer, endpoint, state).boxed() + }), + }, + } + } + + pub fn without_inbound_handler(self) -> Builder> { + Builder { + protocol: self.protocol, + phase: WantOutboundHandler { + state: self.phase.state, + max_inbound: 1, + inbound_handler: Box::new(move |_, peer, _, _| { + async move { Dropped { peer } }.boxed() + }), + }, + } + } +} + +impl Builder> { + pub fn with_outbound_handler( + self, + max_pending_outbound: usize, + handler: impl Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &TState, + TOutboundInfo, + ) -> TOutboundFuture + + Send + + 'static, + ) -> FromFnProto + where + TOutboundFuture: Future + Send + 'static, + { + FromFnProto { + protocol: self.protocol, + on_new_inbound: self.phase.inbound_handler, + on_new_outbound: Box::new(move |stream, peer, endpoint, state, info| { + handler(stream, peer, endpoint, state, info).boxed() + }), + inbound_streams_limit: self.phase.max_inbound, + pending_outbound_streams_limit: max_pending_outbound, + state: self.phase.state, + } + } + + pub fn without_outbound_handler(self) -> FromFnProto { + FromFnProto { + protocol: self.protocol, + on_new_inbound: self.phase.inbound_handler, + on_new_outbound: Box::new(|_, _, _, _, info| { + async move { void::unreachable(info) }.boxed() + }), + inbound_streams_limit: self.phase.max_inbound, + pending_outbound_streams_limit: 0, + state: self.phase.state, + } + } +} + +#[derive(Debug)] +pub struct Dropped { + pub peer: PeerId, +} + #[derive(Debug)] pub enum OutEvent { InboundFinished(I), @@ -620,12 +721,9 @@ mod tests { type OutEvent = HashMap; fn new_handler(&mut self) -> Self::ConnectionHandler { - from_fn( - "/hello/1.0.0", - &self.state, - 5, - 5, - |mut stream, _, _, state| { + from_fn("/hello/1.0.0") + .with_state(&self.state) + .with_inbound_handler(5, |mut stream, _, _, state| { let my_name = state.name.to_owned(); async move { @@ -637,8 +735,8 @@ mod tests { Ok(Name(String::from_utf8(received_name).unwrap())) } - }, - |mut stream, _, _, state, _| { + }) + .with_outbound_handler(5, |mut stream, _, _, state, _| { let my_name = state.name.to_owned(); async move { @@ -651,8 +749,7 @@ mod tests { Ok(Name(String::from_utf8(received_name).unwrap())) } - }, - ) + }) } fn inject_connection_established( From df74f1461b54cfb4007478e692249212ed814ff9 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 11 Nov 2022 16:00:13 +1100 Subject: [PATCH 25/34] Update docs --- swarm/src/handler/from_fn.rs | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 68a4a71b5a6..ba13140578c 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -34,15 +34,11 @@ use void::Void; /// /// Inbound substreams may be opened at any time by the remote. To facilitate this one and more usecases, /// the supplied callbacks for inbound and outbound substream are given access to the handler's `state` -/// field. This `state` field can contain arbitrary data and can be updated by the [`NetworkBehaviour`] -/// via [`InEvent::UpdateState`]. +/// field. State can be shared between the [`NetworkBehaviour`] and a [`FromFn`] [`ConnectionHandler`] +/// via the [`Shared`] abstraction. /// -/// The design of this [`ConnectionHandler`] trades boilerplate (you don't have to write your own handler) -/// and simplicity (small API surface) for eventual consistency, depending on your protocol design: -/// -/// Most likely, the [`NetworkBehaviour`] is the authoritive source of `TState` but updates to it have -/// to be manually performed via [`InEvent::UpdateState`]. Thus, the state given to newly created -/// substreams, may be outdated and only eventually-consistent. +/// [`Shared`] tracks a piece of state and updates all registered [`ConnectionHandler`]s whenever the +/// state changes. /// /// [`NetworkBehaviour`]: crate::NetworkBehaviour pub fn from_fn(protocol: &'static str) -> Builder { From f84388fee9ad80307a3969163c3da720d57f8c94 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 11 Nov 2022 16:11:00 +1100 Subject: [PATCH 26/34] Make inbound streams impossible when no handler is configured --- protocols/rendezvous/src/client.rs | 11 ++- swarm/src/handler/from_fn.rs | 126 ++++++++++++++++------------- 2 files changed, 77 insertions(+), 60 deletions(-) diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index c24d31f6c6f..264ad4374ab 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -35,7 +35,6 @@ use libp2p_core::identity::error::SigningError; use libp2p_core::identity::Keypair; use libp2p_core::{ConnectedPoint, Multiaddr, PeerId, PeerRecord}; use libp2p_swarm::handler::from_fn; -use libp2p_swarm::handler::from_fn::Dropped; use libp2p_swarm::{ CloseConnection, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, @@ -45,12 +44,13 @@ use std::future::Future; use std::io; use std::iter::FromIterator; use std::task::{Context, Poll}; +use void::Void; pub struct Behaviour { events: VecDeque< NetworkBehaviourAction< Event, - from_fn::FromFnProto, OpenInfo, ()>, + from_fn::FromFnProto, OpenInfo, ()>, >, >, keypair: Keypair, @@ -197,8 +197,7 @@ pub enum OpenInfo { } impl NetworkBehaviour for Behaviour { - type ConnectionHandler = - from_fn::FromFnProto, OpenInfo, ()>; + type ConnectionHandler = from_fn::FromFnProto, OpenInfo, ()>; type OutEvent = Event; fn new_handler(&mut self) -> Self::ConnectionHandler { @@ -221,10 +220,10 @@ impl NetworkBehaviour for Behaviour { &mut self, __id: PeerId, _: ConnectionId, - event: from_fn::OutEvent, OpenInfo>, + event: from_fn::OutEvent, OpenInfo>, ) { match event { - from_fn::OutEvent::InboundFinished(Dropped { .. }) => {} + from_fn::OutEvent::InboundFinished(never) => void::unreachable(never), from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::Discovered { .. })) => {} from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::Registered { .. })) => {} from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::DiscoverFailed { .. })) => {} diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index ba13140578c..9c6550a116d 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -8,7 +8,8 @@ use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::StreamExt; use libp2p_core::connection::ConnectionId; -use libp2p_core::upgrade::{NegotiationError, ReadyUpgrade}; +use libp2p_core::either::EitherOutput; +use libp2p_core::upgrade::{DeniedUpgrade, EitherUpgrade, NegotiationError, ReadyUpgrade}; use libp2p_core::{ConnectedPoint, PeerId, UpgradeError}; use std::collections::{HashSet, VecDeque}; use std::error::Error; @@ -62,14 +63,16 @@ pub struct WantInboundHandler { pub struct WantOutboundHandler { state: Arc, max_inbound: usize, - inbound_handler: Box< - dyn Fn( - NegotiatedSubstream, - PeerId, - &ConnectedPoint, - &TState, - ) -> BoxFuture<'static, TInbound> - + Send, + inbound_handler: Option< + Box< + dyn Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &TState, + ) -> BoxFuture<'static, TInbound> + + Send, + >, >, } @@ -112,22 +115,20 @@ impl Builder> { phase: WantOutboundHandler { state: self.phase.state, max_inbound, - inbound_handler: Box::new(move |stream, peer, endpoint, state| { + inbound_handler: Some(Box::new(move |stream, peer, endpoint, state| { handler(stream, peer, endpoint, state).boxed() - }), + })), }, } } - pub fn without_inbound_handler(self) -> Builder> { + pub fn without_inbound_handler(self) -> Builder> { Builder { protocol: self.protocol, phase: WantOutboundHandler { state: self.phase.state, - max_inbound: 1, - inbound_handler: Box::new(move |_, peer, _, _| { - async move { Dropped { peer } }.boxed() - }), + max_inbound: 0, + inbound_handler: None, }, } } @@ -176,11 +177,6 @@ impl Builder> { } } -#[derive(Debug)] -pub struct Dropped { - pub peer: PeerId, -} - #[derive(Debug)] pub enum OutEvent { InboundFinished(I), @@ -319,14 +315,16 @@ pub enum InEvent { pub struct FromFnProto { protocol: &'static str, - on_new_inbound: Box< - dyn Fn( - NegotiatedSubstream, - PeerId, - &ConnectedPoint, - &TState, - ) -> BoxFuture<'static, TInbound> - + Send, + on_new_inbound: Option< + Box< + dyn Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &TState, + ) -> BoxFuture<'static, TInbound> + + Send, + >, >, on_new_outbound: Box< dyn Fn( @@ -378,7 +376,11 @@ where } fn inbound_protocol(&self) -> ::InboundProtocol { - ReadyUpgrade::new(self.protocol) + if self.on_new_inbound.is_some() { + EitherUpgrade::B(ReadyUpgrade::new(self.protocol)) + } else { + EitherUpgrade::A(DeniedUpgrade) + } } } @@ -390,14 +392,16 @@ pub struct FromFn { inbound_streams: FuturesUnordered>, outbound_streams: FuturesUnordered>, - on_new_inbound: Box< - dyn Fn( - NegotiatedSubstream, - PeerId, - &ConnectedPoint, - &TState, - ) -> BoxFuture<'static, TInbound> - + Send, + on_new_inbound: Option< + Box< + dyn Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &TState, + ) -> BoxFuture<'static, TInbound> + + Send, + >, >, on_new_outbound: Box< dyn Fn( @@ -433,13 +437,17 @@ where type InEvent = InEvent; type OutEvent = OutEvent; type Error = Void; - type InboundProtocol = ReadyUpgrade<&'static str>; + type InboundProtocol = EitherUpgrade>; type OutboundProtocol = ReadyUpgrade<&'static str>; type InboundOpenInfo = (); type OutboundOpenInfo = TOutboundInfo; fn listen_protocol(&self) -> SubstreamProtocol { - SubstreamProtocol::new(ReadyUpgrade::new(self.protocol), ()) + if self.on_new_inbound.is_some() { + SubstreamProtocol::new(EitherUpgrade::B(ReadyUpgrade::new(self.protocol)), ()) + } else { + SubstreamProtocol::new(EitherUpgrade::A(DeniedUpgrade), ()) + } } fn inject_fully_negotiated_inbound( @@ -447,21 +455,31 @@ where protocol: ::Output, _: Self::InboundOpenInfo, ) { - if self.inbound_streams.len() >= self.inbound_streams_limit { - log::debug!( - "Dropping inbound substream because limit ({}) would be exceeded", - self.inbound_streams_limit - ); - return; - } + match protocol { + EitherOutput::First(never) => void::unreachable(never), + EitherOutput::Second(protocol) => { + if self.inbound_streams.len() >= self.inbound_streams_limit { + log::debug!( + "Dropping inbound substream because limit ({}) would be exceeded", + self.inbound_streams_limit + ); + return; + } - let inbound_future = (self.on_new_inbound)( - protocol, - self.remote_peer_id, - &self.connected_point, - &mut self.state, - ); - self.inbound_streams.push(inbound_future); + let on_new_inbound = self + .on_new_inbound + .as_ref() + .expect("to have callback when protocol was negotiated"); + + let inbound_future = (on_new_inbound)( + protocol, + self.remote_peer_id, + &self.connected_point, + &mut self.state, + ); + self.inbound_streams.push(inbound_future); + } + } } fn inject_fully_negotiated_outbound( From 6d1add5cd87d412a40e87a4cfdc76f06e69e53c7 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 11 Nov 2022 23:05:46 +1100 Subject: [PATCH 27/34] Allow for streaming protocols --- protocols/rendezvous/src/client.rs | 12 ++-- protocols/rendezvous/src/server.rs | 12 ++-- swarm/src/handler/from_fn.rs | 95 ++++++++++++++++++++---------- 3 files changed, 77 insertions(+), 42 deletions(-) diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index 264ad4374ab..a7d7a4a7607 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -223,12 +223,12 @@ impl NetworkBehaviour for Behaviour { event: from_fn::OutEvent, OpenInfo>, ) { match event { - from_fn::OutEvent::InboundFinished(never) => void::unreachable(never), - from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::Discovered { .. })) => {} - from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::Registered { .. })) => {} - from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::DiscoverFailed { .. })) => {} - from_fn::OutEvent::OutboundFinished(Ok(OutboundEvent::RegisterFailed(..))) => {} - from_fn::OutEvent::OutboundFinished(Err(e)) => {} + from_fn::OutEvent::InboundEmitted(never) => void::unreachable(never), + from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::Discovered { .. })) => {} + from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::Registered { .. })) => {} + from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::DiscoverFailed { .. })) => {} + from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::RegisterFailed(..))) => {} + from_fn::OutEvent::OutboundEmitted(Err(e)) => {} from_fn::OutEvent::FailedToOpen(from_fn::OpenError::Timeout(info)) => {} from_fn::OutEvent::FailedToOpen(from_fn::OpenError::NegotiationFailed(..)) => {} from_fn::OutEvent::FailedToOpen(from_fn::OpenError::LimitExceeded(..)) => {} diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index 303c2c14d06..73be2d1a3ec 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -27,7 +27,7 @@ use asynchronous_codec::Framed; use bimap::BiMap; use futures::future::BoxFuture; use futures::stream::FuturesUnordered; -use futures::{ready, SinkExt}; +use futures::{ready, SinkExt, Stream}; use futures::{FutureExt, StreamExt}; use libp2p_core::connection::ConnectionId; use libp2p_core::{ConnectedPoint, Multiaddr, PeerId}; @@ -155,26 +155,26 @@ impl NetworkBehaviour for Behaviour { event: from_fn::OutEvent, Error>, Void, Void>, ) { match event { - from_fn::OutEvent::InboundFinished(Ok(Some(InboundOutEvent::NewRegistration( + from_fn::OutEvent::InboundEmitted(Ok(Some(InboundOutEvent::NewRegistration( new_registration, )))) => self .registrations .add(new_registration, &mut self.registration_data), - from_fn::OutEvent::InboundFinished(Ok(Some(InboundOutEvent::Unregister( + from_fn::OutEvent::InboundEmitted(Ok(Some(InboundOutEvent::Unregister( namespace, )))) => self .registrations .remove(namespace, peer_id, &mut self.registration_data), - from_fn::OutEvent::OutboundFinished(_) => {} + from_fn::OutEvent::OutboundEmitted(_) => {} from_fn::OutEvent::FailedToOpen(never) => match never { from_fn::OpenError::Timeout(never) => void::unreachable(never), from_fn::OpenError::LimitExceeded(never) => void::unreachable(never), from_fn::OpenError::NegotiationFailed(never, _) => void::unreachable(never), }, - from_fn::OutEvent::InboundFinished(Err(error)) => { + from_fn::OutEvent::InboundEmitted(Err(error)) => { log::debug!("Inbound stream from {peer_id} failed: {error}"); } - from_fn::OutEvent::InboundFinished(Ok(None)) => {} + from_fn::OutEvent::InboundEmitted(Ok(None)) => {} } } diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 9c6550a116d..06f30c42b62 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -3,10 +3,8 @@ use crate::{ ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, IntoConnectionHandler, KeepAlive, NegotiatedSubstream, NetworkBehaviourAction, NotifyHandler, SubstreamProtocol, }; -use futures::future::BoxFuture; -use futures::future::FutureExt; -use futures::stream::FuturesUnordered; -use futures::StreamExt; +use futures::stream::{BoxStream, SelectAll}; +use futures::{Stream, StreamExt}; use libp2p_core::connection::ConnectionId; use libp2p_core::either::EitherOutput; use libp2p_core::upgrade::{DeniedUpgrade, EitherUpgrade, NegotiationError, ReadyUpgrade}; @@ -70,7 +68,7 @@ pub struct WantOutboundHandler { PeerId, &ConnectedPoint, &TState, - ) -> BoxFuture<'static, TInbound> + ) -> BoxStream<'static, TInbound> + Send, >, >, @@ -100,15 +98,30 @@ impl Builder { } impl Builder> { - pub fn with_inbound_handler( + pub fn with_inbound_handler( self, max_inbound: usize, - handler: impl Fn(NegotiatedSubstream, PeerId, &ConnectedPoint, &TState) -> TInboundFuture + handler: impl Fn(NegotiatedSubstream, PeerId, &ConnectedPoint, &TState) -> TInboundStream + Send + 'static, ) -> Builder> where - TInboundFuture: Future + Send + 'static, + TInboundStream: Future + Send + 'static, + { + self.with_streaming_inbound_handler(max_inbound, move |stream, peer, endpoint, state| { + futures::stream::once(handler(stream, peer, endpoint, state)) + }) + } + + pub fn with_streaming_inbound_handler( + self, + max_inbound: usize, + handler: impl Fn(NegotiatedSubstream, PeerId, &ConnectedPoint, &TState) -> TInboundStream + + Send + + 'static, + ) -> Builder> + where + TInboundStream: Stream + Send + 'static, { Builder { protocol: self.protocol, @@ -135,7 +148,7 @@ impl Builder> { } impl Builder> { - pub fn with_outbound_handler( + pub fn with_outbound_handler( self, max_pending_outbound: usize, handler: impl Fn( @@ -144,12 +157,36 @@ impl Builder> { &ConnectedPoint, &TState, TOutboundInfo, - ) -> TOutboundFuture + ) -> TOutboundStream + Send + 'static, ) -> FromFnProto where - TOutboundFuture: Future + Send + 'static, + TOutboundStream: Future + Send + 'static, + { + self.with_streaming_outbound_handler( + max_pending_outbound, + move |stream, peer, endpoint, state, info| { + futures::stream::once(handler(stream, peer, endpoint, state, info)) + }, + ) + } + + pub fn with_streaming_outbound_handler( + self, + max_pending_outbound: usize, + handler: impl Fn( + NegotiatedSubstream, + PeerId, + &ConnectedPoint, + &TState, + TOutboundInfo, + ) -> TOutboundStream + + Send + + 'static, + ) -> FromFnProto + where + TOutboundStream: Stream + Send + 'static, { FromFnProto { protocol: self.protocol, @@ -167,9 +204,7 @@ impl Builder> { FromFnProto { protocol: self.protocol, on_new_inbound: self.phase.inbound_handler, - on_new_outbound: Box::new(|_, _, _, _, info| { - async move { void::unreachable(info) }.boxed() - }), + on_new_outbound: Box::new(|_, _, _, _, info| void::unreachable(info)), inbound_streams_limit: self.phase.max_inbound, pending_outbound_streams_limit: 0, state: self.phase.state, @@ -179,8 +214,8 @@ impl Builder> { #[derive(Debug)] pub enum OutEvent { - InboundFinished(I), - OutboundFinished(O), + InboundEmitted(I), + OutboundEmitted(O), FailedToOpen(OpenError), } @@ -322,7 +357,7 @@ pub struct FromFnProto { PeerId, &ConnectedPoint, &TState, - ) -> BoxFuture<'static, TInbound> + ) -> BoxStream<'static, TInbound> + Send, >, >, @@ -333,7 +368,7 @@ pub struct FromFnProto { &ConnectedPoint, &TState, TOutboundOpenInfo, - ) -> BoxFuture<'static, TOutbound> + ) -> BoxStream<'static, TOutbound> + Send, >, @@ -362,8 +397,8 @@ where protocol: self.protocol, remote_peer_id: *remote_peer_id, connected_point: connected_point.clone(), - inbound_streams: FuturesUnordered::default(), - outbound_streams: FuturesUnordered::default(), + inbound_streams: SelectAll::default(), + outbound_streams: SelectAll::default(), on_new_inbound: self.on_new_inbound, on_new_outbound: self.on_new_outbound, inbound_streams_limit: self.inbound_streams_limit, @@ -389,8 +424,8 @@ pub struct FromFn { remote_peer_id: PeerId, connected_point: ConnectedPoint, - inbound_streams: FuturesUnordered>, - outbound_streams: FuturesUnordered>, + inbound_streams: SelectAll>, + outbound_streams: SelectAll>, on_new_inbound: Option< Box< @@ -399,7 +434,7 @@ pub struct FromFn { PeerId, &ConnectedPoint, &TState, - ) -> BoxFuture<'static, TInbound> + ) -> BoxStream<'static, TInbound> + Send, >, >, @@ -410,7 +445,7 @@ pub struct FromFn { &ConnectedPoint, &TState, TOutboundInfo, - ) -> BoxFuture<'static, TOutbound> + ) -> BoxStream<'static, TOutbound> + Send, >, @@ -553,7 +588,7 @@ where match self.outbound_streams.poll_next_unpin(cx) { Poll::Ready(Some(outbound_done)) => { - return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::OutboundFinished( + return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::OutboundEmitted( outbound_done, ))); } @@ -566,7 +601,7 @@ where match self.inbound_streams.poll_next_unpin(cx) { Poll::Ready(Some(inbound_done)) => { - return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::InboundFinished( + return Poll::Ready(ConnectionHandlerEvent::Custom(OutEvent::InboundEmitted( inbound_done, ))); } @@ -795,18 +830,18 @@ mod tests { event: <::Handler as ConnectionHandler>::OutEvent, ) { match event { - OutEvent::InboundFinished(Ok(name)) => { + OutEvent::InboundEmitted(Ok(name)) => { self.greeting_count.entry(name).or_default().add_assign(1); self.pending_events.push_back(self.greeting_count.clone()) } - OutEvent::OutboundFinished(Ok(name)) => { + OutEvent::OutboundEmitted(Ok(name)) => { self.greeting_count.entry(name).or_default().add_assign(1); self.pending_events.push_back(self.greeting_count.clone()) } - OutEvent::InboundFinished(_) => {} - OutEvent::OutboundFinished(_) => {} + OutEvent::InboundEmitted(_) => {} + OutEvent::OutboundEmitted(_) => {} OutEvent::FailedToOpen(OpenError::Timeout(_)) => {} OutEvent::FailedToOpen(OpenError::NegotiationFailed(_, _neg_error)) => {} OutEvent::FailedToOpen(OpenError::LimitExceeded(_)) => {} From 913caf3cec10ee817543b998d379ff237b0b4736 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sat, 12 Nov 2022 10:40:51 +1100 Subject: [PATCH 28/34] Pass `ConnectedPoint` by value This clone is not very expensive and offers a huge ergonomic improvement. --- protocols/rendezvous/src/client.rs | 2 +- protocols/rendezvous/src/server.rs | 13 +++++----- swarm/src/handler/from_fn.rs | 40 +++++++++++++++--------------- 3 files changed, 27 insertions(+), 28 deletions(-) diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index a7d7a4a7607..9947f1873bb 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -383,7 +383,7 @@ impl NetworkBehaviour for Behaviour { fn outbound_stream_handler( substream: NegotiatedSubstream, _: PeerId, - _: &ConnectedPoint, + _: ConnectedPoint, _: &(), request: OpenInfo, ) -> impl Future> { diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index 73be2d1a3ec..a98f1d26f96 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -27,7 +27,7 @@ use asynchronous_codec::Framed; use bimap::BiMap; use futures::future::BoxFuture; use futures::stream::FuturesUnordered; -use futures::{ready, SinkExt, Stream}; +use futures::{ready, SinkExt}; use futures::{FutureExt, StreamExt}; use libp2p_core::connection::ConnectionId; use libp2p_core::{ConnectedPoint, Multiaddr, PeerId}; @@ -160,11 +160,10 @@ impl NetworkBehaviour for Behaviour { )))) => self .registrations .add(new_registration, &mut self.registration_data), - from_fn::OutEvent::InboundEmitted(Ok(Some(InboundOutEvent::Unregister( - namespace, - )))) => self - .registrations - .remove(namespace, peer_id, &mut self.registration_data), + from_fn::OutEvent::InboundEmitted(Ok(Some(InboundOutEvent::Unregister(namespace)))) => { + self.registrations + .remove(namespace, peer_id, &mut self.registration_data) + } from_fn::OutEvent::OutboundEmitted(_) => {} from_fn::OutEvent::FailedToOpen(never) => match never { from_fn::OpenError::Timeout(never) => void::unreachable(never), @@ -202,7 +201,7 @@ impl NetworkBehaviour for Behaviour { fn inbound_stream_handler( substream: NegotiatedSubstream, _: PeerId, - _: &ConnectedPoint, + _: ConnectedPoint, registrations: &RegistrationData, ) -> impl Future, Error>> { let mut registrations = registrations.clone(); diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 06f30c42b62..7a0a0d03291 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -66,8 +66,8 @@ pub struct WantOutboundHandler { dyn Fn( NegotiatedSubstream, PeerId, - &ConnectedPoint, - &TState, + ConnectedPoint, + Arc, ) -> BoxStream<'static, TInbound> + Send, >, @@ -101,7 +101,7 @@ impl Builder> { pub fn with_inbound_handler( self, max_inbound: usize, - handler: impl Fn(NegotiatedSubstream, PeerId, &ConnectedPoint, &TState) -> TInboundStream + handler: impl Fn(NegotiatedSubstream, PeerId, ConnectedPoint, &TState) -> TInboundStream + Send + 'static, ) -> Builder> @@ -116,7 +116,7 @@ impl Builder> { pub fn with_streaming_inbound_handler( self, max_inbound: usize, - handler: impl Fn(NegotiatedSubstream, PeerId, &ConnectedPoint, &TState) -> TInboundStream + handler: impl Fn(NegotiatedSubstream, PeerId, ConnectedPoint, &TState) -> TInboundStream + Send + 'static, ) -> Builder> @@ -129,7 +129,7 @@ impl Builder> { state: self.phase.state, max_inbound, inbound_handler: Some(Box::new(move |stream, peer, endpoint, state| { - handler(stream, peer, endpoint, state).boxed() + handler(stream, peer, endpoint, &state).boxed() })), }, } @@ -154,7 +154,7 @@ impl Builder> { handler: impl Fn( NegotiatedSubstream, PeerId, - &ConnectedPoint, + ConnectedPoint, &TState, TOutboundInfo, ) -> TOutboundStream @@ -178,7 +178,7 @@ impl Builder> { handler: impl Fn( NegotiatedSubstream, PeerId, - &ConnectedPoint, + ConnectedPoint, &TState, TOutboundInfo, ) -> TOutboundStream @@ -192,7 +192,7 @@ impl Builder> { protocol: self.protocol, on_new_inbound: self.phase.inbound_handler, on_new_outbound: Box::new(move |stream, peer, endpoint, state, info| { - handler(stream, peer, endpoint, state, info).boxed() + handler(stream, peer, endpoint, &state, info).boxed() }), inbound_streams_limit: self.phase.max_inbound, pending_outbound_streams_limit: max_pending_outbound, @@ -355,8 +355,8 @@ pub struct FromFnProto { dyn Fn( NegotiatedSubstream, PeerId, - &ConnectedPoint, - &TState, + ConnectedPoint, + Arc, ) -> BoxStream<'static, TInbound> + Send, >, @@ -365,8 +365,8 @@ pub struct FromFnProto { dyn Fn( NegotiatedSubstream, PeerId, - &ConnectedPoint, - &TState, + ConnectedPoint, + Arc, TOutboundOpenInfo, ) -> BoxStream<'static, TOutbound> + Send, @@ -432,8 +432,8 @@ pub struct FromFn { dyn Fn( NegotiatedSubstream, PeerId, - &ConnectedPoint, - &TState, + ConnectedPoint, + Arc, ) -> BoxStream<'static, TInbound> + Send, >, @@ -442,8 +442,8 @@ pub struct FromFn { dyn Fn( NegotiatedSubstream, PeerId, - &ConnectedPoint, - &TState, + ConnectedPoint, + Arc, TOutboundInfo, ) -> BoxStream<'static, TOutbound> + Send, @@ -509,8 +509,8 @@ where let inbound_future = (on_new_inbound)( protocol, self.remote_peer_id, - &self.connected_point, - &mut self.state, + self.connected_point.clone(), + Arc::clone(&self.state), ); self.inbound_streams.push(inbound_future); } @@ -525,8 +525,8 @@ where let outbound_future = (self.on_new_outbound)( protocol, self.remote_peer_id, - &self.connected_point, - &mut self.state, + self.connected_point.clone(), + Arc::clone(&self.state), info, ); self.outbound_streams.push(outbound_future); From e9cd5fc011aecdbb9b647f71b17d741a99ba3b21 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sat, 12 Nov 2022 10:52:26 +1100 Subject: [PATCH 29/34] Allow regular async functions to be used as handlers --- protocols/rendezvous/src/client.rs | 107 ++++++++++++++--------------- protocols/rendezvous/src/server.rs | 102 ++++++++++++++------------- swarm/src/handler/from_fn.rs | 46 +++++-------- 3 files changed, 122 insertions(+), 133 deletions(-) diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index 9947f1873bb..495ba95b82f 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -43,6 +43,7 @@ use std::collections::{HashMap, VecDeque}; use std::future::Future; use std::io; use std::iter::FromIterator; +use std::sync::Arc; use std::task::{Context, Poll}; use void::Void; @@ -380,69 +381,67 @@ impl NetworkBehaviour for Behaviour { // } // } -fn outbound_stream_handler( +async fn outbound_stream_handler( substream: NegotiatedSubstream, _: PeerId, _: ConnectedPoint, - _: &(), + _: Arc<()>, request: OpenInfo, -) -> impl Future> { +) -> Result { let mut substream = Framed::new(substream, RendezvousCodec::default()); - async move { - substream - .send(match request.clone() { - OpenInfo::RegisterRequest(new_registration) => Message::Register(new_registration), - OpenInfo::UnregisterRequest(namespace) => Message::Unregister(namespace), - OpenInfo::DiscoverRequest { - namespace, - cookie, - limit, - } => Message::Discover { - namespace, - cookie, - limit, - }, - }) - .await?; - - let response = substream.next().await.transpose()?; - - let out_event = match (request, response) { - (OpenInfo::RegisterRequest(r), Some(Message::RegisterResponse(Ok(ttl)))) => { - OutboundEvent::Registered { - namespace: r.namespace, - ttl, - } - } - (OpenInfo::RegisterRequest(r), Some(Message::RegisterResponse(Err(e)))) => { - OutboundEvent::RegisterFailed(r.namespace, e) - } - ( - OpenInfo::DiscoverRequest { .. }, - Some(Message::DiscoverResponse(Ok((registrations, cookie)))), - ) => OutboundEvent::Discovered { - registrations, + substream + .send(match request.clone() { + OpenInfo::RegisterRequest(new_registration) => Message::Register(new_registration), + OpenInfo::UnregisterRequest(namespace) => Message::Unregister(namespace), + OpenInfo::DiscoverRequest { + namespace, + cookie, + limit, + } => Message::Discover { + namespace, cookie, + limit, }, - ( - OpenInfo::DiscoverRequest { namespace, .. }, - Some(Message::DiscoverResponse(Err(error))), - ) => OutboundEvent::DiscoverFailed { namespace, error }, - (OpenInfo::UnregisterRequest(_), None) => { - // All good. + }) + .await?; - todo!() - } - (_, None) => { - // EOF? - todo!() - } - _ => { - panic!("protocol violation") // TODO: Make two different codecs to avoid this? + let response = substream.next().await.transpose()?; + + let out_event = match (request, response) { + (OpenInfo::RegisterRequest(r), Some(Message::RegisterResponse(Ok(ttl)))) => { + OutboundEvent::Registered { + namespace: r.namespace, + ttl, } - }; + } + (OpenInfo::RegisterRequest(r), Some(Message::RegisterResponse(Err(e)))) => { + OutboundEvent::RegisterFailed(r.namespace, e) + } + ( + OpenInfo::DiscoverRequest { .. }, + Some(Message::DiscoverResponse(Ok((registrations, cookie)))), + ) => OutboundEvent::Discovered { + registrations, + cookie, + }, + ( + OpenInfo::DiscoverRequest { namespace, .. }, + Some(Message::DiscoverResponse(Err(error))), + ) => OutboundEvent::DiscoverFailed { namespace, error }, + (OpenInfo::UnregisterRequest(_), None) => { + // All good. - Ok(out_event) - } + todo!() + } + (_, None) => { + // EOF? + todo!() + } + _ => { + panic!("protocol violation") // TODO: Make two different codecs to avoid this? + } + }; + + Ok(out_event) } diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index a98f1d26f96..21a83226bdf 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -40,6 +40,7 @@ use std::collections::{HashMap, HashSet}; use std::future::Future; use std::io; use std::iter::FromIterator; +use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; use void::Void; @@ -198,71 +199,68 @@ impl NetworkBehaviour for Behaviour { } } -fn inbound_stream_handler( +async fn inbound_stream_handler( substream: NegotiatedSubstream, _: PeerId, _: ConnectedPoint, - registrations: &RegistrationData, -) -> impl Future, Error>> { - let mut registrations = registrations.clone(); + registrations: Arc, +) -> Result, Error> { let mut substream = Framed::new(substream, RendezvousCodec::default()); - async move { - let message = substream - .next() - .await - .ok_or_else(|| Error::Io(io::ErrorKind::UnexpectedEof.into()))??; + let message = substream + .next() + .await + .ok_or_else(|| Error::Io(io::ErrorKind::UnexpectedEof.into()))??; - let out_event = match message { - Message::Register(new_registration) => { - // TODO: Validate registration + let out_event = match message { + Message::Register(new_registration) => { + // TODO: Validate registration + substream + .send(Message::RegisterResponse(Ok( + new_registration.effective_ttl() + ))) + .await?; + substream.close().await?; + + Some(InboundOutEvent::NewRegistration(new_registration)) + } + Message::Unregister(namespace) => { + substream.close().await?; + + Some(InboundOutEvent::Unregister(namespace)) + } + Message::Discover { + namespace, + cookie, + limit, + } => match registrations.get(namespace, cookie, limit) { + Ok((registrations, cookie)) => { substream - .send(Message::RegisterResponse(Ok( - new_registration.effective_ttl() - ))) + .send(Message::DiscoverResponse(Ok(( + registrations.cloned().collect(), + cookie, + )))) .await?; substream.close().await?; - Some(InboundOutEvent::NewRegistration(new_registration)) + None } - Message::Unregister(namespace) => { + Err(e) => { + substream + .send(Message::DiscoverResponse(Err(ErrorCode::InvalidCookie))) + .await?; substream.close().await?; - Some(InboundOutEvent::Unregister(namespace)) - } - Message::Discover { - namespace, - cookie, - limit, - } => match registrations.get(namespace, cookie, limit) { - Ok((registrations, cookie)) => { - substream - .send(Message::DiscoverResponse(Ok(( - registrations.cloned().collect(), - cookie, - )))) - .await?; - substream.close().await?; - - None - } - Err(e) => { - substream - .send(Message::DiscoverResponse(Err(ErrorCode::InvalidCookie))) - .await?; - substream.close().await?; - - None - } - }, - Message::DiscoverResponse(_) | Message::RegisterResponse(_) => { - panic!("protocol violation") + None } - }; + }, + Message::DiscoverResponse(_) | Message::RegisterResponse(_) => { + panic!("protocol violation") + } + }; - Ok(out_event) - } + Ok(out_event) } #[derive(Debug)] @@ -298,7 +296,7 @@ pub struct RegistrationData { impl RegistrationData { pub fn get( - &mut self, + &self, discover_namespace: Option, cookie: Option, limit: Option, @@ -348,8 +346,8 @@ impl RegistrationData { let new_cookie = discover_namespace .map(Cookie::for_namespace) .unwrap_or_else(Cookie::for_all_namespaces); - self.cookies - .insert(new_cookie.clone(), reggos_of_last_discover); + // self.cookies + // .insert(new_cookie.clone(), reggos_of_last_discover); let reggos = &self.registrations; let registrations = ids diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 7a0a0d03291..9be5d2f724d 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -101,7 +101,7 @@ impl Builder> { pub fn with_inbound_handler( self, max_inbound: usize, - handler: impl Fn(NegotiatedSubstream, PeerId, ConnectedPoint, &TState) -> TInboundStream + handler: impl Fn(NegotiatedSubstream, PeerId, ConnectedPoint, Arc) -> TInboundStream + Send + 'static, ) -> Builder> @@ -116,7 +116,7 @@ impl Builder> { pub fn with_streaming_inbound_handler( self, max_inbound: usize, - handler: impl Fn(NegotiatedSubstream, PeerId, ConnectedPoint, &TState) -> TInboundStream + handler: impl Fn(NegotiatedSubstream, PeerId, ConnectedPoint, Arc) -> TInboundStream + Send + 'static, ) -> Builder> @@ -129,7 +129,7 @@ impl Builder> { state: self.phase.state, max_inbound, inbound_handler: Some(Box::new(move |stream, peer, endpoint, state| { - handler(stream, peer, endpoint, &state).boxed() + handler(stream, peer, endpoint, state).boxed() })), }, } @@ -155,7 +155,7 @@ impl Builder> { NegotiatedSubstream, PeerId, ConnectedPoint, - &TState, + Arc, TOutboundInfo, ) -> TOutboundStream + Send @@ -179,7 +179,7 @@ impl Builder> { NegotiatedSubstream, PeerId, ConnectedPoint, - &TState, + Arc, TOutboundInfo, ) -> TOutboundStream + Send @@ -192,7 +192,7 @@ impl Builder> { protocol: self.protocol, on_new_inbound: self.phase.inbound_handler, on_new_outbound: Box::new(move |stream, peer, endpoint, state, info| { - handler(stream, peer, endpoint, &state, info).boxed() + handler(stream, peer, endpoint, state, info).boxed() }), inbound_streams_limit: self.phase.max_inbound, pending_outbound_streams_limit: max_pending_outbound, @@ -772,32 +772,24 @@ mod tests { fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn("/hello/1.0.0") .with_state(&self.state) - .with_inbound_handler(5, |mut stream, _, _, state| { - let my_name = state.name.to_owned(); - - async move { - let mut received_name = Vec::new(); - stream.read_to_end(&mut received_name).await?; + .with_inbound_handler(5, |mut stream, _, _, state| async move { + let mut received_name = Vec::new(); + stream.read_to_end(&mut received_name).await?; - stream.write_all(&my_name.0.as_bytes()).await?; - stream.close().await?; + stream.write_all(state.name.0.as_bytes()).await?; + stream.close().await?; - Ok(Name(String::from_utf8(received_name).unwrap())) - } + Ok(Name(String::from_utf8(received_name).unwrap())) }) - .with_outbound_handler(5, |mut stream, _, _, state, _| { - let my_name = state.name.to_owned(); - - async move { - stream.write_all(&my_name.0.as_bytes()).await?; - stream.flush().await?; - stream.close().await?; + .with_outbound_handler(5, |mut stream, _, _, state, _| async move { + stream.write_all(state.name.0.as_bytes()).await?; + stream.flush().await?; + stream.close().await?; - let mut received_name = Vec::new(); - stream.read_to_end(&mut received_name).await?; + let mut received_name = Vec::new(); + stream.read_to_end(&mut received_name).await?; - Ok(Name(String::from_utf8(received_name).unwrap())) - } + Ok(Name(String::from_utf8(received_name).unwrap())) }) } From 580157674de079e34eda3b88cfb23173ea115ea0 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sat, 12 Nov 2022 11:00:48 +1100 Subject: [PATCH 30/34] Flatten errors --- protocols/rendezvous/src/client.rs | 3 +- protocols/rendezvous/src/server.rs | 7 +++- swarm/src/handler/from_fn.rs | 54 ++++++++++++++++++++++++------ 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index 495ba95b82f..b3d7d2cc976 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -232,7 +232,8 @@ impl NetworkBehaviour for Behaviour { from_fn::OutEvent::OutboundEmitted(Err(e)) => {} from_fn::OutEvent::FailedToOpen(from_fn::OpenError::Timeout(info)) => {} from_fn::OutEvent::FailedToOpen(from_fn::OpenError::NegotiationFailed(..)) => {} - from_fn::OutEvent::FailedToOpen(from_fn::OpenError::LimitExceeded(..)) => {} + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::LimitExceeded { .. }) => {} + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::Unsupported { .. }) => {} } // // let new_events = match event { diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index 21a83226bdf..3b425be0b14 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -168,8 +168,13 @@ impl NetworkBehaviour for Behaviour { from_fn::OutEvent::OutboundEmitted(_) => {} from_fn::OutEvent::FailedToOpen(never) => match never { from_fn::OpenError::Timeout(never) => void::unreachable(never), - from_fn::OpenError::LimitExceeded(never) => void::unreachable(never), + from_fn::OpenError::LimitExceeded { + open_info: never, .. + } => void::unreachable(never), from_fn::OpenError::NegotiationFailed(never, _) => void::unreachable(never), + from_fn::OpenError::Unsupported { + open_info: never, .. + } => void::unreachable(never), }, from_fn::OutEvent::InboundEmitted(Err(error)) => { log::debug!("Inbound stream from {peer_id} failed: {error}"); diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 9be5d2f724d..2ce08045ac7 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -7,7 +7,9 @@ use futures::stream::{BoxStream, SelectAll}; use futures::{Stream, StreamExt}; use libp2p_core::connection::ConnectionId; use libp2p_core::either::EitherOutput; -use libp2p_core::upgrade::{DeniedUpgrade, EitherUpgrade, NegotiationError, ReadyUpgrade}; +use libp2p_core::upgrade::{ + DeniedUpgrade, EitherUpgrade, NegotiationError, ProtocolError, ReadyUpgrade, +}; use libp2p_core::{ConnectedPoint, PeerId, UpgradeError}; use std::collections::{HashSet, VecDeque}; use std::error::Error; @@ -221,9 +223,19 @@ pub enum OutEvent { #[derive(Debug)] pub enum OpenError { + /// The time limit for the negotiation handshake was exceeded. Timeout(OpenInfo), - LimitExceeded(OpenInfo), - NegotiationFailed(OpenInfo, NegotiationError), + /// We have hit the configured limit for the maximum number of pending substreams. + LimitExceeded { + open_info: OpenInfo, + pending_substreams: usize, + }, + /// The remote does not support this protocol. + Unsupported { + open_info: OpenInfo, + protocol: &'static str, + }, + NegotiationFailed(OpenInfo, ProtocolError), } /// A wrapper for state that is shared across all connections. @@ -322,8 +334,16 @@ impl fmt::Display for OpenError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { OpenError::Timeout(_) => write!(f, "opening new substream timed out"), - OpenError::LimitExceeded(_) => write!(f, "limit for pending dials exceeded"), + OpenError::LimitExceeded { + pending_substreams, .. + } => write!( + f, + "limit for pending openings ({pending_substreams}) exceeded" + ), OpenError::NegotiationFailed(_, _) => Ok(()), // Don't print anything to avoid double printing of error. + OpenError::Unsupported { protocol, .. } => { + write!(f, "remote peer does not support {protocol}") + } } } } @@ -335,8 +355,9 @@ where fn source(&self) -> Option<&(dyn Error + 'static)> { match self { OpenError::Timeout(_) => None, - OpenError::LimitExceeded(_) => None, + OpenError::LimitExceeded { .. } => None, OpenError::NegotiationFailed(_, source) => Some(source), + OpenError::Unsupported { .. } => None, } } } @@ -537,8 +558,10 @@ where InEvent::UpdateState(new_state) => self.state = new_state, InEvent::NewOutbound(open_info) => { if self.pending_outbound_streams.len() >= self.pending_outbound_streams_limit { - self.failed_open - .push_back(OpenError::LimitExceeded(open_info)); + self.failed_open.push_back(OpenError::LimitExceeded { + open_info, + pending_substreams: self.pending_outbound_streams.len(), + }); } else { self.pending_outbound_streams.push_back(open_info); } @@ -556,9 +579,17 @@ where self.failed_open.push_back(OpenError::Timeout(info)) } ConnectionHandlerUpgrErr::Timer => {} - ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Select(negotiation)) => self + ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Select( + NegotiationError::ProtocolError(error), + )) => self .failed_open - .push_back(OpenError::NegotiationFailed(info, negotiation)), + .push_back(OpenError::NegotiationFailed(info, error)), + ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Select(NegotiationError::Failed)) => { + self.failed_open.push_back(OpenError::Unsupported { + open_info: info, + protocol: self.protocol, + }) + } ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Apply(apply)) => { void::unreachable(apply) } @@ -835,8 +866,9 @@ mod tests { OutEvent::InboundEmitted(_) => {} OutEvent::OutboundEmitted(_) => {} OutEvent::FailedToOpen(OpenError::Timeout(_)) => {} - OutEvent::FailedToOpen(OpenError::NegotiationFailed(_, _neg_error)) => {} - OutEvent::FailedToOpen(OpenError::LimitExceeded(_)) => {} + OutEvent::FailedToOpen(OpenError::NegotiationFailed(_, _)) => {} + OutEvent::FailedToOpen(OpenError::LimitExceeded { .. }) => {} + OutEvent::FailedToOpen(OpenError::Unsupported { .. }) => {} } } From c15433fe17c7233bfeeaa9c2ef9d8d708c6f8492 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Sat, 12 Nov 2022 12:34:39 +1100 Subject: [PATCH 31/34] Implement `libp2p-ping` with `from_fn` abstraction --- protocols/ping/src/handler.rs | 400 ---------------------------------- protocols/ping/src/lib.rs | 335 ++++++++++++++++++++++++---- 2 files changed, 289 insertions(+), 446 deletions(-) delete mode 100644 protocols/ping/src/handler.rs diff --git a/protocols/ping/src/handler.rs b/protocols/ping/src/handler.rs deleted file mode 100644 index af1ef898981..00000000000 --- a/protocols/ping/src/handler.rs +++ /dev/null @@ -1,400 +0,0 @@ -// Copyright 2019 Parity Technologies (UK) Ltd. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. - -use crate::{protocol, PROTOCOL_NAME}; -use futures::future::BoxFuture; -use futures::prelude::*; -use futures_timer::Delay; -use libp2p_core::upgrade::ReadyUpgrade; -use libp2p_core::{upgrade::NegotiationError, UpgradeError}; -use libp2p_swarm::{ - ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, KeepAlive, - NegotiatedSubstream, SubstreamProtocol, -}; -use std::collections::VecDeque; -use std::{ - error::Error, - fmt, io, - num::NonZeroU32, - task::{Context, Poll}, - time::Duration, -}; -use void::Void; - -/// The configuration for outbound pings. -#[derive(Debug, Clone)] -pub struct Config { - /// The timeout of an outbound ping. - timeout: Duration, - /// The duration between the last successful outbound or inbound ping - /// and the next outbound ping. - interval: Duration, - /// The maximum number of failed outbound pings before the associated - /// connection is deemed unhealthy, indicating to the `Swarm` that it - /// should be closed. - max_failures: NonZeroU32, - /// Whether the connection should generally be kept alive unless - /// `max_failures` occur. - keep_alive: bool, -} - -impl Config { - /// Creates a new [`Config`] with the following default settings: - /// - /// * [`Config::with_interval`] 15s - /// * [`Config::with_timeout`] 20s - /// * [`Config::with_max_failures`] 1 - /// * [`Config::with_keep_alive`] false - /// - /// These settings have the following effect: - /// - /// * A ping is sent every 15 seconds on a healthy connection. - /// * Every ping sent must yield a response within 20 seconds in order to - /// be successful. - /// * A single ping failure is sufficient for the connection to be subject - /// to being closed. - /// * The connection may be closed at any time as far as the ping protocol - /// is concerned, i.e. the ping protocol itself does not keep the - /// connection alive. - pub fn new() -> Self { - Self { - timeout: Duration::from_secs(20), - interval: Duration::from_secs(15), - max_failures: NonZeroU32::new(1).expect("1 != 0"), - keep_alive: false, - } - } - - /// Sets the ping timeout. - pub fn with_timeout(mut self, d: Duration) -> Self { - self.timeout = d; - self - } - - /// Sets the ping interval. - pub fn with_interval(mut self, d: Duration) -> Self { - self.interval = d; - self - } - - /// Sets the maximum number of consecutive ping failures upon which the remote - /// peer is considered unreachable and the connection closed. - pub fn with_max_failures(mut self, n: NonZeroU32) -> Self { - self.max_failures = n; - self - } - - /// Sets whether the ping protocol itself should keep the connection alive, - /// apart from the maximum allowed failures. - /// - /// By default, the ping protocol itself allows the connection to be closed - /// at any time, i.e. in the absence of ping failures the connection lifetime - /// is determined by other protocol handlers. - /// - /// If the maximum number of allowed ping failures is reached, the - /// connection is always terminated as a result of [`ConnectionHandler::poll`] - /// returning an error, regardless of the keep-alive setting. - #[deprecated( - since = "0.40.0", - note = "Use `libp2p::swarm::behaviour::KeepAlive` if you need to keep connections alive unconditionally." - )] - pub fn with_keep_alive(mut self, b: bool) -> Self { - self.keep_alive = b; - self - } -} - -impl Default for Config { - fn default() -> Self { - Self::new() - } -} - -/// The successful result of processing an inbound or outbound ping. -#[derive(Debug)] -pub enum Success { - /// Received a ping and sent back a pong. - Pong, - /// Sent a ping and received back a pong. - /// - /// Includes the round-trip time. - Ping { rtt: Duration }, -} - -/// An outbound ping failure. -#[derive(Debug)] -pub enum Failure { - /// The ping timed out, i.e. no response was received within the - /// configured ping timeout. - Timeout, - /// The peer does not support the ping protocol. - Unsupported, - /// The ping failed for reasons other than a timeout. - Other { - error: Box, - }, -} - -impl fmt::Display for Failure { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Failure::Timeout => f.write_str("Ping timeout"), - Failure::Other { error } => write!(f, "Ping error: {}", error), - Failure::Unsupported => write!(f, "Ping protocol not supported"), - } - } -} - -impl Error for Failure { - fn source(&self) -> Option<&(dyn Error + 'static)> { - match self { - Failure::Timeout => None, - Failure::Other { error } => Some(&**error), - Failure::Unsupported => None, - } - } -} - -/// Protocol handler that handles pinging the remote at a regular period -/// and answering ping queries. -/// -/// If the remote doesn't respond, produces an error that closes the connection. -pub struct Handler { - /// Configuration options. - config: Config, - /// The timer used for the delay to the next ping as well as - /// the ping timeout. - timer: Delay, - /// Outbound ping failures that are pending to be processed by `poll()`. - pending_errors: VecDeque, - /// The number of consecutive ping failures that occurred. - /// - /// Each successful ping resets this counter to 0. - failures: u32, - /// The outbound ping state. - outbound: Option, - /// The inbound pong handler, i.e. if there is an inbound - /// substream, this is always a future that waits for the - /// next inbound ping to be answered. - inbound: Option, - /// Tracks the state of our handler. - state: State, -} - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum State { - /// We are inactive because the other peer doesn't support ping. - Inactive { - /// Whether or not we've reported the missing support yet. - /// - /// This is used to avoid repeated events being emitted for a specific connection. - reported: bool, - }, - /// We are actively pinging the other peer. - Active, -} - -impl Handler { - /// Builds a new [`Handler`] with the given configuration. - pub fn new(config: Config) -> Self { - Handler { - config, - timer: Delay::new(Duration::new(0, 0)), - pending_errors: VecDeque::with_capacity(2), - failures: 0, - outbound: None, - inbound: None, - state: State::Active, - } - } -} - -impl ConnectionHandler for Handler { - type InEvent = Void; - type OutEvent = crate::Result; - type Error = Failure; - type InboundProtocol = ReadyUpgrade<&'static [u8]>; - type OutboundProtocol = ReadyUpgrade<&'static [u8]>; - type OutboundOpenInfo = (); - type InboundOpenInfo = (); - - fn listen_protocol(&self) -> SubstreamProtocol, ()> { - SubstreamProtocol::new(ReadyUpgrade::new(PROTOCOL_NAME), ()) - } - - fn inject_fully_negotiated_inbound(&mut self, stream: NegotiatedSubstream, (): ()) { - self.inbound = Some(protocol::recv_ping(stream).boxed()); - } - - fn inject_fully_negotiated_outbound(&mut self, stream: NegotiatedSubstream, (): ()) { - self.timer.reset(self.config.timeout); - self.outbound = Some(OutboundState::Ping(protocol::send_ping(stream).boxed())); - } - - fn inject_event(&mut self, _: Void) {} - - fn inject_dial_upgrade_error(&mut self, _info: (), error: ConnectionHandlerUpgrErr) { - self.outbound = None; // Request a new substream on the next `poll`. - - let error = match error { - ConnectionHandlerUpgrErr::Upgrade(UpgradeError::Select(NegotiationError::Failed)) => { - debug_assert_eq!(self.state, State::Active); - - self.state = State::Inactive { reported: false }; - return; - } - // Note: This timeout only covers protocol negotiation. - ConnectionHandlerUpgrErr::Timeout => Failure::Timeout, - e => Failure::Other { error: Box::new(e) }, - }; - - self.pending_errors.push_front(error); - } - - fn connection_keep_alive(&self) -> KeepAlive { - if self.config.keep_alive { - KeepAlive::Yes - } else { - KeepAlive::No - } - } - - fn poll( - &mut self, - cx: &mut Context<'_>, - ) -> Poll, (), crate::Result, Self::Error>> - { - match self.state { - State::Inactive { reported: true } => { - return Poll::Pending; // nothing to do on this connection - } - State::Inactive { reported: false } => { - self.state = State::Inactive { reported: true }; - return Poll::Ready(ConnectionHandlerEvent::Custom(Err(Failure::Unsupported))); - } - State::Active => {} - } - - // Respond to inbound pings. - if let Some(fut) = self.inbound.as_mut() { - match fut.poll_unpin(cx) { - Poll::Pending => {} - Poll::Ready(Err(e)) => { - log::debug!("Inbound ping error: {:?}", e); - self.inbound = None; - } - Poll::Ready(Ok(stream)) => { - // A ping from a remote peer has been answered, wait for the next. - self.inbound = Some(protocol::recv_ping(stream).boxed()); - return Poll::Ready(ConnectionHandlerEvent::Custom(Ok(Success::Pong))); - } - } - } - - loop { - // Check for outbound ping failures. - if let Some(error) = self.pending_errors.pop_back() { - log::debug!("Ping failure: {:?}", error); - - self.failures += 1; - - // Note: For backward-compatibility, with configured - // `max_failures == 1`, the first failure is always "free" - // and silent. This allows peers who still use a new substream - // for each ping to have successful ping exchanges with peers - // that use a single substream, since every successful ping - // resets `failures` to `0`, while at the same time emitting - // events only for `max_failures - 1` failures, as before. - if self.failures > 1 || self.config.max_failures.get() > 1 { - if self.failures >= self.config.max_failures.get() { - log::debug!("Too many failures ({}). Closing connection.", self.failures); - return Poll::Ready(ConnectionHandlerEvent::Close(error)); - } - - return Poll::Ready(ConnectionHandlerEvent::Custom(Err(error))); - } - } - - // Continue outbound pings. - match self.outbound.take() { - Some(OutboundState::Ping(mut ping)) => match ping.poll_unpin(cx) { - Poll::Pending => { - if self.timer.poll_unpin(cx).is_ready() { - self.pending_errors.push_front(Failure::Timeout); - } else { - self.outbound = Some(OutboundState::Ping(ping)); - break; - } - } - Poll::Ready(Ok((stream, rtt))) => { - self.failures = 0; - self.timer.reset(self.config.interval); - self.outbound = Some(OutboundState::Idle(stream)); - return Poll::Ready(ConnectionHandlerEvent::Custom(Ok(Success::Ping { - rtt, - }))); - } - Poll::Ready(Err(e)) => { - self.pending_errors - .push_front(Failure::Other { error: Box::new(e) }); - } - }, - Some(OutboundState::Idle(stream)) => match self.timer.poll_unpin(cx) { - Poll::Pending => { - self.outbound = Some(OutboundState::Idle(stream)); - break; - } - Poll::Ready(()) => { - self.timer.reset(self.config.timeout); - self.outbound = - Some(OutboundState::Ping(protocol::send_ping(stream).boxed())); - } - }, - Some(OutboundState::OpenStream) => { - self.outbound = Some(OutboundState::OpenStream); - break; - } - None => { - self.outbound = Some(OutboundState::OpenStream); - let protocol = SubstreamProtocol::new(ReadyUpgrade::new(PROTOCOL_NAME), ()) - .with_timeout(self.config.timeout); - return Poll::Ready(ConnectionHandlerEvent::OutboundSubstreamRequest { - protocol, - }); - } - } - } - - Poll::Pending - } -} - -type PingFuture = BoxFuture<'static, Result<(NegotiatedSubstream, Duration), io::Error>>; -type PongFuture = BoxFuture<'static, Result>; - -/// The current state w.r.t. outbound pings. -enum OutboundState { - /// A new substream is being negotiated for the ping protocol. - OpenStream, - /// The substream is idle, waiting to send the next ping. - Idle(NegotiatedSubstream), - /// A ping is being sent and the response awaited. - Ping(PingFuture), -} diff --git a/protocols/ping/src/lib.rs b/protocols/ping/src/lib.rs index 02babd2ea06..df8aab3fe1c 100644 --- a/protocols/ping/src/lib.rs +++ b/protocols/ping/src/lib.rs @@ -42,50 +42,40 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] -mod handler; mod protocol; -use handler::Handler; -pub use handler::{Config, Failure, Success}; -use libp2p_core::{connection::ConnectionId, PeerId}; -use libp2p_swarm::{NetworkBehaviour, NetworkBehaviourAction, PollParameters}; -use std::{ - collections::VecDeque, - task::{Context, Poll}, +use crate::protocol::{recv_ping, send_ping}; +use futures::future::Either; +use futures_timer::Delay; +use libp2p_core::connection::ConnectionId; +use libp2p_core::{ConnectedPoint, Multiaddr, PeerId}; +use libp2p_swarm::handler::from_fn; +use libp2p_swarm::{ + CloseConnection, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, }; +use std::collections::{HashMap, VecDeque}; +use std::error::Error; +use std::num::NonZeroU32; +use std::task::{Context, Poll}; +use std::time::Duration; +use std::{fmt, io}; -#[deprecated(since = "0.39.1", note = "Use libp2p::ping::Config instead.")] -pub type PingConfig = Config; +pub use crate::protocol::PROTOCOL_NAME; -#[deprecated(since = "0.39.1", note = "Use libp2p::ping::Event instead.")] -pub type PingEvent = Event; - -#[deprecated(since = "0.39.1", note = "Use libp2p::ping::Success instead.")] -pub type PingSuccess = Success; - -#[deprecated(since = "0.39.1", note = "Use libp2p::ping::Failure instead.")] -pub type PingFailure = Failure; - -#[deprecated(since = "0.39.1", note = "Use libp2p::ping::Result instead.")] -pub type PingResult = Result; - -#[deprecated(since = "0.39.1", note = "Use libp2p::ping::Behaviour instead.")] -pub type Ping = Behaviour; - -pub use self::protocol::PROTOCOL_NAME; - -/// The result of an inbound or outbound ping. pub type Result = std::result::Result; +type Handler = from_fn::FromFnProto; + /// A [`NetworkBehaviour`] that responds to inbound pings and /// periodically sends outbound pings on every established connection. /// /// See the crate root documentation for more information. +#[derive(Default)] pub struct Behaviour { /// Configuration for outbound pings. config: Config, - /// Queue of events to yield to the swarm. - events: VecDeque, + actions: VecDeque>, + failures: HashMap<(PeerId, ConnectionId), (u32, VecDeque)>, } /// Event generated by the `Ping` network behaviour. @@ -102,14 +92,138 @@ impl Behaviour { pub fn new(config: Config) -> Self { Self { config, - events: VecDeque::new(), + actions: Default::default(), + failures: Default::default(), + } + } + + fn reset_num_failures(&mut self, peer: PeerId, connection_id: ConnectionId) { + self.failures.entry((peer, connection_id)).or_default().0 = 0; + } + + fn record_failure(&mut self, peer: PeerId, connection_id: ConnectionId, e: Failure) { + self.failures + .entry((peer, connection_id)) + .or_default() + .1 + .push_back(e); + } +} + +/// The configuration for outbound pings. +#[derive(Debug, Clone)] +pub struct Config { + /// The timeout of an outbound ping. + pub(crate) timeout: Duration, + /// The duration between the last successful outbound or inbound ping + /// and the next outbound ping. + pub(crate) interval: Duration, + /// The maximum number of failed outbound pings before the associated + /// connection is deemed unhealthy, indicating to the `Swarm` that it + /// should be closed. + pub(crate) max_failures: NonZeroU32, +} + +impl Config { + /// Creates a new [`Config`] with the following default settings: + /// + /// * [`Config::with_interval`] 15s + /// * [`Config::with_timeout`] 20s + /// * [`Config::with_max_failures`] 1 + /// * [`Config::with_keep_alive`] false + /// + /// These settings have the following effect: + /// + /// * A ping is sent every 15 seconds on a healthy connection. + /// * Every ping sent must yield a response within 20 seconds in order to + /// be successful. + /// * A single ping failure is sufficient for the connection to be subject + /// to being closed. + /// * The connection may be closed at any time as far as the ping protocol + /// is concerned, i.e. the ping protocol itself does not keep the + /// connection alive. + pub fn new() -> Self { + Self { + timeout: Duration::from_secs(20), + interval: Duration::from_secs(15), + max_failures: NonZeroU32::new(1).expect("1 != 0"), } } + + /// Sets the ping timeout. + pub fn with_timeout(mut self, d: Duration) -> Self { + self.timeout = d; + self + } + + /// Sets the ping interval. + pub fn with_interval(mut self, d: Duration) -> Self { + self.interval = d; + self + } + + /// Sets the maximum number of consecutive ping failures upon which the remote + /// peer is considered unreachable and the connection closed. + pub fn with_max_failures(mut self, n: NonZeroU32) -> Self { + self.max_failures = n; + self + } } -impl Default for Behaviour { +impl Default for Config { fn default() -> Self { - Self::new(Config::new()) + Self::new() + } +} + +/// The successful result of processing an inbound or outbound ping. +#[derive(Debug)] +pub enum Success { + /// Received a ping and sent back a pong. + Pong, + /// Sent a ping and received back a pong. + /// + /// Includes the round-trip time. + Ping { rtt: Duration }, +} + +/// An outbound ping failure. +#[derive(Debug)] +pub enum Failure { + /// The ping timed out, i.e. no response was received within the + /// configured ping timeout. + Timeout, + /// The peer does not support the ping protocol. + Unsupported, + /// The ping failed for reasons other than a timeout. + Other { + error: Box, + }, +} + +impl From for Failure { + fn from(e: io::Error) -> Self { + Failure::Other { error: Box::new(e) } + } +} + +impl fmt::Display for Failure { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Failure::Timeout => f.write_str("Ping timeout"), + Failure::Other { error } => write!(f, "Ping error: {}", error), + Failure::Unsupported => write!(f, "Ping protocol not supported"), + } + } +} + +impl Error for Failure { + fn source(&self) -> Option<&(dyn Error + 'static)> { + match self { + Failure::Timeout => None, + Failure::Other { error } => Some(&**error), + Failure::Unsupported => None, + } } } @@ -118,11 +232,104 @@ impl NetworkBehaviour for Behaviour { type OutEvent = Event; fn new_handler(&mut self) -> Self::ConnectionHandler { - Handler::new(self.config.clone()) + from_fn::from_fn(std::str::from_utf8(PROTOCOL_NAME).unwrap()) + .without_state() + .with_streaming_inbound_handler(1, |stream, _, _, _| { + futures::stream::try_unfold(stream, |stream| async move { + let stream = recv_ping(stream).await?; + + Ok(Some((Success::Pong, stream))) + }) + }) + .with_streaming_outbound_handler(1, { + let interval = self.config.interval; + let timeout = self.config.timeout; + + move |stream, _, _, _, _| { + futures::stream::try_unfold(stream, move |stream| async move { + Delay::new(interval).await; + + let ping = send_ping(stream); + futures::pin_mut!(ping); + + match futures::future::select(Delay::new(timeout), ping).await { + Either::Left(((), _unfinished_ping)) => Err(Failure::Timeout), + Either::Right((Ok((stream, rtt)), _)) => { + Ok(Some((Success::Ping { rtt }, stream))) + } + Either::Right((Err(e), _)) => { + Err(Failure::Other { error: Box::new(e) }) + } + } + }) + } + }) + } + + fn inject_connection_established( + &mut self, + peer: &PeerId, + connection: &ConnectionId, + _: &ConnectedPoint, + _: Option<&Vec>, + _: usize, + ) { + self.actions.push_back(start_ping_action(peer, connection)); } - fn inject_event(&mut self, peer: PeerId, _: ConnectionId, result: Result) { - self.events.push_front(Event { peer, result }) + fn inject_event( + &mut self, + peer: PeerId, + connection: ConnectionId, + event: from_fn::OutEvent, + ) { + match event { + from_fn::OutEvent::InboundEmitted(Ok(success)) => { + self.actions + .push_back(NetworkBehaviourAction::GenerateEvent(Event { + peer, + result: Ok(success), + })) + } + from_fn::OutEvent::OutboundEmitted(Ok(success)) => { + self.actions + .push_back(NetworkBehaviourAction::GenerateEvent(Event { + peer, + result: Ok(success), + })); + self.reset_num_failures(peer, connection); + } + from_fn::OutEvent::InboundEmitted(Err(e)) => { + log::debug!("Inbound ping error: {:?}", e); + } + from_fn::OutEvent::OutboundEmitted(Err(e)) => { + self.record_failure(peer, connection, e); + } + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::Timeout(())) => { + self.record_failure(peer, connection, Failure::Timeout); + } + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::Unsupported { + open_info: (), + .. + }) => { + self.record_failure(peer, connection, Failure::Unsupported); + } + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::NegotiationFailed((), error)) => { + self.record_failure( + peer, + connection, + Failure::Other { + error: Box::new(error), + }, + ); + } + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::LimitExceeded { + open_info: (), + .. + }) => { + unreachable!("We only ever open a new stream if the old one is dead.") + } + } } fn poll( @@ -130,18 +337,54 @@ impl NetworkBehaviour for Behaviour { _: &mut Context<'_>, _: &mut impl PollParameters, ) -> Poll> { - if let Some(e) = self.events.pop_back() { - let Event { result, peer } = &e; + if let Some(action) = self.actions.pop_front() { + return Poll::Ready(action); + } - match result { - Ok(Success::Ping { .. }) => log::debug!("Ping sent to {:?}", peer), - Ok(Success::Pong) => log::debug!("Ping received from {:?}", peer), - _ => {} - } + for ((peer, connection), (failures, pending_errors)) in self.failures.iter_mut() { + // Check for outbound ping failures. + if let Some(error) = pending_errors.pop_back() { + log::debug!("Ping failure: {:?}", error); - Poll::Ready(NetworkBehaviourAction::GenerateEvent(e)) - } else { - Poll::Pending + *failures += 1; + + // Note: For backward-compatibility, with configured + // `max_failures == 1`, the first failure is always "free" + // and silent. This allows peers who still use a new substream + // for each ping to have successful ping exchanges with peers + // that use a single substream, since every successful ping + // resets `failures` to `0`, while at the same time emitting + // events only for `max_failures - 1` failures, as before. + if *failures > 1 || self.config.max_failures.get() > 1 { + if *failures >= self.config.max_failures.get() { + log::debug!("Too many failures ({}). Closing connection.", failures); + return Poll::Ready(NetworkBehaviourAction::CloseConnection { + peer_id: *peer, + connection: CloseConnection::One(*connection), + }); + } + } + + self.actions.push_back(start_ping_action(peer, connection)); + + return Poll::Ready(NetworkBehaviourAction::GenerateEvent(Event { + peer: *peer, + result: Err(error), + })); + } } + + Poll::Pending + } +} + +fn start_ping_action( + peer: &PeerId, + connection: &ConnectionId, +) -> NetworkBehaviourAction { + NetworkBehaviourAction::NotifyHandler { + peer_id: *peer, + handler: NotifyHandler::One(*connection), + event: from_fn::InEvent::NewOutbound(()), } } From 628e9ab20240fd0ae98c5b8b8e64f35b67640e2f Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 17 Nov 2022 00:33:49 +1100 Subject: [PATCH 32/34] Fully migrate rendezvous --- protocols/rendezvous/src/client.rs | 202 +++--- protocols/rendezvous/src/codec.rs | 41 +- protocols/rendezvous/src/server.rs | 830 ++++++++++++----------- protocols/rendezvous/tests/rendezvous.rs | 4 +- swarm/src/handler/from_fn.rs | 9 +- 5 files changed, 533 insertions(+), 553 deletions(-) diff --git a/protocols/rendezvous/src/client.rs b/protocols/rendezvous/src/client.rs index b3d7d2cc976..32a03e18aff 100644 --- a/protocols/rendezvous/src/client.rs +++ b/protocols/rendezvous/src/client.rs @@ -19,8 +19,8 @@ // DEALINGS IN THE SOFTWARE. use crate::codec::{ - Cookie, Error, ErrorCode, Message, Namespace, NewRegistration, Registration, RendezvousCodec, - Ttl, + Cookie, Error, ErrorCode, Message, Namespace, NewRegistrationRequest, Registration, + RendezvousCodec, Ttl, }; use crate::PROTOCOL_IDENT; use asynchronous_codec::Framed; @@ -29,22 +29,19 @@ use futures::future::FutureExt; use futures::stream::FuturesUnordered; use futures::stream::StreamExt; use futures::SinkExt; -use instant::Duration; use libp2p_core::connection::ConnectionId; use libp2p_core::identity::error::SigningError; use libp2p_core::identity::Keypair; use libp2p_core::{ConnectedPoint, Multiaddr, PeerId, PeerRecord}; use libp2p_swarm::handler::from_fn; use libp2p_swarm::{ - CloseConnection, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, - PollParameters, + NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, NotifyHandler, PollParameters, }; use std::collections::{HashMap, VecDeque}; -use std::future::Future; -use std::io; use std::iter::FromIterator; use std::sync::Arc; use std::task::{Context, Poll}; +use std::time::Duration; use void::Void; pub struct Behaviour { @@ -188,7 +185,7 @@ pub enum OutboundEvent { #[allow(clippy::enum_variant_names)] #[derive(Debug, Clone)] pub enum OpenInfo { - RegisterRequest(NewRegistration), + RegisterRequest(NewRegistrationRequest), UnregisterRequest(Namespace), DiscoverRequest { namespace: Option, @@ -219,43 +216,94 @@ impl NetworkBehaviour for Behaviour { fn inject_event( &mut self, - __id: PeerId, + peer_id: PeerId, _: ConnectionId, event: from_fn::OutEvent, OpenInfo>, ) { - match event { + let new_events = match event { from_fn::OutEvent::InboundEmitted(never) => void::unreachable(never), - from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::Discovered { .. })) => {} - from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::Registered { .. })) => {} - from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::DiscoverFailed { .. })) => {} - from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::RegisterFailed(..))) => {} - from_fn::OutEvent::OutboundEmitted(Err(e)) => {} - from_fn::OutEvent::FailedToOpen(from_fn::OpenError::Timeout(info)) => {} - from_fn::OutEvent::FailedToOpen(from_fn::OpenError::NegotiationFailed(..)) => {} - from_fn::OutEvent::FailedToOpen(from_fn::OpenError::LimitExceeded { .. }) => {} - from_fn::OutEvent::FailedToOpen(from_fn::OpenError::Unsupported { .. }) => {} - } - // - // let new_events = match event { - // handler::OutboundOutEvent::InboundEvent { message, .. } => void::unreachable(message), - // handler::OutboundOutEvent::OutboundEvent { message, .. } => handle_outbound_event( - // message, - // peer_id, - // &mut self.discovered_peers, - // &mut self.expiring_registrations, - // ), - // handler::OutboundOutEvent::InboundError { error, .. } => void::unreachable(error), - // handler::OutboundOutEvent::OutboundError { error, .. } => { - // log::warn!("Connection with peer {} failed: {}", peer_id, error); - // - // vec![NetworkBehaviourAction::CloseConnection { - // peer_id, - // connection: CloseConnection::One(connection_id), - // }] - // } - // }; - // - // self.events.extend(new_events); + from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::Discovered { + registrations, + cookie, + })) => { + self.discovered_peers + .extend(registrations.iter().map(|registration| { + let peer_id = registration.record.peer_id(); + let namespace = registration.namespace.clone(); + let addresses = registration.record.addresses().to_vec(); + + ((peer_id, namespace), addresses) + })); + self.expiring_registrations + .extend(registrations.iter().cloned().map(|registration| { + async move { + // if the timer errors we consider it expired + futures_timer::Delay::new(Duration::from_secs(registration.ttl as u64)) + .await; + + (registration.record.peer_id(), registration.namespace) + } + .boxed() + })); + + vec![NetworkBehaviourAction::GenerateEvent(Event::Discovered { + rendezvous_node: peer_id, + registrations, + cookie, + })] + } + from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::Registered { + namespace, + ttl, + })) => { + vec![NetworkBehaviourAction::GenerateEvent(Event::Registered { + rendezvous_node: peer_id, + ttl, + namespace, + })] + } + from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::DiscoverFailed { + namespace, + error, + })) => { + vec![NetworkBehaviourAction::GenerateEvent( + Event::DiscoverFailed { + rendezvous_node: peer_id, + namespace, + error, + }, + )] + } + from_fn::OutEvent::OutboundEmitted(Ok(OutboundEvent::RegisterFailed( + namespace, + error, + ))) => { + vec![NetworkBehaviourAction::GenerateEvent( + Event::RegisterFailed(RegisterError::Remote { + rendezvous_node: peer_id, + namespace, + error, + }), + )] + } + from_fn::OutEvent::OutboundEmitted(Err(_)) => { + todo!() + } + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::Timeout(_)) => { + todo!() + } + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::NegotiationFailed(..)) => { + todo!() + } + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::LimitExceeded { .. }) => { + todo!() + } + from_fn::OutEvent::FailedToOpen(from_fn::OpenError::Unsupported { .. }) => { + todo!() + } + }; + + self.events.extend(new_events); } fn poll( @@ -285,7 +333,7 @@ impl NetworkBehaviour for Behaviour { Ok(peer_record) => NetworkBehaviourAction::NotifyHandler { peer_id: rendezvous_node, event: from_fn::InEvent::NewOutbound(OpenInfo::RegisterRequest( - NewRegistration { + NewRegistrationRequest { namespace, record: peer_record, ttl, @@ -314,74 +362,6 @@ impl NetworkBehaviour for Behaviour { } } -// fn handle_outbound_event( -// event: outbound::OutEvent, -// peer_id: PeerId, -// discovered_peers: &mut HashMap<(PeerId, Namespace), Vec>, -// expiring_registrations: &mut FuturesUnordered>, -// ) -> Vec< -// NetworkBehaviourAction< -// Event, -// SubstreamConnectionHandler, -// >, -// > { -// match event { -// outbound::OutEvent::Registered { namespace, ttl } => { -// vec![NetworkBehaviourAction::GenerateEvent(Event::Registered { -// rendezvous_node: peer_id, -// ttl, -// namespace, -// })] -// } -// outbound::OutEvent::RegisterFailed(namespace, error) => { -// vec![NetworkBehaviourAction::GenerateEvent( -// Event::RegisterFailed(RegisterError::Remote { -// rendezvous_node: peer_id, -// namespace, -// error, -// }), -// )] -// } -// outbound::OutEvent::Discovered { -// registrations, -// cookie, -// } => { -// discovered_peers.extend(registrations.iter().map(|registration| { -// let peer_id = registration.record.peer_id(); -// let namespace = registration.namespace.clone(); -// -// let addresses = registration.record.addresses().to_vec(); -// -// ((peer_id, namespace), addresses) -// })); -// expiring_registrations.extend(registrations.iter().cloned().map(|registration| { -// async move { -// // if the timer errors we consider it expired -// futures_timer::Delay::new(Duration::from_secs(registration.ttl as u64)).await; -// -// (registration.record.peer_id(), registration.namespace) -// } -// .boxed() -// })); -// -// vec![NetworkBehaviourAction::GenerateEvent(Event::Discovered { -// rendezvous_node: peer_id, -// registrations, -// cookie, -// })] -// } -// outbound::OutEvent::DiscoverFailed { namespace, error } => { -// vec![NetworkBehaviourAction::GenerateEvent( -// Event::DiscoverFailed { -// rendezvous_node: peer_id, -// namespace, -// error, -// }, -// )] -// } -// } -// } - async fn outbound_stream_handler( substream: NegotiatedSubstream, _: PeerId, @@ -444,5 +424,7 @@ async fn outbound_stream_handler( } }; + substream.close().await?; + Ok(out_event) } diff --git a/protocols/rendezvous/src/codec.rs b/protocols/rendezvous/src/codec.rs index 88af5a1fa98..16f62d29a29 100644 --- a/protocols/rendezvous/src/codec.rs +++ b/protocols/rendezvous/src/codec.rs @@ -18,7 +18,6 @@ // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. -use crate::DEFAULT_TTL; use asynchronous_codec::{BytesMut, Decoder, Encoder}; use libp2p_core::{peer_record, signed_envelope, PeerRecord, SignedEnvelope}; use rand::RngCore; @@ -30,7 +29,7 @@ pub type Ttl = u64; #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone)] pub enum Message { - Register(NewRegistration), + Register(NewRegistrationRequest), RegisterResponse(Result), Unregister(Namespace), Discover { @@ -161,26 +160,12 @@ impl Cookie { pub struct InvalidCookie; #[derive(Debug, Clone)] -pub struct NewRegistration { +pub struct NewRegistrationRequest { pub namespace: Namespace, pub record: PeerRecord, pub ttl: Option, } -impl NewRegistration { - pub fn new(namespace: Namespace, record: PeerRecord, ttl: Option) -> Self { - Self { - namespace, - record, - ttl, - } - } - - pub fn effective_ttl(&self) -> Ttl { - self.ttl.unwrap_or(DEFAULT_TTL) - } -} - #[derive(Debug, Clone, PartialEq, Eq)] pub struct Registration { pub namespace: Namespace, @@ -251,7 +236,7 @@ impl From for wire::Message { use wire::message::*; match message { - Message::Register(NewRegistration { + Message::Register(NewRegistrationRequest { namespace, record, ttl, @@ -375,16 +360,20 @@ impl TryFrom for Message { signed_peer_record: Some(signed_peer_record), }), .. - } => Message::Register(NewRegistration { - namespace: ns + } => { + let namespace = ns .map(Namespace::new) .transpose()? - .ok_or(ConversionError::MissingNamespace)?, - ttl, - record: PeerRecord::from_signed_envelope(SignedEnvelope::from_protobuf_encoding( - &signed_peer_record, - )?)?, - }), + .ok_or(ConversionError::MissingNamespace)?; + let record = PeerRecord::from_signed_envelope( + SignedEnvelope::from_protobuf_encoding(&signed_peer_record)?, + )?; + Message::Register(NewRegistrationRequest { + namespace, + record, + ttl, + }) + } wire::Message { r#type: Some(1), register_response: diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index 3b425be0b14..875f59a60de 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -19,10 +19,10 @@ // DEALINGS IN THE SOFTWARE. use crate::codec::{ - Cookie, Error, ErrorCode, Message, Namespace, NewRegistration, Registration, RendezvousCodec, - Ttl, + Cookie, Error, ErrorCode, Message, Namespace, NewRegistrationRequest, Registration, + RendezvousCodec, Ttl, }; -use crate::{MAX_TTL, MIN_TTL, PROTOCOL_IDENT}; +use crate::{DEFAULT_TTL, MAX_TTL, MIN_TTL, PROTOCOL_IDENT}; use asynchronous_codec::Framed; use bimap::BiMap; use futures::future::BoxFuture; @@ -30,24 +30,28 @@ use futures::stream::FuturesUnordered; use futures::{ready, SinkExt}; use futures::{FutureExt, StreamExt}; use libp2p_core::connection::ConnectionId; -use libp2p_core::{ConnectedPoint, Multiaddr, PeerId}; +use libp2p_core::{ConnectedPoint, Multiaddr, PeerId, PeerRecord}; use libp2p_swarm::handler::from_fn; use libp2p_swarm::{ - from_fn, CloseConnection, IntoConnectionHandler, NegotiatedSubstream, NetworkBehaviour, - NetworkBehaviourAction, PollParameters, + from_fn, IntoConnectionHandler, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, + PollParameters, }; -use std::collections::{HashMap, HashSet}; -use std::future::Future; +use std::collections::{HashMap, HashSet, VecDeque}; use std::io; -use std::iter::FromIterator; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::Duration; use void::Void; pub struct Behaviour { - registrations: Registrations, - registration_data: from_fn::Shared, + next_expiry: FuturesUnordered>, + registrations: from_fn::Shared, + events: VecDeque< + NetworkBehaviourAction< + Event, + from_fn::FromFnProto, Error>, Void, Void, Registrations>, + >, + >, } pub struct Config { @@ -80,8 +84,39 @@ impl Behaviour { /// Create a new instance of the rendezvous [`NetworkBehaviour`]. pub fn new(config: Config) -> Self { Self { - registrations: Registrations::with_config(config), - registration_data: from_fn::Shared::new(RegistrationData::default()), + next_expiry: FuturesUnordered::new(), + registrations: from_fn::Shared::new(Registrations::with_config(config)), + events: Default::default(), + } + } + + fn poll_expiry_timers(&mut self, cx: &mut Context<'_>) -> Poll> { + loop { + let expired_registration = match ready!(self.next_expiry.poll_next_unpin(cx)) { + Some(r) => r, + None => return Poll::Ready(None), // TODO: Register waker and return Pending? + }; + + // clean up our cookies + self.registrations.cookies.retain(|_, registrations| { + registrations.remove(&expired_registration); + + // retain all cookies where there are still registrations left + !registrations.is_empty() + }); + + self.registrations + .registrations_for_peer + .remove_by_right(&expired_registration); + + match self + .registrations + .registrations + .remove(&expired_registration) + { + Some(registration) => return Poll::Ready(Some(ExpiredRegistration(registration))), + None => continue, + } } } } @@ -115,12 +150,12 @@ pub enum Event { impl NetworkBehaviour for Behaviour { type ConnectionHandler = - from_fn::FromFnProto, Error>, Void, Void, RegistrationData>; + from_fn::FromFnProto, Error>, Void, Void, Registrations>; type OutEvent = Event; fn new_handler(&mut self) -> Self::ConnectionHandler { from_fn(PROTOCOL_IDENT) - .with_state(&self.registration_data) + .with_state(&self.registrations) .with_inbound_handler(10, inbound_stream_handler) .without_outbound_handler() } @@ -133,7 +168,7 @@ impl NetworkBehaviour for Behaviour { _: Option<&Vec>, _: usize, ) { - self.registration_data + self.registrations .register_connection(*peer_id, *connection_id) } @@ -145,27 +180,73 @@ impl NetworkBehaviour for Behaviour { _: ::Handler, _remaining_established: usize, ) { - self.registration_data + self.registrations .unregister_connection(*peer_id, *connection_id) } fn inject_event( &mut self, - peer_id: PeerId, + peer: PeerId, _: ConnectionId, event: from_fn::OutEvent, Error>, Void, Void>, ) { - match event { + let new_events = match event { from_fn::OutEvent::InboundEmitted(Ok(Some(InboundOutEvent::NewRegistration( new_registration, - )))) => self - .registrations - .add(new_registration, &mut self.registration_data), - from_fn::OutEvent::InboundEmitted(Ok(Some(InboundOutEvent::Unregister(namespace)))) => { + )))) => { + let (registration, expiry) = self.registrations.add(new_registration); + self.next_expiry.push(expiry); + + vec![NetworkBehaviourAction::GenerateEvent( + Event::PeerRegistered { peer, registration }, + )] + } + from_fn::OutEvent::InboundEmitted(Ok(Some(InboundOutEvent::RegistrationFailed { + error, + namespace, + }))) => { + vec![NetworkBehaviourAction::GenerateEvent( + Event::PeerNotRegistered { + peer, + error, + namespace, + }, + )] + } + from_fn::OutEvent::InboundEmitted(Ok(Some(InboundOutEvent::Discovered { + cookie, + registrations, + previous_registrations, + }))) => { self.registrations - .remove(namespace, peer_id, &mut self.registration_data) + .cookies + .insert(cookie, previous_registrations); + + vec![NetworkBehaviourAction::GenerateEvent( + Event::DiscoverServed { + enquirer: peer, + registrations, + }, + )] } - from_fn::OutEvent::OutboundEmitted(_) => {} + from_fn::OutEvent::InboundEmitted(Ok(Some(InboundOutEvent::DiscoverFailed { + error, + }))) => { + vec![NetworkBehaviourAction::GenerateEvent( + Event::DiscoverNotServed { + enquirer: peer, + error, + }, + )] + } + from_fn::OutEvent::InboundEmitted(Ok(Some(InboundOutEvent::Unregister(namespace)))) => { + self.registrations.remove(namespace.clone(), peer); + + vec![NetworkBehaviourAction::GenerateEvent( + Event::PeerUnregistered { peer, namespace }, + )] + } + from_fn::OutEvent::OutboundEmitted(never) => void::unreachable(never), from_fn::OutEvent::FailedToOpen(never) => match never { from_fn::OpenError::Timeout(never) => void::unreachable(never), from_fn::OpenError::LimitExceeded { @@ -177,10 +258,16 @@ impl NetworkBehaviour for Behaviour { } => void::unreachable(never), }, from_fn::OutEvent::InboundEmitted(Err(error)) => { - log::debug!("Inbound stream from {peer_id} failed: {error}"); + log::debug!("Inbound stream from {peer} failed: {error}"); + + vec![] } - from_fn::OutEvent::InboundEmitted(Ok(None)) => {} - } + from_fn::OutEvent::InboundEmitted(Ok(None)) => { + vec![] + } + }; + + self.events.extend(new_events) } fn poll( @@ -188,15 +275,17 @@ impl NetworkBehaviour for Behaviour { cx: &mut Context<'_>, _: &mut impl PollParameters, ) -> Poll> { - if let Poll::Ready(ExpiredRegistration(registration)) = - self.registrations.poll(&mut self.registration_data, cx) - { + if let Some(event) = self.events.pop_front() { + return Poll::Ready(event); + } + + if let Poll::Ready(Some(ExpiredRegistration(registration))) = self.poll_expiry_timers(cx) { return Poll::Ready(NetworkBehaviourAction::GenerateEvent( Event::RegistrationExpired(registration), )); } - if let Poll::Ready(action) = self.registration_data.poll(cx) { + if let Poll::Ready(action) = self.registrations.poll(cx) { return Poll::Ready(action); } @@ -206,9 +295,9 @@ impl NetworkBehaviour for Behaviour { async fn inbound_stream_handler( substream: NegotiatedSubstream, - _: PeerId, + peer: PeerId, _: ConnectedPoint, - registrations: Arc, + registrations: Arc, ) -> Result, Error> { let mut substream = Framed::new(substream, RendezvousCodec::default()); @@ -219,45 +308,57 @@ async fn inbound_stream_handler( let out_event = match message { Message::Register(new_registration) => { - // TODO: Validate registration + let namespace = new_registration.namespace.clone(); - substream - .send(Message::RegisterResponse(Ok( - new_registration.effective_ttl() - ))) - .await?; - substream.close().await?; + match registrations.new_registration(peer, new_registration) { + Ok(new_registration) => { + substream + .send(Message::RegisterResponse(Ok( + new_registration.effective_ttl() + ))) + .await?; - Some(InboundOutEvent::NewRegistration(new_registration)) - } - Message::Unregister(namespace) => { - substream.close().await?; + Some(InboundOutEvent::NewRegistration(new_registration)) + } + Err(error) => { + substream + .send(Message::RegisterResponse(Err(error))) + .await?; - Some(InboundOutEvent::Unregister(namespace)) + Some(InboundOutEvent::RegistrationFailed { namespace, error }) + } + } } + Message::Unregister(namespace) => Some(InboundOutEvent::Unregister(namespace)), Message::Discover { namespace, cookie, limit, } => match registrations.get(namespace, cookie, limit) { - Ok((registrations, cookie)) => { + Ok((registrations, cookie, sent_registrations)) => { + let registrations = registrations.cloned().collect::>(); + substream .send(Message::DiscoverResponse(Ok(( - registrations.cloned().collect(), - cookie, + registrations.clone(), + cookie.clone(), )))) .await?; - substream.close().await?; - None + Some(InboundOutEvent::Discovered { + cookie, + registrations, + previous_registrations: sent_registrations, + }) } - Err(e) => { + Err(_) => { substream .send(Message::DiscoverResponse(Err(ErrorCode::InvalidCookie))) .await?; - substream.close().await?; - None + Some(InboundOutEvent::DiscoverFailed { + error: ErrorCode::InvalidCookie, + }) } }, Message::DiscoverResponse(_) | Message::RegisterResponse(_) => { @@ -265,17 +366,32 @@ async fn inbound_stream_handler( } }; + substream.close().await?; + Ok(out_event) } #[derive(Debug)] +#[allow(clippy::large_enum_variant)] pub enum InboundOutEvent { NewRegistration(NewRegistration), + RegistrationFailed { + namespace: Namespace, + error: ErrorCode, + }, + Discovered { + cookie: Cookie, + registrations: Vec, + previous_registrations: HashSet, + }, + DiscoverFailed { + error: ErrorCode, + }, Unregister(Namespace), } #[derive(Debug, Eq, PartialEq, Hash, Copy, Clone)] -struct RegistrationId(u64); +pub struct RegistrationId(u64); impl RegistrationId { fn new() -> Self { @@ -286,26 +402,129 @@ impl RegistrationId { #[derive(Debug, PartialEq)] struct ExpiredRegistration(Registration); +#[derive(Debug, Clone)] pub struct Registrations { min_ttl: Ttl, max_ttl: Ttl, - next_expiry: FuturesUnordered>, -} -#[derive(Debug, Clone, Default)] -pub struct RegistrationData { registrations_for_peer: BiMap<(PeerId, Namespace), RegistrationId>, registrations: HashMap, cookies: HashMap>, } -impl RegistrationData { +#[derive(Debug)] +pub struct NewRegistration { + namespace: Namespace, + record: PeerRecord, + ttl: Option, +} + +impl NewRegistration { + pub fn effective_ttl(&self) -> Ttl { + self.ttl.unwrap_or(DEFAULT_TTL) + } +} + +impl Default for Registrations { + fn default() -> Self { + Registrations::with_config(Config::default()) + } +} + +impl Registrations { + pub fn with_config(config: Config) -> Self { + Self { + min_ttl: config.min_ttl, + max_ttl: config.max_ttl, + registrations_for_peer: Default::default(), + registrations: Default::default(), + cookies: Default::default(), + } + } + + pub fn new_registration( + &self, + from: PeerId, + new_registration: NewRegistrationRequest, + ) -> Result { + let ttl = new_registration.ttl.unwrap_or(DEFAULT_TTL); + + if ttl > self.max_ttl { + return Err(ErrorCode::InvalidTtl); + } + if ttl < self.min_ttl { + return Err(ErrorCode::InvalidTtl); + } + + if new_registration.record.peer_id() != from { + return Err(ErrorCode::NotAuthorized); + } + + Ok(NewRegistration { + namespace: new_registration.namespace, + record: new_registration.record, + ttl: new_registration.ttl, + }) + } + + pub fn add( + &mut self, + new_registration: NewRegistration, + ) -> (Registration, BoxFuture<'static, RegistrationId>) { + let ttl = new_registration.effective_ttl(); + let namespace = new_registration.namespace; + let registration_id = RegistrationId::new(); + + if let Some(old_registration) = self + .registrations_for_peer + .get_by_left(&(new_registration.record.peer_id(), namespace.clone())) + { + self.registrations.remove(old_registration); + } + + self.registrations_for_peer.insert( + (new_registration.record.peer_id(), namespace.clone()), + registration_id, + ); + + let registration = Registration { + namespace, + record: new_registration.record, + ttl, + }; + self.registrations + .insert(registration_id, registration.clone()); + + let expiry = futures_timer::Delay::new(Duration::from_secs(ttl as u64)) + .map(move |_| registration_id) + .boxed(); + + (registration, expiry) + } + + pub fn remove(&mut self, namespace: Namespace, peer_id: PeerId) { + let reggo_to_remove = self + .registrations_for_peer + .remove_by_left(&(peer_id, namespace)); + + if let Some((_, reggo_to_remove)) = reggo_to_remove { + self.registrations.remove(®go_to_remove); + } + } + pub fn get( &self, discover_namespace: Option, cookie: Option, limit: Option, - ) -> Result<(impl Iterator + '_, Cookie), CookieNamespaceMismatch> { + ) -> Result< + ( + impl Iterator + '_, + Cookie, + HashSet, + ), + CookieNamespaceMismatch, + > { let cookie_namespace = cookie.as_ref().and_then(|cookie| cookie.namespace()); match (discover_namespace.as_ref(), cookie_namespace) { @@ -351,15 +570,13 @@ impl RegistrationData { let new_cookie = discover_namespace .map(Cookie::for_namespace) .unwrap_or_else(Cookie::for_all_namespaces); - // self.cookies - // .insert(new_cookie.clone(), reggos_of_last_discover); let reggos = &self.registrations; let registrations = ids .into_iter() .map(move |id| reggos.get(&id).expect("bad internal datastructure")); - Ok((registrations, new_cookie)) + Ok((registrations, new_cookie, reggos_of_last_discover)) } } @@ -371,371 +588,164 @@ pub enum TtlOutOfRange { TooShort { bound: Ttl, requested: Ttl }, } -impl Default for Registrations { - fn default() -> Self { - Registrations::with_config(Config::default()) - } -} +#[derive(Debug, thiserror::Error, Eq, PartialEq)] +#[error("The provided cookie is not valid for a DISCOVER request for the given namespace")] +pub struct CookieNamespaceMismatch; -impl Registrations { - pub fn with_config(config: Config) -> Self { - Self { - min_ttl: config.min_ttl, - max_ttl: config.max_ttl, - next_expiry: FuturesUnordered::from_iter(vec![futures::future::pending().boxed()]), - } +#[cfg(test)] +mod tests { + use libp2p_core::{identity, PeerRecord}; + + use super::*; + + #[test] + fn given_cookie_from_discover_when_discover_again_then_only_get_diff() { + let mut registrations = Registrations::default(); + registrations.add(new_dummy_registration("foo")); + registrations.add(new_dummy_registration("foo")); + + let (initial_discover, cookie, existing_registrations) = + registrations.get(None, None, None).unwrap(); + assert_eq!(initial_discover.count(), 2); + registrations + .cookies + .insert(cookie.clone(), existing_registrations); + + let (subsequent_discover, _, _) = registrations.get(None, Some(cookie), None).unwrap(); + assert_eq!(subsequent_discover.count(), 0); } - pub fn add(&mut self, new_registration: NewRegistration, data: &mut RegistrationData) { - // TOOD: Assume validation has by done by newtype. - let ttl = new_registration.effective_ttl(); - // if ttl > self.max_ttl { - // return Err(TtlOutOfRange::TooLong { - // bound: self.max_ttl, - // requested: ttl, - // }); - // } - // if ttl < self.min_ttl { - // return Err(TtlOutOfRange::TooShort { - // bound: self.min_ttl, - // requested: ttl, - // }); - // } + #[test] + fn given_registrations_when_discover_all_then_all_are_returned() { + let mut registrations = Registrations::default(); + registrations.add(new_dummy_registration("foo")); + registrations.add(new_dummy_registration("foo")); - let namespace = new_registration.namespace; - let registration_id = RegistrationId::new(); + let (discover, _, _) = registrations.get(None, None, None).unwrap(); - if let Some(old_registration) = data - .registrations_for_peer - .get_by_left(&(new_registration.record.peer_id(), namespace.clone())) - { - data.registrations.remove(old_registration); - } + assert_eq!(discover.count(), 2); + } - data.registrations_for_peer.insert( - (new_registration.record.peer_id(), namespace.clone()), - registration_id, + #[test] + fn given_registrations_when_discover_only_for_specific_namespace_then_only_those_are_returned() + { + let mut registrations = Registrations::default(); + registrations.add(new_dummy_registration("foo")); + registrations.add(new_dummy_registration("bar")); + + let (discover, _, _) = registrations + .get(Some(Namespace::from_static("foo")), None, None) + .unwrap(); + + assert_eq!( + discover.map(|r| &r.namespace).collect::>(), + vec!["foo"] ); + } - let registration = Registration { - namespace, - record: new_registration.record, - ttl, - }; - data.registrations.insert(registration_id, registration); + #[test] + fn given_reregistration_old_registration_is_discarded() { + let alice = identity::Keypair::generate_ed25519(); + let mut registrations = Registrations::default(); + registrations.add(new_registration("foo", alice.clone(), None)); + registrations.add(new_registration("foo", alice, None)); - let next_expiry = futures_timer::Delay::new(Duration::from_secs(ttl as u64)) - .map(move |_| registration_id) - .boxed(); + let (discover, _, _) = registrations + .get(Some(Namespace::from_static("foo")), None, None) + .unwrap(); - self.next_expiry.push(next_expiry); + assert_eq!( + discover.map(|r| &r.namespace).collect::>(), + vec!["foo"] + ); } - pub fn remove(&self, namespace: Namespace, peer_id: PeerId, data: &mut RegistrationData) { - let reggo_to_remove = data - .registrations_for_peer - .remove_by_left(&(peer_id, namespace)); - - if let Some((_, reggo_to_remove)) = reggo_to_remove { - data.registrations.remove(®go_to_remove); - } + #[test] + fn given_cookie_from_2nd_discover_does_not_return_nodes_from_first_discover() { + let mut registrations = Registrations::default(); + registrations.add(new_dummy_registration("foo")); + registrations.add(new_dummy_registration("foo")); + + let (initial_discover, cookie1, existing_registrations) = + registrations.get(None, None, None).unwrap(); + assert_eq!(initial_discover.count(), 2); + registrations + .cookies + .insert(cookie1.clone(), existing_registrations); + + let (subsequent_discover, cookie2, existing_registrations) = + registrations.get(None, Some(cookie1), None).unwrap(); + assert_eq!(subsequent_discover.count(), 0); + registrations + .cookies + .insert(cookie2.clone(), existing_registrations); + + let (subsequent_discover, _, _) = registrations.get(None, Some(cookie2), None).unwrap(); + assert_eq!(subsequent_discover.count(), 0); } - fn poll( - &mut self, - data: &mut RegistrationData, - cx: &mut Context<'_>, - ) -> Poll { - let expired_registration = ready!(self.next_expiry.poll_next_unpin(cx)).expect( - "This stream should never finish because it is initialised with a pending future", + #[test] + fn cookie_from_different_discover_request_is_not_valid() { + let mut registrations = Registrations::default(); + registrations.add(new_dummy_registration("foo")); + registrations.add(new_dummy_registration("bar")); + + let (_, foo_discover_cookie, _) = registrations + .get(Some(Namespace::from_static("foo")), None, None) + .unwrap(); + let result = registrations.get( + Some(Namespace::from_static("bar")), + Some(foo_discover_cookie), + None, ); - // clean up our cookies - data.cookies.retain(|_, registrations| { - registrations.remove(&expired_registration); + assert!(matches!(result, Err(CookieNamespaceMismatch))) + } - // retain all cookies where there are still registrations left - !registrations.is_empty() - }); + #[test] + fn given_limit_discover_only_returns_n_results() { + let mut registrations = Registrations::default(); + registrations.add(new_dummy_registration("foo")); + registrations.add(new_dummy_registration("foo")); - data.registrations_for_peer - .remove_by_right(&expired_registration); - match data.registrations.remove(&expired_registration) { - None => self.poll(data, cx), - Some(registration) => Poll::Ready(ExpiredRegistration(registration)), - } + let (registrations, _, _) = registrations.get(None, None, Some(1)).unwrap(); + + assert_eq!(registrations.count(), 1); } -} -#[derive(Debug, thiserror::Error, Eq, PartialEq)] -#[error("The provided cookie is not valid for a DISCOVER request for the given namespace")] -pub struct CookieNamespaceMismatch; + #[test] + fn given_limit_cookie_can_be_used_for_pagination() { + let mut registrations = Registrations::default(); + registrations.add(new_dummy_registration("foo")); + registrations.add(new_dummy_registration("foo")); + + let (discover1, cookie, existing_registrations) = + registrations.get(None, None, Some(1)).unwrap(); + assert_eq!(discover1.count(), 1); + registrations + .cookies + .insert(cookie.clone(), existing_registrations); + + let (discover2, _, _) = registrations.get(None, Some(cookie), None).unwrap(); + assert_eq!(discover2.count(), 1); + } -#[cfg(test)] -mod tests { - // use instant::SystemTime; - // use std::option::Option::None; - // - // use libp2p_core::{identity, PeerRecord}; - // - // use super::*; - // - // #[test] - // fn given_cookie_from_discover_when_discover_again_then_only_get_diff() { - // let mut registrations = Registrations::default(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // - // let (initial_discover, cookie) = registrations.get(None, None, None).unwrap(); - // assert_eq!(initial_discover.count(), 2); - // - // let (subsequent_discover, _) = registrations.get(None, Some(cookie), None).unwrap(); - // assert_eq!(subsequent_discover.count(), 0); - // } - // - // #[test] - // fn given_registrations_when_discover_all_then_all_are_returned() { - // let mut registrations = Registrations::default(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // - // let (discover, _) = registrations.get(None, None, None).unwrap(); - // - // assert_eq!(discover.count(), 2); - // } - // - // #[test] - // fn given_registrations_when_discover_only_for_specific_namespace_then_only_those_are_returned() - // { - // let mut registrations = Registrations::default(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // registrations.add(new_dummy_registration("bar")).unwrap(); - // - // let (discover, _) = registrations - // .get(Some(Namespace::from_static("foo")), None, None) - // .unwrap(); - // - // assert_eq!( - // discover.map(|r| &r.namespace).collect::>(), - // vec!["foo"] - // ); - // } - // - // #[test] - // fn given_reregistration_old_registration_is_discarded() { - // let alice = identity::Keypair::generate_ed25519(); - // let mut registrations = Registrations::default(); - // registrations - // .add(new_registration("foo", alice.clone(), None)) - // .unwrap(); - // registrations - // .add(new_registration("foo", alice, None)) - // .unwrap(); - // - // let (discover, _) = registrations - // .get(Some(Namespace::from_static("foo")), None, None) - // .unwrap(); - // - // assert_eq!( - // discover.map(|r| &r.namespace).collect::>(), - // vec!["foo"] - // ); - // } - // - // #[test] - // fn given_cookie_from_2nd_discover_does_not_return_nodes_from_first_discover() { - // let mut registrations = Registrations::default(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // - // let (initial_discover, cookie1) = registrations.get(None, None, None).unwrap(); - // assert_eq!(initial_discover.count(), 2); - // - // let (subsequent_discover, cookie2) = registrations.get(None, Some(cookie1), None).unwrap(); - // assert_eq!(subsequent_discover.count(), 0); - // - // let (subsequent_discover, _) = registrations.get(None, Some(cookie2), None).unwrap(); - // assert_eq!(subsequent_discover.count(), 0); - // } - // - // #[test] - // fn cookie_from_different_discover_request_is_not_valid() { - // let mut registrations = Registrations::default(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // registrations.add(new_dummy_registration("bar")).unwrap(); - // - // let (_, foo_discover_cookie) = registrations - // .get(Some(Namespace::from_static("foo")), None, None) - // .unwrap(); - // let result = registrations.get( - // Some(Namespace::from_static("bar")), - // Some(foo_discover_cookie), - // None, - // ); - // - // assert!(matches!(result, Err(CookieNamespaceMismatch))) - // } - // - // #[tokio::test] - // async fn given_two_registration_ttls_one_expires_one_lives() { - // let mut registrations = Registrations::with_config(Config { - // min_ttl: 0, - // max_ttl: 4, - // }); - // - // let start_time = SystemTime::now(); - // - // registrations - // .add(new_dummy_registration_with_ttl("foo", 1)) - // .unwrap(); - // registrations - // .add(new_dummy_registration_with_ttl("bar", 4)) - // .unwrap(); - // - // let event = registrations.next_event().await; - // - // let elapsed = start_time.elapsed().unwrap(); - // assert!(elapsed.as_secs() >= 1); - // assert!(elapsed.as_secs() < 2); - // - // assert_eq!(event.0.namespace, Namespace::from_static("foo")); - // - // { - // let (mut discovered_foo, _) = registrations - // .get(Some(Namespace::from_static("foo")), None, None) - // .unwrap(); - // assert!(discovered_foo.next().is_none()); - // } - // let (mut discovered_bar, _) = registrations - // .get(Some(Namespace::from_static("bar")), None, None) - // .unwrap(); - // assert!(discovered_bar.next().is_some()); - // } - // - // #[tokio::test] - // async fn given_peer_unregisters_before_expiry_do_not_emit_registration_expired() { - // let mut registrations = Registrations::with_config(Config { - // min_ttl: 1, - // max_ttl: 10, - // }); - // let dummy_registration = new_dummy_registration_with_ttl("foo", 2); - // let namespace = dummy_registration.namespace.clone(); - // let peer_id = dummy_registration.record.peer_id(); - // - // registrations.add(dummy_registration).unwrap(); - // registrations.no_event_for(1).await; - // registrations.remove(namespace, peer_id); - // - // registrations.no_event_for(3).await - // } - // - // /// FuturesUnordered stop polling for ready futures when poll_next() is called until a None - // /// value is returned. To prevent the next_expiry future from going to "sleep", next_expiry - // /// is initialised with a future that always returns pending. This test ensures that - // /// FuturesUnordered does not stop polling for ready futures. - // #[tokio::test] - // async fn given_all_registrations_expired_then_successfully_handle_new_registration_and_expiry() - // { - // let mut registrations = Registrations::with_config(Config { - // min_ttl: 0, - // max_ttl: 10, - // }); - // let dummy_registration = new_dummy_registration_with_ttl("foo", 1); - // - // registrations.add(dummy_registration.clone()).unwrap(); - // let _ = registrations.next_event_in_at_most(2).await; - // - // registrations.no_event_for(1).await; - // - // registrations.add(dummy_registration).unwrap(); - // let _ = registrations.next_event_in_at_most(2).await; - // } - // - // #[tokio::test] - // async fn cookies_are_cleaned_up_if_registrations_expire() { - // let mut registrations = Registrations::with_config(Config { - // min_ttl: 1, - // max_ttl: 10, - // }); - // - // registrations - // .add(new_dummy_registration_with_ttl("foo", 2)) - // .unwrap(); - // let (_, _) = registrations.get(None, None, None).unwrap(); - // - // assert_eq!(registrations.data.cookies.len(), 1); - // - // let _ = registrations.next_event_in_at_most(3).await; - // - // assert_eq!(registrations.data.cookies.len(), 0); - // } - // - // #[test] - // fn given_limit_discover_only_returns_n_results() { - // let mut registrations = Registrations::default(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // - // let (registrations, _) = registrations.get(None, None, Some(1)).unwrap(); - // - // assert_eq!(registrations.count(), 1); - // } - // - // #[test] - // fn given_limit_cookie_can_be_used_for_pagination() { - // let mut registrations = Registrations::default(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // registrations.add(new_dummy_registration("foo")).unwrap(); - // - // let (discover1, cookie) = registrations.get(None, None, Some(1)).unwrap(); - // assert_eq!(discover1.count(), 1); - // - // let (discover2, _) = registrations.get(None, Some(cookie), None).unwrap(); - // assert_eq!(discover2.count(), 1); - // } - // - // fn new_dummy_registration(namespace: &'static str) -> NewRegistration { - // let identity = identity::Keypair::generate_ed25519(); - // - // new_registration(namespace, identity, None) - // } - // - // fn new_dummy_registration_with_ttl(namespace: &'static str, ttl: Ttl) -> NewRegistration { - // let identity = identity::Keypair::generate_ed25519(); - // - // new_registration(namespace, identity, Some(ttl)) - // } - // - // fn new_registration( - // namespace: &'static str, - // identity: identity::Keypair, - // ttl: Option, - // ) -> NewRegistration { - // NewRegistration::new( - // Namespace::from_static(namespace), - // PeerRecord::new(&identity, vec!["/ip4/127.0.0.1/tcp/1234".parse().unwrap()]).unwrap(), - // ttl, - // ) - // } - // - // /// Defines utility functions that make the tests more readable. - // impl Registrations { - // async fn next_event(&mut self) -> ExpiredRegistration { - // futures::future::poll_fn(|cx| self.poll(cx)).await - // } - // - // /// Polls [`Registrations`] for `seconds` and panics if it returns a event during this time. - // async fn no_event_for(&mut self, seconds: u64) { - // tokio::time::timeout(Duration::from_secs(seconds), self.next_event()) - // .await - // .unwrap_err(); - // } - // - // /// Polls [`Registrations`] for at most `seconds` and panics if doesn't return an event within that time. - // async fn next_event_in_at_most(&mut self, seconds: u64) -> ExpiredRegistration { - // tokio::time::timeout(Duration::from_secs(seconds), self.next_event()) - // .await - // .unwrap() - // } - // } + fn new_dummy_registration(namespace: &'static str) -> NewRegistration { + let identity = identity::Keypair::generate_ed25519(); + + new_registration(namespace, identity, None) + } + + fn new_registration( + namespace: &'static str, + identity: identity::Keypair, + ttl: Option, + ) -> NewRegistration { + NewRegistration { + namespace: Namespace::from_static(namespace), + record: PeerRecord::new(&identity, vec!["/ip4/127.0.0.1/tcp/1234".parse().unwrap()]) + .unwrap(), + ttl, + } + } } diff --git a/protocols/rendezvous/tests/rendezvous.rs b/protocols/rendezvous/tests/rendezvous.rs index 85fdacd8ae4..ae452da8646 100644 --- a/protocols/rendezvous/tests/rendezvous.rs +++ b/protocols/rendezvous/tests/rendezvous.rs @@ -274,7 +274,7 @@ async fn registration_on_clients_expire() { let roberts_peer_id = *robert.local_peer_id(); robert.spawn_into_runtime(); - let registration_ttl = 3; + let registration_ttl = 2; alice .behaviour_mut() @@ -292,7 +292,7 @@ async fn registration_on_clients_expire() { } }; - tokio::time::sleep(Duration::from_secs(registration_ttl + 5)).await; + tokio::time::sleep(Duration::from_secs(registration_ttl + 3)).await; let event = bob.select_next_some().await; let error = bob.dial(*alice.local_peer_id()).unwrap_err(); diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 2ce08045ac7..3e3a4647282 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -676,12 +676,12 @@ mod tests { SwarmEvent, }; use futures::{AsyncReadExt, AsyncWriteExt}; - use libp2p::plaintext::PlainText2Config; - use libp2p::yamux; use libp2p_core::connection::ConnectionId; use libp2p_core::transport::MemoryTransport; use libp2p_core::upgrade::Version; use libp2p_core::{identity, Multiaddr, PeerId, Transport}; + use libp2p_plaintext::PlainText2Config; + use libp2p_yamux as yamux; use std::collections::HashMap; use std::io; use std::ops::AddAssign; @@ -908,7 +908,7 @@ mod tests { .multiplex(yamux::YamuxConfig::default()) .boxed(); - let swarm = Swarm::new( + Swarm::without_executor( transport, HelloBehaviour { state: Shared::new(State { @@ -919,8 +919,7 @@ mod tests { greeting_count: Default::default(), }, identity.public().to_peer_id(), - ); - swarm + ) } // TODO: Add test for max pending dials From 7bdd953bc063293cfb49acea36d17d790d30185d Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Thu, 17 Nov 2022 00:39:11 +1100 Subject: [PATCH 33/34] Minimise diff --- protocols/rendezvous/src/server.rs | 63 +++++++++++++----------------- 1 file changed, 27 insertions(+), 36 deletions(-) diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index 875f59a60de..7eb8e4acbf5 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -404,12 +404,11 @@ struct ExpiredRegistration(Registration); #[derive(Debug, Clone)] pub struct Registrations { - min_ttl: Ttl, - max_ttl: Ttl, - registrations_for_peer: BiMap<(PeerId, Namespace), RegistrationId>, registrations: HashMap, cookies: HashMap>, + min_ttl: Ttl, + max_ttl: Ttl, } #[derive(Debug)] @@ -442,31 +441,6 @@ impl Registrations { } } - pub fn new_registration( - &self, - from: PeerId, - new_registration: NewRegistrationRequest, - ) -> Result { - let ttl = new_registration.ttl.unwrap_or(DEFAULT_TTL); - - if ttl > self.max_ttl { - return Err(ErrorCode::InvalidTtl); - } - if ttl < self.min_ttl { - return Err(ErrorCode::InvalidTtl); - } - - if new_registration.record.peer_id() != from { - return Err(ErrorCode::NotAuthorized); - } - - Ok(NewRegistration { - namespace: new_registration.namespace, - record: new_registration.record, - ttl: new_registration.ttl, - }) - } - pub fn add( &mut self, new_registration: NewRegistration, @@ -502,6 +476,31 @@ impl Registrations { (registration, expiry) } + pub fn new_registration( + &self, + from: PeerId, + new_registration: NewRegistrationRequest, + ) -> Result { + let ttl = new_registration.ttl.unwrap_or(DEFAULT_TTL); + + if ttl > self.max_ttl { + return Err(ErrorCode::InvalidTtl); + } + if ttl < self.min_ttl { + return Err(ErrorCode::InvalidTtl); + } + + if new_registration.record.peer_id() != from { + return Err(ErrorCode::NotAuthorized); + } + + Ok(NewRegistration { + namespace: new_registration.namespace, + record: new_registration.record, + ttl: new_registration.ttl, + }) + } + pub fn remove(&mut self, namespace: Namespace, peer_id: PeerId) { let reggo_to_remove = self .registrations_for_peer @@ -580,14 +579,6 @@ impl Registrations { } } -#[derive(Debug, thiserror::Error)] -pub enum TtlOutOfRange { - #[error("Requested TTL ({requested}s) is too long; max {bound}s")] - TooLong { bound: Ttl, requested: Ttl }, - #[error("Requested TTL ({requested}s) is too short; min {bound}s")] - TooShort { bound: Ttl, requested: Ttl }, -} - #[derive(Debug, thiserror::Error, Eq, PartialEq)] #[error("The provided cookie is not valid for a DISCOVER request for the given namespace")] pub struct CookieNamespaceMismatch; From 6f007031b19b7f94632925b9fec62ad5c12d9505 Mon Sep 17 00:00:00 2001 From: Thomas Eizinger Date: Fri, 18 Nov 2022 18:34:09 +1100 Subject: [PATCH 34/34] Update to new behaviour interface --- protocols/rendezvous/src/server.rs | 46 +++--------------------- swarm/src/handler/from_fn.rs | 56 ++++++++++++++---------------- 2 files changed, 31 insertions(+), 71 deletions(-) diff --git a/protocols/rendezvous/src/server.rs b/protocols/rendezvous/src/server.rs index 253c6e4902d..3dd1f9ca282 100644 --- a/protocols/rendezvous/src/server.rs +++ b/protocols/rendezvous/src/server.rs @@ -30,12 +30,11 @@ use futures::stream::FuturesUnordered; use futures::{ready, SinkExt}; use futures::{FutureExt, StreamExt}; use libp2p_core::connection::ConnectionId; -use libp2p_core::{ConnectedPoint, Multiaddr, PeerId, PeerRecord}; +use libp2p_core::{ConnectedPoint, PeerId, PeerRecord}; use libp2p_swarm::behaviour::FromSwarm; use libp2p_swarm::handler::from_fn; use libp2p_swarm::{ - from_fn, IntoConnectionHandler, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, - PollParameters, + from_fn, NegotiatedSubstream, NetworkBehaviour, NetworkBehaviourAction, PollParameters, }; use std::collections::{HashMap, HashSet, VecDeque}; use std::io; @@ -161,28 +160,8 @@ impl NetworkBehaviour for Behaviour { .without_outbound_handler() } - fn inject_connection_established( - &mut self, - peer_id: &PeerId, - connection_id: &ConnectionId, - _: &ConnectedPoint, - _: Option<&Vec>, - _: usize, - ) { - self.registrations - .register_connection(*peer_id, *connection_id) - } - - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - connection_id: &ConnectionId, - _: &ConnectedPoint, - _: ::Handler, - _remaining_established: usize, - ) { - self.registrations - .unregister_connection(*peer_id, *connection_id) + fn on_swarm_event(&mut self, event: FromSwarm) { + self.registrations.on_swarm_event(&event); } fn on_connection_handler_event( @@ -292,23 +271,6 @@ impl NetworkBehaviour for Behaviour { Poll::Pending } - - fn on_swarm_event(&mut self, event: FromSwarm) { - match event { - FromSwarm::ConnectionEstablished(_) - | FromSwarm::ConnectionClosed(_) - | FromSwarm::AddressChange(_) - | FromSwarm::DialFailure(_) - | FromSwarm::ListenFailure(_) - | FromSwarm::NewListener(_) - | FromSwarm::NewListenAddr(_) - | FromSwarm::ExpiredListenAddr(_) - | FromSwarm::ListenerError(_) - | FromSwarm::ListenerClosed(_) - | FromSwarm::NewExternalAddr(_) - | FromSwarm::ExpiredExternalAddr(_) => {} - } - } } async fn inbound_stream_handler( diff --git a/swarm/src/handler/from_fn.rs b/swarm/src/handler/from_fn.rs index 3e3a4647282..355a7e3057e 100644 --- a/swarm/src/handler/from_fn.rs +++ b/swarm/src/handler/from_fn.rs @@ -1,3 +1,4 @@ +use crate::behaviour::{ConnectionClosed, ConnectionEstablished, FromSwarm}; use crate::handler::{InboundUpgradeSend, OutboundUpgradeSend}; use crate::{ ConnectionHandler, ConnectionHandlerEvent, ConnectionHandlerUpgrErr, IntoConnectionHandler, @@ -271,12 +272,27 @@ where } } - pub fn register_connection(&mut self, peer_id: PeerId, id: ConnectionId) { - self.connections.insert((peer_id, id)); - } - - pub fn unregister_connection(&mut self, peer_id: PeerId, id: ConnectionId) { - self.connections.remove(&(peer_id, id)); + pub fn on_swarm_event(&mut self, event: &FromSwarm) + where + H: IntoConnectionHandler, + { + match event { + FromSwarm::ConnectionEstablished(ConnectionEstablished { + peer_id, + connection_id, + .. + }) => { + self.connections.insert((*peer_id, *connection_id)); + } + FromSwarm::ConnectionClosed(ConnectionClosed { + peer_id, + connection_id, + .. + }) => { + self.connections.remove(&(*peer_id, *connection_id)); + } + _ => {} + } } pub fn poll( @@ -679,7 +695,7 @@ mod tests { use libp2p_core::connection::ConnectionId; use libp2p_core::transport::MemoryTransport; use libp2p_core::upgrade::Version; - use libp2p_core::{identity, Multiaddr, PeerId, Transport}; + use libp2p_core::{identity, PeerId, Transport}; use libp2p_plaintext::PlainText2Config; use libp2p_yamux as yamux; use std::collections::HashMap; @@ -824,32 +840,14 @@ mod tests { }) } - fn inject_connection_established( - &mut self, - peer_id: &PeerId, - connection_id: &ConnectionId, - _endpoint: &ConnectedPoint, - _failed_addresses: Option<&Vec>, - _other_established: usize, - ) { - self.state.register_connection(*peer_id, *connection_id); - } - - fn inject_connection_closed( - &mut self, - peer_id: &PeerId, - connection_id: &ConnectionId, - _: &ConnectedPoint, - _: ::Handler, - _remaining_established: usize, - ) { - self.state.unregister_connection(*peer_id, *connection_id); + fn on_swarm_event(&mut self, event: FromSwarm) { + self.state.on_swarm_event(&event); } - fn inject_event( + fn on_connection_handler_event( &mut self, _peer_id: PeerId, - _connection: ConnectionId, + _connection_id: ConnectionId, event: <::Handler as ConnectionHandler>::OutEvent, ) { match event {