From a4da8630ba0f56eb04a4ead6c374a850724445ac Mon Sep 17 00:00:00 2001 From: Milos Stankovic <82043364+morph-dev@users.noreply.github.com> Date: Tue, 4 Feb 2025 11:01:38 +0200 Subject: [PATCH] feat!: distinguish peer and peer_id on api level (#136) --- .circleci/config.yml | 3 +- src/cid.rs | 11 +--- src/conn.rs | 40 +++++++------ src/event.rs | 7 ++- src/lib.rs | 1 + src/peer.rs | 96 ++++++++++++++++++++++++++++++ src/socket.rs | 139 ++++++++++++++++++++++++++----------------- src/stream.rs | 17 +++--- src/testutils.rs | 28 ++++++--- src/udp.rs | 18 +++--- tests/socket.rs | 31 ++++++---- tests/stream.rs | 13 ++-- 12 files changed, 275 insertions(+), 129 deletions(-) create mode 100644 src/peer.rs diff --git a/.circleci/config.yml b/.circleci/config.yml index 1eada56..97a9003 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -7,5 +7,4 @@ workflows: - rust/lint-test-build: clippy_arguments: '--all-targets --all-features -- --deny warnings' release: true - version: 1.71.1 - + version: 1.81.0 diff --git a/src/cid.rs b/src/cid.rs index b4b0dc5..1db52be 100644 --- a/src/cid.rs +++ b/src/cid.rs @@ -1,15 +1,6 @@ -use std::fmt::Debug; -use std::hash::Hash; -use std::net::SocketAddr; - -/// A remote peer. -pub trait ConnectionPeer: Clone + Debug + Eq + Hash + PartialEq + Send + Sync {} - -impl ConnectionPeer for SocketAddr {} - #[derive(Clone, Copy, Debug, Hash, Eq, PartialEq)] pub struct ConnectionId
{
pub send: u16,
pub recv: u16,
- pub peer: P,
+ pub peer_id: P,
}
diff --git a/src/conn.rs b/src/conn.rs
index e8dbcc3..d2336e0 100644
--- a/src/conn.rs
+++ b/src/conn.rs
@@ -8,10 +8,12 @@ use delay_map::HashMapDelay;
use futures::StreamExt;
use tokio::sync::{mpsc, oneshot, Notify};
-use crate::cid::{ConnectionId, ConnectionPeer};
+use crate::cid::ConnectionId;
use crate::congestion;
use crate::event::{SocketEvent, StreamEvent};
use crate::packet::{Packet, PacketBuilder, PacketType, SelectiveAck};
+use crate::peer::ConnectionPeer;
+use crate::peer::Peer;
use crate::recv::ReceiveBuffer;
use crate::send::SendBuffer;
use crate::sent::{SentPackets, SentPacketsError};
@@ -167,9 +169,10 @@ impl From ,
+ cid: ConnectionId ,
config: ConnectionConfig,
endpoint: Endpoint,
peer_ts_diff: Duration,
@@ -185,7 +188,8 @@ pub struct Connection ,
+ cid: ConnectionId ,
config: ConnectionConfig,
syn: Option ,
now: Instant,
) {
let (payload, len) = if packet.payload().is_empty() {
@@ -1189,7 +1194,7 @@ impl {
- Outgoing((Packet, P)),
- Shutdown(ConnectionId ),
+pub enum SocketEvent )),
+ Shutdown(ConnectionId ,
+}
+
+impl {
+ /// Creates new instance that stores peer
+ pub fn new(peer: P) -> Self {
+ Self {
+ id: peer.id(),
+ peer: Some(peer),
+ }
+ }
+
+ /// Creates new instance that only stores peer's id
+ pub fn new_id(peer_id: P::Id) -> Self {
+ Self {
+ id: peer_id,
+ peer: None,
+ }
+ }
+
+ /// Returns peer's id
+ pub fn id(&self) -> &P::Id {
+ &self.id
+ }
+
+ /// Returns optional reference to peer
+ pub fn peer(&self) -> Option<&P> {
+ self.peer.as_ref()
+ }
+
+ /// Consolidates given peer into `Self` whilst consuming it.
+ ///
+ /// See [ConnectionPeer::consolidate] for details.
+ ///
+ /// Panics if ids are not matching.
+ pub fn consolidate(&mut self, other: Self) {
+ assert!(self.id == other.id, "Consolidating with non-equal peer");
+ let Some(other_peer) = other.peer else {
+ return;
+ };
+
+ self.peer = match self.peer.take() {
+ Some(peer) => Some(P::consolidate(peer, other_peer)),
+ None => Some(other_peer),
+ };
+ }
+}
diff --git a/src/socket.rs b/src/socket.rs
index 06a654d..4ea0766 100644
--- a/src/socket.rs
+++ b/src/socket.rs
@@ -11,20 +11,27 @@ use tokio::net::UdpSocket;
use tokio::sync::mpsc::UnboundedSender;
use tokio::sync::{mpsc, oneshot};
-use crate::cid::{ConnectionId, ConnectionPeer};
+use crate::cid::ConnectionId;
use crate::conn::ConnectionConfig;
use crate::event::{SocketEvent, StreamEvent};
use crate::packet::{Packet, PacketBuilder, PacketType};
+use crate::peer::{ConnectionPeer, Peer};
use crate::stream::UtpStream;
use crate::udp::AsyncUdpSocket;
type ConnChannel = UnboundedSender {
+struct Accept ,
+ accept: Accept ,
+}
+
const MAX_UDP_PAYLOAD_SIZE: usize = u16::MAX as usize;
const CID_GENERATION_TRY_WARNING_COUNT: usize = 10;
@@ -36,10 +43,10 @@ const CID_GENERATION_TRY_WARNING_COUNT: usize = 10;
/// but thee uTP config refactor is currently very low priority.
const AWAITING_CONNECTION_TIMEOUT: Duration = Duration::from_secs(20);
-pub struct UtpSocket {
- conns: Arc , ConnectionId )>,
+ accepts_with_cid: UnboundedSender UtpSocket
where
- P: ConnectionPeer + Unpin + 'static,
+ P: ConnectionPeer > =
+ let mut awaiting: HashMapDelay > =
HashMapDelay::new(AWAITING_CONNECTION_TIMEOUT);
- let mut incoming_conns: HashMapDelay , Packet)> =
HashMapDelay::new(AWAITING_CONNECTION_TIMEOUT);
let (socket_event_tx, mut socket_event_rx) = mpsc::unbounded_channel();
@@ -84,18 +91,19 @@ where
loop {
tokio::select! {
biased;
- Ok((n, src)) = socket.recv_from(&mut buf) => {
+ Ok((n, mut peer)) = socket.recv_from(&mut buf) => {
+ let peer_id = peer.id();
let packet = match Packet::decode(&buf[..n]) {
Ok(pkt) => pkt,
Err(..) => {
- tracing::warn!(?src, "unable to decode uTP packet");
+ tracing::warn!(?peer, "unable to decode uTP packet");
continue;
}
};
- let peer_init_cid = cid_from_packet(&packet, &src, IdType::SendIdPeerInitiated);
- let we_init_cid = cid_from_packet(&packet, &src, IdType::SendIdWeInitiated);
- let acc_cid = cid_from_packet(&packet, &src, IdType::RecvId);
+ let peer_init_cid = cid_from_packet:: (&packet, peer_id, IdType::SendIdPeerInitiated);
+ let we_init_cid = cid_from_packet:: (&packet, peer_id, IdType::SendIdWeInitiated);
+ let acc_cid = cid_from_packet:: (&packet, peer_id, IdType::RecvId);
let mut conns = conns.write().unwrap();
let conn = conns
.get(&acc_cid)
@@ -107,12 +115,14 @@ where
}
None => {
if std::matches!(packet.packet_type(), PacketType::Syn) {
- let cid = cid_from_packet(&packet, &src, IdType::RecvId);
+ let cid = acc_cid;
// If there was an awaiting connection with the CID, then
// create a new stream for that connection. Otherwise, add the
// connection to the incoming connections.
- if let Some(accept) = awaiting.remove(&cid) {
+ if let Some(accept_with_cid) = awaiting.remove(&cid) {
+ peer.consolidate(accept_with_cid.peer);
+
let (connected_tx, connected_rx) = oneshot::channel();
let (events_tx, events_rx) = mpsc::unbounded_channel();
@@ -120,7 +130,8 @@ where
let stream = UtpStream::new(
cid,
- accept.config,
+ peer,
+ accept_with_cid.accept.config,
Some(packet),
socket_event_tx.clone(),
events_rx,
@@ -128,10 +139,10 @@ where
);
tokio::spawn(async move {
- Self::await_connected(stream, accept, connected_rx).await
+ Self::await_connected(stream, accept_with_cid.accept.stream, connected_rx).await
});
} else {
- incoming_conns.insert(cid, packet);
+ incoming_conns.insert(cid, (peer, packet));
}
} else {
tracing::debug!(
@@ -151,7 +162,7 @@ where
let reset_packet =
PacketBuilder::new(PacketType::Reset, packet.conn_id(), crate::time::now_micros(), 100_000, random_seq_num)
.build();
- let event = SocketEvent::Outgoing((reset_packet, src.clone()));
+ let event = SocketEvent::Outgoing((reset_packet, peer));
if socket_event_tx.send(event).is_err() {
tracing::warn!("Cannot transmit reset packet: socket closed channel");
return;
@@ -161,18 +172,19 @@ where
},
}
}
- Some((accept, cid)) = accepts_with_cid_rx.recv() => {
- let Some(syn) = incoming_conns.remove(&cid) else {
- awaiting.insert(cid, accept);
+ Some(accept_with_cid) = accepts_with_cid_rx.recv() => {
+ let Some((mut peer, syn)) = incoming_conns.remove(&accept_with_cid.cid) else {
+ awaiting.insert(accept_with_cid.cid.clone(), accept_with_cid);
continue;
};
- Self::select_accept_helper(cid, syn, conns.clone(), accept, socket_event_tx.clone());
+ peer.consolidate(accept_with_cid.peer);
+ Self::select_accept_helper(accept_with_cid.cid, peer, syn, conns.clone(), accept_with_cid.accept, socket_event_tx.clone());
}
Some(accept) = accepts_rx.recv(), if !incoming_conns.is_empty() => {
- let (cid, _) = incoming_conns.iter().next().expect("at least one incoming connection");
+ let cid = incoming_conns.keys().next().expect("at least one incoming connection");
let cid = cid.clone();
- let packet = incoming_conns.remove(&cid).expect("to delete incoming connection");
- Self::select_accept_helper(cid, packet, conns.clone(), accept, socket_event_tx.clone());
+ let (peer, packet) = incoming_conns.remove(&cid).expect("to delete incoming connection");
+ Self::select_accept_helper(cid, peer, packet, conns.clone(), accept, socket_event_tx.clone());
}
Some(event) = socket_event_rx.recv() => {
match event {
@@ -195,11 +207,11 @@ where
}
}
}
- Some(Ok((cid, accept))) = awaiting.next() => {
+ Some(Ok((cid, accept_with_cid))) = awaiting.next() => {
// accept_with_cid didn't receive an inbound connection within the timeout period
// log it and return a timeout error
tracing::debug!(%cid.send, %cid.recv, "accept_with_cid timed out");
- let _ = accept
+ let _ = accept_with_cid.accept
.stream
.send(Err(io::Error::from(io::ErrorKind::TimedOut)));
}
@@ -218,14 +230,14 @@ where
/// Internal cid generation
fn generate_cid(
&self,
- peer: P,
+ peer_id: P::Id,
is_initiator: bool,
event_tx: Option {
+ ) -> ConnectionId {
- self.generate_cid(peer, is_initiator, None)
+ pub fn cid(&self, peer_id: P::Id, is_initiator: bool) -> ConnectionId ,
+ cid: ConnectionId ,
config: ConnectionConfig,
) -> io::Result ,
+ config: ConnectionConfig,
+ ) -> io::Result ,
+ cid: ConnectionId ,
config: ConnectionConfig,
) -> io::Result ,
- accept: Accept ,
+ callback: oneshot::Sender ,
+ cid: ConnectionId ,
syn: Packet,
- conns: Arc ,
socket_event_tx: UnboundedSender {
+) -> ConnectionId Drop for UtpSocket {
+impl {
fn drop(&mut self) {
for conn in self.conns.read().unwrap().values() {
let _ = conn.send(StreamEvent::Shutdown);
diff --git a/src/stream.rs b/src/stream.rs
index 363f3a8..4311709 100644
--- a/src/stream.rs
+++ b/src/stream.rs
@@ -4,18 +4,19 @@ use tokio::sync::{mpsc, oneshot};
use tokio::task;
use tracing::Instrument;
-use crate::cid::{ConnectionId, ConnectionPeer};
+use crate::cid::ConnectionId;
use crate::congestion::DEFAULT_MAX_PACKET_SIZE_BYTES;
use crate::conn;
use crate::event::{SocketEvent, StreamEvent};
use crate::packet::Packet;
+use crate::peer::{ConnectionPeer, Peer};
/// The size of the send and receive buffers.
// TODO: Make the buffer size configurable.
const BUF: usize = 1024 * 1024;
-pub struct UtpStream {
- cid: ConnectionId ,
+pub struct UtpStream ,
+ cid: ConnectionId ,
config: conn::ConnectionConfig,
syn: Option {
+ pub fn cid(&self) -> &ConnectionId UtpStream {
+impl {
// Send signal to the connection event loop to exit, after all outgoing writes have completed.
// Public callers should use close() instead.
fn shutdown(&mut self) -> io::Result<()> {
@@ -130,7 +133,7 @@ impl UtpStream {
}
}
-impl Drop for UtpStream {
+impl {
fn drop(&mut self) {
let _ = self.shutdown();
}
diff --git a/src/testutils.rs b/src/testutils.rs
index 4372d0a..2ea0f00 100644
--- a/src/testutils.rs
+++ b/src/testutils.rs
@@ -5,7 +5,8 @@ use std::sync::Arc;
use async_trait::async_trait;
use tokio::sync::mpsc;
-use crate::cid::{ConnectionId, ConnectionPeer};
+use crate::cid::ConnectionId;
+use crate::peer::{ConnectionPeer, Peer};
use crate::udp::AsyncUdpSocket;
/// A mock socket that can be used to simulate a perfect link.
@@ -38,8 +39,8 @@ impl AsyncUdpSocket ) -> io::Result )>;
}
#[async_trait]
impl AsyncUdpSocket(mut socket: S) -> Self
where
@@ -62,10 +69,10 @@ where
let conns = HashMap::new();
let conns = Arc::new(RwLock::new(conns));
- let mut awaiting: HashMapDelay