mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-06-20 05:16:35 +00:00
Refactor the Identify protocol. (#1231)
* Refactor the Identify protocol. Thereby updating the documentation. The low-level protocol and handler modules are no longer exposed and some constructors of the IdentifyEvent renamed. * Update protocols/identify/src/protocol.rs Co-Authored-By: Pierre Krieger <pierre.krieger1708@gmail.com>
This commit is contained in:
@ -18,9 +18,13 @@
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use crate::protocol::{RemoteInfo, IdentifyProtocolConfig};
|
||||
use crate::protocol::{RemoteInfo, IdentifyProtocolConfig, ReplySubstream};
|
||||
use futures::prelude::*;
|
||||
use libp2p_core::upgrade::{DeniedUpgrade, OutboundUpgrade};
|
||||
use libp2p_core::upgrade::{
|
||||
InboundUpgrade,
|
||||
OutboundUpgrade,
|
||||
Negotiated
|
||||
};
|
||||
use libp2p_swarm::{
|
||||
KeepAlive,
|
||||
SubstreamProtocol,
|
||||
@ -28,10 +32,11 @@ use libp2p_swarm::{
|
||||
ProtocolsHandlerEvent,
|
||||
ProtocolsHandlerUpgrErr
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{io, marker::PhantomData, time::Duration};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use wasm_timer::{Delay, Instant};
|
||||
use void::{Void, unreachable};
|
||||
use void::Void;
|
||||
|
||||
/// Delay between the moment we connect and the first time we identify.
|
||||
const DELAY_TO_FIRST_ID: Duration = Duration::from_millis(500);
|
||||
@ -40,67 +45,73 @@ 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 PeriodicIdHandler<TSubstream> {
|
||||
/// Protocol handler for sending and receiving identification requests.
|
||||
///
|
||||
/// Outbound requests are sent periodically. The handler performs expects
|
||||
/// at least one identification request to be answered by the remote before
|
||||
/// permitting the underlying connection to be closed.
|
||||
pub struct IdentifyHandler<TSubstream> {
|
||||
/// Configuration for the protocol.
|
||||
config: IdentifyProtocolConfig,
|
||||
|
||||
/// If `Some`, we successfully generated an `PeriodicIdHandlerEvent` and we will produce
|
||||
/// it the next time `poll()` is invoked.
|
||||
pending_result: Option<PeriodicIdHandlerEvent>,
|
||||
/// Pending events to yield.
|
||||
events: SmallVec<[IdentifyHandlerEvent<TSubstream>; 4]>,
|
||||
|
||||
/// Future that fires when we need to identify the node again.
|
||||
next_id: Delay,
|
||||
|
||||
/// If `true`, we have started an identification of the remote at least once in the past.
|
||||
first_id_happened: bool,
|
||||
/// Whether the handler should keep the connection alive.
|
||||
keep_alive: KeepAlive,
|
||||
|
||||
/// Marker for strong typing.
|
||||
marker: PhantomData<TSubstream>,
|
||||
}
|
||||
|
||||
/// Event produced by the periodic identifier.
|
||||
/// Event produced by the `IdentifyHandler`.
|
||||
#[derive(Debug)]
|
||||
pub enum PeriodicIdHandlerEvent {
|
||||
pub enum IdentifyHandlerEvent<TSubstream> {
|
||||
/// We obtained identification information from the remote
|
||||
Identified(RemoteInfo),
|
||||
/// We received a request for identification.
|
||||
Identify(ReplySubstream<Negotiated<TSubstream>>),
|
||||
/// Failed to identify the remote.
|
||||
IdentificationError(ProtocolsHandlerUpgrErr<io::Error>),
|
||||
}
|
||||
|
||||
impl<TSubstream> PeriodicIdHandler<TSubstream> {
|
||||
/// Builds a new `PeriodicIdHandler`.
|
||||
#[inline]
|
||||
impl<TSubstream> IdentifyHandler<TSubstream> {
|
||||
/// Creates a new `IdentifyHandler`.
|
||||
pub fn new() -> Self {
|
||||
PeriodicIdHandler {
|
||||
IdentifyHandler {
|
||||
config: IdentifyProtocolConfig,
|
||||
pending_result: None,
|
||||
events: SmallVec::new(),
|
||||
next_id: Delay::new(Instant::now() + DELAY_TO_FIRST_ID),
|
||||
first_id_happened: false,
|
||||
keep_alive: KeepAlive::Yes,
|
||||
marker: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TSubstream> ProtocolsHandler for PeriodicIdHandler<TSubstream>
|
||||
impl<TSubstream> ProtocolsHandler for IdentifyHandler<TSubstream>
|
||||
where
|
||||
TSubstream: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type InEvent = Void;
|
||||
type OutEvent = PeriodicIdHandlerEvent;
|
||||
type OutEvent = IdentifyHandlerEvent<TSubstream>;
|
||||
type Error = wasm_timer::Error;
|
||||
type Substream = TSubstream;
|
||||
type InboundProtocol = DeniedUpgrade;
|
||||
type InboundProtocol = IdentifyProtocolConfig;
|
||||
type OutboundProtocol = IdentifyProtocolConfig;
|
||||
type OutboundOpenInfo = ();
|
||||
|
||||
#[inline]
|
||||
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol> {
|
||||
SubstreamProtocol::new(DeniedUpgrade)
|
||||
SubstreamProtocol::new(self.config.clone())
|
||||
}
|
||||
|
||||
fn inject_fully_negotiated_inbound(&mut self, protocol: Void) {
|
||||
unreachable(protocol)
|
||||
fn inject_fully_negotiated_inbound(
|
||||
&mut self,
|
||||
protocol: <Self::InboundProtocol as InboundUpgrade<TSubstream>>::Output
|
||||
) {
|
||||
self.events.push(IdentifyHandlerEvent::Identify(protocol))
|
||||
}
|
||||
|
||||
fn inject_fully_negotiated_outbound(
|
||||
@ -108,42 +119,39 @@ where
|
||||
protocol: <Self::OutboundProtocol as OutboundUpgrade<TSubstream>>::Output,
|
||||
_info: Self::OutboundOpenInfo,
|
||||
) {
|
||||
self.pending_result = Some(PeriodicIdHandlerEvent::Identified(protocol));
|
||||
self.first_id_happened = true;
|
||||
self.events.push(IdentifyHandlerEvent::Identified(protocol));
|
||||
self.keep_alive = KeepAlive::No;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inject_event(&mut self, _: Self::InEvent) {}
|
||||
|
||||
#[inline]
|
||||
fn inject_dial_upgrade_error(&mut self, _: Self::OutboundOpenInfo, err: ProtocolsHandlerUpgrErr<<Self::OutboundProtocol as OutboundUpgrade<Self::Substream>>::Error>) {
|
||||
self.pending_result = Some(PeriodicIdHandlerEvent::IdentificationError(err));
|
||||
self.first_id_happened = true;
|
||||
fn inject_dial_upgrade_error(
|
||||
&mut self,
|
||||
_info: Self::OutboundOpenInfo,
|
||||
err: ProtocolsHandlerUpgrErr<
|
||||
<Self::OutboundProtocol as OutboundUpgrade<Self::Substream>>::Error
|
||||
>
|
||||
) {
|
||||
self.events.push(IdentifyHandlerEvent::IdentificationError(err));
|
||||
self.keep_alive = KeepAlive::No;
|
||||
self.next_id.reset(Instant::now() + TRY_AGAIN_ON_ERR);
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn connection_keep_alive(&self) -> KeepAlive {
|
||||
if self.first_id_happened {
|
||||
KeepAlive::No
|
||||
} else {
|
||||
KeepAlive::Yes
|
||||
}
|
||||
self.keep_alive
|
||||
}
|
||||
|
||||
fn poll(
|
||||
&mut self,
|
||||
) -> Poll<
|
||||
fn poll(&mut self) -> Poll<
|
||||
ProtocolsHandlerEvent<
|
||||
Self::OutboundProtocol,
|
||||
Self::OutboundOpenInfo,
|
||||
PeriodicIdHandlerEvent,
|
||||
IdentifyHandlerEvent<TSubstream>,
|
||||
>,
|
||||
Self::Error,
|
||||
> {
|
||||
if let Some(pending_result) = self.pending_result.take() {
|
||||
if !self.events.is_empty() {
|
||||
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
|
||||
pending_result,
|
||||
self.events.remove(0),
|
||||
)));
|
||||
}
|
||||
|
@ -18,27 +18,23 @@
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
use crate::listen_handler::IdentifyListenHandler;
|
||||
use crate::periodic_id_handler::{PeriodicIdHandler, PeriodicIdHandlerEvent};
|
||||
use crate::protocol::{IdentifyInfo, IdentifySender, IdentifySenderFuture};
|
||||
use crate::handler::{IdentifyHandler, IdentifyHandlerEvent};
|
||||
use crate::protocol::{IdentifyInfo, ReplySubstream, ReplyFuture};
|
||||
use futures::prelude::*;
|
||||
use libp2p_core::{
|
||||
ConnectedPoint,
|
||||
Multiaddr,
|
||||
PeerId,
|
||||
PublicKey,
|
||||
either::EitherOutput,
|
||||
upgrade::Negotiated
|
||||
upgrade::{Negotiated, UpgradeError}
|
||||
};
|
||||
use libp2p_swarm::{
|
||||
NetworkBehaviour,
|
||||
NetworkBehaviourAction,
|
||||
PollParameters,
|
||||
ProtocolsHandler,
|
||||
ProtocolsHandlerSelect,
|
||||
ProtocolsHandlerUpgrErr
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use std::{collections::HashMap, collections::VecDeque, io};
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use void::Void;
|
||||
@ -54,24 +50,36 @@ pub struct Identify<TSubstream> {
|
||||
local_public_key: PublicKey,
|
||||
/// For each peer we're connected to, the observed address to send back to it.
|
||||
observed_addresses: HashMap<PeerId, Multiaddr>,
|
||||
/// List of senders to answer, with the observed multiaddr.
|
||||
to_answer: SmallVec<[(PeerId, IdentifySender<Negotiated<TSubstream>>, Multiaddr); 4]>,
|
||||
/// List of futures that send back information back to remotes.
|
||||
futures: SmallVec<[(PeerId, IdentifySenderFuture<Negotiated<TSubstream>>); 4]>,
|
||||
/// Events that need to be produced outside when polling..
|
||||
events: VecDeque<NetworkBehaviourAction<EitherOutput<Void, Void>, IdentifyEvent>>,
|
||||
/// Pending replies to send.
|
||||
pending_replies: VecDeque<Reply<TSubstream>>,
|
||||
/// Pending events to be emitted when polled.
|
||||
events: VecDeque<NetworkBehaviourAction<Void, IdentifyEvent>>,
|
||||
}
|
||||
|
||||
/// A pending reply to an inbound identification request.
|
||||
enum Reply<TSubstream> {
|
||||
/// The reply is queued for sending.
|
||||
Queued {
|
||||
peer: PeerId,
|
||||
io: ReplySubstream<Negotiated<TSubstream>>,
|
||||
observed: Multiaddr
|
||||
},
|
||||
/// The reply is being sent.
|
||||
Sending {
|
||||
peer: PeerId,
|
||||
io: ReplyFuture<Negotiated<TSubstream>>
|
||||
}
|
||||
}
|
||||
|
||||
impl<TSubstream> Identify<TSubstream> {
|
||||
/// Creates a `Identify`.
|
||||
/// Creates a new `Identify` network behaviour.
|
||||
pub fn new(protocol_version: String, agent_version: String, local_public_key: PublicKey) -> Self {
|
||||
Identify {
|
||||
protocol_version,
|
||||
agent_version,
|
||||
local_public_key,
|
||||
observed_addresses: HashMap::new(),
|
||||
to_answer: SmallVec::new(),
|
||||
futures: SmallVec::new(),
|
||||
pending_replies: VecDeque::new(),
|
||||
events: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
@ -81,11 +89,11 @@ impl<TSubstream> NetworkBehaviour for Identify<TSubstream>
|
||||
where
|
||||
TSubstream: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type ProtocolsHandler = ProtocolsHandlerSelect<IdentifyListenHandler<TSubstream>, PeriodicIdHandler<TSubstream>>;
|
||||
type ProtocolsHandler = IdentifyHandler<TSubstream>;
|
||||
type OutEvent = IdentifyEvent;
|
||||
|
||||
fn new_handler(&mut self) -> Self::ProtocolsHandler {
|
||||
IdentifyListenHandler::new().select(PeriodicIdHandler::new())
|
||||
IdentifyHandler::new()
|
||||
}
|
||||
|
||||
fn addresses_of_peer(&mut self, _: &PeerId) -> Vec<Multiaddr> {
|
||||
@ -111,31 +119,35 @@ where
|
||||
event: <Self::ProtocolsHandler as ProtocolsHandler>::OutEvent,
|
||||
) {
|
||||
match event {
|
||||
EitherOutput::Second(PeriodicIdHandlerEvent::Identified(remote)) => {
|
||||
self.events
|
||||
.push_back(NetworkBehaviourAction::GenerateEvent(IdentifyEvent::Identified {
|
||||
IdentifyHandlerEvent::Identified(remote) => {
|
||||
self.events.push_back(
|
||||
NetworkBehaviourAction::GenerateEvent(
|
||||
IdentifyEvent::Received {
|
||||
peer_id,
|
||||
info: remote.info,
|
||||
observed_addr: remote.observed_addr.clone(),
|
||||
}));
|
||||
self.events
|
||||
.push_back(NetworkBehaviourAction::ReportObservedAddr {
|
||||
self.events.push_back(
|
||||
NetworkBehaviourAction::ReportObservedAddr {
|
||||
address: remote.observed_addr,
|
||||
});
|
||||
}
|
||||
EitherOutput::First(sender) => {
|
||||
IdentifyHandlerEvent::Identify(sender) => {
|
||||
let observed = self.observed_addresses.get(&peer_id)
|
||||
.expect("We only receive events from nodes we're connected to. We insert \
|
||||
into the hashmap when we connect to a node and remove only when we \
|
||||
disconnect; QED");
|
||||
self.to_answer.push((peer_id, sender, observed.clone()));
|
||||
self.pending_replies.push_back(
|
||||
Reply::Queued {
|
||||
peer: peer_id,
|
||||
io: sender,
|
||||
observed: observed.clone()
|
||||
});
|
||||
}
|
||||
EitherOutput::Second(PeriodicIdHandlerEvent::IdentificationError(err)) => {
|
||||
self.events
|
||||
.push_back(NetworkBehaviourAction::GenerateEvent(IdentifyEvent::Error {
|
||||
peer_id,
|
||||
error: err,
|
||||
}));
|
||||
IdentifyHandlerEvent::IdentificationError(error) => {
|
||||
self.events.push_back(
|
||||
NetworkBehaviourAction::GenerateEvent(
|
||||
IdentifyEvent::Error { peer_id, error }));
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -153,10 +165,10 @@ where
|
||||
return Async::Ready(event);
|
||||
}
|
||||
|
||||
for (peer_id, sender, observed) in self.to_answer.drain() {
|
||||
if let Some(r) = self.pending_replies.pop_front() {
|
||||
// The protocol names can be bytes, but the identify protocol except UTF-8 strings.
|
||||
// There's not much we can do to solve this conflict except strip non-UTF-8 characters.
|
||||
let protocols = params
|
||||
let protocols: Vec<_> = params
|
||||
.supported_protocols()
|
||||
.map(|p| String::from_utf8_lossy(&p).to_string())
|
||||
.collect();
|
||||
@ -164,70 +176,80 @@ where
|
||||
let mut listen_addrs: Vec<_> = params.external_addresses().collect();
|
||||
listen_addrs.extend(params.listened_addresses());
|
||||
|
||||
let send_back_info = IdentifyInfo {
|
||||
let mut sending = 0;
|
||||
let to_send = self.pending_replies.len() + 1;
|
||||
let mut reply = Some(r);
|
||||
loop {
|
||||
match reply {
|
||||
Some(Reply::Queued { peer, io, observed }) => {
|
||||
let info = IdentifyInfo {
|
||||
public_key: self.local_public_key.clone(),
|
||||
protocol_version: self.protocol_version.clone(),
|
||||
agent_version: self.agent_version.clone(),
|
||||
listen_addrs,
|
||||
protocols,
|
||||
listen_addrs: listen_addrs.clone(),
|
||||
protocols: protocols.clone(),
|
||||
};
|
||||
|
||||
let future = sender.send(send_back_info, &observed);
|
||||
self.futures.push((peer_id, future));
|
||||
let io = io.send(info, &observed);
|
||||
reply = Some(Reply::Sending { peer, io });
|
||||
}
|
||||
|
||||
// Removes each future one by one, and pushes them back if they're not ready.
|
||||
for n in (0..self.futures.len()).rev() {
|
||||
let (peer_id, mut future) = self.futures.swap_remove(n);
|
||||
match future.poll() {
|
||||
Some(Reply::Sending { peer, mut io }) => {
|
||||
sending += 1;
|
||||
match io.poll() {
|
||||
Ok(Async::Ready(())) => {
|
||||
let event = IdentifyEvent::SendBack {
|
||||
peer_id,
|
||||
result: Ok(()),
|
||||
};
|
||||
let event = IdentifyEvent::Sent { peer_id: peer };
|
||||
return Async::Ready(NetworkBehaviourAction::GenerateEvent(event));
|
||||
},
|
||||
Ok(Async::NotReady) => self.futures.push((peer_id, future)),
|
||||
Ok(Async::NotReady) => {
|
||||
self.pending_replies.push_back(Reply::Sending { peer, io });
|
||||
if sending == to_send {
|
||||
// All remaining futures are NotReady
|
||||
break
|
||||
} else {
|
||||
reply = self.pending_replies.pop_front();
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
let event = IdentifyEvent::SendBack {
|
||||
peer_id,
|
||||
result: Err(err),
|
||||
let event = IdentifyEvent::Error {
|
||||
peer_id: peer,
|
||||
error: ProtocolsHandlerUpgrErr::Upgrade(UpgradeError::Apply(err))
|
||||
};
|
||||
return Async::Ready(NetworkBehaviourAction::GenerateEvent(event));
|
||||
},
|
||||
}
|
||||
}
|
||||
None => unreachable!()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Async::NotReady
|
||||
}
|
||||
}
|
||||
|
||||
/// Event generated by the `Identify`.
|
||||
/// Event emitted by the `Identify` behaviour.
|
||||
#[derive(Debug)]
|
||||
pub enum IdentifyEvent {
|
||||
/// We obtained identification information from the remote
|
||||
Identified {
|
||||
/// Peer that has been successfully identified.
|
||||
/// Identifying information has been received from a peer.
|
||||
Received {
|
||||
/// The peer that has been identified.
|
||||
peer_id: PeerId,
|
||||
/// Information of the remote.
|
||||
/// The information provided by the peer.
|
||||
info: IdentifyInfo,
|
||||
/// Address the remote observes us as.
|
||||
/// The address observed by the peer for the local node.
|
||||
observed_addr: Multiaddr,
|
||||
},
|
||||
/// Identifying information of the local node has been sent to a peer.
|
||||
Sent {
|
||||
/// The peer that the information has been sent to.
|
||||
peer_id: PeerId,
|
||||
},
|
||||
/// Error while attempting to identify the remote.
|
||||
Error {
|
||||
/// Peer that we fail to identify.
|
||||
/// The peer with whom the error originated.
|
||||
peer_id: PeerId,
|
||||
/// The error that happened.
|
||||
/// The error that occurred.
|
||||
error: ProtocolsHandlerUpgrErr<io::Error>,
|
||||
},
|
||||
/// Finished sending back our identification information to a remote.
|
||||
SendBack {
|
||||
/// Peer that we sent our identification info to.
|
||||
peer_id: PeerId,
|
||||
/// Contains the error that potentially happened when sending back.
|
||||
result: Result<(), io::Error>,
|
||||
},
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -246,7 +268,7 @@ mod tests {
|
||||
use libp2p_secio::SecioConfig;
|
||||
use libp2p_swarm::Swarm;
|
||||
use libp2p_mplex::MplexConfig;
|
||||
use rand::Rng;
|
||||
use rand::{Rng, thread_rng};
|
||||
use std::{fmt, io};
|
||||
use tokio::runtime::current_thread;
|
||||
|
||||
@ -290,7 +312,7 @@ mod tests {
|
||||
};
|
||||
|
||||
let addr: Multiaddr = {
|
||||
let port = rand::thread_rng().gen_range(49152, std::u16::MAX);
|
||||
let port = thread_rng().gen_range(49152, std::u16::MAX);
|
||||
format!("/ip4/127.0.0.1/tcp/{}", port).parse().unwrap()
|
||||
};
|
||||
|
||||
@ -299,13 +321,13 @@ mod tests {
|
||||
|
||||
// nb. Either swarm may receive the `Identified` event first, upon which
|
||||
// it will permit the connection to be closed, as defined by
|
||||
// `PeriodicIdHandler::connection_keep_alive`. Hence the test succeeds if
|
||||
// `IdentifyHandler::connection_keep_alive`. Hence the test succeeds if
|
||||
// either `Identified` event arrives correctly.
|
||||
current_thread::Runtime::new().unwrap().block_on(
|
||||
future::poll_fn(move || -> Result<_, io::Error> {
|
||||
loop {
|
||||
match swarm1.poll().unwrap() {
|
||||
Async::Ready(Some(IdentifyEvent::Identified { info, .. })) => {
|
||||
Async::Ready(Some(IdentifyEvent::Received { info, .. })) => {
|
||||
assert_eq!(info.public_key, pubkey2);
|
||||
assert_eq!(info.protocol_version, "c");
|
||||
assert_eq!(info.agent_version, "d");
|
||||
@ -313,13 +335,13 @@ mod tests {
|
||||
assert!(info.listen_addrs.is_empty());
|
||||
return Ok(Async::Ready(()))
|
||||
},
|
||||
Async::Ready(Some(IdentifyEvent::SendBack { result: Ok(()), .. })) => (),
|
||||
Async::Ready(Some(IdentifyEvent::Sent { .. })) => (),
|
||||
Async::Ready(e) => panic!("{:?}", e),
|
||||
Async::NotReady => {}
|
||||
}
|
||||
|
||||
match swarm2.poll().unwrap() {
|
||||
Async::Ready(Some(IdentifyEvent::Identified { info, .. })) => {
|
||||
Async::Ready(Some(IdentifyEvent::Received { info, .. })) => {
|
||||
assert_eq!(info.public_key, pubkey1);
|
||||
assert_eq!(info.protocol_version, "a");
|
||||
assert_eq!(info.agent_version, "b");
|
||||
@ -327,7 +349,7 @@ mod tests {
|
||||
assert_eq!(info.listen_addrs.len(), 1);
|
||||
return Ok(Async::Ready(()))
|
||||
},
|
||||
Async::Ready(Some(IdentifyEvent::SendBack { result: Ok(()), .. })) => (),
|
||||
Async::Ready(Some(IdentifyEvent::Sent { .. })) => (),
|
||||
Async::Ready(e) => panic!("{:?}", e),
|
||||
Async::NotReady => break
|
||||
}
|
||||
|
@ -18,59 +18,29 @@
|
||||
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
|
||||
// DEALINGS IN THE SOFTWARE.
|
||||
|
||||
//! Implementation of the `/ipfs/id/1.0.0` protocol. Allows a node A to query another node B which
|
||||
//! information B knows about A. Also includes the addresses B is listening on.
|
||||
//! Implementation of the [Identify] protocol.
|
||||
//!
|
||||
//! When two nodes connect to each other, the listening half sends a message to the dialing half,
|
||||
//! indicating the information, and then the protocol stops.
|
||||
//! This implementation of the protocol periodically exchanges
|
||||
//! [`IdentifyInfo`] messages between the peers on an established connection.
|
||||
//!
|
||||
//! At least one identification request is sent on a newly established
|
||||
//! connection, beyond which the behaviour does not keep connections alive.
|
||||
//!
|
||||
//! # Usage
|
||||
//!
|
||||
//! Both low-level and high-level usages are available.
|
||||
//! The [`Identify`] struct implements a `NetworkBehaviour` that negotiates
|
||||
//! and executes the protocol on every established connection, emitting
|
||||
//! [`IdentifyEvent`]s.
|
||||
//!
|
||||
//! ## High-level usage through the `IdentifyTransport` struct
|
||||
//!
|
||||
//! This crate provides the `IdentifyTransport` struct, which wraps around a `Transport` and an
|
||||
//! implementation of `Peerstore`. `IdentifyTransport` is itself a transport that accepts
|
||||
//! multiaddresses of the form `/p2p/...` or `/ipfs/...`.
|
||||
//!
|
||||
//! > **Note**: All the documentation refers to `/p2p/...`, however `/ipfs/...` is also supported.
|
||||
//!
|
||||
//! If you dial a multiaddr of the form `/p2p/...`, then the `IdentifyTransport` will look into
|
||||
//! the `Peerstore` for any known multiaddress for this peer and try to dial them using the
|
||||
//! underlying transport. If you dial any other multiaddr, then it will dial this multiaddr using
|
||||
//! the underlying transport, then negotiate the *identify* protocol with the remote in order to
|
||||
//! obtain its ID, then add it to the peerstore, and finally dial the same multiaddr again and
|
||||
//! return the connection.
|
||||
//!
|
||||
//! Listening doesn't support multiaddresses of the form `/p2p/...` (because that wouldn't make
|
||||
//! sense). Any address passed to `listen_on` will be passed directly to the underlying transport.
|
||||
//!
|
||||
//! Whenever a remote connects to us, either through listening or through `next_incoming`, the
|
||||
//! `IdentifyTransport` dials back the remote, upgrades the connection to the *identify* protocol
|
||||
//! in order to obtain the ID of the remote, stores the information in the peerstore, and finally
|
||||
//! only returns the connection. From the exterior, the multiaddress of the remote is of the form
|
||||
//! `/p2p/...`. If the remote doesn't support the *identify* protocol, then the socket is closed.
|
||||
//!
|
||||
//! Because of the behaviour of `IdentifyProtocol`, it is recommended to build it on top of a
|
||||
//! `ConnectionReuse`.
|
||||
//!
|
||||
//! ## Low-level usage through the `IdentifyProtocolConfig` struct
|
||||
//!
|
||||
//! The `IdentifyProtocolConfig` struct implements the `ConnectionUpgrade` trait. Using it will
|
||||
//! negotiate the *identify* protocol.
|
||||
//!
|
||||
//! The output of the upgrade is a `IdentifyOutput`. If we are the dialer, then `IdentifyOutput`
|
||||
//! will contain the information sent by the remote. If we are the listener, then it will contain
|
||||
//! a `IdentifySender` struct that can be used to transmit back to the remote the information about
|
||||
//! it.
|
||||
//! [Identify]: https://github.com/libp2p/specs/tree/master/identify
|
||||
//! [`Identify`]: self::Identify
|
||||
//! [`IdentifyEvent`]: self::IdentifyEvent
|
||||
//! [`IdentifyInfo`]: self::IdentifyEvent
|
||||
|
||||
pub use self::identify::{Identify, IdentifyEvent};
|
||||
pub use self::protocol::IdentifyInfo;
|
||||
|
||||
pub mod listen_handler;
|
||||
pub mod periodic_id_handler;
|
||||
pub mod protocol;
|
||||
|
||||
mod handler;
|
||||
mod identify;
|
||||
mod protocol;
|
||||
mod structs_proto;
|
||||
|
@ -1,112 +0,0 @@
|
||||
// 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 crate::protocol::{IdentifySender, IdentifyProtocolConfig};
|
||||
use futures::prelude::*;
|
||||
use libp2p_core::upgrade::{DeniedUpgrade, InboundUpgrade, OutboundUpgrade, Negotiated};
|
||||
use libp2p_swarm::{
|
||||
KeepAlive,
|
||||
SubstreamProtocol,
|
||||
ProtocolsHandler,
|
||||
ProtocolsHandlerEvent,
|
||||
ProtocolsHandlerUpgrErr
|
||||
};
|
||||
use smallvec::SmallVec;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use void::{Void, unreachable};
|
||||
|
||||
/// Protocol handler that identifies the remote at a regular period.
|
||||
pub struct IdentifyListenHandler<TSubstream> {
|
||||
/// Configuration for the protocol.
|
||||
config: IdentifyProtocolConfig,
|
||||
|
||||
/// List of senders to yield to the user.
|
||||
pending_result: SmallVec<[IdentifySender<Negotiated<TSubstream>>; 4]>,
|
||||
}
|
||||
|
||||
impl<TSubstream> IdentifyListenHandler<TSubstream> {
|
||||
/// Builds a new `IdentifyListenHandler`.
|
||||
#[inline]
|
||||
pub fn new() -> Self {
|
||||
IdentifyListenHandler {
|
||||
config: IdentifyProtocolConfig,
|
||||
pending_result: SmallVec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<TSubstream> ProtocolsHandler for IdentifyListenHandler<TSubstream>
|
||||
where
|
||||
TSubstream: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type InEvent = Void;
|
||||
type OutEvent = IdentifySender<Negotiated<TSubstream>>;
|
||||
type Error = Void;
|
||||
type Substream = TSubstream;
|
||||
type InboundProtocol = IdentifyProtocolConfig;
|
||||
type OutboundProtocol = DeniedUpgrade;
|
||||
type OutboundOpenInfo = ();
|
||||
|
||||
#[inline]
|
||||
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol> {
|
||||
SubstreamProtocol::new(self.config.clone())
|
||||
}
|
||||
|
||||
fn inject_fully_negotiated_inbound(
|
||||
&mut self,
|
||||
protocol: <Self::InboundProtocol as InboundUpgrade<TSubstream>>::Output
|
||||
) {
|
||||
self.pending_result.push(protocol)
|
||||
}
|
||||
|
||||
fn inject_fully_negotiated_outbound(&mut self, protocol: Void, _: Self::OutboundOpenInfo) {
|
||||
unreachable(protocol)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn inject_event(&mut self, _: Self::InEvent) {}
|
||||
|
||||
#[inline]
|
||||
fn inject_dial_upgrade_error(&mut self, _: Self::OutboundOpenInfo, _: ProtocolsHandlerUpgrErr<<Self::OutboundProtocol as OutboundUpgrade<Self::Substream>>::Error>) {}
|
||||
|
||||
#[inline]
|
||||
fn connection_keep_alive(&self) -> KeepAlive {
|
||||
KeepAlive::No
|
||||
}
|
||||
|
||||
fn poll(
|
||||
&mut self,
|
||||
) -> Poll<
|
||||
ProtocolsHandlerEvent<
|
||||
Self::OutboundProtocol,
|
||||
Self::OutboundOpenInfo,
|
||||
Self::OutEvent,
|
||||
>,
|
||||
Self::Error,
|
||||
> {
|
||||
if !self.pending_result.is_empty() {
|
||||
return Ok(Async::Ready(ProtocolsHandlerEvent::Custom(
|
||||
self.pending_result.remove(0),
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(Async::NotReady)
|
||||
}
|
||||
}
|
@ -33,12 +33,12 @@ use protobuf::parse_from_bytes as protobuf_parse_from_bytes;
|
||||
use protobuf::RepeatedField;
|
||||
use std::convert::TryFrom;
|
||||
use std::io::{Error as IoError, ErrorKind as IoErrorKind};
|
||||
use std::iter;
|
||||
use std::{fmt, iter};
|
||||
use tokio_codec::Framed;
|
||||
use tokio_io::{AsyncRead, AsyncWrite};
|
||||
use unsigned_varint::codec;
|
||||
|
||||
/// Configuration for an upgrade to the identity protocol.
|
||||
/// Configuration for an upgrade to the `Identify` protocol.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IdentifyProtocolConfig;
|
||||
|
||||
@ -52,15 +52,26 @@ pub struct RemoteInfo {
|
||||
_priv: ()
|
||||
}
|
||||
|
||||
/// Object used to send back information to the client.
|
||||
pub struct IdentifySender<T> {
|
||||
/// The substream on which a reply is expected to be sent.
|
||||
pub struct ReplySubstream<T> {
|
||||
inner: Framed<T, codec::UviBytes<Vec<u8>>>,
|
||||
}
|
||||
|
||||
impl<T> IdentifySender<T> where T: AsyncWrite {
|
||||
/// Sends back information to the client. Returns a future that is signalled whenever the
|
||||
/// info have been sent.
|
||||
pub fn send(self, info: IdentifyInfo, observed_addr: &Multiaddr) -> IdentifySenderFuture<T> {
|
||||
impl<T> fmt::Debug for ReplySubstream<T> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_tuple("ReplySubstream").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ReplySubstream<T>
|
||||
where
|
||||
T: AsyncWrite
|
||||
{
|
||||
/// Sends back the requested information on the substream.
|
||||
///
|
||||
/// Consumes the substream, returning a `ReplyFuture` that resolves
|
||||
/// when the reply has been sent on the underlying connection.
|
||||
pub fn send(self, info: IdentifyInfo, observed_addr: &Multiaddr) -> ReplyFuture<T> {
|
||||
debug!("Sending identify info to client");
|
||||
trace!("Sending: {:?}", info);
|
||||
|
||||
@ -83,7 +94,7 @@ impl<T> IdentifySender<T> where T: AsyncWrite {
|
||||
.write_to_bytes()
|
||||
.expect("writing protobuf failed; should never happen");
|
||||
|
||||
IdentifySenderFuture {
|
||||
ReplyFuture {
|
||||
inner: self.inner,
|
||||
item: Some(bytes),
|
||||
}
|
||||
@ -96,14 +107,14 @@ impl<T> IdentifySender<T> where T: AsyncWrite {
|
||||
// means that we would require `T: AsyncWrite` in this struct definition. This requirement
|
||||
// would then propagate everywhere.
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct IdentifySenderFuture<T> {
|
||||
pub struct ReplyFuture<T> {
|
||||
/// The Sink where to send the data.
|
||||
inner: Framed<T, codec::UviBytes<Vec<u8>>>,
|
||||
/// Bytes to send, or `None` if we've already sent them.
|
||||
item: Option<Vec<u8>>,
|
||||
}
|
||||
|
||||
impl<T> Future for IdentifySenderFuture<T>
|
||||
impl<T> Future for ReplyFuture<T>
|
||||
where T: AsyncWrite
|
||||
{
|
||||
type Item = ();
|
||||
@ -123,19 +134,20 @@ where T: AsyncWrite
|
||||
}
|
||||
}
|
||||
|
||||
/// Information sent from the listener to the dialer.
|
||||
/// Information of a peer sent in `Identify` protocol responses.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct IdentifyInfo {
|
||||
/// Public key of the node.
|
||||
/// The public key underlying the peer's `PeerId`.
|
||||
pub public_key: PublicKey,
|
||||
/// Version of the "global" protocol, e.g. `ipfs/1.0.0` or `polkadot/1.0.0`.
|
||||
/// Version of the protocol family used by the peer, 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.
|
||||
/// Name and version of the peer, similar to the `User-Agent` header in
|
||||
/// the HTTP protocol.
|
||||
pub agent_version: String,
|
||||
/// Addresses that the node is listening on.
|
||||
/// The addresses that the peer is listening on.
|
||||
pub listen_addrs: Vec<Multiaddr>,
|
||||
/// Protocols supported by the node, e.g. `/ipfs/ping/1.0.0`.
|
||||
/// The list of protocols supported by the peer, e.g. `/ipfs/ping/1.0.0`.
|
||||
pub protocols: Vec<String>,
|
||||
}
|
||||
|
||||
@ -152,15 +164,14 @@ impl<C> InboundUpgrade<C> for IdentifyProtocolConfig
|
||||
where
|
||||
C: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Output = IdentifySender<Negotiated<C>>;
|
||||
type Output = ReplySubstream<Negotiated<C>>;
|
||||
type Error = IoError;
|
||||
type Future = FutureResult<Self::Output, IoError>;
|
||||
|
||||
fn upgrade_inbound(self, socket: Negotiated<C>, _: Self::Info) -> Self::Future {
|
||||
trace!("Upgrading inbound connection");
|
||||
let socket = Framed::new(socket, codec::UviBytes::default());
|
||||
let sender = IdentifySender { inner: socket };
|
||||
future::ok(sender)
|
||||
let inner = Framed::new(socket, codec::UviBytes::default());
|
||||
future::ok(ReplySubstream { inner })
|
||||
}
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user