srt_runtime/io.rs
1//! Async real-socket UDP adapter over the sans-IO SRT engine.
2//!
3//! The sans-IO [`crate::caller::CallerHandshake`] / [`crate::listener::ListenerHandshake`]
4//! engines never touch a socket — they turn state transitions into typed events
5//! and byte buffers. The ARQ sender/receiver, TSBPD scheduler, and LiveCC
6//! pacing controller follow the same contract: all take caller-supplied
7//! `now: core::time::Duration` and never read a wall clock.
8//!
9//! This module is the thin tokio layer that actually moves bytes over a
10//! [`tokio::net::UdpSocket`], drives the handshake to completion, and then
11//! runs a **background driver task** per connection that pumps socket RX →
12//! engines → socket TX and, crucially, ticks the timers (retransmit, ACK,
13//! NAK, TSBPD release) on a fixed [`tokio::time::interval`] — so loss recovery
14//! keeps making progress even when the application is neither sending nor
15//! receiving at that instant. The sans-IO core stays `no_std`; the adapter is
16//! pure plumbing.
17//!
18//! # Why a background task
19//!
20//! SRT reliability is bidirectional and continuous: a receiver that detects a
21//! gap emits a NAK, and the *sender* must react to that NAK by retransmitting
22//! — long after the application handed it the original payload. A purely
23//! pull-based `send`/`recv` (one that only advances the protocol while the
24//! app is blocked inside a call) deadlocks the moment a fire-and-forget sender
25//! stops calling `send`: the inbound NAK is never drained and the lost packet
26//! is never resent. The driver task decouples protocol progress from
27//! application call timing: [`SrtSocket::send`] enqueues a payload and returns
28//! immediately, [`SrtSocket::recv`] awaits a delivered payload, and the task
29//! in between runs the select loop (RX / app-send / periodic tick) forever
30//! until the peer shuts down or the [`SrtSocket`] is dropped.
31//!
32//! # Structure
33//!
34//! - [`SrtListener`] — binds a UDP port and accepts incoming SRT connections,
35//! returning a [`SrtSocket`] per connected peer.
36//! - [`SrtSocket`] — a handle to an established SRT connection (caller or
37//! listener role) with async [`send`](SrtSocket::send) and
38//! [`recv`](SrtSocket::recv) for application payloads; the actual protocol
39//! runs on the background driver task the handle owns.
40//!
41//! # Feature gate
42//!
43//! Only available with `features = ["tokio"]` (implies `std`). Without the
44//! `tokio` feature, the crate stays `no_std`+`alloc` and nothing in this
45//! module is compiled.
46
47use std::hash::{BuildHasher, Hash, Hasher};
48use std::sync::Arc;
49
50use alloc::collections::VecDeque;
51use core::time::Duration;
52
53use tokio::net::UdpSocket;
54use tokio::sync::mpsc;
55use tokio::time::Instant;
56
57use crate::arq::{Receiver as ArqReceiver, Sender as ArqSender};
58use crate::caller::{CallerHandshake, CallerHandshakeState};
59use crate::error::{Error, Result};
60use crate::handshake_sm::{HandshakeConfig, HandshakeOutput, derive_cookie};
61use crate::listener::{ListenerHandshake, ListenerHandshakeState};
62use crate::livecc::{LiveCC, MaxBwConfig};
63use crate::packet::misc::KeepAlivePacket;
64use crate::packet::{ControlPacket, DataPacket, SrtPacket};
65use crate::tsbpd::TsbpdScheduler;
66
67// ===========================================================================
68// Constants
69// ===========================================================================
70
71/// Maximum UDP datagram size.
72const MAX_DATAGRAM: usize = 1500;
73/// Interval on which the driver task ticks the timer-based engine work (ACK,
74/// NAK, retransmit, TSBPD release). Small enough that loss recovery is prompt
75/// on a low-latency link, large enough not to busy-spin.
76const TICK_INTERVAL_MS: u64 = 2;
77/// Default TSBPD drift (zero when no estimate available).
78const DEFAULT_DRIFT_US: u64 = 0;
79/// TLPKTDROP (`draft-sharabayko-srt-01` §4.6) is **disabled** in this
80/// adapter: it exists to *discard* packets that could not be recovered in
81/// time, which is the exact opposite of the reliable, lossless in-order
82/// delivery `SrtSocket::recv` promises. With it off, the TSBPD scheduler
83/// waits for the ARQ layer's NAK-driven retransmission instead of skipping a
84/// gap — so every payload is delivered, in order. (A live/latency-bounded
85/// mode that re-enables drop is a future follow-up.)
86const DEFAULT_TLPKT_DROP_ENABLED: bool = false;
87/// Default max bandwidth (1 Gbps).
88const DEFAULT_MAX_BW: MaxBwConfig = MaxBwConfig::Set(125_000_000);
89/// Handshake timeout.
90const HANDSHAKE_TIMEOUT: Duration = Duration::from_secs(5);
91
92// ===========================================================================
93// Outbound queue
94// ===========================================================================
95
96/// One datagram queued for transmission, plus whether LiveCC packet pacing
97/// (`PKT_SND_PERIOD`, `draft-sharabayko-srt-01` §5.1) applies to it.
98///
99/// §5.1.2 paces DATA packets only — ACK/NAK/ACKACK/Keep-Alive control
100/// feedback must never be throttled behind the pacing delay, or loss
101/// recovery (which rides on that same control traffic) stalls along with
102/// it.
103#[derive(Debug)]
104struct OutboundPacket {
105 bytes: Vec<u8>,
106 /// `true` for an (original or retransmitted) DATA packet — the only
107 /// kind LiveCC pacing applies to.
108 is_data: bool,
109}
110
111impl OutboundPacket {
112 fn data(bytes: Vec<u8>) -> Self {
113 OutboundPacket {
114 bytes,
115 is_data: true,
116 }
117 }
118
119 fn control(bytes: Vec<u8>) -> Self {
120 OutboundPacket {
121 bytes,
122 is_data: false,
123 }
124 }
125}
126
127// ===========================================================================
128// SrtSocket — a handle to an established SRT connection
129// ===========================================================================
130
131/// A handle to an established SRT connection over UDP.
132///
133/// Created by [`SrtSocket::connect`] (caller role) or
134/// [`SrtListener::accept`] (listener role). The protocol itself runs on a
135/// background [`tokio`] task the handle owns; [`send`](Self::send) enqueues an
136/// application payload and [`recv`](Self::recv) awaits a delivered one.
137/// Dropping the handle aborts the driver task.
138#[derive(Debug)]
139pub struct SrtSocket {
140 peer_addr: std::net::SocketAddr,
141 /// Application payloads flowing to the driver task for transmission.
142 to_driver: mpsc::UnboundedSender<Vec<u8>>,
143 /// TSBPD/ARQ-delivered payloads flowing back from the driver task.
144 from_driver: mpsc::UnboundedReceiver<Vec<u8>>,
145 /// The driver task; aborted on drop.
146 driver: Option<tokio::task::JoinHandle<()>>,
147}
148
149impl SrtSocket {
150 /// Connect to a remote SRT peer as a Caller.
151 pub async fn connect<A: tokio::net::ToSocketAddrs>(
152 remote_addr: A,
153 config: HandshakeConfig,
154 ) -> Result<Self> {
155 let local = "0.0.0.0:0".parse::<std::net::SocketAddr>().unwrap();
156 Self::connect_from(local, remote_addr, config).await
157 }
158
159 /// Connect from a specific local address.
160 pub async fn connect_from<A: tokio::net::ToSocketAddrs>(
161 local_addr: std::net::SocketAddr,
162 remote_addr: A,
163 config: HandshakeConfig,
164 ) -> Result<Self> {
165 let socket = UdpSocket::bind(local_addr)
166 .await
167 .map_err(|e| io_err("bind", e))?;
168 let peer = resolve_one(remote_addr).await?;
169 let socket = Arc::new(socket);
170
171 let own_socket_id = config.initial_seq_number;
172 let mut hs = CallerHandshake::new(own_socket_id, config.clone());
173
174 // Send INDUCTION.
175 let induction = hs.start().map_err(|_| Error::InvalidField {
176 what: "caller start",
177 reason: "start failed",
178 })?;
179 socket
180 .send_to(&induction, peer)
181 .await
182 .map_err(|e| io_err("send induction", e))?;
183
184 let mut buf = [0u8; MAX_DATAGRAM];
185
186 loop {
187 match hs.state() {
188 CallerHandshakeState::Connected => break,
189 CallerHandshakeState::Rejected | CallerHandshakeState::TimedOut => {
190 return Err(Error::InvalidField {
191 what: "hs state",
192 reason: "rejected or timed out",
193 });
194 }
195 _ => {}
196 }
197
198 let n = tokio::time::timeout(HANDSHAKE_TIMEOUT, socket.recv_from(&mut buf)).await;
199
200 match n {
201 Ok(Ok((len, _src))) => {
202 let bytes = &buf[..len];
203 let outcomes = hs.feed_bytes(bytes).map_err(|_| Error::InvalidField {
204 what: "hs feed",
205 reason: "feed failed",
206 })?;
207
208 for outcome in outcomes {
209 match outcome {
210 HandshakeOutput::Send(bytes) => {
211 socket
212 .send_to(&bytes, peer)
213 .await
214 .map_err(|e| io_err("send hs", e))?;
215 }
216 HandshakeOutput::Connected(params) => {
217 // The peer's ISN (seeds ARQ/TSBPD sequence
218 // tracking) is carried in the handshake bytes
219 // we just fed; the peer's SRT Socket ID (the
220 // wire `dest_socket_id` for every outgoing
221 // packet) is the negotiated
222 // `params.peer_socket_id` — the two are
223 // unrelated values (§3).
224 let peer_isn = extract_isn_from_bytes(bytes).unwrap_or(0);
225 let epoch = Instant::now();
226 let tsbpd_delay_ms = u64::from(config.latency_ms);
227 let tsbpd_time_base = 0u64;
228 let conn = SrtSocket::spawn(
229 socket,
230 peer,
231 config.initial_seq_number,
232 peer_isn,
233 params.peer_socket_id,
234 tsbpd_time_base,
235 tsbpd_delay_ms,
236 epoch,
237 );
238 return Ok(conn);
239 }
240 HandshakeOutput::Rejected(_) => {
241 return Err(Error::InvalidField {
242 what: "hs rejected",
243 reason: "peer rejected",
244 });
245 }
246 HandshakeOutput::TimedOut => {
247 return Err(Error::InvalidField {
248 what: "hs timeout",
249 reason: "caller timed out",
250 });
251 }
252 }
253 }
254 }
255 Ok(Err(e)) => return Err(io_err("recv hs", e)),
256 Err(_) => {
257 // Tick retransmit.
258 for outcome in hs.tick() {
259 match outcome {
260 HandshakeOutput::Send(bytes) => {
261 socket
262 .send_to(&bytes, peer)
263 .await
264 .map_err(|e| io_err("retransmit", e))?;
265 }
266 HandshakeOutput::TimedOut => {
267 return Err(Error::InvalidField {
268 what: "hs timeout",
269 reason: "retransmit exhausted",
270 });
271 }
272 _ => {}
273 }
274 }
275 }
276 }
277 }
278
279 Err(Error::InvalidField {
280 what: "handshake",
281 reason: "unreachable",
282 })
283 }
284
285 /// Build the engine state, spawn its background driver task, and return
286 /// the [`SrtSocket`] handle wired to it.
287 #[allow(clippy::too_many_arguments)]
288 fn spawn(
289 udp: Arc<UdpSocket>,
290 peer_addr: std::net::SocketAddr,
291 our_initial_seq: u32,
292 peer_initial_seq: u32,
293 peer_socket_id: u32,
294 tsbpd_time_base: u64,
295 tsbpd_delay_ms: u64,
296 epoch: Instant,
297 ) -> Self {
298 let (to_driver, app_out) = mpsc::unbounded_channel::<Vec<u8>>();
299 let (deliver, from_driver) = mpsc::unbounded_channel::<Vec<u8>>();
300
301 let driver = Driver {
302 udp,
303 peer_addr,
304 peer_socket_id,
305 // `dest_socket_id` on every outgoing DATA/ACKACK/NAK/ACK packet
306 // must be the peer's negotiated SRT Socket ID, not its ISN — the
307 // two are unrelated values (§3).
308 sender: ArqSender::new(peer_socket_id),
309 receiver: ArqReceiver::new(peer_socket_id, peer_initial_seq),
310 tsbpd: TsbpdScheduler::new(
311 peer_initial_seq,
312 tsbpd_time_base,
313 tsbpd_delay_ms,
314 DEFAULT_DRIFT_US,
315 DEFAULT_TLPKT_DROP_ENABLED,
316 None,
317 ),
318 livecc: LiveCC::new(DEFAULT_MAX_BW),
319 next_message_number: 1,
320 next_send_seq: our_initial_seq,
321 epoch,
322 staged: std::collections::BTreeMap::new(),
323 outbound: VecDeque::new(),
324 deliver,
325 peer_shutdown: false,
326 };
327
328 let handle = tokio::spawn(driver.run(app_out));
329
330 SrtSocket {
331 peer_addr,
332 to_driver,
333 from_driver,
334 driver: Some(handle),
335 }
336 }
337
338 /// Enqueue a payload for transmission to the peer.
339 ///
340 /// Returns immediately once the payload is handed to the driver task —
341 /// actual transmission, ACK/NAK handling, and retransmission all happen
342 /// on that task. Fails only if the driver task has stopped (peer shut
343 /// down or connection error).
344 pub async fn send(&mut self, payload: &[u8]) -> Result<()> {
345 self.to_driver
346 .send(payload.to_vec())
347 .map_err(|_| Error::Io {
348 kind: std::io::ErrorKind::BrokenPipe,
349 context: "send",
350 })
351 }
352
353 /// The peer's socket address.
354 pub fn peer_addr(&self) -> std::net::SocketAddr {
355 self.peer_addr
356 }
357
358 /// Receive the next payload, waiting until one is available.
359 /// Returns `None` once the peer has shut down (or the driver task has
360 /// stopped) and no further payloads will arrive.
361 pub async fn recv(&mut self) -> Result<Option<Vec<u8>>> {
362 Ok(self.from_driver.recv().await)
363 }
364}
365
366impl Drop for SrtSocket {
367 fn drop(&mut self) {
368 if let Some(handle) = self.driver.take() {
369 handle.abort();
370 }
371 }
372}
373
374// ===========================================================================
375// Driver — the per-connection background task
376// ===========================================================================
377
378/// The engine state driven by one connection's background task. Owns the
379/// socket, the sans-IO ARQ/TSBPD/LiveCC engines, and the outbound queue; runs
380/// the RX / app-send / periodic-tick select loop in [`Driver::run`].
381struct Driver {
382 udp: Arc<UdpSocket>,
383 peer_addr: std::net::SocketAddr,
384 peer_socket_id: u32,
385
386 // ARQ
387 sender: ArqSender,
388 receiver: ArqReceiver,
389
390 // TSBPD
391 tsbpd: TsbpdScheduler,
392
393 // LiveCC pacing
394 livecc: LiveCC,
395
396 next_message_number: u32,
397 next_send_seq: u32,
398
399 // Wall-clock epoch for `now: Duration`.
400 epoch: Instant,
401
402 // Staging: seq → payload bytes, released to `deliver` by TSBPD/ARQ.
403 staged: std::collections::BTreeMap<u32, Vec<u8>>,
404
405 // Outbound datagram queue (data paced, control not).
406 outbound: VecDeque<OutboundPacket>,
407
408 // Delivered payloads flowing back to the application handle.
409 deliver: mpsc::UnboundedSender<Vec<u8>>,
410
411 peer_shutdown: bool,
412}
413
414impl Driver {
415 /// The select loop: socket RX, application-send, and a periodic engine
416 /// tick — the tick arm is what keeps retransmit/ACK/NAK progressing when
417 /// neither peer is actively sending application data.
418 async fn run(mut self, mut app_out: mpsc::UnboundedReceiver<Vec<u8>>) {
419 // Clone the `Arc` so the RX future borrows a *local*, leaving the
420 // other select arms free to borrow `self` mutably.
421 let udp = Arc::clone(&self.udp);
422 let mut buf = [0u8; MAX_DATAGRAM];
423 let mut ticker = tokio::time::interval(Duration::from_millis(TICK_INTERVAL_MS));
424 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
425 let mut app_open = true;
426
427 // Set when the application handle is gone (its `to_driver` sender was
428 // dropped): the loop makes one final flush pass and then exits, so a
429 // dropped [`SrtSocket`] does not leave a driver task parked forever on
430 // the socket/timer arms — which would keep a current-thread runtime
431 // from shutting down. (`SrtSocket::Drop` also aborts the task; this is
432 // the cooperative path that does not rely on abort-during-shutdown.)
433 let mut shutting_down = false;
434
435 loop {
436 tokio::select! {
437 // Application handed us a payload to send.
438 maybe = app_out.recv(), if app_open => {
439 match maybe {
440 Some(payload) => self.send_one(&payload),
441 None => {
442 // Handle dropped its sender — no more app data
443 // will ever arrive. Stop polling this (now
444 // permanently `Ready(None)`) arm and tear down.
445 app_open = false;
446 shutting_down = true;
447 }
448 }
449 }
450 // A datagram arrived from the network.
451 r = udp.recv_from(&mut buf) => {
452 match r {
453 Ok((len, src)) if src == self.peer_addr => {
454 // A malformed/foreign datagram is ignored, not fatal.
455 let _ = self.ingress(&buf[..len]);
456 }
457 Ok(_) => {} // datagram from another peer; ignore.
458 Err(_) => break, // socket error — end the task.
459 }
460 }
461 // Periodic timers: retransmit, ACK, NAK, TSBPD release.
462 _ = ticker.tick() => {
463 self.tick_engines();
464 }
465 }
466
467 if self.flush_outbound().await.is_err() {
468 break;
469 }
470 if self.peer_shutdown || shutting_down {
471 break;
472 }
473 }
474 // Dropping `self.deliver` here closes the channel, so the handle's
475 // `recv` returns `None` (clean shutdown / task ended).
476 }
477
478 fn send_one(&mut self, payload: &[u8]) {
479 let now = self.elapsed();
480 self.livecc.on_data_packet(payload.len() as u64);
481 let bytes = self
482 .sender
483 .on_data(self.next_send_seq, self.next_message_number, payload, now);
484 self.next_send_seq = self.next_send_seq.wrapping_add(1);
485 self.next_message_number = self.next_message_number.wrapping_add(1);
486 self.outbound.push_back(OutboundPacket::data(bytes));
487 }
488
489 fn ingress(&mut self, bytes: &[u8]) -> Result<()> {
490 let now = self.elapsed();
491 let packet = SrtPacket::parse(bytes)?;
492
493 match packet {
494 SrtPacket::Data(d) => {
495 // The ARQ receiver drives *reliability* only: loss detection
496 // and the resulting NAK (rules 4, 14) plus the ACK point.
497 // Application delivery is the TSBPD scheduler's job — it is
498 // the single in-order delivery authority (see below), so its
499 // `outcome.delivered` is intentionally NOT used to deliver
500 // here. Running both cursors over one `staged` map races
501 // them and reorders retransmitted packets.
502 let outcome = self.receiver.feed_data(d.seq_number, now);
503 if let Some(nak_bytes) = outcome.nak {
504 self.outbound.push_back(OutboundPacket::control(nak_bytes));
505 }
506
507 self.staged
508 .entry(d.seq_number)
509 .or_insert_with(|| d.data.to_vec());
510
511 // TSBPD is the sole delivery cursor: it releases packets in
512 // strict sequence order, waiting for a NAK-recovered gap to
513 // be filled rather than skipping it (TLPKTDROP disabled — see
514 // `DEFAULT_TLPKT_DROP_ENABLED`).
515 let tsbpd_out = self.tsbpd.feed_data(d.seq_number, d.timestamp, now);
516 for &seq in &tsbpd_out.delivered {
517 if let Some(payload) = self.staged.remove(&seq) {
518 let _ = self.deliver.send(payload);
519 }
520 }
521 }
522 SrtPacket::Control(ref c) => match c {
523 ControlPacket::Ack(ack) => {
524 if let Some(ackack_bytes) = self.sender.on_ack(ack, now) {
525 self.outbound
526 .push_back(OutboundPacket::control(ackack_bytes));
527 }
528 }
529 ControlPacket::Nak(nak) => {
530 // Record the reported loss; the next `tick_engines`
531 // (or this cycle's, if a tick fired) drains the
532 // retransmit queue — retransmits are queued ahead of any
533 // new first-time data (rules 5, 15, 16).
534 self.sender.on_nak(nak);
535 }
536 ControlPacket::AckAck(ackack) => {
537 self.receiver.on_ackack(ackack, now);
538 }
539 ControlPacket::KeepAlive(_) => {
540 let pkt = ControlPacket::KeepAlive(KeepAlivePacket {
541 timestamp: self.elapsed_us(),
542 dest_socket_id: self.peer_socket_id,
543 });
544 let mut buf = vec![0u8; pkt.serialized_len()];
545 let _ = pkt.serialize_into(&mut buf);
546 self.outbound.push_back(OutboundPacket::control(buf));
547 }
548 ControlPacket::Shutdown(_) => {
549 self.peer_shutdown = true;
550 }
551 _ => {}
552 },
553 }
554
555 Ok(())
556 }
557
558 fn tick_engines(&mut self) {
559 let now = self.elapsed();
560
561 // Retransmitted DATA packets first (rules 5, 15, 16, 18): they are
562 // drained from the NAK-populated loss list and queued *before* any
563 // new first-time data appended later this cycle, reproducing the
564 // sans-IO engine's "loss list before first transmission" priority
565 // (see `arq::sender`'s module doc). Feed LiveCC the same as a
566 // first-time send (`specs/rules/srt-livecc.md` §5.1.2, L3216-3217:
567 // "original or retransmitted") and tag them `is_data` so pacing
568 // applies.
569 for bytes in self.sender.tick(now) {
570 if let Ok(dp) = DataPacket::parse(&bytes) {
571 self.livecc.on_data_packet(dp.data.len() as u64);
572 }
573 self.outbound.push_back(OutboundPacket::data(bytes));
574 }
575
576 // Periodic ACK/NAK (rules 11, 12, 21, 22): control feedback, never
577 // paced.
578 for bytes in self.receiver.tick(now) {
579 self.outbound.push_back(OutboundPacket::control(bytes));
580 }
581
582 let tsbpd_out = self.tsbpd.tick(now);
583 for &seq in &tsbpd_out.delivered {
584 if let Some(payload) = self.staged.remove(&seq) {
585 let _ = self.deliver.send(payload);
586 }
587 }
588 }
589
590 fn elapsed(&self) -> Duration {
591 Instant::now().duration_since(self.epoch)
592 }
593
594 fn elapsed_us(&self) -> u32 {
595 self.elapsed().as_micros().min(u128::from(u32::MAX)) as u32
596 }
597
598 async fn flush_outbound(&mut self) -> Result<()> {
599 while let Some(item) = self.outbound.pop_front() {
600 // `specs/rules/srt-livecc.md` §5.1.2: `PKT_SND_PERIOD` paces DATA
601 // packets only — control feedback (ACK/NAK/ACKACK/Keep-Alive)
602 // must go out immediately, or loss recovery (which rides on
603 // that same control traffic) would be throttled right along
604 // with the data it is meant to unblock.
605 if item.is_data {
606 let period = self.livecc.on_ack_received();
607 if period > Duration::ZERO {
608 tokio::time::sleep(period).await;
609 }
610 }
611 self.udp
612 .send_to(&item.bytes, self.peer_addr)
613 .await
614 .map_err(|e| io_err("send", e))?;
615 }
616 Ok(())
617 }
618}
619
620// ===========================================================================
621// SrtListener
622// ===========================================================================
623
624/// An SRT listener that accepts incoming Caller connections.
625#[derive(Debug)]
626pub struct SrtListener {
627 udp: Arc<UdpSocket>,
628 config: HandshakeConfig,
629 next_socket_id: u32,
630 /// Per-listener secret input to [`derive_cookie`] (`draft-sharabayko-srt-01`
631 /// §4.3.1.1: "a cookie that is crafted based on host, port and current
632 /// time"). Generated once at [`SrtListener::bind`] so every SYN Cookie
633 /// this listener hands out is per-instance, not a fixed shared value a
634 /// remote peer could pre-compute and replay against a different listener.
635 cookie_secret: u64,
636 pending: std::collections::HashMap<std::net::SocketAddr, PendingListener>,
637 outbound_queue: std::collections::HashMap<std::net::SocketAddr, VecDeque<Vec<u8>>>,
638}
639
640#[derive(Debug)]
641struct PendingListener {
642 handshake: ListenerHandshake,
643 params: Option<HandshakeOutput>,
644 /// The peer's ISN, extracted from the INDUCTION handshake packet.
645 peer_initial_seq: u32,
646}
647
648impl SrtListener {
649 /// Bind an SRT listener on `addr`.
650 pub async fn bind<A: tokio::net::ToSocketAddrs>(
651 addr: A,
652 config: HandshakeConfig,
653 ) -> Result<Self> {
654 let socket = UdpSocket::bind(addr).await.map_err(|e| io_err("bind", e))?;
655 Ok(SrtListener {
656 udp: Arc::new(socket),
657 config,
658 next_socket_id: 1,
659 cookie_secret: random_u64(),
660 pending: std::collections::HashMap::new(),
661 outbound_queue: std::collections::HashMap::new(),
662 })
663 }
664
665 /// The local socket address the listener is bound to.
666 pub fn local_addr(&self) -> Result<std::net::SocketAddr> {
667 self.udp.local_addr().map_err(|e| io_err("local_addr", e))
668 }
669
670 /// Accept the next incoming SRT connection.
671 pub async fn accept(&mut self) -> Result<SrtSocket> {
672 let mut buf = [0u8; MAX_DATAGRAM];
673
674 loop {
675 if let Some(conn) = self.drain_completed() {
676 return conn;
677 }
678
679 let n = tokio::time::timeout(Duration::from_millis(100), self.udp.recv_from(&mut buf))
680 .await;
681
682 match n {
683 Ok(Ok((len, src))) => {
684 let _ = self.handle_datagram(src, &buf[..len]);
685 self.flush_for_peer(src).await?;
686 }
687 Ok(Err(e)) => return Err(io_err("recv_from", e)),
688 Err(_) => {
689 self.tick_pending();
690 self.flush_all().await?;
691 }
692 }
693 }
694 }
695
696 fn handle_datagram(&mut self, src: std::net::SocketAddr, bytes: &[u8]) -> Result<()> {
697 let packet = SrtPacket::parse(bytes).map_err(|_| Error::InvalidField {
698 what: "parse",
699 reason: "non-SRT datagram",
700 })?;
701
702 let ctrl = match packet {
703 SrtPacket::Control(c) => c,
704 _ => return Ok(()),
705 };
706
707 let is_new = !self.pending.contains_key(&src);
708
709 if is_new {
710 let peer_isn = match &ctrl {
711 ControlPacket::Handshake(hp) => hp.initial_seq_number,
712 _ => return Ok(()),
713 };
714
715 let own_socket_id = self.next_socket_id;
716 self.next_socket_id = self.next_socket_id.wrapping_add(1);
717 // §4.3.1.1: "a cookie that is crafted based on host, port and
718 // current time with 1 minute accuracy" — `derive_cookie` mixes
719 // exactly those inputs (`crate::handshake_sm`'s existing,
720 // documented derivation), keyed by this listener's own secret so
721 // two listeners never hand out the same cookie for the same
722 // peer/time bucket.
723 let peer_key = addr_to_u64(&src);
724 let time_bucket = unix_time_bucket();
725 let syn_cookie = derive_cookie(peer_key, time_bucket, self.cookie_secret);
726 let hs = ListenerHandshake::new(own_socket_id, syn_cookie, self.config.clone());
727 self.pending.insert(
728 src,
729 PendingListener {
730 handshake: hs,
731 params: None,
732 peer_initial_seq: peer_isn,
733 },
734 );
735 }
736
737 let entry = self.pending.get_mut(&src).ok_or(Error::InvalidField {
738 what: "pending",
739 reason: "no pending entry",
740 })?;
741
742 let outcomes = entry
743 .handshake
744 .feed(&ctrl)
745 .map_err(|_| Error::InvalidField {
746 what: "listener feed",
747 reason: "feed failed",
748 })?;
749
750 for outcome in outcomes {
751 match outcome {
752 HandshakeOutput::Send(bytes) => {
753 self.outbound_queue.entry(src).or_default().push_back(bytes);
754 }
755 HandshakeOutput::Connected(_) => {
756 entry.params = Some(HandshakeOutput::Connected(
757 entry.handshake.negotiated().unwrap().clone(),
758 ));
759 }
760 HandshakeOutput::Rejected(_) => {
761 self.pending.remove(&src);
762 return Err(Error::InvalidField {
763 what: "hs rejected",
764 reason: "peer rejected",
765 });
766 }
767 HandshakeOutput::TimedOut => {
768 self.pending.remove(&src);
769 return Err(Error::InvalidField {
770 what: "hs timeout",
771 reason: "listener",
772 });
773 }
774 }
775 }
776
777 Ok(())
778 }
779
780 fn tick_pending(&mut self) {
781 let mut to_remove = Vec::new();
782 for (addr, entry) in self.pending.iter_mut() {
783 for outcome in entry.handshake.tick() {
784 match outcome {
785 HandshakeOutput::Send(bytes) => {
786 self.outbound_queue
787 .entry(*addr)
788 .or_default()
789 .push_back(bytes);
790 }
791 HandshakeOutput::TimedOut => {
792 to_remove.push(*addr);
793 }
794 _ => {}
795 }
796 }
797 }
798 for addr in to_remove {
799 self.pending.remove(&addr);
800 }
801 }
802
803 fn drain_completed(&mut self) -> Option<Result<SrtSocket>> {
804 let addr = self
805 .pending
806 .iter()
807 .find(|(_, p)| {
808 p.params.is_some()
809 && matches!(p.handshake.state(), ListenerHandshakeState::Connected)
810 })
811 .map(|(addr, _)| *addr)?;
812
813 let entry = self.pending.remove(&addr)?;
814 let peer_initial_seq = entry.peer_initial_seq;
815 // The peer's negotiated SRT Socket ID (distinct from its ISN above)
816 // — `drain_completed` only reaches entries filtered to
817 // `ListenerHandshakeState::Connected`, so `negotiated()` is always
818 // `Some` here.
819 let peer_socket_id = entry
820 .handshake
821 .negotiated()
822 .expect("filtered to Connected state")
823 .peer_socket_id;
824 let our_initial_seq = self.config.initial_seq_number;
825 let tsbpd_delay_ms = u64::from(self.config.latency_ms);
826 let tsbpd_time_base = 0;
827 let epoch = Instant::now();
828
829 // Share the listener's Arc<UdpSocket> with the connection's driver.
830 let conn = SrtSocket::spawn(
831 Arc::clone(&self.udp),
832 addr,
833 our_initial_seq,
834 peer_initial_seq,
835 peer_socket_id,
836 tsbpd_time_base,
837 tsbpd_delay_ms,
838 epoch,
839 );
840 Some(Ok(conn))
841 }
842
843 async fn flush_for_peer(&mut self, addr: std::net::SocketAddr) -> Result<()> {
844 if let Some(queue) = self.outbound_queue.get_mut(&addr) {
845 while let Some(bytes) = queue.pop_front() {
846 self.udp
847 .send_to(&bytes, addr)
848 .await
849 .map_err(|e| io_err("send_to", e))?;
850 }
851 }
852 Ok(())
853 }
854
855 async fn flush_all(&mut self) -> Result<()> {
856 let addrs: Vec<std::net::SocketAddr> = self.outbound_queue.keys().copied().collect();
857 for addr in addrs {
858 self.flush_for_peer(addr).await?;
859 }
860 Ok(())
861 }
862}
863
864// ===========================================================================
865// Helpers
866// ===========================================================================
867
868/// Maps an OS I/O failure to a structured [`Error::Io`], preserving the
869/// `std::io::ErrorKind` (bind failures are then distinguishable from
870/// mid-connection resets, etc.) and the call site that failed. `std::io::Error`
871/// itself is not `Clone`/`Eq` (this crate's [`Error`] derives both), so only
872/// its `kind()` is kept — see the `S4` release-audit finding.
873fn io_err(context: &'static str, e: std::io::Error) -> Error {
874 Error::Io {
875 kind: e.kind(),
876 context,
877 }
878}
879
880/// Mixes a [`std::net::SocketAddr`] into a `u64` for use as `derive_cookie`'s
881/// `peer_key` input (§4.3.1.1: the cookie is "crafted based on host,
882/// port..."). Not a spec-defined algorithm — any stable, well-distributed
883/// mix of the peer's address is sufficient here.
884fn addr_to_u64(addr: &std::net::SocketAddr) -> u64 {
885 let mut hasher = std::collections::hash_map::DefaultHasher::new();
886 addr.hash(&mut hasher);
887 hasher.finish()
888}
889
890/// The current UNIX time, bucketed to 1-minute accuracy — the `time_bucket`
891/// input `derive_cookie` expects (§4.3.1.1: "...and current time with 1
892/// minute accuracy").
893fn unix_time_bucket() -> u32 {
894 let secs = std::time::SystemTime::now()
895 .duration_since(std::time::UNIX_EPOCH)
896 .unwrap_or_default()
897 .as_secs();
898 (secs / 60) as u32
899}
900
901/// A per-process/per-listener random `u64`, used as `derive_cookie`'s
902/// `secret` input. Sourced from `std::collections::hash_map::RandomState`
903/// (the standard library's own OS-seeded randomness, already used internally
904/// for `HashMap` DoS resistance) rather than pulling in a `rand` dependency
905/// for one seed value.
906fn random_u64() -> u64 {
907 std::collections::hash_map::RandomState::new()
908 .build_hasher()
909 .finish()
910}
911
912async fn resolve_one<A: tokio::net::ToSocketAddrs>(addr: A) -> Result<std::net::SocketAddr> {
913 let mut addrs = tokio::net::lookup_host(addr)
914 .await
915 .map_err(|e| io_err("resolve", e))?;
916 addrs.next().ok_or(Error::InvalidField {
917 what: "resolve",
918 reason: "no addrs",
919 })
920}
921
922/// Extract the `initial_seq_number` from a handshake control packet's bytes.
923/// Returns `None` if the bytes don't parse as a handshake control packet.
924fn extract_isn_from_bytes(bytes: &[u8]) -> Option<u32> {
925 let pkt = SrtPacket::parse(bytes).ok()?;
926 match pkt {
927 SrtPacket::Control(ControlPacket::Handshake(hp)) => Some(hp.initial_seq_number),
928 _ => None,
929 }
930}