Skip to main content

srt_runtime/packet/
nak.rs

1//! NAK (Negative Acknowledgement / Loss Report) control packet —
2//! `draft-sharabayko-srt-01` §3.2.5, Figure 14, and the loss-list coding of
3//! Appendix A.
4//!
5//! The CIF is a sequence of 31-bit sequence-number entries: a single lost
6//! packet (top bit clear), or a range `[a, b]` encoded as two consecutive
7//! entries — `a` with its top bit set, `b` with its top bit clear
8//! (Appendix A, Figures 21/22).
9//!
10//! [`NakPacket::raw_loss_list`] is kept as a borrowed byte slice (the same
11//! lazy-loop convention `dvb-si` uses for descriptor loops) rather than
12//! eagerly decoded into an owned list — walk it with
13//! [`NakPacket::entries`].
14
15use alloc::vec::Vec;
16
17use super::{Error, F_BIT, Result, SEQ_NUMBER_MASK, be32};
18
19/// One decoded entry of a NAK loss list (Appendix A). Data-carrying ADT — not
20/// a spec/field label, so it is exempt from the `name()`/`Display`
21/// convention (see `tests/label_coverage.rs`).
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde", derive(serde::Serialize))]
24pub enum LossListEntry {
25    /// A single lost packet sequence number (Figure 21).
26    Single(u32),
27    /// An inclusive range of lost packet sequence numbers `[first, last]`
28    /// (Figure 22).
29    Range(u32, u32),
30}
31
32/// NAK control packet (§3.2.5, Figure 14).
33#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
34#[cfg_attr(feature = "serde", derive(serde::Serialize))]
35pub struct NakPacket<'a> {
36    /// Timestamp (§3).
37    pub timestamp: u32,
38    /// Destination Socket ID (§3).
39    pub dest_socket_id: u32,
40    /// The raw loss-list CIF bytes. Walk with [`Self::entries`].
41    pub raw_loss_list: &'a [u8],
42}
43
44impl<'a> NakPacket<'a> {
45    pub(crate) fn parse_cif(timestamp: u32, dest_socket_id: u32, cif: &'a [u8]) -> Self {
46        NakPacket {
47            timestamp,
48            dest_socket_id,
49            raw_loss_list: cif,
50        }
51    }
52
53    pub(crate) fn cif_len(&self) -> usize {
54        self.raw_loss_list.len()
55    }
56
57    pub(crate) fn write_cif(&self, buf: &mut [u8]) {
58        buf.copy_from_slice(self.raw_loss_list);
59    }
60
61    /// Iterate the decoded loss-list entries (Appendix A). Yields an
62    /// [`Error`] (never panics) on a malformed entry, then stops.
63    pub fn entries(&self) -> LossListIter<'a> {
64        LossListIter {
65            rest: self.raw_loss_list,
66        }
67    }
68}
69
70/// Iterator over a NAK loss list's decoded entries. See [`NakPacket::entries`].
71#[derive(Debug, Clone)]
72pub struct LossListIter<'a> {
73    rest: &'a [u8],
74}
75
76impl Iterator for LossListIter<'_> {
77    type Item = Result<LossListEntry>;
78
79    fn next(&mut self) -> Option<Self::Item> {
80        if self.rest.is_empty() {
81            return None;
82        }
83        if self.rest.len() < 4 {
84            self.rest = &[];
85            return Some(Err(Error::InvalidLossList {
86                reason: "trailing bytes are not a whole 4-byte entry",
87            }));
88        }
89        let w0 = be32(self.rest, 0);
90        self.rest = &self.rest[4..];
91        if w0 & F_BIT != 0 {
92            let first = w0 & SEQ_NUMBER_MASK;
93            if self.rest.len() < 4 {
94                self.rest = &[];
95                return Some(Err(Error::InvalidLossList {
96                    reason: "range entry missing its end sequence number",
97                }));
98            }
99            let w1 = be32(self.rest, 0);
100            self.rest = &self.rest[4..];
101            if w1 & F_BIT != 0 {
102                return Some(Err(Error::InvalidLossList {
103                    reason: "range end entry had its top bit set",
104                }));
105            }
106            Some(Ok(LossListEntry::Range(first, w1 & SEQ_NUMBER_MASK)))
107        } else {
108            Some(Ok(LossListEntry::Single(w0 & SEQ_NUMBER_MASK)))
109        }
110    }
111}
112
113/// Build the raw loss-list bytes for a NAK CIF from decoded entries
114/// (Appendix A). Inverse of [`NakPacket::entries`].
115///
116/// # Errors
117/// [`Error::FieldTooWide`] if a sequence number does not fit in 31 bits.
118pub fn build_loss_list(entries: &[LossListEntry]) -> Result<Vec<u8>> {
119    let mut out = Vec::with_capacity(entries.len() * 4);
120    let check = |seq: u32| -> Result<()> {
121        if seq > SEQ_NUMBER_MASK {
122            return Err(Error::FieldTooWide {
123                what: "loss list sequence number",
124                value: u64::from(seq),
125                bits: 31,
126            });
127        }
128        Ok(())
129    };
130    for entry in entries {
131        match *entry {
132            LossListEntry::Single(seq) => {
133                check(seq)?;
134                out.extend_from_slice(&seq.to_be_bytes());
135            }
136            LossListEntry::Range(first, last) => {
137                check(first)?;
138                check(last)?;
139                out.extend_from_slice(&(first | F_BIT).to_be_bytes());
140                out.extend_from_slice(&last.to_be_bytes());
141            }
142        }
143    }
144    Ok(out)
145}
146
147#[cfg(test)]
148mod tests {
149    use super::super::control::ControlPacket;
150    use super::*;
151
152    #[test]
153    fn single_and_range_entries_round_trip_hand_computed_bytes() {
154        let entries = [
155            LossListEntry::Single(5),
156            LossListEntry::Range(10, 20),
157            LossListEntry::Single(30),
158        ];
159        let raw = build_loss_list(&entries).unwrap();
160        assert_eq!(raw.len(), 16);
161        assert_eq!(&raw[0..4], &5u32.to_be_bytes());
162        assert_eq!(&raw[4..8], &(10u32 | 0x8000_0000).to_be_bytes());
163        assert_eq!(&raw[8..12], &20u32.to_be_bytes());
164        assert_eq!(&raw[12..16], &30u32.to_be_bytes());
165
166        let pkt = ControlPacket::Nak(NakPacket {
167            timestamp: 1,
168            dest_socket_id: 2,
169            raw_loss_list: &raw,
170        });
171        let mut buf = alloc::vec![0u8; pkt.serialized_len()];
172        pkt.serialize_into(&mut buf).unwrap();
173        let parsed = ControlPacket::parse(&buf).unwrap();
174        assert_eq!(parsed, pkt);
175
176        if let ControlPacket::Nak(n) = parsed {
177            let decoded: Vec<LossListEntry> = n.entries().map(|e| e.unwrap()).collect();
178            assert_eq!(&decoded, &entries);
179        } else {
180            panic!("expected NAK");
181        }
182    }
183
184    #[test]
185    fn malformed_range_end_top_bit_errs_without_panic() {
186        // Range start (top bit set) followed by another range-start-looking
187        // word (top bit also set) — invalid per Appendix A.
188        let mut raw = Vec::new();
189        raw.extend_from_slice(&(1u32 | 0x8000_0000).to_be_bytes());
190        raw.extend_from_slice(&(2u32 | 0x8000_0000).to_be_bytes());
191        let n = NakPacket {
192            timestamp: 0,
193            dest_socket_id: 0,
194            raw_loss_list: &raw,
195        };
196        let mut it = n.entries();
197        assert!(matches!(
198            it.next(),
199            Some(Err(Error::InvalidLossList { .. }))
200        ));
201        assert!(it.next().is_none());
202    }
203
204    #[test]
205    fn overwide_sequence_number_errs() {
206        assert!(matches!(
207            build_loss_list(&[LossListEntry::Single(0x8000_0000)]),
208            Err(Error::FieldTooWide { .. })
209        ));
210    }
211}