blob: cc9eb0b7e6adc45e7522368c1f20be480ad90d5a [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
use std::{
collections::{HashMap, HashSet},
convert::Infallible,
future::{Ready, ready},
io,
sync::{Arc, Mutex, MutexGuard},
task::{Context, Poll},
};
use libp2p::{
Multiaddr, PeerId,
core::{
Endpoint,
transport::PortUse,
upgrade::{InboundUpgrade, OutboundUpgrade, UpgradeInfo},
},
swarm::{
ConnectionDenied, ConnectionHandler, ConnectionId, FromSwarm, NetworkBehaviour, Stream,
StreamProtocol, THandler, THandlerInEvent, THandlerOutEvent, ToSwarm,
behaviour::ConnectionClosed,
handler::{
ConnectionEvent, DialUpgradeError, FullyNegotiatedInbound, FullyNegotiatedOutbound,
},
},
};
use tokio::sync::{mpsc, oneshot};
use super::address::is_relayed_address;
const OUTBOUND_COMMAND_CAPACITY: usize = 1;
pub(super) struct Behaviour {
protocol: StreamProtocol,
direct_only: bool,
incoming: mpsc::Sender<InboundStream>,
shared: Arc<Mutex<DirectConnections>>,
}
pub(super) struct InboundStream {
pub(super) peer_id: PeerId,
pub(super) stream: Stream,
}
impl Behaviour {
pub(super) fn new(
protocol: StreamProtocol,
incoming_capacity: usize,
direct_only: bool,
) -> (Self, Control, mpsc::Receiver<InboundStream>) {
let (incoming, receiver) = mpsc::channel(incoming_capacity);
let shared = Arc::new(Mutex::new(DirectConnections::default()));
(
Self {
protocol,
direct_only,
incoming,
shared: shared.clone(),
},
Control { shared },
receiver,
)
}
fn handler(&mut self, connection_id: ConnectionId, peer_id: PeerId, relayed: bool) -> Handler {
if self.direct_only && relayed {
return Handler::relayed();
}
let (sender, receiver) = mpsc::channel(OUTBOUND_COMMAND_CAPACITY);
lock(&self.shared).insert(connection_id, peer_id, sender);
Handler::direct(
peer_id,
self.protocol.clone(),
self.incoming.clone(),
receiver,
)
}
}
impl NetworkBehaviour for Behaviour {
type ConnectionHandler = Handler;
type ToSwarm = Infallible;
fn handle_established_inbound_connection(
&mut self,
connection_id: ConnectionId,
peer_id: PeerId,
local_addr: &Multiaddr,
remote_addr: &Multiaddr,
) -> Result<THandler<Self>, ConnectionDenied> {
Ok(self.handler(
connection_id,
peer_id,
is_relayed_address(local_addr) || is_relayed_address(remote_addr),
))
}
fn handle_established_outbound_connection(
&mut self,
connection_id: ConnectionId,
peer_id: PeerId,
address: &Multiaddr,
_: Endpoint,
_: PortUse,
) -> Result<THandler<Self>, ConnectionDenied> {
Ok(self.handler(connection_id, peer_id, is_relayed_address(address)))
}
fn on_swarm_event(&mut self, event: FromSwarm) {
if let FromSwarm::ConnectionClosed(ConnectionClosed { connection_id, .. }) = event {
lock(&self.shared).remove(connection_id);
}
}
fn on_connection_handler_event(
&mut self,
_: PeerId,
_: ConnectionId,
event: THandlerOutEvent<Self>,
) {
libp2p::core::util::unreachable(event);
}
fn poll(&mut self, _: &mut Context<'_>) -> Poll<ToSwarm<Self::ToSwarm, THandlerInEvent<Self>>> {
Poll::Pending
}
}
#[derive(Clone)]
pub(super) struct Control {
shared: Arc<Mutex<DirectConnections>>,
}
impl Control {
pub(super) fn has_connection(&self, peer_id: PeerId, excluded: &HashSet<ConnectionId>) -> bool {
lock(&self.shared).connection(peer_id, excluded).is_some()
}
pub(super) async fn open_stream(
&mut self,
peer_id: PeerId,
excluded: &HashSet<ConnectionId>,
) -> Result<OpenedStream, OpenStreamError> {
let (connection_id, sender) = lock(&self.shared)
.connection(peer_id, excluded)
.ok_or(OpenStreamError::NoDirectConnection)?;
let (result, receiver) = oneshot::channel();
sender
.send(NewStream { result })
.await
.map_err(|_| OpenStreamError::ConnectionClosed)?;
let stream = receiver
.await
.map_err(|_| OpenStreamError::ConnectionClosed)??;
Ok(OpenedStream {
connection_id,
stream,
})
}
}
pub(super) struct OpenedStream {
pub(super) connection_id: ConnectionId,
pub(super) stream: Stream,
}
#[derive(Debug)]
pub(super) enum OpenStreamError {
NoDirectConnection,
ConnectionClosed,
UnsupportedProtocol,
Io(io::Error),
}
impl std::fmt::Display for OpenStreamError {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::NoDirectConnection => write!(formatter, "peer has no verified direct connection"),
Self::ConnectionClosed => write!(formatter, "direct connection closed"),
Self::UnsupportedProtocol => {
write!(formatter, "peer does not support the application protocol")
}
Self::Io(error) => error.fmt(formatter),
}
}
}
#[derive(Default)]
struct DirectConnections {
connections: HashMap<ConnectionId, DirectConnection>,
}
struct DirectConnection {
peer_id: PeerId,
sender: mpsc::Sender<NewStream>,
}
impl DirectConnections {
fn insert(
&mut self,
connection_id: ConnectionId,
peer_id: PeerId,
sender: mpsc::Sender<NewStream>,
) {
self.connections
.insert(connection_id, DirectConnection { peer_id, sender });
}
fn remove(&mut self, connection_id: ConnectionId) {
self.connections.remove(&connection_id);
}
fn connection(
&self,
peer_id: PeerId,
excluded: &HashSet<ConnectionId>,
) -> Option<(ConnectionId, mpsc::Sender<NewStream>)> {
self.connections
.iter()
.find(|(connection_id, connection)| {
connection.peer_id == peer_id
&& !excluded.contains(connection_id)
&& !connection.sender.is_closed()
})
.map(|(connection_id, connection)| (*connection_id, connection.sender.clone()))
}
}
fn lock(shared: &Arc<Mutex<DirectConnections>>) -> MutexGuard<'_, DirectConnections> {
shared
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner())
}
pub(super) struct Handler {
peer_id: Option<PeerId>,
protocol: Option<StreamProtocol>,
incoming: Option<mpsc::Sender<InboundStream>>,
commands: Option<mpsc::Receiver<NewStream>>,
pending: HashMap<u64, oneshot::Sender<Result<Stream, OpenStreamError>>>,
next_request_id: u64,
}
impl Handler {
fn direct(
peer_id: PeerId,
protocol: StreamProtocol,
incoming: mpsc::Sender<InboundStream>,
commands: mpsc::Receiver<NewStream>,
) -> Self {
Self {
peer_id: Some(peer_id),
protocol: Some(protocol),
incoming: Some(incoming),
commands: Some(commands),
pending: HashMap::new(),
next_request_id: 0,
}
}
fn relayed() -> Self {
Self {
peer_id: None,
protocol: None,
incoming: None,
commands: None,
pending: HashMap::new(),
next_request_id: 0,
}
}
}
impl ConnectionHandler for Handler {
type FromBehaviour = Infallible;
type ToBehaviour = Infallible;
type InboundProtocol = ProtocolUpgrade;
type OutboundProtocol = ProtocolUpgrade;
type InboundOpenInfo = ();
type OutboundOpenInfo = u64;
fn listen_protocol(
&self,
) -> libp2p::swarm::SubstreamProtocol<Self::InboundProtocol, Self::InboundOpenInfo> {
libp2p::swarm::SubstreamProtocol::new(
ProtocolUpgrade(self.protocol.iter().cloned().collect()),
(),
)
}
fn on_behaviour_event(&mut self, event: Self::FromBehaviour) {
libp2p::core::util::unreachable(event);
}
fn poll(
&mut self,
context: &mut Context<'_>,
) -> Poll<libp2p::swarm::ConnectionHandlerEvent<Self::OutboundProtocol, u64, Self::ToBehaviour>>
{
let Some(commands) = self.commands.as_mut() else {
return Poll::Pending;
};
loop {
match commands.poll_recv(context) {
Poll::Ready(Some(command)) if command.result.is_closed() => continue,
Poll::Ready(Some(command)) => {
let protocol = self
.protocol
.clone()
.expect("only direct handlers receive stream commands");
let request_id = self.next_request_id;
self.next_request_id = self.next_request_id.wrapping_add(1);
self.pending.insert(request_id, command.result);
return Poll::Ready(
libp2p::swarm::ConnectionHandlerEvent::OutboundSubstreamRequest {
protocol: libp2p::swarm::SubstreamProtocol::new(
ProtocolUpgrade(vec![protocol]),
request_id,
),
},
);
}
Poll::Ready(None) | Poll::Pending => return Poll::Pending,
}
}
}
fn on_connection_event(
&mut self,
event: ConnectionEvent<
Self::InboundProtocol,
Self::OutboundProtocol,
Self::InboundOpenInfo,
Self::OutboundOpenInfo,
>,
) {
match event {
ConnectionEvent::FullyNegotiatedInbound(FullyNegotiatedInbound {
protocol: (stream, _),
..
}) => {
if let Some(incoming) = self.incoming.as_ref() {
let _ = incoming.try_send(InboundStream {
peer_id: self.peer_id.expect("direct handlers have a peer id"),
stream,
});
}
}
ConnectionEvent::FullyNegotiatedOutbound(FullyNegotiatedOutbound {
protocol: (stream, _),
info,
}) => {
let Some(result) = self.pending.remove(&info) else {
return;
};
let _ = result.send(Ok(stream));
}
ConnectionEvent::DialUpgradeError(DialUpgradeError { info, error }) => {
let Some(result) = self.pending.remove(&info) else {
return;
};
let error = match error {
libp2p::swarm::StreamUpgradeError::Timeout => {
OpenStreamError::Io(io::Error::from(io::ErrorKind::TimedOut))
}
libp2p::swarm::StreamUpgradeError::Apply(value) => {
libp2p::core::util::unreachable(value)
}
libp2p::swarm::StreamUpgradeError::NegotiationFailed => {
OpenStreamError::UnsupportedProtocol
}
libp2p::swarm::StreamUpgradeError::Io(error) => OpenStreamError::Io(error),
};
let _ = result.send(Err(error));
}
_ => {}
}
}
}
struct NewStream {
result: oneshot::Sender<Result<Stream, OpenStreamError>>,
}
#[derive(Clone)]
pub(super) struct ProtocolUpgrade(Vec<StreamProtocol>);
impl UpgradeInfo for ProtocolUpgrade {
type Info = StreamProtocol;
type InfoIter = std::vec::IntoIter<StreamProtocol>;
fn protocol_info(&self) -> Self::InfoIter {
self.0.clone().into_iter()
}
}
impl InboundUpgrade<Stream> for ProtocolUpgrade {
type Output = (Stream, StreamProtocol);
type Error = Infallible;
type Future = Ready<Result<Self::Output, Self::Error>>;
fn upgrade_inbound(self, stream: Stream, protocol: StreamProtocol) -> Self::Future {
ready(Ok((stream, protocol)))
}
}
impl OutboundUpgrade<Stream> for ProtocolUpgrade {
type Output = (Stream, StreamProtocol);
type Error = Infallible;
type Future = Ready<Result<Self::Output, Self::Error>>;
fn upgrade_outbound(self, stream: Stream, protocol: StreamProtocol) -> Self::Future {
ready(Ok((stream, protocol)))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn application_protocol_is_registered_only_on_direct_connections() {
let protocol = StreamProtocol::new("/maka/test/1");
let peer_id = PeerId::random();
let (mut behaviour, control, _) = Behaviour::new(protocol.clone(), 1, true);
let relayed = behaviour
.handle_established_outbound_connection(
ConnectionId::new_unchecked(1),
peer_id,
&"/memory/1/p2p-circuit".parse().expect("relay address"),
Endpoint::Dialer,
PortUse::Reuse,
)
.expect("relayed handler");
assert_eq!(
relayed.listen_protocol().upgrade().protocol_info().count(),
0
);
assert!(
lock(&control.shared)
.connection(peer_id, &HashSet::new())
.is_none()
);
assert!(!control.has_connection(peer_id, &HashSet::new()));
let direct = behaviour
.handle_established_outbound_connection(
ConnectionId::new_unchecked(2),
peer_id,
&"/ip4/127.0.0.1/udp/1/quic-v1"
.parse()
.expect("direct address"),
Endpoint::Dialer,
PortUse::Reuse,
)
.expect("direct handler");
assert_eq!(
direct
.listen_protocol()
.upgrade()
.protocol_info()
.collect::<Vec<_>>(),
vec![protocol]
);
assert!(
lock(&control.shared)
.connection(peer_id, &HashSet::new())
.is_some()
);
assert!(control.has_connection(peer_id, &HashSet::new()));
assert!(
!control.has_connection(peer_id, &HashSet::from([ConnectionId::new_unchecked(2)]),)
);
}
}