use crate::bytes::Reader;
use crate::error::{MalformedDetail, ResourceLimit};
pub(crate) const MAGIC: &[u8; 4] = b"OggS";
const VERSION: u8 = 0;
pub(crate) const CONTINUED: u8 = 0x01;
pub(crate) const BOS: u8 = 0x02;
pub(crate) const EOS: u8 = 0x04;
pub(crate) const NO_GRANULE: u64 = u64::MAX;
const HEADER_BYTES: usize = 27;
const CRC_AT: usize = 22;
const CRC_LEN: usize = 4;
const FLAGS_AT: usize = 5;
const MAX_SEGMENTS: usize = 255;
const MAX_SEGMENT: usize = 255;
const POLY: u32 = 0x04c1_1db7;
#[derive(Debug, PartialEq, Eq)]
pub(crate) struct Page<'a> {
pub(crate) flags: u8,
pub(crate) granule: u64,
pub(crate) serial: u32,
pub(crate) lacing: &'a [u8],
pub(crate) body: &'a [u8],
pub(crate) offset: usize,
}
pub(crate) struct Packet<'a> {
pub(crate) fragments: Vec<&'a [u8]>,
pub(crate) len: usize,
pub(crate) granule: u64,
pub(crate) ends_page: bool,
}
impl<'a> Packet<'a> {
pub(crate) fn bytes(&self) -> std::borrow::Cow<'a, [u8]> {
match self.fragments.as_slice() {
[single] => std::borrow::Cow::Borrowed(single),
many => std::borrow::Cow::Owned(many.concat()),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum WalkError {
Malformed {
detail: MalformedDetail,
offset: usize,
},
Limit(ResourceLimit),
}
impl WalkError {
const fn at(detail: MalformedDetail, offset: usize) -> Self {
Self::Malformed { detail, offset }
}
}
pub(crate) fn crc(data: &[u8]) -> u32 {
continue_crc(0, data)
}
pub(crate) fn pages<'a>(input: &'a [u8], budget: &mut u32) -> Result<Vec<Page<'a>>, WalkError> {
let mut r = Reader::new(input);
let mut out: Vec<Page<'a>> = Vec::new();
while r.remaining() > 0 {
let at = r.position();
*budget = budget
.checked_sub(1)
.ok_or(WalkError::Limit(ResourceLimit::ItemCount))?;
let header = r
.peek(HEADER_BYTES)
.ok_or(WalkError::at(MalformedDetail::Truncated, at))?;
if header.get(..MAGIC.len()) != Some(MAGIC.as_slice()) {
return Err(WalkError::at(MalformedDetail::MissingMarker, at));
}
if header.get(4) != Some(&VERSION) {
return Err(WalkError::at(MalformedDetail::UnexpectedMarker, at));
}
r.skip(MAGIC.len().saturating_add(1))
.ok_or(WalkError::at(MalformedDetail::Truncated, at))?;
let flags = r
.u8()
.ok_or(WalkError::at(MalformedDetail::Truncated, at))?;
let granule = u64_le(&mut r).ok_or(WalkError::at(MalformedDetail::Truncated, at))?;
let serial = r
.u32_le()
.ok_or(WalkError::at(MalformedDetail::Truncated, at))?;
let _sequence = r
.u32_le()
.ok_or(WalkError::at(MalformedDetail::Truncated, at))?;
let declared_crc = r
.u32_le()
.ok_or(WalkError::at(MalformedDetail::Truncated, at))?;
let segments = r
.u8()
.ok_or(WalkError::at(MalformedDetail::Truncated, at))?;
let lacing = r
.take(usize::from(segments))
.ok_or(WalkError::at(MalformedDetail::Truncated, at))?;
let body_len = lacing
.iter()
.fold(0usize, |sum, n| sum.saturating_add(usize::from(*n)));
let body = r
.take(body_len)
.ok_or(WalkError::at(MalformedDetail::LengthOutOfRange, at))?;
let raw = input
.get(at..r.position())
.ok_or(WalkError::at(MalformedDetail::Truncated, at))?;
if computed_crc(raw) != declared_crc {
return Err(WalkError::at(MalformedDetail::UnexpectedMarker, at));
}
out.push(Page {
flags,
granule,
serial,
lacing,
body,
offset: at,
});
}
if out.is_empty() {
return Err(WalkError::at(MalformedDetail::MissingMarker, 0));
}
Ok(out)
}
fn computed_crc(raw: &[u8]) -> u32 {
let head = raw.get(..CRC_AT).unwrap_or_default();
let tail = raw
.get(CRC_AT.saturating_add(CRC_LEN)..)
.unwrap_or_default();
let mut r = crc(head);
r = continue_crc(r, &[0u8; CRC_LEN]);
r = continue_crc(r, tail);
r
}
fn continue_crc(state: u32, data: &[u8]) -> u32 {
let mut r = state;
for byte in data {
r ^= u32::from(*byte).wrapping_shl(24);
for _ in 0..8 {
r = if r & 0x8000_0000 == 0 {
r.wrapping_shl(1)
} else {
r.wrapping_shl(1) ^ POLY
};
}
}
r
}
pub(crate) fn packets<'a>(pages: &[Page<'a>]) -> Result<Vec<Packet<'a>>, WalkError> {
let mut out: Vec<Packet<'a>> = Vec::new();
let mut open: Option<Packet<'a>> = None;
for page in pages {
if (page.flags & CONTINUED == 0) != open.is_none() {
return Err(WalkError::at(
MalformedDetail::UnexpectedMarker,
page.offset,
));
}
let mut finished_here = 0usize;
let mut r = Reader::new(page.body);
for value in page.lacing {
let piece = r
.take(usize::from(*value))
.ok_or(WalkError::at(MalformedDetail::Truncated, page.offset))?;
let mut packet = open.take().unwrap_or(Packet {
fragments: Vec::new(),
len: 0,
granule: NO_GRANULE,
ends_page: false,
});
if !piece.is_empty() {
packet.fragments.push(piece);
}
packet.len = packet.len.saturating_add(piece.len());
if usize::from(*value) == MAX_SEGMENT {
open = Some(packet);
} else {
packet.granule = page.granule;
out.push(packet);
finished_here = finished_here.saturating_add(1);
}
}
if finished_here == 0 {
if page.granule != NO_GRANULE {
return Err(WalkError::at(
MalformedDetail::UnexpectedMarker,
page.offset,
));
}
} else if let Some(last) = out.last_mut() {
last.ends_page = true;
}
}
if open.is_some() {
return Err(WalkError::at(
MalformedDetail::Truncated,
pages.last().map_or(0, |p| p.offset),
));
}
if out.is_empty() {
return Err(WalkError::at(MalformedDetail::MissingMarker, 0));
}
Ok(out)
}
pub(crate) struct Emit<'a> {
pub(crate) fragments: Vec<&'a [u8]>,
pub(crate) len: usize,
pub(crate) granule: u64,
pub(crate) ends_page: bool,
}
impl<'a> Emit<'a> {
pub(crate) fn copied(packet: &Packet<'a>) -> Self {
Self {
fragments: packet.fragments.clone(),
len: packet.len,
granule: packet.granule,
ends_page: packet.ends_page,
}
}
pub(crate) fn replacing(packet: &Packet<'a>, bytes: &'a [u8]) -> Self {
Self {
fragments: vec![bytes],
len: bytes.len(),
granule: packet.granule,
ends_page: packet.ends_page,
}
}
}
pub(crate) fn write(serial: u32, packets: &[Emit<'_>]) -> Result<Vec<u8>, MalformedDetail> {
let mut w = Writer {
serial,
sequence: 0,
out: Vec::new(),
lacing: Vec::new(),
body: Vec::new(),
continued: false,
last_page_at: None,
};
for packet in packets {
w.packet(packet)?;
}
w.finish()
}
struct Writer {
serial: u32,
sequence: u32,
out: Vec<u8>,
lacing: Vec<u8>,
body: Vec<u8>,
continued: bool,
last_page_at: Option<usize>,
}
impl Writer {
fn packet(&mut self, packet: &Emit<'_>) -> Result<(), MalformedDetail> {
let mut cursor = Cursor {
parts: &packet.fragments,
part: 0,
at: 0,
};
let mut written = 0usize;
loop {
if self.lacing.len() >= MAX_SEGMENTS {
self.flush(NO_GRANULE)?;
self.continued = true;
}
let take = MAX_SEGMENT.min(packet.len.saturating_sub(written));
cursor.take(take, &mut self.body);
self.lacing.push(u8::try_from(take).unwrap_or(0));
written = written.saturating_add(take);
if take < MAX_SEGMENT {
break;
}
}
if packet.ends_page {
self.flush(packet.granule)?;
}
Ok(())
}
fn flush(&mut self, granule: u64) -> Result<(), MalformedDetail> {
let mut flags = 0u8;
if self.continued {
flags |= CONTINUED;
}
if self.sequence == 0 {
flags |= BOS;
}
let segments = u8::try_from(self.lacing.len()).map_err(|_| {
MalformedDetail::LengthOutOfRange
})?;
let at = self.out.len();
self.out.extend_from_slice(MAGIC);
self.out.push(VERSION);
self.out.push(flags);
self.out.extend_from_slice(&granule.to_le_bytes());
self.out.extend_from_slice(&self.serial.to_le_bytes());
self.out.extend_from_slice(&self.sequence.to_le_bytes());
self.out.extend_from_slice(&[0u8; CRC_LEN]);
self.out.push(segments);
self.out.extend_from_slice(&self.lacing);
self.out.extend_from_slice(&self.body);
stamp_crc(&mut self.out, at);
self.sequence = self
.sequence
.checked_add(1)
.ok_or(MalformedDetail::LengthOutOfRange)?;
self.lacing.clear();
self.body.clear();
self.continued = false;
self.last_page_at = Some(at);
Ok(())
}
fn finish(mut self) -> Result<Vec<u8>, MalformedDetail> {
if !self.lacing.is_empty() {
self.flush(NO_GRANULE)?;
}
let at = self.last_page_at.ok_or(MalformedDetail::Truncated)?;
if let Some(flags) = self.out.get_mut(at.saturating_add(FLAGS_AT)) {
*flags |= EOS;
}
stamp_crc(&mut self.out, at);
Ok(self.out)
}
}
fn stamp_crc(out: &mut [u8], at: usize) {
let crc_at = at.saturating_add(CRC_AT);
if let Some(field) = out.get_mut(crc_at..crc_at.saturating_add(CRC_LEN)) {
field.fill(0);
}
let value = crc(out.get(at..).unwrap_or_default());
if let Some(field) = out.get_mut(crc_at..crc_at.saturating_add(CRC_LEN)) {
field.copy_from_slice(&value.to_le_bytes());
}
}
#[cfg(feature = "fuzzing")]
pub(crate) fn restamp(data: &mut [u8]) {
let mut at = 0usize;
loop {
let Some(header) = data.get(at..at.saturating_add(HEADER_BYTES)) else {
return;
};
if header.get(..MAGIC.len()) != Some(MAGIC.as_slice()) {
return;
}
let segments = usize::from(header.last().copied().unwrap_or_default());
let lacing_at = at.saturating_add(HEADER_BYTES);
let body_at = lacing_at.saturating_add(segments);
let Some(lacing) = data.get(lacing_at..body_at) else {
return;
};
let end = lacing
.iter()
.fold(body_at, |sum, n| sum.saturating_add(usize::from(*n)));
let Some(page) = data.get_mut(at..end) else {
return;
};
stamp_crc(page, 0);
at = end;
}
}
struct Cursor<'a> {
parts: &'a [&'a [u8]],
part: usize,
at: usize,
}
impl Cursor<'_> {
fn take(&mut self, n: usize, out: &mut Vec<u8>) {
let mut left = n;
while left > 0 {
let Some(part) = self.parts.get(self.part) else {
return;
};
let Some(rest) = part.get(self.at..) else {
return;
};
if rest.is_empty() {
self.part = self.part.saturating_add(1);
self.at = 0;
continue;
}
let take = left.min(rest.len());
out.extend_from_slice(rest.get(..take).unwrap_or_default());
self.at = self.at.saturating_add(take);
left = left.saturating_sub(take);
}
}
}
fn u64_le(r: &mut Reader<'_>) -> Option<u64> {
let bytes: [u8; 8] = r.take(8)?.try_into().ok()?;
Some(u64::from_le_bytes(bytes))
}
#[cfg(test)]
mod tests {
#![allow(
clippy::unwrap_used,
clippy::indexing_slicing,
clippy::arithmetic_side_effects
)]
use super::*;
fn page(flags: u8, granule: u64, serial: u32, sequence: u32, packets: &[&[u8]]) -> Vec<u8> {
let mut lacing = Vec::new();
let mut body = Vec::new();
for packet in packets {
let mut written = 0;
loop {
let take = 255.min(packet.len() - written);
lacing.push(u8::try_from(take).unwrap());
body.extend_from_slice(&packet[written..written + take]);
written += take;
if take < 255 {
break;
}
}
}
let mut out = MAGIC.to_vec();
out.push(0);
out.push(flags);
out.extend_from_slice(&granule.to_le_bytes());
out.extend_from_slice(&serial.to_le_bytes());
out.extend_from_slice(&sequence.to_le_bytes());
out.extend_from_slice(&[0u8; 4]);
out.push(u8::try_from(lacing.len()).unwrap());
out.extend_from_slice(&lacing);
out.extend_from_slice(&body);
stamp_crc(&mut out, 0);
out
}
fn page_continuing(flags: u8, serial: u32, sequence: u32, fragment: &[u8]) -> Vec<u8> {
assert_eq!(
fragment.len() % 255,
0,
"a continuing fragment fills its segments"
);
let mut out = MAGIC.to_vec();
out.push(0);
out.push(flags);
out.extend_from_slice(&NO_GRANULE.to_le_bytes());
out.extend_from_slice(&serial.to_le_bytes());
out.extend_from_slice(&sequence.to_le_bytes());
out.extend_from_slice(&[0u8; 4]);
let segments = fragment.len() / 255;
out.push(u8::try_from(segments).unwrap());
out.extend_from_slice(&vec![255u8; segments]);
out.extend_from_slice(fragment);
stamp_crc(&mut out, 0);
out
}
fn walk(data: &[u8]) -> Result<Vec<Page<'_>>, WalkError> {
let mut budget = 4096;
pages(data, &mut budget)
}
#[test]
fn the_crc_matches_the_value_rfc_3533_defines() {
assert_eq!(crc(b""), 0);
assert_eq!(crc(b"OggS"), 0x5FB0_A94F);
}
#[test]
fn a_page_round_trips_through_the_walker() {
let data = page(BOS | EOS, 42, 7, 0, &[b"HELLO"]);
let read = walk(&data).unwrap();
assert_eq!(read.len(), 1);
assert_eq!(read[0].granule, 42);
assert_eq!(read[0].serial, 7);
assert_eq!(read[0].body, b"HELLO");
}
#[test]
fn a_page_whose_crc_does_not_match_is_refused() {
let mut data = page(BOS | EOS, 0, 1, 0, &[b"HELLO"]);
let at = data.len() - 1;
data[at] ^= 0xFF;
assert_eq!(
walk(&data),
Err(WalkError::at(MalformedDetail::UnexpectedMarker, 0))
);
}
#[cfg(feature = "fuzzing")]
#[test]
fn restamp_repairs_every_framed_page_and_stops_at_a_truncated_one() {
let mut data = page(BOS, 0, 1, 0, &[b"HELLO"]);
data.extend_from_slice(&page(EOS, 0, 1, 1, &[b"WORLD"]));
let first = data.len() / 2;
data[first - 1] ^= 0xFF;
let last = data.len() - 1;
data[last] ^= 0xFF;
assert!(walk(&data).is_err());
restamp(&mut data);
assert_eq!(walk(&data).unwrap().len(), 2);
let mut truncated = data[..data.len() - 1].to_vec();
truncated[first - 1] ^= 0xFF;
let tail = truncated[first..].to_vec();
restamp(&mut truncated);
assert!(walk(&truncated[..first]).is_ok());
assert_eq!(
truncated[first..],
tail,
"a page it cannot frame is left alone"
);
}
#[test]
fn bytes_wrapped_around_the_pages_are_refused_rather_than_handed_back() {
let mut data = b"PREFIX".to_vec();
data.extend_from_slice(&page(BOS | EOS, 0, 1, 0, &[b"HELLO"]));
assert!(matches!(
walk(&data),
Err(WalkError::Malformed {
detail: MalformedDetail::MissingMarker,
..
})
));
let mut trailing = page(BOS | EOS, 0, 1, 0, &[b"HELLO"]);
trailing.extend_from_slice(b"APPENDED");
assert!(walk(&trailing).is_err());
}
#[test]
fn a_packet_spread_over_pages_is_reassembled() {
let long: Vec<u8> = (0..600u32)
.map(|n| u8::try_from(n % 251).unwrap())
.collect();
let mut data = page_continuing(BOS, 1, 0, &long[0..510]);
data.extend_from_slice(&page(CONTINUED | EOS, 99, 1, 1, &[&long[510..]]));
let read = walk(&data).unwrap();
let assembled = packets(&read).unwrap();
assert_eq!(assembled.len(), 1);
assert_eq!(assembled[0].bytes().as_ref(), long.as_slice());
assert_eq!(assembled[0].granule, 99);
assert!(assembled[0].ends_page);
}
#[test]
fn a_page_that_finishes_nothing_must_say_so_with_a_minus_one_granule() {
let long: Vec<u8> = vec![0x5A; 600];
let mut data = page_continuing(BOS, 1, 0, &long[0..510]);
data[6..14].copy_from_slice(&12_345u64.to_le_bytes());
stamp_crc(&mut data, 0);
data.extend_from_slice(&page(CONTINUED | EOS, 99, 1, 1, &[&long[510..]]));
let read = walk(&data).unwrap();
assert!(matches!(
packets(&read),
Err(WalkError::Malformed {
detail: MalformedDetail::UnexpectedMarker,
..
})
));
}
#[test]
fn a_stream_ending_mid_packet_is_refused() {
let data = page_continuing(BOS | EOS, 1, 0, &[0x5A; 255]);
let read = walk(&data).unwrap();
assert!(matches!(
packets(&read),
Err(WalkError::Malformed {
detail: MalformedDetail::Truncated,
..
})
));
}
#[test]
fn what_this_module_writes_it_can_read_back() {
let long: Vec<u8> = (0..900u32)
.map(|n| u8::try_from(n % 251).unwrap())
.collect();
let mut data = page(BOS, 0, 0xDEAD, 0, &[b"HEAD"]);
data.extend_from_slice(&page_continuing(0, 0xDEAD, 1, &long[0..765]));
data.extend_from_slice(&page(CONTINUED | EOS, 4096, 0xDEAD, 2, &[&long[765..]]));
let read = walk(&data).unwrap();
let assembled = packets(&read).unwrap();
let emitted: Vec<Emit<'_>> = assembled.iter().map(Emit::copied).collect();
let written = write(0, &emitted).unwrap();
let again = packets(&walk(&written).unwrap()).unwrap();
assert_eq!(again.len(), assembled.len());
for (before, after) in assembled.iter().zip(&again) {
assert_eq!(before.bytes(), after.bytes());
assert_eq!(before.granule, after.granule);
}
}
#[test]
fn the_written_stream_is_flagged_and_numbered_from_zero() {
let data = page(BOS | EOS, 5, 0x1234_5678, 900, &[b"ONE", b"TWO"]);
let read = walk(&data).unwrap();
let assembled = packets(&read).unwrap();
let emitted: Vec<Emit<'_>> = assembled.iter().map(Emit::copied).collect();
let written = write(0, &emitted).unwrap();
let out = walk(&written).unwrap();
assert_eq!(out.len(), 1);
assert_eq!(out[0].serial, 0, "the serial was not rewritten");
assert_eq!(out[0].flags, BOS | EOS);
assert_eq!(written.get(18..22), Some(&0u32.to_le_bytes()[..]));
}
#[test]
fn a_packet_longer_than_one_page_is_spread_and_the_carrier_pages_say_minus_one() {
let long: Vec<u8> = vec![0x33; 70_000];
let emitted = vec![Emit {
fragments: vec![&long],
len: long.len(),
granule: 1000,
ends_page: true,
}];
let written = write(0, &emitted).unwrap();
let read = walk(&written).unwrap();
assert!(read.len() > 1, "the packet was not spread");
assert_eq!(read[0].granule, NO_GRANULE);
assert_eq!(read[read.len() - 1].granule, 1000);
let assembled = packets(&read).unwrap();
assert_eq!(assembled[0].bytes().as_ref(), long.as_slice());
}
#[test]
fn a_zero_length_packet_survives_the_round_trip() {
let data = page(BOS | EOS, 1, 1, 0, &[b"", b"AB"]);
let assembled = packets(&walk(&data).unwrap()).unwrap();
assert_eq!(assembled.len(), 2);
assert_eq!(assembled[0].len, 0);
let emitted: Vec<Emit<'_>> = assembled.iter().map(Emit::copied).collect();
let written = write(0, &emitted).unwrap();
assert_eq!(packets(&walk(&written).unwrap()).unwrap().len(), 2);
}
#[test]
fn a_wrong_stream_structure_version_is_refused() {
let mut data = page(BOS | EOS, 0, 1, 0, &[b"HELLO"]);
data[4] = 1;
assert_eq!(
walk(&data),
Err(WalkError::at(MalformedDetail::UnexpectedMarker, 0))
);
}
#[test]
fn the_page_budget_is_charged() {
let mut data = Vec::new();
for n in 0..5u32 {
data.extend_from_slice(&page(0, u64::from(n), 1, n, &[b"X"]));
}
let mut budget = 3;
assert_eq!(
pages(&data, &mut budget),
Err(WalkError::Limit(ResourceLimit::ItemCount))
);
}
#[test]
fn truncation_at_every_length_is_refused_but_never_panics() {
let mut data = page(BOS, 0, 1, 0, &[b"HEAD"]);
data.extend_from_slice(&page(EOS, 10, 1, 1, &[b"AUDIO"]));
for n in 0..=data.len() {
let mut budget = 4096;
if let Ok(read) = pages(&data[0..n], &mut budget) {
let _ = packets(&read);
}
}
}
}