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