use std::{
collections::HashSet,
sync::Arc,
task::{Context, Poll},
time::Duration,
};
use either::Either;
use futures::prelude::*;
use futures_bounded::Timeout;
use futures_timer::Delay;
use libp2p_core::{
Multiaddr,
upgrade::{ReadyUpgrade, SelectUpgrade},
};
use libp2p_identity::PeerId;
use libp2p_swarm::{
ConnectionHandler, ConnectionHandlerEvent, StreamProtocol, StreamUpgradeError,
SubstreamProtocol, SupportedProtocols,
handler::{
ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound,
ProtocolSupport,
},
};
use smallvec::SmallVec;
use tracing::Level;
use crate::{
PROTOCOL_NAME, PUSH_PROTOCOL_NAME,
behaviour::KeyType,
protocol::{self, Info, PushInfo, UpgradeError},
};
const STREAM_TIMEOUT: Duration = Duration::from_secs(60);
const MAX_CONCURRENT_STREAMS_PER_CONNECTION: usize = 10;
pub struct Handler {
remote_peer_id: PeerId,
events: SmallVec<
[ConnectionHandlerEvent<
Either<ReadyUpgrade<StreamProtocol>, ReadyUpgrade<StreamProtocol>>,
(),
Event,
>; 4],
>,
active_streams: futures_bounded::FuturesSet<Result<Success, UpgradeError>>,
trigger_next_identify: Delay,
exchanged_one_periodic_identify: bool,
interval: Duration,
local_key: Arc<KeyType>,
protocol_version: String,
agent_version: String,
observed_addr: Multiaddr,
remote_info: Option<Info>,
local_supported_protocols: SupportedProtocols,
remote_supported_protocols: HashSet<StreamProtocol>,
external_addresses: HashSet<Multiaddr>,
}
#[derive(Debug)]
pub enum InEvent {
AddressesChanged(HashSet<Multiaddr>),
Push,
}
#[derive(Debug)]
#[allow(clippy::large_enum_variant)]
pub enum Event {
Identified(Info),
Identification,
IdentificationPushed(Info),
IdentificationError(StreamUpgradeError<UpgradeError>),
}
impl Handler {
pub(crate) fn new(
interval: Duration,
remote_peer_id: PeerId,
local_key: Arc<KeyType>,
protocol_version: String,
agent_version: String,
observed_addr: Multiaddr,
external_addresses: HashSet<Multiaddr>,
) -> Self {
Self {
remote_peer_id,
events: SmallVec::new(),
active_streams: futures_bounded::FuturesSet::new(
move || futures_bounded::Delay::futures_timer(STREAM_TIMEOUT),
MAX_CONCURRENT_STREAMS_PER_CONNECTION,
),
trigger_next_identify: Delay::new(Duration::ZERO),
exchanged_one_periodic_identify: false,
interval,
local_key,
protocol_version,
agent_version,
observed_addr,
local_supported_protocols: SupportedProtocols::default(),
remote_supported_protocols: HashSet::default(),
remote_info: Default::default(),
external_addresses,
}
}
fn on_fully_negotiated_inbound(
&mut self,
FullyNegotiatedInbound {
protocol: output, ..
}: FullyNegotiatedInbound<<Self as ConnectionHandler>::InboundProtocol>,
) {
match output {
future::Either::Left(stream) => {
let info = self.build_info();
if self
.active_streams
.try_push(
protocol::send_identify(stream, info).map_ok(|_| Success::SentIdentify),
)
.is_err()
{
tracing::warn!("Dropping inbound stream because we are at capacity");
} else {
self.exchanged_one_periodic_identify = true;
}
}
future::Either::Right(stream) => {
if self
.active_streams
.try_push(protocol::recv_push(stream).map_ok(Success::ReceivedIdentifyPush))
.is_err()
{
tracing::warn!(
"Dropping inbound identify push stream because we are at capacity"
);
}
}
}
}
fn on_fully_negotiated_outbound(
&mut self,
FullyNegotiatedOutbound {
protocol: output, ..
}: FullyNegotiatedOutbound<<Self as ConnectionHandler>::OutboundProtocol>,
) {
match output {
future::Either::Left(stream) => {
if self
.active_streams
.try_push(protocol::recv_identify(stream).map_ok(Success::ReceivedIdentify))
.is_err()
{
tracing::warn!("Dropping outbound identify stream because we are at capacity");
}
}
future::Either::Right(stream) => {
let info = self.build_info();
if self
.active_streams
.try_push(
protocol::send_identify(stream, info).map_ok(Success::SentIdentifyPush),
)
.is_err()
{
tracing::warn!(
"Dropping outbound identify push stream because we are at capacity"
);
}
}
}
}
fn build_info(&mut self) -> Info {
let signed_envelope = match self.local_key.as_ref() {
KeyType::PublicKey(_) => None,
KeyType::Keypair { keypair, .. } => libp2p_core::PeerRecord::new(
keypair,
Vec::from_iter(self.external_addresses.iter().cloned()),
)
.ok()
.map(|r| r.into_signed_envelope()),
};
Info {
public_key: self.local_key.public_key().clone(),
protocol_version: self.protocol_version.clone(),
agent_version: self.agent_version.clone(),
listen_addrs: Vec::from_iter(self.external_addresses.iter().cloned()),
protocols: Vec::from_iter(self.local_supported_protocols.iter().cloned()),
observed_addr: self.observed_addr.clone(),
signed_peer_record: signed_envelope,
}
}
fn handle_incoming_info(&mut self, info: &Info) -> bool {
let derived_peer_id = info.public_key.to_peer_id();
if self.remote_peer_id != derived_peer_id {
return false;
}
self.remote_info.replace(info.clone());
self.update_supported_protocols_for_remote(info);
true
}
fn update_supported_protocols_for_remote(&mut self, remote_info: &Info) {
let new_remote_protocols = HashSet::from_iter(remote_info.protocols.clone());
let remote_added_protocols = new_remote_protocols
.difference(&self.remote_supported_protocols)
.cloned()
.collect::<HashSet<_>>();
let remote_removed_protocols = self
.remote_supported_protocols
.difference(&new_remote_protocols)
.cloned()
.collect::<HashSet<_>>();
if !remote_added_protocols.is_empty() {
self.events
.push(ConnectionHandlerEvent::ReportRemoteProtocols(
ProtocolSupport::Added(remote_added_protocols),
));
}
if !remote_removed_protocols.is_empty() {
self.events
.push(ConnectionHandlerEvent::ReportRemoteProtocols(
ProtocolSupport::Removed(remote_removed_protocols),
));
}
self.remote_supported_protocols = new_remote_protocols;
}
fn local_protocols_to_string(&mut self) -> String {
self.local_supported_protocols
.iter()
.map(|p| p.to_string())
.collect::<Vec<_>>()
.join(", ")
}
}
impl ConnectionHandler for Handler {
type FromBehaviour = InEvent;
type ToBehaviour = Event;
type InboundProtocol =
SelectUpgrade<ReadyUpgrade<StreamProtocol>, ReadyUpgrade<StreamProtocol>>;
type OutboundProtocol = Either<ReadyUpgrade<StreamProtocol>, ReadyUpgrade<StreamProtocol>>;
type OutboundOpenInfo = ();
type InboundOpenInfo = ();
fn listen_protocol(&self) -> SubstreamProtocol<Self::InboundProtocol> {
SubstreamProtocol::new(
SelectUpgrade::new(
ReadyUpgrade::new(PROTOCOL_NAME),
ReadyUpgrade::new(PUSH_PROTOCOL_NAME),
),
(),
)
}
fn on_behaviour_event(&mut self, event: Self::FromBehaviour) {
match event {
InEvent::AddressesChanged(addresses) => {
self.external_addresses = addresses;
}
InEvent::Push => {
self.events
.push(ConnectionHandlerEvent::OutboundSubstreamRequest {
protocol: SubstreamProtocol::new(
Either::Right(ReadyUpgrade::new(PUSH_PROTOCOL_NAME)),
(),
),
});
}
}
}
#[tracing::instrument(level = "trace", name = "ConnectionHandler::poll", skip(self, cx))]
fn poll(
&mut self,
cx: &mut Context<'_>,
) -> Poll<ConnectionHandlerEvent<Self::OutboundProtocol, (), Event>> {
if let Some(event) = self.events.pop() {
return Poll::Ready(event);
}
if let Poll::Ready(()) = self.trigger_next_identify.poll_unpin(cx) {
self.trigger_next_identify.reset(self.interval);
let event = ConnectionHandlerEvent::OutboundSubstreamRequest {
protocol: SubstreamProtocol::new(
Either::Left(ReadyUpgrade::new(PROTOCOL_NAME)),
(),
),
};
return Poll::Ready(event);
}
while let Poll::Ready(ready) = self.active_streams.poll_unpin(cx) {
match ready {
Ok(Ok(Success::ReceivedIdentify(remote_info))) => {
if self.handle_incoming_info(&remote_info) {
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Event::Identified(remote_info),
));
} else {
tracing::warn!(
%self.remote_peer_id,
?remote_info.public_key,
derived_peer_id=%remote_info.public_key.to_peer_id(),
"Discarding received identify message as public key does not match remote peer ID",
);
}
}
Ok(Ok(Success::SentIdentifyPush(info))) => {
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Event::IdentificationPushed(info),
));
}
Ok(Ok(Success::SentIdentify)) => {
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Event::Identification,
));
}
Ok(Ok(Success::ReceivedIdentifyPush(remote_push_info))) => {
if let Some(mut info) = self.remote_info.clone() {
info.merge(remote_push_info);
if self.handle_incoming_info(&info) {
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Event::Identified(info),
));
} else {
tracing::warn!(
%self.remote_peer_id,
?info.public_key,
derived_peer_id=%info.public_key.to_peer_id(),
"Discarding received identify message as public key does not match remote peer ID",
);
}
}
}
Ok(Err(e)) => {
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Event::IdentificationError(StreamUpgradeError::Apply(e)),
));
}
Err(Timeout { .. }) => {
return Poll::Ready(ConnectionHandlerEvent::NotifyBehaviour(
Event::IdentificationError(StreamUpgradeError::Timeout),
));
}
}
}
Poll::Pending
}
fn on_connection_event(
&mut self,
event: ConnectionEvent<Self::InboundProtocol, Self::OutboundProtocol>,
) {
match event {
ConnectionEvent::FullyNegotiatedInbound(fully_negotiated_inbound) => {
self.on_fully_negotiated_inbound(fully_negotiated_inbound)
}
ConnectionEvent::FullyNegotiatedOutbound(fully_negotiated_outbound) => {
self.on_fully_negotiated_outbound(fully_negotiated_outbound)
}
ConnectionEvent::DialUpgradeError(DialUpgradeError { error, .. }) => {
self.events.push(ConnectionHandlerEvent::NotifyBehaviour(
Event::IdentificationError(
#[allow(unused)]
error.map_upgrade_err(|e| libp2p_core::util::unreachable(e.into_inner())),
),
));
self.trigger_next_identify.reset(self.interval);
}
ConnectionEvent::LocalProtocolsChange(change) => {
let before = tracing::enabled!(Level::DEBUG)
.then(|| self.local_protocols_to_string())
.unwrap_or_default();
let protocols_changed = self.local_supported_protocols.on_protocols_change(change);
let after = tracing::enabled!(Level::DEBUG)
.then(|| self.local_protocols_to_string())
.unwrap_or_default();
if protocols_changed && self.exchanged_one_periodic_identify {
tracing::debug!(
peer=%self.remote_peer_id,
%before,
%after,
"Supported listen protocols changed, pushing to peer"
);
self.events
.push(ConnectionHandlerEvent::OutboundSubstreamRequest {
protocol: SubstreamProtocol::new(
Either::Right(ReadyUpgrade::new(PUSH_PROTOCOL_NAME)),
(),
),
});
}
}
_ => {}
}
}
}
enum Success {
SentIdentify,
ReceivedIdentify(Info),
SentIdentifyPush(Info),
ReceivedIdentifyPush(PushInfo),
}