use alloc::vec::Vec;
use core::fmt;
use super::crc;
pub const CAPTURE_PATTERN: [u8; 4] = *b"OggS";
pub(crate) const HEADER_LEN: usize = 27;
pub(crate) const MAX_SEGMENTS: usize = 255;
pub const NO_GRANULE: u64 = u64::MAX;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum OggError {
MissingCapturePattern,
UnsupportedVersion(u8),
Truncated,
BadCrc,
}
impl fmt::Display for OggError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
OggError::MissingCapturePattern => f.write_str("missing OggS capture pattern"),
OggError::UnsupportedVersion(v) => write!(f, "unsupported Ogg version {v}"),
OggError::Truncated => f.write_str("page truncated"),
OggError::BadCrc => f.write_str("page CRC mismatch"),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for OggError {}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Page<'a> {
pub continued: bool,
pub bos: bool,
pub eos: bool,
pub granule_position: u64,
pub serial: u32,
pub sequence: u32,
pub segments: &'a [u8],
pub body: &'a [u8],
}
impl<'a> Page<'a> {
pub fn parse(data: &'a [u8]) -> Result<(Self, usize), OggError> {
if data.len() < HEADER_LEN {
return Err(
if data.starts_with(&CAPTURE_PATTERN) || CAPTURE_PATTERN.starts_with(data) {
OggError::Truncated
} else {
OggError::MissingCapturePattern
},
);
}
if data[0..4] != CAPTURE_PATTERN {
return Err(OggError::MissingCapturePattern);
}
if data[4] != 0 {
return Err(OggError::UnsupportedVersion(data[4]));
}
let n_segments = usize::from(data[26]);
let body_start = HEADER_LEN + n_segments;
if data.len() < body_start {
return Err(OggError::Truncated);
}
let segments = &data[HEADER_LEN..body_start];
let body_len: usize = segments.iter().map(|&v| usize::from(v)).sum();
let total = body_start + body_len;
if data.len() < total {
return Err(OggError::Truncated);
}
let declared_crc = u32::from_le_bytes([data[22], data[23], data[24], data[25]]);
let mut actual = crc::update(0, &data[..22]);
actual = crc::update(actual, &[0, 0, 0, 0]);
actual = crc::update(actual, &data[26..total]);
if actual != declared_crc {
return Err(OggError::BadCrc);
}
let flags = data[5];
Ok((
Page {
continued: flags & 0x01 != 0,
bos: flags & 0x02 != 0,
eos: flags & 0x04 != 0,
granule_position: u64::from_le_bytes([
data[6], data[7], data[8], data[9], data[10], data[11], data[12], data[13],
]),
serial: u32::from_le_bytes([data[14], data[15], data[16], data[17]]),
sequence: u32::from_le_bytes([data[18], data[19], data[20], data[21]]),
segments,
body: &data[body_start..total],
},
total,
))
}
}
#[derive(Debug, Clone)]
pub struct PageReader<'a> {
data: &'a [u8],
pos: usize,
}
impl<'a> PageReader<'a> {
#[must_use]
pub const fn new(data: &'a [u8]) -> Self {
PageReader { data, pos: 0 }
}
#[must_use]
pub const fn position(&self) -> usize {
self.pos
}
}
impl<'a> Iterator for PageReader<'a> {
type Item = Page<'a>;
fn next(&mut self) -> Option<Page<'a>> {
while self.pos < self.data.len() {
match Page::parse(&self.data[self.pos..]) {
Ok((page, consumed)) => {
self.pos += consumed;
return Some(page);
},
Err(OggError::Truncated) => return None,
Err(_) => {
let from = self.pos + 1;
match find_capture(&self.data[from..]) {
Some(off) => self.pos = from + off,
None => return None,
}
},
}
}
None
}
}
fn find_capture(data: &[u8]) -> Option<usize> {
data.windows(4).position(|w| w == CAPTURE_PATTERN)
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct OggPacket {
pub data: Vec<u8>,
pub granule_position: u64,
pub eos: bool,
pub completes_page: bool,
}
#[derive(Debug, Clone)]
pub struct PacketReader<'a> {
pages: PageReader<'a>,
serial: u32,
partial: Vec<u8>,
have_partial: bool,
poisoned: bool,
last_sequence: Option<u32>,
ready: alloc::collections::VecDeque<OggPacket>,
}
const MAX_PACKET_LEN: usize = 16 * 1024 * 1024;
impl<'a> PacketReader<'a> {
#[must_use]
pub fn new(data: &'a [u8], serial: u32) -> Self {
PacketReader {
pages: PageReader::new(data),
serial,
partial: Vec::new(),
have_partial: false,
poisoned: false,
last_sequence: None,
ready: alloc::collections::VecDeque::new(),
}
}
fn ingest(&mut self, page: &Page<'a>) {
let consecutive = self
.last_sequence
.is_none_or(|prev| page.sequence == prev.wrapping_add(1));
self.last_sequence = Some(page.sequence);
if !consecutive || page.continued != self.have_partial {
self.partial.clear();
self.have_partial = false;
self.poisoned = false;
}
let mut offset = 0usize;
let mut last_complete_idx: Option<usize> = None;
let mut iter = page.segments.iter().peekable();
while let Some(&lacing) = iter.next() {
let len = usize::from(lacing);
if self.partial.len() + len > MAX_PACKET_LEN {
self.poisoned = true;
self.partial.clear();
}
if !self.poisoned {
self.partial.extend_from_slice(&page.body[offset..offset + len]);
}
offset += len;
self.have_partial = true;
if lacing < 255 {
let data = core::mem::take(&mut self.partial);
let poisoned = core::mem::take(&mut self.poisoned);
self.have_partial = false;
if !poisoned {
self.ready.push_back(OggPacket {
data,
granule_position: NO_GRANULE,
eos: page.eos,
completes_page: iter.peek().is_none(),
});
last_complete_idx = Some(self.ready.len() - 1);
}
}
}
if let Some(idx) = last_complete_idx {
self.ready[idx].granule_position = page.granule_position;
}
}
}
impl<'a> Iterator for PacketReader<'a> {
type Item = OggPacket;
fn next(&mut self) -> Option<OggPacket> {
loop {
if let Some(pkt) = self.ready.pop_front() {
return Some(pkt);
}
let page = self.pages.find(|p| p.serial == self.serial)?;
self.ingest(&page);
}
}
}
#[derive(Debug, Clone)]
pub struct PageWriter {
serial: u32,
sequence: u32,
segments: Vec<u8>,
body: Vec<u8>,
page_granule: u64,
continued: bool,
first: bool,
}
impl PageWriter {
#[must_use]
pub const fn new(serial: u32) -> Self {
PageWriter {
serial,
sequence: 0,
segments: Vec::new(),
body: Vec::new(),
page_granule: NO_GRANULE,
continued: false,
first: true,
}
}
pub fn push(&mut self, out: &mut Vec<u8>, packet: &[u8], granule_position: u64, end_of_stream: bool) {
let mut remaining = packet;
loop {
let take = remaining.len().min(255);
self.segments.push(take as u8);
self.body.extend_from_slice(&remaining[..take]);
remaining = &remaining[take..];
let packet_done = take < 255;
if packet_done {
self.page_granule = granule_position;
}
if self.segments.len() == MAX_SEGMENTS {
self.emit(out, end_of_stream && packet_done);
self.continued = !packet_done;
}
if packet_done {
break;
}
}
if end_of_stream && !self.segments.is_empty() {
self.emit(out, true);
}
}
pub fn flush(&mut self, out: &mut Vec<u8>) {
if !self.segments.is_empty() {
self.emit(out, false);
}
}
fn emit(&mut self, out: &mut Vec<u8>, eos: bool) {
let mut flags = 0u8;
if self.continued {
flags |= 0x01;
}
if self.first {
flags |= 0x02;
}
if eos {
flags |= 0x04;
}
let header_start = out.len();
out.extend_from_slice(&CAPTURE_PATTERN);
out.push(0); out.push(flags);
out.extend_from_slice(&self.page_granule.to_le_bytes());
out.extend_from_slice(&self.serial.to_le_bytes());
out.extend_from_slice(&self.sequence.to_le_bytes());
let crc_at = out.len();
out.extend_from_slice(&[0, 0, 0, 0]);
out.push(self.segments.len() as u8);
out.extend_from_slice(&self.segments);
out.extend_from_slice(&self.body);
let crc = crc::update(0, &out[header_start..]);
out[crc_at..crc_at + 4].copy_from_slice(&crc.to_le_bytes());
self.sequence = self.sequence.wrapping_add(1);
self.segments.clear();
self.body.clear();
self.page_granule = NO_GRANULE;
self.continued = false;
self.first = false;
}
}