1use crate::bytes::read_u32_be;
2use crate::error::{FormatError, Result};
3use crate::probe::Extent;
4use crate::size;
5
6pub(crate) const FLAC_MARKER: &[u8; 4] = b"fLaC";
7
8pub(crate) const BLOCK_STREAMINFO: u8 = 0;
9pub(crate) const BLOCK_APPLICATION: u8 = 2;
10pub(crate) const BLOCK_SEEKTABLE: u8 = 3;
11pub(crate) const BLOCK_VORBIS_COMMENT: u8 = 4;
12pub(crate) const BLOCK_CUESHEET: u8 = 5;
13pub(crate) const BLOCK_PICTURE: u8 = 6;
14
15#[derive(Debug, Clone, PartialEq, Eq)]
17pub struct MetadataBlock {
18 pub block_type: u8,
19 pub body: Vec<u8>,
20}
21
22#[derive(Debug, Clone, PartialEq, Eq)]
24pub struct FlacScan {
25 pub audio_offset: u64,
26 pub audio_length: u64,
27 pub preserved: Vec<MetadataBlock>,
28}
29
30#[derive(Debug, Clone, PartialEq, Eq)]
34pub struct FlacMeta {
35 pub audio_offset: u64,
36 pub preserved: Vec<MetadataBlock>,
37}
38
39struct BlockHead {
41 index: usize,
43 block_type: u8,
45 len: usize,
47}
48
49enum BlockStep<'a> {
51 Ready(BlockHead, &'a [u8]),
53 Truncated(BlockHead, u64),
57 NeedHeader(u64),
59}
60
61struct BlockWalker<'a> {
67 data: &'a [u8],
68 pos: usize,
69 index: usize,
70 done: bool,
71}
72
73impl<'a> BlockWalker<'a> {
74 fn new(data: &'a [u8]) -> Result<Self> {
76 if data.len() < 4 || &data[0..4] != FLAC_MARKER {
77 return Err(FormatError::NotFlac);
78 }
79 Ok(Self {
80 data,
81 pos: 4,
82 index: 0,
83 done: false,
84 })
85 }
86
87 fn audio_offset(&self) -> u64 {
91 self.pos as u64
92 }
93
94 fn next_block(&mut self) -> Option<BlockStep<'a>> {
97 if self.done {
98 return None;
99 }
100 if self.pos + 4 > self.data.len() {
101 self.done = true;
103 return Some(BlockStep::NeedHeader((self.pos + 4) as u64));
104 }
105 let header = self.data[self.pos];
106 let is_last = (header & 0x80) != 0;
107 let block_type = header & 0x7F;
108 let len = u24_be(
109 self.data[self.pos + 1],
110 self.data[self.pos + 2],
111 self.data[self.pos + 3],
112 );
113 let head = BlockHead {
114 index: self.index,
115 block_type,
116 len,
117 };
118 let body_start = self.pos + 4;
119 let body_end = body_start + len;
120 if body_end > self.data.len() {
121 self.done = true;
122 return Some(BlockStep::Truncated(head, body_end as u64));
123 }
124 self.pos = body_end;
125 self.index += 1;
126 if is_last {
127 self.done = true;
128 }
129 Some(BlockStep::Ready(head, &self.data[body_start..body_end]))
130 }
131}
132
133fn parse_blocks(data: &[u8]) -> Result<FlacMeta> {
134 let mut walker = BlockWalker::new(data)?;
135 let mut preserved = Vec::new();
136 while let Some(step) = walker.next_block() {
137 match step {
138 BlockStep::Ready(head, body) => {
139 check_streaminfo_position(head.index, head.block_type, head.len)?;
140 if matches!(
141 head.block_type,
142 BLOCK_STREAMINFO | BLOCK_APPLICATION | BLOCK_SEEKTABLE | BLOCK_CUESHEET
143 ) {
144 preserved.push(MetadataBlock {
145 block_type: head.block_type,
146 body: body.to_vec(),
147 });
148 }
149 }
150 BlockStep::Truncated(..) | BlockStep::NeedHeader(_) => {
151 return Err(FormatError::Malformed);
152 }
153 }
154 }
155 Ok(FlacMeta {
156 audio_offset: walker.audio_offset(),
157 preserved,
158 })
159}
160
161pub fn read_metadata(data: &[u8]) -> Result<FlacMeta> {
165 parse_blocks(data)
166}
167
168pub fn read_metadata_bounded(prefix: &[u8]) -> Result<Extent<FlacMeta>> {
173 let mut walker = BlockWalker::new(prefix)?;
174 let mut preserved = Vec::new();
175 while let Some(step) = walker.next_block() {
176 match step {
177 BlockStep::Ready(head, body) => {
178 check_streaminfo_position(head.index, head.block_type, head.len)?;
179 if matches!(
180 head.block_type,
181 BLOCK_STREAMINFO | BLOCK_APPLICATION | BLOCK_SEEKTABLE | BLOCK_CUESHEET
182 ) {
183 preserved.push(MetadataBlock {
184 block_type: head.block_type,
185 body: body.to_vec(),
186 });
187 }
188 }
189 BlockStep::Truncated(head, up_to) => {
190 check_streaminfo_position(head.index, head.block_type, head.len)?;
194 return Ok(Extent::NeedMore { up_to });
195 }
196 BlockStep::NeedHeader(up_to) => {
197 return Ok(Extent::NeedMore { up_to });
198 }
199 }
200 }
201 Ok(Extent::Complete(FlacMeta {
202 audio_offset: walker.audio_offset(),
203 preserved,
204 }))
205}
206
207pub fn locate_audio(data: &[u8]) -> Result<FlacScan> {
210 let meta = parse_blocks(data)?;
211 Ok(FlacScan {
212 audio_offset: meta.audio_offset,
213 audio_length: data.len() as u64 - meta.audio_offset,
214 preserved: meta.preserved,
215 })
216}
217
218use crate::input::{
219 ArtInput, BinaryTagInput, EmbeddedBinaryTag, EmbeddedPicture, PictureType, TagInput,
220};
221use crate::layout::{RegionLayout, Segment};
222
223pub const MAX_BLOCK_BODY: u64 = 0x00FF_FFFF;
225
226const STREAMINFO_BODY_LEN: usize = 34;
229
230fn check_streaminfo_position(index: usize, block_type: u8, body_len: usize) -> Result<()> {
235 let is_streaminfo = block_type == BLOCK_STREAMINFO;
236 if index == 0 {
237 if !is_streaminfo || body_len != STREAMINFO_BODY_LEN {
238 return Err(FormatError::Malformed);
239 }
240 } else if is_streaminfo {
241 return Err(FormatError::Malformed);
242 }
243 Ok(())
244}
245
246pub(crate) fn push_block_header(
247 out: &mut Vec<u8>,
248 block_type: u8,
249 body_len: usize,
250 is_last: bool,
251) -> Result<()> {
252 let len = u32::try_from(body_len)
255 .ok()
256 .filter(|&v| u64::from(v) <= MAX_BLOCK_BODY)
257 .ok_or(FormatError::TooLarge)?;
258 let first = (if is_last { 0x80 } else { 0 }) | (block_type & 0x7F);
259 out.push(first);
260 out.extend_from_slice(&len.to_be_bytes()[1..]);
261 Ok(())
262}
263
264pub fn structural_block_type(kind: &str) -> Option<u8> {
268 match kind {
269 "STREAMINFO" => Some(BLOCK_STREAMINFO),
270 "SEEKTABLE" => Some(BLOCK_SEEKTABLE),
271 _ => None,
272 }
273}
274
275pub fn split_preserved(
282 blocks: &[MetadataBlock],
283) -> (Vec<(String, Vec<u8>)>, Vec<EmbeddedBinaryTag>) {
284 let mut structural = Vec::new();
285 let mut binary = Vec::new();
286 for blk in blocks {
287 match blk.block_type {
288 BLOCK_STREAMINFO => structural.push(("STREAMINFO".to_string(), blk.body.clone())),
289 BLOCK_SEEKTABLE => structural.push(("SEEKTABLE".to_string(), blk.body.clone())),
290 BLOCK_APPLICATION => binary.push(EmbeddedBinaryTag {
291 key: "APPLICATION".to_string(),
292 payload: blk.body.clone(),
293 }),
294 BLOCK_CUESHEET => binary.push(EmbeddedBinaryTag {
295 key: "CUESHEET".to_string(),
296 payload: blk.body.clone(),
297 }),
298 _ => {}
299 }
300 }
301 (structural, binary)
302}
303
304pub(crate) fn picture_body_framing(art: &ArtInput, description: &str) -> Result<Vec<u8>> {
310 let mut out = Vec::new();
311 out.extend_from_slice(&art.picture_type.get().to_be_bytes());
312 out.extend_from_slice(
313 &u32::try_from(art.mime.len())
314 .map_err(|_| FormatError::TooLarge)?
315 .to_be_bytes(),
316 );
317 out.extend_from_slice(art.mime.as_bytes());
318 out.extend_from_slice(
319 &u32::try_from(description.len())
320 .map_err(|_| FormatError::TooLarge)?
321 .to_be_bytes(),
322 );
323 out.extend_from_slice(description.as_bytes());
324 out.extend_from_slice(&art.width.to_be_bytes());
325 out.extend_from_slice(&art.height.to_be_bytes());
326 out.extend_from_slice(&0u32.to_be_bytes()); out.extend_from_slice(&0u32.to_be_bytes()); out.extend_from_slice(
329 &u32::try_from(art.data_len.get())
330 .map_err(|_| FormatError::TooLarge)?
331 .to_be_bytes(),
332 ); Ok(out)
334}
335
336pub fn synthesize_layout(
342 structural: &[MetadataBlock],
343 audio_offset: u64,
344 audio_length: u64,
345 tags: &[TagInput],
346 binary_tags: &[BinaryTagInput],
347 arts: &[ArtInput],
348) -> Result<RegionLayout> {
349 let streaminfo: Vec<&MetadataBlock> = structural
350 .iter()
351 .filter(|b| b.block_type == BLOCK_STREAMINFO)
352 .collect();
353 if streaminfo.len() != 1 || streaminfo[0].body.len() != STREAMINFO_BODY_LEN {
354 return Err(FormatError::Malformed);
355 }
356
357 let mut ordered: Vec<&MetadataBlock> = structural.iter().collect();
358 ordered.sort_by_key(|b| b.block_type);
359
360 let valid_binary: Vec<&BinaryTagInput> = binary_tags
361 .iter()
362 .filter(|bt| matches!(bt.key.as_str(), "APPLICATION" | "CUESHEET"))
363 .collect();
364
365 let nonempty_art = arts.len();
366 let num_blocks = ordered.len() + 1 + valid_binary.len() + nonempty_art;
367 let last_index = num_blocks - 1;
368
369 let mut segments: Vec<Segment> = Vec::new();
370 let mut buf: Vec<u8> = Vec::new();
371 buf.extend_from_slice(FLAC_MARKER);
372 let mut idx = 0usize;
373
374 for blk in &ordered {
375 push_block_header(&mut buf, blk.block_type, blk.body.len(), idx == last_index)?;
376 buf.extend_from_slice(&blk.body);
377 idx += 1;
378 }
379
380 let vc = crate::vorbiscomment::build(tags)?;
381 if vc.len() as u64 > MAX_BLOCK_BODY {
382 return Err(FormatError::TooLarge);
383 }
384 push_block_header(&mut buf, BLOCK_VORBIS_COMMENT, vc.len(), idx == last_index)?;
385 buf.extend_from_slice(&vc);
386 idx += 1;
387
388 for bt in valid_binary {
389 let block_type = match bt.key.as_str() {
390 "APPLICATION" => BLOCK_APPLICATION,
391 "CUESHEET" => BLOCK_CUESHEET,
392 _ => continue,
393 };
394 if bt.len.get() > MAX_BLOCK_BODY {
395 return Err(FormatError::TooLarge);
396 }
397 push_block_header(
398 &mut buf,
399 block_type,
400 crate::convert::usize_from(bt.len.get()),
401 idx == last_index,
402 )?;
403 segments.push(Segment::Inline(std::mem::take(&mut buf)));
404 segments.push(Segment::BinaryTag {
405 payload_id: bt.payload_id,
406 len: bt.len,
407 });
408 idx += 1;
409 }
410
411 for art in arts {
412 let framing = picture_body_framing(art, &art.description)?;
413 let body_len = size::checked_add(framing.len() as u64, art.data_len.get())?;
414 if body_len > MAX_BLOCK_BODY {
415 return Err(FormatError::TooLarge);
416 }
417 push_block_header(
418 &mut buf,
419 BLOCK_PICTURE,
420 crate::convert::usize_from(body_len),
421 idx == last_index,
422 )?;
423 buf.extend_from_slice(&framing);
424 segments.push(Segment::Inline(std::mem::take(&mut buf)));
425 segments.push(Segment::ArtImage {
426 art_id: art.art_id,
427 len: art.data_len,
428 });
429 idx += 1;
430 }
431
432 if !buf.is_empty() {
433 segments.push(Segment::Inline(buf));
434 }
435 segments.push(Segment::BackingAudio {
436 offset: audio_offset,
437 len: audio_length,
438 });
439
440 Ok(RegionLayout::validated(segments)?)
441}
442
443pub fn read_vorbis_comments(data: &[u8]) -> Result<Vec<(String, String)>> {
447 let mut walker = BlockWalker::new(data)?;
448 while let Some(step) = walker.next_block() {
449 match step {
450 BlockStep::Ready(head, body) => {
451 if head.block_type == BLOCK_VORBIS_COMMENT {
452 return crate::vorbiscomment::parse(body);
453 }
454 }
455 BlockStep::Truncated(..) | BlockStep::NeedHeader(_) => {
456 return Err(FormatError::Malformed);
457 }
458 }
459 }
460 Ok(Vec::new())
461}
462
463fn u24_be(b0: u8, b1: u8, b2: u8) -> usize {
465 u32::from_be_bytes([0, b0, b1, b2]) as usize
466}
467
468pub(crate) fn parse_picture_block(body: &[u8]) -> Result<EmbeddedPicture> {
469 let mut pos = 0usize;
470 let picture_type = read_u32_be(body, pos)?;
471 pos += 4;
472 let mime_len = read_u32_be(body, pos)? as usize;
473 pos += 4;
474 let mime_end = pos + mime_len;
475 if mime_end > body.len() {
476 return Err(FormatError::Malformed);
477 }
478 let mime = String::from_utf8_lossy(&body[pos..mime_end]).into_owned();
479 pos = mime_end;
480 let desc_len = read_u32_be(body, pos)? as usize;
481 pos += 4;
482 let desc_end = pos + desc_len;
483 if desc_end > body.len() {
484 return Err(FormatError::Malformed);
485 }
486 let description = String::from_utf8_lossy(&body[pos..desc_end]).into_owned();
487 pos = desc_end;
488 let width = read_u32_be(body, pos)?;
489 pos += 4;
490 let height = read_u32_be(body, pos)?;
491 pos += 4;
492 let _depth = read_u32_be(body, pos)?;
493 pos += 4;
494 let _colors = read_u32_be(body, pos)?;
495 pos += 4;
496 let data_len = read_u32_be(body, pos)? as usize;
497 pos += 4;
498 let data_end = pos + data_len;
499 if data_end > body.len() {
500 return Err(FormatError::Malformed);
501 }
502 Ok(EmbeddedPicture {
503 mime,
504 picture_type: PictureType::new(picture_type).unwrap_or(PictureType::ZERO),
505 description,
506 width,
507 height,
508 data: body[pos..data_end].to_vec(),
509 })
510}
511
512pub fn read_pictures(data: &[u8]) -> Result<Vec<EmbeddedPicture>> {
515 let mut walker = BlockWalker::new(data)?;
516 let mut out = Vec::new();
517 while let Some(step) = walker.next_block() {
518 match step {
519 BlockStep::Ready(head, body) => {
520 if head.block_type == BLOCK_PICTURE {
521 out.push(parse_picture_block(body)?);
522 }
523 }
524 BlockStep::Truncated(..) | BlockStep::NeedHeader(_) => {
525 return Err(FormatError::Malformed);
526 }
527 }
528 }
529 Ok(out)
530}
531
532#[cfg(test)]
533mod tests {
534 use super::*;
535 use crate::input::{BlobLen, PictureType};
536 use crate::probe::Extent;
537
538 fn flac_with_streaminfo(audio: &[u8]) -> (Vec<u8>, u64) {
541 let mut v = b"fLaC".to_vec();
542 push_block_header(&mut v, BLOCK_STREAMINFO, 34, true).unwrap();
543 v.extend(std::iter::repeat_n(0u8, 34));
544 let audio_offset = v.len() as u64;
545 v.extend_from_slice(audio);
546 (v, audio_offset)
547 }
548
549 #[test]
550 fn read_metadata_bounded_complete_when_prefix_covers_blocks() {
551 let (full, audio_offset) = flac_with_streaminfo(b"AUDIOAUDIO");
552 let prefix = &full[..crate::convert::usize_from(audio_offset) + 2];
554 match read_metadata_bounded(prefix).unwrap() {
555 Extent::Complete(meta) => assert_eq!(meta.audio_offset, audio_offset),
556 other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
557 }
558 }
559
560 #[test]
561 fn read_metadata_bounded_needmore_when_block_body_truncated() {
562 let (full, audio_offset) = flac_with_streaminfo(b"AUDIO");
563 let prefix = &full[..8];
565 match read_metadata_bounded(prefix).unwrap() {
566 Extent::NeedMore { up_to } => assert_eq!(up_to, audio_offset),
567 other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
568 }
569 }
570
571 #[test]
572 fn read_u32_be_assembles_big_endian_and_guards_length() {
573 let data = [0x11u8, 0x22, 0x33, 0x44, 0x55];
574 assert_eq!(read_u32_be(&data, 0).unwrap(), 0x1122_3344);
575 assert_eq!(read_u32_be(&data, 1).unwrap(), 0x2233_4455);
579 assert_eq!(read_u32_be(&data, 2), Err(FormatError::Malformed));
580 }
581
582 #[test]
583 fn push_block_header_emits_24bit_length_big_endian() {
584 let mut out = Vec::new();
586 push_block_header(&mut out, BLOCK_PICTURE, 0x12_3456, false).unwrap();
587 assert_eq!(out, vec![BLOCK_PICTURE, 0x12, 0x34, 0x56]);
588 let mut last = Vec::new();
590 push_block_header(&mut last, BLOCK_VORBIS_COMMENT, 0, true).unwrap();
591 assert_eq!(last, vec![0x80 | BLOCK_VORBIS_COMMENT, 0x00, 0x00, 0x00]);
592 }
593
594 fn raw_block(block_type: u8, body: &[u8], last: bool, len_override: Option<usize>) -> Vec<u8> {
599 let n = len_override.unwrap_or(body.len());
600 let mut v = vec![(if last { 0x80 } else { 0 }) | (block_type & 0x7F)];
601 v.extend_from_slice(&u32::try_from(n).unwrap().to_be_bytes()[1..]);
602 v.extend_from_slice(body);
603 v
604 }
605
606 fn flac_with(blocks: &[Vec<u8>]) -> Vec<u8> {
608 let mut f = b"fLaC".to_vec();
609 for b in blocks {
610 f.extend_from_slice(b);
611 }
612 f
613 }
614
615 fn valid_streaminfo() -> MetadataBlock {
617 MetadataBlock {
618 block_type: BLOCK_STREAMINFO,
619 body: vec![0u8; 34],
620 }
621 }
622
623 #[test]
624 fn locate_audio_rejects_missing_streaminfo() {
625 let file = flac_with(&[raw_block(BLOCK_VORBIS_COMMENT, &[], true, None)]);
627 assert_eq!(locate_audio(&file), Err(FormatError::Malformed));
628 }
629
630 #[test]
631 fn locate_audio_rejects_streaminfo_wrong_body_len() {
632 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &[0u8; 10], true, None)]);
634 assert_eq!(locate_audio(&file), Err(FormatError::Malformed));
635 }
636
637 #[test]
638 fn locate_audio_rejects_duplicate_streaminfo() {
639 let file = flac_with(&[
640 raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None),
641 raw_block(BLOCK_STREAMINFO, &[0u8; 34], true, None),
642 ]);
643 assert_eq!(locate_audio(&file), Err(FormatError::Malformed));
644 }
645
646 #[test]
647 fn read_metadata_bounded_rejects_duplicate_streaminfo() {
648 let file = flac_with(&[
649 raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None),
650 raw_block(BLOCK_STREAMINFO, &[0u8; 34], true, None),
651 ]);
652 assert_eq!(read_metadata_bounded(&file), Err(FormatError::Malformed));
653 }
654
655 #[test]
656 fn bounded_fails_closed_on_bad_first_header_before_widening() {
657 let mut prefix = b"fLaC".to_vec();
662 prefix.push(BLOCK_STREAMINFO | 0x80); prefix.extend_from_slice(&[0xFF, 0xFF, 0xFF]); assert_eq!(read_metadata_bounded(&prefix), Err(FormatError::Malformed));
665 }
666
667 #[test]
668 fn synthesize_layout_rejects_structural_without_streaminfo() {
669 let structural = [MetadataBlock {
671 block_type: BLOCK_SEEKTABLE,
672 body: vec![0u8; 4],
673 }];
674 assert_eq!(
675 synthesize_layout(&structural, 0, 0, &[], &[], &[]),
676 Err(FormatError::Malformed)
677 );
678 }
679
680 #[test]
681 fn parse_blocks_rejects_short_and_wrong_marker() {
682 assert_eq!(parse_blocks(b"fLa"), Err(FormatError::NotFlac));
685 assert_eq!(parse_blocks(b"fLaC"), Err(FormatError::Malformed));
688 assert_eq!(parse_blocks(b"XXXX____"), Err(FormatError::NotFlac));
689 }
690
691 #[test]
692 fn parse_blocks_guards_truncated_block_header() {
693 assert_eq!(parse_blocks(b"fLaC\x80"), Err(FormatError::Malformed));
697 }
698
699 #[test]
700 fn parse_blocks_accepts_header_flush_with_end() {
701 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &[0u8; 34], true, None)]);
705 let meta = parse_blocks(&file).unwrap();
706 assert_eq!(meta.audio_offset, 42); }
708
709 #[test]
710 fn parse_blocks_decodes_24bit_length_high_byte() {
711 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, Some(0x01_0000))]);
715 assert_eq!(parse_blocks(&file), Err(FormatError::Malformed));
716 }
717
718 #[test]
719 fn parse_blocks_preserves_structural_blocks() {
720 let si = vec![0xAA; 34];
722 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &si, true, None)]);
723 let meta = parse_blocks(&file).unwrap();
724 assert_eq!(meta.audio_offset, 4 + 4 + 34);
725 assert_eq!(meta.preserved.len(), 1);
726 assert_eq!(meta.preserved[0].block_type, BLOCK_STREAMINFO);
727 assert_eq!(meta.preserved[0].body, si);
728 }
729
730 fn vc_body(vendor: &str, comments: &[&str]) -> Vec<u8> {
733 let mut v = Vec::new();
734 v.extend_from_slice(&u32::try_from(vendor.len()).unwrap().to_le_bytes());
735 v.extend_from_slice(vendor.as_bytes());
736 v.extend_from_slice(&u32::try_from(comments.len()).unwrap().to_le_bytes());
737 for c in comments {
738 v.extend_from_slice(&u32::try_from(c.len()).unwrap().to_le_bytes());
739 v.extend_from_slice(c.as_bytes());
740 }
741 v
742 }
743
744 #[test]
745 fn read_vorbis_comments_returns_pairs_and_guards_marker() {
746 let vc = vc_body("v", &["TITLE=Hi", "ARTIST=Me"]);
750 let file = flac_with(&[raw_block(BLOCK_VORBIS_COMMENT, &vc, true, None)]);
751 let got = read_vorbis_comments(&file).unwrap();
752 assert_eq!(
753 got,
754 vec![
755 ("title".to_string(), "Hi".to_string()),
756 ("artist".to_string(), "Me".to_string()),
757 ]
758 );
759 assert_eq!(read_vorbis_comments(b"fLa"), Err(FormatError::NotFlac));
762 assert_eq!(read_vorbis_comments(b"fLaC"), Err(FormatError::Malformed));
764 }
765
766 #[test]
767 fn read_vorbis_comments_guards_block_walk() {
768 assert_eq!(
771 read_vorbis_comments(b"fLaC\x80"),
772 Err(FormatError::Malformed)
773 );
774 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, None)]);
777 assert_eq!(read_vorbis_comments(&file).unwrap(), Vec::new());
778 }
779
780 #[test]
781 fn read_vorbis_comments_decodes_24bit_length() {
782 let hi = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, Some(0x01_0000))]);
785 assert_eq!(read_vorbis_comments(&hi), Err(FormatError::Malformed));
786 let mid = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, Some(0x00_0100))]);
789 assert_eq!(read_vorbis_comments(&mid), Err(FormatError::Malformed));
790 }
791
792 fn picture_body(ptype: u32, mime: &str, desc: &str, w: u32, h: u32, data: &[u8]) -> Vec<u8> {
794 let mut v = Vec::new();
795 v.extend_from_slice(&ptype.to_be_bytes());
796 v.extend_from_slice(&u32::try_from(mime.len()).unwrap().to_be_bytes());
797 v.extend_from_slice(mime.as_bytes());
798 v.extend_from_slice(&u32::try_from(desc.len()).unwrap().to_be_bytes());
799 v.extend_from_slice(desc.as_bytes());
800 v.extend_from_slice(&w.to_be_bytes());
801 v.extend_from_slice(&h.to_be_bytes());
802 v.extend_from_slice(&0u32.to_be_bytes()); v.extend_from_slice(&0u32.to_be_bytes()); v.extend_from_slice(&u32::try_from(data.len()).unwrap().to_be_bytes());
805 v.extend_from_slice(data);
806 v
807 }
808
809 #[test]
810 fn parse_picture_block_roundtrips_fields() {
811 let body = picture_body(3, "image/png", "desc", 4, 5, b"PIXELS");
812 let p = parse_picture_block(&body).unwrap();
813 assert_eq!(p.picture_type.get(), 3);
814 assert_eq!(p.mime, "image/png");
815 assert_eq!(p.description, "desc");
816 assert_eq!(p.width, 4);
817 assert_eq!(p.height, 5);
818 assert_eq!(p.data, b"PIXELS");
819 }
820
821 #[test]
822 fn read_picture_clamps_out_of_range_type() {
823 let body = picture_body(99, "png", "", 0, 0, &[0xAB]);
824 let pic = parse_picture_block(&body).unwrap();
825 assert_eq!(pic.picture_type.get(), 0, "out-of-range type clamps to 0");
826 }
827
828 #[test]
829 fn parse_picture_block_guards_field_bounds() {
830 let mut bad_mime = 3u32.to_be_bytes().to_vec();
833 bad_mime.extend_from_slice(&16u32.to_be_bytes()); bad_mime.extend_from_slice(b"ab"); assert_eq!(parse_picture_block(&bad_mime), Err(FormatError::Malformed));
836
837 let mut bad_desc = 3u32.to_be_bytes().to_vec();
839 bad_desc.extend_from_slice(&3u32.to_be_bytes()); bad_desc.extend_from_slice(b"png");
841 bad_desc.extend_from_slice(&16u32.to_be_bytes()); bad_desc.extend_from_slice(b"x"); assert_eq!(parse_picture_block(&bad_desc), Err(FormatError::Malformed));
844
845 let mut trailing = picture_body(3, "png", "", 1, 1, b"DA");
849 trailing.push(0xFF); assert!(parse_picture_block(&trailing).is_ok());
851 }
852
853 #[test]
854 fn read_pictures_extracts_and_guards_marker() {
855 let pic = picture_body(3, "image/jpeg", "front", 8, 8, b"IMG");
857 let file = flac_with(&[raw_block(BLOCK_PICTURE, &pic, true, None)]);
858 let pics = read_pictures(&file).unwrap();
859 assert_eq!(pics.len(), 1);
860 assert_eq!(pics[0].mime, "image/jpeg");
861 assert_eq!(pics[0].data, b"IMG");
862 assert_eq!(read_pictures(b"fLa"), Err(FormatError::NotFlac));
864 assert_eq!(read_pictures(b"fLaC"), Err(FormatError::Malformed));
866 }
867
868 #[test]
869 fn read_pictures_guards_block_walk_and_length() {
870 assert_eq!(read_pictures(b"fLaC\x80"), Err(FormatError::Malformed));
872 let none = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, None)]);
874 assert_eq!(read_pictures(&none).unwrap(), Vec::new());
875 let hi = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, Some(0x01_0000))]);
877 assert_eq!(read_pictures(&hi), Err(FormatError::Malformed));
878 let mid = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, Some(0x00_0100))]);
880 assert_eq!(read_pictures(&mid), Err(FormatError::Malformed));
881 }
882
883 #[test]
886 fn bounded_rejects_short_and_wrong_marker() {
887 assert_eq!(read_metadata_bounded(b"fLa"), Err(FormatError::NotFlac));
892 match read_metadata_bounded(b"fLaC").unwrap() {
897 Extent::NeedMore { up_to } => assert_eq!(up_to, 8),
898 other @ Extent::Complete(_) => panic!("expected NeedMore{{up_to:8}}, got {other:?}"),
899 }
900 assert_eq!(read_metadata_bounded(b"XXXX"), Err(FormatError::NotFlac));
902 }
903
904 #[test]
905 fn bounded_needmore_up_to_is_pos_plus_4_for_truncated_header() {
906 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None)]);
910 assert_eq!(file.len(), 42);
912 match read_metadata_bounded(&file).unwrap() {
913 Extent::NeedMore { up_to } => assert_eq!(up_to, 46),
919 other @ Extent::Complete(_) => panic!("expected NeedMore{{up_to:46}}, got {other:?}"),
920 }
921 }
922
923 #[test]
924 fn bounded_is_last_flag_continues_past_nonlast_block() {
925 let b1 = raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None); let b2 = raw_block(BLOCK_SEEKTABLE, &[0xBB, 0xBB, 0xBB], true, None); let file = flac_with(&[b1, b2]);
930 let expected_offset = (4 + 38 + 7) as u64; match read_metadata_bounded(&file).unwrap() {
932 Extent::Complete(meta) => {
933 assert_eq!(meta.audio_offset, expected_offset);
936 assert_eq!(meta.preserved.len(), 2);
937 }
938 other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
939 }
940 }
941
942 #[test]
943 fn bounded_block_type_mask_preserves_streaminfo() {
944 let body = vec![0x5A; 34];
946 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &body, true, None)]);
947 match read_metadata_bounded(&file).unwrap() {
948 Extent::Complete(meta) => {
949 assert_eq!(meta.preserved.len(), 1);
955 assert_eq!(meta.preserved[0].block_type, BLOCK_STREAMINFO);
956 assert_eq!(meta.preserved[0].body, body);
957 }
958 other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
959 }
960 }
961
962 #[test]
963 fn bounded_decodes_24bit_length_exactly() {
964 let b1 = raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None); let len = 0x01_0203usize;
970 let body = vec![0u8; len];
971 let b2 = raw_block(BLOCK_SEEKTABLE, &body, true, None); let file = flac_with(&[b1, b2]);
973 let expected_offset = (4 + 38 + 4 + len) as u64;
974 match read_metadata_bounded(&file).unwrap() {
975 Extent::Complete(meta) => {
976 assert_eq!(meta.audio_offset, expected_offset);
980 }
981 other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
982 }
983 }
984
985 #[test]
986 fn bounded_length_decodes_high_and_mid_bytes() {
987 let b1 = raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None); let b2 = raw_block(BLOCK_SEEKTABLE, &[], true, Some(0x01_0100)); let file = flac_with(&[b1, b2]);
995 match read_metadata_bounded(&file).unwrap() {
996 Extent::NeedMore { up_to } => {
997 assert_eq!(up_to, 46 + 0x01_0100);
999 }
1000 other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
1001 }
1002 }
1003
1004 #[test]
1005 fn bounded_body_end_equal_to_prefix_is_complete() {
1006 let b1 = raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None); let b2 = raw_block(BLOCK_SEEKTABLE, &[0xCC; 6], true, None); let file = flac_with(&[b1, b2]);
1011 let total = file.len() as u64; match read_metadata_bounded(&file).unwrap() {
1013 Extent::Complete(meta) => assert_eq!(meta.audio_offset, total),
1017 other @ Extent::NeedMore { .. } => {
1018 panic!("expected Complete (exact fit), got {other:?}")
1019 }
1020 }
1021 }
1022
1023 #[test]
1024 fn bounded_preserves_all_structural_block_types() {
1025 let b_si = raw_block(BLOCK_STREAMINFO, &[0x01; 34], false, None);
1029 let b_app = raw_block(BLOCK_APPLICATION, &[0x02, 0x02], false, None);
1030 let b_seek = raw_block(BLOCK_SEEKTABLE, &[0x03, 0x03, 0x03], false, None);
1031 let b_cue = raw_block(BLOCK_CUESHEET, &[0x04], true, None);
1032 let file = flac_with(&[b_si, b_app, b_seek, b_cue]);
1033 match read_metadata_bounded(&file).unwrap() {
1034 Extent::Complete(meta) => {
1035 let types: Vec<u8> = meta.preserved.iter().map(|b| b.block_type).collect();
1036 assert_eq!(
1037 types,
1038 vec![
1039 BLOCK_STREAMINFO,
1040 BLOCK_APPLICATION,
1041 BLOCK_SEEKTABLE,
1042 BLOCK_CUESHEET,
1043 ]
1044 );
1045 assert_eq!(meta.preserved[0].body, vec![0x01; 34]);
1046 assert_eq!(meta.preserved[1].body, vec![0x02, 0x02]);
1047 assert_eq!(meta.preserved[2].body, vec![0x03, 0x03, 0x03]);
1048 assert_eq!(meta.preserved[3].body, vec![0x04]);
1049 }
1050 other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
1051 }
1052 }
1053
1054 #[test]
1055 fn split_preserved_classifies_structural_and_binary() {
1056 use super::{MetadataBlock, split_preserved, structural_block_type};
1057 let blocks = vec![
1059 MetadataBlock {
1060 block_type: 0,
1061 body: vec![0xAA],
1062 },
1063 MetadataBlock {
1064 block_type: 2,
1065 body: b"testDATA".to_vec(),
1066 },
1067 MetadataBlock {
1068 block_type: 3,
1069 body: vec![0xBB],
1070 },
1071 MetadataBlock {
1072 block_type: 5,
1073 body: vec![0xCC; 4],
1074 },
1075 ];
1076 let (structural, binary) = split_preserved(&blocks);
1077
1078 assert_eq!(
1079 structural,
1080 vec![
1081 ("STREAMINFO".to_string(), vec![0xAA]),
1082 ("SEEKTABLE".to_string(), vec![0xBB]),
1083 ]
1084 );
1085 assert_eq!(binary.len(), 2);
1086 assert_eq!(binary[0].key, "APPLICATION");
1087 assert_eq!(binary[0].payload, b"testDATA");
1088 assert_eq!(binary[1].key, "CUESHEET");
1089 assert_eq!(binary[1].payload, vec![0xCC; 4]);
1090
1091 assert_eq!(structural_block_type("STREAMINFO"), Some(0));
1092 assert_eq!(structural_block_type("SEEKTABLE"), Some(3));
1093 assert_eq!(structural_block_type("APPLICATION"), None);
1094 assert_eq!(structural_block_type("bogus"), None);
1095 }
1096
1097 #[test]
1098 fn synthesize_layout_picture_block_size_boundary_is_inclusive() {
1099 let mk = |data_len: u64| ArtInput {
1102 art_id: 1,
1103 mime: "image/png".to_string(),
1104 description: String::new(),
1105 picture_type: PictureType::new(3).unwrap(),
1106 width: 0,
1107 height: 0,
1108 data_len: BlobLen::new(data_len).unwrap(),
1109 };
1110 let art = mk(1);
1114 let framing_len = picture_body_framing(&art, &art.description).unwrap().len() as u64;
1115 let at_limit = 0x00FF_FFFF - framing_len; assert!(synthesize_layout(&[valid_streaminfo()], 0, 0, &[], &[], &[mk(at_limit)]).is_ok());
1119 assert_eq!(
1121 synthesize_layout(&[valid_streaminfo()], 0, 0, &[], &[], &[mk(at_limit + 1)]),
1122 Err(FormatError::TooLarge)
1123 );
1124 }
1125
1126 #[test]
1127 fn synthesize_layout_vorbis_comment_block_size_boundary_is_inclusive() {
1128 let overhead = crate::vorbiscomment::build(&[TagInput::new("title", "")])
1134 .unwrap()
1135 .len() as u64;
1136 let at_limit = "x".repeat(crate::convert::usize_from(MAX_BLOCK_BODY - overhead));
1137 let tags = [TagInput::new("title", at_limit.as_str())];
1138 assert!(synthesize_layout(&[valid_streaminfo()], 0, 0, &tags, &[], &[]).is_ok());
1139 let over = format!("{at_limit}x");
1141 let tags = [TagInput::new("title", over.as_str())];
1142 assert_eq!(
1143 synthesize_layout(&[valid_streaminfo()], 0, 0, &tags, &[], &[]),
1144 Err(FormatError::TooLarge)
1145 );
1146 }
1147
1148 #[test]
1149 fn synthesize_layout_checked_picture_len_rejects_overflow() {
1150 let mk = |data_len: u64| ArtInput {
1154 art_id: 1,
1155 mime: "image/png".to_string(),
1156 description: String::new(),
1157 picture_type: PictureType::new(3).unwrap(),
1158 width: 0,
1159 height: 0,
1160 data_len: BlobLen::new(data_len).unwrap(),
1161 };
1162 assert_eq!(
1163 synthesize_layout(&[valid_streaminfo()], 0, 0, &[], &[], &[mk(u64::MAX)]),
1164 Err(FormatError::TooLarge)
1165 );
1166 }
1167
1168 #[test]
1169 fn synthesize_layout_binary_tag_block_size_boundary_is_inclusive() {
1170 let mk = |len: u64| BinaryTagInput {
1175 key: "APPLICATION".to_string(),
1176 payload_id: 1,
1177 len: BlobLen::new(len).unwrap(),
1178 };
1179 assert!(
1181 synthesize_layout(&[valid_streaminfo()], 0, 0, &[], &[mk(0x00FF_FFFF)], &[]).is_ok()
1182 );
1183 assert_eq!(
1185 synthesize_layout(&[valid_streaminfo()], 0, 0, &[], &[mk(0x0100_0000)], &[]),
1186 Err(FormatError::TooLarge)
1187 );
1188 }
1189}