1use crate::error::{FormatError, Result};
2use crate::probe::Extent;
3use crate::size;
4
5pub(crate) const FLAC_MARKER: &[u8; 4] = b"fLaC";
6
7pub(crate) const BLOCK_STREAMINFO: u8 = 0;
8pub(crate) const BLOCK_APPLICATION: u8 = 2;
9pub(crate) const BLOCK_SEEKTABLE: u8 = 3;
10pub(crate) const BLOCK_VORBIS_COMMENT: u8 = 4;
11pub(crate) const BLOCK_CUESHEET: u8 = 5;
12pub(crate) const BLOCK_PICTURE: u8 = 6;
13
14#[derive(Debug, Clone, PartialEq, Eq)]
16pub struct MetadataBlock {
17 pub block_type: u8,
18 pub body: Vec<u8>,
19}
20
21#[derive(Debug, Clone, PartialEq, Eq)]
23pub struct FlacScan {
24 pub audio_offset: u64,
25 pub audio_length: u64,
26 pub preserved: Vec<MetadataBlock>,
27}
28
29#[derive(Debug, Clone, PartialEq, Eq)]
33pub struct FlacMeta {
34 pub audio_offset: u64,
35 pub preserved: Vec<MetadataBlock>,
36}
37
38fn parse_blocks(data: &[u8]) -> Result<FlacMeta> {
39 if data.len() < 4 || &data[0..4] != FLAC_MARKER {
40 return Err(FormatError::NotFlac);
41 }
42 let mut pos = 4usize;
43 let mut index = 0usize;
44 let mut preserved = Vec::new();
45 loop {
46 if pos + 4 > data.len() {
47 return Err(FormatError::Malformed);
48 }
49 let header = data[pos];
50 let is_last = (header & 0x80) != 0;
51 let block_type = header & 0x7F;
52 let len = u24_be(data[pos + 1], data[pos + 2], data[pos + 3]);
53 let body_start = pos + 4;
54 let body_end = body_start + len;
55 if body_end > data.len() {
56 return Err(FormatError::Malformed);
57 }
58 check_streaminfo_position(index, block_type, len)?;
59 match block_type {
60 BLOCK_STREAMINFO | BLOCK_APPLICATION | BLOCK_SEEKTABLE | BLOCK_CUESHEET => {
61 preserved.push(MetadataBlock {
62 block_type,
63 body: data[body_start..body_end].to_vec(),
64 });
65 }
66 _ => {}
67 }
68 pos = body_end;
69 index += 1;
70 if is_last {
71 break;
72 }
73 }
74 Ok(FlacMeta {
75 audio_offset: pos as u64,
76 preserved,
77 })
78}
79
80pub fn read_metadata(data: &[u8]) -> Result<FlacMeta> {
84 parse_blocks(data)
85}
86
87pub fn read_metadata_bounded(prefix: &[u8]) -> Result<Extent<FlacMeta>> {
92 if prefix.len() < 4 || &prefix[0..4] != FLAC_MARKER {
93 return Err(FormatError::NotFlac);
94 }
95 let mut pos = 4usize;
96 let mut index = 0usize;
97 let mut preserved = Vec::new();
98 loop {
99 if pos + 4 > prefix.len() {
100 return Ok(Extent::NeedMore {
102 up_to: (pos + 4) as u64,
103 });
104 }
105 let header = prefix[pos];
106 let is_last = (header & 0x80) != 0;
107 let block_type = header & 0x7F;
108 let len = u24_be(prefix[pos + 1], prefix[pos + 2], prefix[pos + 3]);
109 check_streaminfo_position(index, block_type, len)?;
113 let body_start = pos + 4;
114 let body_end = body_start + len;
115 if body_end > prefix.len() {
116 return Ok(Extent::NeedMore {
117 up_to: body_end as u64,
118 });
119 }
120 match block_type {
121 BLOCK_STREAMINFO | BLOCK_APPLICATION | BLOCK_SEEKTABLE | BLOCK_CUESHEET => {
122 preserved.push(MetadataBlock {
123 block_type,
124 body: prefix[body_start..body_end].to_vec(),
125 });
126 }
127 _ => {}
128 }
129 pos = body_end;
130 index += 1;
131 if is_last {
132 break;
133 }
134 }
135 Ok(Extent::Complete(FlacMeta {
136 audio_offset: pos as u64,
137 preserved,
138 }))
139}
140
141pub fn locate_audio(data: &[u8]) -> Result<FlacScan> {
144 let meta = parse_blocks(data)?;
145 Ok(FlacScan {
146 audio_offset: meta.audio_offset,
147 audio_length: data.len() as u64 - meta.audio_offset,
148 preserved: meta.preserved,
149 })
150}
151
152use crate::input::{
153 ArtInput, BinaryTagInput, EmbeddedBinaryTag, EmbeddedPicture, PictureType, TagInput,
154};
155use crate::layout::{RegionLayout, Segment};
156
157pub const MAX_BLOCK_BODY: u64 = 0x00FF_FFFF;
159
160const STREAMINFO_BODY_LEN: usize = 34;
163
164fn check_streaminfo_position(index: usize, block_type: u8, body_len: usize) -> Result<()> {
169 let is_streaminfo = block_type == BLOCK_STREAMINFO;
170 if index == 0 {
171 if !is_streaminfo || body_len != STREAMINFO_BODY_LEN {
172 return Err(FormatError::Malformed);
173 }
174 } else if is_streaminfo {
175 return Err(FormatError::Malformed);
176 }
177 Ok(())
178}
179
180pub(crate) fn push_block_header(
181 out: &mut Vec<u8>,
182 block_type: u8,
183 body_len: usize,
184 is_last: bool,
185) -> Result<()> {
186 let len = u32::try_from(body_len)
189 .ok()
190 .filter(|&v| u64::from(v) <= MAX_BLOCK_BODY)
191 .ok_or(FormatError::TooLarge)?;
192 let first = (if is_last { 0x80 } else { 0 }) | (block_type & 0x7F);
193 out.push(first);
194 out.extend_from_slice(&len.to_be_bytes()[1..]);
195 Ok(())
196}
197
198pub fn structural_block_type(kind: &str) -> Option<u8> {
202 match kind {
203 "STREAMINFO" => Some(BLOCK_STREAMINFO),
204 "SEEKTABLE" => Some(BLOCK_SEEKTABLE),
205 _ => None,
206 }
207}
208
209pub fn split_preserved(
216 blocks: &[MetadataBlock],
217) -> (Vec<(String, Vec<u8>)>, Vec<EmbeddedBinaryTag>) {
218 let mut structural = Vec::new();
219 let mut binary = Vec::new();
220 for blk in blocks {
221 match blk.block_type {
222 BLOCK_STREAMINFO => structural.push(("STREAMINFO".to_string(), blk.body.clone())),
223 BLOCK_SEEKTABLE => structural.push(("SEEKTABLE".to_string(), blk.body.clone())),
224 BLOCK_APPLICATION => binary.push(EmbeddedBinaryTag {
225 key: "APPLICATION".to_string(),
226 payload: blk.body.clone(),
227 }),
228 BLOCK_CUESHEET => binary.push(EmbeddedBinaryTag {
229 key: "CUESHEET".to_string(),
230 payload: blk.body.clone(),
231 }),
232 _ => {}
233 }
234 }
235 (structural, binary)
236}
237
238fn picture_body_framing(art: &ArtInput) -> Result<Vec<u8>> {
239 let mut out = Vec::new();
240 out.extend_from_slice(&art.picture_type.get().to_be_bytes());
241 out.extend_from_slice(
242 &u32::try_from(art.mime.len())
243 .map_err(|_| FormatError::TooLarge)?
244 .to_be_bytes(),
245 );
246 out.extend_from_slice(art.mime.as_bytes());
247 out.extend_from_slice(
248 &u32::try_from(art.description.len())
249 .map_err(|_| FormatError::TooLarge)?
250 .to_be_bytes(),
251 );
252 out.extend_from_slice(art.description.as_bytes());
253 out.extend_from_slice(&art.width.to_be_bytes());
254 out.extend_from_slice(&art.height.to_be_bytes());
255 out.extend_from_slice(&0u32.to_be_bytes()); out.extend_from_slice(&0u32.to_be_bytes()); out.extend_from_slice(
258 &u32::try_from(art.data_len.get())
259 .map_err(|_| FormatError::TooLarge)?
260 .to_be_bytes(),
261 ); Ok(out)
263}
264
265pub fn synthesize_layout(
271 structural: &[MetadataBlock],
272 audio_offset: u64,
273 audio_length: u64,
274 tags: &[TagInput],
275 binary_tags: &[BinaryTagInput],
276 arts: &[ArtInput],
277) -> Result<RegionLayout> {
278 let streaminfo: Vec<&MetadataBlock> = structural
279 .iter()
280 .filter(|b| b.block_type == BLOCK_STREAMINFO)
281 .collect();
282 if streaminfo.len() != 1 || streaminfo[0].body.len() != STREAMINFO_BODY_LEN {
283 return Err(FormatError::Malformed);
284 }
285
286 let mut ordered: Vec<&MetadataBlock> = structural.iter().collect();
287 ordered.sort_by_key(|b| b.block_type);
288
289 let valid_binary: Vec<&BinaryTagInput> = binary_tags
290 .iter()
291 .filter(|bt| matches!(bt.key.as_str(), "APPLICATION" | "CUESHEET"))
292 .collect();
293
294 let nonempty_art = arts.len();
295 let num_blocks = ordered.len() + 1 + valid_binary.len() + nonempty_art;
296 let last_index = num_blocks - 1;
297
298 let mut segments: Vec<Segment> = Vec::new();
299 let mut buf: Vec<u8> = Vec::new();
300 buf.extend_from_slice(FLAC_MARKER);
301 let mut idx = 0usize;
302
303 for blk in &ordered {
304 push_block_header(&mut buf, blk.block_type, blk.body.len(), idx == last_index)?;
305 buf.extend_from_slice(&blk.body);
306 idx += 1;
307 }
308
309 let vc = crate::vorbiscomment::build(tags)?;
310 if vc.len() as u64 > MAX_BLOCK_BODY {
311 return Err(FormatError::TooLarge);
312 }
313 push_block_header(&mut buf, BLOCK_VORBIS_COMMENT, vc.len(), idx == last_index)?;
314 buf.extend_from_slice(&vc);
315 idx += 1;
316
317 for bt in valid_binary {
318 let block_type = match bt.key.as_str() {
319 "APPLICATION" => BLOCK_APPLICATION,
320 "CUESHEET" => BLOCK_CUESHEET,
321 _ => continue,
322 };
323 if bt.len.get() > MAX_BLOCK_BODY {
324 return Err(FormatError::TooLarge);
325 }
326 push_block_header(
327 &mut buf,
328 block_type,
329 crate::convert::usize_from(bt.len.get()),
330 idx == last_index,
331 )?;
332 segments.push(Segment::Inline(std::mem::take(&mut buf)));
333 segments.push(Segment::BinaryTag {
334 payload_id: bt.payload_id,
335 len: bt.len,
336 });
337 idx += 1;
338 }
339
340 for art in arts {
341 let framing = picture_body_framing(art)?;
342 let body_len = size::checked_add(framing.len() as u64, art.data_len.get())?;
343 if body_len > MAX_BLOCK_BODY {
344 return Err(FormatError::TooLarge);
345 }
346 push_block_header(
347 &mut buf,
348 BLOCK_PICTURE,
349 crate::convert::usize_from(body_len),
350 idx == last_index,
351 )?;
352 buf.extend_from_slice(&framing);
353 segments.push(Segment::Inline(std::mem::take(&mut buf)));
354 segments.push(Segment::ArtImage {
355 art_id: art.art_id,
356 len: art.data_len,
357 });
358 idx += 1;
359 }
360
361 if !buf.is_empty() {
362 segments.push(Segment::Inline(buf));
363 }
364 segments.push(Segment::BackingAudio {
365 offset: audio_offset,
366 len: audio_length,
367 });
368
369 Ok(RegionLayout::validated(segments)?)
370}
371
372pub fn read_vorbis_comments(data: &[u8]) -> Result<Vec<(String, String)>> {
376 if data.len() < 4 || &data[0..4] != FLAC_MARKER {
377 return Err(FormatError::NotFlac);
378 }
379 let mut pos = 4usize;
380 loop {
381 if pos + 4 > data.len() {
382 return Err(FormatError::Malformed);
383 }
384 let header = data[pos];
385 let is_last = (header & 0x80) != 0;
386 let block_type = header & 0x7F;
387 let len = u24_be(data[pos + 1], data[pos + 2], data[pos + 3]);
388 let body_start = pos + 4;
389 let body_end = body_start + len;
390 if body_end > data.len() {
391 return Err(FormatError::Malformed);
392 }
393 if block_type == BLOCK_VORBIS_COMMENT {
394 return crate::vorbiscomment::parse(&data[body_start..body_end]);
395 }
396 pos = body_end;
397 if is_last {
398 break;
399 }
400 }
401 Ok(Vec::new())
402}
403
404fn u24_be(b0: u8, b1: u8, b2: u8) -> usize {
406 u32::from_be_bytes([0, b0, b1, b2]) as usize
407}
408
409pub(crate) fn read_u32_be(data: &[u8], pos: usize) -> Result<u32> {
410 if pos + 4 > data.len() {
411 return Err(FormatError::Malformed);
412 }
413 Ok(u32::from_be_bytes([
414 data[pos],
415 data[pos + 1],
416 data[pos + 2],
417 data[pos + 3],
418 ]))
419}
420
421pub(crate) fn parse_picture_block(body: &[u8]) -> Result<EmbeddedPicture> {
422 let mut pos = 0usize;
423 let picture_type = read_u32_be(body, pos)?;
424 pos += 4;
425 let mime_len = read_u32_be(body, pos)? as usize;
426 pos += 4;
427 let mime_end = pos + mime_len;
428 if mime_end > body.len() {
429 return Err(FormatError::Malformed);
430 }
431 let mime = String::from_utf8_lossy(&body[pos..mime_end]).into_owned();
432 pos = mime_end;
433 let desc_len = read_u32_be(body, pos)? as usize;
434 pos += 4;
435 let desc_end = pos + desc_len;
436 if desc_end > body.len() {
437 return Err(FormatError::Malformed);
438 }
439 let description = String::from_utf8_lossy(&body[pos..desc_end]).into_owned();
440 pos = desc_end;
441 let width = read_u32_be(body, pos)?;
442 pos += 4;
443 let height = read_u32_be(body, pos)?;
444 pos += 4;
445 let _depth = read_u32_be(body, pos)?;
446 pos += 4;
447 let _colors = read_u32_be(body, pos)?;
448 pos += 4;
449 let data_len = read_u32_be(body, pos)? as usize;
450 pos += 4;
451 let data_end = pos + data_len;
452 if data_end > body.len() {
453 return Err(FormatError::Malformed);
454 }
455 Ok(EmbeddedPicture {
456 mime,
457 picture_type: PictureType::new(picture_type).unwrap_or(PictureType::ZERO),
458 description,
459 width,
460 height,
461 data: body[pos..data_end].to_vec(),
462 })
463}
464
465pub fn read_pictures(data: &[u8]) -> Result<Vec<EmbeddedPicture>> {
468 if data.len() < 4 || &data[0..4] != FLAC_MARKER {
469 return Err(FormatError::NotFlac);
470 }
471 let mut pos = 4usize;
472 let mut out = Vec::new();
473 loop {
474 if pos + 4 > data.len() {
475 return Err(FormatError::Malformed);
476 }
477 let header = data[pos];
478 let is_last = (header & 0x80) != 0;
479 let block_type = header & 0x7F;
480 let len = u24_be(data[pos + 1], data[pos + 2], data[pos + 3]);
481 let body_start = pos + 4;
482 let body_end = body_start + len;
483 if body_end > data.len() {
484 return Err(FormatError::Malformed);
485 }
486 if block_type == BLOCK_PICTURE {
487 out.push(parse_picture_block(&data[body_start..body_end])?);
488 }
489 pos = body_end;
490 if is_last {
491 break;
492 }
493 }
494 Ok(out)
495}
496
497#[cfg(test)]
498mod tests {
499 use super::*;
500 use crate::input::{BlobLen, PictureType};
501 use crate::probe::Extent;
502
503 fn flac_with_streaminfo(audio: &[u8]) -> (Vec<u8>, u64) {
506 let mut v = b"fLaC".to_vec();
507 push_block_header(&mut v, BLOCK_STREAMINFO, 34, true).unwrap();
508 v.extend(std::iter::repeat_n(0u8, 34));
509 let audio_offset = v.len() as u64;
510 v.extend_from_slice(audio);
511 (v, audio_offset)
512 }
513
514 #[test]
515 fn read_metadata_bounded_complete_when_prefix_covers_blocks() {
516 let (full, audio_offset) = flac_with_streaminfo(b"AUDIOAUDIO");
517 let prefix = &full[..crate::convert::usize_from(audio_offset) + 2];
519 match read_metadata_bounded(prefix).unwrap() {
520 Extent::Complete(meta) => assert_eq!(meta.audio_offset, audio_offset),
521 other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
522 }
523 }
524
525 #[test]
526 fn read_metadata_bounded_needmore_when_block_body_truncated() {
527 let (full, audio_offset) = flac_with_streaminfo(b"AUDIO");
528 let prefix = &full[..8];
530 match read_metadata_bounded(prefix).unwrap() {
531 Extent::NeedMore { up_to } => assert_eq!(up_to, audio_offset),
532 other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
533 }
534 }
535
536 #[test]
537 fn read_u32_be_assembles_big_endian_and_guards_length() {
538 let data = [0x11u8, 0x22, 0x33, 0x44, 0x55];
539 assert_eq!(read_u32_be(&data, 0).unwrap(), 0x1122_3344);
540 assert_eq!(read_u32_be(&data, 1).unwrap(), 0x2233_4455);
544 assert_eq!(read_u32_be(&data, 2), Err(FormatError::Malformed));
545 }
546
547 #[test]
548 fn push_block_header_emits_24bit_length_big_endian() {
549 let mut out = Vec::new();
551 push_block_header(&mut out, BLOCK_PICTURE, 0x12_3456, false).unwrap();
552 assert_eq!(out, vec![BLOCK_PICTURE, 0x12, 0x34, 0x56]);
553 let mut last = Vec::new();
555 push_block_header(&mut last, BLOCK_VORBIS_COMMENT, 0, true).unwrap();
556 assert_eq!(last, vec![0x80 | BLOCK_VORBIS_COMMENT, 0x00, 0x00, 0x00]);
557 }
558
559 fn raw_block(block_type: u8, body: &[u8], last: bool, len_override: Option<usize>) -> Vec<u8> {
564 let n = len_override.unwrap_or(body.len());
565 let mut v = vec![(if last { 0x80 } else { 0 }) | (block_type & 0x7F)];
566 v.extend_from_slice(&u32::try_from(n).unwrap().to_be_bytes()[1..]);
567 v.extend_from_slice(body);
568 v
569 }
570
571 fn flac_with(blocks: &[Vec<u8>]) -> Vec<u8> {
573 let mut f = b"fLaC".to_vec();
574 for b in blocks {
575 f.extend_from_slice(b);
576 }
577 f
578 }
579
580 fn valid_streaminfo() -> MetadataBlock {
582 MetadataBlock {
583 block_type: BLOCK_STREAMINFO,
584 body: vec![0u8; 34],
585 }
586 }
587
588 #[test]
589 fn locate_audio_rejects_missing_streaminfo() {
590 let file = flac_with(&[raw_block(BLOCK_VORBIS_COMMENT, &[], true, None)]);
592 assert_eq!(locate_audio(&file), Err(FormatError::Malformed));
593 }
594
595 #[test]
596 fn locate_audio_rejects_streaminfo_wrong_body_len() {
597 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &[0u8; 10], true, None)]);
599 assert_eq!(locate_audio(&file), Err(FormatError::Malformed));
600 }
601
602 #[test]
603 fn locate_audio_rejects_duplicate_streaminfo() {
604 let file = flac_with(&[
605 raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None),
606 raw_block(BLOCK_STREAMINFO, &[0u8; 34], true, None),
607 ]);
608 assert_eq!(locate_audio(&file), Err(FormatError::Malformed));
609 }
610
611 #[test]
612 fn read_metadata_bounded_rejects_duplicate_streaminfo() {
613 let file = flac_with(&[
614 raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None),
615 raw_block(BLOCK_STREAMINFO, &[0u8; 34], true, None),
616 ]);
617 assert_eq!(read_metadata_bounded(&file), Err(FormatError::Malformed));
618 }
619
620 #[test]
621 fn bounded_fails_closed_on_bad_first_header_before_widening() {
622 let mut prefix = b"fLaC".to_vec();
627 prefix.push(BLOCK_STREAMINFO | 0x80); prefix.extend_from_slice(&[0xFF, 0xFF, 0xFF]); assert_eq!(read_metadata_bounded(&prefix), Err(FormatError::Malformed));
630 }
631
632 #[test]
633 fn synthesize_layout_rejects_structural_without_streaminfo() {
634 let structural = [MetadataBlock {
636 block_type: BLOCK_SEEKTABLE,
637 body: vec![0u8; 4],
638 }];
639 assert_eq!(
640 synthesize_layout(&structural, 0, 0, &[], &[], &[]),
641 Err(FormatError::Malformed)
642 );
643 }
644
645 #[test]
646 fn parse_blocks_rejects_short_and_wrong_marker() {
647 assert_eq!(parse_blocks(b"fLa"), Err(FormatError::NotFlac));
650 assert_eq!(parse_blocks(b"fLaC"), Err(FormatError::Malformed));
653 assert_eq!(parse_blocks(b"XXXX____"), Err(FormatError::NotFlac));
654 }
655
656 #[test]
657 fn parse_blocks_guards_truncated_block_header() {
658 assert_eq!(parse_blocks(b"fLaC\x80"), Err(FormatError::Malformed));
662 }
663
664 #[test]
665 fn parse_blocks_accepts_header_flush_with_end() {
666 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &[0u8; 34], true, None)]);
670 let meta = parse_blocks(&file).unwrap();
671 assert_eq!(meta.audio_offset, 42); }
673
674 #[test]
675 fn parse_blocks_decodes_24bit_length_high_byte() {
676 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, Some(0x01_0000))]);
680 assert_eq!(parse_blocks(&file), Err(FormatError::Malformed));
681 }
682
683 #[test]
684 fn parse_blocks_preserves_structural_blocks() {
685 let si = vec![0xAA; 34];
687 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &si, true, None)]);
688 let meta = parse_blocks(&file).unwrap();
689 assert_eq!(meta.audio_offset, 4 + 4 + 34);
690 assert_eq!(meta.preserved.len(), 1);
691 assert_eq!(meta.preserved[0].block_type, BLOCK_STREAMINFO);
692 assert_eq!(meta.preserved[0].body, si);
693 }
694
695 fn vc_body(vendor: &str, comments: &[&str]) -> Vec<u8> {
698 let mut v = Vec::new();
699 v.extend_from_slice(&u32::try_from(vendor.len()).unwrap().to_le_bytes());
700 v.extend_from_slice(vendor.as_bytes());
701 v.extend_from_slice(&u32::try_from(comments.len()).unwrap().to_le_bytes());
702 for c in comments {
703 v.extend_from_slice(&u32::try_from(c.len()).unwrap().to_le_bytes());
704 v.extend_from_slice(c.as_bytes());
705 }
706 v
707 }
708
709 #[test]
710 fn read_vorbis_comments_returns_pairs_and_guards_marker() {
711 let vc = vc_body("v", &["TITLE=Hi", "ARTIST=Me"]);
715 let file = flac_with(&[raw_block(BLOCK_VORBIS_COMMENT, &vc, true, None)]);
716 let got = read_vorbis_comments(&file).unwrap();
717 assert_eq!(
718 got,
719 vec![
720 ("title".to_string(), "Hi".to_string()),
721 ("artist".to_string(), "Me".to_string()),
722 ]
723 );
724 assert_eq!(read_vorbis_comments(b"fLa"), Err(FormatError::NotFlac));
727 assert_eq!(read_vorbis_comments(b"fLaC"), Err(FormatError::Malformed));
729 }
730
731 #[test]
732 fn read_vorbis_comments_guards_block_walk() {
733 assert_eq!(
736 read_vorbis_comments(b"fLaC\x80"),
737 Err(FormatError::Malformed)
738 );
739 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, None)]);
742 assert_eq!(read_vorbis_comments(&file).unwrap(), Vec::new());
743 }
744
745 #[test]
746 fn read_vorbis_comments_decodes_24bit_length() {
747 let hi = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, Some(0x01_0000))]);
750 assert_eq!(read_vorbis_comments(&hi), Err(FormatError::Malformed));
751 let mid = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, Some(0x00_0100))]);
754 assert_eq!(read_vorbis_comments(&mid), Err(FormatError::Malformed));
755 }
756
757 fn picture_body(ptype: u32, mime: &str, desc: &str, w: u32, h: u32, data: &[u8]) -> Vec<u8> {
759 let mut v = Vec::new();
760 v.extend_from_slice(&ptype.to_be_bytes());
761 v.extend_from_slice(&u32::try_from(mime.len()).unwrap().to_be_bytes());
762 v.extend_from_slice(mime.as_bytes());
763 v.extend_from_slice(&u32::try_from(desc.len()).unwrap().to_be_bytes());
764 v.extend_from_slice(desc.as_bytes());
765 v.extend_from_slice(&w.to_be_bytes());
766 v.extend_from_slice(&h.to_be_bytes());
767 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());
770 v.extend_from_slice(data);
771 v
772 }
773
774 #[test]
775 fn parse_picture_block_roundtrips_fields() {
776 let body = picture_body(3, "image/png", "desc", 4, 5, b"PIXELS");
777 let p = parse_picture_block(&body).unwrap();
778 assert_eq!(p.picture_type.get(), 3);
779 assert_eq!(p.mime, "image/png");
780 assert_eq!(p.description, "desc");
781 assert_eq!(p.width, 4);
782 assert_eq!(p.height, 5);
783 assert_eq!(p.data, b"PIXELS");
784 }
785
786 #[test]
787 fn read_picture_clamps_out_of_range_type() {
788 let body = picture_body(99, "png", "", 0, 0, &[0xAB]);
789 let pic = parse_picture_block(&body).unwrap();
790 assert_eq!(pic.picture_type.get(), 0, "out-of-range type clamps to 0");
791 }
792
793 #[test]
794 fn parse_picture_block_guards_field_bounds() {
795 let mut bad_mime = 3u32.to_be_bytes().to_vec();
798 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));
801
802 let mut bad_desc = 3u32.to_be_bytes().to_vec();
804 bad_desc.extend_from_slice(&3u32.to_be_bytes()); bad_desc.extend_from_slice(b"png");
806 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));
809
810 let mut trailing = picture_body(3, "png", "", 1, 1, b"DA");
814 trailing.push(0xFF); assert!(parse_picture_block(&trailing).is_ok());
816 }
817
818 #[test]
819 fn read_pictures_extracts_and_guards_marker() {
820 let pic = picture_body(3, "image/jpeg", "front", 8, 8, b"IMG");
822 let file = flac_with(&[raw_block(BLOCK_PICTURE, &pic, true, None)]);
823 let pics = read_pictures(&file).unwrap();
824 assert_eq!(pics.len(), 1);
825 assert_eq!(pics[0].mime, "image/jpeg");
826 assert_eq!(pics[0].data, b"IMG");
827 assert_eq!(read_pictures(b"fLa"), Err(FormatError::NotFlac));
829 assert_eq!(read_pictures(b"fLaC"), Err(FormatError::Malformed));
831 }
832
833 #[test]
834 fn read_pictures_guards_block_walk_and_length() {
835 assert_eq!(read_pictures(b"fLaC\x80"), Err(FormatError::Malformed));
837 let none = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, None)]);
839 assert_eq!(read_pictures(&none).unwrap(), Vec::new());
840 let hi = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, Some(0x01_0000))]);
842 assert_eq!(read_pictures(&hi), Err(FormatError::Malformed));
843 let mid = flac_with(&[raw_block(BLOCK_STREAMINFO, &[], true, Some(0x00_0100))]);
845 assert_eq!(read_pictures(&mid), Err(FormatError::Malformed));
846 }
847
848 #[test]
851 fn bounded_rejects_short_and_wrong_marker() {
852 assert_eq!(read_metadata_bounded(b"fLa"), Err(FormatError::NotFlac));
857 match read_metadata_bounded(b"fLaC").unwrap() {
862 Extent::NeedMore { up_to } => assert_eq!(up_to, 8),
863 other @ Extent::Complete(_) => panic!("expected NeedMore{{up_to:8}}, got {other:?}"),
864 }
865 assert_eq!(read_metadata_bounded(b"XXXX"), Err(FormatError::NotFlac));
867 }
868
869 #[test]
870 fn bounded_needmore_up_to_is_pos_plus_4_for_truncated_header() {
871 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None)]);
875 assert_eq!(file.len(), 42);
877 match read_metadata_bounded(&file).unwrap() {
878 Extent::NeedMore { up_to } => assert_eq!(up_to, 46),
884 other @ Extent::Complete(_) => panic!("expected NeedMore{{up_to:46}}, got {other:?}"),
885 }
886 }
887
888 #[test]
889 fn bounded_is_last_flag_continues_past_nonlast_block() {
890 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]);
895 let expected_offset = (4 + 38 + 7) as u64; match read_metadata_bounded(&file).unwrap() {
897 Extent::Complete(meta) => {
898 assert_eq!(meta.audio_offset, expected_offset);
901 assert_eq!(meta.preserved.len(), 2);
902 }
903 other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
904 }
905 }
906
907 #[test]
908 fn bounded_block_type_mask_preserves_streaminfo() {
909 let body = vec![0x5A; 34];
911 let file = flac_with(&[raw_block(BLOCK_STREAMINFO, &body, true, None)]);
912 match read_metadata_bounded(&file).unwrap() {
913 Extent::Complete(meta) => {
914 assert_eq!(meta.preserved.len(), 1);
920 assert_eq!(meta.preserved[0].block_type, BLOCK_STREAMINFO);
921 assert_eq!(meta.preserved[0].body, body);
922 }
923 other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
924 }
925 }
926
927 #[test]
928 fn bounded_decodes_24bit_length_exactly() {
929 let b1 = raw_block(BLOCK_STREAMINFO, &[0u8; 34], false, None); let len = 0x01_0203usize;
935 let body = vec![0u8; len];
936 let b2 = raw_block(BLOCK_SEEKTABLE, &body, true, None); let file = flac_with(&[b1, b2]);
938 let expected_offset = (4 + 38 + 4 + len) as u64;
939 match read_metadata_bounded(&file).unwrap() {
940 Extent::Complete(meta) => {
941 assert_eq!(meta.audio_offset, expected_offset);
945 }
946 other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
947 }
948 }
949
950 #[test]
951 fn bounded_length_decodes_high_and_mid_bytes() {
952 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]);
960 match read_metadata_bounded(&file).unwrap() {
961 Extent::NeedMore { up_to } => {
962 assert_eq!(up_to, 46 + 0x01_0100);
964 }
965 other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
966 }
967 }
968
969 #[test]
970 fn bounded_body_end_equal_to_prefix_is_complete() {
971 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]);
976 let total = file.len() as u64; match read_metadata_bounded(&file).unwrap() {
978 Extent::Complete(meta) => assert_eq!(meta.audio_offset, total),
982 other @ Extent::NeedMore { .. } => {
983 panic!("expected Complete (exact fit), got {other:?}")
984 }
985 }
986 }
987
988 #[test]
989 fn bounded_preserves_all_structural_block_types() {
990 let b_si = raw_block(BLOCK_STREAMINFO, &[0x01; 34], false, None);
994 let b_app = raw_block(BLOCK_APPLICATION, &[0x02, 0x02], false, None);
995 let b_seek = raw_block(BLOCK_SEEKTABLE, &[0x03, 0x03, 0x03], false, None);
996 let b_cue = raw_block(BLOCK_CUESHEET, &[0x04], true, None);
997 let file = flac_with(&[b_si, b_app, b_seek, b_cue]);
998 match read_metadata_bounded(&file).unwrap() {
999 Extent::Complete(meta) => {
1000 let types: Vec<u8> = meta.preserved.iter().map(|b| b.block_type).collect();
1001 assert_eq!(
1002 types,
1003 vec![
1004 BLOCK_STREAMINFO,
1005 BLOCK_APPLICATION,
1006 BLOCK_SEEKTABLE,
1007 BLOCK_CUESHEET,
1008 ]
1009 );
1010 assert_eq!(meta.preserved[0].body, vec![0x01; 34]);
1011 assert_eq!(meta.preserved[1].body, vec![0x02, 0x02]);
1012 assert_eq!(meta.preserved[2].body, vec![0x03, 0x03, 0x03]);
1013 assert_eq!(meta.preserved[3].body, vec![0x04]);
1014 }
1015 other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
1016 }
1017 }
1018
1019 #[test]
1020 fn split_preserved_classifies_structural_and_binary() {
1021 use super::{MetadataBlock, split_preserved, structural_block_type};
1022 let blocks = vec![
1024 MetadataBlock {
1025 block_type: 0,
1026 body: vec![0xAA],
1027 },
1028 MetadataBlock {
1029 block_type: 2,
1030 body: b"testDATA".to_vec(),
1031 },
1032 MetadataBlock {
1033 block_type: 3,
1034 body: vec![0xBB],
1035 },
1036 MetadataBlock {
1037 block_type: 5,
1038 body: vec![0xCC; 4],
1039 },
1040 ];
1041 let (structural, binary) = split_preserved(&blocks);
1042
1043 assert_eq!(
1044 structural,
1045 vec![
1046 ("STREAMINFO".to_string(), vec![0xAA]),
1047 ("SEEKTABLE".to_string(), vec![0xBB]),
1048 ]
1049 );
1050 assert_eq!(binary.len(), 2);
1051 assert_eq!(binary[0].key, "APPLICATION");
1052 assert_eq!(binary[0].payload, b"testDATA");
1053 assert_eq!(binary[1].key, "CUESHEET");
1054 assert_eq!(binary[1].payload, vec![0xCC; 4]);
1055
1056 assert_eq!(structural_block_type("STREAMINFO"), Some(0));
1057 assert_eq!(structural_block_type("SEEKTABLE"), Some(3));
1058 assert_eq!(structural_block_type("APPLICATION"), None);
1059 assert_eq!(structural_block_type("bogus"), None);
1060 }
1061
1062 #[test]
1063 fn synthesize_layout_picture_block_size_boundary_is_inclusive() {
1064 let mk = |data_len: u64| ArtInput {
1067 art_id: 1,
1068 mime: "image/png".to_string(),
1069 description: String::new(),
1070 picture_type: PictureType::new(3).unwrap(),
1071 width: 0,
1072 height: 0,
1073 data_len: BlobLen::new(data_len).unwrap(),
1074 };
1075 let framing_len = picture_body_framing(&mk(1)).unwrap().len() as u64;
1079 let at_limit = 0x00FF_FFFF - framing_len; assert!(synthesize_layout(&[valid_streaminfo()], 0, 0, &[], &[], &[mk(at_limit)]).is_ok());
1083 assert_eq!(
1085 synthesize_layout(&[valid_streaminfo()], 0, 0, &[], &[], &[mk(at_limit + 1)]),
1086 Err(FormatError::TooLarge)
1087 );
1088 }
1089
1090 #[test]
1091 fn synthesize_layout_vorbis_comment_block_size_boundary_is_inclusive() {
1092 let overhead = crate::vorbiscomment::build(&[TagInput::new("title", "")])
1098 .unwrap()
1099 .len() as u64;
1100 let at_limit = "x".repeat(crate::convert::usize_from(MAX_BLOCK_BODY - overhead));
1101 let tags = [TagInput::new("title", at_limit.as_str())];
1102 assert!(synthesize_layout(&[valid_streaminfo()], 0, 0, &tags, &[], &[]).is_ok());
1103 let over = format!("{at_limit}x");
1105 let tags = [TagInput::new("title", over.as_str())];
1106 assert_eq!(
1107 synthesize_layout(&[valid_streaminfo()], 0, 0, &tags, &[], &[]),
1108 Err(FormatError::TooLarge)
1109 );
1110 }
1111
1112 #[test]
1113 fn synthesize_layout_checked_picture_len_rejects_overflow() {
1114 let mk = |data_len: u64| ArtInput {
1118 art_id: 1,
1119 mime: "image/png".to_string(),
1120 description: String::new(),
1121 picture_type: PictureType::new(3).unwrap(),
1122 width: 0,
1123 height: 0,
1124 data_len: BlobLen::new(data_len).unwrap(),
1125 };
1126 assert_eq!(
1127 synthesize_layout(&[valid_streaminfo()], 0, 0, &[], &[], &[mk(u64::MAX)]),
1128 Err(FormatError::TooLarge)
1129 );
1130 }
1131
1132 #[test]
1133 fn synthesize_layout_binary_tag_block_size_boundary_is_inclusive() {
1134 let mk = |len: u64| BinaryTagInput {
1139 key: "APPLICATION".to_string(),
1140 payload_id: 1,
1141 len: BlobLen::new(len).unwrap(),
1142 };
1143 assert!(
1145 synthesize_layout(&[valid_streaminfo()], 0, 0, &[], &[mk(0x00FF_FFFF)], &[]).is_ok()
1146 );
1147 assert_eq!(
1149 synthesize_layout(&[valid_streaminfo()], 0, 0, &[], &[mk(0x0100_0000)], &[]),
1150 Err(FormatError::TooLarge)
1151 );
1152 }
1153}