Skip to main content

rtc_interceptor/flexfec/
bit_array.rs

1//! The 128-bit packet mask that says which media packets a repair packet covers.
2
3/// A 128-bit mask, indexed from the **most** significant bit.
4///
5/// Bit 0 is the most significant bit, matching how FlexFEC packet masks are laid out on the wire:
6/// the first media packet after the base sequence number is the leftmost bit. Upstream keeps this
7/// as a `Lo`/`Hi` pair of `u64`s; a single `u128` is the same bits with the seam removed.
8#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
9pub struct BitArray {
10    bits: u128,
11}
12
13/// Bit indices at or above this do not exist in a 128-bit mask.
14const WIDTH: u32 = 128;
15
16impl BitArray {
17    /// An empty mask.
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    /// Set the bit at `index`, counting from the most significant.
23    ///
24    /// Out-of-range indices are ignored rather than panicking: coverage is computed from packet
25    /// counts that are already bounded, so a stray index means a caller bug, not a packet the
26    /// mask should silently mis-cover.
27    pub fn set_bit(&mut self, index: u32) {
28        if index < WIDTH {
29            self.bits |= 1u128 << (WIDTH - 1 - index);
30        }
31    }
32
33    /// Whether the bit at `index` is set.
34    pub fn bit(&self, index: u32) -> bool {
35        index < WIDTH && (self.bits >> (WIDTH - 1 - index)) & 1 == 1
36    }
37
38    /// Clear every bit.
39    pub fn reset(&mut self) {
40        self.bits = 0;
41    }
42
43    /// Whether no bits are set.
44    pub fn is_empty(&self) -> bool {
45        self.bits == 0
46    }
47
48    /// The 15-bit mask covering media packets 0..=14 — the one always present on the wire.
49    pub fn mask1(&self) -> u16 {
50        (self.bits >> (WIDTH - 15)) as u16
51    }
52
53    /// The 31-bit mask covering media packets 15..=45, present when the first k-bit is clear.
54    pub fn mask2(&self) -> u32 {
55        ((self.bits >> (WIDTH - 46)) & 0x7FFF_FFFF) as u32
56    }
57
58    /// The 64-bit mask covering media packets 46..=109, as RFC 8627 lays it out.
59    pub fn mask3(&self) -> u64 {
60        (self.bits >> (WIDTH - 110)) as u64
61    }
62
63    /// The draft-03 variant of [`mask3`](Self::mask3): 63 bits rather than 64.
64    ///
65    /// Draft-03 spends one more bit on the k-flag than the published RFC does, so the third mask
66    /// is one bit narrower and the whole field shifts down by one. This is the single most
67    /// consequential difference between the two formats at the bit level, and the reason a
68    /// draft-03 round trip proves nothing about RFC 8627 conformance.
69    pub fn mask3_draft03(&self) -> u64 {
70        self.mask3() >> 1
71    }
72}
73
74#[cfg(test)]
75mod tests {
76    use super::*;
77
78    fn mask_of(set_bits: &[u32]) -> BitArray {
79        let mut mask = BitArray::new();
80        for &bit in set_bits {
81            mask.set_bit(bit);
82        }
83        mask
84    }
85
86    #[test]
87    fn bits_are_indexed_from_the_most_significant() {
88        let mask = mask_of(&[0]);
89        assert!(mask.bit(0), "bit 0 is the leftmost");
90        assert!(!mask.bit(1));
91        assert_eq!(
92            0x4000,
93            mask.mask1(),
94            "and lands at the top of the 15-bit mask"
95        );
96    }
97
98    #[test]
99    fn setting_and_reading_round_trips_across_the_whole_width() {
100        let mut mask = BitArray::new();
101        for index in 0..WIDTH {
102            assert!(!mask.bit(index));
103            mask.set_bit(index);
104            assert!(mask.bit(index), "bit {index}");
105        }
106        for index in 0..WIDTH {
107            assert!(mask.bit(index), "bit {index} still set");
108        }
109    }
110
111    #[test]
112    fn an_out_of_range_index_is_ignored() {
113        let mut mask = BitArray::new();
114        mask.set_bit(WIDTH);
115        mask.set_bit(u32::MAX);
116        assert!(mask.is_empty(), "no bit was set, and nothing panicked");
117        assert!(!mask.bit(WIDTH));
118    }
119
120    #[test]
121    fn reset_clears_everything() {
122        let mut mask = mask_of(&[0, 64, 127]);
123        assert!(!mask.is_empty());
124        mask.reset();
125        assert!(mask.is_empty());
126        assert_eq!(0, mask.mask1());
127        assert_eq!(0, mask.mask3());
128    }
129
130    /// Vectors from `pion/interceptor`'s `flexfec_coverage_test.go`.
131    ///
132    /// These are the bit-exact part of the format: which media packet each mask bit stands for,
133    /// and where the three masks sit relative to one another. Taken from an independent
134    /// implementation rather than derived here, so agreeing with them is evidence.
135    #[test]
136    fn mask_extraction_matches_upstream_vectors() {
137        struct Case {
138            name: &'static str,
139            set_bits: &'static [u32],
140            mask1: u16,
141            mask2: u32,
142            mask3: u64,
143            mask3_draft03: u64,
144        }
145
146        let cases = [
147            Case {
148                name: "empty",
149                set_bits: &[],
150                mask1: 0,
151                mask2: 0,
152                mask3: 0,
153                mask3_draft03: 0,
154            },
155            Case {
156                name: "one bit in each mask",
157                set_bits: &[5, 20, 50],
158                mask1: 0x200,
159                mask2: 0x2000000,
160                mask3: 0x800000000000000,
161                mask3_draft03: 0x400000000000000,
162            },
163            Case {
164                name: "several bits in each mask",
165                set_bits: &[0, 7, 14, 15, 30, 45, 46, 80, 108, 109],
166                mask1: 0x4081,
167                mask2: 0x40008001,
168                mask3: 0x8000000020000003,
169                mask3_draft03: 0x4000000010000001,
170            },
171            Case {
172                name: "the boundaries of each mask",
173                set_bits: &[0, 14, 15, 45, 46, 108, 109],
174                mask1: 0x4001,
175                mask2: 0x40000001,
176                mask3: 0x8000000000000003,
177                mask3_draft03: 0x4000000000000001,
178            },
179        ];
180
181        for case in cases {
182            let mask = mask_of(case.set_bits);
183            assert_eq!(case.mask1, mask.mask1(), "mask1, {}", case.name);
184            assert_eq!(case.mask2, mask.mask2(), "mask2, {}", case.name);
185            assert_eq!(case.mask3, mask.mask3(), "mask3, {}", case.name);
186            assert_eq!(
187                case.mask3_draft03,
188                mask.mask3_draft03(),
189                "draft-03 mask3, {}",
190                case.name
191            );
192        }
193    }
194
195    /// The three masks partition media packets 0..=109 without overlapping, which is what makes
196    /// "which mask is a packet in" a question with one answer.
197    #[test]
198    fn the_three_masks_partition_the_covered_range() {
199        for index in 0..15 {
200            let mask = mask_of(&[index]);
201            assert_ne!(0, mask.mask1(), "bit {index} belongs to mask1");
202            assert_eq!(0, mask.mask2());
203            assert_eq!(0, mask.mask3());
204        }
205        for index in 15..46 {
206            let mask = mask_of(&[index]);
207            assert_eq!(0, mask.mask1(), "bit {index} is not in mask1");
208            assert_ne!(0, mask.mask2(), "bit {index} belongs to mask2");
209            assert_eq!(0, mask.mask3());
210        }
211        for index in 46..110 {
212            let mask = mask_of(&[index]);
213            assert_eq!(0, mask.mask1());
214            assert_eq!(0, mask.mask2(), "bit {index} is not in mask2");
215            assert_ne!(0, mask.mask3(), "bit {index} belongs to mask3");
216        }
217    }
218
219    /// Bit 109 is the last packet the RFC's mask3 can describe, and the one draft-03's narrower
220    /// mask3 drops.
221    #[test]
222    fn draft03_loses_the_last_bit_the_rfc_mask_carries() {
223        let mask = mask_of(&[109]);
224        assert_eq!(1, mask.mask3(), "the RFC mask carries it");
225        assert_eq!(
226            0,
227            mask.mask3_draft03(),
228            "draft-03's mask is one bit narrower, so it falls off the end"
229        );
230    }
231}