#![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))]
#[cfg(feature = "cbor")]
pub mod cbor;
mod codec;
mod handler;
#[cfg(feature = "json")]
pub mod json;
use std::{
collections::{HashMap, HashSet, VecDeque},
fmt, io,
sync::{atomic::AtomicU64, Arc},
task::{Context, Poll},
time::Duration,
};
pub use codec::Codec;
use futures::channel::oneshot;
use handler::Handler;
pub use handler::ProtocolSupport;
use libp2p_core::{transport::PortUse, ConnectedPoint, Endpoint, Multiaddr};
use libp2p_identity::PeerId;
use libp2p_swarm::{
behaviour::{AddressChange, ConnectionClosed, DialFailure, FromSwarm},
dial_opts::DialOpts,
ConnectionDenied, ConnectionHandler, ConnectionId, DialError, NetworkBehaviour, NotifyHandler,
PeerAddresses, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm,
};
use smallvec::SmallVec;
use crate::handler::OutboundMessage;
#[derive(Debug)]
pub enum Message<TRequest, TResponse, TChannelResponse = TResponse> {
Request {
request_id: InboundRequestId,
request: TRequest,
channel: ResponseChannel<TChannelResponse>,
},
Response {
request_id: OutboundRequestId,
response: TResponse,
},
}
#[derive(Debug)]
pub enum Event<TRequest, TResponse, TChannelResponse = TResponse> {
Message {
peer: PeerId,
connection_id: ConnectionId,
message: Message<TRequest, TResponse, TChannelResponse>,
},
OutboundFailure {
peer: PeerId,
connection_id: ConnectionId,
request_id: OutboundRequestId,
error: OutboundFailure,
},
InboundFailure {
peer: PeerId,
connection_id: ConnectionId,
request_id: InboundRequestId,
error: InboundFailure,
},
ResponseSent {
peer: PeerId,
connection_id: ConnectionId,
request_id: InboundRequestId,
},
}
#[derive(Debug)]
pub enum OutboundFailure {
DialFailure,
Timeout,
ConnectionClosed,
UnsupportedProtocols,
Io(io::Error),
}
impl fmt::Display for OutboundFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OutboundFailure::DialFailure => write!(f, "Failed to dial the requested peer"),
OutboundFailure::Timeout => write!(f, "Timeout while waiting for a response"),
OutboundFailure::ConnectionClosed => {
write!(f, "Connection was closed before a response was received")
}
OutboundFailure::UnsupportedProtocols => {
write!(f, "The remote supports none of the requested protocols")
}
OutboundFailure::Io(e) => write!(f, "IO error on outbound stream: {e}"),
}
}
}
impl std::error::Error for OutboundFailure {}
#[derive(Debug)]
pub enum InboundFailure {
Timeout,
ConnectionClosed,
UnsupportedProtocols,
ResponseOmission,
Io(io::Error),
}
impl fmt::Display for InboundFailure {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
InboundFailure::Timeout => {
write!(f, "Timeout while receiving request or sending response")
}
InboundFailure::ConnectionClosed => {
write!(f, "Connection was closed before a response could be sent")
}
InboundFailure::UnsupportedProtocols => write!(
f,
"The local peer supports none of the protocols requested by the remote"
),
InboundFailure::ResponseOmission => write!(
f,
"The response channel was dropped without sending a response to the remote"
),
InboundFailure::Io(e) => write!(f, "IO error on inbound stream: {e}"),
}
}
}
impl std::error::Error for InboundFailure {}
#[derive(Debug)]
pub struct ResponseChannel<TResponse> {
sender: oneshot::Sender<TResponse>,
}
impl<TResponse> ResponseChannel<TResponse> {
pub fn is_open(&self) -> bool {
!self.sender.is_canceled()
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct InboundRequestId(u64);
impl fmt::Display for InboundRequestId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct OutboundRequestId(u64);
impl fmt::Display for OutboundRequestId {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.0)
}
}
#[derive(Debug, Clone)]
pub struct Config {
request_timeout: Duration,
max_concurrent_streams: usize,
}
impl Default for Config {
fn default() -> Self {
Self {
request_timeout: Duration::from_secs(10),
max_concurrent_streams: 100,
}
}
}
impl Config {
#[deprecated(note = "Use `Config::with_request_timeout` for one-liner constructions.")]
pub fn set_request_timeout(&mut self, v: Duration) -> &mut Self {
self.request_timeout = v;
self
}
pub fn with_request_timeout(mut self, v: Duration) -> Self {
self.request_timeout = v;
self
}
pub fn with_max_concurrent_streams(mut self, num_streams: usize) -> Self {
self.max_concurrent_streams = num_streams;
self
}
}
pub struct Behaviour<TCodec>
where
TCodec: Codec + Clone + Send + 'static,
{
inbound_protocols: SmallVec<[TCodec::Protocol; 2]>,
outbound_protocols: SmallVec<[TCodec::Protocol; 2]>,
next_outbound_request_id: OutboundRequestId,
next_inbound_request_id: Arc<AtomicU64>,
config: Config,
codec: TCodec,
pending_events:
VecDeque<ToSwarm<Event<TCodec::Request, TCodec::Response>, OutboundMessage<TCodec>>>,
connected: HashMap<PeerId, SmallVec<[Connection; 2]>>,
addresses: PeerAddresses,
pending_outbound_requests: HashMap<PeerId, SmallVec<[OutboundMessage<TCodec>; 10]>>,
}
impl<TCodec> Behaviour<TCodec>
where
TCodec: Codec + Default + Clone + Send + 'static,
{
pub fn new<I>(protocols: I, cfg: Config) -> Self
where
I: IntoIterator<Item = (TCodec::Protocol, ProtocolSupport)>,
{
Self::with_codec(TCodec::default(), protocols, cfg)
}
}
impl<TCodec> Behaviour<TCodec>
where
TCodec: Codec + Clone + Send + 'static,
{
pub fn with_codec<I>(codec: TCodec, protocols: I, cfg: Config) -> Self
where
I: IntoIterator<Item = (TCodec::Protocol, ProtocolSupport)>,
{
let mut inbound_protocols = SmallVec::new();
let mut outbound_protocols = SmallVec::new();
for (p, s) in protocols {
if s.inbound() {
inbound_protocols.push(p.clone());
}
if s.outbound() {
outbound_protocols.push(p.clone());
}
}
Behaviour {
inbound_protocols,
outbound_protocols,
next_outbound_request_id: OutboundRequestId(1),
next_inbound_request_id: Arc::new(AtomicU64::new(1)),
config: cfg,
codec,
pending_events: VecDeque::new(),
connected: HashMap::new(),
pending_outbound_requests: HashMap::new(),
addresses: PeerAddresses::default(),
}
}
pub fn send_request(&mut self, peer: &PeerId, request: TCodec::Request) -> OutboundRequestId {
self.send_request_with_addresses(peer, request, Vec::new())
}
pub fn send_request_with_addresses(
&mut self,
peer: &PeerId,
request: TCodec::Request,
addresses: Vec<Multiaddr>,
) -> OutboundRequestId {
let request_id = self.next_outbound_request_id();
let request = OutboundMessage {
request_id,
request,
protocols: self.outbound_protocols.clone(),
};
if let Some(request) = self.try_send_request(peer, request) {
self.pending_events.push_back(ToSwarm::Dial {
opts: DialOpts::peer_id(*peer)
.addresses(addresses)
.extend_addresses_through_behaviour()
.build(),
});
self.pending_outbound_requests
.entry(*peer)
.or_default()
.push(request);
}
request_id
}
pub fn send_response(
&mut self,
ch: ResponseChannel<TCodec::Response>,
rs: TCodec::Response,
) -> Result<(), TCodec::Response> {
ch.sender.send(rs)
}
#[deprecated(note = "Use `Swarm::add_peer_address` instead.")]
pub fn add_address(&mut self, peer: &PeerId, address: Multiaddr) -> bool {
self.addresses.add(*peer, address)
}
#[deprecated(note = "Will be removed with the next breaking release and won't be replaced.")]
pub fn remove_address(&mut self, peer: &PeerId, address: &Multiaddr) {
self.addresses.remove(peer, address);
}
pub fn is_connected(&self, peer: &PeerId) -> bool {
if let Some(connections) = self.connected.get(peer) {
!connections.is_empty()
} else {
false
}
}
pub fn is_pending_outbound(&self, peer: &PeerId, request_id: &OutboundRequestId) -> bool {
let est_conn = self
.connected
.get(peer)
.map(|cs| {
cs.iter()
.any(|c| c.pending_outbound_responses.contains(request_id))
})
.unwrap_or(false);
let pen_conn = self
.pending_outbound_requests
.get(peer)
.map(|rps| rps.iter().any(|rp| rp.request_id == *request_id))
.unwrap_or(false);
est_conn || pen_conn
}
pub fn is_pending_inbound(&self, peer: &PeerId, request_id: &InboundRequestId) -> bool {
self.connected
.get(peer)
.map(|cs| {
cs.iter()
.any(|c| c.pending_inbound_responses.contains(request_id))
})
.unwrap_or(false)
}
fn next_outbound_request_id(&mut self) -> OutboundRequestId {
let request_id = self.next_outbound_request_id;
self.next_outbound_request_id.0 += 1;
request_id
}
fn try_send_request(
&mut self,
peer: &PeerId,
request: OutboundMessage<TCodec>,
) -> Option<OutboundMessage<TCodec>> {
if let Some(connections) = self.connected.get_mut(peer) {
if connections.is_empty() {
return Some(request);
}
let ix = (request.request_id.0 as usize) % connections.len();
let conn = &mut connections[ix];
conn.pending_outbound_responses.insert(request.request_id);
self.pending_events.push_back(ToSwarm::NotifyHandler {
peer_id: *peer,
handler: NotifyHandler::One(conn.id),
event: request,
});
None
} else {
Some(request)
}
}
fn remove_pending_outbound_response(
&mut self,
peer: &PeerId,
connection_id: ConnectionId,
request: OutboundRequestId,
) -> bool {
self.get_connection_mut(peer, connection_id)
.map(|c| c.pending_outbound_responses.remove(&request))
.unwrap_or(false)
}
fn remove_pending_inbound_response(
&mut self,
peer: &PeerId,
connection_id: ConnectionId,
request: InboundRequestId,
) -> bool {
self.get_connection_mut(peer, connection_id)
.map(|c| c.pending_inbound_responses.remove(&request))
.unwrap_or(false)
}
fn get_connection_mut(
&mut self,
peer: &PeerId,
connection_id: ConnectionId,
) -> Option<&mut Connection> {
self.connected
.get_mut(peer)
.and_then(|connections| connections.iter_mut().find(|c| c.id == connection_id))
}
fn on_address_change(
&mut self,
AddressChange {
peer_id,
connection_id,
new,
..
}: AddressChange,
) {
let new_address = match new {
ConnectedPoint::Dialer { address, .. } => Some(address.clone()),
ConnectedPoint::Listener { .. } => None,
};
let connections = self
.connected
.get_mut(&peer_id)
.expect("Address change can only happen on an established connection.");
let connection = connections
.iter_mut()
.find(|c| c.id == connection_id)
.expect("Address change can only happen on an established connection.");
connection.remote_address = new_address;
}
fn on_connection_closed(
&mut self,
ConnectionClosed {
peer_id,
connection_id,
remaining_established,
..
}: ConnectionClosed,
) {
let connections = self
.connected
.get_mut(&peer_id)
.expect("Expected some established connection to peer before closing.");
let connection = connections
.iter()
.position(|c| c.id == connection_id)
.map(|p: usize| connections.remove(p))
.expect("Expected connection to be established before closing.");
debug_assert_eq!(connections.is_empty(), remaining_established == 0);
if connections.is_empty() {
self.connected.remove(&peer_id);
}
for request_id in connection.pending_inbound_responses {
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::InboundFailure {
peer: peer_id,
connection_id,
request_id,
error: InboundFailure::ConnectionClosed,
}));
}
for request_id in connection.pending_outbound_responses {
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::OutboundFailure {
peer: peer_id,
connection_id,
request_id,
error: OutboundFailure::ConnectionClosed,
}));
}
}
fn on_dial_failure(
&mut self,
DialFailure {
peer_id,
connection_id,
error,
}: DialFailure,
) {
if let DialError::DialPeerConditionFalse(_) = error {
return;
}
if let Some(peer) = peer_id {
if let Some(pending) = self.pending_outbound_requests.remove(&peer) {
for request in pending {
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::OutboundFailure {
peer,
connection_id,
request_id: request.request_id,
error: OutboundFailure::DialFailure,
}));
}
}
}
}
fn preload_new_handler(
&mut self,
handler: &mut Handler<TCodec>,
peer: PeerId,
connection_id: ConnectionId,
remote_address: Option<Multiaddr>,
) {
let mut connection = Connection::new(connection_id, remote_address);
if let Some(pending_requests) = self.pending_outbound_requests.remove(&peer) {
for request in pending_requests {
connection
.pending_outbound_responses
.insert(request.request_id);
handler.on_behaviour_event(request);
}
}
self.connected.entry(peer).or_default().push(connection);
}
}
impl<TCodec> NetworkBehaviour for Behaviour<TCodec>
where
TCodec: Codec + Send + Clone + 'static,
{
type ConnectionHandler = Handler<TCodec>;
type ToSwarm = Event<TCodec::Request, TCodec::Response>;
fn handle_established_inbound_connection(
&mut self,
connection_id: ConnectionId,
peer: PeerId,
_: &Multiaddr,
_: &Multiaddr,
) -> Result<THandler<Self>, ConnectionDenied> {
let mut handler = Handler::new(
self.inbound_protocols.clone(),
self.codec.clone(),
self.config.request_timeout,
self.next_inbound_request_id.clone(),
self.config.max_concurrent_streams,
);
self.preload_new_handler(&mut handler, peer, connection_id, None);
Ok(handler)
}
fn handle_pending_outbound_connection(
&mut self,
_connection_id: ConnectionId,
maybe_peer: Option<PeerId>,
_addresses: &[Multiaddr],
_effective_role: Endpoint,
) -> Result<Vec<Multiaddr>, ConnectionDenied> {
let Some(peer) = maybe_peer else {
return Ok(vec![]);
};
let mut addresses = Vec::new();
if let Some(connections) = self.connected.get(&peer) {
addresses.extend(connections.iter().filter_map(|c| c.remote_address.clone()))
}
let cached_addrs = self.addresses.get(&peer);
addresses.extend(cached_addrs);
Ok(addresses)
}
fn handle_established_outbound_connection(
&mut self,
connection_id: ConnectionId,
peer: PeerId,
remote_address: &Multiaddr,
_: Endpoint,
_: PortUse,
) -> Result<THandler<Self>, ConnectionDenied> {
let mut handler = Handler::new(
self.inbound_protocols.clone(),
self.codec.clone(),
self.config.request_timeout,
self.next_inbound_request_id.clone(),
self.config.max_concurrent_streams,
);
self.preload_new_handler(
&mut handler,
peer,
connection_id,
Some(remote_address.clone()),
);
Ok(handler)
}
fn on_swarm_event(&mut self, event: FromSwarm) {
self.addresses.on_swarm_event(&event);
match event {
FromSwarm::ConnectionEstablished(_) => {}
FromSwarm::ConnectionClosed(connection_closed) => {
self.on_connection_closed(connection_closed)
}
FromSwarm::AddressChange(address_change) => self.on_address_change(address_change),
FromSwarm::DialFailure(dial_failure) => self.on_dial_failure(dial_failure),
_ => {}
}
}
fn on_connection_handler_event(
&mut self,
peer: PeerId,
connection_id: ConnectionId,
event: THandlerOutEvent<Self>,
) {
match event {
handler::Event::Response {
request_id,
response,
} => {
let removed =
self.remove_pending_outbound_response(&peer, connection_id, request_id);
debug_assert!(
removed,
"Expect request_id to be pending before receiving response.",
);
let message = Message::Response {
request_id,
response,
};
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::Message {
peer,
connection_id,
message,
}));
}
handler::Event::Request {
request_id,
request,
sender,
} => match self.get_connection_mut(&peer, connection_id) {
Some(connection) => {
let inserted = connection.pending_inbound_responses.insert(request_id);
debug_assert!(inserted, "Expect id of new request to be unknown.");
let channel = ResponseChannel { sender };
let message = Message::Request {
request_id,
request,
channel,
};
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::Message {
peer,
connection_id,
message,
}));
}
None => {
tracing::debug!("Connection ({connection_id}) closed after `Event::Request` ({request_id}) has been emitted.");
}
},
handler::Event::ResponseSent(request_id) => {
let removed =
self.remove_pending_inbound_response(&peer, connection_id, request_id);
debug_assert!(
removed,
"Expect request_id to be pending before response is sent."
);
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::ResponseSent {
peer,
connection_id,
request_id,
}));
}
handler::Event::ResponseOmission(request_id) => {
let removed =
self.remove_pending_inbound_response(&peer, connection_id, request_id);
debug_assert!(
removed,
"Expect request_id to be pending before response is omitted.",
);
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::InboundFailure {
peer,
connection_id,
request_id,
error: InboundFailure::ResponseOmission,
}));
}
handler::Event::OutboundTimeout(request_id) => {
let removed =
self.remove_pending_outbound_response(&peer, connection_id, request_id);
debug_assert!(
removed,
"Expect request_id to be pending before request times out."
);
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::OutboundFailure {
peer,
connection_id,
request_id,
error: OutboundFailure::Timeout,
}));
}
handler::Event::OutboundUnsupportedProtocols(request_id) => {
let removed =
self.remove_pending_outbound_response(&peer, connection_id, request_id);
debug_assert!(
removed,
"Expect request_id to be pending before failing to connect.",
);
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::OutboundFailure {
peer,
connection_id,
request_id,
error: OutboundFailure::UnsupportedProtocols,
}));
}
handler::Event::OutboundStreamFailed { request_id, error } => {
let removed =
self.remove_pending_outbound_response(&peer, connection_id, request_id);
debug_assert!(removed, "Expect request_id to be pending upon failure");
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::OutboundFailure {
peer,
connection_id,
request_id,
error: OutboundFailure::Io(error),
}))
}
handler::Event::InboundTimeout(request_id) => {
let removed =
self.remove_pending_inbound_response(&peer, connection_id, request_id);
if removed {
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::InboundFailure {
peer,
connection_id,
request_id,
error: InboundFailure::Timeout,
}));
} else {
tracing::debug!(
"Inbound request timeout for an unknown request_id ({request_id})"
);
}
}
handler::Event::InboundStreamFailed { request_id, error } => {
let removed =
self.remove_pending_inbound_response(&peer, connection_id, request_id);
if removed {
self.pending_events
.push_back(ToSwarm::GenerateEvent(Event::InboundFailure {
peer,
connection_id,
request_id,
error: InboundFailure::Io(error),
}));
} else {
tracing::debug!("Inbound failure is reported for an unknown request_id ({request_id}): {error}");
}
}
}
}
#[tracing::instrument(level = "trace", name = "NetworkBehaviour::poll", skip(self))]
fn poll(&mut self, _: &mut Context<'_>) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
if let Some(ev) = self.pending_events.pop_front() {
return Poll::Ready(ev);
} else if self.pending_events.capacity() > EMPTY_QUEUE_SHRINK_THRESHOLD {
self.pending_events.shrink_to_fit();
}
Poll::Pending
}
}
const EMPTY_QUEUE_SHRINK_THRESHOLD: usize = 100;
struct Connection {
id: ConnectionId,
remote_address: Option<Multiaddr>,
pending_outbound_responses: HashSet<OutboundRequestId>,
pending_inbound_responses: HashSet<InboundRequestId>,
}
impl Connection {
fn new(id: ConnectionId, remote_address: Option<Multiaddr>) -> Self {
Self {
id,
remote_address,
pending_outbound_responses: Default::default(),
pending_inbound_responses: Default::default(),
}
}
}