Skip to main content

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}