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                                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}