Skip to main content

musefs_format/ogg/
mod.rs

1mod art_source;
2mod b64;
3mod crc;
4mod page;
5
6pub use art_source::{ArtSource, MapArtSource};
7
8pub use b64::{B64Window, b64_len, b64_len_checked, b64_window, encode_b64_slice};
9pub use page::{
10    PageHeader, parse_page, patch_page_header, patch_page_header_algebraic, verify_page_crc,
11};
12
13use crate::error::{FormatError, Result};
14use crate::probe::Extent;
15use crate::size;
16
17/// The codec carried inside an Ogg logical bitstream that we synthesize.
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum Codec {
20    Opus,
21    Vorbis,
22    OggFlac,
23}
24
25const METADATA_BLOCK_PICTURE_KEY: &[u8] = b"METADATA_BLOCK_PICTURE=";
26
27fn detect_codec(first_packet: &[u8]) -> Result<Codec> {
28    if first_packet.len() >= 8 && &first_packet[0..8] == b"OpusHead" {
29        Ok(Codec::Opus)
30    } else if first_packet.len() >= 7 && &first_packet[0..7] == b"\x01vorbis" {
31        Ok(Codec::Vorbis)
32    } else if first_packet.len() >= 5 && &first_packet[0..5] == b"\x7FFLAC" {
33        Ok(Codec::OggFlac)
34    } else {
35        Err(FormatError::Malformed)
36    }
37}
38
39/// For OggFLAC, packet 0 is `0x7F "FLAC" major minor count(2, BE) "fLaC" STREAMINFO`.
40/// The 16-bit big-endian count is the number of metadata-block packets that follow
41/// packet 0.
42fn oggflac_following_packets(first_packet: &[u8]) -> Result<usize> {
43    if first_packet.len() < 9 {
44        return Err(FormatError::Malformed);
45    }
46    Ok(u16::from_be_bytes([first_packet[7], first_packet[8]]) as usize)
47}
48
49/// The parsed Ogg header region: codec, serial, the reassembled header packets,
50/// the number of header pages, and where audio begins.
51#[derive(Debug, Clone, PartialEq, Eq)]
52pub struct OggHeader {
53    pub codec: Codec,
54    pub serial: u32,
55    pub packets: Vec<Vec<u8>>,
56    pub header_pages: u32,
57    pub audio_offset: u64,
58}
59
60/// Reject multiplexed/chained Ogg: within the header region every page must share
61/// the first page's serial and only the first page may carry BOS.
62fn validate_single_bitstream(data: &[u8], audio_offset: u64, serial: u32) -> Result<()> {
63    let mut pos = 0usize;
64    let mut first = true;
65    while (pos as u64) < audio_offset {
66        let h = crate::ogg::page::parse_page(data, pos)?;
67        if h.serial != serial {
68            return Err(FormatError::Malformed);
69        }
70        if !first && (h.header_type & crate::ogg::page::FLAG_BOS) != 0 {
71            return Err(FormatError::Malformed);
72        }
73        first = false;
74        pos += h.total_len();
75    }
76    Ok(())
77}
78
79/// Parse the header region from the front of a logical bitstream. `data` may be the
80/// whole file or just `[0, audio_offset)`; either way parsing stops once all header
81/// packets are reassembled.
82pub fn read_header(data: &[u8]) -> Result<OggHeader> {
83    let first_page = page::parse_page(data, 0)?;
84    let serial = first_page.serial;
85
86    // Reassemble the first packet to detect the codec and (for OggFLAC) the count.
87    let first = page::read_packets(data, 1)?;
88    let first_pkt = first.first().ok_or(FormatError::Malformed)?;
89    let codec = detect_codec(&first_pkt.data)?;
90
91    let want = match codec {
92        Codec::Opus => 2,
93        Codec::Vorbis => 3,
94        Codec::OggFlac => 1 + oggflac_following_packets(&first_pkt.data)?,
95    };
96
97    let pkts = page::read_packets(data, want)?;
98    if pkts.len() != want {
99        return Err(FormatError::Malformed);
100    }
101    let last = pkts.last().unwrap();
102    let audio_offset = last.end_offset as u64;
103    validate_single_bitstream(data, audio_offset, serial)?;
104    Ok(OggHeader {
105        codec,
106        serial,
107        packets: pkts.iter().map(|p| p.data.clone()).collect(),
108        header_pages: last.pages_through_end,
109        audio_offset,
110    })
111}
112
113/// Strip a codec's comment-packet prefix, returning the VorbisComment body slice.
114fn comment_body(codec: Codec, packet: &[u8]) -> Result<&[u8]> {
115    let prefix = match codec {
116        Codec::Opus => 8,    // "OpusTags"
117        Codec::Vorbis => 7,  // 0x03 "vorbis"
118        Codec::OggFlac => 4, // FLAC metadata block header (type + 24-bit length)
119    };
120    if packet.len() < prefix {
121        return Err(FormatError::Malformed);
122    }
123    Ok(&packet[prefix..])
124}
125
126/// The index of the comment packet within the reassembled header packets.
127fn comment_packet_index(header: &OggHeader) -> usize {
128    match header.codec {
129        Codec::Opus | Codec::Vorbis => 1,
130        // OggFLAC: packet 0 is the mapping header; the VORBIS_COMMENT block is
131        // whichever following packet has block type 4.
132        Codec::OggFlac => header
133            .packets
134            .iter()
135            .enumerate()
136            .skip(1)
137            .find(|(_, p)| !p.is_empty() && (p[0] & 0x7F) == 4)
138            .map_or(0, |(i, _)| i),
139    }
140}
141
142/// Read existing `(FIELD, value)` tags from a complete file. Empty if none.
143pub fn read_tags(data: &[u8]) -> Result<Vec<(String, String)>> {
144    let header = read_header(data)?;
145    let idx = comment_packet_index(&header);
146    if idx == 0 {
147        return Ok(Vec::new()); // no comment packet present
148    }
149    let body = comment_body(header.codec, &header.packets[idx])?;
150    let mut tags = crate::vorbiscomment::parse(body)?;
151    // Cover art rides in the comment as a base64 METADATA_BLOCK_PICTURE entry, but
152    // it has its own channel (read_pictures). Excluding it keeps read_tags
153    // text-only and prevents the art being stored — and re-synthesized — twice.
154    tags.retain(|(field, _)| !field.eq_ignore_ascii_case("METADATA_BLOCK_PICTURE"));
155    Ok(tags)
156}
157
158use crate::input::EmbeddedPicture;
159
160/// Extract embedded pictures from a complete file for scan-time ingestion.
161///
162/// Opus/Vorbis carry art as a base64 `METADATA_BLOCK_PICTURE` comment whose decoded
163/// bytes are a FLAC PICTURE block body; OggFLAC carries native PICTURE block
164/// packets (block type 6). Plan 1 only *reads* art (to seed the DB); synthesis does
165/// not yet re-embed it.
166pub fn read_pictures(data: &[u8]) -> Result<Vec<EmbeddedPicture>> {
167    use base64::Engine;
168    let header = read_header(data)?;
169    let mut out = Vec::new();
170    match header.codec {
171        Codec::Opus | Codec::Vorbis => {
172            let idx = comment_packet_index(&header);
173            if idx == 0 {
174                return Ok(out);
175            }
176            let body = comment_body(header.codec, &header.packets[idx])?;
177            for (field, value) in crate::vorbiscomment::parse(body)? {
178                if field.eq_ignore_ascii_case("METADATA_BLOCK_PICTURE") {
179                    let raw = base64::engine::general_purpose::STANDARD
180                        .decode(value.as_bytes())
181                        .map_err(|_| FormatError::Malformed)?;
182                    out.push(crate::flac::parse_picture_block(&raw)?);
183                }
184            }
185        }
186        Codec::OggFlac => {
187            for pkt in header.packets.iter().skip(1) {
188                // `pkt.len() >= 4` guards the `&pkt[4..]` slice: the packet length is
189                // attacker-controlled, so a 1-3 byte type-6 packet must not panic.
190                if pkt.len() >= 4 && (pkt[0] & 0x7F) == 6 {
191                    // Strip the 4-byte FLAC metadata block header.
192                    out.push(crate::flac::parse_picture_block(&pkt[4..])?);
193                }
194            }
195        }
196    }
197    Ok(out)
198}
199
200/// Audio bounds + codec from a complete file, for the scanner.
201#[derive(Debug, Clone, PartialEq, Eq)]
202pub struct OggScan {
203    pub codec: Codec,
204    pub audio_offset: u64,
205    pub audio_length: u64,
206}
207
208pub fn locate_audio(data: &[u8]) -> Result<OggScan> {
209    let header = read_header(data)?;
210    if header.audio_offset > data.len() as u64 {
211        return Err(FormatError::Malformed);
212    }
213    Ok(OggScan {
214        codec: header.codec,
215        audio_offset: header.audio_offset,
216        audio_length: data.len() as u64 - header.audio_offset,
217    })
218}
219
220/// The header region parsed from the front of the file (`[0, audio_offset)`), for
221/// synthesis. Identical to `read_header` but named to mirror `flac::read_metadata`.
222pub fn read_metadata(front: &[u8]) -> Result<OggHeader> {
223    read_header(front)
224}
225
226/// Bounded twin of [`read_metadata`]. OGG header packets (and all OGG embedded
227/// art) are front-anchored, so a prefix covering the header region is sufficient.
228/// `read_header` does not expose an exact byte need, so on a short/truncated
229/// prefix this geometrically grows the window (doubling, capped at `file_len`):
230/// header regions are tiny, so the first 1 MiB window almost always completes,
231/// and the cap guarantees the worst case equals reading the whole file.
232pub fn read_metadata_bounded(prefix: &[u8], file_len: u64) -> Result<Extent<OggHeader>> {
233    match read_header(prefix) {
234        Ok(header) => Ok(Extent::Complete(header)),
235        // `read_header` cannot distinguish a truncated front from genuine
236        // corruption, so we widen optimistically; a real error resurfaces via the
237        // `Err(e)` arm once `prefix` reaches `file_len` (and the caller's retry
238        // limit + full-read fallback bound the cost).
239        Err(_) if (prefix.len() as u64) < file_len => {
240            let grown = ((prefix.len() as u64).saturating_mul(2)).max(64 * 1024);
241            Ok(Extent::NeedMore {
242                up_to: grown.min(file_len),
243            })
244        }
245        Err(e) => Err(e),
246    }
247}
248
249use crate::input::TagInput;
250use crate::layout::{RegionLayout, Segment};
251
252pub fn synthesize_layout(
253    header: &OggHeader,
254    audio_offset: u64,
255    audio_length: u64,
256    tags: &[TagInput],
257    arts: &[OggArt],
258    src: &dyn ArtSource,
259) -> Result<RegionLayout> {
260    let arts: Vec<OggArt> = arts.to_vec();
261    let packet_chunks = build_packets_with_art(header, tags, &arts)?;
262    let mut segments: Vec<Segment> = Vec::new();
263    let mut seq = 0u32;
264    for (i, chunks) in packet_chunks.iter().enumerate() {
265        let (segs, used) =
266            crate::ogg::page::lace_chunks_to_segments(header.serial, seq, i == 0, chunks, src)?;
267        segments.extend(segs);
268        seq += used;
269    }
270    let seq_delta = i64::from(seq) - i64::from(header.header_pages);
271    segments.push(Segment::OggAudio {
272        offset: audio_offset,
273        len: audio_length,
274        seq_delta,
275    });
276    Ok(RegionLayout::validated(segments)?)
277}
278
279/// Build the FLAC PICTURE block *body prefix* (everything before the image data:
280/// type, mime, description, dimensions, depth, colors, data-length) for `art`,
281/// padding the description with spaces so the prefix length is a multiple of 3.
282/// This makes `base64(prefix ++ image) == base64(prefix) ++ base64(image)`, so the
283/// image's base64 is an independent substring that can be served incrementally.
284/// The declared data-length field is the true image length (`art.data_len`).
285///
286/// The actual byte layout is shared with the plain FLAC write path via
287/// [`crate::flac::picture_body_framing`]; only the description padding is unique here.
288fn picture_prefix(art: &crate::input::ArtInput) -> Result<Vec<u8>> {
289    // Unpadded prefix length = 4(type)+4(mimelen)+mime +4(desclen)+desc
290    //   +4(w)+4(h)+4(depth)+4(colors)+4(datalen) = 32 + mime + desc.
291    let base = 32 + art.mime.len() + art.description.len();
292    let pad = (3 - base % 3) % 3;
293    let description = format!("{}{}", art.description, " ".repeat(pad));
294    crate::flac::picture_body_framing(art, &description)
295}
296
297use crate::ogg::page::PayloadChunk;
298use base64::Engine;
299
300/// One image to embed: its metadata. Bytes are read from an `ArtSource` only to
301/// compute page CRCs at synthesis time; they are never retained in the layout.
302#[derive(Clone, Copy)]
303pub struct OggArt<'a> {
304    pub meta: &'a crate::input::ArtInput,
305}
306
307fn b64_encode(bytes: &[u8]) -> Vec<u8> {
308    base64::engine::general_purpose::STANDARD
309        .encode(bytes)
310        .into_bytes()
311}
312
313/// Build the regenerated header packets as chunk lists, embedding `arts`.
314/// Opus/Vorbis: art goes into the comment packet as `METADATA_BLOCK_PICTURE`
315/// comments (last). OggFLAC: each art is a native PICTURE block packet.
316fn build_packets_with_art(
317    header: &OggHeader,
318    tags: &[TagInput],
319    arts: &[OggArt],
320) -> Result<Vec<Vec<PayloadChunk>>> {
321    match header.codec {
322        Codec::Opus | Codec::Vorbis => {
323            // VorbisComment value length is a 32-bit field; guard against overflow
324            // for absurdly large images (cover art is far below this). The full
325            // value includes the key, base64 of the picture prefix, and base64 of
326            // the image; any one of these alone may fit in u32 but the sum may not.
327            for a in arts {
328                let prefix = picture_prefix(a.meta)?;
329                let b64_prefix_len =
330                    b64_len_checked(prefix.len() as u64).ok_or(FormatError::TooLarge)?;
331                let b64_image_len =
332                    b64_len_checked(a.meta.data_len.get()).ok_or(FormatError::TooLarge)?;
333                let value_len = size::checked_sum([
334                    METADATA_BLOCK_PICTURE_KEY.len() as u64,
335                    b64_prefix_len,
336                    b64_image_len,
337                ])?;
338                if value_len > u64::from(u32::MAX) {
339                    return Err(FormatError::TooLarge);
340                }
341            }
342            if header.codec == Codec::Opus {
343                Ok(vec![
344                    vec![PayloadChunk::Bytes(header.packets[0].clone())],
345                    comment_packet_chunks(b"OpusTags", tags, arts, false)?,
346                ])
347            } else {
348                Ok(vec![
349                    vec![PayloadChunk::Bytes(header.packets[0].clone())],
350                    comment_packet_chunks(b"\x03vorbis", tags, arts, true)?,
351                    vec![PayloadChunk::Bytes(header.packets[2].clone())],
352                ])
353            }
354        }
355        Codec::OggFlac => oggflac_packets_with_art(header, tags, arts),
356    }
357}
358
359/// Build a VorbisComment-style comment packet (Opus `OpusTags` / Vorbis
360/// `0x03vorbis`) as chunks: a leading `Bytes` chunk (magic + vendor + count + text
361/// comments + each art comment's framing and base64(prefix)), an `Art` chunk per
362/// image (base64 of the image), and — for Vorbis — a trailing framing-bit `Bytes`
363/// chunk.
364fn comment_packet_chunks(
365    magic: &[u8],
366    tags: &[TagInput],
367    arts: &[OggArt],
368    framing_bit: bool,
369) -> Result<Vec<PayloadChunk>> {
370    let text_body = crate::vorbiscomment::build(tags)?; // vendor + count(text) + text comments
371    let vendor_len = u32::from_le_bytes(text_body[0..4].try_into().unwrap()) as usize;
372    let count_pos = 4 + vendor_len;
373    let text_count = u32::from_le_bytes(text_body[count_pos..count_pos + 4].try_into().unwrap());
374    let mut leading = text_body.clone();
375    let new_count = text_count + u32::try_from(arts.len()).map_err(|_| FormatError::TooLarge)?;
376    leading[count_pos..count_pos + 4].copy_from_slice(&new_count.to_le_bytes());
377
378    let mut chunks: Vec<PayloadChunk> = Vec::new();
379    let mut head = magic.to_vec();
380    head.extend_from_slice(&leading);
381
382    for art in arts {
383        let prefix = picture_prefix(art.meta)?;
384        let b64_prefix = b64_encode(&prefix);
385        let b64_image_len =
386            b64_len_checked(art.meta.data_len.get()).ok_or(FormatError::TooLarge)?;
387        let value_len = size::checked_sum([
388            METADATA_BLOCK_PICTURE_KEY.len() as u64,
389            b64_prefix.len() as u64,
390            b64_image_len,
391        ])?;
392        head.extend_from_slice(
393            &u32::try_from(value_len)
394                .map_err(|_| FormatError::TooLarge)?
395                .to_le_bytes(),
396        );
397        head.extend_from_slice(METADATA_BLOCK_PICTURE_KEY);
398        head.extend_from_slice(&b64_prefix);
399        chunks.push(PayloadChunk::Bytes(std::mem::take(&mut head)));
400        chunks.push(PayloadChunk::Art {
401            art_id: art.meta.art_id,
402            base64: true,
403            art_total: art.meta.data_len.get(),
404        });
405    }
406    if framing_bit {
407        head.push(0x01);
408    }
409    if !head.is_empty() {
410        chunks.push(PayloadChunk::Bytes(head));
411    }
412    Ok(chunks)
413}
414
415/// OggFLAC header packets with art: the text comment packet (no art) plus one
416/// native PICTURE block packet per image. The last metadata-block packet carries
417/// the last-block flag, and packet 0's 16-bit following-packet count is recomputed.
418fn oggflac_packets_with_art(
419    header: &OggHeader,
420    tags: &[TagInput],
421    arts: &[OggArt],
422) -> Result<Vec<Vec<PayloadChunk>>> {
423    if header.packets.is_empty() {
424        return Err(FormatError::Malformed);
425    }
426    let mut structural: Vec<Vec<u8>> = Vec::new();
427    for pkt in header.packets.iter().skip(1) {
428        if !pkt.is_empty() && matches!(pkt[0] & 0x7F, 2 | 3 | 5) {
429            structural.push(pkt.clone());
430        }
431    }
432
433    let vc = crate::vorbiscomment::build(tags)?;
434    if vc.len() as u64 > crate::flac::MAX_BLOCK_BODY {
435        return Err(FormatError::TooLarge);
436    }
437    let mut comment = Vec::new();
438    crate::flac::push_block_header(&mut comment, 4, vc.len(), false)?;
439    comment.extend_from_slice(&vc);
440
441    let following_count = structural.len() + 1 + arts.len();
442    let count = u16::try_from(following_count).map_err(|_| FormatError::TooLarge)?;
443
444    let mut block_packets: Vec<Vec<PayloadChunk>> = Vec::new();
445    for s in &structural {
446        block_packets.push(vec![PayloadChunk::Bytes(s.clone())]);
447    }
448    block_packets.push(vec![PayloadChunk::Bytes(comment)]);
449    for art in arts {
450        let prefix = picture_prefix(art.meta)?;
451        let body_len = size::checked_add(prefix.len() as u64, art.meta.data_len.get())?;
452        if body_len > crate::flac::MAX_BLOCK_BODY {
453            return Err(FormatError::TooLarge);
454        }
455        let mut blk = Vec::new();
456        crate::flac::push_block_header(&mut blk, 6, crate::convert::usize_from(body_len), false)?;
457        blk.extend_from_slice(&prefix);
458        block_packets.push(vec![
459            PayloadChunk::Bytes(blk),
460            PayloadChunk::Art {
461                art_id: art.meta.art_id,
462                base64: false,
463                art_total: art.meta.data_len.get(),
464            },
465        ]);
466    }
467
468    let n = block_packets.len();
469    for (i, bp) in block_packets.iter_mut().enumerate() {
470        if let Some(PayloadChunk::Bytes(b)) = bp.first_mut() {
471            if i + 1 == n {
472                b[0] |= 0x80;
473            } else {
474                b[0] &= 0x7F;
475            }
476        }
477    }
478
479    let mut mapping = header.packets[0].clone();
480    if mapping.len() < 9 {
481        return Err(FormatError::Malformed);
482    }
483    mapping[7..9].copy_from_slice(&count.to_be_bytes());
484
485    let mut out = vec![vec![PayloadChunk::Bytes(mapping)]];
486    out.extend(block_packets);
487    Ok(out)
488}
489
490#[doc(hidden)]
491pub mod page_test_support {
492    pub use crate::ogg::page::{build_header as build_header_pub, lace_packet as lace_packet_pub};
493
494    /// An empty VorbisComment body (vendor + zero comments), for fixtures.
495    pub fn vorbis_body_empty() -> Vec<u8> {
496        crate::vorbiscomment::build(&[]).unwrap()
497    }
498}
499
500#[cfg(test)]
501mod tests {
502    use super::*;
503    use crate::ogg::page::{build_header, lace_packet};
504
505    fn opus_headers() -> Vec<u8> {
506        let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
507        let tags = b"OpusTags\x06\x00\x00\x00musefs\x00\x00\x00\x00".to_vec();
508        let (bytes, _) = build_header(0x1234, &[&head, &tags]);
509        bytes
510    }
511
512    #[test]
513    fn locate_audio_reports_bounds() {
514        let mut data = opus_headers();
515        let header_len = data.len();
516        let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 120]);
517        data.extend_from_slice(&audio);
518
519        let scan = locate_audio(&data).unwrap();
520        assert_eq!(scan.codec, Codec::Opus);
521        assert_eq!(scan.audio_offset, header_len as u64);
522        assert_eq!(scan.audio_length, (data.len() - header_len) as u64);
523    }
524
525    #[test]
526    fn reads_opus_header() {
527        let mut data = opus_headers();
528        // Append one audio page so audio_offset lands before EOF.
529        let (audio, _) = lace_packet(0x1234, 2, false, 960, &[0u8; 100]);
530        let header_len = data.len();
531        data.extend_from_slice(&audio);
532
533        let h = read_header(&data).unwrap();
534        assert_eq!(h.codec, Codec::Opus);
535        assert_eq!(h.serial, 0x1234);
536        assert_eq!(h.packets.len(), 2);
537        assert_eq!(h.audio_offset, header_len as u64);
538        assert_eq!(h.header_pages, 2);
539    }
540
541    #[test]
542    fn oggflac_following_packets_accepts_minimal_9_byte_packet() {
543        // The 16-bit count lives in bytes [7],[8], so a 9-byte first packet is the
544        // minimum valid input (`len < 9` rejects anything shorter). `<=` would
545        // wrongly reject this exact-length packet.
546        let mut pkt = [0u8; 9];
547        pkt[7] = 0x00;
548        pkt[8] = 0x03; // 3 following metadata-block packets
549        assert_eq!(oggflac_following_packets(&pkt).unwrap(), 3);
550    }
551
552    #[test]
553    fn comment_body_accepts_packet_with_empty_body() {
554        // A packet exactly `prefix` bytes long has an empty (but valid) comment
555        // body: `&packet[prefix..]` is the empty slice. `len < prefix` rejects only
556        // shorter packets; `<=` would wrongly reject this one. Opus prefix = 8.
557        assert!(comment_body(Codec::Opus, b"OpusTags").unwrap().is_empty());
558    }
559
560    #[test]
561    fn read_tags_opus() {
562        // Build an OpusTags packet with one real comment via the shared builder.
563        let body =
564            crate::vorbiscomment::build(&[crate::input::TagInput::new("title", "Sun")]).unwrap();
565        let mut tags_pkt = b"OpusTags".to_vec();
566        tags_pkt.extend_from_slice(&body);
567        let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
568        let (mut data, _) = crate::ogg::page::build_header(7, &[&head, &tags_pkt]);
569        let (audio, _) = crate::ogg::page::lace_packet(7, 2, false, 960, &[0u8; 50]);
570        data.extend_from_slice(&audio);
571
572        let tags = read_tags(&data).unwrap();
573        assert_eq!(tags, vec![("title".to_string(), "Sun".to_string())]);
574    }
575
576    #[test]
577    fn read_tags_excludes_metadata_block_picture() {
578        // A METADATA_BLOCK_PICTURE comment whose value is a base64 FLAC picture
579        // block carrying a 1-byte image, plus one ordinary text tag.
580        let mut block = Vec::new();
581        block.extend_from_slice(&3u32.to_be_bytes()); // picture type: front cover
582        block.extend_from_slice(&9u32.to_be_bytes());
583        block.extend_from_slice(b"image/png");
584        block.extend_from_slice(&0u32.to_be_bytes()); // description length
585        block.extend_from_slice(&1u32.to_be_bytes()); // width
586        block.extend_from_slice(&1u32.to_be_bytes()); // height
587        block.extend_from_slice(&8u32.to_be_bytes()); // depth
588        block.extend_from_slice(&0u32.to_be_bytes()); // colors used
589        block.extend_from_slice(&1u32.to_be_bytes()); // image length
590        block.push(0xAB);
591        let pic_value = base64::engine::general_purpose::STANDARD.encode(&block);
592
593        let body = crate::vorbiscomment::build(&[
594            crate::input::TagInput::new("title", "Sun"),
595            crate::input::TagInput::new("METADATA_BLOCK_PICTURE", &pic_value),
596        ])
597        .unwrap();
598        let mut tags_pkt = b"OpusTags".to_vec();
599        tags_pkt.extend_from_slice(&body);
600        let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
601        let (mut data, _) = crate::ogg::page::build_header(7, &[&head, &tags_pkt]);
602        let (audio, _) = crate::ogg::page::lace_packet(7, 2, false, 960, &[0u8; 50]);
603        data.extend_from_slice(&audio);
604
605        // read_tags returns only the text tag — the picture comment is excluded...
606        let tags = read_tags(&data).unwrap();
607        assert_eq!(tags, vec![("title".to_string(), "Sun".to_string())]);
608        // ...while read_pictures still finds the embedded art.
609        let pics = read_pictures(&data).unwrap();
610        assert_eq!(pics.len(), 1);
611        assert_eq!(pics[0].data, vec![0xAB]);
612    }
613
614    #[test]
615    fn synthesize_opus_emits_valid_header_and_audio_segment() {
616        let mut data = opus_headers();
617        let scan = locate_audio({
618            let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 80]);
619            data.extend_from_slice(&audio);
620            &data
621        })
622        .unwrap();
623        let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
624
625        let layout = synthesize_layout(
626            &header,
627            scan.audio_offset,
628            scan.audio_length,
629            &[TagInput::new("album", "Geogaddi")],
630            &[],
631            &MapArtSource::default(),
632        )
633        .unwrap();
634
635        // Collect all Inline header bytes (one per page) until OggAudio.
636        let mut header_bytes: Vec<u8> = Vec::new();
637        let mut audio_seg = None;
638        for seg in layout.segments() {
639            match seg {
640                Segment::Inline(b) => header_bytes.extend_from_slice(b),
641                Segment::OggAudio { offset, len, .. } => {
642                    audio_seg = Some((*offset, *len));
643                    break;
644                }
645                other => panic!("unexpected segment {other:?}"),
646            }
647        }
648        let h = read_header(&header_bytes).unwrap();
649        assert_eq!(h.codec, Codec::Opus);
650        let body = comment_body(Codec::Opus, &h.packets[1]).unwrap();
651        let tags = crate::vorbiscomment::parse(body).unwrap();
652        assert_eq!(tags, vec![("album".to_string(), "Geogaddi".to_string())]);
653        let (offset, len) = audio_seg.expect("expected OggAudio segment");
654        assert_eq!(offset, scan.audio_offset);
655        assert_eq!(len, scan.audio_length);
656    }
657
658    #[test]
659    fn synthesize_emits_nonzero_seq_delta_when_header_page_count_changes() {
660        // seq_delta = synthesized_page_count - original_header_pages. When the
661        // original header spanned a different number of pages than the regenerated
662        // one, the delta is non-zero — pins the subtraction at the OggAudio segment.
663        let mut data = opus_headers();
664        let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 80]);
665        data.extend_from_slice(&audio);
666        let scan = locate_audio(&data).unwrap();
667        let mut header =
668            read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
669
670        // Synthesis re-lays the Opus header into a known page count; record it.
671        let baseline = synthesize_layout(
672            &header,
673            scan.audio_offset,
674            scan.audio_length,
675            &[],
676            &[],
677            &MapArtSource::default(),
678        )
679        .unwrap();
680        let synth_pages = baseline
681            .segments()
682            .iter()
683            .filter(|s| matches!(s, Segment::Inline(_)))
684            .count();
685        assert!(synth_pages >= 1);
686
687        // Pretend the ORIGINAL header spanned three extra pages, so the served audio
688        // pages must be renumbered downward by exactly three.
689        let original_pages = u32::try_from(synth_pages).unwrap() + 3;
690        header.header_pages = original_pages;
691        let layout = synthesize_layout(
692            &header,
693            scan.audio_offset,
694            scan.audio_length,
695            &[],
696            &[],
697            &MapArtSource::default(),
698        )
699        .unwrap();
700        let delta = layout
701            .segments()
702            .iter()
703            .find_map(|s| match s {
704                Segment::OggAudio { seq_delta, .. } => Some(*seq_delta),
705                _ => None,
706            })
707            .expect("expected an OggAudio segment");
708        assert_eq!(
709            delta,
710            i64::try_from(synth_pages).unwrap() - i64::from(original_pages),
711            "seq_delta must be synthesized pages minus original header pages"
712        );
713        assert_eq!(delta, -3);
714    }
715
716    fn vorbis_headers_with(setup: &[u8]) -> Vec<u8> {
717        // Minimal-but-shaped Vorbis ID header (30 bytes from 0x01"vorbis").
718        let mut id = b"\x01vorbis".to_vec();
719        id.extend_from_slice(&0u32.to_le_bytes()); // version
720        id.push(2); // channels
721        id.extend_from_slice(&44100u32.to_le_bytes()); // sample rate
722        id.extend_from_slice(&0u32.to_le_bytes()); // bitrate max
723        id.extend_from_slice(&128_000u32.to_le_bytes()); // nominal
724        id.extend_from_slice(&0u32.to_le_bytes()); // min
725        id.push(0xB8); // blocksizes
726        id.push(0x01); // framing bit
727        let mut comment = b"\x03vorbis".to_vec();
728        comment.extend_from_slice(&crate::vorbiscomment::build(&[]).unwrap());
729        comment.push(0x01);
730        let (bytes, _) = crate::ogg::page::build_header(55, &[&id, &comment, setup]);
731        bytes
732    }
733
734    #[test]
735    fn synthesize_vorbis_preserves_setup_and_rewrites_comment() {
736        let setup = b"\x05vorbis-SETUP-CODEBOOKS-PLACEHOLDER".to_vec();
737        let mut data = vorbis_headers_with(&setup);
738        let (audio, _) = crate::ogg::page::lace_packet(55, 99, false, 1024, &[0u8; 64]);
739        data.extend_from_slice(&audio);
740
741        let scan = locate_audio(&data).unwrap();
742        assert_eq!(scan.codec, Codec::Vorbis);
743        let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
744        // The original setup packet (3rd header packet) must be carried through.
745        assert_eq!(header.packets[2], setup);
746
747        let layout = synthesize_layout(
748            &header,
749            scan.audio_offset,
750            scan.audio_length,
751            &[TagInput::new("artist", "Autechre")],
752            &[],
753            &MapArtSource::default(),
754        )
755        .unwrap();
756
757        let mut header_bytes: Vec<u8> = Vec::new();
758        for seg in layout.segments() {
759            match seg {
760                Segment::Inline(b) => header_bytes.extend_from_slice(b),
761                Segment::OggAudio { .. } => break,
762                other => panic!("unexpected segment {other:?}"),
763            }
764        }
765        let h = read_header(&header_bytes).unwrap();
766        assert_eq!(h.codec, Codec::Vorbis);
767        assert_eq!(h.packets[2], setup); // setup preserved byte-for-byte
768        let body = comment_body(Codec::Vorbis, &h.packets[1]).unwrap();
769        let tags = crate::vorbiscomment::parse(body).unwrap();
770        assert_eq!(tags, vec![("artist".to_string(), "Autechre".to_string())]);
771    }
772
773    #[test]
774    fn read_pictures_opus_decodes_metadata_block_picture() {
775        use base64::Engine;
776        // A minimal FLAC PICTURE block body: type=3, mime="image/png", empty desc,
777        // 1x1, depth 0, colors 0, data="PNG".
778        let mut pic = Vec::new();
779        pic.extend_from_slice(&3u32.to_be_bytes());
780        let mime = b"image/png";
781        pic.extend_from_slice(&u32::try_from(mime.len()).unwrap().to_be_bytes());
782        pic.extend_from_slice(mime);
783        pic.extend_from_slice(&0u32.to_be_bytes()); // desc len
784        pic.extend_from_slice(&1u32.to_be_bytes()); // width
785        pic.extend_from_slice(&1u32.to_be_bytes()); // height
786        pic.extend_from_slice(&0u32.to_be_bytes()); // depth
787        pic.extend_from_slice(&0u32.to_be_bytes()); // colors
788        let img = b"PNG";
789        pic.extend_from_slice(&u32::try_from(img.len()).unwrap().to_be_bytes());
790        pic.extend_from_slice(img);
791        let b64 = base64::engine::general_purpose::STANDARD.encode(&pic);
792
793        let mut body = Vec::new();
794        body.extend_from_slice(
795            &u32::try_from(crate::vorbiscomment::VENDOR.len())
796                .unwrap()
797                .to_le_bytes(),
798        );
799        body.extend_from_slice(crate::vorbiscomment::VENDOR.as_bytes());
800        body.extend_from_slice(&1u32.to_le_bytes()); // one comment
801        let comment = format!("METADATA_BLOCK_PICTURE={b64}");
802        body.extend_from_slice(&u32::try_from(comment.len()).unwrap().to_le_bytes());
803        body.extend_from_slice(comment.as_bytes());
804
805        let mut tags_pkt = b"OpusTags".to_vec();
806        tags_pkt.extend_from_slice(&body);
807        let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
808        let (mut data, _) = crate::ogg::page::build_header(7, &[&head, &tags_pkt]);
809        let (audio, _) = crate::ogg::page::lace_packet(7, 2, false, 960, &[0u8; 50]);
810        data.extend_from_slice(&audio);
811
812        let pics = read_pictures(&data).unwrap();
813        assert_eq!(pics.len(), 1);
814        assert_eq!(pics[0].mime, "image/png");
815        assert_eq!(pics[0].data, b"PNG");
816    }
817
818    #[test]
819    fn read_pictures_oggflac_short_picture_packet_does_not_panic() {
820        // Crafted OggFLAC: a structurally-valid mapping header declaring one
821        // following packet, where that packet is a 1-byte type-6 (PICTURE) block —
822        // too short for the 4-byte FLAC metadata block header. The `&pkt[4..]`
823        // slice must not panic (issue #365).
824        let mut mapping = vec![0x7F];
825        mapping.extend_from_slice(b"FLAC");
826        mapping.push(1);
827        mapping.push(0);
828        mapping.extend_from_slice(&1u16.to_be_bytes()); // one following packet
829        mapping.extend_from_slice(b"fLaC");
830        let mut streaminfo = Vec::new();
831        crate::flac::push_block_header(&mut streaminfo, 0, 34, false).unwrap();
832        streaminfo.extend(std::iter::repeat_n(0u8, 34));
833        mapping.extend_from_slice(&streaminfo);
834
835        // One lacing byte yields this 1-byte packet: block type 6, no body.
836        let short_picture = vec![0x06u8];
837
838        let (data, _) = crate::ogg::page::build_header(77, &[&mapping, &short_picture]);
839
840        // Sanity: the header parses, so we actually reach the picture loop.
841        assert_eq!(read_header(&data).unwrap().codec, Codec::OggFlac);
842        assert!(read_pictures(&data).unwrap().is_empty());
843    }
844
845    fn oggflac_headers() -> Vec<u8> {
846        // STREAMINFO block (type 0): 4-byte header + 34-byte body (zeros are fine
847        // for our framing test).
848        let mut streaminfo = Vec::new();
849        crate::flac::push_block_header(&mut streaminfo, 0, 34, false).unwrap();
850        streaminfo.extend(std::iter::repeat_n(0u8, 34));
851
852        // Mapping header packet: 0x7F "FLAC" v1.0 count "fLaC" STREAMINFO.
853        let mut mapping = vec![0x7F];
854        mapping.extend_from_slice(b"FLAC");
855        mapping.push(1);
856        mapping.push(0);
857        mapping.extend_from_slice(&2u16.to_be_bytes()); // count: SEEKTABLE + VORBIS_COMMENT
858        mapping.extend_from_slice(b"fLaC");
859        mapping.extend_from_slice(&streaminfo);
860
861        // A SEEKTABLE block (type 3, structural — must be preserved).
862        let mut seektable = Vec::new();
863        crate::flac::push_block_header(&mut seektable, 3, 18, false).unwrap();
864        seektable.extend(std::iter::repeat_n(0xEEu8, 18));
865
866        // An existing VORBIS_COMMENT (type 4, last) to be replaced.
867        let mut old_vc = Vec::new();
868        let body = crate::vorbiscomment::build(&[crate::input::TagInput::new("x", "old")]).unwrap();
869        crate::flac::push_block_header(&mut old_vc, 4, body.len(), true).unwrap();
870        old_vc.extend_from_slice(&body);
871
872        let (bytes, _) = crate::ogg::page::build_header(77, &[&mapping, &seektable, &old_vc]);
873        bytes
874    }
875
876    #[test]
877    fn rejects_multiplexed_second_bitstream() {
878        // Two BOS pages with DIFFERENT serials at the start => multiplexed; must reject.
879        let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
880        let (mut data, _) = crate::ogg::page::lace_packet(0x1111, 0, true, 0, &head);
881        // A second logical stream's BOS page (different serial).
882        let (other, _) = crate::ogg::page::lace_packet(
883            0x2222,
884            0,
885            true,
886            0,
887            b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".as_ref(),
888        );
889        data.extend_from_slice(&other);
890        // Some audio after, so audio_offset (if it were accepted) is past these pages.
891        let (audio, _) = crate::ogg::page::lace_packet(0x1111, 1, false, 960, &[0u8; 50]);
892        data.extend_from_slice(&audio);
893        assert!(read_header(&data).is_err());
894        assert!(locate_audio(&data).is_err());
895    }
896
897    #[test]
898    fn synthesize_oggflac_keeps_seektable_replaces_comment_and_count() {
899        let mut data = oggflac_headers();
900        let (audio, _) = crate::ogg::page::lace_packet(77, 3, false, 4096, &[0u8; 64]);
901        data.extend_from_slice(&audio);
902
903        let scan = locate_audio(&data).unwrap();
904        assert_eq!(scan.codec, Codec::OggFlac);
905        let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
906
907        let layout = synthesize_layout(
908            &header,
909            scan.audio_offset,
910            scan.audio_length,
911            &[TagInput::new("title", "Kaini Industries")],
912            &[],
913            &MapArtSource::default(),
914        )
915        .unwrap();
916
917        let mut header_bytes: Vec<u8> = Vec::new();
918        for seg in layout.segments() {
919            match seg {
920                Segment::Inline(b) => header_bytes.extend_from_slice(b),
921                Segment::OggAudio { .. } => break,
922                other => panic!("unexpected segment {other:?}"),
923            }
924        }
925        let h = read_header(&header_bytes).unwrap();
926        assert_eq!(h.codec, Codec::OggFlac);
927        // packet 0 mapping count == number of following blocks (SEEKTABLE + VC == 2)
928        assert_eq!(u16::from_be_bytes([h.packets[0][7], h.packets[0][8]]), 2);
929        // SEEKTABLE preserved
930        assert!(h.packets.iter().skip(1).any(|p| (p[0] & 0x7F) == 3));
931        // exactly one VORBIS_COMMENT, with the new tag, flagged last
932        let vc = h
933            .packets
934            .iter()
935            .skip(1)
936            .find(|p| (p[0] & 0x7F) == 4)
937            .unwrap();
938        assert_eq!(vc[0] & 0x80, 0x80);
939        let tags = crate::vorbiscomment::parse(&vc[4..]).unwrap();
940        assert_eq!(
941            tags,
942            vec![("title".to_string(), "Kaini Industries".to_string())]
943        );
944    }
945
946    #[test]
947    fn synthesize_opus_embeds_art_that_round_trips() {
948        let mut data = opus_headers();
949        let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 80]);
950        data.extend_from_slice(&audio);
951        let scan = locate_audio(&data).unwrap();
952        let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
953
954        let image: Vec<u8> = (0..5000u32).map(|i| (i % 251) as u8).collect();
955        let meta = crate::input::ArtInput {
956            art_id: 7,
957            mime: "image/jpeg".to_string(),
958            description: String::new(),
959            picture_type: crate::input::PictureType::new(3).unwrap(),
960            width: 64,
961            height: 64,
962            data_len: crate::input::BlobLen::new(image.len() as u64).unwrap(),
963        };
964        let src = MapArtSource::new([(meta.art_id, image.clone())]);
965        let layout = synthesize_layout(
966            &header,
967            scan.audio_offset,
968            scan.audio_length,
969            &[TagInput::new("title", "Cover")],
970            &[OggArt { meta: &meta }],
971            &src,
972        )
973        .unwrap();
974
975        // Materialize the header region from the layout, expanding OggArtSlice by
976        // re-deriving its bytes from `image` (mirrors what read_at does).
977        let mut bytes = Vec::new();
978        for s in layout.segments() {
979            match s {
980                Segment::Inline(b) => bytes.extend_from_slice(b),
981                Segment::OggArtSlice {
982                    offset,
983                    len,
984                    base64,
985                    art_total,
986                    ..
987                } => {
988                    assert!(*base64);
989                    let w = b64_window(*offset, len.get(), *art_total);
990                    let raw = &image[crate::convert::usize_from(w.in_start)
991                        ..crate::convert::usize_from(w.in_start + w.in_len)];
992                    bytes.extend_from_slice(
993                        &encode_b64_slice(raw, w.skip, crate::convert::usize_from(len.get()))
994                            .expect("window lies within the encoded output"),
995                    );
996                }
997                Segment::OggAudio { .. } => break, // header region ends here
998                other => panic!("unexpected {other:?}"),
999            }
1000        }
1001
1002        let pics = read_pictures(&bytes).unwrap();
1003        assert_eq!(pics.len(), 1);
1004        assert_eq!(pics[0].mime, "image/jpeg");
1005        assert_eq!(pics[0].data, image);
1006        let h = read_header(&bytes).unwrap();
1007        assert_eq!(h.codec, Codec::Opus);
1008    }
1009
1010    // Materialize the header region of a synthesized layout into bytes, expanding
1011    // each OggArtSlice from `images` (art_id -> raw image), mirroring read_at.
1012    fn materialize_header(layout: &RegionLayout, images: &[(i64, &[u8])]) -> Vec<u8> {
1013        let mut bytes = Vec::new();
1014        for s in layout.segments() {
1015            match s {
1016                Segment::Inline(b) => bytes.extend_from_slice(b),
1017                Segment::OggArtSlice {
1018                    art_id,
1019                    offset,
1020                    len,
1021                    base64,
1022                    art_total,
1023                } => {
1024                    let img = images.iter().find(|(id, _)| id == art_id).expect("image").1;
1025                    if *base64 {
1026                        let w = b64_window(*offset, len.get(), *art_total);
1027                        let raw = &img[crate::convert::usize_from(w.in_start)
1028                            ..crate::convert::usize_from(w.in_start + w.in_len)];
1029                        bytes.extend_from_slice(
1030                            &encode_b64_slice(raw, w.skip, crate::convert::usize_from(len.get()))
1031                                .expect("window lies within the encoded output"),
1032                        );
1033                    } else {
1034                        bytes.extend_from_slice(
1035                            &img[crate::convert::usize_from(*offset)
1036                                ..crate::convert::usize_from(*offset + len.get())],
1037                        );
1038                    }
1039                }
1040                Segment::OggAudio { .. } => break,
1041                other => panic!("unexpected {other:?}"),
1042            }
1043        }
1044        bytes
1045    }
1046
1047    fn art_input(art_id: i64, mime: &str, len: usize) -> crate::input::ArtInput {
1048        crate::input::ArtInput {
1049            art_id,
1050            mime: mime.to_string(),
1051            description: String::new(),
1052            picture_type: crate::input::PictureType::new(3).unwrap(),
1053            width: 10,
1054            height: 10,
1055            data_len: crate::input::BlobLen::new(len as u64).unwrap(),
1056        }
1057    }
1058
1059    #[test]
1060    fn synthesize_vorbis_embeds_art_that_round_trips() {
1061        let setup = b"\x05vorbis-SETUP".to_vec();
1062        let mut data = vorbis_headers_with(&setup);
1063        let (audio, _) = crate::ogg::page::lace_packet(55, 99, false, 1024, &[0u8; 64]);
1064        data.extend_from_slice(&audio);
1065        let scan = locate_audio(&data).unwrap();
1066        let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
1067
1068        let image: Vec<u8> = (0..4000u32).map(|i| (i % 251) as u8).collect();
1069        let meta = art_input(11, "image/png", image.len());
1070        let src = MapArtSource::new([(meta.art_id, image.clone())]);
1071        let layout = synthesize_layout(
1072            &header,
1073            scan.audio_offset,
1074            scan.audio_length,
1075            &[TagInput::new("artist", "X")],
1076            &[OggArt { meta: &meta }],
1077            &src,
1078        )
1079        .unwrap();
1080
1081        let bytes = materialize_header(&layout, &[(11, &image)]);
1082        let h = read_header(&bytes).unwrap();
1083        assert_eq!(h.codec, Codec::Vorbis);
1084        assert_eq!(h.packets[2], setup); // setup preserved
1085        let pics = read_pictures(&bytes).unwrap();
1086        assert_eq!(pics.len(), 1);
1087        assert_eq!(pics[0].data, image);
1088    }
1089
1090    #[test]
1091    fn synthesize_oggflac_embeds_art_that_round_trips() {
1092        let mut data = oggflac_headers();
1093        let (audio, _) = crate::ogg::page::lace_packet(77, 3, false, 4096, &[0u8; 64]);
1094        data.extend_from_slice(&audio);
1095        let scan = locate_audio(&data).unwrap();
1096        let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
1097
1098        let image: Vec<u8> = (0..4000u32).map(|i| (i % 251) as u8).collect();
1099        let meta = art_input(22, "image/png", image.len());
1100        let src = MapArtSource::new([(meta.art_id, image.clone())]);
1101        let layout = synthesize_layout(
1102            &header,
1103            scan.audio_offset,
1104            scan.audio_length,
1105            &[TagInput::new("title", "Y")],
1106            &[OggArt { meta: &meta }],
1107            &src,
1108        )
1109        .unwrap();
1110
1111        let bytes = materialize_header(&layout, &[(22, &image)]);
1112        let h = read_header(&bytes).unwrap();
1113        assert_eq!(h.codec, Codec::OggFlac);
1114        let pics = read_pictures(&bytes).unwrap();
1115        assert_eq!(pics.len(), 1);
1116        assert_eq!(pics[0].data, image);
1117    }
1118
1119    #[test]
1120    fn synthesize_oggflac_embeds_large_art_spanning_pages_round_trips() {
1121        // A >64 KiB raw PICTURE block forces the art run across multiple pages,
1122        // exercising the non-base64 streaming-CRC path at page boundaries (the
1123        // base64 path is covered by the lacer test; this pins the raw path).
1124        let mut data = oggflac_headers();
1125        let (audio, _) = crate::ogg::page::lace_packet(77, 3, false, 4096, &[0u8; 64]);
1126        data.extend_from_slice(&audio);
1127        let scan = locate_audio(&data).unwrap();
1128        let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
1129
1130        let image: Vec<u8> = (0..200_000u32).map(|i| (i % 251) as u8).collect();
1131        let meta = art_input(31, "image/png", image.len());
1132        let src = MapArtSource::new([(meta.art_id, image.clone())]);
1133        let layout = synthesize_layout(
1134            &header,
1135            scan.audio_offset,
1136            scan.audio_length,
1137            &[TagInput::new("title", "Big")],
1138            &[OggArt { meta: &meta }],
1139            &src,
1140        )
1141        .unwrap();
1142
1143        // The art run must split across pages: more than one raw OggArtSlice.
1144        let art_slices = layout
1145            .segments()
1146            .iter()
1147            .filter(|s| matches!(s, Segment::OggArtSlice { base64: false, .. }))
1148            .count();
1149        assert!(
1150            art_slices >= 2,
1151            "expected the raw art to span multiple pages, got {art_slices} slice(s)"
1152        );
1153
1154        let bytes = materialize_header(&layout, &[(31, &image)]);
1155        let h = read_header(&bytes).unwrap();
1156        assert_eq!(h.codec, Codec::OggFlac);
1157        let pics = read_pictures(&bytes).unwrap();
1158        assert_eq!(pics.len(), 1);
1159        assert_eq!(
1160            pics[0].data, image,
1161            "large art must round-trip byte-for-byte"
1162        );
1163    }
1164
1165    #[test]
1166    fn synthesize_opus_embeds_multiple_images() {
1167        let mut data = opus_headers();
1168        let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 64]);
1169        data.extend_from_slice(&audio);
1170        let scan = locate_audio(&data).unwrap();
1171        let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
1172
1173        let img_a: Vec<u8> = (0..3000u32).map(|i| (i % 251) as u8).collect();
1174        let img_b: Vec<u8> = (0..1500u32).map(|i| ((i * 3) % 251) as u8).collect();
1175        let meta_a = art_input(1, "image/png", img_a.len());
1176        let meta_b = art_input(2, "image/jpeg", img_b.len());
1177        let src = MapArtSource::new([
1178            (meta_a.art_id, img_a.clone()),
1179            (meta_b.art_id, img_b.clone()),
1180        ]);
1181        let layout = synthesize_layout(
1182            &header,
1183            scan.audio_offset,
1184            scan.audio_length,
1185            &[TagInput::new("title", "Multi")],
1186            &[OggArt { meta: &meta_a }, OggArt { meta: &meta_b }],
1187            &src,
1188        )
1189        .unwrap();
1190
1191        let bytes = materialize_header(&layout, &[(1, &img_a), (2, &img_b)]);
1192        let h = read_header(&bytes).unwrap();
1193        assert_eq!(h.codec, Codec::Opus);
1194        let pics = read_pictures(&bytes).unwrap();
1195        assert_eq!(pics.len(), 2);
1196        assert_eq!(pics[0].data, img_a);
1197        assert_eq!(pics[1].data, img_b);
1198    }
1199
1200    #[test]
1201    fn oversized_full_art_value_rejected_by_build_packets() {
1202        let meta = crate::input::ArtInput {
1203            art_id: 0,
1204            mime: "image/jpeg".to_string(),
1205            description: String::new(),
1206            data_len: crate::input::BlobLen::new(u64::from(u32::MAX)).unwrap(),
1207            picture_type: crate::input::PictureType::new(3).unwrap(),
1208            width: 0,
1209            height: 0,
1210        };
1211        let art = OggArt { meta: &meta };
1212        let header = OggHeader {
1213            codec: Codec::Vorbis,
1214            serial: 0,
1215            packets: vec![vec![], vec![], vec![]],
1216            header_pages: 1,
1217            audio_offset: 0,
1218        };
1219        let result = build_packets_with_art(&header, &[], &[art]);
1220        assert!(result.is_err(), "expected Err for oversized art");
1221    }
1222
1223    #[test]
1224    fn sum_overflow_art_value_rejected_by_build_packets() {
1225        // data_len and prefix individually fit in u32, but the full value
1226        // (key + b64(prefix) + b64(data)) exceeds u32::MAX.
1227        let meta = crate::input::ArtInput {
1228            art_id: 0,
1229            mime: "image/png".to_string(),
1230            description: "x".repeat(256),
1231            data_len: crate::input::BlobLen::new(3_221_225_470).unwrap(),
1232            picture_type: crate::input::PictureType::new(3).unwrap(),
1233            width: 0,
1234            height: 0,
1235        };
1236        let art = OggArt { meta: &meta };
1237        let header = OggHeader {
1238            codec: Codec::Vorbis,
1239            serial: 0,
1240            packets: vec![vec![], vec![], vec![]],
1241            header_pages: 1,
1242            audio_offset: 0,
1243        };
1244        let result = build_packets_with_art(&header, &[], &[art]);
1245        assert!(
1246            result.is_err(),
1247            "expected Err when key + b64(prefix) + b64(data) overflows u32"
1248        );
1249    }
1250
1251    #[test]
1252    fn art_value_at_u32_max_boundary_is_accepted_by_build_packets() {
1253        // Pin the exact `value_len > u32::MAX` boundary: with mime "image/png" and
1254        // an empty description, key(23) + b64(prefix=42)=56 + b64(data_len) lands on
1255        // u32::MAX EXACTLY when data_len == 3_221_225_412. A correct `>` admits it
1256        // (the downstream u32 length field still fits); `>=`/`==` or a `*`-mutated
1257        // sum would wrongly reject it. The declared 3 GiB image is never
1258        // materialized (image bytes are empty), so the build is cheap.
1259        let meta = crate::input::ArtInput {
1260            art_id: 0,
1261            mime: "image/png".to_string(),
1262            description: String::new(),
1263            data_len: crate::input::BlobLen::new(3_221_225_412).unwrap(),
1264            picture_type: crate::input::PictureType::new(3).unwrap(),
1265            width: 0,
1266            height: 0,
1267        };
1268        let art = OggArt { meta: &meta };
1269        let header = OggHeader {
1270            codec: Codec::Vorbis,
1271            serial: 0,
1272            packets: vec![vec![], vec![], vec![]],
1273            header_pages: 1,
1274            audio_offset: 0,
1275        };
1276        let accepted = build_packets_with_art(&header, &[], &[art]).is_ok();
1277        assert!(
1278            accepted,
1279            "value_len exactly u32::MAX must be accepted by build_packets_with_art"
1280        );
1281    }
1282
1283    #[test]
1284    fn near_u64_max_art_value_rejected_by_build_packets() {
1285        // data_len near u64::MAX makes b64_len(data_len) overflow u64; the builder
1286        // must fail closed with TooLarge at the checked b64 length, not panic
1287        // (debug) inside the pre-flight value_len computation.
1288        let meta = crate::input::ArtInput {
1289            art_id: 0,
1290            mime: "image/jpeg".to_string(),
1291            description: String::new(),
1292            data_len: crate::input::BlobLen::new(u64::MAX).unwrap(),
1293            picture_type: crate::input::PictureType::new(3).unwrap(),
1294            width: 0,
1295            height: 0,
1296        };
1297        let art = OggArt { meta: &meta };
1298        let header = OggHeader {
1299            codec: Codec::Vorbis,
1300            serial: 0,
1301            packets: vec![vec![], vec![], vec![]],
1302            header_pages: 1,
1303            audio_offset: 0,
1304        };
1305        let result = build_packets_with_art(&header, &[], &[art]);
1306        let is_too_large = matches!(&result, Err(FormatError::TooLarge));
1307        assert!(is_too_large, "expected Err(TooLarge) for near-u64::MAX art");
1308    }
1309
1310    #[test]
1311    fn near_u64_max_art_value_rejected_by_oggflac_build_packets() {
1312        // The Ogg-FLAC art path builds a METADATA_BLOCK_PICTURE body as
1313        // prefix + raw image. A hostile data_len near u64::MAX must fail closed
1314        // with TooLarge, not panic (debug) / wrap (release). picture_prefix's u32
1315        // length field already rejects it; the checked add keeps the body-length
1316        // site self-defending regardless of that ordering.
1317        let meta = crate::input::ArtInput {
1318            art_id: 0,
1319            mime: "image/jpeg".to_string(),
1320            description: String::new(),
1321            data_len: crate::input::BlobLen::new(u64::MAX).unwrap(),
1322            picture_type: crate::input::PictureType::new(3).unwrap(),
1323            width: 0,
1324            height: 0,
1325        };
1326        let art = OggArt { meta: &meta };
1327        let header = OggHeader {
1328            codec: Codec::OggFlac,
1329            serial: 0,
1330            packets: vec![vec![0x7F]],
1331            header_pages: 1,
1332            audio_offset: 0,
1333        };
1334        let result = build_packets_with_art(&header, &[], &[art]);
1335        let is_too_large = matches!(&result, Err(FormatError::TooLarge));
1336        assert!(is_too_large, "expected Err(TooLarge) for near-u64::MAX art");
1337    }
1338
1339    #[test]
1340    fn picture_prefix_is_3_aligned_and_declares_image_len() {
1341        let art = crate::input::ArtInput {
1342            art_id: 1,
1343            mime: "image/png".to_string(), // 9 -> base = 32+9+0 = 41 -> pad 1
1344            description: String::new(),
1345            picture_type: crate::input::PictureType::new(3).unwrap(),
1346            width: 1,
1347            height: 1,
1348            data_len: crate::input::BlobLen::new(12345).unwrap(),
1349        };
1350        let p = picture_prefix(&art).unwrap();
1351        assert_eq!(p.len() % 3, 0);
1352        // datalen is the last 4 bytes (big-endian) and equals the true image length.
1353        let dl = u32::from_be_bytes(p[p.len() - 4..].try_into().unwrap());
1354        assert_eq!(dl, 12345);
1355        // Reusing the existing FLAC picture parser proves the framing is valid:
1356        // parse_picture_block expects the body (prefix + image); append dummy image.
1357        let mut body = p.clone();
1358        body.extend(std::iter::repeat_n(0u8, 12345));
1359        let pic = crate::flac::parse_picture_block(&body).unwrap();
1360        assert_eq!(pic.mime, "image/png");
1361        assert_eq!(pic.picture_type.get(), 3);
1362    }
1363
1364    #[test]
1365    fn detect_codec_matches_each_magic_and_rejects_others() {
1366        assert_eq!(detect_codec(b"OpusHead........").unwrap(), Codec::Opus);
1367        assert_eq!(detect_codec(b"\x01vorbis...").unwrap(), Codec::Vorbis);
1368        assert_eq!(detect_codec(b"\x7FFLAC...").unwrap(), Codec::OggFlac);
1369        // Too-short and non-matching inputs must error (kills the :25 && -> || and
1370        // the length-guard mutations).
1371        assert!(detect_codec(b"OpusHea").is_err()); // 7 bytes, len guard
1372        assert!(detect_codec(b"XXXXXXXX").is_err()); // right length, wrong magic
1373        assert!(detect_codec(b"\x01vorbi").is_err()); // 6 bytes
1374    }
1375
1376    #[test]
1377    fn comment_body_strips_each_codec_prefix_and_guards_length() {
1378        assert_eq!(comment_body(Codec::Opus, b"OpusTagsBODY").unwrap(), b"BODY");
1379        assert_eq!(
1380            comment_body(Codec::Vorbis, b"\x03vorbisBODY").unwrap(),
1381            b"BODY"
1382        );
1383        assert_eq!(
1384            comment_body(Codec::OggFlac, b"\x04\x00\x00\x00BODY").unwrap(),
1385            b"BODY"
1386        );
1387        // packet shorter than the prefix errors (kills :113 < -> ==/<=).
1388        assert!(comment_body(Codec::Opus, b"OpusTa").is_err());
1389        assert!(comment_body(Codec::OggFlac, b"\x04\x00\x00").is_err());
1390    }
1391
1392    #[test]
1393    fn oggflac_following_packets_reads_be_count_and_guards_length() {
1394        // 0x7F"FLAC" major minor count(BE) ... ; count bytes at [7],[8].
1395        let pkt = b"\x7FFLAC\x01\x00\x00\x05rest";
1396        assert_eq!(oggflac_following_packets(pkt).unwrap(), 5);
1397        assert!(oggflac_following_packets(b"\x7FFLAC\x01\x00").is_err()); // 7 bytes (<9)
1398    }
1399
1400    #[test]
1401    fn oggflac_comment_block_size_boundary_is_inclusive() {
1402        // The regenerated OggFLAC VORBIS_COMMENT block shares FLAC's 24-bit
1403        // block length. Derive the non-value overhead from production, then
1404        // size the value so the body lands exactly on the limit; one more byte
1405        // errors. The `>` accepts the inclusive limit; the `>=` mutant rejects.
1406        let header = OggHeader {
1407            codec: Codec::OggFlac,
1408            serial: 1,
1409            packets: vec![vec![0x7F; 9]],
1410            header_pages: 1,
1411            audio_offset: 0,
1412        };
1413        let overhead = crate::vorbiscomment::build(&[crate::input::TagInput::new("title", "")])
1414            .unwrap()
1415            .len() as u64;
1416        let at_limit = "x".repeat(crate::convert::usize_from(
1417            crate::flac::MAX_BLOCK_BODY - overhead,
1418        ));
1419        let tags = [crate::input::TagInput::new("title", at_limit.as_str())];
1420        assert!(oggflac_packets_with_art(&header, &tags, &[]).is_ok());
1421        // one byte over must still error, pinning the high side of the boundary.
1422        let over = format!("{at_limit}x");
1423        let tags = [crate::input::TagInput::new("title", over.as_str())];
1424        assert!(matches!(
1425            oggflac_packets_with_art(&header, &tags, &[]),
1426            Err(FormatError::TooLarge)
1427        ));
1428    }
1429
1430    #[test]
1431    fn oggflac_picture_block_size_boundary_is_inclusive() {
1432        // body_len = picture_prefix(meta).len() + data_len; the guard shares
1433        // FLAC's 24-bit block limit. data_len is only a count (image bytes are
1434        // streamed), so the exact boundary is cheap to pin. The `>` accepts the
1435        // inclusive limit — which also pins the `+` assembly, since a product
1436        // of the two terms overshoots it — while the `>=` mutant rejects it.
1437        let header = OggHeader {
1438            codec: Codec::OggFlac,
1439            serial: 1,
1440            packets: vec![vec![0x7F; 9]],
1441            header_pages: 1,
1442            audio_offset: 0,
1443        };
1444        let mk = |data_len: u64| crate::input::ArtInput {
1445            art_id: 1,
1446            mime: "image/png".to_string(),
1447            description: String::new(),
1448            picture_type: crate::input::PictureType::new(3).unwrap(),
1449            width: 0,
1450            height: 0,
1451            data_len: crate::input::BlobLen::new(data_len).unwrap(),
1452        };
1453        let framing_len = picture_prefix(&mk(1)).unwrap().len() as u64;
1454        let at_limit = mk(crate::flac::MAX_BLOCK_BODY - framing_len);
1455        let arts = [OggArt { meta: &at_limit }];
1456        assert!(oggflac_packets_with_art(&header, &[], &arts).is_ok());
1457        // one byte over must still error, pinning the high side of the boundary.
1458        let over = mk(crate::flac::MAX_BLOCK_BODY - framing_len + 1);
1459        let arts = [OggArt { meta: &over }];
1460        assert!(matches!(
1461            oggflac_packets_with_art(&header, &[], &arts),
1462            Err(FormatError::TooLarge)
1463        ));
1464    }
1465
1466    #[test]
1467    fn comment_packet_index_locates_the_comment_block() {
1468        // Opus/Vorbis: always packet index 1 (kills :121 -> 1 only if a non-1 case
1469        // exists; assert OggFLAC search to pin the skip(1)+find logic at :130).
1470        let opus = OggHeader {
1471            codec: Codec::Opus,
1472            serial: 1,
1473            packets: vec![vec![], vec![]],
1474            header_pages: 1,
1475            audio_offset: 0,
1476        };
1477        assert_eq!(comment_packet_index(&opus), 1);
1478
1479        // OggFLAC: packet 0 mapping, packet 1 type 1 (non-comment), packet 2 type 4.
1480        let oggflac = OggHeader {
1481            codec: Codec::OggFlac,
1482            serial: 1,
1483            packets: vec![vec![0x7F], vec![0x01], vec![0x84]], // 0x84 & 0x7F == 4
1484            header_pages: 1,
1485            audio_offset: 0,
1486        };
1487        assert_eq!(comment_packet_index(&oggflac), 2);
1488        // No type-4 block -> 0 (kills the bitmask / == mutations at :130).
1489        let none = OggHeader {
1490            codec: Codec::OggFlac,
1491            serial: 1,
1492            packets: vec![vec![0x7F], vec![0x01], vec![0x05]],
1493            header_pages: 1,
1494            audio_offset: 0,
1495        };
1496        assert_eq!(comment_packet_index(&none), 0);
1497    }
1498
1499    #[test]
1500    fn locate_audio_accepts_empty_audio_region() {
1501        // opus_headers() is header pages only: audio_offset == data.len(). The
1502        // original `>` yields Ok (audio_length 0); the :196 `==`/`>=` mutants reject.
1503        let file = opus_headers();
1504        let scan = locate_audio(&file).unwrap();
1505        assert_eq!(scan.codec, Codec::Opus);
1506        assert_eq!(scan.audio_offset, file.len() as u64);
1507        assert_eq!(scan.audio_length, 0);
1508    }
1509
1510    #[test]
1511    fn picture_prefix_declared_desc_len_pins_padding() {
1512        let art = crate::input::ArtInput {
1513            art_id: 1,
1514            mime: "image/png".into(), // 9
1515            description: "x".into(),  // 1 -> base = 42, 42 % 3 == 0 -> pad 0
1516            picture_type: crate::input::PictureType::new(3).unwrap(),
1517            width: 1,
1518            height: 1,
1519            data_len: crate::input::BlobLen::new(100).unwrap(),
1520        };
1521        let prefix = picture_prefix(&art).unwrap();
1522        assert_eq!(prefix.len() % 3, 0);
1523        // Declared description length lives at offset 8 + mime.len() (after
1524        // type[4] + mimelen[4] + mime). pad = declared - desc.len() must be 0..=2.
1525        let off = 8 + art.mime.len();
1526        let declared = u32::from_be_bytes(prefix[off..off + 4].try_into().unwrap());
1527        let pad = declared - u32::try_from(art.description.len()).unwrap();
1528        assert!(pad <= 2, "pad must be 0..=2, got {pad}");
1529        assert_eq!(pad, 0, "base % 3 == 0 implies pad 0");
1530    }
1531
1532    #[test]
1533    fn synthesis_reads_art_in_page_bounded_windows() {
1534        use std::cell::Cell;
1535        struct Counting<'a> {
1536            inner: MapArtSource,
1537            max: &'a Cell<usize>,
1538        }
1539        impl ArtSource for Counting<'_> {
1540            fn read_window(&self, art_id: i64, offset: u64, buf: &mut [u8]) -> crate::Result<()> {
1541                self.max.set(self.max.get().max(buf.len()));
1542                self.inner.read_window(art_id, offset, buf)
1543            }
1544        }
1545
1546        // Inline Opus fixture, mirroring synthesize_opus_emits_valid_header_and_audio_segment.
1547        let mut data = opus_headers();
1548        let scan = locate_audio({
1549            let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 80]);
1550            data.extend_from_slice(&audio);
1551            &data
1552        })
1553        .unwrap();
1554        let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
1555
1556        let image: Vec<u8> = (0..500_000u32).map(|i| (i % 251) as u8).collect();
1557        let meta = crate::input::ArtInput {
1558            art_id: 7,
1559            mime: "image/jpeg".to_string(),
1560            description: String::new(),
1561            picture_type: crate::input::PictureType::new(3).unwrap(),
1562            width: 0,
1563            height: 0,
1564            data_len: crate::input::BlobLen::new(image.len() as u64).unwrap(),
1565        };
1566        let max = Cell::new(0usize);
1567        let src = Counting {
1568            inner: MapArtSource::new([(7i64, image.clone())]),
1569            max: &max,
1570        };
1571        synthesize_layout(
1572            &header,
1573            scan.audio_offset,
1574            scan.audio_length,
1575            &[],
1576            &[OggArt { meta: &meta }],
1577            &src,
1578        )
1579        .unwrap();
1580        // One Ogg page's payload is at most 255*255 = 65025 bytes; raw windows are
1581        // <= that. The 500 KB image is never read in a single call.
1582        assert!(
1583            max.get() > 0 && max.get() <= 65_025,
1584            "max single read was {}",
1585            max.get()
1586        );
1587    }
1588}
1589
1590#[cfg(test)]
1591mod page_test_support_tests {
1592    /// Pins the fixture's contract: a parseable VorbisComment with zero
1593    /// comments. Consumers (musefs-core's ogg tests) splice it into OpusTags
1594    /// packets, but only format-local tests can kill mutations of it — the
1595    /// mutation gate runs each crate's own suite.
1596    #[test]
1597    fn vorbis_body_empty_is_a_parseable_empty_comment() {
1598        let body = super::page_test_support::vorbis_body_empty();
1599        let parsed = crate::vorbiscomment::parse(&body).unwrap();
1600        assert!(parsed.is_empty());
1601    }
1602}
1603
1604#[cfg(test)]
1605mod bounded_tests {
1606    use super::*;
1607    use crate::ogg::page_test_support::{build_header_pub, lace_packet_pub, vorbis_body_empty};
1608
1609    /// A minimal Opus stream: OpusHead + OpusTags header packets, then a trailing
1610    /// audio page. Returns (full, audio_offset). Mirrors the proven fixture in
1611    /// `musefs-core/src/scan.rs::ogg_probe_tests::probe_detects_opus_and_seeds_tags`.
1612    /// `build_header_pub(serial, &[&[u8]])` laces *all* header packets across
1613    /// pages (BOS set once) and returns `(Vec<u8>, u32)`; `lace_packet_pub` takes
1614    /// `(serial, seq_start, bos, granule, packet)` and returns `(Vec<u8>, u32)`.
1615    fn opus_stream() -> (Vec<u8>, u64) {
1616        let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
1617        let mut tags = b"OpusTags".to_vec();
1618        tags.extend_from_slice(&vorbis_body_empty());
1619        let serial = 0x1234;
1620        let (mut v, _) = build_header_pub(serial, &[&head, &tags]);
1621        let audio_offset = v.len() as u64;
1622        let (audio, _) = lace_packet_pub(serial, 2, false, 960, &[0u8; 100]);
1623        v.extend_from_slice(&audio);
1624        (v, audio_offset)
1625    }
1626
1627    #[test]
1628    fn read_metadata_bounded_complete_when_prefix_covers_header() {
1629        let (full, audio_offset) = opus_stream();
1630        let file_len = full.len() as u64;
1631        let prefix = &full[..crate::convert::usize_from(audio_offset)]; // exactly the header region
1632        match read_metadata_bounded(prefix, file_len).unwrap() {
1633            Extent::Complete(h) => assert_eq!(h.audio_offset, audio_offset),
1634            other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
1635        }
1636    }
1637
1638    #[test]
1639    fn read_metadata_bounded_needmore_when_header_truncated() {
1640        let (full, _audio_offset) = opus_stream();
1641        let file_len = full.len() as u64;
1642        let prefix = &full[..20]; // mid first page
1643        match read_metadata_bounded(prefix, file_len).unwrap() {
1644            Extent::NeedMore { up_to } => assert!(up_to > 20 && up_to <= file_len),
1645            other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
1646        }
1647    }
1648
1649    #[test]
1650    fn read_metadata_bounded_errors_when_whole_file_is_unparseable() {
1651        // A short garbage buffer that IS the whole file: prefix.len() == file_len and
1652        // read_header errors. The guard `(prefix.len() as u64) < file_len` is FALSE,
1653        // so the function must fall to the `Err(e)` arm and return Err — never grow.
1654        let bad: &[u8] = b"not an ogg stream at all"; // capture pattern != "OggS"
1655        // Confirm the premise: read_header genuinely errors on this buffer.
1656        assert!(read_header(bad).is_err());
1657        let len = bad.len() as u64;
1658        // kills ogg L226 guard `< file_len` -> `true`: under `true` this returns
1659        // NeedMore; correct is Err.
1660        // kills ogg L226 `<` -> `<=`: `len <= len` is true -> NeedMore; correct is Err.
1661        match read_metadata_bounded(bad, len) {
1662            Err(_) => {}
1663            Ok(other) => panic!("expected Err when whole file unparseable, got {other:?}"),
1664        }
1665    }
1666
1667    #[test]
1668    fn read_metadata_bounded_doubles_window_exactly() {
1669        // L = 100_000 bytes of garbage (read_header errors): L > 64*1024 so `.max`
1670        // does not mask, and file_len = 10_000_000 > L*2 so `.min` does not clamp.
1671        // Correct up_to = L*2 = 200_000. `+`->100_002, `/`->50_000 all differ.
1672        let buf = vec![0u8; 100_000]; // all zeros: capture pattern != "OggS" -> errors
1673        // Confirm the premise: read_header genuinely errors on this buffer.
1674        assert!(read_header(&buf).is_err());
1675        let file_len = 10_000_000u64;
1676        match read_metadata_bounded(&buf, file_len).unwrap() {
1677            Extent::NeedMore { up_to } => assert_eq!(up_to, 200_000),
1678            other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
1679        }
1680    }
1681
1682    #[test]
1683    fn read_metadata_bounded_floor_is_64kib_for_small_prefix() {
1684        // The `*` at L227 col 74 is the `64 * 1024` FLOOR in `.max(64 * 1024)`,
1685        // not the doubling. To exercise it the floor must bind: a tiny prefix whose
1686        // doubled length (200) is below 64 KiB, with file_len well above 64 KiB so
1687        // `.min(file_len)` doesn't clamp. Correct floor = 65_536.
1688        let buf = vec![0u8; 100]; // garbage: read_header errors
1689        assert!(read_header(&buf).is_err());
1690        let file_len = 10_000_000u64;
1691        // kills ogg L227 `64 * 1024` -> `64 + 1024` (=1088) and `64 / 1024` (=0):
1692        // only `*` yields the 65_536 floor when the doubled length is smaller.
1693        match read_metadata_bounded(&buf, file_len).unwrap() {
1694            Extent::NeedMore { up_to } => assert_eq!(up_to, 65_536),
1695            other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
1696        }
1697    }
1698
1699    #[test]
1700    fn read_metadata_bounded_grows_when_truncated_prefix_shorter_than_file() {
1701        // Pins the TRUE side of the guard: a truncated valid-prefix where
1702        // prefix.len() < file_len must return NeedMore (kills `<`->`<=` from the
1703        // other direction by requiring growth here while requiring Err when equal).
1704        let (full, _audio_offset) = opus_stream();
1705        let file_len = full.len() as u64;
1706        let prefix = &full[..10]; // far short of the header region
1707        assert!(read_header(prefix).is_err());
1708        match read_metadata_bounded(prefix, file_len).unwrap() {
1709            Extent::NeedMore { up_to } => assert!(up_to > prefix.len() as u64),
1710            other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
1711        }
1712    }
1713}