Opus: validate the header in Decoder::from_head (#425)

This commit is contained in:
MarekSpodBiedry
2026-10-13 17:33:29 +02:00
parent 457406a7a5
commit 3ed12de918
2 changed files with 44 additions and 0 deletions
+17
View File
@@ -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))
+27
View File
@@ -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<Decoder> {
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<u8> {
}
v
}
#[cfg(test)]
mod tests {
use super::*;
fn head(channels: usize, streams: usize, coupled: usize, mapping: Vec<u8>) -> 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());
}
}