mod crc;
mod error;
use std::io::{Read, Seek, SeekFrom};
use byteorder::{LittleEndian, ReadBytesExt};
pub use crc::crc32;
pub use error::{PageError, Result};
const CONTINUED_PACKET: u8 = 0x01;
pub const MAX_CONTENT_SIZE: usize = 65025;
pub const CONTAINS_FIRST_PAGE_OF_BITSTREAM: u8 = 0x02;
pub const CONTAINS_LAST_PAGE_OF_BITSTREAM: u8 = 0x04;
#[derive(Clone, PartialEq, Debug)]
pub struct Page {
content: Vec<u8>,
header_type: u8,
pub abgp: u64,
pub serial: u32,
pub seq_num: u32,
checksum: u32,
pub start: u64,
pub end: u64,
}
impl Page {
pub fn new(
header_type_flag: u8,
abgp: u64,
stream_serial: u32,
sequence_number: u32,
content: Vec<u8>,
) -> Result<Self> {
let content_len = content.len();
if content_len > MAX_CONTENT_SIZE {
return Err(PageError::TooMuchData);
}
Ok(Self {
content,
header_type: header_type_flag,
abgp,
serial: stream_serial,
seq_num: sequence_number,
checksum: 0,
start: 0,
end: content_len as u64,
})
}
pub fn as_bytes(&self) -> Result<Vec<u8>> {
let mut segment_table = self.segment_table()?;
let mut bytes = Vec::new();
bytes.extend(b"OggS");
bytes.push(0);
bytes.extend(self.header_type.to_le_bytes());
bytes.extend(self.abgp.to_le_bytes());
bytes.extend(self.serial.to_le_bytes());
bytes.extend(self.seq_num.to_le_bytes());
bytes.extend(self.checksum.to_le_bytes());
bytes.push(segment_table.len() as u8);
bytes.append(&mut segment_table);
bytes.extend(self.content.iter());
Ok(bytes)
}
pub fn read<V>(data: &mut V, skip_content: bool) -> Result<Self>
where
V: Read + Seek,
{
let start = data.seek(SeekFrom::Current(0))?;
let mut sig = [0; 4];
data.read_exact(&mut sig)?;
if &sig != b"OggS" {
return Err(PageError::MissingMagic);
}
let version = data.read_u8()?;
if version != 0 {
return Err(PageError::InvalidVersion);
}
let header_type = data.read_u8()?;
let abgp = data.read_u64::<LittleEndian>()?;
let serial = data.read_u32::<LittleEndian>()?;
let seq_num = data.read_u32::<LittleEndian>()?;
let checksum = data.read_u32::<LittleEndian>()?;
let segments = data.read_u8()?;
if segments < 1 {
return Err(PageError::BadSegmentCount);
}
let mut segment_table = vec![0; segments as usize];
data.read_exact(&mut segment_table)?;
let mut content: Vec<u8> = Vec::new();
let content_len: u16 = segment_table.iter().map(|&b| u16::from(b)).sum();
if skip_content {
data.seek(SeekFrom::Current(i64::from(content_len)))?;
} else {
content = vec![0; content_len as usize];
data.read_exact(&mut content)?;
}
let end = data.seek(SeekFrom::Current(0))?;
Ok(Page {
content,
header_type,
abgp,
serial,
seq_num,
checksum,
start,
end,
})
}
pub fn gen_crc(&mut self) -> Result<()> {
self.checksum = crc::crc32(&*self.as_bytes()?);
Ok(())
}
pub fn extend(&mut self, content: &[u8]) -> Result<Option<Page>> {
let self_len = self.content.len();
let content_len = content.len();
if self_len + content_len <= MAX_CONTENT_SIZE {
self.content.extend(content.iter());
self.end += content_len as u64;
return Ok(None);
}
if content_len <= MAX_CONTENT_SIZE {
let remaining = 65025 - self_len;
self.content.extend(content[0..remaining].iter());
self.header_type = 0;
self.abgp = 1_u64.wrapping_neg(); self.end += remaining as u64;
let mut p = Page {
content: content[remaining..].to_vec(),
header_type: 1,
abgp: 0,
serial: self.serial,
seq_num: self.seq_num + 1,
checksum: 0,
start: self.end,
end: self.start + content.len() as u64,
};
p.gen_crc()?;
return Ok(Some(p));
}
Ok(None)
}
pub fn content(&self) -> &[u8] {
self.content.as_slice()
}
pub fn take_content(self) -> Vec<u8> {
self.content
}
pub fn header_type(&self) -> u8 {
self.header_type
}
pub fn checksum(&self) -> u32 {
self.checksum
}
pub fn segment_table(&self) -> Result<Vec<u8>> {
segment_table(self.content.len())
}
}
#[allow(clippy::eval_order_dependence)]
pub fn paginate(packet: &[u8], stream_serial: u32, abgp: u64, flags: u8) -> Vec<Page> {
let mut pages = Vec::new();
let mut first_page = true;
let mut pos = 0;
for (idx, page) in packet.chunks(MAX_CONTENT_SIZE).enumerate() {
let p = Page {
content: page.to_vec(),
header_type: {
if first_page {
if flags & CONTAINS_FIRST_PAGE_OF_BITSTREAM == 0x02 {
CONTAINS_LAST_PAGE_OF_BITSTREAM
} else {
0
}
} else {
CONTINUED_PACKET
}
},
abgp,
serial: stream_serial,
seq_num: (idx + 1) as u32,
checksum: 0,
start: pos,
end: {
pos += page.len() as u64;
pos
},
};
first_page = false;
pages.push(p);
}
if flags & CONTAINS_LAST_PAGE_OF_BITSTREAM == 0x04 {
if let Some(last) = pages.last_mut() {
last.header_type |= CONTAINS_LAST_PAGE_OF_BITSTREAM;
}
}
if pages.len() > 1 {
let last_idx = pages.len() - 1;
for (idx, p) in pages.iter_mut().enumerate() {
if idx == last_idx {
break;
}
p.abgp = 1_u64.wrapping_neg();
}
}
pages
}
pub fn segment_table(length: usize) -> Result<Vec<u8>> {
let last_len = (length % 255) as u8;
let mut needed = length / 255;
if needed != 255 {
needed += 1
}
if needed > 255 {
return Err(PageError::TooMuchData);
}
let mut segments = Vec::with_capacity(needed);
for i in 0..needed {
if i + 1 < needed {
segments.push(255)
} else {
segments.push(last_len)
}
}
Ok(segments)
}
#[cfg(test)]
mod tests {
use crate::{paginate, segment_table, Page};
use std::io::Cursor;
#[test]
fn opus_ident_header() {
let expected = Page {
content: vec![
0x4F, 0x70, 0x75, 0x73, 0x48, 0x65, 0x61, 0x64, 0x01, 0x02, 0x38, 0x01, 0x80, 0xBB,
0, 0, 0, 0, 0,
],
header_type: 2,
abgp: 0,
serial: 1759377061,
seq_num: 0,
checksum: 3579522525,
start: 0,
end: 47,
};
let content = std::fs::read("test_assets/opus_ident_header.page").unwrap();
let page = Page::read(&mut Cursor::new(content), false).unwrap();
assert_eq!(expected, page);
}
#[test]
fn paginate_large() {
let packet = std::fs::read("test_assets/large_comment_packet.page").unwrap();
let pages = paginate(&*packet, 1234, 0, 0);
let len = pages.len();
assert_eq!(len, 17);
let last_page_content = pages.last().unwrap().content();
assert_eq!(
last_page_content.len() % 255,
*segment_table(last_page_content.len())
.unwrap()
.last()
.unwrap() as usize
);
for (i, page) in pages.into_iter().enumerate() {
assert_eq!(page.serial, 1234);
if i + 1 == len {
assert_eq!(page.abgp, 0);
} else {
assert_eq!(page.abgp, u64::MAX);
}
assert_eq!(page.seq_num, (i + 1) as u32);
if i == 0 {
assert_eq!(page.header_type, 0);
} else {
assert_eq!(page.header_type, 1);
}
}
}
}