test: constrain the AC-3/E-AC-3/DTS header decode and the boxes it emits

All 59 measured survivors in mp4/audio.rs: 50 killed, 9 proven
equivalent, none left unaddressed. No production change — every
extraction reads correct against ETSI TS 102 366 (5.3.2, 5.4.2, Annex
E.1.3, F.4, F.6.1) and TS 102 114 5.3.1.

It was a fixture gap, not a code defect, and a specific one: the
existing fixtures gave several fields the SAME value (fscod=0, bsmod=0,
lfeon=1, acmod=7) and asserted only derived channel counts. Nothing
asserted the emitted dac3/dec3/ddts payload BYTES at all, so the
packer's shifts and masks were entirely unconstrained. A wrong mask
there does not crash — it writes a box declaring the wrong channel
configuration, and a player believes it.

Several kills needed fixtures designed to discriminate rather than
merely exercise:

  the reduced-rate branch needed fscod == 3, which no test in the file
  reached — and `== -> !=` survived on an fscod=0 fixture only because
  the reduced table happens to return 48000 there too

  the DTS LFF mask needed a value where XOR and AND differ in MEANING:
  on the 5.1 fixtures LFF flips 1 -> 2 and BOTH codes mean "LFE present"

  the acmod mix-level skips needed three different acmods, because `^`
  is a no-op unless acmod == 4 exactly

  the ddts bitrate needed 44100/512, which does not divide evenly — at
  48000 the rounding is invisible and `/ -> %` on den/2 survives

The 9 equivalents are one pattern: OR-ing a shifted high part with a
masked low part on disjoint bit lanes, where the mask is on the same
line as the OR. Since cargo-mutants applies one mutation at a time, no
single mutant can break both. Each was applied and observed green.

FILED, not fixed: reserved sample-rate codes are silently guessed as
48 kHz (audio.rs:133, :20/:153) while a reserved DTS AMODE is refused
with a comment explaining why. Same silent-wrong-metadata class,
opposite answer. Refusing would make such a stream unmuxable, which is a
product call.
This commit is contained in:
Matthew Jackson
2026-07-30 21:51:26 -07:00
parent 2efe1425d6
commit 42591c77fc
+398
View File
@@ -917,6 +917,404 @@ mod tests {
assert_eq!(c.amode, 15);
}
// ── (E-)AC-3 bit-field extraction ────────────────────────────────────────
//
// The fixtures below give every BSI field a DIFFERENT value, which the
// channel-count fixtures above deliberately do not: `ac3_frame_5_1` has
// fscod=0, bsmod=0 and lfeon=1, so a mask that drops bits, a shift in the
// wrong direction, or a field read from its neighbour's lane all still
// produce 48 kHz / 6 channels. Field values here are pairwise distinct and
// are read back out of the EMITTED dac3/dec3 payload, so the assertions
// pin the arithmetic in ETSI TS 102 366 §5.3 / Annex E, not just a derived
// channel count.
/// dac3 payload (3 bytes) out of an emitted AC3SpecificBox.
fn dac3_payload(b: &[u8]) -> [u8; 3] {
assert_eq!(&b[4..8], b"dac3");
[b[8], b[9], b[10]]
}
/// dec3 payload (5 bytes) out of an emitted EC3SpecificBox.
fn dec3_payload(b: &[u8]) -> [u8; 5] {
assert_eq!(&b[4..8], b"dec3");
[b[8], b[9], b[10], b[11], b[12]]
}
/// AC-3 syncframe with every BSI field distinct: fscod=1 (44.1 kHz),
/// frmsizecod=37 (bit_rate_code 18), bsid=8, bsmod=5, acmod=6 (2/2),
/// lfeon=1 → 4.1 = 5 channels.
///
/// byte4 = fscod(2)=01 | frmsizecod(6)=100101 → 0x65
/// byte5 = bsid(5)=01000 | bsmod(3)=101 → 0x45
/// byte6 = acmod(3)=110 | surmixlev(2)=00 | lfeon=1 | pad 00 → 0xC4
/// acmod 6 has surround but no centre, so per §5.4.2 cmixlev is ABSENT and
/// surmixlev is present: lfeon lands at bit 5 of byte 6.
fn ac3_frame_distinct() -> Vec<u8> {
vec![0x0B, 0x77, 0x00, 0x00, 0x65, 0x45, 0xC4, 0x00]
}
#[test]
fn ac3_bsi_fields_are_extracted_from_their_own_lanes() {
let c = parse_dolby(&ac3_frame_distinct()).expect("parsed");
// fscod is byte4 bits 7-6: shifting the other way yields 0 → 48 kHz.
assert_eq!(c.fscod, 1, "fscod = byte4 >> 6");
assert_eq!(c.sample_rate, 44_100, "FSCOD_RATES[1]");
assert_eq!(c.bsid, 8);
// bsmod is byte5 bits 2-0, MASKED off bsid: `|` instead of `&` leaks
// bsid into it (0x45 | 0x07 = 71, not 5).
assert_eq!(c.bsmod, 5, "bsmod = byte5 & 0x07");
assert_eq!(c.acmod, 6, "2/2");
assert!(c.lfeon);
assert_eq!(c.channels, 5, "2/2 + LFE");
assert_eq!(c.bit_rate_code, 18, "frmsizecod 37 >> 1");
}
#[test]
fn dac3_payload_packs_each_field_at_its_spec_offset() {
// ETSI TS 102 366 Annex F.4, AC3SpecificBox = 24 bits:
// fscod(2) bsid(5) bsmod(3) acmod(3) lfeon(1) bit_rate_code(5) rsvd(5)
// 01 01000 101 110 1 10010 00000
// = 01010001 01110110 01000000 = 51 76 40
// Nothing else in this module asserts the dac3 BODY, so the packing
// shift/mask was free to change while every channel count still held.
let c = parse_dolby(&ac3_frame_distinct()).expect("parsed");
assert_eq!(dac3_payload(&dac3_box(&c)), [0x51, 0x76, 0x40]);
}
#[test]
fn dac3_truncates_an_oversized_field_instead_of_bleeding_into_its_neighbour() {
// Every field `parse_ac3` produces is already masked to its own width,
// so this states the packer's own contract: `push` writes `bits` bits
// and no more. A mask one bit too wide lets bsmod=8 set the bit that
// belongs to bsid, silently renumbering the bit stream identification.
let cfg = |bsmod, bit_rate_code| DolbyConfig {
fscod: 1,
bsid: 8,
bsmod,
acmod: 6,
lfeon: true,
bit_rate_code,
data_rate_kbps: 0,
sample_rate: 44_100,
channels: 5,
};
// bsmod 8 is one past the 3-bit field, bit_rate_code 32 one past the
// 5-bit field; both must be written as 0, changing no other field.
assert_eq!(
dac3_payload(&dac3_box(&cfg(8, 32))),
dac3_payload(&dac3_box(&cfg(0, 0))),
"an out-of-range field is truncated to its own lane"
);
}
#[test]
fn ac3_optional_mix_level_fields_are_skipped_exactly_when_present() {
// A/52 §5.4.2: cmixlev is present iff (acmod & 0x1) && acmod != 0x1
// (a centre channel, but not centre-only); surmixlev iff acmod & 0x4
// (surround present). Each wrongly skipped or wrongly kept 2-bit field
// moves `lfeon` two bits, so the LFE — and the channel count — is read
// out of the wrong lane. Both fixtures place lfeon=1 at its correct
// offset with 0 bits on either side of it.
//
// acmod=3 (3/0): centre, no surround → cmixlev present, surmixlev not.
// byte6 = 011 | cmixlev 01 | lfeon 1 | pad 00 = 0x6C
let c = parse_dolby(&[0x0B, 0x77, 0x00, 0x00, 0x00, 0x40, 0x6C, 0x00]).expect("parsed");
assert_eq!(c.acmod, 3);
assert!(c.lfeon, "acmod 3 has no surmixlev to skip");
assert_eq!(c.channels, 4, "3/0 + LFE");
// acmod=4 (2/1): surround, no centre → surmixlev present, cmixlev not.
// byte6 = 100 | surmixlev 01 | lfeon 1 | pad 00 = 0x8C
let c = parse_dolby(&[0x0B, 0x77, 0x00, 0x00, 0x00, 0x40, 0x8C, 0x00]).expect("parsed");
assert_eq!(c.acmod, 4);
assert!(c.lfeon, "acmod 4's surmixlev must be skipped");
assert_eq!(c.channels, 4, "2/1 + LFE");
// acmod=6 (2/2): surround, no centre — same rule, LFE still found.
let c = parse_dolby(&ac3_frame_distinct()).expect("parsed");
assert!(c.lfeon);
}
#[test]
fn dolby_syncword_needs_both_bytes() {
// 0x0B alone is not a syncframe. Matching on either byte anchors the
// parse two bytes early, and every BSI field is then read from the
// wrong offset — bsid 0 instead of 8, i.e. an entirely different
// stream description built from the same buffer.
let mut f = vec![0x0B, 0x00];
f.extend_from_slice(&ac3_frame_5_1());
let c = parse_dolby(&f).expect("parsed at the real syncword");
assert_eq!(c.bsid, 8, "sync is 0x0B77, not 0x0B or 0x77");
assert_eq!(c.acmod, 7);
assert_eq!(c.channels, 6);
}
#[test]
fn dolby_frame_shorter_than_the_bsi_is_refused_not_indexed() {
// The length guard is `< 6` because byte 5 (bsid) is read immediately
// after it. Five bytes must be refused; six must still parse.
assert!(
parse_dolby(&[0x0B, 0x77, 0x00, 0x00, 0x00]).is_none(),
"5 bytes cannot hold the bsid byte"
);
// Exactly 6 bytes is the smallest E-AC-3 header this can read, and it
// must NOT be refused: the guard is `<`, not `<=`.
let c = parse_dolby(&eac3_frame_fscod3()).expect("6 bytes is enough for the Annex-E BSI");
assert_eq!(c.bsid, 16);
}
#[test]
fn parse_eac3_refuses_a_frame_shorter_than_its_own_header() {
// `parse_dolby` guards this today, but `parse_eac3` reads f[5] and so
// owns the same precondition independently; if the caller's guard ever
// moves, this one must still refuse rather than panic.
assert!(parse_eac3(&[0x0B, 0x77, 0x00, 0x00, 0x00]).is_none());
assert!(parse_eac3(&eac3_frame_fscod3()).is_some(), "6 bytes parses");
}
/// Annex-E syncframe with every field distinct: frmsiz=0x123 (292 words
/// → 584 bytes), fscod=1 (44.1 kHz), numblkscod=0 (1 block → 256 samples),
/// acmod=5 (3/1), lfeon=1 → 5 channels, bsid=16.
///
/// byte2 = strmtyp 00 | substreamid 000 | frmsiz hi 001 → 0x01
/// byte3 = frmsiz lo 0x23
/// byte4 = fscod 01 | numblkscod 00 | acmod 101 | lfeon 1 → 0x4B
/// byte5 = bsid 10000 | dialnorm hi 000 → 0x80
fn eac3_frame_distinct() -> Vec<u8> {
vec![0x0B, 0x77, 0x01, 0x23, 0x4B, 0x80, 0x00]
}
/// Annex-E syncframe with fscod=3 — the reduced-sample-rate path, which no
/// other fixture reaches. fscod2=2 → 16 kHz, 6 blocks; acmod=2, lfeon=0.
/// byte4 = fscod 11 | fscod2 10 | acmod 010 | lfeon 0 = 0xE4.
/// Exactly 6 bytes: also the minimum-length Annex-E header.
fn eac3_frame_fscod3() -> Vec<u8> {
vec![0x0B, 0x77, 0x00, 0x0F, 0xE4, 0x80]
}
#[test]
fn eac3_bsi_fields_are_extracted_from_their_own_lanes() {
let c = parse_dolby(&eac3_frame_distinct()).expect("parsed");
assert_eq!(c.bsid, 16);
assert_eq!(c.fscod, 1, "byte4 bits 7-6");
assert_eq!(c.sample_rate, 44_100);
assert_eq!(c.acmod, 5, "3/1: byte4 bits 3-1");
assert!(c.lfeon, "byte4 bit 0");
assert_eq!(c.channels, 5, "3/1 + LFE");
// data_rate exercises frmsiz (11 bits split across bytes 2-3) and
// numblkscod (byte4 bits 5-4) together: 584 B over 256 samples at
// 44.1 kHz = 584·8·44100/256/1000 = 804 kbit/s. Dropping frmsiz's high
// 3 bits, or reading numblkscod as 3 blocks instead of 1, moves it.
assert_eq!(c.data_rate_kbps, 804);
}
#[test]
fn dec3_payload_packs_each_field_at_its_spec_offset() {
// ETSI TS 102 366 Annex F.6.1, EC3SpecificBox, one independent
// substream = 40 bits:
// data_rate(13)=804 num_ind_sub(3)=0 | fscod(2)=1 bsid(5)=16
// reserved(1) asvc(1) bsmod(3)=0 acmod(3)=5 lfeon(1)=1
// reserved(3) num_dep_sub(4)=0 reserved(1)
// 0001100100100 000 01 10000 0 0 000 101 1 000 0000 0
// = 00011001 00100000 01100000 00001011 00000000
let c = parse_dolby(&eac3_frame_distinct()).expect("parsed");
assert_eq!(dec3_payload(&dec3_box(&c)), [0x19, 0x20, 0x60, 0x0B, 0x00]);
}
#[test]
fn eac3_fscod3_selects_the_reduced_rate_table_by_fscod2() {
// ETSI TS 102 366 Annex E.1.3.4: when fscod == 3 the two bits that are
// numblkscod otherwise become fscod2, indexing a DIFFERENT rate table
// (and the frame is always 6 blocks). fscod2=2 → 16 kHz. Read as
// numblkscod, or with the wrong mask, this indexes out of the 4-entry
// reduced table or lands on 24 kHz.
let c = parse_dolby(&eac3_frame_fscod3()).expect("parsed");
assert_eq!(c.fscod, 3);
assert_eq!(c.sample_rate, 16_000, "EAC3_REDUCED_RATES[fscod2=2]");
assert_eq!(c.acmod, 2);
assert!(!c.lfeon);
assert_eq!(c.channels, 2);
// 6 blocks (1536 samples), 32 bytes → 32·8·16000/1536/1000 = 2 kbit/s.
assert_eq!(c.data_rate_kbps, 2, "fscod3 frames are always 6 blocks");
}
// ── DTS core header + ddts arithmetic ────────────────────────────────────
/// The 56-bit packed tail of an emitted `ddts` box: 8-byte box header, then
/// DTSSamplingFrequency(4) + maxBitrate(4) + avgBitrate(4) +
/// pcmSampleDepth(1) = 13 bytes.
fn ddts_tail(b: &[u8]) -> [u8; 7] {
assert_eq!(&b[4..8], b"ddts");
let t = &b[8 + 13..];
[t[0], t[1], t[2], t[3], t[4], t[5], t[6]]
}
/// maxBitrate / avgBitrate read back out of an emitted `ddts` box.
fn ddts_bitrates(b: &[u8]) -> (u32, u32) {
assert_eq!(&b[4..8], b"ddts");
(
u32::from_be_bytes([b[12], b[13], b[14], b[15]]),
u32::from_be_bytes([b[16], b[17], b[18], b[19]]),
)
}
#[test]
fn dts_core_sync_needs_all_four_bytes() {
// 0x7FFE8001 is a 32-bit sync. Any partial match — the leading 0x7F, or
// the trailing 0x8001 — anchoring the parse means every field after it
// is read from the wrong offset: AMODE 4 (2 channels) instead of 9
// (5.1) out of the very same buffer.
let mut f = vec![0x7F, 0x00, 0x80, 0x01];
f.extend_from_slice(&[
0x7F, 0xFE, 0x80, 0x01, 0x00, 0x3C, 0x05, 0xF2, 0x77, 0x00, 0x02, 0x00,
]);
let c = parse_dts(&f).expect("parsed at the real core sync");
assert_eq!(c.amode, 9, "sync is 0x7FFE8001, not a prefix of it");
assert_eq!(c.channels, 6);
assert_eq!(c.sample_rate, 48_000);
assert_eq!(c.core_size, 96);
}
#[test]
fn dts_frame_shorter_than_the_core_header_is_refused_not_indexed() {
// The guard is `< 11` because byte 10 (LFF) is read after it. Ten bytes
// must be refused; eleven — the exact header length — must still parse.
let short = vec![0x7F, 0xFE, 0x80, 0x01, 0x01, 0x3F, 0x00, 0x02, 0x74, 0x00];
assert_eq!(short.len(), 10);
assert!(parse_dts(&short).is_none(), "10 bytes cannot hold LFF");
let exact = vec![
0x7F, 0xFE, 0x80, 0x01, 0x01, 0x3F, 0x00, 0x02, 0x74, 0x00, 0x0A,
];
assert_eq!(exact.len(), 11);
let c = parse_dts(&exact).expect("11 bytes is the full core header");
assert!(c.lfe, "LFF is the last field the guard covers");
}
/// A DTS core whose split fields all have their high parts SET, so a lost
/// high bit is visible: NBLKS bit 6 (byte 4 bit 0) and FSIZE bits 13-12
/// (byte 5 bits 1-0). NBLKS=79 → 2560 samples; FSIZE=0x3000 → core 12289.
/// AMODE=9, SFREQ=13 (48 kHz), LFF=1 out of byte 10 = 0x0A.
fn dts_frame_high_bits_set() -> Vec<u8> {
vec![
0x7F, 0xFE, 0x80, 0x01, 0x01, 0x3F, 0x00, 0x02, 0x74, 0x00, 0x0A, 0x00,
]
}
#[test]
fn dts_split_fields_keep_their_high_bits() {
// NBLKS is 7 bits straddling bytes 4-5 and FSIZE is 14 bits straddling
// bytes 5-7 (ETSI TS 102 114 §5.3.1). Shifting the high part the wrong
// way drops it silently: 79 blocks becomes 15, and a 12289-byte core
// becomes 1 — both of which then propagate into the ddts bitrate.
let c = parse_dts(&dts_frame_high_bits_set()).expect("parsed");
assert_eq!(c.frame_samples, 2560, "NBLKS 79 → (79+1)·32");
assert_eq!(c.core_size, 12_289, "FSIZE 0x3000 + 1");
assert_eq!(c.amode, 9);
assert_eq!(c.sample_rate, 48_000);
}
#[test]
fn dts_lff_is_masked_out_of_its_neighbours_not_xored() {
// LFF is 2 bits at byte 10 bits 2-1; byte 10 = 0x0A puts a 1 in bit 3
// as well, so `>> 1` yields 0b101 and only a MASK isolates LFF = 1
// (LFE present). XOR-ing instead yields 0b110 = 2 — also a legal
// "LFE present" code, which is why the 5.1 fixtures could not see it —
// and here flips it to 0b110 & no-mask = 6, i.e. LFE absent, dropping
// a channel from both the count and the ddts speaker mask.
let c = parse_dts(&dts_frame_high_bits_set()).expect("parsed");
assert!(c.lfe, "LFF = 1");
assert_eq!(c.channels, 6, "AMODE 9 (5 ch) + LFE");
assert_eq!(c.channel_layout & 0x0008, 0x0008, "LFE bit in the mask");
}
#[test]
fn dts_ext_sync_in_the_last_core_byte_is_not_an_extension() {
// The EXSS search starts at exactly core_size = FSIZE + 1. Here FSIZE=9
// (core_size 10) and the ext-sync pattern begins at byte 9 — the LAST
// byte of the core, one before the search window. Off-by-one either
// way (start at FSIZE, or at FSIZE-1) tests that byte and mislabels a
// plain DTS core as DTS-HD, emitting `dtsh` for a stream with no
// extension substream at all.
let f = vec![
0x7F, 0xFE, 0x80, 0x01, 0x00, 0x00, 0x00, 0x92, 0x74, 0x64, 0x58, 0x20, 0x25,
];
let c = parse_dts(&f).expect("parsed");
assert_eq!(c.core_size, 10, "FSIZE 9 + 1");
assert!(
!c.has_extension,
"the pattern ends at the core's last byte, inside it"
);
let e = dolby_sample_entry(Codec::Dts, &f).expect("entry built");
assert_eq!(&e[4..8], b"dtsc");
}
#[test]
fn ddts_bitrate_is_the_rounded_core_rate() {
// core_size · 8 · sample_rate / frame_samples, rounded to nearest.
// 8 · 8 · 44100 = 2 822 400 bits per 512 samples → 5512.5 bit/s, which
// must round UP to 5513: the +den/2 is what keeps the declared rate
// from being systematically low. Every operator in that expression
// moves the answer, and nothing else in this module reads maxBitrate
// or avgBitrate out of the emitted box.
let c = DtsConfig {
sample_rate: 44_100,
channels: 6,
amode: 9,
lfe: true,
core_size: 8,
frame_samples: 512,
has_extension: false,
channel_layout: dts_channel_layout(9, true),
};
let (max, avg) = ddts_bitrates(&ddts_box(&c));
assert_eq!(max, 5513, "2822400/512 = 5512.5 → 5513");
assert_eq!(avg, 5513, "both fields carry the same computed rate");
}
#[test]
fn ddts_bitrate_is_zero_rather_than_a_division_by_zero() {
// frame_samples is the divisor. A core header that yields 0 samples per
// frame must produce a 0 bitrate, not divide by it.
let c = DtsConfig {
sample_rate: 48_000,
channels: 6,
amode: 9,
lfe: true,
core_size: 96,
frame_samples: 0,
has_extension: false,
channel_layout: dts_channel_layout(9, true),
};
assert_eq!(ddts_bitrates(&ddts_box(&c)), (0, 0));
}
#[test]
fn ddts_frame_duration_code_covers_every_band() {
// FrameDuration (the tail's top 2 bits) codes the core frame length:
// 0 = 512 samples, 1 = 1024, 2 = 2048, 3 = 4096. NBLKS is 7 bits, so
// (NBLKS+1)·32 reaches 4096 and every band is live. Declaring the wrong
// band tells a decoder the frame is a different duration than it is.
let dur = |frame_samples| {
let c = DtsConfig {
sample_rate: 48_000,
channels: 6,
amode: 9,
lfe: true,
core_size: 96,
frame_samples,
has_extension: false,
channel_layout: dts_channel_layout(9, true),
};
ddts_tail(&ddts_box(&c))[0] >> 6
};
assert_eq!(dur(512), 0, "512 samples");
assert_eq!(dur(1024), 1, "1024 samples");
assert_eq!(dur(2048), 2, "2048 samples");
assert_eq!(dur(4096), 3, "4096 samples");
// Band boundaries, so a band cannot quietly absorb its neighbour.
assert_eq!(dur(513), 1);
assert_eq!(dur(1025), 2);
assert_eq!(dur(2049), 3);
}
#[test]
fn sample_entry_samplerate_does_not_overflow_at_96k() {
// 96 kHz > 65535: the 16.16 integer part must saturate, not wrap to garbage.