use super::bit_array::BitArray;
pub const MAX_MEDIA_PACKETS: u32 = 110;
pub const MAX_FEC_PACKETS: u32 = MAX_MEDIA_PACKETS;
#[derive(Debug, Clone)]
pub struct ProtectionCoverage {
masks: Vec<BitArray>,
num_fec_packets: u32,
num_media_packets: u32,
}
impl ProtectionCoverage {
pub fn new(num_media_packets: u32, num_fec_packets: u32) -> Option<Self> {
if num_media_packets == 0 || num_media_packets > MAX_MEDIA_PACKETS {
return None;
}
let mut coverage = Self {
masks: vec![BitArray::new(); MAX_FEC_PACKETS as usize],
num_fec_packets: 0,
num_media_packets: 0,
};
coverage.update(num_media_packets, num_fec_packets);
Some(coverage)
}
pub fn update(&mut self, num_media_packets: u32, num_fec_packets: u32) {
if num_media_packets == 0 || num_media_packets > MAX_MEDIA_PACKETS {
return;
}
if num_media_packets == self.num_media_packets && num_fec_packets == self.num_fec_packets {
return;
}
self.num_media_packets = num_media_packets;
self.num_fec_packets = num_fec_packets.min(MAX_FEC_PACKETS);
for mask in &mut self.masks {
mask.reset();
}
for fec_index in 0..self.num_fec_packets {
let mut media_index = fec_index;
while media_index < num_media_packets {
self.masks[fec_index as usize].set_bit(media_index);
media_index += self.num_fec_packets;
}
}
}
pub fn num_fec_packets(&self) -> u32 {
self.num_fec_packets
}
pub fn num_media_packets(&self) -> u32 {
self.num_media_packets
}
pub fn mask(&self, fec_index: u32) -> Option<&BitArray> {
(fec_index < self.num_fec_packets).then(|| &self.masks[fec_index as usize])
}
pub fn covered_by(&self, fec_index: u32) -> Vec<u32> {
let Some(mask) = self.mask(fec_index) else {
return Vec::new();
};
(0..self.num_media_packets)
.filter(|&media_index| mask.bit(media_index))
.collect()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn grid(coverage: &ProtectionCoverage) -> Vec<Vec<u32>> {
(0..coverage.num_fec_packets())
.map(|fec_index| coverage.covered_by(fec_index))
.collect()
}
#[test]
fn one_repair_packet_covers_everything() {
let coverage = ProtectionCoverage::new(5, 1).expect("valid shape");
assert_eq!(vec![vec![0, 1, 2, 3, 4]], grid(&coverage));
}
#[test]
fn repair_packets_interleave_rather_than_taking_contiguous_blocks() {
let coverage = ProtectionCoverage::new(6, 2).expect("valid shape");
assert_eq!(vec![vec![0, 2, 4], vec![1, 3, 5]], grid(&coverage));
let coverage = ProtectionCoverage::new(7, 3).expect("valid shape");
assert_eq!(vec![vec![0, 3, 6], vec![1, 4], vec![2, 5]], grid(&coverage));
}
#[test]
fn every_media_packet_is_covered_exactly_once() {
for num_media in 1..=20u32 {
for num_fec in 1..=5u32 {
let coverage = ProtectionCoverage::new(num_media, num_fec).expect("valid shape");
let mut covered: Vec<u32> = grid(&coverage).into_iter().flatten().collect();
covered.sort_unstable();
assert_eq!(
(0..num_media).collect::<Vec<_>>(),
covered,
"{num_media} media packets across {num_fec} repair packets"
);
}
}
}
#[test]
fn surplus_repair_packets_cover_nothing() {
let coverage = ProtectionCoverage::new(2, 4).expect("valid shape");
assert_eq!(vec![vec![0], vec![1], vec![], vec![]], grid(&coverage));
}
#[test]
fn an_impossible_shape_is_rejected() {
assert!(
ProtectionCoverage::new(0, 1).is_none(),
"nothing to protect"
);
assert!(
ProtectionCoverage::new(MAX_MEDIA_PACKETS + 1, 1).is_none(),
"beyond what the packet masks can describe"
);
assert!(ProtectionCoverage::new(MAX_MEDIA_PACKETS, 1).is_some());
}
#[test]
fn the_full_range_reaches_the_last_mask() {
let coverage = ProtectionCoverage::new(MAX_MEDIA_PACKETS, 1).expect("valid shape");
let mask = coverage.mask(0).expect("one repair packet");
assert!(mask.bit(0), "the first media packet");
assert!(mask.bit(MAX_MEDIA_PACKETS - 1), "and the 110th");
assert_ne!(0, mask.mask3(), "which only the third mask can describe");
}
#[test]
fn updating_to_the_same_shape_changes_nothing() {
let mut coverage = ProtectionCoverage::new(6, 2).expect("valid shape");
let before = grid(&coverage);
coverage.update(6, 2);
assert_eq!(before, grid(&coverage));
coverage.update(4, 2);
assert_eq!(
vec![vec![0, 2], vec![1, 3]],
grid(&coverage),
"a different shape does recompute"
);
}
#[test]
fn an_impossible_update_leaves_the_previous_coverage_intact() {
let mut coverage = ProtectionCoverage::new(6, 2).expect("valid shape");
let before = grid(&coverage);
coverage.update(0, 2);
coverage.update(MAX_MEDIA_PACKETS + 1, 2);
assert_eq!(before, grid(&coverage));
}
#[test]
fn asking_about_a_repair_packet_that_does_not_exist_yields_nothing() {
let coverage = ProtectionCoverage::new(5, 2).expect("valid shape");
assert!(coverage.mask(2).is_none());
assert!(coverage.covered_by(2).is_empty());
assert!(coverage.covered_by(u32::MAX).is_empty());
}
}