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 //
225 // `require_peer_isn` errors (rather than
226 // defaulting to 0) if the very bytes that just
227 // drove the handshake state machine to
228 // `Connected` fail to re-parse as a Handshake
229 // control packet — an internal inconsistency
230 // between this extraction shim and
231 // `CallerHandshake`. A silent `0` fallback
232 // would be indistinguishable from a genuine
233 // ISN of 0 and would seed ARQ/TSBPD sequence
234 // tracking wrong for the life of the
235 // connection, surfacing only much later as
236 // inexplicable loss/reordering.
237 let peer_isn = require_peer_isn(bytes)?;
238 let epoch = Instant::now();
239 let tsbpd_delay_ms = u64::from(config.latency_ms);
240 let tsbpd_time_base = 0u64;
241 let conn = SrtSocket::spawn(
242 socket,
243 peer,
244 config.initial_seq_number,
245 peer_isn,
246 params.peer_socket_id,
247 tsbpd_time_base,
248 tsbpd_delay_ms,
249 epoch,
250 );
251 return Ok(conn);
252 }
253 HandshakeOutput::Rejected(_) => {
254 return Err(Error::InvalidField {
255 what: "hs rejected",
256 reason: "peer rejected",
257 });
258 }
259 HandshakeOutput::TimedOut => {
260 return Err(Error::InvalidField {
261 what: "hs timeout",
262 reason: "caller timed out",
263 });
264 }
265 }
266 }
267 }
268 Ok(Err(e)) => return Err(io_err("recv hs", e)),
269 Err(_) => {
270 // Tick retransmit.
271 for outcome in hs.tick() {
272 match outcome {
273 HandshakeOutput::Send(bytes) => {
274 socket
275 .send_to(&bytes, peer)
276 .await
277 .map_err(|e| io_err("retransmit", e))?;
278 }
279 HandshakeOutput::TimedOut => {
280 return Err(Error::InvalidField {
281 what: "hs timeout",
282 reason: "retransmit exhausted",
283 });
284 }
285 _ => {}
286 }
287 }
288 }
289 }
290 }
291
292 Err(Error::InvalidField {
293 what: "handshake",
294 reason: "unreachable",
295 })
296 }
297
298 /// Build the engine state, spawn its background driver task, and return
299 /// the [`SrtSocket`] handle wired to it.
300 #[allow(clippy::too_many_arguments)]
301 fn spawn(
302 udp: Arc<UdpSocket>,
303 peer_addr: std::net::SocketAddr,
304 our_initial_seq: u32,
305 peer_initial_seq: u32,
306 peer_socket_id: u32,
307 tsbpd_time_base: u64,
308 tsbpd_delay_ms: u64,
309 epoch: Instant,
310 ) -> Self {
311 let (to_driver, app_out) = mpsc::unbounded_channel::<Vec<u8>>();
312 let (deliver, from_driver) = mpsc::unbounded_channel::<Vec<u8>>();
313
314 let driver = Driver {
315 udp,
316 peer_addr,
317 peer_socket_id,
318 // `dest_socket_id` on every outgoing DATA/ACKACK/NAK/ACK packet
319 // must be the peer's negotiated SRT Socket ID, not its ISN — the
320 // two are unrelated values (§3).
321 sender: ArqSender::new(peer_socket_id),
322 receiver: ArqReceiver::new(peer_socket_id, peer_initial_seq),
323 tsbpd: TsbpdScheduler::new(
324 peer_initial_seq,
325 tsbpd_time_base,
326 tsbpd_delay_ms,
327 DEFAULT_DRIFT_US,
328 DEFAULT_TLPKT_DROP_ENABLED,
329 None,
330 ),
331 livecc: LiveCC::new(DEFAULT_MAX_BW),
332 next_message_number: 1,
333 next_send_seq: our_initial_seq,
334 epoch,
335 staged: std::collections::BTreeMap::new(),
336 outbound: VecDeque::new(),
337 deliver,
338 peer_shutdown: false,
339 };
340
341 let handle = tokio::spawn(driver.run(app_out));
342
343 SrtSocket {
344 peer_addr,
345 to_driver,
346 from_driver,
347 driver: Some(handle),
348 }
349 }
350
351 /// Enqueue a payload for transmission to the peer.
352 ///
353 /// Returns immediately once the payload is handed to the driver task —
354 /// actual transmission, ACK/NAK handling, and retransmission all happen
355 /// on that task. Fails only if the driver task has stopped (peer shut
356 /// down or connection error).
357 pub async fn send(&mut self, payload: &[u8]) -> Result<()> {
358 self.to_driver
359 .send(payload.to_vec())
360 .map_err(|_| Error::Io {
361 kind: std::io::ErrorKind::BrokenPipe,
362 context: "send",
363 })
364 }
365
366 /// The peer's socket address.
367 pub fn peer_addr(&self) -> std::net::SocketAddr {
368 self.peer_addr
369 }
370
371 /// Receive the next payload, waiting until one is available.
372 /// Returns `None` once the peer has shut down (or the driver task has
373 /// stopped) and no further payloads will arrive.
374 pub async fn recv(&mut self) -> Result<Option<Vec<u8>>> {
375 Ok(self.from_driver.recv().await)
376 }
377}
378
379impl Drop for SrtSocket {
380 fn drop(&mut self) {
381 if let Some(handle) = self.driver.take() {
382 handle.abort();
383 }
384 }
385}
386
387// ===========================================================================
388// Driver — the per-connection background task
389// ===========================================================================
390
391/// The engine state driven by one connection's background task. Owns the
392/// socket, the sans-IO ARQ/TSBPD/LiveCC engines, and the outbound queue; runs
393/// the RX / app-send / periodic-tick select loop in [`Driver::run`].
394struct Driver {
395 udp: Arc<UdpSocket>,
396 peer_addr: std::net::SocketAddr,
397 peer_socket_id: u32,
398
399 // ARQ
400 sender: ArqSender,
401 receiver: ArqReceiver,
402
403 // TSBPD
404 tsbpd: TsbpdScheduler,
405
406 // LiveCC pacing
407 livecc: LiveCC,
408
409 next_message_number: u32,
410 next_send_seq: u32,
411
412 // Wall-clock epoch for `now: Duration`.
413 epoch: Instant,
414
415 // Staging: seq → payload bytes, released to `deliver` by TSBPD/ARQ.
416 staged: std::collections::BTreeMap<u32, Vec<u8>>,
417
418 // Outbound datagram queue (data paced, control not).
419 outbound: VecDeque<OutboundPacket>,
420
421 // Delivered payloads flowing back to the application handle.
422 deliver: mpsc::UnboundedSender<Vec<u8>>,
423
424 peer_shutdown: bool,
425}
426
427impl Driver {
428 /// The select loop: socket RX, application-send, and a periodic engine
429 /// tick — the tick arm is what keeps retransmit/ACK/NAK progressing when
430 /// neither peer is actively sending application data.
431 async fn run(mut self, mut app_out: mpsc::UnboundedReceiver<Vec<u8>>) {
432 // Clone the `Arc` so the RX future borrows a *local*, leaving the
433 // other select arms free to borrow `self` mutably.
434 let udp = Arc::clone(&self.udp);
435 let mut buf = [0u8; MAX_DATAGRAM];
436 let mut ticker = tokio::time::interval(Duration::from_millis(TICK_INTERVAL_MS));
437 ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Delay);
438 let mut app_open = true;
439
440 // Set when the application handle is gone (its `to_driver` sender was
441 // dropped): the loop makes one final flush pass and then exits, so a
442 // dropped [`SrtSocket`] does not leave a driver task parked forever on
443 // the socket/timer arms — which would keep a current-thread runtime
444 // from shutting down. (`SrtSocket::Drop` also aborts the task; this is
445 // the cooperative path that does not rely on abort-during-shutdown.)
446 let mut shutting_down = false;
447
448 loop {
449 tokio::select! {
450 // Application handed us a payload to send.
451 maybe = app_out.recv(), if app_open => {
452 match maybe {
453 Some(payload) => self.send_one(&payload),
454 None => {
455 // Handle dropped its sender — no more app data
456 // will ever arrive. Stop polling this (now
457 // permanently `Ready(None)`) arm and tear down.
458 app_open = false;
459 shutting_down = true;
460 }
461 }
462 }
463 // A datagram arrived from the network.
464 r = udp.recv_from(&mut buf) => {
465 match r {
466 Ok((len, src)) if src == self.peer_addr => {
467 // A malformed/foreign datagram is ignored, not fatal.
468 let _ = self.ingress(&buf[..len]);
469 }
470 Ok(_) => {} // datagram from another peer; ignore.
471 Err(_) => break, // socket error — end the task.
472 }
473 }
474 // Periodic timers: retransmit, ACK, NAK, TSBPD release.
475 _ = ticker.tick() => {
476 self.tick_engines();
477 }
478 }
479
480 if self.flush_outbound().await.is_err() {
481 break;
482 }
483 if self.peer_shutdown || shutting_down {
484 break;
485 }
486 }
487 // Dropping `self.deliver` here closes the channel, so the handle's
488 // `recv` returns `None` (clean shutdown / task ended).
489 }
490
491 fn send_one(&mut self, payload: &[u8]) {
492 let now = self.elapsed();
493 self.livecc.on_data_packet(payload.len() as u64);
494 let bytes = self
495 .sender
496 .on_data(self.next_send_seq, self.next_message_number, payload, now);
497 self.next_send_seq = self.next_send_seq.wrapping_add(1);
498 self.next_message_number = self.next_message_number.wrapping_add(1);
499 self.outbound.push_back(OutboundPacket::data(bytes));
500 }
501
502 fn ingress(&mut self, bytes: &[u8]) -> Result<()> {
503 let now = self.elapsed();
504 let packet = SrtPacket::parse(bytes)?;
505
506 match packet {
507 SrtPacket::Data(d) => {
508 // The ARQ receiver drives *reliability* only: loss detection
509 // and the resulting NAK (rules 4, 14) plus the ACK point.
510 // Application delivery is the TSBPD scheduler's job — it is
511 // the single in-order delivery authority (see below), so its
512 // `outcome.delivered` is intentionally NOT used to deliver
513 // here. Running both cursors over one `staged` map races
514 // them and reorders retransmitted packets.
515 let outcome = self.receiver.feed_data(d.seq_number, now);
516 if let Some(nak_bytes) = outcome.nak {
517 self.outbound.push_back(OutboundPacket::control(nak_bytes));
518 }
519
520 self.staged
521 .entry(d.seq_number)
522 .or_insert_with(|| d.data.to_vec());
523
524 // TSBPD is the sole delivery cursor: it releases packets in
525 // strict sequence order, waiting for a NAK-recovered gap to
526 // be filled rather than skipping it (TLPKTDROP disabled — see
527 // `DEFAULT_TLPKT_DROP_ENABLED`).
528 let tsbpd_out = self.tsbpd.feed_data(d.seq_number, d.timestamp, now);
529 for &seq in &tsbpd_out.delivered {
530 if let Some(payload) = self.staged.remove(&seq) {
531 let _ = self.deliver.send(payload);
532 }
533 }
534 }
535 SrtPacket::Control(ref c) => match c {
536 ControlPacket::Ack(ack) => {
537 if let Some(ackack_bytes) = self.sender.on_ack(ack, now) {
538 self.outbound
539 .push_back(OutboundPacket::control(ackack_bytes));
540 }
541 }
542 ControlPacket::Nak(nak) => {
543 // Record the reported loss; the next `tick_engines`
544 // (or this cycle's, if a tick fired) drains the
545 // retransmit queue — retransmits are queued ahead of any
546 // new first-time data (rules 5, 15, 16).
547 self.sender.on_nak(nak);
548 }
549 ControlPacket::AckAck(ackack) => {
550 self.receiver.on_ackack(ackack, now);
551 }
552 ControlPacket::KeepAlive(_) => {
553 let pkt = ControlPacket::KeepAlive(KeepAlivePacket {
554 timestamp: self.elapsed_us(),
555 dest_socket_id: self.peer_socket_id,
556 });
557 let mut buf = vec![0u8; pkt.serialized_len()];
558 let _ = pkt.serialize_into(&mut buf);
559 self.outbound.push_back(OutboundPacket::control(buf));
560 }
561 ControlPacket::Shutdown(_) => {
562 self.peer_shutdown = true;
563 }
564 _ => {}
565 },
566 }
567
568 Ok(())
569 }
570
571 fn tick_engines(&mut self) {
572 let now = self.elapsed();
573
574 // Retransmitted DATA packets first (rules 5, 15, 16, 18): they are
575 // drained from the NAK-populated loss list and queued *before* any
576 // new first-time data appended later this cycle, reproducing the
577 // sans-IO engine's "loss list before first transmission" priority
578 // (see `arq::sender`'s module doc). Feed LiveCC the same as a
579 // first-time send (`specs/rules/srt-livecc.md` §5.1.2, L3216-3217:
580 // "original or retransmitted") and tag them `is_data` so pacing
581 // applies.
582 for bytes in self.sender.tick(now) {
583 if let Ok(dp) = DataPacket::parse(&bytes) {
584 self.livecc.on_data_packet(dp.data.len() as u64);
585 }
586 self.outbound.push_back(OutboundPacket::data(bytes));
587 }
588
589 // Periodic ACK/NAK (rules 11, 12, 21, 22): control feedback, never
590 // paced.
591 for bytes in self.receiver.tick(now) {
592 self.outbound.push_back(OutboundPacket::control(bytes));
593 }
594
595 let tsbpd_out = self.tsbpd.tick(now);
596 for &seq in &tsbpd_out.delivered {
597 if let Some(payload) = self.staged.remove(&seq) {
598 let _ = self.deliver.send(payload);
599 }
600 }
601 }
602
603 fn elapsed(&self) -> Duration {
604 Instant::now().duration_since(self.epoch)
605 }
606
607 fn elapsed_us(&self) -> u32 {
608 self.elapsed().as_micros().min(u128::from(u32::MAX)) as u32
609 }
610
611 async fn flush_outbound(&mut self) -> Result<()> {
612 while let Some(item) = self.outbound.pop_front() {
613 // `specs/rules/srt-livecc.md` §5.1.2: `PKT_SND_PERIOD` paces DATA
614 // packets only — control feedback (ACK/NAK/ACKACK/Keep-Alive)
615 // must go out immediately, or loss recovery (which rides on
616 // that same control traffic) would be throttled right along
617 // with the data it is meant to unblock.
618 if item.is_data {
619 let period = self.livecc.on_ack_received();
620 if period > Duration::ZERO {
621 tokio::time::sleep(period).await;
622 }
623 }
624 self.udp
625 .send_to(&item.bytes, self.peer_addr)
626 .await
627 .map_err(|e| io_err("send", e))?;
628 }
629 Ok(())
630 }
631}
632
633// ===========================================================================
634// SrtListener
635// ===========================================================================
636
637/// An SRT listener that accepts incoming Caller connections.
638#[derive(Debug)]
639pub struct SrtListener {
640 udp: Arc<UdpSocket>,
641 config: HandshakeConfig,
642 next_socket_id: u32,
643 /// Per-listener secret input to [`derive_cookie`] (`draft-sharabayko-srt-01`
644 /// §4.3.1.1: "a cookie that is crafted based on host, port and current
645 /// time"). Generated once at [`SrtListener::bind`] so every SYN Cookie
646 /// this listener hands out is per-instance, not a fixed shared value a
647 /// remote peer could pre-compute and replay against a different listener.
648 cookie_secret: u64,
649 pending: std::collections::HashMap<std::net::SocketAddr, PendingListener>,
650 outbound_queue: std::collections::HashMap<std::net::SocketAddr, VecDeque<Vec<u8>>>,
651}
652
653#[derive(Debug)]
654struct PendingListener {
655 handshake: ListenerHandshake,
656 params: Option<HandshakeOutput>,
657 /// The peer's ISN, extracted from the INDUCTION handshake packet.
658 peer_initial_seq: u32,
659}
660
661impl SrtListener {
662 /// Bind an SRT listener on `addr`.
663 pub async fn bind<A: tokio::net::ToSocketAddrs>(
664 addr: A,
665 config: HandshakeConfig,
666 ) -> Result<Self> {
667 let socket = UdpSocket::bind(addr).await.map_err(|e| io_err("bind", e))?;
668 Ok(SrtListener {
669 udp: Arc::new(socket),
670 config,
671 next_socket_id: 1,
672 cookie_secret: random_u64(),
673 pending: std::collections::HashMap::new(),
674 outbound_queue: std::collections::HashMap::new(),
675 })
676 }
677
678 /// The local socket address the listener is bound to.
679 pub fn local_addr(&self) -> Result<std::net::SocketAddr> {
680 self.udp.local_addr().map_err(|e| io_err("local_addr", e))
681 }
682
683 /// Accept the next incoming SRT connection.
684 pub async fn accept(&mut self) -> Result<SrtSocket> {
685 let mut buf = [0u8; MAX_DATAGRAM];
686
687 loop {
688 if let Some(conn) = self.drain_completed() {
689 return conn;
690 }
691
692 let n = tokio::time::timeout(Duration::from_millis(100), self.udp.recv_from(&mut buf))
693 .await;
694
695 match n {
696 Ok(Ok((len, src))) => {
697 let _ = self.handle_datagram(src, &buf[..len]);
698 self.flush_for_peer(src).await?;
699 }
700 Ok(Err(e)) => return Err(io_err("recv_from", e)),
701 Err(_) => {
702 self.tick_pending();
703 self.flush_all().await?;
704 }
705 }
706 }
707 }
708
709 fn handle_datagram(&mut self, src: std::net::SocketAddr, bytes: &[u8]) -> Result<()> {
710 let packet = SrtPacket::parse(bytes).map_err(|_| Error::InvalidField {
711 what: "parse",
712 reason: "non-SRT datagram",
713 })?;
714
715 let ctrl = match packet {
716 SrtPacket::Control(c) => c,
717 _ => return Ok(()),
718 };
719
720 let is_new = !self.pending.contains_key(&src);
721
722 if is_new {
723 let peer_isn = match &ctrl {
724 ControlPacket::Handshake(hp) => hp.initial_seq_number,
725 _ => return Ok(()),
726 };
727
728 let own_socket_id = self.next_socket_id;
729 self.next_socket_id = self.next_socket_id.wrapping_add(1);
730 // §4.3.1.1: "a cookie that is crafted based on host, port and
731 // current time with 1 minute accuracy" — `derive_cookie` mixes
732 // exactly those inputs (`crate::handshake_sm`'s existing,
733 // documented derivation), keyed by this listener's own secret so
734 // two listeners never hand out the same cookie for the same
735 // peer/time bucket.
736 let peer_key = addr_to_u64(&src);
737 let time_bucket = unix_time_bucket();
738 let syn_cookie = derive_cookie(peer_key, time_bucket, self.cookie_secret);
739 let hs = ListenerHandshake::new(own_socket_id, syn_cookie, self.config.clone());
740 self.pending.insert(
741 src,
742 PendingListener {
743 handshake: hs,
744 params: None,
745 peer_initial_seq: peer_isn,
746 },
747 );
748 }
749
750 let entry = self.pending.get_mut(&src).ok_or(Error::InvalidField {
751 what: "pending",
752 reason: "no pending entry",
753 })?;
754
755 let outcomes = entry
756 .handshake
757 .feed(&ctrl)
758 .map_err(|_| Error::InvalidField {
759 what: "listener feed",
760 reason: "feed failed",
761 })?;
762
763 for outcome in outcomes {
764 match outcome {
765 HandshakeOutput::Send(bytes) => {
766 self.outbound_queue.entry(src).or_default().push_back(bytes);
767 }
768 HandshakeOutput::Connected(_) => {
769 entry.params = Some(HandshakeOutput::Connected(
770 entry.handshake.negotiated().unwrap().clone(),
771 ));
772 }
773 HandshakeOutput::Rejected(_) => {
774 self.pending.remove(&src);
775 return Err(Error::InvalidField {
776 what: "hs rejected",
777 reason: "peer rejected",
778 });
779 }
780 HandshakeOutput::TimedOut => {
781 self.pending.remove(&src);
782 return Err(Error::InvalidField {
783 what: "hs timeout",
784 reason: "listener",
785 });
786 }
787 }
788 }
789
790 Ok(())
791 }
792
793 fn tick_pending(&mut self) {
794 let mut to_remove = Vec::new();
795 for (addr, entry) in self.pending.iter_mut() {
796 for outcome in entry.handshake.tick() {
797 match outcome {
798 HandshakeOutput::Send(bytes) => {
799 self.outbound_queue
800 .entry(*addr)
801 .or_default()
802 .push_back(bytes);
803 }
804 HandshakeOutput::TimedOut => {
805 to_remove.push(*addr);
806 }
807 _ => {}
808 }
809 }
810 }
811 for addr in to_remove {
812 self.pending.remove(&addr);
813 }
814 }
815
816 fn drain_completed(&mut self) -> Option<Result<SrtSocket>> {
817 let addr = self
818 .pending
819 .iter()
820 .find(|(_, p)| {
821 p.params.is_some()
822 && matches!(p.handshake.state(), ListenerHandshakeState::Connected)
823 })
824 .map(|(addr, _)| *addr)?;
825
826 let entry = self.pending.remove(&addr)?;
827 let peer_initial_seq = entry.peer_initial_seq;
828 // The peer's negotiated SRT Socket ID (distinct from its ISN above)
829 // — `drain_completed` only reaches entries filtered to
830 // `ListenerHandshakeState::Connected`, so `negotiated()` is always
831 // `Some` here.
832 let peer_socket_id = entry
833 .handshake
834 .negotiated()
835 .expect("filtered to Connected state")
836 .peer_socket_id;
837 let our_initial_seq = self.config.initial_seq_number;
838 let tsbpd_delay_ms = u64::from(self.config.latency_ms);
839 let tsbpd_time_base = 0;
840 let epoch = Instant::now();
841
842 // Share the listener's Arc<UdpSocket> with the connection's driver.
843 let conn = SrtSocket::spawn(
844 Arc::clone(&self.udp),
845 addr,
846 our_initial_seq,
847 peer_initial_seq,
848 peer_socket_id,
849 tsbpd_time_base,
850 tsbpd_delay_ms,
851 epoch,
852 );
853 Some(Ok(conn))
854 }
855
856 async fn flush_for_peer(&mut self, addr: std::net::SocketAddr) -> Result<()> {
857 if let Some(queue) = self.outbound_queue.get_mut(&addr) {
858 while let Some(bytes) = queue.pop_front() {
859 self.udp
860 .send_to(&bytes, addr)
861 .await
862 .map_err(|e| io_err("send_to", e))?;
863 }
864 }
865 Ok(())
866 }
867
868 async fn flush_all(&mut self) -> Result<()> {
869 let addrs: Vec<std::net::SocketAddr> = self.outbound_queue.keys().copied().collect();
870 for addr in addrs {
871 self.flush_for_peer(addr).await?;
872 }
873 Ok(())
874 }
875}
876
877// ===========================================================================
878// Helpers
879// ===========================================================================
880
881/// Maps an OS I/O failure to a structured [`Error::Io`], preserving the
882/// `std::io::ErrorKind` (bind failures are then distinguishable from
883/// mid-connection resets, etc.) and the call site that failed. `std::io::Error`
884/// itself is not `Clone`/`Eq` (this crate's [`Error`] derives both), so only
885/// its `kind()` is kept — see the `S4` release-audit finding.
886fn io_err(context: &'static str, e: std::io::Error) -> Error {
887 Error::Io {
888 kind: e.kind(),
889 context,
890 }
891}
892
893/// Mixes a [`std::net::SocketAddr`] into a `u64` for use as `derive_cookie`'s
894/// `peer_key` input (§4.3.1.1: the cookie is "crafted based on host,
895/// port..."). Not a spec-defined algorithm — any stable, well-distributed
896/// mix of the peer's address is sufficient here.
897fn addr_to_u64(addr: &std::net::SocketAddr) -> u64 {
898 let mut hasher = std::collections::hash_map::DefaultHasher::new();
899 addr.hash(&mut hasher);
900 hasher.finish()
901}
902
903/// The current UNIX time, bucketed to 1-minute accuracy — the `time_bucket`
904/// input `derive_cookie` expects (§4.3.1.1: "...and current time with 1
905/// minute accuracy").
906fn unix_time_bucket() -> u32 {
907 let secs = std::time::SystemTime::now()
908 .duration_since(std::time::UNIX_EPOCH)
909 .unwrap_or_default()
910 .as_secs();
911 (secs / 60) as u32
912}
913
914/// A per-process/per-listener random `u64`, used as `derive_cookie`'s
915/// `secret` input. Sourced from `std::collections::hash_map::RandomState`
916/// (the standard library's own OS-seeded randomness, already used internally
917/// for `HashMap` DoS resistance) rather than pulling in a `rand` dependency
918/// for one seed value.
919fn random_u64() -> u64 {
920 std::collections::hash_map::RandomState::new()
921 .build_hasher()
922 .finish()
923}
924
925async fn resolve_one<A: tokio::net::ToSocketAddrs>(addr: A) -> Result<std::net::SocketAddr> {
926 let mut addrs = tokio::net::lookup_host(addr)
927 .await
928 .map_err(|e| io_err("resolve", e))?;
929 addrs.next().ok_or(Error::InvalidField {
930 what: "resolve",
931 reason: "no addrs",
932 })
933}
934
935/// Extract the peer's `initial_seq_number` from a handshake control packet's
936/// bytes. This seeds ARQ and TSBPD sequence tracking for the entire
937/// connection, so "the bytes didn't parse as a Handshake" must be a distinct,
938/// caller-visible outcome from "the peer's ISN is genuinely 0" — the two are
939/// otherwise indistinguishable to a caller that only sees a `u32`. Returns
940/// [`Error::InvalidField`] rather than defaulting to `0` on any parse
941/// failure.
942fn require_peer_isn(bytes: &[u8]) -> Result<u32> {
943 match SrtPacket::parse(bytes) {
944 Ok(SrtPacket::Control(ControlPacket::Handshake(hp))) => Ok(hp.initial_seq_number),
945 _ => Err(Error::InvalidField {
946 what: "peer isn",
947 reason: "handshake reached Connected but its final packet did not re-parse as a \
948 Handshake control packet; refusing to seed ARQ/TSBPD with a fabricated ISN",
949 }),
950 }
951}
952
953#[cfg(test)]
954mod isn_tests {
955 use super::*;
956 use crate::packet::{
957 EncryptionField, HandshakeExtensionFlags, HandshakeExtensions, HandshakePacket,
958 HandshakeType,
959 };
960
961 fn handshake_bytes(initial_seq_number: u32) -> Vec<u8> {
962 let hp = HandshakePacket {
963 timestamp: 0,
964 dest_socket_id: 0,
965 version: 5,
966 encryption_field: EncryptionField::NoEncryption,
967 extension_field: HandshakeExtensionFlags(0),
968 initial_seq_number,
969 mtu: 1500,
970 max_flow_window_size: 8192,
971 handshake_type: HandshakeType::Conclusion,
972 srt_socket_id: 42,
973 syn_cookie: 0,
974 peer_ip: [0; 4],
975 extensions: HandshakeExtensions(&[]),
976 };
977 crate::handshake_sm::build_bytes(hp).expect("build handshake bytes")
978 }
979
980 #[test]
981 fn require_peer_isn_extracts_nonzero_isn() {
982 let bytes = handshake_bytes(0xABCD_1234);
983 assert_eq!(require_peer_isn(&bytes).unwrap(), 0xABCD_1234);
984 }
985
986 #[test]
987 fn require_peer_isn_distinguishes_genuine_zero_from_parse_failure() {
988 // A genuine ISN of 0 is a valid, well-formed handshake and must
989 // succeed as Ok(0) — not be conflated with "couldn't parse".
990 let bytes = handshake_bytes(0);
991 assert_eq!(require_peer_isn(&bytes).unwrap(), 0);
992
993 // Bytes that don't parse as a Handshake control packet at all
994 // (too short to even carry the fixed SRT header) must error, never
995 // silently produce a plausible-looking 0.
996 let err = require_peer_isn(&[0u8; 4]).unwrap_err();
997 assert!(matches!(
998 err,
999 Error::InvalidField {
1000 what: "peer isn",
1001 ..
1002 }
1003 ));
1004 }
1005
1006 #[test]
1007 fn require_peer_isn_rejects_non_handshake_control_packet() {
1008 // A well-formed control packet of the WRONG type (Keep-Alive, not
1009 // Handshake) must also error rather than default to 0.
1010 let ka = ControlPacket::KeepAlive(KeepAlivePacket {
1011 timestamp: 0,
1012 dest_socket_id: 7,
1013 });
1014 let mut buf = alloc::vec![0u8; ka.serialized_len()];
1015 ka.serialize_into(&mut buf).expect("serialize keepalive");
1016 let err = require_peer_isn(&buf).unwrap_err();
1017 assert!(matches!(
1018 err,
1019 Error::InvalidField {
1020 what: "peer isn",
1021 ..
1022 }
1023 ));
1024 }
1025}