diff --git a/crates/opus/src/header.rs b/crates/opus/src/header.rs index 2fe0c752..90423aec 100644 --- a/crates/opus/src/header.rs +++ b/crates/opus/src/header.rs @@ -123,6 +123,23 @@ impl OpusHead { Ok(OpusHead { channels, pre_skip, input_sample_rate, output_gain, mapping_family: family, streams, coupled_streams: coupled, mapping }) } + /// Checks the invariants the decoder indexes by, for headers built by hand rather than parsed. + pub fn validate(&self) -> Result<()> { + if self.channels == 0 { + return Err(Error::InvalidHeader("zero channels")); + } + if self.streams == 0 || self.coupled_streams > self.streams || self.streams + self.coupled_streams > 255 { + return Err(Error::InvalidHeader("bad stream counts")); + } + if self.mapping.len() != self.channels { + return Err(Error::InvalidHeader("channel mapping length differs from channel count")); + } + if self.mapping.iter().any(|&m| m != 255 && m as usize >= self.streams + self.coupled_streams) { + return Err(Error::InvalidHeader("channel mapping out of range")); + } + Ok(()) + } + /// Linear output gain factor. pub fn gain_linear(&self) -> f32 { 10f32.powf(self.output_gain as f32 / (20.0 * 256.0)) diff --git a/crates/opus/src/multistream.rs b/crates/opus/src/multistream.rs index a41ba10c..e3042ca0 100644 --- a/crates/opus/src/multistream.rs +++ b/crates/opus/src/multistream.rs @@ -32,6 +32,7 @@ impl Decoder { /// Creates a decoder for a parsed header at `sample_rate` (8/12/16/24/48 kHz). pub fn from_head(head: OpusHead, sample_rate: u32) -> Result { + head.validate()?; let mut streams = Vec::with_capacity(head.streams); for s in 0..head.streams { let ch = if s < head.coupled_streams { 2 } else { 1 }; @@ -169,3 +170,29 @@ fn reframe(p: &Packet) -> Vec { } v } + +#[cfg(test)] +mod tests { + use super::*; + + fn head(channels: usize, streams: usize, coupled: usize, mapping: Vec) -> OpusHead { + OpusHead { channels, pre_skip: 0, input_sample_rate: 48000, output_gain: 0, mapping_family: 1, streams, coupled_streams: coupled, mapping } + } + + #[test] + fn from_head_rejects_inconsistent_headers() { + assert!(Decoder::from_head(head(1, 0, 0, vec![0]), 48000).is_err(), "zero streams"); + assert!(Decoder::from_head(head(2, 1, 0, vec![0]), 48000).is_err(), "short mapping"); + assert!(Decoder::from_head(head(1, 1, 0, vec![1]), 48000).is_err(), "mapping index out of range"); + assert!(Decoder::from_head(head(1, 1, 2, vec![0]), 48000).is_err(), "more coupled than streams"); + assert!(Decoder::from_head(head(0, 1, 0, vec![]), 48000).is_err(), "zero channels"); + } + + #[test] + fn from_head_accepts_valid_headers() { + assert!(Decoder::from_head(head(1, 1, 0, vec![0]), 48000).is_ok()); + assert!(Decoder::from_head(head(2, 1, 1, vec![0, 1]), 48000).is_ok()); + assert!(Decoder::from_head(head(3, 2, 1, vec![0, 1, 2]), 48000).is_ok()); + assert!(Decoder::from_head(head(2, 1, 0, vec![0, 255]), 48000).is_ok()); + } +}