mod art_source;
mod b64;
mod crc;
mod page;
pub use art_source::{ArtSource, MapArtSource};
pub use b64::{B64Window, b64_len, b64_len_checked, b64_window, encode_b64_slice};
pub use page::{
PageHeader, parse_page, patch_page_header, patch_page_header_algebraic, verify_page_crc,
};
use crate::error::{FormatError, Result};
use crate::probe::Extent;
use crate::size;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Codec {
Opus,
Vorbis,
OggFlac,
}
const METADATA_BLOCK_PICTURE_KEY: &[u8] = b"METADATA_BLOCK_PICTURE=";
fn detect_codec(first_packet: &[u8]) -> Result<Codec> {
if first_packet.len() >= 8 && &first_packet[0..8] == b"OpusHead" {
Ok(Codec::Opus)
} else if first_packet.len() >= 7 && &first_packet[0..7] == b"\x01vorbis" {
Ok(Codec::Vorbis)
} else if first_packet.len() >= 5 && &first_packet[0..5] == b"\x7FFLAC" {
Ok(Codec::OggFlac)
} else {
Err(FormatError::Malformed)
}
}
fn oggflac_following_packets(first_packet: &[u8]) -> Result<usize> {
if first_packet.len() < 9 {
return Err(FormatError::Malformed);
}
Ok(u16::from_be_bytes([first_packet[7], first_packet[8]]) as usize)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OggHeader {
pub codec: Codec,
pub serial: u32,
pub packets: Vec<Vec<u8>>,
pub header_pages: u32,
pub audio_offset: u64,
}
fn validate_single_bitstream(data: &[u8], audio_offset: u64, serial: u32) -> Result<()> {
let mut pos = 0usize;
let mut first = true;
while (pos as u64) < audio_offset {
let h = crate::ogg::page::parse_page(data, pos)?;
if h.serial != serial {
return Err(FormatError::Malformed);
}
if !first && (h.header_type & crate::ogg::page::FLAG_BOS) != 0 {
return Err(FormatError::Malformed);
}
first = false;
pos += h.total_len();
}
Ok(())
}
pub fn read_header(data: &[u8]) -> Result<OggHeader> {
let first_page = page::parse_page(data, 0)?;
let serial = first_page.serial;
let first = page::read_packets(data, 1)?;
let first_pkt = first.first().ok_or(FormatError::Malformed)?;
let codec = detect_codec(&first_pkt.data)?;
let want = match codec {
Codec::Opus => 2,
Codec::Vorbis => 3,
Codec::OggFlac => 1 + oggflac_following_packets(&first_pkt.data)?,
};
let pkts = page::read_packets(data, want)?;
if pkts.len() != want {
return Err(FormatError::Malformed);
}
let last = pkts.last().unwrap();
let audio_offset = last.end_offset as u64;
validate_single_bitstream(data, audio_offset, serial)?;
Ok(OggHeader {
codec,
serial,
packets: pkts.iter().map(|p| p.data.clone()).collect(),
header_pages: last.pages_through_end,
audio_offset,
})
}
fn comment_body(codec: Codec, packet: &[u8]) -> Result<&[u8]> {
let prefix = match codec {
Codec::Opus => 8, Codec::Vorbis => 7, Codec::OggFlac => 4, };
if packet.len() < prefix {
return Err(FormatError::Malformed);
}
Ok(&packet[prefix..])
}
fn comment_packet_index(header: &OggHeader) -> usize {
match header.codec {
Codec::Opus | Codec::Vorbis => 1,
Codec::OggFlac => header
.packets
.iter()
.enumerate()
.skip(1)
.find(|(_, p)| !p.is_empty() && (p[0] & 0x7F) == 4)
.map_or(0, |(i, _)| i),
}
}
pub fn read_tags(data: &[u8]) -> Result<Vec<(String, String)>> {
let header = read_header(data)?;
let idx = comment_packet_index(&header);
if idx == 0 {
return Ok(Vec::new()); }
let body = comment_body(header.codec, &header.packets[idx])?;
let mut tags = crate::vorbiscomment::parse(body)?;
tags.retain(|(field, _)| !field.eq_ignore_ascii_case("METADATA_BLOCK_PICTURE"));
Ok(tags)
}
use crate::input::EmbeddedPicture;
pub fn read_pictures(data: &[u8]) -> Result<Vec<EmbeddedPicture>> {
use base64::Engine;
let header = read_header(data)?;
let mut out = Vec::new();
match header.codec {
Codec::Opus | Codec::Vorbis => {
let idx = comment_packet_index(&header);
if idx == 0 {
return Ok(out);
}
let body = comment_body(header.codec, &header.packets[idx])?;
for (field, value) in crate::vorbiscomment::parse(body)? {
if field.eq_ignore_ascii_case("METADATA_BLOCK_PICTURE") {
let raw = base64::engine::general_purpose::STANDARD
.decode(value.as_bytes())
.map_err(|_| FormatError::Malformed)?;
out.push(crate::flac::parse_picture_block(&raw)?);
}
}
}
Codec::OggFlac => {
for pkt in header.packets.iter().skip(1) {
if pkt.len() >= 4 && (pkt[0] & 0x7F) == 6 {
out.push(crate::flac::parse_picture_block(&pkt[4..])?);
}
}
}
}
Ok(out)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OggScan {
pub codec: Codec,
pub audio_offset: u64,
pub audio_length: u64,
}
pub fn locate_audio(data: &[u8]) -> Result<OggScan> {
let header = read_header(data)?;
if header.audio_offset > data.len() as u64 {
return Err(FormatError::Malformed);
}
Ok(OggScan {
codec: header.codec,
audio_offset: header.audio_offset,
audio_length: data.len() as u64 - header.audio_offset,
})
}
pub fn read_metadata(front: &[u8]) -> Result<OggHeader> {
read_header(front)
}
pub fn read_metadata_bounded(prefix: &[u8], file_len: u64) -> Result<Extent<OggHeader>> {
match read_header(prefix) {
Ok(header) => Ok(Extent::Complete(header)),
Err(_) if (prefix.len() as u64) < file_len => {
let grown = ((prefix.len() as u64).saturating_mul(2)).max(64 * 1024);
Ok(Extent::NeedMore {
up_to: grown.min(file_len),
})
}
Err(e) => Err(e),
}
}
use crate::input::TagInput;
use crate::layout::{RegionLayout, Segment};
pub fn synthesize_layout(
header: &OggHeader,
audio_offset: u64,
audio_length: u64,
tags: &[TagInput],
arts: &[OggArt],
src: &dyn ArtSource,
) -> Result<RegionLayout> {
let arts: Vec<OggArt> = arts.to_vec();
let packet_chunks = build_packets_with_art(header, tags, &arts)?;
let mut segments: Vec<Segment> = Vec::new();
let mut seq = 0u32;
for (i, chunks) in packet_chunks.iter().enumerate() {
let (segs, used) =
crate::ogg::page::lace_chunks_to_segments(header.serial, seq, i == 0, chunks, src)?;
segments.extend(segs);
seq += used;
}
let seq_delta = i64::from(seq) - i64::from(header.header_pages);
segments.push(Segment::OggAudio {
offset: audio_offset,
len: audio_length,
seq_delta,
});
Ok(RegionLayout::validated(segments)?)
}
fn picture_prefix(art: &crate::input::ArtInput) -> Result<Vec<u8>> {
let base = 32 + art.mime.len() + art.description.len();
let pad = (3 - base % 3) % 3;
let description = format!("{}{}", art.description, " ".repeat(pad));
crate::flac::picture_body_framing(art, &description)
}
use crate::ogg::page::PayloadChunk;
use base64::Engine;
#[derive(Clone, Copy)]
pub struct OggArt<'a> {
pub meta: &'a crate::input::ArtInput,
}
fn b64_encode(bytes: &[u8]) -> Vec<u8> {
base64::engine::general_purpose::STANDARD
.encode(bytes)
.into_bytes()
}
fn build_packets_with_art(
header: &OggHeader,
tags: &[TagInput],
arts: &[OggArt],
) -> Result<Vec<Vec<PayloadChunk>>> {
match header.codec {
Codec::Opus | Codec::Vorbis => {
for a in arts {
let prefix = picture_prefix(a.meta)?;
let b64_prefix_len =
b64_len_checked(prefix.len() as u64).ok_or(FormatError::TooLarge)?;
let b64_image_len =
b64_len_checked(a.meta.data_len.get()).ok_or(FormatError::TooLarge)?;
let value_len = size::checked_sum([
METADATA_BLOCK_PICTURE_KEY.len() as u64,
b64_prefix_len,
b64_image_len,
])?;
if value_len > u64::from(u32::MAX) {
return Err(FormatError::TooLarge);
}
}
if header.codec == Codec::Opus {
Ok(vec![
vec![PayloadChunk::Bytes(header.packets[0].clone())],
comment_packet_chunks(b"OpusTags", tags, arts, false)?,
])
} else {
Ok(vec![
vec![PayloadChunk::Bytes(header.packets[0].clone())],
comment_packet_chunks(b"\x03vorbis", tags, arts, true)?,
vec![PayloadChunk::Bytes(header.packets[2].clone())],
])
}
}
Codec::OggFlac => oggflac_packets_with_art(header, tags, arts),
}
}
fn comment_packet_chunks(
magic: &[u8],
tags: &[TagInput],
arts: &[OggArt],
framing_bit: bool,
) -> Result<Vec<PayloadChunk>> {
let text_body = crate::vorbiscomment::build(tags)?; let vendor_len = u32::from_le_bytes(text_body[0..4].try_into().unwrap()) as usize;
let count_pos = 4 + vendor_len;
let text_count = u32::from_le_bytes(text_body[count_pos..count_pos + 4].try_into().unwrap());
let mut leading = text_body.clone();
let new_count = text_count + u32::try_from(arts.len()).map_err(|_| FormatError::TooLarge)?;
leading[count_pos..count_pos + 4].copy_from_slice(&new_count.to_le_bytes());
let mut chunks: Vec<PayloadChunk> = Vec::new();
let mut head = magic.to_vec();
head.extend_from_slice(&leading);
for art in arts {
let prefix = picture_prefix(art.meta)?;
let b64_prefix = b64_encode(&prefix);
let b64_image_len =
b64_len_checked(art.meta.data_len.get()).ok_or(FormatError::TooLarge)?;
let value_len = size::checked_sum([
METADATA_BLOCK_PICTURE_KEY.len() as u64,
b64_prefix.len() as u64,
b64_image_len,
])?;
head.extend_from_slice(
&u32::try_from(value_len)
.map_err(|_| FormatError::TooLarge)?
.to_le_bytes(),
);
head.extend_from_slice(METADATA_BLOCK_PICTURE_KEY);
head.extend_from_slice(&b64_prefix);
chunks.push(PayloadChunk::Bytes(std::mem::take(&mut head)));
chunks.push(PayloadChunk::Art {
art_id: art.meta.art_id,
base64: true,
art_total: art.meta.data_len.get(),
});
}
if framing_bit {
head.push(0x01);
}
if !head.is_empty() {
chunks.push(PayloadChunk::Bytes(head));
}
Ok(chunks)
}
fn oggflac_packets_with_art(
header: &OggHeader,
tags: &[TagInput],
arts: &[OggArt],
) -> Result<Vec<Vec<PayloadChunk>>> {
if header.packets.is_empty() {
return Err(FormatError::Malformed);
}
let mut structural: Vec<Vec<u8>> = Vec::new();
for pkt in header.packets.iter().skip(1) {
if !pkt.is_empty() && matches!(pkt[0] & 0x7F, 2 | 3 | 5) {
structural.push(pkt.clone());
}
}
let vc = crate::vorbiscomment::build(tags)?;
if vc.len() as u64 > crate::flac::MAX_BLOCK_BODY {
return Err(FormatError::TooLarge);
}
let mut comment = Vec::new();
crate::flac::push_block_header(&mut comment, 4, vc.len(), false)?;
comment.extend_from_slice(&vc);
let following_count = structural.len() + 1 + arts.len();
let count = u16::try_from(following_count).map_err(|_| FormatError::TooLarge)?;
let mut block_packets: Vec<Vec<PayloadChunk>> = Vec::new();
for s in &structural {
block_packets.push(vec![PayloadChunk::Bytes(s.clone())]);
}
block_packets.push(vec![PayloadChunk::Bytes(comment)]);
for art in arts {
let prefix = picture_prefix(art.meta)?;
let body_len = size::checked_add(prefix.len() as u64, art.meta.data_len.get())?;
if body_len > crate::flac::MAX_BLOCK_BODY {
return Err(FormatError::TooLarge);
}
let mut blk = Vec::new();
crate::flac::push_block_header(&mut blk, 6, crate::convert::usize_from(body_len), false)?;
blk.extend_from_slice(&prefix);
block_packets.push(vec![
PayloadChunk::Bytes(blk),
PayloadChunk::Art {
art_id: art.meta.art_id,
base64: false,
art_total: art.meta.data_len.get(),
},
]);
}
let n = block_packets.len();
for (i, bp) in block_packets.iter_mut().enumerate() {
if let Some(PayloadChunk::Bytes(b)) = bp.first_mut() {
if i + 1 == n {
b[0] |= 0x80;
} else {
b[0] &= 0x7F;
}
}
}
let mut mapping = header.packets[0].clone();
if mapping.len() < 9 {
return Err(FormatError::Malformed);
}
mapping[7..9].copy_from_slice(&count.to_be_bytes());
let mut out = vec![vec![PayloadChunk::Bytes(mapping)]];
out.extend(block_packets);
Ok(out)
}
#[doc(hidden)]
pub mod page_test_support {
pub use crate::ogg::page::{build_header as build_header_pub, lace_packet as lace_packet_pub};
pub fn vorbis_body_empty() -> Vec<u8> {
crate::vorbiscomment::build(&[]).unwrap()
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ogg::page::{build_header, lace_packet};
fn opus_headers() -> Vec<u8> {
let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
let tags = b"OpusTags\x06\x00\x00\x00musefs\x00\x00\x00\x00".to_vec();
let (bytes, _) = build_header(0x1234, &[&head, &tags]);
bytes
}
#[test]
fn locate_audio_reports_bounds() {
let mut data = opus_headers();
let header_len = data.len();
let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 120]);
data.extend_from_slice(&audio);
let scan = locate_audio(&data).unwrap();
assert_eq!(scan.codec, Codec::Opus);
assert_eq!(scan.audio_offset, header_len as u64);
assert_eq!(scan.audio_length, (data.len() - header_len) as u64);
}
#[test]
fn reads_opus_header() {
let mut data = opus_headers();
let (audio, _) = lace_packet(0x1234, 2, false, 960, &[0u8; 100]);
let header_len = data.len();
data.extend_from_slice(&audio);
let h = read_header(&data).unwrap();
assert_eq!(h.codec, Codec::Opus);
assert_eq!(h.serial, 0x1234);
assert_eq!(h.packets.len(), 2);
assert_eq!(h.audio_offset, header_len as u64);
assert_eq!(h.header_pages, 2);
}
#[test]
fn oggflac_following_packets_accepts_minimal_9_byte_packet() {
let mut pkt = [0u8; 9];
pkt[7] = 0x00;
pkt[8] = 0x03; assert_eq!(oggflac_following_packets(&pkt).unwrap(), 3);
}
#[test]
fn comment_body_accepts_packet_with_empty_body() {
assert!(comment_body(Codec::Opus, b"OpusTags").unwrap().is_empty());
}
#[test]
fn read_tags_opus() {
let body =
crate::vorbiscomment::build(&[crate::input::TagInput::new("title", "Sun")]).unwrap();
let mut tags_pkt = b"OpusTags".to_vec();
tags_pkt.extend_from_slice(&body);
let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
let (mut data, _) = crate::ogg::page::build_header(7, &[&head, &tags_pkt]);
let (audio, _) = crate::ogg::page::lace_packet(7, 2, false, 960, &[0u8; 50]);
data.extend_from_slice(&audio);
let tags = read_tags(&data).unwrap();
assert_eq!(tags, vec![("title".to_string(), "Sun".to_string())]);
}
#[test]
fn read_tags_excludes_metadata_block_picture() {
let mut block = Vec::new();
block.extend_from_slice(&3u32.to_be_bytes()); block.extend_from_slice(&9u32.to_be_bytes());
block.extend_from_slice(b"image/png");
block.extend_from_slice(&0u32.to_be_bytes()); block.extend_from_slice(&1u32.to_be_bytes()); block.extend_from_slice(&1u32.to_be_bytes()); block.extend_from_slice(&8u32.to_be_bytes()); block.extend_from_slice(&0u32.to_be_bytes()); block.extend_from_slice(&1u32.to_be_bytes()); block.push(0xAB);
let pic_value = base64::engine::general_purpose::STANDARD.encode(&block);
let body = crate::vorbiscomment::build(&[
crate::input::TagInput::new("title", "Sun"),
crate::input::TagInput::new("METADATA_BLOCK_PICTURE", &pic_value),
])
.unwrap();
let mut tags_pkt = b"OpusTags".to_vec();
tags_pkt.extend_from_slice(&body);
let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
let (mut data, _) = crate::ogg::page::build_header(7, &[&head, &tags_pkt]);
let (audio, _) = crate::ogg::page::lace_packet(7, 2, false, 960, &[0u8; 50]);
data.extend_from_slice(&audio);
let tags = read_tags(&data).unwrap();
assert_eq!(tags, vec![("title".to_string(), "Sun".to_string())]);
let pics = read_pictures(&data).unwrap();
assert_eq!(pics.len(), 1);
assert_eq!(pics[0].data, vec![0xAB]);
}
#[test]
fn synthesize_opus_emits_valid_header_and_audio_segment() {
let mut data = opus_headers();
let scan = locate_audio({
let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 80]);
data.extend_from_slice(&audio);
&data
})
.unwrap();
let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
let layout = synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[TagInput::new("album", "Geogaddi")],
&[],
&MapArtSource::default(),
)
.unwrap();
let mut header_bytes: Vec<u8> = Vec::new();
let mut audio_seg = None;
for seg in layout.segments() {
match seg {
Segment::Inline(b) => header_bytes.extend_from_slice(b),
Segment::OggAudio { offset, len, .. } => {
audio_seg = Some((*offset, *len));
break;
}
other => panic!("unexpected segment {other:?}"),
}
}
let h = read_header(&header_bytes).unwrap();
assert_eq!(h.codec, Codec::Opus);
let body = comment_body(Codec::Opus, &h.packets[1]).unwrap();
let tags = crate::vorbiscomment::parse(body).unwrap();
assert_eq!(tags, vec![("album".to_string(), "Geogaddi".to_string())]);
let (offset, len) = audio_seg.expect("expected OggAudio segment");
assert_eq!(offset, scan.audio_offset);
assert_eq!(len, scan.audio_length);
}
#[test]
fn synthesize_emits_nonzero_seq_delta_when_header_page_count_changes() {
let mut data = opus_headers();
let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 80]);
data.extend_from_slice(&audio);
let scan = locate_audio(&data).unwrap();
let mut header =
read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
let baseline = synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[],
&[],
&MapArtSource::default(),
)
.unwrap();
let synth_pages = baseline
.segments()
.iter()
.filter(|s| matches!(s, Segment::Inline(_)))
.count();
assert!(synth_pages >= 1);
let original_pages = u32::try_from(synth_pages).unwrap() + 3;
header.header_pages = original_pages;
let layout = synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[],
&[],
&MapArtSource::default(),
)
.unwrap();
let delta = layout
.segments()
.iter()
.find_map(|s| match s {
Segment::OggAudio { seq_delta, .. } => Some(*seq_delta),
_ => None,
})
.expect("expected an OggAudio segment");
assert_eq!(
delta,
i64::try_from(synth_pages).unwrap() - i64::from(original_pages),
"seq_delta must be synthesized pages minus original header pages"
);
assert_eq!(delta, -3);
}
fn vorbis_headers_with(setup: &[u8]) -> Vec<u8> {
let mut id = b"\x01vorbis".to_vec();
id.extend_from_slice(&0u32.to_le_bytes()); id.push(2); id.extend_from_slice(&44100u32.to_le_bytes()); id.extend_from_slice(&0u32.to_le_bytes()); id.extend_from_slice(&128_000u32.to_le_bytes()); id.extend_from_slice(&0u32.to_le_bytes()); id.push(0xB8); id.push(0x01); let mut comment = b"\x03vorbis".to_vec();
comment.extend_from_slice(&crate::vorbiscomment::build(&[]).unwrap());
comment.push(0x01);
let (bytes, _) = crate::ogg::page::build_header(55, &[&id, &comment, setup]);
bytes
}
#[test]
fn synthesize_vorbis_preserves_setup_and_rewrites_comment() {
let setup = b"\x05vorbis-SETUP-CODEBOOKS-PLACEHOLDER".to_vec();
let mut data = vorbis_headers_with(&setup);
let (audio, _) = crate::ogg::page::lace_packet(55, 99, false, 1024, &[0u8; 64]);
data.extend_from_slice(&audio);
let scan = locate_audio(&data).unwrap();
assert_eq!(scan.codec, Codec::Vorbis);
let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
assert_eq!(header.packets[2], setup);
let layout = synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[TagInput::new("artist", "Autechre")],
&[],
&MapArtSource::default(),
)
.unwrap();
let mut header_bytes: Vec<u8> = Vec::new();
for seg in layout.segments() {
match seg {
Segment::Inline(b) => header_bytes.extend_from_slice(b),
Segment::OggAudio { .. } => break,
other => panic!("unexpected segment {other:?}"),
}
}
let h = read_header(&header_bytes).unwrap();
assert_eq!(h.codec, Codec::Vorbis);
assert_eq!(h.packets[2], setup); let body = comment_body(Codec::Vorbis, &h.packets[1]).unwrap();
let tags = crate::vorbiscomment::parse(body).unwrap();
assert_eq!(tags, vec![("artist".to_string(), "Autechre".to_string())]);
}
#[test]
fn read_pictures_opus_decodes_metadata_block_picture() {
use base64::Engine;
let mut pic = Vec::new();
pic.extend_from_slice(&3u32.to_be_bytes());
let mime = b"image/png";
pic.extend_from_slice(&u32::try_from(mime.len()).unwrap().to_be_bytes());
pic.extend_from_slice(mime);
pic.extend_from_slice(&0u32.to_be_bytes()); pic.extend_from_slice(&1u32.to_be_bytes()); pic.extend_from_slice(&1u32.to_be_bytes()); pic.extend_from_slice(&0u32.to_be_bytes()); pic.extend_from_slice(&0u32.to_be_bytes()); let img = b"PNG";
pic.extend_from_slice(&u32::try_from(img.len()).unwrap().to_be_bytes());
pic.extend_from_slice(img);
let b64 = base64::engine::general_purpose::STANDARD.encode(&pic);
let mut body = Vec::new();
body.extend_from_slice(
&u32::try_from(crate::vorbiscomment::VENDOR.len())
.unwrap()
.to_le_bytes(),
);
body.extend_from_slice(crate::vorbiscomment::VENDOR.as_bytes());
body.extend_from_slice(&1u32.to_le_bytes()); let comment = format!("METADATA_BLOCK_PICTURE={b64}");
body.extend_from_slice(&u32::try_from(comment.len()).unwrap().to_le_bytes());
body.extend_from_slice(comment.as_bytes());
let mut tags_pkt = b"OpusTags".to_vec();
tags_pkt.extend_from_slice(&body);
let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
let (mut data, _) = crate::ogg::page::build_header(7, &[&head, &tags_pkt]);
let (audio, _) = crate::ogg::page::lace_packet(7, 2, false, 960, &[0u8; 50]);
data.extend_from_slice(&audio);
let pics = read_pictures(&data).unwrap();
assert_eq!(pics.len(), 1);
assert_eq!(pics[0].mime, "image/png");
assert_eq!(pics[0].data, b"PNG");
}
#[test]
fn read_pictures_oggflac_short_picture_packet_does_not_panic() {
let mut mapping = vec![0x7F];
mapping.extend_from_slice(b"FLAC");
mapping.push(1);
mapping.push(0);
mapping.extend_from_slice(&1u16.to_be_bytes()); mapping.extend_from_slice(b"fLaC");
let mut streaminfo = Vec::new();
crate::flac::push_block_header(&mut streaminfo, 0, 34, false).unwrap();
streaminfo.extend(std::iter::repeat_n(0u8, 34));
mapping.extend_from_slice(&streaminfo);
let short_picture = vec![0x06u8];
let (data, _) = crate::ogg::page::build_header(77, &[&mapping, &short_picture]);
assert_eq!(read_header(&data).unwrap().codec, Codec::OggFlac);
assert!(read_pictures(&data).unwrap().is_empty());
}
fn oggflac_headers() -> Vec<u8> {
let mut streaminfo = Vec::new();
crate::flac::push_block_header(&mut streaminfo, 0, 34, false).unwrap();
streaminfo.extend(std::iter::repeat_n(0u8, 34));
let mut mapping = vec![0x7F];
mapping.extend_from_slice(b"FLAC");
mapping.push(1);
mapping.push(0);
mapping.extend_from_slice(&2u16.to_be_bytes()); mapping.extend_from_slice(b"fLaC");
mapping.extend_from_slice(&streaminfo);
let mut seektable = Vec::new();
crate::flac::push_block_header(&mut seektable, 3, 18, false).unwrap();
seektable.extend(std::iter::repeat_n(0xEEu8, 18));
let mut old_vc = Vec::new();
let body = crate::vorbiscomment::build(&[crate::input::TagInput::new("x", "old")]).unwrap();
crate::flac::push_block_header(&mut old_vc, 4, body.len(), true).unwrap();
old_vc.extend_from_slice(&body);
let (bytes, _) = crate::ogg::page::build_header(77, &[&mapping, &seektable, &old_vc]);
bytes
}
#[test]
fn rejects_multiplexed_second_bitstream() {
let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
let (mut data, _) = crate::ogg::page::lace_packet(0x1111, 0, true, 0, &head);
let (other, _) = crate::ogg::page::lace_packet(
0x2222,
0,
true,
0,
b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".as_ref(),
);
data.extend_from_slice(&other);
let (audio, _) = crate::ogg::page::lace_packet(0x1111, 1, false, 960, &[0u8; 50]);
data.extend_from_slice(&audio);
assert!(read_header(&data).is_err());
assert!(locate_audio(&data).is_err());
}
#[test]
fn synthesize_oggflac_keeps_seektable_replaces_comment_and_count() {
let mut data = oggflac_headers();
let (audio, _) = crate::ogg::page::lace_packet(77, 3, false, 4096, &[0u8; 64]);
data.extend_from_slice(&audio);
let scan = locate_audio(&data).unwrap();
assert_eq!(scan.codec, Codec::OggFlac);
let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
let layout = synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[TagInput::new("title", "Kaini Industries")],
&[],
&MapArtSource::default(),
)
.unwrap();
let mut header_bytes: Vec<u8> = Vec::new();
for seg in layout.segments() {
match seg {
Segment::Inline(b) => header_bytes.extend_from_slice(b),
Segment::OggAudio { .. } => break,
other => panic!("unexpected segment {other:?}"),
}
}
let h = read_header(&header_bytes).unwrap();
assert_eq!(h.codec, Codec::OggFlac);
assert_eq!(u16::from_be_bytes([h.packets[0][7], h.packets[0][8]]), 2);
assert!(h.packets.iter().skip(1).any(|p| (p[0] & 0x7F) == 3));
let vc = h
.packets
.iter()
.skip(1)
.find(|p| (p[0] & 0x7F) == 4)
.unwrap();
assert_eq!(vc[0] & 0x80, 0x80);
let tags = crate::vorbiscomment::parse(&vc[4..]).unwrap();
assert_eq!(
tags,
vec![("title".to_string(), "Kaini Industries".to_string())]
);
}
#[test]
fn synthesize_opus_embeds_art_that_round_trips() {
let mut data = opus_headers();
let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 80]);
data.extend_from_slice(&audio);
let scan = locate_audio(&data).unwrap();
let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
let image: Vec<u8> = (0..5000u32).map(|i| (i % 251) as u8).collect();
let meta = crate::input::ArtInput {
art_id: 7,
mime: "image/jpeg".to_string(),
description: String::new(),
picture_type: crate::input::PictureType::new(3).unwrap(),
width: 64,
height: 64,
data_len: crate::input::BlobLen::new(image.len() as u64).unwrap(),
};
let src = MapArtSource::new([(meta.art_id, image.clone())]);
let layout = synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[TagInput::new("title", "Cover")],
&[OggArt { meta: &meta }],
&src,
)
.unwrap();
let mut bytes = Vec::new();
for s in layout.segments() {
match s {
Segment::Inline(b) => bytes.extend_from_slice(b),
Segment::OggArtSlice {
offset,
len,
base64,
art_total,
..
} => {
assert!(*base64);
let w = b64_window(*offset, len.get(), *art_total);
let raw = &image[crate::convert::usize_from(w.in_start)
..crate::convert::usize_from(w.in_start + w.in_len)];
bytes.extend_from_slice(
&encode_b64_slice(raw, w.skip, crate::convert::usize_from(len.get()))
.expect("window lies within the encoded output"),
);
}
Segment::OggAudio { .. } => break, other => panic!("unexpected {other:?}"),
}
}
let pics = read_pictures(&bytes).unwrap();
assert_eq!(pics.len(), 1);
assert_eq!(pics[0].mime, "image/jpeg");
assert_eq!(pics[0].data, image);
let h = read_header(&bytes).unwrap();
assert_eq!(h.codec, Codec::Opus);
}
fn materialize_header(layout: &RegionLayout, images: &[(i64, &[u8])]) -> Vec<u8> {
let mut bytes = Vec::new();
for s in layout.segments() {
match s {
Segment::Inline(b) => bytes.extend_from_slice(b),
Segment::OggArtSlice {
art_id,
offset,
len,
base64,
art_total,
} => {
let img = images.iter().find(|(id, _)| id == art_id).expect("image").1;
if *base64 {
let w = b64_window(*offset, len.get(), *art_total);
let raw = &img[crate::convert::usize_from(w.in_start)
..crate::convert::usize_from(w.in_start + w.in_len)];
bytes.extend_from_slice(
&encode_b64_slice(raw, w.skip, crate::convert::usize_from(len.get()))
.expect("window lies within the encoded output"),
);
} else {
bytes.extend_from_slice(
&img[crate::convert::usize_from(*offset)
..crate::convert::usize_from(*offset + len.get())],
);
}
}
Segment::OggAudio { .. } => break,
other => panic!("unexpected {other:?}"),
}
}
bytes
}
fn art_input(art_id: i64, mime: &str, len: usize) -> crate::input::ArtInput {
crate::input::ArtInput {
art_id,
mime: mime.to_string(),
description: String::new(),
picture_type: crate::input::PictureType::new(3).unwrap(),
width: 10,
height: 10,
data_len: crate::input::BlobLen::new(len as u64).unwrap(),
}
}
#[test]
fn synthesize_vorbis_embeds_art_that_round_trips() {
let setup = b"\x05vorbis-SETUP".to_vec();
let mut data = vorbis_headers_with(&setup);
let (audio, _) = crate::ogg::page::lace_packet(55, 99, false, 1024, &[0u8; 64]);
data.extend_from_slice(&audio);
let scan = locate_audio(&data).unwrap();
let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
let image: Vec<u8> = (0..4000u32).map(|i| (i % 251) as u8).collect();
let meta = art_input(11, "image/png", image.len());
let src = MapArtSource::new([(meta.art_id, image.clone())]);
let layout = synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[TagInput::new("artist", "X")],
&[OggArt { meta: &meta }],
&src,
)
.unwrap();
let bytes = materialize_header(&layout, &[(11, &image)]);
let h = read_header(&bytes).unwrap();
assert_eq!(h.codec, Codec::Vorbis);
assert_eq!(h.packets[2], setup); let pics = read_pictures(&bytes).unwrap();
assert_eq!(pics.len(), 1);
assert_eq!(pics[0].data, image);
}
#[test]
fn synthesize_oggflac_embeds_art_that_round_trips() {
let mut data = oggflac_headers();
let (audio, _) = crate::ogg::page::lace_packet(77, 3, false, 4096, &[0u8; 64]);
data.extend_from_slice(&audio);
let scan = locate_audio(&data).unwrap();
let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
let image: Vec<u8> = (0..4000u32).map(|i| (i % 251) as u8).collect();
let meta = art_input(22, "image/png", image.len());
let src = MapArtSource::new([(meta.art_id, image.clone())]);
let layout = synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[TagInput::new("title", "Y")],
&[OggArt { meta: &meta }],
&src,
)
.unwrap();
let bytes = materialize_header(&layout, &[(22, &image)]);
let h = read_header(&bytes).unwrap();
assert_eq!(h.codec, Codec::OggFlac);
let pics = read_pictures(&bytes).unwrap();
assert_eq!(pics.len(), 1);
assert_eq!(pics[0].data, image);
}
#[test]
fn synthesize_oggflac_embeds_large_art_spanning_pages_round_trips() {
let mut data = oggflac_headers();
let (audio, _) = crate::ogg::page::lace_packet(77, 3, false, 4096, &[0u8; 64]);
data.extend_from_slice(&audio);
let scan = locate_audio(&data).unwrap();
let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
let image: Vec<u8> = (0..200_000u32).map(|i| (i % 251) as u8).collect();
let meta = art_input(31, "image/png", image.len());
let src = MapArtSource::new([(meta.art_id, image.clone())]);
let layout = synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[TagInput::new("title", "Big")],
&[OggArt { meta: &meta }],
&src,
)
.unwrap();
let art_slices = layout
.segments()
.iter()
.filter(|s| matches!(s, Segment::OggArtSlice { base64: false, .. }))
.count();
assert!(
art_slices >= 2,
"expected the raw art to span multiple pages, got {art_slices} slice(s)"
);
let bytes = materialize_header(&layout, &[(31, &image)]);
let h = read_header(&bytes).unwrap();
assert_eq!(h.codec, Codec::OggFlac);
let pics = read_pictures(&bytes).unwrap();
assert_eq!(pics.len(), 1);
assert_eq!(
pics[0].data, image,
"large art must round-trip byte-for-byte"
);
}
#[test]
fn synthesize_opus_embeds_multiple_images() {
let mut data = opus_headers();
let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 64]);
data.extend_from_slice(&audio);
let scan = locate_audio(&data).unwrap();
let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
let img_a: Vec<u8> = (0..3000u32).map(|i| (i % 251) as u8).collect();
let img_b: Vec<u8> = (0..1500u32).map(|i| ((i * 3) % 251) as u8).collect();
let meta_a = art_input(1, "image/png", img_a.len());
let meta_b = art_input(2, "image/jpeg", img_b.len());
let src = MapArtSource::new([
(meta_a.art_id, img_a.clone()),
(meta_b.art_id, img_b.clone()),
]);
let layout = synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[TagInput::new("title", "Multi")],
&[OggArt { meta: &meta_a }, OggArt { meta: &meta_b }],
&src,
)
.unwrap();
let bytes = materialize_header(&layout, &[(1, &img_a), (2, &img_b)]);
let h = read_header(&bytes).unwrap();
assert_eq!(h.codec, Codec::Opus);
let pics = read_pictures(&bytes).unwrap();
assert_eq!(pics.len(), 2);
assert_eq!(pics[0].data, img_a);
assert_eq!(pics[1].data, img_b);
}
#[test]
fn oversized_full_art_value_rejected_by_build_packets() {
let meta = crate::input::ArtInput {
art_id: 0,
mime: "image/jpeg".to_string(),
description: String::new(),
data_len: crate::input::BlobLen::new(u64::from(u32::MAX)).unwrap(),
picture_type: crate::input::PictureType::new(3).unwrap(),
width: 0,
height: 0,
};
let art = OggArt { meta: &meta };
let header = OggHeader {
codec: Codec::Vorbis,
serial: 0,
packets: vec![vec![], vec![], vec![]],
header_pages: 1,
audio_offset: 0,
};
let result = build_packets_with_art(&header, &[], &[art]);
assert!(result.is_err(), "expected Err for oversized art");
}
#[test]
fn sum_overflow_art_value_rejected_by_build_packets() {
let meta = crate::input::ArtInput {
art_id: 0,
mime: "image/png".to_string(),
description: "x".repeat(256),
data_len: crate::input::BlobLen::new(3_221_225_470).unwrap(),
picture_type: crate::input::PictureType::new(3).unwrap(),
width: 0,
height: 0,
};
let art = OggArt { meta: &meta };
let header = OggHeader {
codec: Codec::Vorbis,
serial: 0,
packets: vec![vec![], vec![], vec![]],
header_pages: 1,
audio_offset: 0,
};
let result = build_packets_with_art(&header, &[], &[art]);
assert!(
result.is_err(),
"expected Err when key + b64(prefix) + b64(data) overflows u32"
);
}
#[test]
fn art_value_at_u32_max_boundary_is_accepted_by_build_packets() {
let meta = crate::input::ArtInput {
art_id: 0,
mime: "image/png".to_string(),
description: String::new(),
data_len: crate::input::BlobLen::new(3_221_225_412).unwrap(),
picture_type: crate::input::PictureType::new(3).unwrap(),
width: 0,
height: 0,
};
let art = OggArt { meta: &meta };
let header = OggHeader {
codec: Codec::Vorbis,
serial: 0,
packets: vec![vec![], vec![], vec![]],
header_pages: 1,
audio_offset: 0,
};
let accepted = build_packets_with_art(&header, &[], &[art]).is_ok();
assert!(
accepted,
"value_len exactly u32::MAX must be accepted by build_packets_with_art"
);
}
#[test]
fn near_u64_max_art_value_rejected_by_build_packets() {
let meta = crate::input::ArtInput {
art_id: 0,
mime: "image/jpeg".to_string(),
description: String::new(),
data_len: crate::input::BlobLen::new(u64::MAX).unwrap(),
picture_type: crate::input::PictureType::new(3).unwrap(),
width: 0,
height: 0,
};
let art = OggArt { meta: &meta };
let header = OggHeader {
codec: Codec::Vorbis,
serial: 0,
packets: vec![vec![], vec![], vec![]],
header_pages: 1,
audio_offset: 0,
};
let result = build_packets_with_art(&header, &[], &[art]);
let is_too_large = matches!(&result, Err(FormatError::TooLarge));
assert!(is_too_large, "expected Err(TooLarge) for near-u64::MAX art");
}
#[test]
fn near_u64_max_art_value_rejected_by_oggflac_build_packets() {
let meta = crate::input::ArtInput {
art_id: 0,
mime: "image/jpeg".to_string(),
description: String::new(),
data_len: crate::input::BlobLen::new(u64::MAX).unwrap(),
picture_type: crate::input::PictureType::new(3).unwrap(),
width: 0,
height: 0,
};
let art = OggArt { meta: &meta };
let header = OggHeader {
codec: Codec::OggFlac,
serial: 0,
packets: vec![vec![0x7F]],
header_pages: 1,
audio_offset: 0,
};
let result = build_packets_with_art(&header, &[], &[art]);
let is_too_large = matches!(&result, Err(FormatError::TooLarge));
assert!(is_too_large, "expected Err(TooLarge) for near-u64::MAX art");
}
#[test]
fn picture_prefix_is_3_aligned_and_declares_image_len() {
let art = crate::input::ArtInput {
art_id: 1,
mime: "image/png".to_string(), description: String::new(),
picture_type: crate::input::PictureType::new(3).unwrap(),
width: 1,
height: 1,
data_len: crate::input::BlobLen::new(12345).unwrap(),
};
let p = picture_prefix(&art).unwrap();
assert_eq!(p.len() % 3, 0);
let dl = u32::from_be_bytes(p[p.len() - 4..].try_into().unwrap());
assert_eq!(dl, 12345);
let mut body = p.clone();
body.extend(std::iter::repeat_n(0u8, 12345));
let pic = crate::flac::parse_picture_block(&body).unwrap();
assert_eq!(pic.mime, "image/png");
assert_eq!(pic.picture_type.get(), 3);
}
#[test]
fn detect_codec_matches_each_magic_and_rejects_others() {
assert_eq!(detect_codec(b"OpusHead........").unwrap(), Codec::Opus);
assert_eq!(detect_codec(b"\x01vorbis...").unwrap(), Codec::Vorbis);
assert_eq!(detect_codec(b"\x7FFLAC...").unwrap(), Codec::OggFlac);
assert!(detect_codec(b"OpusHea").is_err()); assert!(detect_codec(b"XXXXXXXX").is_err()); assert!(detect_codec(b"\x01vorbi").is_err()); }
#[test]
fn comment_body_strips_each_codec_prefix_and_guards_length() {
assert_eq!(comment_body(Codec::Opus, b"OpusTagsBODY").unwrap(), b"BODY");
assert_eq!(
comment_body(Codec::Vorbis, b"\x03vorbisBODY").unwrap(),
b"BODY"
);
assert_eq!(
comment_body(Codec::OggFlac, b"\x04\x00\x00\x00BODY").unwrap(),
b"BODY"
);
assert!(comment_body(Codec::Opus, b"OpusTa").is_err());
assert!(comment_body(Codec::OggFlac, b"\x04\x00\x00").is_err());
}
#[test]
fn oggflac_following_packets_reads_be_count_and_guards_length() {
let pkt = b"\x7FFLAC\x01\x00\x00\x05rest";
assert_eq!(oggflac_following_packets(pkt).unwrap(), 5);
assert!(oggflac_following_packets(b"\x7FFLAC\x01\x00").is_err()); }
#[test]
fn oggflac_comment_block_size_boundary_is_inclusive() {
let header = OggHeader {
codec: Codec::OggFlac,
serial: 1,
packets: vec![vec![0x7F; 9]],
header_pages: 1,
audio_offset: 0,
};
let overhead = crate::vorbiscomment::build(&[crate::input::TagInput::new("title", "")])
.unwrap()
.len() as u64;
let at_limit = "x".repeat(crate::convert::usize_from(
crate::flac::MAX_BLOCK_BODY - overhead,
));
let tags = [crate::input::TagInput::new("title", at_limit.as_str())];
assert!(oggflac_packets_with_art(&header, &tags, &[]).is_ok());
let over = format!("{at_limit}x");
let tags = [crate::input::TagInput::new("title", over.as_str())];
assert!(matches!(
oggflac_packets_with_art(&header, &tags, &[]),
Err(FormatError::TooLarge)
));
}
#[test]
fn oggflac_picture_block_size_boundary_is_inclusive() {
let header = OggHeader {
codec: Codec::OggFlac,
serial: 1,
packets: vec![vec![0x7F; 9]],
header_pages: 1,
audio_offset: 0,
};
let mk = |data_len: u64| crate::input::ArtInput {
art_id: 1,
mime: "image/png".to_string(),
description: String::new(),
picture_type: crate::input::PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: crate::input::BlobLen::new(data_len).unwrap(),
};
let framing_len = picture_prefix(&mk(1)).unwrap().len() as u64;
let at_limit = mk(crate::flac::MAX_BLOCK_BODY - framing_len);
let arts = [OggArt { meta: &at_limit }];
assert!(oggflac_packets_with_art(&header, &[], &arts).is_ok());
let over = mk(crate::flac::MAX_BLOCK_BODY - framing_len + 1);
let arts = [OggArt { meta: &over }];
assert!(matches!(
oggflac_packets_with_art(&header, &[], &arts),
Err(FormatError::TooLarge)
));
}
#[test]
fn comment_packet_index_locates_the_comment_block() {
let opus = OggHeader {
codec: Codec::Opus,
serial: 1,
packets: vec![vec![], vec![]],
header_pages: 1,
audio_offset: 0,
};
assert_eq!(comment_packet_index(&opus), 1);
let oggflac = OggHeader {
codec: Codec::OggFlac,
serial: 1,
packets: vec![vec![0x7F], vec![0x01], vec![0x84]], header_pages: 1,
audio_offset: 0,
};
assert_eq!(comment_packet_index(&oggflac), 2);
let none = OggHeader {
codec: Codec::OggFlac,
serial: 1,
packets: vec![vec![0x7F], vec![0x01], vec![0x05]],
header_pages: 1,
audio_offset: 0,
};
assert_eq!(comment_packet_index(&none), 0);
}
#[test]
fn locate_audio_accepts_empty_audio_region() {
let file = opus_headers();
let scan = locate_audio(&file).unwrap();
assert_eq!(scan.codec, Codec::Opus);
assert_eq!(scan.audio_offset, file.len() as u64);
assert_eq!(scan.audio_length, 0);
}
#[test]
fn picture_prefix_declared_desc_len_pins_padding() {
let art = crate::input::ArtInput {
art_id: 1,
mime: "image/png".into(), description: "x".into(), picture_type: crate::input::PictureType::new(3).unwrap(),
width: 1,
height: 1,
data_len: crate::input::BlobLen::new(100).unwrap(),
};
let prefix = picture_prefix(&art).unwrap();
assert_eq!(prefix.len() % 3, 0);
let off = 8 + art.mime.len();
let declared = u32::from_be_bytes(prefix[off..off + 4].try_into().unwrap());
let pad = declared - u32::try_from(art.description.len()).unwrap();
assert!(pad <= 2, "pad must be 0..=2, got {pad}");
assert_eq!(pad, 0, "base % 3 == 0 implies pad 0");
}
#[test]
fn synthesis_reads_art_in_page_bounded_windows() {
use std::cell::Cell;
struct Counting<'a> {
inner: MapArtSource,
max: &'a Cell<usize>,
}
impl ArtSource for Counting<'_> {
fn read_window(&self, art_id: i64, offset: u64, buf: &mut [u8]) -> crate::Result<()> {
self.max.set(self.max.get().max(buf.len()));
self.inner.read_window(art_id, offset, buf)
}
}
let mut data = opus_headers();
let scan = locate_audio({
let (audio, _) = crate::ogg::page::lace_packet(0x1234, 2, false, 960, &[0u8; 80]);
data.extend_from_slice(&audio);
&data
})
.unwrap();
let header = read_metadata(&data[..crate::convert::usize_from(scan.audio_offset)]).unwrap();
let image: Vec<u8> = (0..500_000u32).map(|i| (i % 251) as u8).collect();
let meta = crate::input::ArtInput {
art_id: 7,
mime: "image/jpeg".to_string(),
description: String::new(),
picture_type: crate::input::PictureType::new(3).unwrap(),
width: 0,
height: 0,
data_len: crate::input::BlobLen::new(image.len() as u64).unwrap(),
};
let max = Cell::new(0usize);
let src = Counting {
inner: MapArtSource::new([(7i64, image.clone())]),
max: &max,
};
synthesize_layout(
&header,
scan.audio_offset,
scan.audio_length,
&[],
&[OggArt { meta: &meta }],
&src,
)
.unwrap();
assert!(
max.get() > 0 && max.get() <= 65_025,
"max single read was {}",
max.get()
);
}
}
#[cfg(test)]
mod page_test_support_tests {
#[test]
fn vorbis_body_empty_is_a_parseable_empty_comment() {
let body = super::page_test_support::vorbis_body_empty();
let parsed = crate::vorbiscomment::parse(&body).unwrap();
assert!(parsed.is_empty());
}
}
#[cfg(test)]
mod bounded_tests {
use super::*;
use crate::ogg::page_test_support::{build_header_pub, lace_packet_pub, vorbis_body_empty};
fn opus_stream() -> (Vec<u8>, u64) {
let head = b"OpusHead\x01\x02\x38\x01\x80\xbb\x00\x00\x00\x00\x00".to_vec();
let mut tags = b"OpusTags".to_vec();
tags.extend_from_slice(&vorbis_body_empty());
let serial = 0x1234;
let (mut v, _) = build_header_pub(serial, &[&head, &tags]);
let audio_offset = v.len() as u64;
let (audio, _) = lace_packet_pub(serial, 2, false, 960, &[0u8; 100]);
v.extend_from_slice(&audio);
(v, audio_offset)
}
#[test]
fn read_metadata_bounded_complete_when_prefix_covers_header() {
let (full, audio_offset) = opus_stream();
let file_len = full.len() as u64;
let prefix = &full[..crate::convert::usize_from(audio_offset)]; match read_metadata_bounded(prefix, file_len).unwrap() {
Extent::Complete(h) => assert_eq!(h.audio_offset, audio_offset),
other @ Extent::NeedMore { .. } => panic!("expected Complete, got {other:?}"),
}
}
#[test]
fn read_metadata_bounded_needmore_when_header_truncated() {
let (full, _audio_offset) = opus_stream();
let file_len = full.len() as u64;
let prefix = &full[..20]; match read_metadata_bounded(prefix, file_len).unwrap() {
Extent::NeedMore { up_to } => assert!(up_to > 20 && up_to <= file_len),
other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
}
}
#[test]
fn read_metadata_bounded_errors_when_whole_file_is_unparseable() {
let bad: &[u8] = b"not an ogg stream at all"; assert!(read_header(bad).is_err());
let len = bad.len() as u64;
match read_metadata_bounded(bad, len) {
Err(_) => {}
Ok(other) => panic!("expected Err when whole file unparseable, got {other:?}"),
}
}
#[test]
fn read_metadata_bounded_doubles_window_exactly() {
let buf = vec![0u8; 100_000]; assert!(read_header(&buf).is_err());
let file_len = 10_000_000u64;
match read_metadata_bounded(&buf, file_len).unwrap() {
Extent::NeedMore { up_to } => assert_eq!(up_to, 200_000),
other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
}
}
#[test]
fn read_metadata_bounded_floor_is_64kib_for_small_prefix() {
let buf = vec![0u8; 100]; assert!(read_header(&buf).is_err());
let file_len = 10_000_000u64;
match read_metadata_bounded(&buf, file_len).unwrap() {
Extent::NeedMore { up_to } => assert_eq!(up_to, 65_536),
other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
}
}
#[test]
fn read_metadata_bounded_grows_when_truncated_prefix_shorter_than_file() {
let (full, _audio_offset) = opus_stream();
let file_len = full.len() as u64;
let prefix = &full[..10]; assert!(read_header(prefix).is_err());
match read_metadata_bounded(prefix, file_len).unwrap() {
Extent::NeedMore { up_to } => assert!(up_to > prefix.len() as u64),
other @ Extent::Complete(_) => panic!("expected NeedMore, got {other:?}"),
}
}
}