Skip to main content

sapflow_srt/
sender.rs

1//! Bounded live sending window (SRT draft sections 4.8 and 5.1).
2//!
3//! This first sender uses an explicit bandwidth ceiling. It fails the
4//! session when retained media reaches its age budget; negotiated late
5//! dropping and automatic bitrate adaptation are subsequent milestones.
6
7use std::collections::VecDeque;
8
9use crate::crypto::Encryptor;
10use crate::packet::{Ack, Control, ControlPacket, DataPacket, Position};
11use crate::rtt::Rtt;
12use crate::seq::Seq;
13
14/// Limits cover pending AND unacknowledged payloads. Payload ownership is
15/// transferred into the queue, retained once, and copied once per wire send.
16#[derive(Debug, Clone)]
17pub struct SendConfig {
18    /// Most packets held at once, queued or awaiting acknowledgement; default
19    /// 4096.
20    pub max_packets: usize,
21    /// Sum of retained payload allocation capacities (metadata is packet-bounded).
22    pub max_bytes: usize,
23    /// Maximum age since enqueue, in microseconds. Expiry closes the session.
24    pub max_age_us: u64,
25    /// One complete live message per datagram (1316 fits seven TS packets).
26    pub max_payload: usize,
27    /// Bytes/second including the SRT header, shared by originals and retries.
28    pub bandwidth: u64,
29    /// Per-packet retransmissions before ending the session.
30    pub max_retransmits: u32,
31}
32
33impl Default for SendConfig {
34    fn default() -> Self {
35        Self {
36            max_packets: 4096,
37            max_bytes: 4 * 1024 * 1024,
38            max_age_us: 3_000_000,
39            max_payload: 1316,
40            bandwidth: 1_250_000,
41            max_retransmits: 32,
42        }
43    }
44}
45
46impl SendConfig {
47    pub(crate) fn valid(&self) -> bool {
48        (1..=65_536).contains(&self.max_packets)
49            && (1..=64 * 1024 * 1024).contains(&self.max_bytes)
50            && (1..=60_000_000).contains(&self.max_age_us)
51            && (1..=1456).contains(&self.max_payload)
52            && (1..=1_000_000_000).contains(&self.bandwidth)
53            && (1..=1024).contains(&self.max_retransmits)
54    }
55}
56
57/// Why a message was not accepted for sending.
58#[derive(Debug, Clone, Copy, PartialEq, Eq)]
59pub enum SendError {
60    /// The session is not open: not yet connected, or already closed or
61    /// failed.
62    NotConnected,
63    /// The message has no bytes.
64    Empty,
65    /// The message is longer than [`SendConfig::max_payload`].
66    PayloadTooLarge,
67    /// The packet or byte budget is used up (backpressure).
68    QueueFull,
69}
70
71impl std::fmt::Display for SendError {
72    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
73        f.write_str(match self {
74            Self::NotConnected => "SRT sender is not connected",
75            Self::Empty => "empty live message",
76            Self::PayloadTooLarge => "live message exceeds negotiated payload size",
77            Self::QueueFull => "SRT packet or byte budget exhausted",
78        })
79    }
80}
81impl std::error::Error for SendError {}
82
83/// Counters for the sender. Byte counts are payload bytes, without the SRT
84/// header, unless noted.
85#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
86pub struct SendStats {
87    /// Messages accepted into the send queue.
88    pub accepted_packets: u64,
89    /// Payload bytes in accepted messages.
90    pub accepted_bytes: u64,
91    /// Data packets put on the wire, originals and retransmissions.
92    pub sent_packets: u64,
93    /// Payload bytes in those packets.
94    pub sent_bytes: u64,
95    /// Of those, retransmissions.
96    pub retransmitted_packets: u64,
97    /// Payload bytes in retransmissions.
98    pub retransmitted_bytes: u64,
99    /// Transport acknowledgements, not proof of playback or application receipt.
100    pub acknowledged_packets: u64,
101    /// Payload bytes acknowledged.
102    pub acknowledged_bytes: u64,
103    /// Messages refused with a [`SendError`].
104    pub rejected_packets: u64,
105    /// Packets still queued or unacknowledged when the session closed.
106    pub abandoned_packets: u64,
107    /// Payload bytes in those packets.
108    pub abandoned_bytes: u64,
109    /// Packets queued or awaiting acknowledgement now.
110    pub buffered_packets: usize,
111    /// Allocation capacity held by those payloads, in bytes; what
112    /// [`SendConfig::max_bytes`] limits.
113    pub buffered_bytes: usize,
114    /// Highest `buffered_packets` reached.
115    pub peak_buffered_packets: usize,
116    /// Highest `buffered_bytes` reached.
117    pub peak_buffered_bytes: usize,
118    /// Valid NAK packets received.
119    pub nak_packets: u64,
120    /// Control packets ignored as invalid or unsupported.
121    pub invalid_controls: u64,
122    /// Packets queued for retransmission by the retransmission timeout rather
123    /// than by a NAK.
124    pub timeout_retransmissions: u64,
125    /// Smoothed round-trip time, in microseconds.
126    pub rtt_us: u64,
127    /// New data packets encrypted; retransmissions reuse the ciphertext and
128    /// are not counted.
129    pub encrypted_packets: u64,
130    /// Key material requests sent, repeats included.
131    pub km_requests: u64,
132    /// Key material responses that matched the outstanding request.
133    pub km_responses: u64,
134}
135
136#[derive(Debug, Clone, Copy, PartialEq, Eq)]
137pub(crate) enum Failure {
138    MediaExpired,
139    Crypto,
140    RetryLimit,
141    PeerClosed,
142    PeerError(u32),
143}
144
145struct Queued {
146    payload: Vec<u8>,
147    born: i64,
148}
149
150struct Flight {
151    data: Queued,
152    seq: Seq,
153    message: u32,
154    last_sent: i64,
155    retries: u32,
156    requested: bool,
157    key: u8,
158}
159
160pub(crate) struct Sender {
161    config: SendConfig,
162    pending: VecDeque<Queued>,
163    flight: VecDeque<Flight>,
164    retries: VecDeque<Seq>,
165    next_seq: Seq,
166    next_message: u32,
167    acked: Seq,
168    peer_socket: u32,
169    flow_window: usize,
170    peer_space: usize,
171    next_send: i64,
172    last_send: i64,
173    last_receive: i64,
174    next_rto: Option<i64>,
175    rto_count: u32,
176    rtt: Rtt,
177    peer_variance: i64,
178    ack_ack: Option<u32>,
179    stats: SendStats,
180    failure: Option<Failure>,
181    crypto: Option<Encryptor>,
182    key_counts: [usize; 2],
183}
184
185impl Sender {
186    pub(crate) fn new(
187        config: SendConfig,
188        now: i64,
189        peer_socket: u32,
190        initial: Seq,
191        window: usize,
192    ) -> Self {
193        Self {
194            config,
195            pending: VecDeque::new(),
196            flight: VecDeque::new(),
197            retries: VecDeque::new(),
198            next_seq: initial,
199            next_message: 1,
200            acked: initial,
201            peer_socket,
202            flow_window: window,
203            peer_space: window,
204            next_send: now,
205            last_send: now,
206            last_receive: now,
207            next_rto: None,
208            rto_count: 0,
209            rtt: Rtt::new(),
210            peer_variance: 50_000,
211            ack_ack: None,
212            stats: SendStats::default(),
213            failure: None,
214            crypto: None,
215            key_counts: [0; 2],
216        }
217    }
218
219    pub(crate) fn set_crypto(&mut self, crypto: Option<Encryptor>) {
220        self.crypto = crypto;
221    }
222
223    pub(crate) fn stats(&self) -> SendStats {
224        let mut s = self.stats;
225        s.rtt_us = self.rtt.rtt() as u64;
226        s
227    }
228
229    pub(crate) fn peer_socket(&self) -> u32 {
230        self.peer_socket
231    }
232    pub(crate) fn last_receive(&self) -> i64 {
233        self.last_receive
234    }
235    pub(crate) fn failure(&self) -> Option<Failure> {
236        self.failure
237    }
238    pub(crate) fn max_payload(&self) -> usize {
239        self.config.max_payload
240    }
241
242    pub(crate) fn try_enqueue(
243        &mut self,
244        now: i64,
245        payload: Vec<u8>,
246    ) -> Result<(), (SendError, Vec<u8>)> {
247        let error = if self.failure.is_some() {
248            Some(SendError::NotConnected)
249        } else if payload.is_empty() {
250            Some(SendError::Empty)
251        } else if payload.len() > self.config.max_payload {
252            Some(SendError::PayloadTooLarge)
253        } else if self.stats.buffered_packets >= self.config.max_packets
254            || payload.capacity()
255                > self
256                    .config
257                    .max_bytes
258                    .saturating_sub(self.stats.buffered_bytes)
259        {
260            Some(SendError::QueueFull)
261        } else {
262            None
263        };
264        if let Some(error) = error {
265            self.stats.rejected_packets += 1;
266            return Err((error, payload));
267        }
268        self.stats.accepted_packets += 1;
269        self.stats.accepted_bytes += payload.len() as u64;
270        self.stats.buffered_packets += 1;
271        self.stats.buffered_bytes += payload.capacity();
272        self.stats.peak_buffered_packets = self
273            .stats
274            .peak_buffered_packets
275            .max(self.stats.buffered_packets);
276        self.stats.peak_buffered_bytes = self
277            .stats
278            .peak_buffered_bytes
279            .max(self.stats.buffered_bytes);
280        self.pending.push_back(Queued { payload, born: now });
281        Ok(())
282    }
283
284    pub(crate) fn clear(&mut self) {
285        self.stats.abandoned_packets += self.stats.buffered_packets as u64;
286        self.stats.abandoned_bytes += self
287            .pending
288            .iter()
289            .map(|p| p.payload.len() as u64)
290            .chain(self.flight.iter().map(|f| f.data.payload.len() as u64))
291            .sum::<u64>();
292        self.crypto = None;
293        self.key_counts = [0; 2];
294        self.stats.buffered_packets = 0;
295        self.stats.buffered_bytes = 0;
296        self.pending = VecDeque::new();
297        self.flight = VecDeque::new();
298        self.retries = VecDeque::new();
299        self.ack_ack = None;
300        self.next_rto = None;
301    }
302
303    fn rto(&self) -> i64 {
304        self.rtt
305            .retry()
306            .max(self.rtt.rtt() + 4 * self.peer_variance)
307            + 20_000
308    }
309
310    pub(crate) fn on_control(&mut self, now: i64, control: &Control) {
311        if self.failure.is_some() {
312            return;
313        }
314        let valid = match control {
315            Control::Ack(ack) => self.on_ack(now, ack),
316            Control::Nak(ranges) => self.on_nak(now, ranges),
317            Control::Other {
318                control_type: 0x7fff,
319                subtype: 4,
320                info: 0,
321                cif,
322            } => {
323                let confirmed = self
324                    .crypto
325                    .as_mut()
326                    .is_some_and(|crypto| crypto.confirm(cif));
327                if confirmed {
328                    self.stats.km_responses += 1;
329                }
330                confirmed
331            }
332            Control::KeepAlive => true,
333            Control::Shutdown => {
334                self.failure = Some(Failure::PeerClosed);
335                true
336            }
337            Control::PeerError { code } => {
338                self.failure = Some(Failure::PeerError(*code));
339                true
340            }
341            // File-mode congestion notifications are not LiveCC signals.
342            _ => false,
343        };
344        if valid {
345            self.last_receive = now;
346        } else {
347            self.stats.invalid_controls += 1;
348        }
349    }
350
351    fn on_ack(&mut self, now: i64, ack: &Ack) -> bool {
352        let advance = self.acked.distance_to(ack.ack);
353        if advance < 0 || ack.ack.distance_to(self.next_seq) < 0 {
354            return false;
355        }
356        if ack.number != 0 && ack.stats.is_some() {
357            self.ack_ack = Some(ack.number);
358        }
359        if let Some(stats) = ack.stats {
360            self.peer_space = (stats.buffer as usize).min(self.flow_window);
361            self.rtt.sample(i64::from(stats.rtt));
362            self.peer_variance = i64::from(stats.rtt_var).min(60_000_000);
363        }
364        while self
365            .flight
366            .front()
367            .is_some_and(|f| f.seq.distance_to(ack.ack) > 0)
368        {
369            if let Some(f) = self.flight.pop_front() {
370                if let Some(count) = f
371                    .key
372                    .checked_sub(1)
373                    .and_then(|k| self.key_counts.get_mut(k as usize))
374                {
375                    *count = count.saturating_sub(1);
376                }
377                self.stats.acknowledged_packets += 1;
378                self.stats.acknowledged_bytes += f.data.payload.len() as u64;
379                self.stats.buffered_packets -= 1;
380                self.stats.buffered_bytes -= f.data.payload.capacity();
381            }
382        }
383        self.acked = ack.ack;
384        self.retries.retain(|seq| seq.distance_to(ack.ack) <= 0);
385        if advance > 0 {
386            self.rto_count = 0;
387            self.next_rto = (!self.flight.is_empty()).then(|| now.saturating_add(self.rto()));
388        }
389        true
390    }
391
392    fn on_nak(&mut self, now: i64, ranges: &[(Seq, Seq)]) -> bool {
393        // Validate every range before changing state. Never enumerate an
394        // attacker-selected range: intersect it with our retained window.
395        if ranges.is_empty()
396            || ranges
397                .iter()
398                .any(|(a, b)| a.distance_to(*b) < 0 || b.distance_to(self.next_seq) <= 0)
399        {
400            return false;
401        }
402        self.stats.nak_packets += 1;
403        let Some(first) = self.flight.front().map(|f| f.seq) else {
404            return true;
405        };
406        let mut work_left = self.config.max_packets;
407        for (a, b) in ranges {
408            let start = first.distance_to(*a).max(0) as usize;
409            let end = first.distance_to(*b);
410            if end < 0 {
411                continue;
412            }
413            let end = (end as usize).min(self.flight.len().saturating_sub(1));
414            for index in start..=end {
415                if work_left == 0 {
416                    return true;
417                }
418                work_left -= 1;
419                if let Some(f) = self.flight.get_mut(index) {
420                    // An initial NAK is urgent; repeated requests must not
421                    // race the retransmission already on its way.
422                    if !f.requested
423                        && (f.retries == 0
424                            || now.saturating_sub(f.last_sent) >= self.rtt.rtt().max(1000))
425                    {
426                        f.requested = true;
427                        self.retries.push_back(f.seq);
428                    }
429                }
430            }
431        }
432        true
433    }
434
435    pub(crate) fn on_timer(&mut self, now: i64) {
436        if self.failure.is_some() {
437            return;
438        }
439        if self
440            .oldest()
441            .is_some_and(|born| now.saturating_sub(born) >= self.config.max_age_us as i64)
442        {
443            self.failure = Some(Failure::MediaExpired);
444            return;
445        }
446        if self.next_rto.is_some_and(|deadline| now >= deadline) {
447            self.rto_count = self.rto_count.saturating_add(1);
448            for f in &mut self.flight {
449                if !f.requested {
450                    f.requested = true;
451                    self.retries.push_back(f.seq);
452                    self.stats.timeout_retransmissions += 1;
453                }
454            }
455            self.next_rto =
456                Some(now.saturating_add(self.rto().saturating_mul(i64::from(self.rto_count + 1))));
457        }
458    }
459
460    fn oldest(&self) -> Option<i64> {
461        self.flight
462            .front()
463            .map(|f| f.data.born)
464            .into_iter()
465            .chain(self.pending.front().map(|p| p.born))
466            .min()
467    }
468
469    pub(crate) fn next_timer(&self) -> i64 {
470        let mut next = self.last_send.saturating_add(1_000_000);
471        if self.ack_ack.is_some() {
472            next = next.min(self.last_receive);
473        }
474        if !self.retries.is_empty()
475            || (!self.pending.is_empty()
476                && self.flight.len() < self.flow_window
477                && self.peer_space > 0)
478        {
479            next = next.min(self.next_send);
480        }
481        if let Some(born) = self.oldest() {
482            next = next.min(born.saturating_add(self.config.max_age_us as i64));
483        }
484        if let Some(at) = self.crypto.as_ref().and_then(Encryptor::deadline) {
485            next = next.min(at);
486        }
487        if let Some(rto) = self.next_rto {
488            next = next.min(rto);
489        }
490        next
491    }
492
493    pub(crate) fn poll_transmit(&mut self, now: i64) -> Option<Vec<u8>> {
494        self.on_timer(now);
495        if self.failure.is_some() {
496            return None;
497        }
498        if let Some(km) = self.crypto.as_mut().and_then(|c| c.poll_km(now)) {
499            self.stats.km_requests += 1;
500            return Some(self.control(
501                now,
502                Control::Other {
503                    control_type: 0x7fff,
504                    subtype: 3,
505                    info: 0,
506                    cif: km,
507                },
508            ));
509        }
510        if let Some(number) = self.ack_ack.take() {
511            return Some(self.control(now, Control::AckAck { number }));
512        }
513        if now >= self.next_send {
514            while let Some(seq) = self.retries.pop_front() {
515                let first = self.flight.front()?.seq;
516                let index = first.distance_to(seq);
517                if index < 0 {
518                    continue;
519                }
520                let Some(f) = self.flight.get_mut(index as usize) else {
521                    continue;
522                };
523                f.requested = false;
524                if f.retries >= self.config.max_retransmits {
525                    self.failure = Some(Failure::RetryLimit);
526                    return None;
527                }
528                f.retries += 1;
529                f.last_sent = now;
530                let len = f.data.payload.len();
531                let out = encode_data(f, self.peer_socket, true);
532                self.stats.retransmitted_packets += 1;
533                self.stats.retransmitted_bytes += len as u64;
534                self.sent(now, len);
535                return Some(out);
536            }
537            if self.flight.len() < self.flow_window
538                && self.peer_space > 0
539                && let Some(mut data) = self.pending.pop_front()
540            {
541                let retained = self
542                    .key_counts
543                    .iter()
544                    .enumerate()
545                    .fold(0, |bits, (i, count)| {
546                        bits | if *count > 0 { 1 << i } else { 0 }
547                    });
548                let key = if let Some(crypto) = &mut self.crypto {
549                    match crypto.encrypt(self.next_seq, &mut data.payload, retained) {
550                        Ok(key) => key,
551                        Err(_) => {
552                            self.pending.push_front(data);
553                            self.failure = Some(Failure::Crypto);
554                            return None;
555                        }
556                    }
557                } else {
558                    0
559                };
560                if let Some(count) = key
561                    .checked_sub(1)
562                    .and_then(|k| self.key_counts.get_mut(k as usize))
563                {
564                    *count += 1;
565                }
566                if key != 0 {
567                    self.stats.encrypted_packets += 1;
568                }
569                let f = Flight {
570                    key,
571                    data,
572                    seq: self.next_seq,
573                    message: self.next_message,
574                    last_sent: now,
575                    retries: 0,
576                    requested: false,
577                };
578                self.next_seq = self.next_seq.next();
579                self.next_message = (self.next_message + 1) & 0x03ff_ffff;
580                if self.next_message == 0 {
581                    self.next_message = 1;
582                }
583                let len = f.data.payload.len();
584                let out = encode_data(&f, self.peer_socket, false);
585                self.flight.push_back(f);
586                self.peer_space -= 1;
587                if self.next_rto.is_none() {
588                    self.next_rto = Some(now.saturating_add(self.rto()));
589                }
590                self.sent(now, len);
591                return Some(out);
592            }
593        }
594        if now >= self.last_send.saturating_add(1_000_000) {
595            return Some(self.control(now, Control::KeepAlive));
596        }
597        None
598    }
599
600    fn sent(&mut self, now: i64, len: usize) {
601        self.stats.sent_packets += 1;
602        self.stats.sent_bytes += len as u64;
603        self.last_send = now;
604        // Charge actual packet size, conservatively including the SRT
605        // header. No catch-up burst after a suspended or delayed executor.
606        let period = ((len as u64 + 16) * 1_000_000).div_ceil(self.config.bandwidth);
607        self.next_send = now.saturating_add(period.max(1) as i64);
608    }
609
610    fn control(&mut self, now: i64, body: Control) -> Vec<u8> {
611        let mut bytes = Vec::with_capacity(16);
612        ControlPacket::new(now as u32, self.peer_socket, body).encode(&mut bytes);
613        self.last_send = now;
614        bytes
615    }
616}
617
618fn encode_data(f: &Flight, dest: u32, retransmitted: bool) -> Vec<u8> {
619    let mut bytes = Vec::with_capacity(16 + f.data.payload.len());
620    DataPacket {
621        seq: f.seq,
622        position: Position::Only,
623        in_order: false,
624        key: f.key,
625        retransmitted,
626        message: f.message,
627        timestamp: f.data.born as u32,
628        dest,
629        payload: &f.data.payload,
630    }
631    .encode(&mut bytes);
632    bytes
633}