From 45cd7db6e9b25dd4a79f764b433e2a85e4a058f8 Mon Sep 17 00:00:00 2001 From: James Ray <16969914+jamesray1@users.noreply.github.com> Date: Mon, 29 Oct 2018 20:38:32 +1100 Subject: [PATCH 01/20] Remove spaces before semicolons (#591) --- core/src/nodes/collection.rs | 28 +++++++++---------- core/src/nodes/handled_node.rs | 2 +- core/src/nodes/handled_node_tasks.rs | 2 +- core/src/nodes/raw_swarm.rs | 40 ++++++++++++++-------------- muxers/mplex/src/lib.rs | 4 +-- protocols/floodsub/src/lib.rs | 10 +++---- protocols/identify/src/protocol.rs | 4 +-- protocols/kad/src/high_level.rs | 8 +++--- protocols/kad/src/protocol.rs | 8 +++--- protocols/kad/src/query.rs | 8 +++--- protocols/ping/src/lib.rs | 2 +- protocols/secio/src/handshake.rs | 6 ++--- protocols/secio/src/lib.rs | 2 +- stores/datastore/src/lib.rs | 2 +- transports/dns/src/lib.rs | 2 +- transports/websocket/src/desktop.rs | 2 +- 16 files changed, 65 insertions(+), 65 deletions(-) diff --git a/core/src/nodes/collection.rs b/core/src/nodes/collection.rs index fc17f43aaf8..97c880ebf7f 100644 --- a/core/src/nodes/collection.rs +++ b/core/src/nodes/collection.rs @@ -192,8 +192,8 @@ impl<'a, TInEvent, TOutEvent, THandler> CollectionReachEvent<'a, TInEvent, TOutE let ret_value = if let Some(former_task_id) = former_task_id { self.parent.inner.task(former_task_id) .expect("whenever we receive a TaskClosed event or close a node, we remove the \ - corresponding entry from self.nodes ; therefore all elements in \ - self.nodes are valid tasks in the HandledNodesTasks ; qed") + corresponding entry from self.nodes; therefore all elements in \ + self.nodes are valid tasks in the HandledNodesTasks; qed") .close(); let _former_other_state = self.parent.tasks.remove(&former_task_id); debug_assert_eq!(_former_other_state, Some(TaskState::Connected(self.peer_id.clone()))); @@ -237,10 +237,10 @@ impl<'a, TInEvent, TOutEvent, THandler> Drop for CollectionReachEvent<'a, TInEve let task_state = self.parent.tasks.remove(&self.id); debug_assert!(if let Some(TaskState::Pending) = task_state { true } else { false }); self.parent.inner.task(self.id) - .expect("we create the CollectionReachEvent with a valid task id ; the \ + .expect("we create the CollectionReachEvent with a valid task id; the \ CollectionReachEvent mutably borrows the collection, therefore nothing \ - can delete this task during the lifetime of the CollectionReachEvent ; \ - therefore the task is still valid when we delete it ; qed") + can delete this task during the lifetime of the CollectionReachEvent; \ + therefore the task is still valid when we delete it; qed") .close(); } } @@ -304,9 +304,9 @@ impl CollectionStream CollectionStream { - // TODO: this variant shouldn't happen ; prove this + // TODO: this variant shouldn't happen; prove this panic!() }, (Some(TaskState::Connected(peer_id)), Ok(()), _handler) => { @@ -402,9 +402,9 @@ impl CollectionStream { - panic!("self.tasks is always kept in sync with the tasks in self.inner ; \ + panic!("self.tasks is always kept in sync with the tasks in self.inner; \ when we add a task in self.inner we add a corresponding entry in \ - self.tasks, and remove the entry only when the task is closed ; \ + self.tasks, and remove the entry only when the task is closed; \ qed") }, } @@ -420,9 +420,9 @@ impl CollectionStream peer_id.clone(), _ => panic!("we can only receive NodeEvent events from a task after we \ - received a corresponding NodeReached event from that same task ; \ + received a corresponding NodeReached event from that same task; \ when we receive a NodeReached event, we ensure that the entry in \ - self.tasks is switched to the Connected state ; qed"), + self.tasks is switched to the Connected state; qed"), }; Async::Ready(CollectionEvent::NodeEvent { @@ -457,8 +457,8 @@ impl<'a, TInEvent> PeerMut<'a, TInEvent> { let old_task_id = self.nodes.remove(&peer_id); debug_assert_eq!(old_task_id, Some(self.inner.id())); } else { - panic!("a PeerMut can only be created if an entry is present in nodes ; an entry in \ - nodes always matched a Connected entry in tasks ; qed"); + panic!("a PeerMut can only be created if an entry is present in nodes; an entry in \ + nodes always matched a Connected entry in tasks; qed"); }; self.inner.close(); diff --git a/core/src/nodes/handled_node.rs b/core/src/nodes/handled_node.rs index dd2f2c13b40..c7ae87528f1 100644 --- a/core/src/nodes/handled_node.rs +++ b/core/src/nodes/handled_node.rs @@ -25,7 +25,7 @@ use std::io::Error as IoError; /// Handler for the substreams of a node. // TODO: right now it is possible for a node handler to be built, then shut down right after if we -// realize we dialed the wrong peer for example ; this could be surprising and should either +// realize we dialed the wrong peer for example; this could be surprising and should either // be documented or changed (favouring the "documented" right now) pub trait NodeHandler { /// Custom event that can be received from the outside. diff --git a/core/src/nodes/handled_node_tasks.rs b/core/src/nodes/handled_node_tasks.rs index 611330d2857..9cd7e69ffcd 100644 --- a/core/src/nodes/handled_node_tasks.rs +++ b/core/src/nodes/handled_node_tasks.rs @@ -407,7 +407,7 @@ where node.inject_event(event); }, Ok(Async::Ready(None)) => { - // Node closed by the external API ; start shutdown process. + // Node closed by the external API; start shutdown process. node.shutdown(); break; } diff --git a/core/src/nodes/raw_swarm.rs b/core/src/nodes/raw_swarm.rs index 1f25fe43bc2..6498c758426 100644 --- a/core/src/nodes/raw_swarm.rs +++ b/core/src/nodes/raw_swarm.rs @@ -481,7 +481,7 @@ where if actual_peer_id == expected_peer_id { Ok((actual_peer_id, muxer)) } else { - let msg = format!("public key mismatch ; expected = {:?} ; obtained = {:?}", + let msg = format!("public key mismatch; expected = {:?}; obtained = {:?}", expected_peer_id, actual_peer_id); Err(IoError::new(IoErrorKind::Other, msg)) } @@ -573,10 +573,10 @@ where }) => { let endpoint = self.reach_attempts.connected_points.remove(&peer_id) .expect("We insert into connected_points whenever a connection is \ - opened and remove only when a connection is closed ; the \ + opened and remove only when a connection is closed; the \ underlying API is guaranteed to always deliver a connection \ closed message after it has been opened, and no two closed \ - messages ; qed"); + messages; qed"); debug_assert!(!self.reach_attempts.out_reach_attempts.contains_key(&peer_id)); action = Default::default(); out_event = RawSwarmEvent::NodeError { @@ -588,10 +588,10 @@ where Async::Ready(CollectionEvent::NodeClosed { peer_id }) => { let endpoint = self.reach_attempts.connected_points.remove(&peer_id) .expect("We insert into connected_points whenever a connection is \ - opened and remove only when a connection is closed ; the \ + opened and remove only when a connection is closed; the \ underlying API is guaranteed to always deliver a connection \ closed message after it has been opened, and no two closed \ - messages ; qed"); + messages; qed"); debug_assert!(!self.reach_attempts.out_reach_attempts.contains_key(&peer_id)); action = Default::default(); out_event = RawSwarmEvent::NodeClosed { peer_id, endpoint }; @@ -607,15 +607,15 @@ where } if let Some(interrupt) = action.interrupt { - // TODO: improve proof or remove ; this is too complicated right now + // TODO: improve proof or remove; this is too complicated right now self.active_nodes .interrupt(interrupt) - .expect("interrupt is guaranteed to be gathered from `out_reach_attempts` ; + .expect("interrupt is guaranteed to be gathered from `out_reach_attempts`; we insert in out_reach_attempts only when we call \ active_nodes.add_reach_attempt, and we remove only when we call \ - interrupt or when a reach attempt succeeds or errors ; therefore the \ + interrupt or when a reach attempt succeeds or errors; therefore the \ out_reach_attempts should always be in sync with the actual \ - attempts ; qed"); + attempts; qed"); } return Async::Ready(out_event); @@ -688,9 +688,9 @@ where if outcome == CollectionNodeAccept::ReplacedExisting { let closed_endpoint = closed_endpoint .expect("We insert into connected_points whenever a connection is opened and \ - remove only when a connection is closed ; the underlying API is \ + remove only when a connection is closed; the underlying API is \ guaranteed to always deliver a connection closed message after it has \ - been opened, and no two closed messages ; qed"); + been opened, and no two closed messages; qed"); return (action, RawSwarmEvent::Replaced { peer_id, endpoint: opened_endpoint, @@ -726,9 +726,9 @@ where if outcome == CollectionNodeAccept::ReplacedExisting { let closed_endpoint = closed_endpoint .expect("We insert into connected_points whenever a connection is opened and \ - remove only when a connection is closed ; the underlying API is guaranteed \ + remove only when a connection is closed; the underlying API is guaranteed \ to always deliver a connection closed message after it has been opened, \ - and no two closed messages ; qed"); + and no two closed messages; qed"); return (Default::default(), RawSwarmEvent::Replaced { peer_id, endpoint: opened_endpoint, @@ -740,7 +740,7 @@ where } // We didn't find any entry in neither the outgoing connections not ingoing connections. - // TODO: improve proof or remove ; this is too complicated right now + // TODO: improve proof or remove; this is too complicated right now panic!("The API of collection guarantees that the id sent back in NodeReached (which is where \ we call handle_node_reached) is one that was passed to add_reach_attempt. Whenever we \ call add_reach_attempt, we also insert at the same time an entry either in \ @@ -817,7 +817,7 @@ where TTrans: Transport } // The id was neither in the outbound list nor the inbound list. - // TODO: improve proof or remove ; this is too complicated right now + // TODO: improve proof or remove; this is too complicated right now panic!("The API of collection guarantees that the id sent back in ReachError events \ (which is where we call handle_reach_error) is one that was passed to \ add_reach_attempt. Whenever we call add_reach_attempt, we also insert \ @@ -999,7 +999,7 @@ impl<'a, TInEvent> PeerConnected<'a, TInEvent> { /// Closes the connection to this node. /// /// No `NodeClosed` message will be generated for this node. - // TODO: consider returning a `PeerNotConnected` ; however this makes all the borrows things + // TODO: consider returning a `PeerNotConnected`; however this makes all the borrows things // much more annoying to deal with pub fn close(self) { self.connected_points.remove(&self.peer_id); @@ -1011,9 +1011,9 @@ impl<'a, TInEvent> PeerConnected<'a, TInEvent> { pub fn endpoint(&self) -> &ConnectedPoint { self.connected_points.get(&self.peer_id) .expect("We insert into connected_points whenever a connection is opened and remove \ - only when a connection is closed ; the underlying API is guaranteed to always \ + only when a connection is closed; the underlying API is guaranteed to always \ deliver a connection closed message after it has been opened, and no two \ - closed messages ; qed") + closed messages; qed") } /// Sends an event to the node. @@ -1031,13 +1031,13 @@ pub struct PeerPendingConnect<'a, TInEvent: 'a, TOutEvent: 'a, THandler: 'a> { impl<'a, TInEvent, TOutEvent, THandler> PeerPendingConnect<'a, TInEvent, TOutEvent, THandler> { /// Interrupt this connection attempt. - // TODO: consider returning a PeerNotConnected ; however that is really pain in terms of + // TODO: consider returning a PeerNotConnected; however that is really pain in terms of // borrows #[inline] pub fn interrupt(self) { let attempt = self.attempt.remove(); if let Err(_) = self.active_nodes.interrupt(attempt.id) { - // TODO: improve proof or remove ; this is too complicated right now + // TODO: improve proof or remove; this is too complicated right now panic!("We retreived this attempt.id from out_reach_attempts. We insert in \ out_reach_attempts only at the same time as we call add_reach_attempt. \ Whenever we receive a NodeReached, NodeReplaced or ReachError event, which \ diff --git a/muxers/mplex/src/lib.rs b/muxers/mplex/src/lib.rs index 2de8c8b9a62..14b2445162a 100644 --- a/muxers/mplex/src/lib.rs +++ b/muxers/mplex/src/lib.rs @@ -329,7 +329,7 @@ where C: AsyncRead + AsyncWrite let mut inner = self.inner.lock(); if inner.opened_substreams.len() >= inner.config.max_substreams { - debug!("Refused substream ; reached maximum number of substreams {}", inner.config.max_substreams); + debug!("Refused substream; reached maximum number of substreams {}", inner.config.max_substreams); return Err(IoError::new(IoErrorKind::ConnectionRefused, "exceeded maximum number of open substreams")); } @@ -460,7 +460,7 @@ where C: AsyncRead + AsyncWrite Ok(Async::Ready(Some(data))) => substream.current_data = data, Ok(Async::Ready(None)) => return Ok(Async::Ready(0)), Ok(Async::NotReady) => { - // There was no data packet in the buffer about this substream ; maybe it's + // There was no data packet in the buffer about this substream; maybe it's // because it has been closed. if inner.opened_substreams.contains(&(substream.num, substream.endpoint)) { return Ok(Async::NotReady) diff --git a/protocols/floodsub/src/lib.rs b/protocols/floodsub/src/lib.rs index c84150bbce1..48e04ea34a3 100644 --- a/protocols/floodsub/src/lib.rs +++ b/protocols/floodsub/src/lib.rs @@ -345,7 +345,7 @@ impl FloodSubController { let topics = topics.into_iter(); if log_enabled!(Level::Debug) { - debug!("Queuing sub/unsub message ; sub = {:?} ; unsub = {:?}", + debug!("Queuing sub/unsub message; sub = {:?}; unsub = {:?}", topics.clone().filter(|t| t.1) .map(|t| t.0.hash().clone().into_string()) .collect::>(), @@ -389,7 +389,7 @@ impl FloodSubController { { let topics = topics.into_iter().collect::>(); - debug!("Queueing publish message ; topics = {:?} ; data_len = {:?}", + debug!("Queueing publish message; topics = {:?}; data_len = {:?}", topics.iter().map(|t| t.hash().clone().into_string()).collect::>(), data.len()); @@ -554,7 +554,7 @@ fn handle_packet_received( let mut input = match protobuf::parse_from_bytes::(&bytes) { Ok(msg) => msg, Err(err) => { - debug!("Failed to parse protobuf message ; err = {:?}", err); + debug!("Failed to parse protobuf message; err = {:?}", err); return Err(err.into()); } }; @@ -588,7 +588,7 @@ fn handle_packet_received( .lock() .insert(hash((from.clone(), publish.take_seqno()))) { - trace!("Skipping message because we had already received it ; payload = {} bytes", + trace!("Skipping message because we had already received it; payload = {} bytes", publish.get_data().len()); continue; } @@ -609,7 +609,7 @@ fn handle_packet_received( .map(|h| TopicHash::from_raw(h)) .collect::>(); - trace!("Processing message for topics {:?} ; payload = {} bytes", + trace!("Processing message for topics {:?}; payload = {} bytes", topics, publish.get_data().len()); diff --git a/protocols/identify/src/protocol.rs b/protocols/identify/src/protocol.rs index 44e1987348d..aed10431b69 100644 --- a/protocols/identify/src/protocol.rs +++ b/protocols/identify/src/protocol.rs @@ -87,7 +87,7 @@ where let bytes = message .write_to_bytes() - .expect("writing protobuf failed ; should never happen"); + .expect("writing protobuf failed; should never happen"); let future = self.inner.send(bytes).map(|_| ()); Box::new(future) as Box<_> @@ -142,7 +142,7 @@ where let (info, observed_addr) = match parse_proto_msg(msg) { Ok(v) => v, Err(err) => { - debug!("Failed to parse protobuf message ; error = {:?}", err); + debug!("Failed to parse protobuf message; error = {:?}", err); return Err(err.into()); } }; diff --git a/protocols/kad/src/high_level.rs b/protocols/kad/src/high_level.rs index 34f52315563..e086d7a29b7 100644 --- a/protocols/kad/src/high_level.rs +++ b/protocols/kad/src/high_level.rs @@ -192,7 +192,7 @@ where F: FnMut(&PeerId) -> Fut + Send + 'a, fn gen_random_id(my_id: &PeerId, bucket_num: usize) -> Result { let my_id_len = my_id.as_bytes().len(); - // TODO: this 2 is magic here ; it is the length of the hash of the multihash + // TODO: this 2 is magic here; it is the length of the hash of the multihash let bits_diff = bucket_num + 1; if bits_diff > 8 * (my_id_len - 2) { return Err(()); @@ -232,7 +232,7 @@ where F: FnMut(&PeerId) -> Fut + 'a, Fut: IntoFuture + 'a, Fut::Future: Send, { - debug!("Start query for {:?} ; num results = {}", searched_key, num_results); + debug!("Start query for {:?}; num results = {}", searched_key, num_results); // State of the current iterative process. struct State<'a, F> { @@ -322,7 +322,7 @@ where F: FnMut(&PeerId) -> Fut + 'a, to_contact }; - debug!("New query round ; {} queries in progress ; contacting {} new peers", + debug!("New query round; {} queries in progress; contacting {} new peers", state.current_attempts_fut.len(), to_contact.len()); @@ -449,7 +449,7 @@ where F: FnMut(&PeerId) -> Fut + 'a, } else { if !local_nearest_node_updated { - trace!("Loop didn't update closer node ; jumping to step 2"); + trace!("Loop didn't update closer node; jumping to step 2"); state.stage = Stage::SecondStep; } } diff --git a/protocols/kad/src/protocol.rs b/protocols/kad/src/protocol.rs index 2ba18e5e786..bad4fe75861 100644 --- a/protocols/kad/src/protocol.rs +++ b/protocols/kad/src/protocol.rs @@ -88,7 +88,7 @@ impl KadPeer { // Builds a `KadPeer` from its raw protobuf equivalent. // TODO: use TryFrom once stable fn from_peer(peer: &mut protobuf_structs::dht::Message_Peer) -> Result { - // TODO: this is in fact a CID ; not sure if this should be handled in `from_bytes` or + // TODO: this is in fact a CID; not sure if this should be handled in `from_bytes` or // as a special case here let node_id = PeerId::from_bytes(peer.get_id().to_vec()) .map_err(|_| IoError::new(IoErrorKind::InvalidData, "invalid peer id"))?; @@ -339,7 +339,7 @@ fn proto_to_msg(mut message: protobuf_structs::dht::Message) -> Result Result Result { // TODO: for now we don't parse the peer properly, so it is possible that we get - // parsing errors for peers even when they are valid ; we ignore these + // parsing errors for peers even when they are valid; we ignore these // errors for now, but ultimately we should just error altogether let provider_peer = message.mut_providerPeers() .iter_mut() diff --git a/protocols/kad/src/query.rs b/protocols/kad/src/query.rs index f78e5a1a0e3..bd6644c91e1 100644 --- a/protocols/kad/src/query.rs +++ b/protocols/kad/src/query.rs @@ -100,7 +100,7 @@ where fn gen_random_id(my_id: &PeerId, bucket_num: usize) -> Result { let my_id_len = my_id.as_bytes().len(); - // TODO: this 2 is magic here ; it is the length of the hash of the multihash + // TODO: this 2 is magic here; it is the length of the hash of the multihash let bits_diff = bucket_num + 1; if bits_diff > 8 * (my_id_len - 2) { return Err(()); @@ -137,7 +137,7 @@ where FBuckets: Fn(PeerId) -> Vec + 'a + Clone, FFindNode: Fn(Multiaddr, PeerId) -> Box, Error = IoError> + Send> + 'a + Clone, { - debug!("Start query for {:?} ; num results = {}", searched_key, num_results); + debug!("Start query for {:?}; num results = {}", searched_key, num_results); // State of the current iterative process. struct State<'a> { @@ -230,7 +230,7 @@ where to_contact }; - debug!("New query round ; {} queries in progress ; contacting {} new peers", + debug!("New query round; {} queries in progress; contacting {} new peers", state.current_attempts_fut.len(), to_contact.len()); @@ -350,7 +350,7 @@ where } else { if !local_nearest_node_updated { - trace!("Loop didn't update closer node ; jumping to step 2"); + trace!("Loop didn't update closer node; jumping to step 2"); state.stage = Stage::SecondStep; } } diff --git a/protocols/ping/src/lib.rs b/protocols/ping/src/lib.rs index dafb448cb67..e8c59a00f1a 100644 --- a/protocols/ping/src/lib.rs +++ b/protocols/ping/src/lib.rs @@ -311,7 +311,7 @@ where TSocket: AsyncRead + AsyncWrite PingListenerState::Listening => { match self.inner.poll() { Ok(Async::Ready(Some(payload))) => { - debug!("Received ping (payload={:?}) ; sending back", payload); + debug!("Received ping (payload={:?}); sending back", payload); self.state = PingListenerState::Sending(payload.freeze()) }, Ok(Async::Ready(None)) => self.state = PingListenerState::Closing, diff --git a/protocols/secio/src/handshake.rs b/protocols/secio/src/handshake.rs index 18e2de7feac..da4a5a910e6 100644 --- a/protocols/secio/src/handshake.rs +++ b/protocols/secio/src/handshake.rs @@ -324,7 +324,7 @@ where .and_then(|context| { // Generate our nonce. let context = context.with_local()?; - trace!("starting handshake ; local nonce = {:?}", context.state.nonce); + trace!("starting handshake; local nonce = {:?}", context.state.nonce); Ok(context) }) .and_then(|context| { @@ -346,7 +346,7 @@ where return Err(err.into()) }, }; - trace!("received proposition from remote ; pubkey = {:?} ; nonce = {:?}", + trace!("received proposition from remote; pubkey = {:?}; nonce = {:?}", context.state.public_key, context.state.nonce); Ok((socket, context)) }) @@ -436,7 +436,7 @@ where let remote_exch = match protobuf_parse_from_bytes::(&raw) { Ok(e) => e, Err(err) => { - debug!("failed to parse remote's exchange protobuf ; {:?}", err); + debug!("failed to parse remote's exchange protobuf; {:?}", err); return Err(SecioError::HandshakeParsingFailure); } }; diff --git a/protocols/secio/src/lib.rs b/protocols/secio/src/lib.rs index 8ccc2310559..4978110f412 100644 --- a/protocols/secio/src/lib.rs +++ b/protocols/secio/src/lib.rs @@ -306,7 +306,7 @@ impl SecioKeyPair { SecioKeyPairInner::Secp256k1 { ref private } => { let secp = secp256k1::Secp256k1::with_caps(secp256k1::ContextFlag::SignOnly); let pubkey = secp256k1::key::PublicKey::from_secret_key(&secp, private) - .expect("wrong secp256k1 private key ; type safety violated"); + .expect("wrong secp256k1 private key; type safety violated"); PublicKey::Secp256k1(pubkey.serialize_vec(&secp, true).to_vec()) } } diff --git a/stores/datastore/src/lib.rs b/stores/datastore/src/lib.rs index 616f09f129e..341aea2b146 100644 --- a/stores/datastore/src/lib.rs +++ b/stores/datastore/src/lib.rs @@ -21,7 +21,7 @@ //! General-purpose key-value storage. //! The keys are strings, and the values are of any type you want. //! -//! > **Note**: This crate is meant to be a utility for the implementation of other crates ; it +//! > **Note**: This crate is meant to be a utility for the implementation of other crates; it //! > does not directly participate in the stack of libp2p. //! //! This crate provides the `Datastore` trait, whose template parameter is the type of the value. diff --git a/transports/dns/src/lib.rs b/transports/dns/src/lib.rs index 9b7948ac2ee..b3ab759173a 100644 --- a/transports/dns/src/lib.rs +++ b/transports/dns/src/lib.rs @@ -179,7 +179,7 @@ where } } -// How to resolve ; to an IPv4 address or an IPv6 address? +// How to resolve; to an IPv4 address or an IPv6 address? #[derive(Debug, Copy, Clone, PartialEq, Eq)] enum ResolveTy { Dns4, diff --git a/transports/websocket/src/desktop.rs b/transports/websocket/src/desktop.rs index 5c347fd9cdd..0b071df0b8c 100644 --- a/transports/websocket/src/desktop.rs +++ b/transports/websocket/src/desktop.rs @@ -206,7 +206,7 @@ where OwnedMessage::Binary(data) => Ok(data), OwnedMessage::Text(data) => Ok(data.into_bytes()), // TODO: pings and pongs and close messages need to be - // answered ; and this is really hard ; for now we produce + // answered; and this is really hard; for now we produce // an error when that happens _ => Err(IoError::new(IoErrorKind::Other, "unimplemented")), } From 7c8d8b5096d09142d8155048fd2f484b9977287b Mon Sep 17 00:00:00 2001 From: Chevdor Date: Tue, 30 Oct 2018 10:48:24 +0100 Subject: [PATCH 02/20] Add substrate to the list of projects using libp2p (#595) --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index edc4282a434..b998309b2f4 100644 --- a/README.md +++ b/README.md @@ -22,3 +22,4 @@ libp2p = { git = "https://github.com/libp2p/rust-libp2p" } (open a pull request if you want your project to be added here) - https://github.com/paritytech/polkadot +- https://github.com/paritytech/substrate From 29b1b0b3bae2b350dc1cfd7daeba1e15161711ab Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Wed, 31 Oct 2018 08:31:15 +0100 Subject: [PATCH 03/20] Reexport multihash from the facade (#587) --- Cargo.toml | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 341e829d764..9ea458b63c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,6 +13,7 @@ secio-secp256k1 = ["libp2p-secio/secp256k1"] bytes = "0.4" futures = "0.1" multiaddr = { path = "./misc/multiaddr" } +multihash = { path = "./misc/multihash" } libp2p-mplex = { path = "./muxers/mplex" } libp2p-identify = { path = "./protocols/identify" } libp2p-kad = { path = "./protocols/kad" } diff --git a/src/lib.rs b/src/lib.rs index 71550220a1d..1962a60c447 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -135,6 +135,7 @@ pub extern crate futures; #[cfg(not(target_os = "emscripten"))] pub extern crate tokio_current_thread; pub extern crate multiaddr; +pub extern crate multihash; pub extern crate tokio_io; pub extern crate tokio_codec; From 61acb7c13f9c3f28ee2a1a159ad454ef489edaa9 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Wed, 31 Oct 2018 09:55:59 +0100 Subject: [PATCH 04/20] Use websocket 0.21.0 (#597) --- transports/websocket/Cargo.toml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/transports/websocket/Cargo.toml b/transports/websocket/Cargo.toml index addbb90cfba..02981dda342 100644 --- a/transports/websocket/Cargo.toml +++ b/transports/websocket/Cargo.toml @@ -13,9 +13,7 @@ rw-stream-sink = { path = "../../misc/rw-stream-sink" } tokio-io = "0.1" [target.'cfg(not(target_os = "emscripten"))'.dependencies] -# TODO: restore the upstream version once the branch is merged -websocket = { git = "https://github.com/tomaka/rust-websocket", branch = "send", default-features = false, features = ["async", "async-ssl"] } -#websocket = { version = "0.20.2", default-features = false, features = ["async", "async-ssl"] } +websocket = { version = "0.21.0", default-features = false, features = ["async", "async-ssl"] } [target.'cfg(target_os = "emscripten")'.dependencies] stdweb = { version = "0.1.3", default-features = false } From 4627f2118026c3359acbc7151108f77060feb815 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Wed, 31 Oct 2018 11:17:12 +0100 Subject: [PATCH 05/20] Use paritytech/rust-secp256k1 (#598) --- protocols/secio/Cargo.toml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/protocols/secio/Cargo.toml b/protocols/secio/Cargo.toml index 09511affbfa..7f6e4816d4a 100644 --- a/protocols/secio/Cargo.toml +++ b/protocols/secio/Cargo.toml @@ -12,8 +12,7 @@ libp2p-core = { path = "../../core" } log = "0.4.1" protobuf = "2.0.2" rand = "0.5" -# TODO: use the paritytech repo after https://github.com/paritytech/rust-secp256k1/pull/14 -eth-secp256k1 = { git = "https://github.com/tomaka/rust-secp256k1", branch = "pub-rand", optional = true } +eth-secp256k1 = { git = "https://github.com/paritytech/rust-secp256k1", optional = true } aes-ctr = "0.1.0" aesni = { version = "0.4.1", features = ["nocheck"], optional = true } twofish = "0.1.0" From c730b4b3c980c678ba29ce4c74bb6721d1f105fa Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Thu, 1 Nov 2018 11:06:32 +0100 Subject: [PATCH 06/20] Add ProtocolsHandler trait (#573) * Add ProtocolsHandler trait * Reexport symbols * Add a note about shutting down * Add map_protocol * Add a NodeHandlerWrapperBuilder * Update core/src/nodes/protocols_handler.rs Co-Authored-By: tomaka * Fix compilation --- core/Cargo.toml | 1 + core/src/lib.rs | 3 +- core/src/nodes/handled_node.rs | 11 + core/src/nodes/mod.rs | 2 + core/src/nodes/protocols_handler.rs | 682 ++++++++++++++++++++++++++++ 5 files changed, 697 insertions(+), 2 deletions(-) create mode 100644 core/src/nodes/protocols_handler.rs diff --git a/core/Cargo.toml b/core/Cargo.toml index ba64ec2d4ca..f01edae9783 100644 --- a/core/Cargo.toml +++ b/core/Cargo.toml @@ -20,6 +20,7 @@ rw-stream-sink = { path = "../misc/rw-stream-sink" } smallvec = "0.6" tokio-executor = "0.1.4" tokio-io = "0.1" +tokio-timer = "0.2" void = "1" [dev-dependencies] diff --git a/core/src/lib.rs b/core/src/lib.rs index 82ee1a8283c..d135f1699b1 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -184,6 +184,7 @@ extern crate rw_stream_sink; extern crate smallvec; extern crate tokio_executor; extern crate tokio_io; +extern crate tokio_timer; extern crate void; #[cfg(test)] @@ -193,8 +194,6 @@ extern crate tokio; #[cfg(test)] extern crate tokio_codec; #[cfg(test)] -extern crate tokio_timer; -#[cfg(test)] #[macro_use] extern crate assert_matches; #[cfg(test)] diff --git a/core/src/nodes/handled_node.rs b/core/src/nodes/handled_node.rs index c7ae87528f1..c341d750c4d 100644 --- a/core/src/nodes/handled_node.rs +++ b/core/src/nodes/handled_node.rs @@ -41,6 +41,12 @@ pub trait NodeHandler { /// Sends a new substream to the handler. /// /// The handler is responsible for upgrading the substream to whatever protocol it wants. + /// + /// # Panic + /// + /// Implementations are allowed to panic in the case of dialing if the `user_data` in + /// `endpoint` doesn't correspond to what was returned earlier when polling, or is used + /// multiple times. fn inject_substream(&mut self, substream: Self::Substream, endpoint: NodeHandlerEndpoint); /// Indicates to the handler that the inbound part of the muxer has been closed, and that @@ -49,6 +55,11 @@ pub trait NodeHandler { /// Indicates to the handler that an outbound substream failed to open because the outbound /// part of the muxer has been closed. + /// + /// # Panic + /// + /// Implementations are allowed to panic if `user_data` doesn't correspond to what was returned + /// earlier when polling, or is used multiple times. fn inject_outbound_closed(&mut self, user_data: Self::OutboundOpenInfo); /// Injects an event coming from the outside into the handler. diff --git a/core/src/nodes/mod.rs b/core/src/nodes/mod.rs index 1d78ea7b3a3..2636de3567a 100644 --- a/core/src/nodes/mod.rs +++ b/core/src/nodes/mod.rs @@ -23,8 +23,10 @@ pub mod handled_node; pub mod handled_node_tasks; pub mod listeners; pub mod node; +pub mod protocols_handler; pub mod raw_swarm; pub use self::node::Substream; pub use self::handled_node::{NodeHandlerEvent, NodeHandlerEndpoint}; +pub use self::protocols_handler::{ProtocolsHandler, ProtocolsHandlerEvent}; pub use self::raw_swarm::{ConnectedPoint, Peer, RawSwarm, RawSwarmEvent}; diff --git a/core/src/nodes/protocols_handler.rs b/core/src/nodes/protocols_handler.rs new file mode 100644 index 00000000000..0ae832ef39e --- /dev/null +++ b/core/src/nodes/protocols_handler.rs @@ -0,0 +1,682 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use futures::prelude::*; +use nodes::handled_node::{NodeHandler, NodeHandlerEndpoint, NodeHandlerEvent}; +use std::{io, marker::PhantomData, time::Duration}; +use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_timer::Timeout; +use upgrade::{self, apply::UpgradeApplyFuture, DeniedConnectionUpgrade}; +use void::Void; +use {ConnectionUpgrade, Endpoint}; + +/// Handler for a set of protocols for a specific connection with a remote. +/// +/// This trait should be implemented on struct that hold the state for a specific protocol +/// behaviour with a specific remote. +/// +/// # Handling a protocol +/// +/// Protocols with the remote can be opened in two different ways: +/// +/// - Dialing, which is a voluntary process. In order to do so, make `poll()` return an +/// `OutboundSubstreamRequest` variant containing the connection upgrade to use. +/// - Listening, which is used to determine which protocols are supported when the remote wants +/// to open a substream. The `listen_protocol()` method should return the upgrades supported when +/// listening. +/// +/// The upgrade when dialing and the upgrade when listening have to be of the same type, but you +/// are free to return for example an `OrUpgrade` enum, or an enum of yours, containing the upgrade +/// you want depending on the situation. +/// +/// # Shutting down +/// +/// Implementors of this trait should keep in mind that the connection can be closed at any time. +/// When a connection is closed (either by us or by the remote) `shutdown()` is called and the +/// handler continues to be processed until it produces `None`. Then only the handler is destroyed. +/// +/// This makes it possible for the handler to finish delivering events even after knowing that it +/// is shutting down. +/// +/// Implementors of this trait should keep in mind that when `shutdown()` is called, the connection +/// might already be closed or unresponsive. They should therefore not rely on being able to +/// deliver messages. +/// +/// # Relationship with `NodeHandler`. +/// +/// This trait is very similar to the `NodeHandler` trait. The fundamental differences are: +/// +/// - The `NodeHandler` trait gives you more control and is therefore more difficult to implement. +/// - The `NodeHandler` trait is designed to have exclusive ownership of the connection with a +/// node, while the `ProtocolsHandler` trait is designed to handle only a specific set of +/// protocols. Two or more implementations of `ProtocolsHandler` can be combined into one that +/// supports all the protocols together, which is not possible with `NodeHandler`. +/// +// TODO: add a "blocks connection closing" system, so that we can gracefully close a connection +// when it's no longer needed, and so that for example the periodic pinging system does not +// keep the connection alive forever +pub trait ProtocolsHandler { + /// Custom event that can be received from the outside. + type InEvent; + /// Custom event that can be produced by the handler and that will be returned to the outside. + type OutEvent; + /// The type of the substream that contains the raw data. + type Substream: AsyncRead + AsyncWrite; + /// The upgrade for the protocol or protocols handled by this handler. + type Protocol: ConnectionUpgrade; + /// Information about a substream. Can be sent to the handler through a `NodeHandlerEndpoint`, + /// and will be passed back in `inject_substream` or `inject_outbound_closed`. + type OutboundOpenInfo; + + /// Produces a `ConnectionUpgrade` for the protocol or protocols to accept when listening. + /// + /// > **Note**: You should always accept all the protocols you support, even if in a specific + /// > context you wouldn't accept one in particular (eg. only allow one substream at + /// > a time for a given protocol). The reason is that remotes are allowed to put the + /// > list of supported protocols in a cache in order to avoid spurious queries. + fn listen_protocol(&self) -> Self::Protocol; + + /// Injects a fully-negotiated substream in the handler. + /// + /// This method is called when a substream has been successfully opened and negotiated. + fn inject_fully_negotiated( + &mut self, + protocol: >::Output, + endpoint: NodeHandlerEndpoint, + ); + + /// Injects an event coming from the outside in the handler. + fn inject_event(&mut self, event: &Self::InEvent); + + /// Indicates to the handler that upgrading a substream to the given protocol has failed. + fn inject_dial_upgrade_error(&mut self, info: Self::OutboundOpenInfo, error: io::Error); + + /// Indicates the handler that the inbound part of the muxer has been closed, and that + /// therefore no more inbound substream will be produced. + fn inject_inbound_closed(&mut self); + + /// Indicates the node that it should shut down. After that, it is expected that `poll()` + /// returns `Ready(None)` as soon as possible. + /// + /// This method allows an implementation to perform a graceful shutdown of the substreams, and + /// send back various events. + fn shutdown(&mut self); + + /// Should behave like `Stream::poll()`. Should close if no more event can be produced and the + /// node should be closed. + /// + /// > **Note**: If this handler is combined with other handlers, as soon as `poll()` returns + /// > `Ok(Async::Ready(None))`, all the other handlers will receive a call to + /// > `shutdown()` and will eventually be closed and destroyed. + fn poll( + &mut self, + ) -> Poll< + Option>, + io::Error, + >; + + /// Adds a closure that turns the input event into something else. + #[inline] + fn map_in_event(self, map: TMap) -> MapInEvent + where + Self: Sized, + TMap: Fn(&TNewIn) -> Option<&Self::InEvent>, + { + MapInEvent { + inner: self, + map, + marker: PhantomData, + } + } + + /// Adds a closure that turns the output event into something else. + #[inline] + fn map_out_event(self, map: TMap) -> MapOutEvent + where + Self: Sized, + TMap: FnMut(Self::OutEvent) -> TNewOut, + { + MapOutEvent { inner: self, map } + } + + /// Creates a builder that will allow creating a `NodeHandler` that handles this protocol + /// exclusively. + #[inline] + fn into_node_handler_builder(self) -> NodeHandlerWrapperBuilder + where + Self: Sized, + { + NodeHandlerWrapperBuilder { + handler: self, + in_timeout: Duration::from_secs(10), + out_timeout: Duration::from_secs(10), + } + } + + /// Builds an implementation of `NodeHandler` that handles this protocol exclusively. + /// + /// > **Note**: This is a shortcut for `self.into_node_handler_builder().build()`. + #[inline] + fn into_node_handler(self) -> NodeHandlerWrapper + where + Self: Sized, + { + self.into_node_handler_builder().build() + } +} + +/// Event produced by a handler. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub enum ProtocolsHandlerEvent { + /// Require a new outbound substream to be opened with the remote. + OutboundSubstreamRequest { + /// The upgrade to apply on the substream. + upgrade: TConnectionUpgrade, + /// User-defind information, passed back when the substream is open. + info: TOutboundOpenInfo, + }, + + /// Other event. + Custom(TCustom), +} + +/// Event produced by a handler. +impl + ProtocolsHandlerEvent +{ + /// If this is `OutboundSubstreamRequest`, maps the content to something else. + #[inline] + pub fn map_outbound_open_info( + self, + map: F, + ) -> ProtocolsHandlerEvent + where + F: FnOnce(TOutboundOpenInfo) -> I, + { + match self { + ProtocolsHandlerEvent::OutboundSubstreamRequest { upgrade, info } => { + ProtocolsHandlerEvent::OutboundSubstreamRequest { + upgrade, + info: map(info), + } + } + ProtocolsHandlerEvent::Custom(val) => ProtocolsHandlerEvent::Custom(val), + } + } + + /// If this is `OutboundSubstreamRequest`, maps the protocol to another. + #[inline] + pub fn map_protocol( + self, + map: F, + ) -> ProtocolsHandlerEvent + where + F: FnOnce(TConnectionUpgrade) -> I, + { + match self { + ProtocolsHandlerEvent::OutboundSubstreamRequest { upgrade, info } => { + ProtocolsHandlerEvent::OutboundSubstreamRequest { + upgrade: map(upgrade), + info, + } + } + ProtocolsHandlerEvent::Custom(val) => ProtocolsHandlerEvent::Custom(val), + } + } + + /// If this is `Custom`, maps the content to something else. + #[inline] + pub fn map_custom( + self, + map: F, + ) -> ProtocolsHandlerEvent + where + F: FnOnce(TCustom) -> I, + { + match self { + ProtocolsHandlerEvent::OutboundSubstreamRequest { upgrade, info } => { + ProtocolsHandlerEvent::OutboundSubstreamRequest { upgrade, info } + } + ProtocolsHandlerEvent::Custom(val) => ProtocolsHandlerEvent::Custom(map(val)), + } + } +} + +/// Implementation of `ProtocolsHandler` that doesn't handle anything. +pub struct DummyProtocolsHandler { + shutting_down: bool, + marker: PhantomData, +} + +impl Default for DummyProtocolsHandler { + #[inline] + fn default() -> Self { + DummyProtocolsHandler { + shutting_down: false, + marker: PhantomData, + } + } +} + +impl ProtocolsHandler for DummyProtocolsHandler +where + TSubstream: AsyncRead + AsyncWrite, +{ + type InEvent = Void; + type OutEvent = Void; + type Substream = TSubstream; + type Protocol = DeniedConnectionUpgrade; + type OutboundOpenInfo = Void; + + #[inline] + fn listen_protocol(&self) -> Self::Protocol { + DeniedConnectionUpgrade + } + + #[inline] + fn inject_fully_negotiated( + &mut self, + _: >::Output, + _: NodeHandlerEndpoint, + ) { + } + + #[inline] + fn inject_event(&mut self, _: &Self::InEvent) {} + + #[inline] + fn inject_dial_upgrade_error(&mut self, _: Self::OutboundOpenInfo, _: io::Error) {} + + #[inline] + fn inject_inbound_closed(&mut self) {} + + #[inline] + fn shutdown(&mut self) { + self.shutting_down = true; + } + + #[inline] + fn poll( + &mut self, + ) -> Poll< + Option>, + io::Error, + > { + if self.shutting_down { + Ok(Async::Ready(None)) + } else { + Ok(Async::NotReady) + } + } +} + +/// Wrapper around a protocol handler that turns the input event into something else. +pub struct MapInEvent { + inner: TProtoHandler, + map: TMap, + marker: PhantomData, +} + +impl ProtocolsHandler for MapInEvent +where + TProtoHandler: ProtocolsHandler, + TMap: Fn(&TNewIn) -> Option<&TProtoHandler::InEvent>, +{ + type InEvent = TNewIn; + type OutEvent = TProtoHandler::OutEvent; + type Substream = TProtoHandler::Substream; + type Protocol = TProtoHandler::Protocol; + type OutboundOpenInfo = TProtoHandler::OutboundOpenInfo; + + #[inline] + fn listen_protocol(&self) -> Self::Protocol { + self.inner.listen_protocol() + } + + #[inline] + fn inject_fully_negotiated( + &mut self, + protocol: >::Output, + endpoint: NodeHandlerEndpoint, + ) { + self.inner.inject_fully_negotiated(protocol, endpoint) + } + + #[inline] + fn inject_event(&mut self, event: &TNewIn) { + if let Some(event) = (self.map)(event) { + self.inner.inject_event(event); + } + } + + #[inline] + fn inject_dial_upgrade_error(&mut self, info: Self::OutboundOpenInfo, error: io::Error) { + self.inner.inject_dial_upgrade_error(info, error) + } + + #[inline] + fn inject_inbound_closed(&mut self) { + self.inner.inject_inbound_closed() + } + + #[inline] + fn shutdown(&mut self) { + self.inner.shutdown() + } + + #[inline] + fn poll( + &mut self, + ) -> Poll< + Option>, + io::Error, + > { + self.inner.poll() + } +} + +/// Wrapper around a protocol handler that turns the output event into something else. +pub struct MapOutEvent { + inner: TProtoHandler, + map: TMap, +} + +impl ProtocolsHandler for MapOutEvent +where + TProtoHandler: ProtocolsHandler, + TMap: FnMut(TProtoHandler::OutEvent) -> TNewOut, +{ + type InEvent = TProtoHandler::InEvent; + type OutEvent = TNewOut; + type Substream = TProtoHandler::Substream; + type Protocol = TProtoHandler::Protocol; + type OutboundOpenInfo = TProtoHandler::OutboundOpenInfo; + + #[inline] + fn listen_protocol(&self) -> Self::Protocol { + self.inner.listen_protocol() + } + + #[inline] + fn inject_fully_negotiated( + &mut self, + protocol: >::Output, + endpoint: NodeHandlerEndpoint, + ) { + self.inner.inject_fully_negotiated(protocol, endpoint) + } + + #[inline] + fn inject_event(&mut self, event: &Self::InEvent) { + self.inner.inject_event(event) + } + + #[inline] + fn inject_dial_upgrade_error(&mut self, info: Self::OutboundOpenInfo, error: io::Error) { + self.inner.inject_dial_upgrade_error(info, error) + } + + #[inline] + fn inject_inbound_closed(&mut self) { + self.inner.inject_inbound_closed() + } + + #[inline] + fn shutdown(&mut self) { + self.inner.shutdown() + } + + #[inline] + fn poll( + &mut self, + ) -> Poll< + Option>, + io::Error, + > { + Ok(self.inner.poll()?.map(|ev| { + ev.map(|ev| match ev { + ProtocolsHandlerEvent::Custom(ev) => ProtocolsHandlerEvent::Custom((self.map)(ev)), + ProtocolsHandlerEvent::OutboundSubstreamRequest { upgrade, info } => { + ProtocolsHandlerEvent::OutboundSubstreamRequest { upgrade, info } + } + }) + })) + } +} + +/// Prototype for a `NodeHandlerWrapper`. +pub struct NodeHandlerWrapperBuilder +where + TProtoHandler: ProtocolsHandler, +{ + /// The underlying handler. + handler: TProtoHandler, + /// Timeout for incoming substreams negotiation. + in_timeout: Duration, + /// Timeout for outgoing substreams negotiation. + out_timeout: Duration, +} + +impl NodeHandlerWrapperBuilder +where + TProtoHandler: ProtocolsHandler +{ + /// Sets the timeout to use when negotiating a protocol on an ingoing substream. + #[inline] + pub fn with_in_negotiation_timeout(mut self, timeout: Duration) -> Self { + self.in_timeout = timeout; + self + } + + /// Sets the timeout to use when negotiating a protocol on an outgoing substream. + #[inline] + pub fn with_out_negotiation_timeout(mut self, timeout: Duration) -> Self { + self.out_timeout = timeout; + self + } + + /// Builds the `NodeHandlerWrapper`. + #[inline] + pub fn build(self) -> NodeHandlerWrapper { + NodeHandlerWrapper { + handler: self.handler, + negotiating_in: Vec::new(), + negotiating_out: Vec::new(), + in_timeout: self.in_timeout, + out_timeout: self.out_timeout, + queued_dial_upgrades: Vec::new(), + unique_dial_upgrade_id: 0, + } + } +} + +/// Wraps around an implementation of `ProtocolsHandler`, and implements `NodeHandler`. +// TODO: add a caching system for protocols that are supported or not +pub struct NodeHandlerWrapper +where + TProtoHandler: ProtocolsHandler, +{ + /// The underlying handler. + handler: TProtoHandler, + /// Futures that upgrade incoming substreams. + negotiating_in: + Vec>>, + /// Futures that upgrade outgoing substreams. The first element of the tuple is the userdata + /// to pass back once successfully opened. + negotiating_out: Vec<( + TProtoHandler::OutboundOpenInfo, + Timeout>, + )>, + /// Timeout for incoming substreams negotiation. + in_timeout: Duration, + /// Timeout for outgoing substreams negotiation. + out_timeout: Duration, + /// For each outbound substream request, how to upgrade it. The first element of the tuple + /// is the unique identifier (see `unique_dial_upgrade_id`). + queued_dial_upgrades: Vec<(u64, TProtoHandler::Protocol)>, + /// Unique identifier assigned to each queued dial upgrade. + unique_dial_upgrade_id: u64, +} + +impl NodeHandler for NodeHandlerWrapper +where + TProtoHandler: ProtocolsHandler, + >::NamesIter: Clone, +{ + type InEvent = TProtoHandler::InEvent; + type OutEvent = TProtoHandler::OutEvent; + type Substream = TProtoHandler::Substream; + // The first element of the tuple is the unique upgrade identifier + // (see `unique_dial_upgrade_id`). + type OutboundOpenInfo = (u64, TProtoHandler::OutboundOpenInfo); + + fn inject_substream( + &mut self, + substream: Self::Substream, + endpoint: NodeHandlerEndpoint, + ) { + match endpoint { + NodeHandlerEndpoint::Listener => { + let protocol = self.handler.listen_protocol(); + let upgrade = upgrade::apply(substream, protocol, Endpoint::Listener); + let with_timeout = Timeout::new(upgrade, self.in_timeout); + self.negotiating_in.push(with_timeout); + } + NodeHandlerEndpoint::Dialer((upgrade_id, user_data)) => { + let pos = match self + .queued_dial_upgrades + .iter() + .position(|(id, _)| id == &upgrade_id) + { + Some(p) => p, + None => { + debug_assert!(false, "Received an upgrade with an invalid upgrade ID"); + return; + } + }; + + let (_, proto_upgrade) = self.queued_dial_upgrades.remove(pos); + let upgrade = upgrade::apply(substream, proto_upgrade, Endpoint::Dialer); + let with_timeout = Timeout::new(upgrade, self.out_timeout); + self.negotiating_out.push((user_data, with_timeout)); + } + } + } + + #[inline] + fn inject_inbound_closed(&mut self) { + self.handler.inject_inbound_closed(); + } + + fn inject_outbound_closed(&mut self, user_data: Self::OutboundOpenInfo) { + let pos = match self + .queued_dial_upgrades + .iter() + .position(|(id, _)| id == &user_data.0) + { + Some(p) => p, + None => { + debug_assert!( + false, + "Received an outbound closed error with an invalid upgrade ID" + ); + return; + } + }; + + self.queued_dial_upgrades.remove(pos); + self.handler + .inject_dial_upgrade_error(user_data.1, io::ErrorKind::ConnectionReset.into()); + } + + #[inline] + fn inject_event(&mut self, event: Self::InEvent) { + self.handler.inject_event(&event); + } + + #[inline] + fn shutdown(&mut self) { + self.handler.shutdown(); + } + + fn poll( + &mut self, + ) -> Poll>, io::Error> { + // Continue negotiation of newly-opened substreams on the listening side. + // We remove each element from `negotiating_in` one by one and add them back if not ready. + for n in (0..self.negotiating_in.len()).rev() { + let mut in_progress = self.negotiating_in.swap_remove(n); + match in_progress.poll() { + Ok(Async::Ready(upgrade)) => { + self.handler + .inject_fully_negotiated(upgrade, NodeHandlerEndpoint::Listener); + } + Ok(Async::NotReady) => { + self.negotiating_in.push(in_progress); + } + // TODO: return a diagnostic event? + Err(_err) => {} + } + } + + // Continue negotiation of newly-opened substreams. + // We remove each element from `negotiating_out` one by one and add them back if not ready. + for n in (0..self.negotiating_out.len()).rev() { + let (upgr_info, mut in_progress) = self.negotiating_out.swap_remove(n); + match in_progress.poll() { + Ok(Async::Ready(upgrade)) => { + let endpoint = NodeHandlerEndpoint::Dialer(upgr_info); + self.handler.inject_fully_negotiated(upgrade, endpoint); + } + Ok(Async::NotReady) => { + self.negotiating_out.push((upgr_info, in_progress)); + } + Err(err) => { + let msg = format!("Error while upgrading: {:?}", err); + let err = io::Error::new(io::ErrorKind::Other, msg); + self.handler.inject_dial_upgrade_error(upgr_info, err); + } + } + } + + // Poll the handler at the end so that we see the consequences of the method calls on + // `self.handler`. + match self.handler.poll()? { + Async::Ready(Some(ProtocolsHandlerEvent::Custom(event))) => { + return Ok(Async::Ready(Some(NodeHandlerEvent::Custom(event)))); + } + Async::Ready(Some(ProtocolsHandlerEvent::OutboundSubstreamRequest { + upgrade, + info, + })) => { + let id = self.unique_dial_upgrade_id; + self.unique_dial_upgrade_id += 1; + self.queued_dial_upgrades.push((id, upgrade)); + return Ok(Async::Ready(Some( + NodeHandlerEvent::OutboundSubstreamRequest((id, info)), + ))); + } + Async::Ready(None) => return Ok(Async::Ready(None)), + Async::NotReady => (), + }; + + Ok(Async::NotReady) + } +} From 9d9121719308491358ae6e4b0be83cdffa992985 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Fri, 2 Nov 2018 10:06:59 +0100 Subject: [PATCH 07/20] Add a PeriodicIdentification protocol handler (#579) * Add ProtocolsHandler trait * Reexport symbols * Add a note about shutting down * Add a PeriodicIdentification protocol handler --- protocols/identify/src/lib.rs | 2 + protocols/identify/src/periodic_id_handler.rs | 180 ++++++++++++++++++ 2 files changed, 182 insertions(+) create mode 100644 protocols/identify/src/periodic_id_handler.rs diff --git a/protocols/identify/src/lib.rs b/protocols/identify/src/lib.rs index 503bb55cade..26f7a1ccd58 100644 --- a/protocols/identify/src/lib.rs +++ b/protocols/identify/src/lib.rs @@ -81,8 +81,10 @@ extern crate tokio_timer; extern crate unsigned_varint; extern crate void; +pub use self::periodic_id_handler::{PeriodicIdentification, PeriodicIdentificationEvent}; pub use self::protocol::{IdentifyInfo, IdentifyOutput}; pub use self::protocol::{IdentifyProtocolConfig, IdentifySender}; +mod periodic_id_handler; mod protocol; mod structs_proto; diff --git a/protocols/identify/src/periodic_id_handler.rs b/protocols/identify/src/periodic_id_handler.rs new file mode 100644 index 00000000000..cd9e6e602a5 --- /dev/null +++ b/protocols/identify/src/periodic_id_handler.rs @@ -0,0 +1,180 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use futures::prelude::*; +use libp2p_core::nodes::handled_node::NodeHandlerEndpoint; +use libp2p_core::nodes::protocols_handler::{ProtocolsHandler, ProtocolsHandlerEvent}; +use libp2p_core::upgrade::{self, toggleable::Toggleable}; +use libp2p_core::{ConnectionUpgrade, Multiaddr}; +use std::io; +use std::marker::PhantomData; +use std::time::{Duration, Instant}; +use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_timer::Delay; +use void::Void; +use {IdentifyInfo, IdentifyOutput, IdentifyProtocolConfig}; + +/// Delay between the moment we connect and the first time we identify. +const DELAY_TO_FIRST_ID: Duration = Duration::from_millis(500); +/// After an identification succeeded, wait this long before the next time. +const DELAY_TO_NEXT_ID: Duration = Duration::from_secs(5 * 60); +/// After we failed to identify the remote, try again after the given delay. +const TRY_AGAIN_ON_ERR: Duration = Duration::from_secs(60 * 60); + +/// Protocol handler that identifies the remote at a regular period. +pub struct PeriodicIdentification { + /// Configuration for the protocol. + config: Toggleable, + + /// If `Some`, we successfully generated an `PeriodicIdentificationEvent` and we will produce + /// it the next time `poll()` is invoked. + pending_result: Option, + + /// Future that fires when we need to identify the node again. If `None`, means that we should + /// shut down. + next_id: Option, + + /// Marker for strong typing. + marker: PhantomData, +} + +/// Event produced by the periodic identifier. +#[derive(Debug)] +pub enum PeriodicIdentificationEvent { + /// We obtained identification information from the remote + Identified { + /// Information of the remote. + info: IdentifyInfo, + /// Address the remote observes us as. + observed_addr: Multiaddr, + }, + + /// Failed to identify the remote. + IdentificationError(io::Error), +} + +impl PeriodicIdentification { + /// Builds a new `PeriodicIdentification`. + #[inline] + pub fn new() -> Self { + PeriodicIdentification { + config: upgrade::toggleable(IdentifyProtocolConfig), + pending_result: None, + next_id: Some(Delay::new(Instant::now() + DELAY_TO_FIRST_ID)), + marker: PhantomData, + } + } +} + +impl ProtocolsHandler for PeriodicIdentification +where + TSubstream: AsyncRead + AsyncWrite + Send + Sync + 'static, // TODO: remove useless bounds +{ + type InEvent = Void; + type OutEvent = PeriodicIdentificationEvent; + type Substream = TSubstream; + type Protocol = Toggleable; + type OutboundOpenInfo = (); + + #[inline] + fn listen_protocol(&self) -> Self::Protocol { + let mut upgrade = self.config.clone(); + upgrade.disable(); + upgrade + } + + fn inject_fully_negotiated( + &mut self, + protocol: >::Output, + _endpoint: NodeHandlerEndpoint, + ) { + match protocol { + IdentifyOutput::RemoteInfo { + info, + observed_addr, + } => { + self.pending_result = Some(PeriodicIdentificationEvent::Identified { + info, + observed_addr, + }); + } + IdentifyOutput::Sender { .. } => unreachable!( + "Sender can only be produced if we listen for the identify \ + protocol ; however we disable it in listen_protocol" + ), + } + } + + #[inline] + fn inject_event(&mut self, _: &Self::InEvent) {} + + #[inline] + fn inject_inbound_closed(&mut self) {} + + #[inline] + fn inject_dial_upgrade_error(&mut self, _: Self::OutboundOpenInfo, err: io::Error) { + self.pending_result = Some(PeriodicIdentificationEvent::IdentificationError(err)); + if let Some(ref mut next_id) = self.next_id { + next_id.reset(Instant::now() + TRY_AGAIN_ON_ERR); + } + } + + #[inline] + fn shutdown(&mut self) { + self.next_id = None; + } + + fn poll( + &mut self, + ) -> Poll< + Option< + ProtocolsHandlerEvent< + Self::Protocol, + Self::OutboundOpenInfo, + PeriodicIdentificationEvent, + >, + >, + io::Error, + > { + if let Some(pending_result) = self.pending_result.take() { + return Ok(Async::Ready(Some(ProtocolsHandlerEvent::Custom( + pending_result, + )))); + } + + let next_id = match self.next_id { + Some(ref mut nid) => nid, + None => return Ok(Async::Ready(None)), + }; + + // Poll the future that fires when we need to identify the node again. + match next_id.poll() { + Ok(Async::NotReady) => Ok(Async::NotReady), + Ok(Async::Ready(())) => { + next_id.reset(Instant::now() + DELAY_TO_NEXT_ID); + let mut upgrade = self.config.clone(); + upgrade.enable(); + let ev = ProtocolsHandlerEvent::OutboundSubstreamRequest { upgrade, info: () }; + Ok(Async::Ready(Some(ev))) + } + Err(err) => Err(io::Error::new(io::ErrorKind::Other, err)), + } + } +} From 9249e31218bbc680d82f9df38cad4909f8bdc0a0 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Fri, 2 Nov 2018 13:15:17 +0100 Subject: [PATCH 08/20] Some minor fixes reported by clippy (#600) --- core/src/nodes/collection.rs | 4 ++-- core/src/nodes/handled_node_tasks.rs | 6 +++--- core/src/nodes/raw_swarm.rs | 2 +- core/src/transport/and_then.rs | 12 ++++++------ 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/src/nodes/collection.rs b/core/src/nodes/collection.rs index 97c880ebf7f..aadd25a4732 100644 --- a/core/src/nodes/collection.rs +++ b/core/src/nodes/collection.rs @@ -297,8 +297,8 @@ impl CollectionStream Err(()), Entry::Occupied(entry) => { match entry.get() { - &TaskState::Connected(_) => return Err(()), - &TaskState::Pending => (), + TaskState::Connected(_) => return Err(()), + TaskState::Pending => (), }; entry.remove(); diff --git a/core/src/nodes/handled_node_tasks.rs b/core/src/nodes/handled_node_tasks.rs index 9cd7e69ffcd..272ded2a96e 100644 --- a/core/src/nodes/handled_node_tasks.rs +++ b/core/src/nodes/handled_node_tasks.rs @@ -183,7 +183,7 @@ impl HandledNodesTasks Option> { - match self.tasks.entry(id.clone()) { + match self.tasks.entry(id) { Entry::Occupied(inner) => Some(Task { inner }), Entry::Vacant(_) => None, } @@ -378,7 +378,7 @@ where for event in events_buffer { node.inject_event(event); } - if let Err(_) = self.events_tx.unbounded_send((event, self.id)) { + if self.events_tx.unbounded_send((event, self.id)).is_err() { node.shutdown(); } self.inner = NodeTaskInner::Node(node); @@ -425,7 +425,7 @@ where }, Ok(Async::Ready(Some(event))) => { let event = InToExtMessage::NodeEvent(event); - if let Err(_) = self.events_tx.unbounded_send((event, self.id)) { + if self.events_tx.unbounded_send((event, self.id)).is_err() { node.shutdown(); } } diff --git a/core/src/nodes/raw_swarm.rs b/core/src/nodes/raw_swarm.rs index 6498c758426..3e2261a3e68 100644 --- a/core/src/nodes/raw_swarm.rs +++ b/core/src/nodes/raw_swarm.rs @@ -1036,7 +1036,7 @@ impl<'a, TInEvent, TOutEvent, THandler> PeerPendingConnect<'a, TInEvent, TOutEve #[inline] pub fn interrupt(self) { let attempt = self.attempt.remove(); - if let Err(_) = self.active_nodes.interrupt(attempt.id) { + if self.active_nodes.interrupt(attempt.id).is_err() { // TODO: improve proof or remove; this is too complicated right now panic!("We retreived this attempt.id from out_reach_attempts. We insert in \ out_reach_attempts only at the same time as we call add_reach_attempt. \ diff --git a/core/src/transport/and_then.rs b/core/src/transport/and_then.rs index 461448a5d09..d6bc2517685 100644 --- a/core/src/transport/and_then.rs +++ b/core/src/transport/and_then.rs @@ -57,10 +57,10 @@ where let (listening_stream, new_addr) = match self.transport.listen_on(addr) { Ok((l, new_addr)) => (l, new_addr), - Err((trans, addr)) => { + Err((transport, addr)) => { let builder = AndThen { - transport: trans, - upgrade: upgrade, + transport, + upgrade, }; return Err((builder, addr)); @@ -96,10 +96,10 @@ where let dialed_fut = match self.transport.dial(addr.clone()) { Ok(f) => f, - Err((trans, addr)) => { + Err((transport, addr)) => { let builder = AndThen { - transport: trans, - upgrade: upgrade, + transport, + upgrade, }; return Err((builder, addr)); From 437a8c0fde041d39717ccd1ec6c7cc0a523593e3 Mon Sep 17 00:00:00 2001 From: David Date: Fri, 2 Nov 2018 14:12:21 +0100 Subject: [PATCH 09/20] Tests for HandledNode (#546) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Add unit tests for core::nodes::NodeStream * Move DummyMuxer to core/tests * Address grumbles * Impl Debug for SubstreamRef

* Add test for poll() * Don't need to open a substream * pretty printer test * More tests for NodeStream poll() * ListenerStream unit tests: transport() and listeners() * Tests for nodes/listeners.rs * Add a few tests to help illustrate the "drowning" behaviour of busy listeners * Tests for HandledNode * Address grumbles * Remove non-project specific stuff * Address grumbles * Prefer freestanding function * Untangle the code for old shutdown test from the new tests Add HandlerState and use it in TestBuilder Shorter test names * WIP – tests pass * Use a newtype to lighten up the function signatures a bit Test NotReady case * Cleanup Event enum Track events as they reach the Handler Describe complex test logic * Assert on the event trace * More tests for poll() * Switch to using usize as the OutboundOpenInfo so we can assert on event contents More tests for poll() * whitespace * Move Handler related code to dummy_handler * Fixes broken test after upstream changes * Clarify the behaviour of is_shutting_down * Fix broken test * Fix tests after recent changes on master * no tabs * whitespace * rustfmt * Add public HandledNode.handler() method that returns a ref to the NodeHandler Don't use private members in tests * Add HandledNode.handler_mut that returns a mutable ref to the NodeHandler * Remove debugging stmts * Fix parse error --- core/src/nodes/handled_node.rs | 334 +++++++++++++++++++++++++++++--- core/src/nodes/node.rs | 1 - core/src/tests/dummy_handler.rs | 105 ++++++++++ core/src/tests/dummy_muxer.rs | 8 +- core/src/tests/mod.rs | 4 + 5 files changed, 417 insertions(+), 35 deletions(-) create mode 100644 core/src/tests/dummy_handler.rs diff --git a/core/src/nodes/handled_node.rs b/core/src/nodes/handled_node.rs index c341d750c4d..2a7f28e307a 100644 --- a/core/src/nodes/handled_node.rs +++ b/core/src/nodes/handled_node.rs @@ -65,7 +65,7 @@ pub trait NodeHandler { /// Injects an event coming from the outside into the handler. fn inject_event(&mut self, event: Self::InEvent); - /// Indicates that the node that it should shut down. After that, it is expected that `poll()` + /// Indicates to the node that it should shut down. After that, it is expected that `poll()` /// returns `Ready(None)` as soon as possible. /// /// This method allows an implementation to perform a graceful shutdown of the substreams, and @@ -176,6 +176,16 @@ where } } + /// Returns a reference to the `NodeHandler` + pub fn handler(&self) -> &THandler{ + &self.handler + } + + /// Returns a mutable reference to the `NodeHandler` + pub fn handler_mut(&mut self) -> &mut THandler{ + &mut self.handler + } + /// Injects an event to the handler. #[inline] pub fn inject_event(&mut self, event: THandler::InEvent) { @@ -286,7 +296,6 @@ where } } } - Ok(Async::NotReady) } } @@ -294,44 +303,81 @@ where #[cfg(test)] mod tests { use super::*; - use muxing::{StreamMuxer, Shutdown}; - use std::marker::PhantomData; use tokio::runtime::current_thread; + use tests::dummy_muxer::{DummyMuxer, DummyConnectionState}; + use tests::dummy_handler::{Handler, HandlerState, Event}; + use std::marker::PhantomData; + + // Concrete `HandledNode` + type TestHandledNode = HandledNode; - // TODO: move somewhere? this could be useful as a dummy - struct InstaCloseMuxer; - impl StreamMuxer for InstaCloseMuxer { - type Substream = (); - type OutboundSubstream = (); - fn poll_inbound(&self) -> Poll, IoError> { Ok(Async::Ready(None)) } - fn open_outbound(&self) -> Self::OutboundSubstream { () } - fn poll_outbound(&self, _: &mut Self::OutboundSubstream) -> Poll, IoError> { Ok(Async::Ready(None)) } - fn destroy_outbound(&self, _: Self::OutboundSubstream) {} - fn read_substream(&self, _: &mut Self::Substream, _: &mut [u8]) -> Poll { panic!() } - fn write_substream(&self, _: &mut Self::Substream, _: &[u8]) -> Poll { panic!() } - fn flush_substream(&self, _: &mut Self::Substream) -> Poll<(), IoError> { panic!() } - fn shutdown_substream(&self, _: &mut Self::Substream, _: Shutdown) -> Poll<(), IoError> { panic!() } - fn destroy_substream(&self, _: Self::Substream) { panic!() } - fn shutdown(&self, _: Shutdown) -> Poll<(), IoError> { Ok(Async::Ready(())) } - fn flush_all(&self) -> Poll<(), IoError> { Ok(Async::Ready(())) } + struct TestBuilder { + muxer: DummyMuxer, + handler: Handler, + want_open_substream: bool, + substream_user_data: usize, + } + + impl TestBuilder { + fn new() -> Self { + TestBuilder { + muxer: DummyMuxer::new(), + handler: Handler::default(), + want_open_substream: false, + substream_user_data: 0, + } + } + + fn with_muxer_inbound_state(&mut self, state: DummyConnectionState) -> &mut Self { + self.muxer.set_inbound_connection_state(state); + self + } + + fn with_muxer_outbound_state(&mut self, state: DummyConnectionState) -> &mut Self { + self.muxer.set_outbound_connection_state(state); + self + } + + fn with_handler_state(&mut self, state: HandlerState) -> &mut Self { + self.handler.state = Some(state); + self + } + + fn with_open_substream(&mut self, user_data: usize) -> &mut Self { + self.want_open_substream = true; + self.substream_user_data = user_data; + self + } + + fn handled_node(&mut self) -> TestHandledNode { + let mut h = HandledNode::new(self.muxer.clone(), self.handler.clone()); + if self.want_open_substream { + h.node.get_mut().open_substream(self.substream_user_data).expect("open substream should work"); + } + h + } + } + + // Set the state of the `Handler` after `inject_outbound_closed` is called + fn set_next_handler_outbound_state( handled_node: &mut TestHandledNode, next_state: HandlerState) { + handled_node.handler.next_outbound_state = Some(next_state); } #[test] fn proper_shutdown() { - // Test that `shutdown()` is properly called on the handler once a node stops. - struct Handler { + struct ShutdownHandler { did_substream_attempt: bool, inbound_closed: bool, substream_attempt_cancelled: bool, shutdown_called: bool, - marker: PhantomData, - }; - impl NodeHandler for Handler { + marker: PhantomData + } + impl NodeHandler for ShutdownHandler { type InEvent = (); type OutEvent = (); type Substream = T; type OutboundOpenInfo = (); - fn inject_substream(&mut self, _: T, _: NodeHandlerEndpoint<()>) { panic!() } + fn inject_substream(&mut self, _: Self::Substream, _: NodeHandlerEndpoint) { panic!() } fn inject_inbound_closed(&mut self) { assert!(!self.inbound_closed); self.inbound_closed = true; @@ -357,13 +403,20 @@ mod tests { } } } - impl Drop for Handler { + + impl Drop for ShutdownHandler { fn drop(&mut self) { - assert!(self.shutdown_called); + if self.did_substream_attempt { + assert!(self.shutdown_called); + } } } - let handled = HandledNode::new(InstaCloseMuxer, Handler { + // Test that `shutdown()` is properly called on the handler once a node stops. + let mut muxer = DummyMuxer::new(); + muxer.set_inbound_connection_state(DummyConnectionState::Closed); + muxer.set_outbound_connection_state(DummyConnectionState::Closed); + let handled = HandledNode::new(muxer, ShutdownHandler { did_substream_attempt: false, inbound_closed: false, substream_attempt_cancelled: false, @@ -373,4 +426,227 @@ mod tests { current_thread::Runtime::new().unwrap().block_on(handled.for_each(|_| Ok(()))).unwrap(); } + + #[test] + fn can_inject_event() { + let mut handled = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Closed) + .handled_node(); + + let event = Event::Custom("banana"); + handled.inject_event(event.clone()); + assert_eq!(handled.handler().events, vec![event]); + } + + #[test] + fn knows_if_inbound_is_closed() { + let mut handled = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Closed) + .with_handler_state(HandlerState::Ready(None)) // or we get into an infinite loop + .handled_node(); + handled.poll().expect("poll failed"); + assert!(!handled.is_inbound_open()) + } + + #[test] + fn knows_if_outbound_is_closed() { + let mut handled = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Pending) + .with_muxer_outbound_state(DummyConnectionState::Closed) + .with_handler_state(HandlerState::Ready(None)) // or we get into an infinite loop + .with_open_substream(987) // without at least one substream we do not poll_outbound so we never get the event + .handled_node(); + + handled.poll().expect("poll failed"); + assert!(!handled.is_outbound_open()); + } + + #[test] + fn is_shutting_down_is_true_when_called_shutdown_on_the_handled_node() { + let mut handled = TestBuilder::new() + .with_handler_state(HandlerState::Ready(None)) // Stop the loop towards the end of the first run + .handled_node(); + assert!(!handled.is_shutting_down()); + handled.poll().expect("poll should work"); + handled.shutdown(); + assert!(handled.is_shutting_down()); + } + + #[test] + fn is_shutting_down_is_true_when_in_and_outbounds_are_closed() { + let mut handled = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Closed) + .with_muxer_outbound_state(DummyConnectionState::Closed) + .with_open_substream(123) // avoid infinite loop + .handled_node(); + + handled.poll().expect("poll should work"); + + // Shutting down (in- and outbound are closed, and the handler is shutdown) + assert!(handled.is_shutting_down()); + } + + #[test] + fn is_shutting_down_is_true_when_handler_is_gone() { + // when in-/outbound NodeStreams are open or Async::Ready(None) we reach the handlers `poll()` and initiate shutdown. + let mut handled = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Pending) + .with_muxer_outbound_state(DummyConnectionState::Pending) + .with_handler_state(HandlerState::Ready(None)) // avoid infinite loop + .handled_node(); + + handled.poll().expect("poll should work"); + + assert!(handled.is_shutting_down()); + } + + #[test] + fn is_shutting_down_is_true_when_handler_is_gone_even_if_in_and_outbounds_are_open() { + let mut handled = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Opened) + .with_muxer_outbound_state(DummyConnectionState::Opened) + .with_open_substream(123) + .with_handler_state(HandlerState::Ready(None)) + .handled_node(); + + handled.poll().expect("poll should work"); + + assert!(handled.is_shutting_down()); + } + + #[test] + fn poll_with_unready_node_stream_polls_handler() { + let mut handled = TestBuilder::new() + // make NodeStream return NotReady + .with_muxer_inbound_state(DummyConnectionState::Pending) + // make Handler return return Ready(None) so we break the infinite loop + .with_handler_state(HandlerState::Ready(None)) + .handled_node(); + + assert_matches!(handled.poll(), Ok(Async::Ready(None))); + } + + #[test] + fn poll_with_unready_node_stream_and_handler_emits_custom_event() { + let expected_event = Some(NodeHandlerEvent::Custom(Event::Custom("pineapple"))); + let mut handled = TestBuilder::new() + // make NodeStream return NotReady + .with_muxer_inbound_state(DummyConnectionState::Pending) + // make Handler return return Ready(Some(…)) + .with_handler_state(HandlerState::Ready(expected_event)) + .handled_node(); + + assert_matches!(handled.poll(), Ok(Async::Ready(Some(event))) => { + assert_matches!(event, Event::Custom("pineapple")) + }); + } + + #[test] + fn handler_emits_outbound_closed_when_opening_new_substream_on_closed_node() { + let open_event = Some(NodeHandlerEvent::OutboundSubstreamRequest(456)); + let mut handled = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Pending) + .with_muxer_outbound_state(DummyConnectionState::Closed) + .with_handler_state(HandlerState::Ready(open_event)) + .handled_node(); + + set_next_handler_outbound_state( + &mut handled, + HandlerState::Ready(Some(NodeHandlerEvent::Custom(Event::Custom("pear")))) + ); + handled.poll().expect("poll works"); + assert_eq!(handled.handler().events, vec![Event::OutboundClosed]); + } + + #[test] + fn poll_returns_not_ready_when_node_stream_and_handler_is_not_ready() { + let mut handled = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Closed) + .with_muxer_outbound_state(DummyConnectionState::Closed) + .with_open_substream(12) + .with_handler_state(HandlerState::NotReady) + .handled_node(); + + // Under the hood, this is what happens when calling `poll()`: + // - we reach `node.poll_inbound()` and because the connection is + // closed, `inbound_finished` is set to true. + // - an Async::Ready(NodeEvent::InboundClosed) is yielded (also calls + // `inject_inbound_close`, but that's irrelevant here) + // - back in `poll()` we cal `handler.poll()` which does nothing because + // `HandlerState` is `NotReady`: loop continues + // - polls the node again which now skips the inbound block because + // `inbound_finished` is true. + // - Now `poll_outbound()` is called which returns `Async::Ready(None)` + // and sets `outbound_finished` to true. …calls destroy_outbound and + // yields Ready(OutboundClosed) …so the HandledNode calls + // `inject_outbound_closed`. + // - Now we have `inbound_finished` and `outbound_finished` set (and no + // more outbound substreams). + // - Next we poll the handler again which again does nothing because + // HandlerState is NotReady (and the node is still there) + // - HandledNode polls the node again: we skip inbound and there are no + // more outbound substreams so we skip that too; the addr is now + // Resolved so that part is skipped too + // - We reach the last section and the NodeStream yields Async::Ready(None) + // - Back in HandledNode the Async::Ready(None) triggers a shutdown + // – …and causes the Handler to yield Async::Ready(None) + // – which in turn makes the HandledNode to yield Async::Ready(None) as well + assert_matches!(handled.poll(), Ok(Async::Ready(None))); + assert_eq!(handled.handler().events, vec![ + Event::InboundClosed, Event::OutboundClosed + ]); + } + + #[test] + fn poll_yields_inbound_closed_event() { + let mut h = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Closed) + .with_handler_state(HandlerState::Err) // stop the loop + .handled_node(); + + assert_eq!(h.handler().events, vec![]); + let _ = h.poll(); + assert_eq!(h.handler().events, vec![Event::InboundClosed]); + } + + #[test] + fn poll_yields_outbound_closed_event() { + let mut h = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Pending) + .with_open_substream(32) + .with_muxer_outbound_state(DummyConnectionState::Closed) + .with_handler_state(HandlerState::Err) // stop the loop + .handled_node(); + + assert_eq!(h.handler().events, vec![]); + let _ = h.poll(); + assert_eq!(h.handler().events, vec![Event::OutboundClosed]); + } + + #[test] + fn poll_yields_outbound_substream() { + let mut h = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Pending) + .with_muxer_outbound_state(DummyConnectionState::Opened) + .with_open_substream(1) + .with_handler_state(HandlerState::Err) // stop the loop + .handled_node(); + + assert_eq!(h.handler().events, vec![]); + let _ = h.poll(); + assert_eq!(h.handler().events, vec![Event::Substream(Some(1))]); + } + + #[test] + fn poll_yields_inbound_substream() { + let mut h = TestBuilder::new() + .with_muxer_inbound_state(DummyConnectionState::Opened) + .with_muxer_outbound_state(DummyConnectionState::Pending) + .with_handler_state(HandlerState::Err) // stop the loop + .handled_node(); + + assert_eq!(h.handler().events, vec![]); + let _ = h.poll(); + assert_eq!(h.handler().events, vec![Event::Substream(None)]); + } } diff --git a/core/src/nodes/node.rs b/core/src/nodes/node.rs index e072e57d068..d35ef2a6ffd 100644 --- a/core/src/nodes/node.rs +++ b/core/src/nodes/node.rs @@ -306,7 +306,6 @@ where } } } - // Closing the node if there's no way we can do anything more. if self.inbound_state == StreamState::Closed && self.outbound_state == StreamState::Closed diff --git a/core/src/tests/dummy_handler.rs b/core/src/tests/dummy_handler.rs new file mode 100644 index 00000000000..8b9bbbdada2 --- /dev/null +++ b/core/src/tests/dummy_handler.rs @@ -0,0 +1,105 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Concrete `NodeHandler` implementation and assorted testing types + +use std::io::{self, Error as IoError}; + +use super::dummy_muxer::DummyMuxer; +use futures::prelude::*; +use muxing::SubstreamRef; +use nodes::handled_node::{NodeHandler, NodeHandlerEndpoint, NodeHandlerEvent}; +use std::sync::Arc; + +#[derive(Debug, PartialEq, Clone)] +pub(crate) struct Handler { + pub events: Vec, + pub state: Option, + pub next_outbound_state: Option, +} + +impl Default for Handler { + fn default() -> Self { + Handler { + events: Vec::new(), + state: None, + next_outbound_state: None, + } + } +} + +#[derive(Debug, PartialEq, Clone)] +pub(crate) enum HandlerState { + NotReady, + Ready(Option>), + Err, +} + +#[derive(Debug, PartialEq, Clone)] +pub(crate) enum Event { + Custom(&'static str), + Substream(Option), + OutboundClosed, + InboundClosed, +} + +impl NodeHandler for Handler { + type InEvent = Event; + type OutEvent = Event; + type OutboundOpenInfo = usize; + type Substream = SubstreamRef>; + fn inject_substream( + &mut self, + _: Self::Substream, + endpoint: NodeHandlerEndpoint, + ) { + let user_data = match endpoint { + NodeHandlerEndpoint::Dialer(user_data) => Some(user_data), + NodeHandlerEndpoint::Listener => None, + }; + self.events.push(Event::Substream(user_data)); + } + fn inject_inbound_closed(&mut self) { + self.events.push(Event::InboundClosed); + } + fn inject_outbound_closed(&mut self, _: usize) { + self.events.push(Event::OutboundClosed); + if let Some(ref state) = self.next_outbound_state { + self.state = Some(state.clone()); + } + } + fn inject_event(&mut self, inevent: Self::InEvent) { + self.events.push(inevent) + } + fn shutdown(&mut self) { + self.state = Some(HandlerState::Ready(None)); + } + fn poll(&mut self) -> Poll>, IoError> { + match self.state { + Some(ref state) => match state { + HandlerState::NotReady => Ok(Async::NotReady), + HandlerState::Ready(None) => Ok(Async::Ready(None)), + HandlerState::Ready(Some(event)) => Ok(Async::Ready(Some(event.clone()))), + HandlerState::Err => Err(io::Error::new(io::ErrorKind::Other, "oh noes")), + }, + None => Ok(Async::NotReady), + } + } +} diff --git a/core/src/tests/dummy_muxer.rs b/core/src/tests/dummy_muxer.rs index e91620b73b5..bd7832d3edf 100644 --- a/core/src/tests/dummy_muxer.rs +++ b/core/src/tests/dummy_muxer.rs @@ -22,8 +22,6 @@ //! version of the trait along with a way to setup the muxer to behave in the //! desired way when testing other components. -extern crate futures; - use std::io::Error as IoError; use muxing::{StreamMuxer, Shutdown}; use futures::prelude::*; @@ -36,19 +34,19 @@ pub struct DummyOutboundSubstream {} /// Control the muxer state by setting the "connection" state as to set up a mock /// muxer for higher level components. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Clone)] pub enum DummyConnectionState { Pending, // use this to trigger the Async::NotReady code path Closed, // use this to trigger the Async::Ready(None) code path Opened, // use this to trigger the Async::Ready(Some(_)) code path } -#[derive(Debug)] +#[derive(Debug, Clone)] struct DummyConnection { state: DummyConnectionState } /// `DummyMuxer` implements `StreamMuxer` and methods to control its behavior when used in tests -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct DummyMuxer{ in_connection: DummyConnection, out_connection: DummyConnection, diff --git a/core/src/tests/mod.rs b/core/src/tests/mod.rs index 97f3b6fea0c..5c86aec1c1d 100644 --- a/core/src/tests/mod.rs +++ b/core/src/tests/mod.rs @@ -20,5 +20,9 @@ #[cfg(test)] pub(crate) mod dummy_muxer; + #[cfg(test)] pub(crate) mod dummy_transport; + +#[cfg(test)] +pub(crate) mod dummy_handler; From 4225d2631b83af56e9b075018702a0d4c1f024e6 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Fri, 2 Nov 2018 14:23:38 +0100 Subject: [PATCH 10/20] Add a IdentifyTransport (#569) * Add a IdentifyTransport * Retreiver -> Retriever * Move the muxer in the IdRetrieverState --- protocols/identify/src/id_transport.rs | 229 +++++++++++++++++++++++++ protocols/identify/src/lib.rs | 2 + 2 files changed, 231 insertions(+) create mode 100644 protocols/identify/src/id_transport.rs diff --git a/protocols/identify/src/id_transport.rs b/protocols/identify/src/id_transport.rs new file mode 100644 index 00000000000..b83318ca4ae --- /dev/null +++ b/protocols/identify/src/id_transport.rs @@ -0,0 +1,229 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +//! Contains the `IdentifyTransport` type. + +use futures::prelude::*; +use libp2p_core::{Endpoint, Multiaddr, PeerId, PublicKey, Transport, muxing, upgrade::apply}; +use protocol::{IdentifyOutput, IdentifyProtocolConfig}; +use std::io::{Error as IoError, ErrorKind as IoErrorKind}; +use std::mem; +use std::sync::Arc; + +/// Wraps around an implementation of `Transport` that yields a muxer. Will use the muxer to +/// open a substream with the remote and retreive its peer id. Then yields a +/// `(PeerId, impl StreamMuxer)`. +/// +/// This transport can be used if you don't use any encryption layer, or if you want to make +/// encryption optional, in which case you have no other way to know the `PeerId` of the remote +/// than to ask for it. +/// +/// > **Note**: If you use this transport, keep in mind that the `PeerId` returned by the remote +/// > can be anything and shouldn't necessarily be trusted. +#[derive(Debug, Clone)] +pub struct IdentifyTransport { + /// The underlying transport we wrap around. + transport: TTrans, +} + +impl IdentifyTransport { + /// Creates an `IdentifyTransport` that wraps around the given transport. + #[inline] + pub fn new(transport: TTrans) -> Self { + IdentifyTransport { + transport, + } + } +} + +// TODO: don't use boxes +impl Transport for IdentifyTransport +where + TTrans: Transport, + TMuxer: muxing::StreamMuxer + Send + Sync + 'static, // TODO: remove unnecessary bounds + TMuxer::Substream: Send + Sync + 'static, // TODO: remove unnecessary bounds + TMuxer::OutboundSubstream: Send + 'static, // TODO: remove unnecessary bounds + TTrans::Dial: Send + Sync + 'static, + TTrans::Listener: Send + 'static, + TTrans::ListenerUpgrade: Send + 'static, +{ + type Output = (PeerId, TMuxer); + type Listener = Box + Send>; + type ListenerUpgrade = Box + Send>; + type Dial = Box + Send>; + + #[inline] + fn listen_on(self, addr: Multiaddr) -> Result<(Self::Listener, Multiaddr), (Self, Multiaddr)> { + let (listener, new_addr) = match self.transport.listen_on(addr) { + Ok((l, a)) => (l, a), + Err((inner, addr)) => { + let id = IdentifyTransport { + transport: inner, + }; + return Err((id, addr)); + } + }; + + let listener = listener + .map(move |(upgrade, remote_addr)| { + let upgr = upgrade + .and_then(move |muxer| { + IdRetriever::new(muxer, IdentifyProtocolConfig, Endpoint::Listener) + }); + (Box::new(upgr) as Box + Send>, remote_addr) + }); + + Ok((Box::new(listener) as Box<_>, new_addr)) + } + + #[inline] + fn dial(self, addr: Multiaddr) -> Result { + // We dial a first time the node. + let dial = match self.transport.dial(addr.clone()) { + Ok(d) => d, + Err((transport, addr)) => { + let id = IdentifyTransport { + transport, + }; + return Err((id, addr)); + } + }; + + let dial = dial.and_then(move |muxer| { + IdRetriever::new(muxer, IdentifyProtocolConfig, Endpoint::Dialer) + }); + + Ok(Box::new(dial) as Box<_>) + } + + #[inline] + fn nat_traversal(&self, a: &Multiaddr, b: &Multiaddr) -> Option { + self.transport.nat_traversal(a, b) + } +} + +/// Implementation of `Future` that asks the remote of its `PeerId`. +// TODO: remove unneeded bounds +struct IdRetriever +where TMuxer: muxing::StreamMuxer + Send + Sync + 'static, + TMuxer::Substream: Send, +{ + /// Internal state. + state: IdRetrieverState, + /// Whether we're dialing or listening. + endpoint: Endpoint, +} + +enum IdRetrieverState +where TMuxer: muxing::StreamMuxer + Send + Sync + 'static, + TMuxer::Substream: Send, +{ + /// We are in the process of opening a substream with the remote. + OpeningSubstream(Arc, muxing::OutboundSubstreamRefWrapFuture>, IdentifyProtocolConfig), + /// We opened the substream and are currently negotiating the identify protocol. + NegotiatingIdentify(Arc, apply::UpgradeApplyFuture>, IdentifyProtocolConfig>), + /// We retreived the remote's public key and are ready to yield it when polled again. + Finishing(Arc, PublicKey), + /// Something bad happend, or the `Future` is finished, and shouldn't be polled again. + Poisoned, +} + +impl IdRetriever +where TMuxer: muxing::StreamMuxer + Send + Sync + 'static, + TMuxer::Substream: Send, +{ + /// Creates a new `IdRetriever` ready to be polled. + fn new(muxer: TMuxer, config: IdentifyProtocolConfig, endpoint: Endpoint) -> Self { + let muxer = Arc::new(muxer); + let opening = muxing::outbound_from_ref_and_wrap(muxer.clone()); + + IdRetriever { + state: IdRetrieverState::OpeningSubstream(muxer, opening, config), + endpoint, + } + } +} + +impl Future for IdRetriever +where TMuxer: muxing::StreamMuxer + Send + Sync + 'static, + TMuxer::Substream: Send, +{ + type Item = (PeerId, TMuxer); + type Error = IoError; + + fn poll(&mut self) -> Poll { + // This loop is here so that we can continue polling until we're ready. + loop { + // In order to satisfy the borrow checker, we extract the state and temporarily put + // `Poisoned` instead. + match mem::replace(&mut self.state, IdRetrieverState::Poisoned) { + IdRetrieverState::OpeningSubstream(muxer, mut opening, config) => { + match opening.poll() { + Ok(Async::Ready(Some(substream))) => { + let upgrade = apply::apply(substream, config, self.endpoint); + self.state = IdRetrieverState::NegotiatingIdentify(muxer, upgrade); + }, + Ok(Async::Ready(None)) => { + return Err(IoError::new(IoErrorKind::Other, "remote refused our identify attempt")); + }, + Ok(Async::NotReady) => { + self.state = IdRetrieverState::OpeningSubstream(muxer, opening, config); + return Ok(Async::NotReady); + }, + Err(err) => return Err(err), + } + }, + IdRetrieverState::NegotiatingIdentify(muxer, mut nego) => { + match nego.poll() { + Ok(Async::Ready(IdentifyOutput::RemoteInfo { info, .. })) => { + self.state = IdRetrieverState::Finishing(muxer, info.public_key); + }, + Ok(Async::Ready(IdentifyOutput::Sender { .. })) => { + unreachable!("IdentifyOutput::Sender can never be the output from \ + the dialing side"); + }, + Ok(Async::NotReady) => { + self.state = IdRetrieverState::NegotiatingIdentify(muxer, nego); + return Ok(Async::NotReady); + }, + Err(err) => return Err(err), + } + }, + IdRetrieverState::Finishing(muxer, public_key) => { + // Here is a tricky part: we need to get back the muxer in order to return + // it, but it is in an `Arc`. + let unwrapped = Arc::try_unwrap(muxer).unwrap_or_else(|_| { + panic!("we clone the Arc only to put it into substreams ; once in the \ + Finishing state, no substream or upgrade exists anymore ; \ + therefore there exists only one instance of the Arc ; qed") + }); + + // We leave `Poisoned` as the state when returning. + return Ok(Async::Ready((public_key.into(), unwrapped))); + }, + IdRetrieverState::Poisoned => { + panic!("Future state panicked inside poll() or is finished") + }, + } + } + } +} + +// TODO: write basic working test diff --git a/protocols/identify/src/lib.rs b/protocols/identify/src/lib.rs index 26f7a1ccd58..3684177f71f 100644 --- a/protocols/identify/src/lib.rs +++ b/protocols/identify/src/lib.rs @@ -81,10 +81,12 @@ extern crate tokio_timer; extern crate unsigned_varint; extern crate void; +pub use self::id_transport::IdentifyTransport; pub use self::periodic_id_handler::{PeriodicIdentification, PeriodicIdentificationEvent}; pub use self::protocol::{IdentifyInfo, IdentifyOutput}; pub use self::protocol::{IdentifyProtocolConfig, IdentifySender}; +mod id_transport; mod periodic_id_handler; mod protocol; mod structs_proto; From 0f3ef5ee0a76044371f7a3b00581ef472edb5a8d Mon Sep 17 00:00:00 2001 From: James Ray <16969914+jamesray1@users.noreply.github.com> Date: Sat, 3 Nov 2018 02:40:00 +1100 Subject: [PATCH 11/20] eg. -> e.g.; ie. -> i.e. via repren (#592) * eg. -> e.g.; ie. -> i.e. via repren * se.g. -> seg. --- core/src/lib.rs | 2 +- core/src/transport/mod.rs | 4 ++-- core/src/upgrade/traits.rs | 2 +- misc/multistream-select/src/length_delimited.rs | 2 +- misc/multistream-select/src/lib.rs | 2 +- misc/multistream-select/src/listener_select.rs | 2 +- protocols/identify/src/protocol.rs | 4 ++-- protocols/kad/src/kad_server.rs | 2 +- stores/peerstore/src/lib.rs | 2 +- stores/peerstore/src/peerstore.rs | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/core/src/lib.rs b/core/src/lib.rs index d135f1699b1..4d727bb5ab0 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -27,7 +27,7 @@ //! //! The main trait that this crate provides is `Transport`, which provides the `dial` and //! `listen_on` methods and can be used to dial or listen on a multiaddress. The `swarm` crate -//! itself does not provide any concrete (ie. non-dummy, non-adapter) implementation of this trait. +//! itself does not provide any concrete (i.e. non-dummy, non-adapter) implementation of this trait. //! It is implemented on structs that are provided by external crates, such as `TcpConfig` from //! `tcp-transport`, `UdpConfig`, or `WebsocketConfig` (note: as of the writing of this //! documentation, the last two structs don't exist yet). diff --git a/core/src/transport/mod.rs b/core/src/transport/mod.rs index 7a353f1e524..21c7107c38d 100644 --- a/core/src/transport/mod.rs +++ b/core/src/transport/mod.rs @@ -55,7 +55,7 @@ pub use self::upgrade::UpgradedNode; /// A transport is an object that can be used to produce connections by listening or dialing a /// peer. /// -/// This trait is implemented on concrete transports (eg. TCP, UDP, etc.), but also on wrappers +/// This trait is implemented on concrete transports (e.g. TCP, UDP, etc.), but also on wrappers /// around them. /// /// > **Note**: The methods of this trait use `self` and not `&self` or `&mut self`. In other @@ -74,7 +74,7 @@ pub trait Transport { type Listener: Stream; /// After a connection has been received, we may need to do some asynchronous pre-processing - /// on it (eg. an intermediary protocol negotiation). While this pre-processing takes place, we + /// on it (e.g. an intermediary protocol negotiation). While this pre-processing takes place, we /// want to be able to continue polling on the listener. type ListenerUpgrade: Future; diff --git a/core/src/upgrade/traits.rs b/core/src/upgrade/traits.rs index 16493e4cf1b..7f987b65ec8 100644 --- a/core/src/upgrade/traits.rs +++ b/core/src/upgrade/traits.rs @@ -73,7 +73,7 @@ pub trait ConnectionUpgrade { /// This method is called after protocol negotiation has been performed. /// - /// Because performing the upgrade may not be instantaneous (eg. it may require a handshake), + /// Because performing the upgrade may not be instantaneous (e.g. it may require a handshake), /// this function returns a future instead of the direct output. fn upgrade(self, socket: C, id: Self::UpgradeIdentifier, ty: Endpoint) -> Self::Future; } diff --git a/misc/multistream-select/src/length_delimited.rs b/misc/multistream-select/src/length_delimited.rs index f342f724216..8ac8c7124e3 100644 --- a/misc/multistream-select/src/length_delimited.rs +++ b/misc/multistream-select/src/length_delimited.rs @@ -81,7 +81,7 @@ where /// # Panic /// /// Will panic if called while there is data inside the buffer. **This can only happen if - /// you call `poll()` manually**. Using this struct as it is intended to be used (ie. through + /// you call `poll()` manually**. Using this struct as it is intended to be used (i.e. through /// the modifiers provided by the `futures` crate) will always leave the object in a state in /// which `into_inner()` will not panic. #[inline] diff --git a/misc/multistream-select/src/lib.rs b/misc/multistream-select/src/lib.rs index 102dc37ad91..f68e6b42fcc 100644 --- a/misc/multistream-select/src/lib.rs +++ b/misc/multistream-select/src/lib.rs @@ -28,7 +28,7 @@ //! //! Whenever a new connection or a new multiplexed substream is opened, libp2p uses //! `multistream-select` to negotiate with the remote which protocol to use. After a protocol has -//! been successfully negotiated, the stream (ie. the connection or the multiplexed substream) +//! been successfully negotiated, the stream (i.e. the connection or the multiplexed substream) //! immediately stops using `multistream-select` and starts using the negotiated protocol. //! //! ## Protocol explanation diff --git a/misc/multistream-select/src/listener_select.rs b/misc/multistream-select/src/listener_select.rs index a910ad4ae80..c47c44e22da 100644 --- a/misc/multistream-select/src/listener_select.rs +++ b/misc/multistream-select/src/listener_select.rs @@ -31,7 +31,7 @@ use ProtocolChoiceError; /// Helps selecting a protocol amongst the ones supported. /// /// This function expects a socket and an iterator of the list of supported protocols. The iterator -/// must be clonable (ie. iterable multiple times), because the list may need to be accessed +/// must be clonable (i.e. iterable multiple times), because the list may need to be accessed /// multiple times. /// /// The iterator must produce tuples of the name of the protocol that is advertised to the remote, diff --git a/protocols/identify/src/protocol.rs b/protocols/identify/src/protocol.rs index aed10431b69..f6ec3f20b51 100644 --- a/protocols/identify/src/protocol.rs +++ b/protocols/identify/src/protocol.rs @@ -99,14 +99,14 @@ where pub struct IdentifyInfo { /// Public key of the node. pub public_key: PublicKey, - /// Version of the "global" protocol, eg. `ipfs/1.0.0` or `polkadot/1.0.0`. + /// Version of the "global" protocol, e.g. `ipfs/1.0.0` or `polkadot/1.0.0`. pub protocol_version: String, /// Name and version of the client. Can be thought as similar to the `User-Agent` header /// of HTTP. pub agent_version: String, /// Addresses that the node is listening on. pub listen_addrs: Vec, - /// Protocols supported by the node, eg. `/ipfs/ping/1.0.0`. + /// Protocols supported by the node, e.g. `/ipfs/ping/1.0.0`. pub protocols: Vec, } diff --git a/protocols/kad/src/kad_server.rs b/protocols/kad/src/kad_server.rs index 521bc1a0ee0..b4180502cf2 100644 --- a/protocols/kad/src/kad_server.rs +++ b/protocols/kad/src/kad_server.rs @@ -96,7 +96,7 @@ where pub struct KadConnecController { // In order to send a request, we use this sender to send a tuple. The first element of the // tuple is the message to send to the remote, and the second element is what is used to - // receive the response. If the query doesn't expect a response (eg. `PUT_VALUE`), then the + // receive the response. If the query doesn't expect a response (e.g. `PUT_VALUE`), then the // one-shot sender will be dropped without being used. inner: mpsc::UnboundedSender<(KadMsg, oneshot::Sender)>, } diff --git a/stores/peerstore/src/lib.rs b/stores/peerstore/src/lib.rs index c615f94f31b..34a07874762 100644 --- a/stores/peerstore/src/lib.rs +++ b/stores/peerstore/src/lib.rs @@ -31,7 +31,7 @@ //! - `MemoryPeerstore`: Stores the information in memory. //! //! Note that the peerstore implementations do not consider information inside a peer store to be -//! critical. In case of an error (eg. corrupted file, disk error, etc.) they will prefer to lose +//! critical. In case of an error (e.g. corrupted file, disk error, etc.) they will prefer to lose //! data rather than returning the error. //! //! # Example diff --git a/stores/peerstore/src/peerstore.rs b/stores/peerstore/src/peerstore.rs index a1661499e26..9f94cc66b33 100644 --- a/stores/peerstore/src/peerstore.rs +++ b/stores/peerstore/src/peerstore.rs @@ -24,7 +24,7 @@ use {PeerId, TTL}; /// Implemented on objects that store peers. /// -/// Note that the methods of this trait take by ownership (ie. `self` instead of `&self` or +/// Note that the methods of this trait take by ownership (i.e. `self` instead of `&self` or /// `&mut self`). This was made so that the associated types could hold `self` internally and /// because Rust doesn't have higher-ranked trait bounds yet. /// From fc63947e7b43c157db7d0d599bd74e90265cfc7a Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Fri, 2 Nov 2018 16:49:28 +0100 Subject: [PATCH 12/20] Add a peer id generator (#583) * Add a peer id generator * Improve usage message * Better help * Rustfmt --- Cargo.toml | 1 + misc/peer-id-generator/Cargo.toml | 12 ++++ misc/peer-id-generator/src/main.rs | 89 ++++++++++++++++++++++++++++++ 3 files changed, 102 insertions(+) create mode 100644 misc/peer-id-generator/Cargo.toml create mode 100644 misc/peer-id-generator/src/main.rs diff --git a/Cargo.toml b/Cargo.toml index 9ea458b63c1..bdd25c539a4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -55,6 +55,7 @@ members = [ "misc/multiaddr", "misc/multihash", "misc/multistream-select", + "misc/peer-id-generator", "misc/rw-stream-sink", "transports/dns", "protocols/floodsub", diff --git a/misc/peer-id-generator/Cargo.toml b/misc/peer-id-generator/Cargo.toml new file mode 100644 index 00000000000..101d33153ea --- /dev/null +++ b/misc/peer-id-generator/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "peer-id-generator" +version = "0.1.0" +description = "Generate peer ids that are prefixed with a specific string" +authors = ["Parity Technologies "] +license = "MIT" + +[dependencies] +libp2p-core = { path = "../../core" } +libp2p-secio = { path = "../../protocols/secio" } +num_cpus = "1.8" +rand = "0.5" diff --git a/misc/peer-id-generator/src/main.rs b/misc/peer-id-generator/src/main.rs new file mode 100644 index 00000000000..ce1cfb57ed4 --- /dev/null +++ b/misc/peer-id-generator/src/main.rs @@ -0,0 +1,89 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +extern crate libp2p_core; +extern crate libp2p_secio; +extern crate num_cpus; +extern crate rand; + +use libp2p_core::PeerId; +use libp2p_secio::SecioKeyPair; +use std::{env, str, thread, time::Duration}; + +fn main() { + // Due to the fact that a peer id uses a SHA-256 multihash, it always starts with the + // bytes 0x1220, meaning that only some characters are valid. + const ALLOWED_FIRST_BYTE: &'static [u8] = b"NPQRSTUVWXYZ"; + + let prefix = + match env::args().nth(1) { + Some(prefix) => prefix, + None => { + println!( + "Usage: {} \n\n\ + Generates a peer id that starts with the chosen prefix using a secp256k1 public \ + key.\n\n\ + Prefix must be a sequence of characters in the base58 \ + alphabet, and must start with one of the following: {}", + env::current_exe().unwrap().file_name().unwrap().to_str().unwrap(), + str::from_utf8(ALLOWED_FIRST_BYTE).unwrap() + ); + return; + } + }; + + // The base58 alphabet is not necessarily obvious. + const ALPHABET: &'static [u8] = b"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; + if prefix.as_bytes().iter().any(|c| !ALPHABET.contains(c)) { + println!("Prefix {} is not valid base58", prefix); + return; + } + + // Checking conformity to ALLOWED_FIRST_BYTE. + if !prefix.is_empty() { + if !ALLOWED_FIRST_BYTE.contains(&prefix.as_bytes()[0]) { + println!("Prefix {} is not reachable", prefix); + println!( + "Only the following bytes are possible as first byte: {}", + str::from_utf8(ALLOWED_FIRST_BYTE).unwrap() + ); + return; + } + } + + // Find peer IDs in a multithreaded fashion. + for _ in 0..num_cpus::get() { + let prefix = prefix.clone(); + thread::spawn(move || loop { + let private_key: [u8; 32] = rand::random(); + let generated = SecioKeyPair::secp256k1_raw_key(private_key).unwrap(); + let peer_id: PeerId = generated.to_public_key().into_peer_id(); + let base58 = peer_id.to_base58(); + if base58[2..].starts_with(&prefix) { + println!("Found {:?}", peer_id); + println!("=> Private key = {:?}", private_key); + } + }); + } + + loop { + thread::sleep(Duration::from_secs(3600)); + } +} From 631ceaea4b84b040c2524a30c46fa4ab160767f8 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Sat, 3 Nov 2018 09:54:31 +0100 Subject: [PATCH 13/20] Fix stack overflow when printing a SubstreamRef (#599) * Fix stack overflow when printing a SubstreamRef * Fix concern * More problems --- core/src/muxing.rs | 3 ++- core/src/nodes/node.rs | 38 +++++++++++++++++++---------------- core/src/tests/dummy_muxer.rs | 2 ++ 3 files changed, 25 insertions(+), 18 deletions(-) diff --git a/core/src/muxing.rs b/core/src/muxing.rs index f07088bc744..d96df5d692e 100644 --- a/core/src/muxing.rs +++ b/core/src/muxing.rs @@ -268,9 +268,10 @@ impl

fmt::Debug for SubstreamRef

where P: Deref, P::Target: StreamMuxer, + ::Substream: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { - write!(f, "Substream({:?})", self) + write!(f, "Substream({:?})", self.substream) } } diff --git a/core/src/nodes/node.rs b/core/src/nodes/node.rs index d35ef2a6ffd..6d418c26083 100644 --- a/core/src/nodes/node.rs +++ b/core/src/nodes/node.rs @@ -80,7 +80,6 @@ enum StreamState { } /// Event that can happen on the `NodeStream`. -#[derive(Debug)] pub enum NodeEvent where TMuxer: muxing::StreamMuxer, @@ -346,32 +345,37 @@ where } } -// TODO: -/*impl fmt::Debug for NodeEvent -where TTrans: Transport, - ::Error: fmt::Debug, +impl fmt::Debug for NodeEvent +where + TMuxer: muxing::StreamMuxer, + TMuxer::Substream: fmt::Debug, + TUserData: fmt::Debug, { - fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - NodeEvent::Incoming { ref listen_addr, .. } => { - f.debug_struct("NodeEvent::Incoming") - .field("listen_addr", listen_addr) + NodeEvent::InboundSubstream { substream } => { + f.debug_struct("NodeEvent::OutboundClosed") + .field("substream", substream) .finish() }, - NodeEvent::Closed { ref listen_addr, .. } => { - f.debug_struct("NodeEvent::Closed") - .field("listen_addr", listen_addr) + NodeEvent::OutboundSubstream { user_data, substream } => { + f.debug_struct("NodeEvent::OutboundSubstream") + .field("user_data", user_data) + .field("substream", substream) .finish() }, - NodeEvent::Error { ref listen_addr, ref error, .. } => { - f.debug_struct("NodeEvent::Error") - .field("listen_addr", listen_addr) - .field("error", error) + NodeEvent::OutboundClosed { user_data } => { + f.debug_struct("NodeEvent::OutboundClosed") + .field("user_data", user_data) + .finish() + }, + NodeEvent::InboundClosed => { + f.debug_struct("NodeEvent::InboundClosed") .finish() }, } } -}*/ +} #[cfg(test)] mod node_stream { diff --git a/core/src/tests/dummy_muxer.rs b/core/src/tests/dummy_muxer.rs index bd7832d3edf..63f9e78162a 100644 --- a/core/src/tests/dummy_muxer.rs +++ b/core/src/tests/dummy_muxer.rs @@ -27,9 +27,11 @@ use muxing::{StreamMuxer, Shutdown}; use futures::prelude::*; /// Substream type +#[derive(Debug)] pub struct DummySubstream {} /// OutboundSubstream type +#[derive(Debug)] pub struct DummyOutboundSubstream {} /// Control the muxer state by setting the "connection" state as to set up a mock From 44055180457313517efd5d3cee9f8a558029c939 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Sun, 4 Nov 2018 09:47:15 +0100 Subject: [PATCH 14/20] Add a PeriodicPingHandler and a PingListenHandler (#574) * Add ProtocolsHandler trait * Reexport symbols * Add a note about shutting down * Add a PeriodicPingHandler and a PingListenHandler * Fix core doctest * Add tolerating not supported * Fix concerns --- core/src/lib.rs | 2 +- protocols/ping/Cargo.toml | 3 + protocols/ping/src/dial_handler.rs | 335 ++++++++++++++++++++ protocols/ping/src/lib.rs | 414 +------------------------ protocols/ping/src/listen_handler.rs | 142 +++++++++ protocols/ping/src/protocol.rs | 443 +++++++++++++++++++++++++++ 6 files changed, 935 insertions(+), 404 deletions(-) create mode 100644 protocols/ping/src/dial_handler.rs create mode 100644 protocols/ping/src/listen_handler.rs create mode 100644 protocols/ping/src/protocol.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index 4d727bb5ab0..dbe2426a808 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -132,7 +132,7 @@ //! extern crate tokio; //! //! use futures::{Future, Stream}; -//! use libp2p_ping::{Ping, PingOutput}; +//! use libp2p_ping::protocol::{Ping, PingOutput}; //! use libp2p_core::Transport; //! use tokio::runtime::current_thread::Runtime; //! diff --git a/protocols/ping/Cargo.toml b/protocols/ping/Cargo.toml index 9689d9c5bbb..9487c3bc054 100644 --- a/protocols/ping/Cargo.toml +++ b/protocols/ping/Cargo.toml @@ -5,6 +5,7 @@ authors = ["Parity Technologies "] license = "MIT" [dependencies] +arrayvec = "0.4" bytes = "0.4" libp2p-core = { path = "../../core" } log = "0.4.1" @@ -15,6 +16,8 @@ parking_lot = "0.6" rand = "0.5" tokio-codec = "0.1" tokio-io = "0.1" +tokio-timer = "0.2.6" +void = "1.0" [dev-dependencies] libp2p-tcp-transport = { path = "../../transports/tcp" } diff --git a/protocols/ping/src/dial_handler.rs b/protocols/ping/src/dial_handler.rs new file mode 100644 index 00000000000..c62b921d5dd --- /dev/null +++ b/protocols/ping/src/dial_handler.rs @@ -0,0 +1,335 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use futures::prelude::*; +use libp2p_core::{ + nodes::{NodeHandlerEndpoint, ProtocolsHandler, ProtocolsHandlerEvent}, + upgrade::toggleable, + ConnectionUpgrade, +}; +use protocol::{Ping, PingDialer, PingOutput}; +use std::{ + io, mem, + time::{Duration, Instant}, +}; +use tokio_io::{AsyncRead, AsyncWrite}; +use tokio_timer::Delay; +use void::Void; + +/// Protocol handler that handles pinging the remote at a regular period. +/// +/// If the remote doesn't respond, produces `Unresponsive` and closes the connection. +pub struct PeriodicPingHandler { + /// Configuration for the ping protocol. + ping_config: toggleable::Toggleable>, + + /// State of the outgoing ping. + out_state: OutState, + + /// Duration after which we consider that a ping failed. + ping_timeout: Duration, + + /// After a ping succeeded, wait this long before the next ping. + delay_to_next_ping: Duration, + + /// If true, we switch to the `Disabled` state if the remote doesn't support the ping protocol. + /// If false, we close the connection. + tolerate_unsupported: bool, +} + +/// State of the outgoing ping substream. +enum OutState { + /// We need to open a new substream. + NeedToOpen { + /// Timeout after which we decide that it's not going to work out. + /// + /// Theoretically the handler should be polled immediately after we set the state to + /// `NeedToOpen` and then we immediately transition away from it. However if the local node + /// is for some reason busy, creating the `Delay` here avoids being overly generous with + /// the ping timeout. + expires: Delay, + }, + + /// Upgrading a substream to use ping. + /// + /// We produced a substream open request, and are waiting for it to be upgraded to a full + /// ping-powered substream. + Upgrading { + /// Timeout after which we decide that it's not going to work out. + /// + /// The user of the `ProtocolsHandler` should ensure that there's a timeout when upgrading, + /// but by storing a timeout here as well we ensure that we keep track of how long the + /// ping has lasted. + expires: Delay, + }, + + /// We sent a ping and we are waiting for the pong. + WaitingForPong { + /// Substream where we should receive the pong. + substream: PingDialer, + /// Timeout after which we decide that we're not going to receive the pong. + expires: Delay, + }, + + /// We received a pong and now we have nothing to do except wait a bit before sending the + /// next ping. + Idle { + /// The substream to use to send pings. + substream: PingDialer, + /// When to send the ping next. + next_ping: Delay, + }, + + /// The ping dialer is disabled. Don't do anything. + Disabled, + + /// The dialer has been closed. + Shutdown, + + /// Something bad happened during the previous polling. + Poisoned, +} + +/// Event produced by the periodic pinger. +#[derive(Debug, Copy, Clone)] +pub enum OutEvent { + /// The node has been determined to be unresponsive. + Unresponsive, + + /// Started pinging the remote. This can be used to print a diagnostic message in the logs. + PingStart, + + /// The node has successfully responded to a ping. + PingSuccess(Duration), +} + +impl PeriodicPingHandler { + /// Builds a new `PeriodicPingHandler`. + pub fn new() -> PeriodicPingHandler { + let ping_timeout = Duration::from_secs(30); + + PeriodicPingHandler { + ping_config: toggleable::toggleable(Default::default()), + out_state: OutState::NeedToOpen { + expires: Delay::new(Instant::now() + ping_timeout), + }, + ping_timeout, + delay_to_next_ping: Duration::from_secs(15), + tolerate_unsupported: false, + } + } +} + +impl Default for PeriodicPingHandler { + #[inline] + fn default() -> Self { + PeriodicPingHandler::new() + } +} + +impl ProtocolsHandler for PeriodicPingHandler +where + TSubstream: AsyncRead + AsyncWrite, +{ + type InEvent = Void; + type OutEvent = OutEvent; + type Substream = TSubstream; + type Protocol = toggleable::Toggleable>; + type OutboundOpenInfo = (); + + #[inline] + fn listen_protocol(&self) -> Self::Protocol { + let mut config = self.ping_config; + config.disable(); + config + } + + fn inject_fully_negotiated( + &mut self, + protocol: >::Output, + _endpoint: NodeHandlerEndpoint, + ) { + match protocol { + PingOutput::Pinger(mut substream) => { + debug_assert!(_endpoint.is_dialer()); + match mem::replace(&mut self.out_state, OutState::Poisoned) { + OutState::Upgrading { expires } => { + // We always upgrade with the intent of immediately pinging. + substream.ping(Instant::now()); + self.out_state = OutState::WaitingForPong { substream, expires }; + } + _ => (), + } + } + PingOutput::Ponger(_) => { + debug_assert!(false, "Received an unexpected incoming ping substream"); + } + } + } + + fn inject_event(&mut self, _: &Self::InEvent) {} + + fn inject_inbound_closed(&mut self) {} + + #[inline] + fn inject_dial_upgrade_error(&mut self, _: Self::OutboundOpenInfo, _: io::Error) { + // In case of error while upgrading, there's not much we can do except shut down. + // TODO: we assume that the error is about ping not being supported, which is not + // necessarily the case + if self.tolerate_unsupported { + self.out_state = OutState::Disabled; + } else { + self.out_state = OutState::Shutdown; + } + } + + fn shutdown(&mut self) { + // Put `Shutdown` in `self.out_state` if we don't have any substream open. + // Otherwise, keep the state as it is but call `shutdown()` on the substream. This + // guarantees that the dialer will return `None` at some point. + match self.out_state { + OutState::WaitingForPong { + ref mut substream, .. + } => substream.shutdown(), + OutState::Idle { + ref mut substream, .. + } => substream.shutdown(), + ref mut s => *s = OutState::Shutdown, + } + } + + fn poll( + &mut self, + ) -> Poll< + Option>, + io::Error, + > { + // Shortcut for polling a `tokio_timer::Delay` + macro_rules! poll_delay { + ($delay:expr => { NotReady => $notready:expr, Ready => $ready:expr, }) => ( + match $delay.poll() { + Ok(Async::NotReady) => $notready, + Ok(Async::Ready(())) => $ready, + Err(err) => { + warn!(target: "sub-libp2p", "Ping timer errored: {:?}", err); + return Err(io::Error::new(io::ErrorKind::Other, err)); + } + } + ) + } + + loop { + match mem::replace(&mut self.out_state, OutState::Poisoned) { + OutState::Shutdown | OutState::Poisoned => { + // This shuts down the whole connection with the remote. + return Ok(Async::Ready(None)); + }, + + OutState::Disabled => { + return Ok(Async::NotReady); + } + + // Need to open an outgoing substream. + OutState::NeedToOpen { expires } => { + // Note that we ignore the expiration here, as it's pretty unlikely to happen. + // The expiration is only here to be transmitted to the `Upgrading`. + self.out_state = OutState::Upgrading { expires }; + return Ok(Async::Ready(Some( + ProtocolsHandlerEvent::OutboundSubstreamRequest { + upgrade: self.ping_config, + info: (), + }, + ))); + } + + // Waiting for the upgrade to be negotiated. + OutState::Upgrading { mut expires } => poll_delay!(expires => { + NotReady => { + self.out_state = OutState::Upgrading { expires }; + return Ok(Async::NotReady); + }, + Ready => { + self.out_state = OutState::Shutdown; + let ev = OutEvent::Unresponsive; + return Ok(Async::Ready(Some(ProtocolsHandlerEvent::Custom(ev)))); + }, + }), + + // Waiting for the pong. + OutState::WaitingForPong { + mut substream, + mut expires, + } => { + // We start by dialing the substream, leaving one last chance for it to + // produce the pong even if the expiration happened. + match substream.poll()? { + Async::Ready(Some(started)) => { + self.out_state = OutState::Idle { + substream, + next_ping: Delay::new(Instant::now() + self.delay_to_next_ping), + }; + let ev = OutEvent::PingSuccess(started.elapsed()); + return Ok(Async::Ready(Some(ProtocolsHandlerEvent::Custom(ev)))); + } + Async::NotReady => {} + Async::Ready(None) => { + self.out_state = OutState::Shutdown; + return Ok(Async::Ready(None)); + } + }; + + // Check the expiration. + poll_delay!(expires => { + NotReady => { + self.out_state = OutState::WaitingForPong { substream, expires }; + // Both `substream` and `expires` and not ready, so it's fine to return + // not ready. + return Ok(Async::NotReady); + }, + Ready => { + self.out_state = OutState::Shutdown; + let ev = OutEvent::Unresponsive; + return Ok(Async::Ready(Some(ProtocolsHandlerEvent::Custom(ev)))); + }, + }) + } + + OutState::Idle { + mut substream, + mut next_ping, + } => { + // Poll the future that fires when we need to ping the node again. + poll_delay!(next_ping => { + NotReady => { + self.out_state = OutState::Idle { substream, next_ping }; + return Ok(Async::NotReady); + }, + Ready => { + let expires = Delay::new(Instant::now() + self.ping_timeout); + substream.ping(Instant::now()); + self.out_state = OutState::WaitingForPong { substream, expires }; + return Ok(Async::Ready(Some(ProtocolsHandlerEvent::Custom(OutEvent::PingStart)))); + }, + }) + } + } + } + } +} diff --git a/protocols/ping/src/lib.rs b/protocols/ping/src/lib.rs index e8c59a00f1a..dacab9cc926 100644 --- a/protocols/ping/src/lib.rs +++ b/protocols/ping/src/lib.rs @@ -1,4 +1,4 @@ -// Copyright 2017 Parity Technologies (UK) Ltd. +// 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"), @@ -56,7 +56,7 @@ //! extern crate tokio; //! //! use futures::{Future, Stream}; -//! use libp2p_ping::{Ping, PingOutput}; +//! use libp2p_ping::protocol::{Ping, PingOutput}; //! use libp2p_core::Transport; //! use tokio::runtime::current_thread::Runtime; //! @@ -82,7 +82,9 @@ //! ``` //! +extern crate arrayvec; extern crate bytes; +#[macro_use] extern crate futures; extern crate libp2p_core; #[macro_use] @@ -92,407 +94,13 @@ extern crate parking_lot; extern crate rand; extern crate tokio_codec; extern crate tokio_io; +extern crate tokio_timer; +extern crate void; -use bytes::{BufMut, Bytes, BytesMut}; -use futures::{prelude::*, future::{FutureResult, IntoFuture}, task}; -use libp2p_core::{ConnectionUpgrade, Endpoint}; -use rand::{distributions::Standard, prelude::*, rngs::EntropyRng}; -use std::collections::VecDeque; -use std::io::Error as IoError; -use std::{iter, marker::PhantomData, mem}; -use tokio_codec::{Decoder, Encoder, Framed}; -use tokio_io::{AsyncRead, AsyncWrite}; - -/// Represents a prototype for an upgrade to handle the ping protocol. -/// -/// According to the design of libp2p, this struct would normally contain the configuration options -/// for the protocol, but in the case of `Ping` no configuration is required. -#[derive(Debug, Copy, Clone)] -pub struct Ping(PhantomData); - -impl Default for Ping { - #[inline] - fn default() -> Self { - Ping(PhantomData) - } -} - -/// Output of a `Ping` upgrade. -pub enum PingOutput { - /// We are on the dialing side. - Pinger(PingDialer), - /// We are on the listening side. - Ponger(PingListener), -} - -impl ConnectionUpgrade for Ping -where - TSocket: AsyncRead + AsyncWrite, -{ - type NamesIter = iter::Once<(Bytes, Self::UpgradeIdentifier)>; - type UpgradeIdentifier = (); - - #[inline] - fn protocol_names(&self) -> Self::NamesIter { - iter::once(("/ipfs/ping/1.0.0".into(), ())) - } - - type Output = PingOutput; - type Future = FutureResult; - - #[inline] - fn upgrade( - self, - socket: TSocket, - _: Self::UpgradeIdentifier, - endpoint: Endpoint, - ) -> Self::Future { - let out = match endpoint { - Endpoint::Dialer => upgrade_as_dialer(socket), - Endpoint::Listener => upgrade_as_listener(socket), - }; - - Ok(out).into_future() - } -} - -/// Upgrades a connection from the dialer side. -fn upgrade_as_dialer(socket: TSocket) -> PingOutput -where TSocket: AsyncRead + AsyncWrite, -{ - let dialer = PingDialer { - inner: Framed::new(socket, Codec), - need_writer_flush: false, - needs_close: false, - sent_pings: VecDeque::with_capacity(4), - rng: EntropyRng::default(), - pings_to_send: VecDeque::with_capacity(4), - }; - - PingOutput::Pinger(dialer) -} - -/// Upgrades a connection from the listener side. -fn upgrade_as_listener(socket: TSocket) -> PingOutput -where TSocket: AsyncRead + AsyncWrite, -{ - let listener = PingListener { - inner: Framed::new(socket, Codec), - state: PingListenerState::Listening, - }; - - PingOutput::Ponger(listener) -} - -/// Sends pings and receives the pongs. -/// -/// Implements `Stream`. The stream indicates when we receive a pong. -pub struct PingDialer { - /// The underlying socket. - inner: Framed, - /// If true, need to flush the sink. - need_writer_flush: bool, - /// If true, need to close the sink. - needs_close: bool, - /// List of pings that have been sent to the remote and that are waiting for an answer. - sent_pings: VecDeque<(Bytes, TUserData)>, - /// Random number generator for the ping payload. - rng: EntropyRng, - /// List of pings to send to the remote. - pings_to_send: VecDeque<(Bytes, TUserData)>, -} - -impl PingDialer { - /// Sends a ping to the remote. - /// - /// The stream will produce an event containing the user data when we receive the pong. - pub fn ping(&mut self, user_data: TUserData) { - let payload: [u8; 32] = self.rng.sample(Standard); - debug!("Preparing for ping with payload {:?}", payload); - self.pings_to_send.push_back((Bytes::from(payload.to_vec()), user_data)); - } -} - -impl Stream for PingDialer -where TSocket: AsyncRead + AsyncWrite, -{ - type Item = TUserData; - type Error = IoError; - - fn poll(&mut self) -> Poll, Self::Error> { - if self.needs_close { - match self.inner.close() { - Ok(Async::Ready(())) => return Ok(Async::Ready(None)), - Ok(Async::NotReady) => return Ok(Async::NotReady), - Err(err) => return Err(err), - } - } - - while let Some((ping, user_data)) = self.pings_to_send.pop_front() { - match self.inner.start_send(ping.clone()) { - Ok(AsyncSink::Ready) => self.need_writer_flush = true, - Ok(AsyncSink::NotReady(_)) => { - self.pings_to_send.push_front((ping, user_data)); - break; - }, - Err(err) => return Err(err), - } - - self.sent_pings.push_back((ping, user_data)); - } - - if self.need_writer_flush { - match self.inner.poll_complete() { - Ok(Async::Ready(())) => self.need_writer_flush = false, - Ok(Async::NotReady) => (), - Err(err) => return Err(err), - } - } - - loop { - match self.inner.poll() { - Ok(Async::Ready(Some(pong))) => { - if let Some(pos) = self.sent_pings.iter().position(|&(ref p, _)| p == &pong) { - let (_, user_data) = self.sent_pings.remove(pos) - .expect("Grabbed a valid position just above"); - return Ok(Async::Ready(Some(user_data))); - } else { - debug!("Received pong that doesn't match what we sent: {:?}", pong); - } - }, - Ok(Async::NotReady) => break, - Ok(Async::Ready(None)) => { - // Notify the current task so that we poll again. - self.needs_close = true; - task::current().notify(); - return Ok(Async::NotReady); - } - Err(err) => return Err(err), - } - } - - Ok(Async::NotReady) - } -} - -/// Listens to incoming pings and answers them. -/// -/// Implements `Future`. The future terminates when the underlying socket closes. -pub struct PingListener { - /// The underlying socket. - inner: Framed, - /// State of the listener. - state: PingListenerState, -} - -#[derive(Debug)] -enum PingListenerState { - /// We are waiting for the next ping on the socket. - Listening, - /// We are trying to send a pong. - Sending(Bytes), - /// We are flusing the underlying sink. - Flushing, - /// We are shutting down everything. - Closing, - /// A panic happened during the processing. - Poisoned, -} - -impl Future for PingListener -where TSocket: AsyncRead + AsyncWrite -{ - type Item = (); - type Error = IoError; - - fn poll(&mut self) -> Poll { - loop { - match mem::replace(&mut self.state, PingListenerState::Poisoned) { - PingListenerState::Listening => { - match self.inner.poll() { - Ok(Async::Ready(Some(payload))) => { - debug!("Received ping (payload={:?}); sending back", payload); - self.state = PingListenerState::Sending(payload.freeze()) - }, - Ok(Async::Ready(None)) => self.state = PingListenerState::Closing, - Ok(Async::NotReady) => { - self.state = PingListenerState::Listening; - return Ok(Async::NotReady); - }, - Err(err) => return Err(err), - } - }, - PingListenerState::Sending(data) => { - match self.inner.start_send(data) { - Ok(AsyncSink::Ready) => self.state = PingListenerState::Flushing, - Ok(AsyncSink::NotReady(data)) => { - self.state = PingListenerState::Sending(data); - return Ok(Async::NotReady); - }, - Err(err) => return Err(err), - } - }, - PingListenerState::Flushing => { - match self.inner.poll_complete() { - Ok(Async::Ready(())) => self.state = PingListenerState::Listening, - Ok(Async::NotReady) => { - self.state = PingListenerState::Flushing; - return Ok(Async::NotReady); - }, - Err(err) => return Err(err), - } - }, - PingListenerState::Closing => { - match self.inner.close() { - Ok(Async::Ready(())) => return Ok(Async::Ready(())), - Ok(Async::NotReady) => { - self.state = PingListenerState::Closing; - return Ok(Async::NotReady); - }, - Err(err) => return Err(err), - } - }, - PingListenerState::Poisoned => panic!("Poisoned or errored PingListener"), - } - } - } -} - -// Implementation of the `Codec` trait of tokio-io. Splits frames into groups of 32 bytes. -#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] -struct Codec; - -impl Decoder for Codec { - type Item = BytesMut; - type Error = IoError; - - #[inline] - fn decode(&mut self, buf: &mut BytesMut) -> Result, IoError> { - if buf.len() >= 32 { - Ok(Some(buf.split_to(32))) - } else { - Ok(None) - } - } -} - -impl Encoder for Codec { - type Item = Bytes; - type Error = IoError; - - #[inline] - fn encode(&mut self, mut data: Bytes, buf: &mut BytesMut) -> Result<(), IoError> { - if !data.is_empty() { - let split = 32 * (1 + ((data.len() - 1) / 32)); - buf.reserve(split); - buf.put(data.split_to(split)); - } - Ok(()) - } -} - -#[cfg(test)] -mod tests { - extern crate tokio; - extern crate tokio_tcp; - - use self::tokio::runtime::current_thread::Runtime; - use self::tokio_tcp::TcpListener; - use self::tokio_tcp::TcpStream; - use super::{Ping, PingOutput}; - use futures::{Future, Stream}; - use libp2p_core::{ConnectionUpgrade, Endpoint}; - - // TODO: rewrite tests with the MemoryTransport - - #[test] - fn ping_pong() { - let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); - let listener_addr = listener.local_addr().unwrap(); - - let server = listener - .incoming() - .into_future() - .map_err(|(e, _)| e.into()) - .and_then(|(c, _)| { - Ping::<()>::default().upgrade( - c.unwrap(), - (), - Endpoint::Listener, - ) - }) - .and_then(|out| match out { - PingOutput::Ponger(service) => service, - _ => unreachable!(), - }); - - let client = TcpStream::connect(&listener_addr) - .map_err(|e| e.into()) - .and_then(|c| { - Ping::<()>::default().upgrade( - c, - (), - Endpoint::Dialer, - ) - }) - .and_then(|out| match out { - PingOutput::Pinger(mut pinger) => { - pinger.ping(()); - pinger.into_future().map(|_| ()).map_err(|_| panic!()) - }, - _ => unreachable!(), - }) - .map(|_| ()); - let mut rt = Runtime::new().unwrap(); - let _ = rt.block_on(server.select(client).map_err(|_| panic!())).unwrap(); - } - - #[test] - fn multipings() { - // Check that we can send multiple pings in a row and it will still work. - let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); - let listener_addr = listener.local_addr().unwrap(); - - let server = listener - .incoming() - .into_future() - .map_err(|(e, _)| e.into()) - .and_then(|(c, _)| { - Ping::::default().upgrade( - c.unwrap(), - (), - Endpoint::Listener, - ) - }) - .and_then(|out| match out { - PingOutput::Ponger(service) => service, - _ => unreachable!(), - }); +pub use self::dial_handler::PeriodicPingHandler; +pub use self::listen_handler::PingListenHandler; - let client = TcpStream::connect(&listener_addr) - .map_err(|e| e.into()) - .and_then(|c| { - Ping::::default().upgrade( - c, - (), - Endpoint::Dialer, - ) - }) - .and_then(|out| match out { - PingOutput::Pinger(mut pinger) => { - for n in 0..20 { - pinger.ping(n); - } +pub mod protocol; - pinger - .take(20) - .collect() - .map(|val| { assert_eq!(val, (0..20).collect::>()); }) - .map_err(|_| panic!()) - }, - _ => unreachable!(), - }); - let mut rt = Runtime::new().unwrap(); - let _ = rt.block_on(server.select(client)).unwrap_or_else(|_| panic!()); - } -} +mod dial_handler; +mod listen_handler; diff --git a/protocols/ping/src/listen_handler.rs b/protocols/ping/src/listen_handler.rs new file mode 100644 index 00000000000..5684b4ab0f3 --- /dev/null +++ b/protocols/ping/src/listen_handler.rs @@ -0,0 +1,142 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use arrayvec::ArrayVec; +use futures::prelude::*; +use libp2p_core::{ + nodes::{NodeHandlerEndpoint, ProtocolsHandler, ProtocolsHandlerEvent}, + ConnectionUpgrade, +}; +use protocol::{Ping, PingListener, PingOutput}; +use std::io; +use tokio_io::{AsyncRead, AsyncWrite}; +use void::Void; + +/// Handler for handling pings received from a remote. +pub struct PingListenHandler { + /// Configuration for the ping protocol. + ping_config: Ping<()>, + + /// The ping substreams that were opened by the remote. + /// Note that we only accept a certain number of substreams, after which we refuse new ones + /// to avoid being DDoSed. + ping_in_substreams: ArrayVec<[PingListener; 8]>, + + /// If true, we're in the shutdown process and we shouldn't accept new substreams. + shutdown: bool, +} + +impl PingListenHandler { + /// Builds a new `PingListenHandler`. + pub fn new() -> PingListenHandler { + PingListenHandler { + ping_config: Default::default(), + shutdown: false, + ping_in_substreams: ArrayVec::new(), + } + } +} + +impl Default for PingListenHandler { + #[inline] + fn default() -> Self { + PingListenHandler::new() + } +} + +impl ProtocolsHandler for PingListenHandler +where + TSubstream: AsyncRead + AsyncWrite, +{ + type InEvent = Void; + type OutEvent = Void; + type Substream = TSubstream; + type Protocol = Ping<()>; + type OutboundOpenInfo = (); + + #[inline] + fn listen_protocol(&self) -> Self::Protocol { + self.ping_config + } + + fn inject_fully_negotiated( + &mut self, + protocol: >::Output, + _endpoint: NodeHandlerEndpoint, + ) { + if self.shutdown { + return; + } + + match protocol { + PingOutput::Pinger(_) => { + debug_assert!(false, "Received an unexpected outgoing ping substream"); + } + PingOutput::Ponger(listener) => { + debug_assert!(_endpoint.is_listener()); + // Try insert the element, but don't care if the list is full. + let _ = self.ping_in_substreams.try_push(listener); + } + } + } + + #[inline] + fn inject_event(&mut self, _: &Self::InEvent) {} + + #[inline] + fn inject_inbound_closed(&mut self) {} + + #[inline] + fn inject_dial_upgrade_error(&mut self, _: Self::OutboundOpenInfo, _: io::Error) {} + + #[inline] + fn shutdown(&mut self) { + for ping in self.ping_in_substreams.iter_mut() { + ping.shutdown(); + } + + self.shutdown = true; + } + + fn poll( + &mut self, + ) -> Poll< + Option>, + io::Error, + > { + // Removes each substream one by one, and pushes them back if they're not ready (which + // should be the case 99% of the time). + for n in (0..self.ping_in_substreams.len()).rev() { + let mut ping = self.ping_in_substreams.swap_remove(n); + match ping.poll() { + Ok(Async::Ready(())) => {} + Ok(Async::NotReady) => self.ping_in_substreams.push(ping), + Err(err) => warn!(target: "sub-libp2p", "Remote ping substream errored: {:?}", err), + } + } + + // Special case if shutting down. + if self.shutdown && self.ping_in_substreams.is_empty() { + return Ok(Async::Ready(None)); + } + + Ok(Async::NotReady) + } +} diff --git a/protocols/ping/src/protocol.rs b/protocols/ping/src/protocol.rs new file mode 100644 index 00000000000..a748871c7fe --- /dev/null +++ b/protocols/ping/src/protocol.rs @@ -0,0 +1,443 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use bytes::{BufMut, Bytes, BytesMut}; +use futures::{prelude::*, future::FutureResult, future::IntoFuture}; +use libp2p_core::{ConnectionUpgrade, Endpoint}; +use rand::{distributions::Standard, prelude::*, rngs::EntropyRng}; +use std::collections::VecDeque; +use std::io::Error as IoError; +use std::{iter, marker::PhantomData, mem}; +use tokio_codec::{Decoder, Encoder, Framed}; +use tokio_io::{AsyncRead, AsyncWrite}; + +/// Represents a prototype for an upgrade to handle the ping protocol. +/// +/// According to the design of libp2p, this struct would normally contain the configuration options +/// for the protocol, but in the case of `Ping` no configuration is required. +#[derive(Debug, Copy, Clone)] +pub struct Ping(PhantomData); + +impl Default for Ping { + #[inline] + fn default() -> Self { + Ping(PhantomData) + } +} + +/// Output of a `Ping` upgrade. +pub enum PingOutput { + /// We are on the dialing side. + Pinger(PingDialer), + /// We are on the listening side. + Ponger(PingListener), +} + +impl ConnectionUpgrade for Ping +where + TSocket: AsyncRead + AsyncWrite, +{ + type NamesIter = iter::Once<(Bytes, Self::UpgradeIdentifier)>; + type UpgradeIdentifier = (); + + #[inline] + fn protocol_names(&self) -> Self::NamesIter { + iter::once(("/ipfs/ping/1.0.0".into(), ())) + } + + type Output = PingOutput; + type Future = FutureResult; + + #[inline] + fn upgrade( + self, + socket: TSocket, + _: Self::UpgradeIdentifier, + endpoint: Endpoint, + ) -> Self::Future { + let out = match endpoint { + Endpoint::Dialer => upgrade_as_dialer(socket), + Endpoint::Listener => upgrade_as_listener(socket), + }; + + Ok(out).into_future() + } +} + +/// Upgrades a connection from the dialer side. +fn upgrade_as_dialer(socket: TSocket) -> PingOutput +where TSocket: AsyncRead + AsyncWrite, +{ + let dialer = PingDialer { + inner: Framed::new(socket, Codec), + need_writer_flush: false, + needs_close: false, + sent_pings: VecDeque::with_capacity(4), + rng: EntropyRng::default(), + pings_to_send: VecDeque::with_capacity(4), + }; + + PingOutput::Pinger(dialer) +} + +/// Upgrades a connection from the listener side. +fn upgrade_as_listener(socket: TSocket) -> PingOutput +where TSocket: AsyncRead + AsyncWrite, +{ + let listener = PingListener { + inner: Framed::new(socket, Codec), + state: PingListenerState::Listening, + }; + + PingOutput::Ponger(listener) +} + +/// Sends pings and receives the pongs. +/// +/// Implements `Stream`. The stream indicates when we receive a pong. +pub struct PingDialer { + /// The underlying socket. + inner: Framed, + /// If true, need to flush the sink. + need_writer_flush: bool, + /// If true, need to close the sink. + needs_close: bool, + /// List of pings that have been sent to the remote and that are waiting for an answer. + sent_pings: VecDeque<(Bytes, TUserData)>, + /// Random number generator for the ping payload. + rng: EntropyRng, + /// List of pings to send to the remote. + pings_to_send: VecDeque<(Bytes, TUserData)>, +} + +impl PingDialer { + /// Sends a ping to the remote. + /// + /// The stream will produce an event containing the user data when we receive the pong. + pub fn ping(&mut self, user_data: TUserData) { + let payload: [u8; 32] = self.rng.sample(Standard); + debug!("Preparing for ping with payload {:?}", payload); + self.pings_to_send.push_back((Bytes::from(payload.to_vec()), user_data)); + } +} + +impl PingDialer +where TSocket: AsyncRead + AsyncWrite, +{ + /// Call this when the ping dialer needs to shut down. After this, the `Stream` is guaranteed + /// to finish soon-ish. + #[inline] + pub fn shutdown(&mut self) { + self.needs_close = true; + } +} + +impl Stream for PingDialer +where TSocket: AsyncRead + AsyncWrite, +{ + type Item = TUserData; + type Error = IoError; + + fn poll(&mut self) -> Poll, Self::Error> { + if self.needs_close { + try_ready!(self.inner.close()); + return Ok(Async::Ready(None)); + } + + while let Some((ping, user_data)) = self.pings_to_send.pop_front() { + match self.inner.start_send(ping.clone()) { + Ok(AsyncSink::Ready) => self.need_writer_flush = true, + Ok(AsyncSink::NotReady(_)) => { + self.pings_to_send.push_front((ping, user_data)); + break; + }, + Err(err) => return Err(err), + } + + self.sent_pings.push_back((ping, user_data)); + } + + if self.need_writer_flush { + match self.inner.poll_complete() { + Ok(Async::Ready(())) => self.need_writer_flush = false, + Ok(Async::NotReady) => (), + Err(err) => return Err(err), + } + } + + loop { + match self.inner.poll() { + Ok(Async::Ready(Some(pong))) => { + if let Some(pos) = self.sent_pings.iter().position(|&(ref p, _)| p == &pong) { + let (_, user_data) = self.sent_pings.remove(pos) + .expect("Grabbed a valid position just above"); + return Ok(Async::Ready(Some(user_data))); + } else { + debug!("Received pong that doesn't match what we sent: {:?}", pong); + } + }, + Ok(Async::NotReady) => break, + Ok(Async::Ready(None)) => { + // Notify the current task so that we poll again. + self.needs_close = true; + try_ready!(self.inner.close()); + return Ok(Async::Ready(None)); + } + Err(err) => return Err(err), + } + } + + Ok(Async::NotReady) + } +} + +/// Listens to incoming pings and answers them. +/// +/// Implements `Future`. The future terminates when the underlying socket closes. +pub struct PingListener { + /// The underlying socket. + inner: Framed, + /// State of the listener. + state: PingListenerState, +} + +#[derive(Debug)] +enum PingListenerState { + /// We are waiting for the next ping on the socket. + Listening, + /// We are trying to send a pong. + Sending(Bytes), + /// We are flusing the underlying sink. + Flushing, + /// We are shutting down everything. + Closing, + /// A panic happened during the processing. + Poisoned, +} + +impl PingListener +where TSocket: AsyncRead + AsyncWrite +{ + /// Call this when the ping listener needs to shut down. After this, the `Future` is guaranteed + /// to finish soon-ish. + #[inline] + pub fn shutdown(&mut self) { + self.state = PingListenerState::Closing; + } +} + +impl Future for PingListener +where TSocket: AsyncRead + AsyncWrite +{ + type Item = (); + type Error = IoError; + + fn poll(&mut self) -> Poll { + loop { + match mem::replace(&mut self.state, PingListenerState::Poisoned) { + PingListenerState::Listening => { + match self.inner.poll() { + Ok(Async::Ready(Some(payload))) => { + debug!("Received ping (payload={:?}) ; sending back", payload); + self.state = PingListenerState::Sending(payload.freeze()) + }, + Ok(Async::Ready(None)) => self.state = PingListenerState::Closing, + Ok(Async::NotReady) => { + self.state = PingListenerState::Listening; + return Ok(Async::NotReady); + }, + Err(err) => return Err(err), + } + }, + PingListenerState::Sending(data) => { + match self.inner.start_send(data) { + Ok(AsyncSink::Ready) => self.state = PingListenerState::Flushing, + Ok(AsyncSink::NotReady(data)) => { + self.state = PingListenerState::Sending(data); + return Ok(Async::NotReady); + }, + Err(err) => return Err(err), + } + }, + PingListenerState::Flushing => { + match self.inner.poll_complete() { + Ok(Async::Ready(())) => self.state = PingListenerState::Listening, + Ok(Async::NotReady) => { + self.state = PingListenerState::Flushing; + return Ok(Async::NotReady); + }, + Err(err) => return Err(err), + } + }, + PingListenerState::Closing => { + match self.inner.close() { + Ok(Async::Ready(())) => return Ok(Async::Ready(())), + Ok(Async::NotReady) => { + self.state = PingListenerState::Closing; + return Ok(Async::NotReady); + }, + Err(err) => return Err(err), + } + }, + PingListenerState::Poisoned => panic!("Poisoned or errored PingListener"), + } + } + } +} + +// Implementation of the `Codec` trait of tokio-io. Splits frames into groups of 32 bytes. +#[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash)] +struct Codec; + +impl Decoder for Codec { + type Item = BytesMut; + type Error = IoError; + + #[inline] + fn decode(&mut self, buf: &mut BytesMut) -> Result, IoError> { + if buf.len() >= 32 { + Ok(Some(buf.split_to(32))) + } else { + Ok(None) + } + } +} + +impl Encoder for Codec { + type Item = Bytes; + type Error = IoError; + + #[inline] + fn encode(&mut self, mut data: Bytes, buf: &mut BytesMut) -> Result<(), IoError> { + if !data.is_empty() { + let split = 32 * (1 + ((data.len() - 1) / 32)); + buf.reserve(split); + buf.put(data.split_to(split)); + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + extern crate tokio; + extern crate tokio_tcp; + + use self::tokio_tcp::TcpListener; + use self::tokio_tcp::TcpStream; + use super::{Ping, PingOutput}; + use futures::{Future, Stream}; + use libp2p_core::{ConnectionUpgrade, Endpoint}; + + // TODO: rewrite tests with the MemoryTransport + + #[test] + fn ping_pong() { + let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); + let listener_addr = listener.local_addr().unwrap(); + + let server = listener + .incoming() + .into_future() + .map_err(|(e, _)| e.into()) + .and_then(|(c, _)| { + Ping::<()>::default().upgrade( + c.unwrap(), + (), + Endpoint::Listener, + ) + }) + .and_then(|out| match out { + PingOutput::Ponger(service) => service, + _ => unreachable!(), + }); + + let client = TcpStream::connect(&listener_addr) + .map_err(|e| e.into()) + .and_then(|c| { + Ping::<()>::default().upgrade( + c, + (), + Endpoint::Dialer, + ) + }) + .and_then(|out| match out { + PingOutput::Pinger(mut pinger) => { + pinger.ping(()); + pinger.into_future().map(|_| ()).map_err(|_| panic!()) + }, + _ => unreachable!(), + }) + .map(|_| ()); + + let mut runtime = tokio::runtime::Runtime::new().unwrap(); + runtime.block_on(server.select(client).map_err(|_| panic!())).unwrap(); + } + + #[test] + fn multipings() { + // Check that we can send multiple pings in a row and it will still work. + let listener = TcpListener::bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); + let listener_addr = listener.local_addr().unwrap(); + + let server = listener + .incoming() + .into_future() + .map_err(|(e, _)| e.into()) + .and_then(|(c, _)| { + Ping::::default().upgrade( + c.unwrap(), + (), + Endpoint::Listener, + ) + }) + .and_then(|out| match out { + PingOutput::Ponger(service) => service, + _ => unreachable!(), + }); + + let client = TcpStream::connect(&listener_addr) + .map_err(|e| e.into()) + .and_then(|c| { + Ping::::default().upgrade( + c, + (), + Endpoint::Dialer, + ) + }) + .and_then(|out| match out { + PingOutput::Pinger(mut pinger) => { + for n in 0..20 { + pinger.ping(n); + } + + pinger + .take(20) + .collect() + .map(|val| { assert_eq!(val, (0..20).collect::>()); }) + .map_err(|_| panic!()) + }, + _ => unreachable!(), + }); + + let mut runtime = tokio::runtime::Runtime::new().unwrap(); + runtime.block_on(server.select(client)).unwrap_or_else(|_| panic!()); + } +} From 1b4dada4d0f8cb2624a5e1e6db817832a971f943 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Mon, 5 Nov 2018 16:53:04 +0100 Subject: [PATCH 15/20] Inject event by value in ProtocolsHandler (#605) --- core/src/nodes/protocols_handler.rs | 12 ++++++------ protocols/identify/src/periodic_id_handler.rs | 2 +- protocols/ping/src/dial_handler.rs | 2 +- protocols/ping/src/listen_handler.rs | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/core/src/nodes/protocols_handler.rs b/core/src/nodes/protocols_handler.rs index 0ae832ef39e..0037b5a17cf 100644 --- a/core/src/nodes/protocols_handler.rs +++ b/core/src/nodes/protocols_handler.rs @@ -103,7 +103,7 @@ pub trait ProtocolsHandler { ); /// Injects an event coming from the outside in the handler. - fn inject_event(&mut self, event: &Self::InEvent); + fn inject_event(&mut self, event: Self::InEvent); /// Indicates to the handler that upgrading a substream to the given protocol has failed. fn inject_dial_upgrade_error(&mut self, info: Self::OutboundOpenInfo, error: io::Error); @@ -299,7 +299,7 @@ where } #[inline] - fn inject_event(&mut self, _: &Self::InEvent) {} + fn inject_event(&mut self, _: Self::InEvent) {} #[inline] fn inject_dial_upgrade_error(&mut self, _: Self::OutboundOpenInfo, _: io::Error) {} @@ -337,7 +337,7 @@ pub struct MapInEvent { impl ProtocolsHandler for MapInEvent where TProtoHandler: ProtocolsHandler, - TMap: Fn(&TNewIn) -> Option<&TProtoHandler::InEvent>, + TMap: Fn(TNewIn) -> Option, { type InEvent = TNewIn; type OutEvent = TProtoHandler::OutEvent; @@ -360,7 +360,7 @@ where } #[inline] - fn inject_event(&mut self, event: &TNewIn) { + fn inject_event(&mut self, event: TNewIn) { if let Some(event) = (self.map)(event) { self.inner.inject_event(event); } @@ -424,7 +424,7 @@ where } #[inline] - fn inject_event(&mut self, event: &Self::InEvent) { + fn inject_event(&mut self, event: Self::InEvent) { self.inner.inject_event(event) } @@ -608,7 +608,7 @@ where #[inline] fn inject_event(&mut self, event: Self::InEvent) { - self.handler.inject_event(&event); + self.handler.inject_event(event); } #[inline] diff --git a/protocols/identify/src/periodic_id_handler.rs b/protocols/identify/src/periodic_id_handler.rs index cd9e6e602a5..bc54e9c0eaf 100644 --- a/protocols/identify/src/periodic_id_handler.rs +++ b/protocols/identify/src/periodic_id_handler.rs @@ -123,7 +123,7 @@ where } #[inline] - fn inject_event(&mut self, _: &Self::InEvent) {} + fn inject_event(&mut self, _: Self::InEvent) {} #[inline] fn inject_inbound_closed(&mut self) {} diff --git a/protocols/ping/src/dial_handler.rs b/protocols/ping/src/dial_handler.rs index c62b921d5dd..bb7ae0b05c8 100644 --- a/protocols/ping/src/dial_handler.rs +++ b/protocols/ping/src/dial_handler.rs @@ -184,7 +184,7 @@ where } } - fn inject_event(&mut self, _: &Self::InEvent) {} + fn inject_event(&mut self, _: Self::InEvent) {} fn inject_inbound_closed(&mut self) {} diff --git a/protocols/ping/src/listen_handler.rs b/protocols/ping/src/listen_handler.rs index 5684b4ab0f3..04c0c6a6278 100644 --- a/protocols/ping/src/listen_handler.rs +++ b/protocols/ping/src/listen_handler.rs @@ -98,7 +98,7 @@ where } #[inline] - fn inject_event(&mut self, _: &Self::InEvent) {} + fn inject_event(&mut self, _: Self::InEvent) {} #[inline] fn inject_inbound_closed(&mut self) {} From 61f280f3efb038bd3178a2b7a7fc0ec08e558eec Mon Sep 17 00:00:00 2001 From: James Ray <16969914+jamesray1@users.noreply.github.com> Date: Thu, 8 Nov 2018 03:01:33 +1100 Subject: [PATCH 16/20] Fix grammar in core/nodes etc. (#608) * Fix grammar * Nothing we can *do* except ... * Implementer * Implementation --- core/src/nodes/node.rs | 2 +- muxers/mplex/src/lib.rs | 2 +- transports/tcp/src/lib.rs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/nodes/node.rs b/core/src/nodes/node.rs index 6d418c26083..181c58efd74 100644 --- a/core/src/nodes/node.rs +++ b/core/src/nodes/node.rs @@ -25,7 +25,7 @@ use std::fmt; use std::io::Error as IoError; use std::sync::Arc; -// Implementor notes +// Implementation notes // ================= // // In order to minimize the risk of bugs in higher-level code, we want to avoid as much as diff --git a/muxers/mplex/src/lib.rs b/muxers/mplex/src/lib.rs index 14b2445162a..12ff37bc7e2 100644 --- a/muxers/mplex/src/lib.rs +++ b/muxers/mplex/src/lib.rs @@ -164,7 +164,7 @@ pub struct Multiplex { // Struct shared throughout the implementation. struct MultiplexInner { - // Errored that happend earlier. Should poison any attempt to use this `MultiplexError`. + // Error that happened earlier. Should poison any attempt to use this `MultiplexError`. error: Result<(), IoError>, // Underlying stream. inner: executor::Spawn>>, diff --git a/transports/tcp/src/lib.rs b/transports/tcp/src/lib.rs index e0b7ab42f3e..2d38a91235c 100644 --- a/transports/tcp/src/lib.rs +++ b/transports/tcp/src/lib.rs @@ -310,7 +310,7 @@ impl Stream for TcpListenStream { .expect("generating a multiaddr from a socket addr never fails"), Err(err) => { // If we can't get the address of the newly-opened socket, there's - // nothing we can except ignore this connection attempt. + // nothing we can do except ignore this connection attempt. error!("Ignored incoming because could't determine its \ address: {:?}", err); continue From c3a92472bbedb9dcfcc94ade9d8b09b8f3e81554 Mon Sep 17 00:00:00 2001 From: David Date: Wed, 7 Nov 2018 18:14:47 +0100 Subject: [PATCH 17/20] Use chashmap from crates (#615) --- stores/datastore/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stores/datastore/Cargo.toml b/stores/datastore/Cargo.toml index 9e0c494f141..3ca0ad2b808 100644 --- a/stores/datastore/Cargo.toml +++ b/stores/datastore/Cargo.toml @@ -6,7 +6,7 @@ license = "MIT" [dependencies] base64 = "0.7" -chashmap = { git = "https://github.com/redox-os/tfs" } +chashmap = "2.2" futures = "0.1" serde = "1.0" serde_json = "1.0" From 8377a2f50deaff68694d1c0ab438fe7fb152a26d Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Wed, 7 Nov 2018 18:30:21 +0100 Subject: [PATCH 18/20] Remove dependency on tokio_current_thread (#606) --- Cargo.toml | 1 - src/lib.rs | 2 -- 2 files changed, 3 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index bdd25c539a4..c42bbcb22ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -35,7 +35,6 @@ tokio-io = "0.1" [target.'cfg(not(target_os = "emscripten"))'.dependencies] libp2p-dns = { path = "./transports/dns" } libp2p-tcp-transport = { path = "./transports/tcp" } -tokio-current-thread = "0.1" [target.'cfg(target_os = "emscripten")'.dependencies] stdweb = { version = "0.1.3", default-features = false } diff --git a/src/lib.rs b/src/lib.rs index 1962a60c447..98f50832798 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -132,8 +132,6 @@ pub extern crate bytes; pub extern crate futures; -#[cfg(not(target_os = "emscripten"))] -pub extern crate tokio_current_thread; pub extern crate multiaddr; pub extern crate multihash; pub extern crate tokio_io; From c04d0fe7972f6117157d4e6ec6ef23ed8ec07ee6 Mon Sep 17 00:00:00 2001 From: Pierre Krieger Date: Wed, 7 Nov 2018 19:29:07 +0100 Subject: [PATCH 19/20] Add back the Swarm (#613) --- core/src/lib.rs | 1 + core/src/nodes/mod.rs | 2 + core/src/nodes/raw_swarm.rs | 5 +- core/src/nodes/swarm.rs | 304 ++++++++++++++++++++++++++++++++++++ core/src/topology/mod.rs | 62 ++++++++ 5 files changed, 373 insertions(+), 1 deletion(-) create mode 100644 core/src/nodes/swarm.rs create mode 100644 core/src/topology/mod.rs diff --git a/core/src/lib.rs b/core/src/lib.rs index dbe2426a808..11d279b404d 100644 --- a/core/src/lib.rs +++ b/core/src/lib.rs @@ -212,6 +212,7 @@ mod tests; pub mod either; pub mod muxing; pub mod nodes; +pub mod topology; pub mod transport; pub mod upgrade; diff --git a/core/src/nodes/mod.rs b/core/src/nodes/mod.rs index 2636de3567a..3db430bb346 100644 --- a/core/src/nodes/mod.rs +++ b/core/src/nodes/mod.rs @@ -25,8 +25,10 @@ pub mod listeners; pub mod node; pub mod protocols_handler; pub mod raw_swarm; +pub mod swarm; pub use self::node::Substream; pub use self::handled_node::{NodeHandlerEvent, NodeHandlerEndpoint}; pub use self::protocols_handler::{ProtocolsHandler, ProtocolsHandlerEvent}; pub use self::raw_swarm::{ConnectedPoint, Peer, RawSwarm, RawSwarmEvent}; +pub use self::swarm::{Swarm, NetworkBehavior, NetworkBehaviorAction}; diff --git a/core/src/nodes/raw_swarm.rs b/core/src/nodes/raw_swarm.rs index 3e2261a3e68..f2fb5148385 100644 --- a/core/src/nodes/raw_swarm.rs +++ b/core/src/nodes/raw_swarm.rs @@ -1130,7 +1130,10 @@ where TOutEvent: Send + 'static, { let mut addrs = addrs.into_iter(); - let first = addrs.next().unwrap(); // TODO: bad + let first = match addrs.next() { + Some(f) => f, + None => return Err(self) + }; let rest = addrs.collect(); self.connect_inner(handler, first, rest) } diff --git a/core/src/nodes/swarm.rs b/core/src/nodes/swarm.rs new file mode 100644 index 00000000000..7cc92b5c867 --- /dev/null +++ b/core/src/nodes/swarm.rs @@ -0,0 +1,304 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use futures::prelude::*; +use muxing::StreamMuxer; +use nodes::handled_node::NodeHandler; +use nodes::node::Substream; +use nodes::protocols_handler::{NodeHandlerWrapper, ProtocolsHandler}; +use nodes::raw_swarm::{RawSwarm, RawSwarmEvent, ConnectedPoint}; +use std::{io, ops::{Deref, DerefMut}}; +use topology::Topology; +use {ConnectionUpgrade, Multiaddr, PeerId, Transport}; + +/// Contains the state of the network, plus the way it should behave. +pub struct Swarm +where TTransport: Transport, + TBehaviour: NetworkBehavior, +{ + raw_swarm: RawSwarm< + TTransport, + <::ProtocolsHandler as ProtocolsHandler>::InEvent, + <::ProtocolsHandler as ProtocolsHandler>::OutEvent, + NodeHandlerWrapper, + >, + + /// Handles which nodes to connect to and how to handle the events sent back by the protocol + /// handlers. + behaviour: TBehaviour, + + /// Holds the topology of the network. In other words all the nodes that we think exist, even + /// if we're not connected to them. + topology: TTopology, +} + +impl Deref for Swarm +where TTransport: Transport, + TBehaviour: NetworkBehavior, +{ + type Target = TBehaviour; + + #[inline] + fn deref(&self) -> &Self::Target { + &self.behaviour + } +} + +impl DerefMut for Swarm +where TTransport: Transport, + TBehaviour: NetworkBehavior, +{ + #[inline] + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.behaviour + } +} + +impl Swarm +where TBehaviour: NetworkBehavior, + TMuxer: StreamMuxer + Send + Sync + 'static, + ::OutboundSubstream: Send + 'static, + ::Substream: Send + 'static, + TTransport: Transport + Clone, + TTransport::Dial: Send + 'static, + TTransport::Listener: Send + 'static, + TTransport::ListenerUpgrade: Send + 'static, + TBehaviour::ProtocolsHandler: ProtocolsHandler> + Send + 'static, + ::InEvent: Send + 'static, + ::OutEvent: Send + 'static, + ::Protocol: ConnectionUpgrade> + Send + 'static, + <::Protocol as ConnectionUpgrade>>::Future: Send + 'static, + <::Protocol as ConnectionUpgrade>>::NamesIter: Clone + Send + 'static, + <::Protocol as ConnectionUpgrade>>::UpgradeIdentifier: Send + 'static, + ::OutboundOpenInfo: Send + 'static, // TODO: shouldn't be necessary + as NodeHandler>::OutboundOpenInfo: Send + 'static, // TODO: shouldn't be necessary + TTopology: Topology, +{ + /// Builds a new `Swarm`. + #[inline] + pub fn new(transport: TTransport, behaviour: TBehaviour, topology: TTopology) -> Self { + let raw_swarm = RawSwarm::new(transport); + Swarm { + raw_swarm, + behaviour, + topology, + } + } + + /// Returns the transport passed when building this object. + #[inline] + pub fn transport(me: &Self) -> &TTransport { + me.raw_swarm.transport() + } + + /// Starts listening on the given address. + /// + /// Returns an error if the address is not supported. + /// On success, returns an alternative version of the address. + #[inline] + pub fn listen_on(me: &mut Self, addr: Multiaddr) -> Result { + me.raw_swarm.listen_on(addr) + } + + /// Tries to dial the given address. + /// + /// Returns an error if the address is not supported. + #[inline] + pub fn dial_addr(me: &mut Self, addr: Multiaddr) -> Result<(), Multiaddr> { + let handler = me.behaviour.new_handler(); + me.raw_swarm.dial(addr, handler.into_node_handler()) + } + + /// Tries to reach the given peer using the elements in the topology. + /// + /// Has no effect if we are already connected to that peer, or if no address is known for the + /// peer. + #[inline] + pub fn dial(me: &mut Self, peer_id: PeerId) { + let addrs = me.topology.addresses_of_peer(&peer_id); + let handler = me.behaviour.new_handler().into_node_handler(); + if let Some(peer) = me.raw_swarm.peer(peer_id).as_not_connected() { + let _ = peer.connect_iter(addrs, handler); + } + } + + /// Returns an iterator that produces the list of addresses we're listening on. + #[inline] + pub fn listeners(me: &Self) -> impl Iterator { + RawSwarm::listeners(&me.raw_swarm) + } + + /// Returns the topology of the swarm. + #[inline] + pub fn topology(me: &Self) -> &TTopology { + &me.topology + } + + /// Returns the topology of the swarm. + #[inline] + pub fn topology_mut(me: &mut Self) -> &mut TTopology { + &mut me.topology + } +} + +impl Stream for Swarm +where TBehaviour: NetworkBehavior, + TMuxer: StreamMuxer + Send + Sync + 'static, + ::OutboundSubstream: Send + 'static, + ::Substream: Send + 'static, + TTransport: Transport + Clone, + TTransport::Dial: Send + 'static, + TTransport::Listener: Send + 'static, + TTransport::ListenerUpgrade: Send + 'static, + TBehaviour::ProtocolsHandler: ProtocolsHandler> + Send + 'static, + ::InEvent: Send + 'static, + ::OutEvent: Send + 'static, + ::Protocol: ConnectionUpgrade> + Send + 'static, + <::Protocol as ConnectionUpgrade>>::Future: Send + 'static, + <::Protocol as ConnectionUpgrade>>::NamesIter: Clone + Send + 'static, + <::Protocol as ConnectionUpgrade>>::UpgradeIdentifier: Send + 'static, + ::OutboundOpenInfo: Send + 'static, // TODO: shouldn't be necessary + as NodeHandler>::OutboundOpenInfo: Send + 'static, // TODO: shouldn't be necessary + TTopology: Topology, +{ + type Item = TBehaviour::OutEvent; + type Error = io::Error; + + #[inline] + fn poll(&mut self) -> Poll, io::Error> { + loop { + let mut raw_swarm_not_ready = false; + + match self.raw_swarm.poll() { + Async::NotReady => raw_swarm_not_ready = true, + Async::Ready(RawSwarmEvent::NodeEvent { peer_id, event }) => { + self.behaviour.inject_node_event(peer_id, event); + }, + Async::Ready(RawSwarmEvent::Connected { peer_id, endpoint }) => { + self.behaviour.inject_connected(peer_id, endpoint); + }, + Async::Ready(RawSwarmEvent::NodeClosed { peer_id, endpoint }) | + Async::Ready(RawSwarmEvent::NodeError { peer_id, endpoint, .. }) => { + self.behaviour.inject_disconnected(&peer_id, endpoint); + }, + Async::Ready(RawSwarmEvent::Replaced { peer_id, closed_endpoint, endpoint }) => { + self.behaviour.inject_disconnected(&peer_id, closed_endpoint); + self.behaviour.inject_connected(peer_id, endpoint); + }, + Async::Ready(RawSwarmEvent::IncomingConnection(incoming)) => { + let handler = self.behaviour.new_handler(); + incoming.accept(handler.into_node_handler()); + }, + Async::Ready(RawSwarmEvent::ListenerClosed { .. }) => {}, + Async::Ready(RawSwarmEvent::IncomingConnectionError { .. }) => {}, + Async::Ready(RawSwarmEvent::DialError { .. }) => {}, + Async::Ready(RawSwarmEvent::UnknownPeerDialError { .. }) => {}, + } + + match self.behaviour.poll() { + Async::NotReady if raw_swarm_not_ready => return Ok(Async::NotReady), + Async::NotReady => (), + Async::Ready(NetworkBehaviorAction::GenerateEvent(event)) => { + return Ok(Async::Ready(Some(event))); + }, + Async::Ready(NetworkBehaviorAction::DialAddress { address }) => { + let _ = Swarm::dial_addr(self, address); + }, + Async::Ready(NetworkBehaviorAction::DialPeer { peer_id }) => { + Swarm::dial(self, peer_id) + }, + Async::Ready(NetworkBehaviorAction::SendEvent { peer_id, event }) => { + if let Some(mut peer) = self.raw_swarm.peer(peer_id).as_connected() { + peer.send_event(event); + } + }, + } + } + } +} + +/// A behaviour for the network. Allows customizing the swarm. +/// +/// This trait has been designed to be composable. Multiple implementations can be combined into +/// one that handles all the behaviours at once. +pub trait NetworkBehavior { + /// Handler for all the protocols the network supports. + type ProtocolsHandler: ProtocolsHandler; + /// Event generated by the swarm. + type OutEvent; + + /// Builds a new `ProtocolsHandler`. + fn new_handler(&mut self) -> Self::ProtocolsHandler; + + /// Indicates the behaviour that we connected to the node with the given peer id through the + /// given endpoint. + fn inject_connected(&mut self, peer_id: PeerId, endpoint: ConnectedPoint); + + /// Indicates the behaviour that we disconnected from the node with the given peer id. The + /// endpoint is the one we used to be connected to. + fn inject_disconnected(&mut self, peer_id: &PeerId, endpoint: ConnectedPoint); + + /// Indicates the behaviour that the node with the given peer id has generated an event for + /// us. + /// + /// > **Note**: This method is only called for events generated by the protocols handler. + fn inject_node_event( + &mut self, + peer_id: PeerId, + event: ::OutEvent + ); + + /// Polls for things that swarm should do. + /// + /// This API mimics the API of the `Stream` trait. + fn poll(&mut self) -> Async::InEvent, Self::OutEvent>>; +} + +/// Action to perform. +#[derive(Debug, Clone)] +pub enum NetworkBehaviorAction { + /// Generate an event for the outside. + GenerateEvent(TOutEvent), + + // TODO: report new raw connection for usage after intercepting an address dial + + /// Instructs the swarm to dial the given multiaddress without any expectation of a peer id. + DialAddress { + /// The address to dial. + address: Multiaddr, + }, + + /// Instructs the swarm to try reach the given peer. + DialPeer { + /// The peer to try reach. + peer_id: PeerId, + }, + + /// If we're connected to the given peer, sends a message to the protocol handler. + /// + /// If we're not connected to this peer, does nothing. If necessary, the implementation of + /// `NetworkBehaviour` is supposed to track which peers we are connected to. + SendEvent { + /// The peer which to send the message to. + peer_id: PeerId, + /// Event to send to the peer. + event: TInEvent, + }, +} diff --git a/core/src/topology/mod.rs b/core/src/topology/mod.rs new file mode 100644 index 00000000000..f27a6512d37 --- /dev/null +++ b/core/src/topology/mod.rs @@ -0,0 +1,62 @@ +// Copyright 2018 Parity Technologies (UK) Ltd. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the "Software"), +// to deal in the Software without restriction, including without limitation +// the rights to use, copy, modify, merge, publish, distribute, sublicense, +// and/or sell copies of the Software, and to permit persons to whom the +// Software is furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER +// DEALINGS IN THE SOFTWARE. + +use std::collections::HashMap; +use {Multiaddr, PeerId}; + +/// Storage for the network topology. +pub trait Topology { + /// Adds a discovered address to the topology. + fn add_discovered_address(&mut self, peer: &PeerId, addr: Multiaddr); + /// Returns the addresses to try use to reach the given peer. + fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec; +} + +/// Topology of the network stored in memory. +pub struct MemoryTopology { + list: HashMap>, +} + +impl MemoryTopology { + /// Creates an empty topology. + #[inline] + pub fn empty() -> MemoryTopology { + MemoryTopology { + list: Default::default() + } + } +} + +impl Default for MemoryTopology { + #[inline] + fn default() -> MemoryTopology { + MemoryTopology::empty() + } +} + +impl Topology for MemoryTopology { + fn add_discovered_address(&mut self, peer: &PeerId, addr: Multiaddr) { + self.list.entry(peer.clone()).or_insert_with(|| Vec::new()).push(addr); + } + + fn addresses_of_peer(&mut self, peer: &PeerId) -> Vec { + self.list.get(peer).map(|v| v.clone()).unwrap_or(Vec::new()) + } +} From d961e656a74d1bab5366d371a06f9e10d5f4a6c5 Mon Sep 17 00:00:00 2001 From: David Date: Thu, 8 Nov 2018 14:55:54 +0100 Subject: [PATCH 20/20] Use vecdeque for fair polling (#610) * WIP * Use pop_back() Improve tests * Avoid unwrapping * Update core/src/nodes/listeners.rs Co-Authored-By: dvdplm --- core/src/nodes/listeners.rs | 118 ++++++++++++++++++++++-------------- 1 file changed, 72 insertions(+), 46 deletions(-) diff --git a/core/src/nodes/listeners.rs b/core/src/nodes/listeners.rs index c47a05ec6f8..66ea0e894b3 100644 --- a/core/src/nodes/listeners.rs +++ b/core/src/nodes/listeners.rs @@ -22,6 +22,7 @@ use futures::prelude::*; use std::fmt; use void::Void; use {Multiaddr, Transport}; +use std::collections::VecDeque; /// Implementation of `futures::Stream` that allows listening on multiaddresses. /// @@ -81,7 +82,7 @@ where /// Transport used to spawn listeners. transport: TTrans, /// All the active listeners. - listeners: Vec>, + listeners: VecDeque>, } /// A single active listener. @@ -131,7 +132,7 @@ where pub fn new(transport: TTrans) -> Self { ListenersStream { transport, - listeners: Vec::new(), + listeners: VecDeque::new(), } } @@ -141,7 +142,7 @@ where pub fn with_capacity(transport: TTrans, capacity: usize) -> Self { ListenersStream { transport, - listeners: Vec::with_capacity(capacity), + listeners: VecDeque::with_capacity(capacity), } } @@ -158,7 +159,7 @@ where .listen_on(addr) .map_err(|(_, addr)| addr)?; - self.listeners.push(Listener { + self.listeners.push_back(Listener { listener, address: new_addr.clone(), }); @@ -181,15 +182,17 @@ where /// Provides an API similar to `Stream`, except that it cannot error. pub fn poll(&mut self) -> Async> { // We remove each element from `listeners` one by one and add them back. - for n in (0..self.listeners.len()).rev() { - let mut listener = self.listeners.swap_remove(n); + let mut remaining = self.listeners.len(); + while let Some(mut listener) = self.listeners.pop_back() { match listener.listener.poll() { Ok(Async::NotReady) => { - self.listeners.push(listener); + self.listeners.push_front(listener); + remaining -= 1; + if remaining == 0 { break } } Ok(Async::Ready(Some((upgrade, send_back_addr)))) => { let listen_addr = listener.address.clone(); - self.listeners.push(listener); + self.listeners.push_front(listener); return Async::Ready(ListenersEvent::Incoming { upgrade, listen_addr, @@ -379,14 +382,17 @@ mod tests { } #[test] - fn listener_stream_poll_with_ready_listeners_is_ready() { - let mut t = DummyTransport::new(); - t.set_initial_listener_state(ListenerState::Ok(Async::Ready(Some(1)))); + fn listener_stream_poll_with_ready_listeners_yields_upgrade() { + let mut transport = DummyTransport::new(); + transport.set_initial_listener_state(ListenerState::Ok(Async::Ready(Some(1)))); + let mut ls = ListenersStream::new(transport); + let addr1 = "/ip4/127.0.0.1/tcp/1234".parse::().expect("bad multiaddr"); let addr2 = "/ip4/127.0.0.2/tcp/4321".parse::().expect("bad multiaddr"); - let mut ls = ListenersStream::new(t); - ls.listen_on(addr1).expect("listen_on failed"); - ls.listen_on(addr2).expect("listen_on failed"); + + ls.listen_on(addr1).expect("listen_on works"); + ls.listen_on(addr2).expect("listen_on works"); + assert_eq!(ls.listeners.len(), 2); assert_matches!(ls.poll(), Async::Ready(listeners_event) => { assert_matches!(listeners_event, ListenersEvent::Incoming{mut upgrade, listen_addr, ..} => { @@ -396,21 +402,7 @@ mod tests { }); }) }); - // TODO: When several listeners are continuously Async::Ready – - // admittetdly a corner case – the last one is processed first and then - // put back *last* on the pile. This means that at the next poll() it - // will get polled again and if it always has data to yield, it will - // effectively block all other listeners from being "heard". One way - // around this is to switch to using a `VecDeque` to keep the listeners - // collection, and instead of pushing the processed item to the end of - // the list, stick it on top so that it'll be processed *last* instead - // during the next poll. This might also get us a performance win as - // even in the normal case, the most recently polled listener is more - // unlikely to have anything to yield than the others so we might avoid - // a few unneeded poll calls. - - // Make the second listener return NotReady so we get the first listener next poll() - set_listener_state(&mut ls, 1, ListenerState::Ok(Async::NotReady)); + assert_matches!(ls.poll(), Async::Ready(listeners_event) => { assert_matches!(listeners_event, ListenersEvent::Incoming{mut upgrade, listen_addr, ..} => { assert_eq!(listen_addr.to_string(), "/ip4/127.0.0.1/tcp/1234"); @@ -419,7 +411,18 @@ mod tests { }); }) }); - assert_eq!(ls.listeners.len(), 2); + + set_listener_state(&mut ls, 1, ListenerState::Ok(Async::NotReady)); + assert_matches!(ls.poll(), Async::Ready(listeners_event) => { + assert_matches!(listeners_event, ListenersEvent::Incoming{mut upgrade, listen_addr, ..} => { + assert_eq!(listen_addr.to_string(), "/ip4/127.0.0.1/tcp/1234"); + assert_matches!(upgrade.poll().unwrap(), Async::Ready(tup) => { + // Second time we poll this Listener, so we get `2` from the transport Stream + assert_eq!(tup, 2) + }); + }) + }); + } #[test] @@ -450,48 +453,71 @@ mod tests { } #[test] - fn listener_stream_poll_chatty_listeners_may_drown_others() { + fn listener_stream_poll_chatty_listeners_each_get_their_turn() { let mut t = DummyTransport::new(); t.set_initial_listener_state(ListenerState::Ok(Async::Ready(Some(1)))); let mut ls = ListenersStream::new(t); + // Create 4 Listeners for n in 0..4 { - let addr = format!("/ip4/127.0.0.{}/tcp/123{}", n, n).parse::().expect("bad multiaddr"); + let addr = format!("/ip4/127.0.0.{}/tcp/{}", n, n).parse::().expect("bad multiaddr"); ls.listen_on(addr).expect("listen_on failed"); } - // polling processes listeners in reverse order - // Only the last listener ever gets processed - for _n in 0..10 { + // Poll() processes listeners in reverse order. Each listener is polled + // in turn. + for n in (0..4).rev() { assert_matches!(ls.poll(), Async::Ready(ListenersEvent::Incoming{listen_addr, ..}) => { - assert_eq!(listen_addr.to_string(), "/ip4/127.0.0.3/tcp/1233") + assert_eq!(listen_addr.to_string(), format!("/ip4/127.0.0.{}/tcp/{}", n, n)) }) } - // Make last listener NotReady so now only the third listener is processed + // Doing it again yields them in the same order + for n in (0..4).rev() { + assert_matches!(ls.poll(), Async::Ready(ListenersEvent::Incoming{listen_addr, ..}) => { + assert_eq!(listen_addr.to_string(), format!("/ip4/127.0.0.{}/tcp/{}", n, n)) + }) + } + + // Make last listener NotReady; it will become the first element and + // retried after trying the other Listeners. set_listener_state(&mut ls, 3, ListenerState::Ok(Async::NotReady)); - for _n in 0..10 { + for n in (0..3).rev() { + assert_matches!(ls.poll(), Async::Ready(ListenersEvent::Incoming{listen_addr, ..}) => { + assert_eq!(listen_addr.to_string(), format!("/ip4/127.0.0.{}/tcp/{}", n, n)) + }) + } + for n in (0..3).rev() { assert_matches!(ls.poll(), Async::Ready(ListenersEvent::Incoming{listen_addr, ..}) => { - assert_eq!(listen_addr.to_string(), "/ip4/127.0.0.2/tcp/1232") + assert_eq!(listen_addr.to_string(), format!("/ip4/127.0.0.{}/tcp/{}", n, n)) + }) + } + + // Turning the last listener back on means we now have 4 "good" + // listeners, and each get their turn. + set_listener_state(&mut ls, 3, ListenerState::Ok(Async::Ready(Some(2)))); + for n in (0..4).rev() { + assert_matches!(ls.poll(), Async::Ready(ListenersEvent::Incoming{listen_addr, ..}) => { + assert_eq!(listen_addr.to_string(), format!("/ip4/127.0.0.{}/tcp/{}", n, n)) }) } } #[test] - fn listener_stream_poll_processes_listeners_as_expected_if_they_are_not_yielding_continuously() { + fn listener_stream_poll_processes_listeners_in_turn() { let mut t = DummyTransport::new(); t.set_initial_listener_state(ListenerState::Ok(Async::Ready(Some(1)))); let mut ls = ListenersStream::new(t); for n in 0..4 { - let addr = format!("/ip4/127.0.0.{}/tcp/123{}", n, n).parse::().expect("bad multiaddr"); + let addr = format!("/ip4/127.0.0.{}/tcp/{}", n, n).parse::().expect("bad multiaddr"); ls.listen_on(addr).expect("listen_on failed"); } - // If the listeners do not yield items continuously (the normal case) we - // process them in the expected, reverse, order. + for n in (0..4).rev() { assert_matches!(ls.poll(), Async::Ready(ListenersEvent::Incoming{listen_addr, ..}) => { - assert_eq!(listen_addr.to_string(), format!("/ip4/127.0.0.{}/tcp/123{}", n, n)); + assert_eq!(listen_addr.to_string(), format!("/ip4/127.0.0.{}/tcp/{}", n, n)); }); - // kick the last listener (current) to NotReady state - set_listener_state(&mut ls, 3, ListenerState::Ok(Async::NotReady)); + set_listener_state(&mut ls, 0, ListenerState::Ok(Async::NotReady)); } + // All Listeners are NotReady, so poll yields NotReady + assert_matches!(ls.poll(), Async::NotReady); } }