use crate::crc::ogg_crc32;
use crate::error::FlacError;
use crate::metadata::FLAC_MARKER;
const OGG_MAGIC: &[u8; 4] = b"OggS";
const OGG_HEADER_LEN: usize = 27;
const FLAC_MAPPING_TYPE: u8 = 0x7F;
const FLAC_MAPPING_SIG: &[u8; 4] = b"FLAC";
const FLAG_CONTINUED: u8 = 0x01;
const FLAG_BOS: u8 = 0x02;
const FLAG_EOS: u8 = 0x04;
const MAPPING_PREFIX_LEN: usize = 9;
const ENCODER_SERIAL: u32 = 0x664C_4143;
pub fn is_ogg(bytes: &[u8]) -> bool {
bytes.len() >= 4 && &bytes[0..4] == OGG_MAGIC
}
pub fn to_native_flac(bytes: &[u8], headers_only: bool) -> Result<Vec<u8>, FlacError> {
let mut pos = 0usize;
let mut serial: Option<u32> = None;
let mut partial: Vec<u8> = Vec::new();
let mut packets: Vec<Vec<u8>> = Vec::new();
let mut wanted_headers: Option<usize> = None;
while pos + OGG_HEADER_LEN <= bytes.len() {
if &bytes[pos..pos + 4] != OGG_MAGIC {
return Err(FlacError::CorruptStream(
"expected an Ogg page boundary".into(),
));
}
if bytes[pos + 4] != 0 {
return Err(FlacError::Unsupported(format!(
"Ogg stream structure version {}",
bytes[pos + 4]
)));
}
let flags = bytes[pos + 5];
let page_serial = u32::from_le_bytes([
bytes[pos + 14],
bytes[pos + 15],
bytes[pos + 16],
bytes[pos + 17],
]);
let n_segments = bytes[pos + 26] as usize;
let seg_table = pos + OGG_HEADER_LEN;
let body_start = seg_table + n_segments;
if body_start > bytes.len() {
return Err(FlacError::Truncated);
}
let body_len: usize = bytes[seg_table..body_start]
.iter()
.map(|&s| s as usize)
.sum();
let body_end = body_start
.checked_add(body_len)
.ok_or(FlacError::Truncated)?;
if body_end > bytes.len() {
return Err(FlacError::Truncated);
}
let is_flac_bos = flags & FLAG_BOS != 0
&& body_len >= 5
&& bytes[body_start] == FLAC_MAPPING_TYPE
&& &bytes[body_start + 1..body_start + 5] == FLAC_MAPPING_SIG;
if serial.is_none() {
if is_flac_bos {
serial = Some(page_serial);
} else {
pos = body_end;
continue;
}
} else if Some(page_serial) != serial {
pos = body_end;
continue;
}
let stored_crc = u32::from_le_bytes([
bytes[pos + 22],
bytes[pos + 23],
bytes[pos + 24],
bytes[pos + 25],
]);
let mut page = bytes[pos..body_end].to_vec();
page[22..26].fill(0);
if ogg_crc32(&page) != stored_crc {
return Err(FlacError::CrcMismatch);
}
let mut seg_pos = body_start;
for i in 0..n_segments {
let s = bytes[seg_table + i] as usize;
partial.extend_from_slice(&bytes[seg_pos..seg_pos + s]);
seg_pos += s;
if s < 255 {
packets.push(std::mem::take(&mut partial));
if wanted_headers.is_none() {
wanted_headers = Some(1 + mapping_header_count(&packets[0])?);
}
if headers_only {
if let Some(w) = wanted_headers {
if packets.len() >= w {
return rebuild(&packets, Some(w));
}
}
}
}
}
if flags & FLAG_EOS != 0 {
break;
}
pos = body_end;
}
if serial.is_none() {
return Err(FlacError::NotFlac);
}
let keep = if headers_only { wanted_headers } else { None };
rebuild(&packets, keep)
}
fn mapping_header_count(first: &[u8]) -> Result<usize, FlacError> {
if first.len() < MAPPING_PREFIX_LEN + 4
|| first[0] != FLAC_MAPPING_TYPE
|| &first[1..5] != FLAC_MAPPING_SIG
|| &first[MAPPING_PREFIX_LEN..MAPPING_PREFIX_LEN + 4] != FLAC_MARKER
{
return Err(FlacError::CorruptStream(
"invalid FLAC-to-Ogg mapping header".into(),
));
}
Ok(u16::from_be_bytes([first[7], first[8]]) as usize)
}
fn rebuild(packets: &[Vec<u8>], keep: Option<usize>) -> Result<Vec<u8>, FlacError> {
let first = packets.first().ok_or(FlacError::Truncated)?;
mapping_header_count(first)?;
let limit = keep.unwrap_or(packets.len()).min(packets.len());
let mut native = Vec::new();
native.extend_from_slice(&first[MAPPING_PREFIX_LEN..]);
for packet in &packets[1..limit] {
native.extend_from_slice(packet);
}
Ok(native)
}
pub struct Packet {
pub data: Vec<u8>,
pub granule: i64,
}
pub fn mux(packets: &[Packet]) -> Vec<u8> {
struct Seg {
value: u8,
ends_granule: Option<i64>,
}
let mut segs: Vec<Seg> = Vec::new();
let mut body: Vec<u8> = Vec::new();
let mut first_packet_last_seg = 0usize;
for (i, packet) in packets.iter().enumerate() {
body.extend_from_slice(&packet.data);
let mut remaining = packet.data.len();
loop {
let value = remaining.min(255);
remaining -= value;
let ends = value < 255;
segs.push(Seg {
value: value as u8,
ends_granule: if ends { Some(packet.granule) } else { None },
});
if ends {
break;
}
}
if i == 0 {
first_packet_last_seg = segs.len() - 1;
}
}
let mut out = Vec::new();
let mut seq: u32 = 0;
let mut seg_idx = 0usize;
let mut body_pos = 0usize;
let mut first_page = true;
let mut continued = false;
while seg_idx < segs.len() {
let mut count = 0usize;
let mut page_granule: i64 = -1;
while seg_idx + count < segs.len() && count < 255 {
if let Some(g) = segs[seg_idx + count].ends_granule {
page_granule = g;
}
count += 1;
if seg_idx + count - 1 == first_packet_last_seg {
break;
}
}
let page_body_len: usize = segs[seg_idx..seg_idx + count]
.iter()
.map(|s| s.value as usize)
.sum();
let this_continued = continued;
continued = segs[seg_idx + count - 1].value == 255;
let is_eos = seg_idx + count >= segs.len();
let mut flags = 0u8;
if first_page {
flags |= FLAG_BOS;
}
if is_eos {
flags |= FLAG_EOS;
}
if this_continued {
flags |= FLAG_CONTINUED;
}
let page_start = out.len();
out.extend_from_slice(OGG_MAGIC);
out.push(0); out.push(flags);
out.extend_from_slice(&page_granule.to_le_bytes());
out.extend_from_slice(&ENCODER_SERIAL.to_le_bytes());
out.extend_from_slice(&seq.to_le_bytes());
let crc_at = out.len();
out.extend_from_slice(&[0u8; 4]); out.push(count as u8);
for j in 0..count {
out.push(segs[seg_idx + j].value);
}
out.extend_from_slice(&body[body_pos..body_pos + page_body_len]);
let crc = ogg_crc32(&out[page_start..]);
out[crc_at..crc_at + 4].copy_from_slice(&crc.to_le_bytes());
body_pos += page_body_len;
seg_idx += count;
seq += 1;
first_page = false;
}
out
}
#[cfg(test)]
mod tests {
use super::*;
fn mapping_header(count: u16, body: &[u8]) -> Vec<u8> {
let mut p = vec![FLAC_MAPPING_TYPE];
p.extend_from_slice(FLAC_MAPPING_SIG);
p.extend_from_slice(&[1, 0]);
p.extend_from_slice(&count.to_be_bytes());
p.extend_from_slice(FLAC_MARKER);
p.extend_from_slice(body);
p
}
fn expected_native(packets: &[Packet]) -> Vec<u8> {
let mut v = packets[0].data[MAPPING_PREFIX_LEN..].to_vec();
for p in &packets[1..] {
v.extend_from_slice(&p.data);
}
v
}
#[test]
fn is_ogg_detects_capture_pattern() {
assert!(is_ogg(b"OggS....."));
assert!(!is_ogg(b"fLaC"));
assert!(!is_ogg(b"Og"));
}
#[test]
fn mux_then_demux_round_trips() {
let packets = vec![
Packet {
data: mapping_header(0, &[0xAB; 38]),
granule: 0,
},
Packet {
data: vec![0x11; 100],
granule: 100,
},
Packet {
data: vec![0x22; 50],
granule: 150,
},
];
let ogg = mux(&packets);
assert!(is_ogg(&ogg));
let native = to_native_flac(&ogg, false).unwrap();
assert_eq!(native, expected_native(&packets));
}
#[test]
fn packet_spanning_multiple_pages_round_trips() {
let big: Vec<u8> = (0..70_000u32).map(|i| i as u8).collect();
let packets = vec![
Packet {
data: mapping_header(0, &[0xCD; 38]),
granule: 0,
},
Packet {
data: big.clone(),
granule: 1,
},
];
let ogg = mux(&packets);
let native = to_native_flac(&ogg, false).unwrap();
assert_eq!(native, expected_native(&packets));
let pages = ogg.windows(4).filter(|w| w == OGG_MAGIC).count();
assert!(
pages >= 3,
"expected the big packet to span pages, got {pages}"
);
}
#[test]
fn headers_only_stops_after_declared_header_packets() {
let packets = vec![
Packet {
data: mapping_header(1, &[0x01; 38]),
granule: 0,
},
Packet {
data: vec![0x84, 0, 0, 1, 0x55],
granule: 0,
}, Packet {
data: vec![0xFF; 200],
granule: 4096,
}, ];
let ogg = mux(&packets);
let headers = to_native_flac(&ogg, true).unwrap();
let mut want = packets[0].data[MAPPING_PREFIX_LEN..].to_vec();
want.extend_from_slice(&packets[1].data);
assert_eq!(headers, want);
}
#[test]
fn mapping_header_count_reads_field_and_validates() {
assert_eq!(
mapping_header_count(&mapping_header(2, &[0; 38])).unwrap(),
2
);
let mut bad = mapping_header(0, &[0; 38]);
bad[9] = b'X'; assert!(mapping_header_count(&bad).is_err());
assert!(mapping_header_count(&[0x7F, b'F']).is_err());
}
#[test]
fn truncated_and_corrupt_pages_error_without_panic() {
let packets = vec![
Packet {
data: mapping_header(0, &[0xAB; 38]),
granule: 0,
},
Packet {
data: vec![0x11; 100],
granule: 100,
},
];
let ogg = mux(&packets);
for cut in 0..ogg.len() {
let _ = to_native_flac(&ogg[..cut], false);
}
let mut corrupt = ogg.clone();
let last = corrupt.len() - 1;
corrupt[last] ^= 0xFF;
assert!(matches!(
to_native_flac(&corrupt, false),
Err(FlacError::CrcMismatch)
));
}
#[test]
fn non_flac_ogg_is_not_flac() {
let packets = vec![Packet {
data: vec![0x01; 20],
granule: 0,
}];
let ogg = mux(&packets);
assert!(matches!(
to_native_flac(&ogg, false),
Err(FlacError::NotFlac)
));
}
fn make_page(serial: u32, seq: u32, flags: u8, body: &[u8], crc: Option<u32>) -> Vec<u8> {
let mut p = Vec::new();
p.extend_from_slice(OGG_MAGIC);
p.push(0);
p.push(flags);
p.extend_from_slice(&0i64.to_le_bytes());
p.extend_from_slice(&serial.to_le_bytes());
p.extend_from_slice(&seq.to_le_bytes());
let crc_at = p.len();
p.extend_from_slice(&[0u8; 4]);
let mut segs = Vec::new();
let mut rem = body.len();
loop {
let v = rem.min(255);
rem -= v;
segs.push(v as u8);
if v < 255 {
break;
}
}
p.push(segs.len() as u8);
p.extend_from_slice(&segs);
p.extend_from_slice(body);
let c = crc.unwrap_or_else(|| {
let mut q = p.clone();
q[crc_at..crc_at + 4].fill(0);
ogg_crc32(&q)
});
p[crc_at..crc_at + 4].copy_from_slice(&c.to_le_bytes());
p
}
#[test]
fn exact_multiple_of_255_packet_round_trips() {
for len in [255usize, 510, 255 * 4] {
let packets = vec![
Packet {
data: mapping_header(0, &[0x09; 38]),
granule: 0,
},
Packet {
data: vec![0x7E; len],
granule: 1,
},
];
let ogg = mux(&packets);
assert_eq!(
to_native_flac(&ogg, false).unwrap(),
expected_native(&packets),
"len {len}"
);
}
}
#[test]
fn skips_foreign_multiplexed_stream_without_checking_its_crc() {
let flac = vec![
Packet {
data: mapping_header(0, &[0x42; 38]),
granule: 0,
},
Packet {
data: vec![0xAA; 80],
granule: 100,
},
];
let foreign = make_page(
0x1234_5678,
0,
FLAG_BOS,
b"NotFLACheader",
Some(0xDEAD_BEEF),
);
let mut stream = foreign;
stream.extend_from_slice(&mux(&flac));
assert_eq!(
to_native_flac(&stream, false).unwrap(),
expected_native(&flac)
);
}
#[test]
fn chained_streams_decode_the_first() {
let first = vec![
Packet {
data: mapping_header(0, &[0x01; 38]),
granule: 0,
},
Packet {
data: vec![0x11; 40],
granule: 10,
},
];
let second = vec![
Packet {
data: mapping_header(0, &[0x02; 38]),
granule: 0,
},
Packet {
data: vec![0x22; 40],
granule: 10,
},
];
let mut stream = mux(&first);
stream.extend_from_slice(&mux(&second));
assert_eq!(
to_native_flac(&stream, false).unwrap(),
expected_native(&first)
);
}
#[test]
fn rejects_unsupported_ogg_version() {
let mut ogg = mux(&[Packet {
data: mapping_header(0, &[0; 38]),
granule: 0,
}]);
ogg[4] = 1; assert!(matches!(
to_native_flac(&ogg, false),
Err(FlacError::Unsupported(_))
));
}
}