use crate::error::{Error, Result};
use crate::mkv::ids;
pub(crate) const MAX_ID_LEN: usize = 4;
pub(crate) const MAX_SIZE_LEN: usize = 8;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Header {
pub id: u32,
pub size: Option<u64>,
pub len: u64,
}
impl Header {
pub fn known_size(&self, path: &str, offset: u64) -> Result<u64> {
self.size.ok_or_else(|| {
Error::mkv_malformed(
path,
offset,
"only Segment and Cluster can have an unknown size",
)
})
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Vint {
Value(u64, usize),
Short,
Invalid,
}
pub(crate) fn vint(bytes: &[u8]) -> Vint {
let Some(&first) = bytes.first() else {
return Vint::Short;
};
if first == 0 {
return Vint::Invalid;
}
let len = first.leading_zeros() as usize + 1;
let Some(all) = bytes.get(..len) else {
return Vint::Short;
};
let mask = (0xFFu32 >> len) as u8;
let value = all
.iter()
.skip(1)
.fold(u64::from(first & mask), |acc, &byte| {
(acc << 8) | u64::from(byte)
});
Vint::Value(value, len)
}
fn all_ones(len: usize) -> u64 {
(1u64 << (7 * len)) - 1
}
pub(crate) fn header(bytes: &[u8], offset: u64, path: &str) -> Result<Option<Header>> {
let Some(&first) = bytes.first() else {
return Ok(None);
};
let id_len = first.leading_zeros() as usize + 1;
if first == 0 || id_len > MAX_ID_LEN {
return Err(Error::mkv_malformed(
path,
offset,
format!(
"an element ID starts with byte {first:#04x}; an ID holds 1 to {MAX_ID_LEN} bytes"
),
));
}
let Some(id_bytes) = bytes.get(..id_len) else {
return Ok(None);
};
let id = id_bytes
.iter()
.fold(0u32, |acc, &byte| (acc << 8) | u32::from(byte));
let rest = bytes.get(id_len..).unwrap_or_default();
let (size, size_len) = match vint(rest) {
Vint::Short => return Ok(None),
Vint::Invalid => {
return Err(Error::mkv_malformed(
format!("{path}/{}", ids::name(id)),
offset,
format!(
"the element size starts with byte 0x00; a size holds 1 to {MAX_SIZE_LEN} bytes"
),
));
}
Vint::Value(value, len) => (value, len),
};
Ok(Some(Header {
id,
size: (size != all_ones(size_len)).then_some(size),
len: (id_len + size_len) as u64,
}))
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct Element<'a> {
pub id: u32,
pub offset: u64,
pub body_offset: u64,
pub body: &'a [u8],
}
impl<'a> Element<'a> {
fn malformed(&self, path: &str, reason: impl Into<String>) -> Error {
Error::mkv_malformed(path, self.offset, reason)
}
pub fn uint(&self, path: &str) -> Result<u64> {
if self.body.len() > 8 {
return Err(self.malformed(
path,
format!(
"an unsigned integer holds at most 8 bytes, this one {}",
self.body.len()
),
));
}
Ok(self
.body
.iter()
.fold(0u64, |acc, &byte| (acc << 8) | u64::from(byte)))
}
pub fn int(&self, path: &str) -> Result<i64> {
let raw = self.uint(path)?;
let bits = 8 * self.body.len() as u32;
if bits == 0 || bits == 64 {
return Ok(raw as i64);
}
let shift = 64 - bits;
Ok(((raw << shift) as i64) >> shift)
}
pub fn float(&self, path: &str) -> Result<f64> {
match self.body.len() {
0 => Ok(0.0),
4 => Ok(f64::from(f32::from_bits(self.uint(path)? as u32))),
8 => Ok(f64::from_bits(self.uint(path)?)),
len => Err(self.malformed(
path,
format!("a float holds 0, 4 or 8 bytes, this one {len}"),
)),
}
}
pub fn string(&self, path: &str) -> Result<String> {
let end = self
.body
.iter()
.rposition(|&byte| byte != 0)
.map_or(0, |last| last + 1);
let text = self.body.get(..end).unwrap_or_default();
std::str::from_utf8(text)
.map(str::to_owned)
.map_err(|err| self.malformed(path, format!("the text is not UTF-8: {err}")))
}
pub fn binary(&self) -> Vec<u8> {
self.body.to_vec()
}
pub fn children(&self, path: &'a str) -> Children<'a> {
Children::new(self.body, self.body_offset, path)
}
}
#[derive(Debug)]
pub(crate) struct Children<'a> {
bytes: &'a [u8],
base: u64,
pos: usize,
path: &'a str,
done: bool,
}
impl<'a> Children<'a> {
pub fn new(bytes: &'a [u8], base: u64, path: &'a str) -> Self {
Self {
bytes,
base,
pos: 0,
path,
done: false,
}
}
fn step(&mut self) -> Result<Option<Element<'a>>> {
let rest = self.bytes.get(self.pos..).unwrap_or_default();
if rest.is_empty() {
return Ok(None);
}
let offset = self.base + self.pos as u64;
let header = header(rest, offset, self.path)?.ok_or_else(|| {
Error::mkv_malformed(
self.path,
offset,
"an element header runs past the end of its parent",
)
})?;
let child = format!("{}/{}", self.path, ids::name(header.id));
let size = header.known_size(&child, offset)?;
let remaining = rest.len() as u64 - header.len;
if size > remaining {
return Err(Error::mkv_malformed(
&child,
offset,
format!("the element declares {size} bytes, but its parent holds {remaining} more"),
));
}
let start = header.len as usize;
let end = start + size as usize;
self.pos += end;
Ok(Some(Element {
id: header.id,
offset,
body_offset: offset + header.len,
body: rest.get(start..end).unwrap_or_default(),
}))
}
}
impl<'a> Iterator for Children<'a> {
type Item = Result<Element<'a>>;
fn next(&mut self) -> Option<Self::Item> {
while !self.done {
match self.step() {
Ok(Some(element)) if matches!(element.id, ids::VOID | ids::CRC32) => continue,
Ok(Some(element)) => return Some(Ok(element)),
Ok(None) => self.done = true,
Err(err) => {
self.done = true;
return Some(Err(err));
}
}
}
None
}
}
pub(crate) fn child_path(parent: &str, id: u32) -> String {
format!("{parent}/{}", ids::name(id))
}
pub(crate) fn indexed_path(parent: &str, id: u32, n: usize) -> String {
format!("{parent}/{}[{n}]", ids::name(id))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vint_decodes_each_length() {
assert_eq!(vint(&[0x81]), Vint::Value(1, 1));
assert_eq!(vint(&[0x40, 0x02]), Vint::Value(2, 2));
assert_eq!(vint(&[0x01, 0, 0, 0, 0, 0, 0, 3]), Vint::Value(3, 8));
assert_eq!(vint(&[0x40]), Vint::Short);
assert_eq!(vint(&[0x00]), Vint::Invalid);
}
#[test]
fn header_reads_unknown_sizes_of_every_length() {
let one = header(&[0x1F, 0x43, 0xB6, 0x75, 0xFF], 0, "t")
.ok()
.flatten();
assert_eq!(one.map(|h| h.size), Some(None));
let eight = [
0x1F, 0x43, 0xB6, 0x75, 0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
];
let eight = header(&eight, 0, "t").ok().flatten();
assert_eq!(
eight.map(|h| (h.id, h.size, h.len)),
Some((ids::CLUSTER, None, 12))
);
}
#[test]
fn header_refuses_long_ids() {
assert!(header(&[0x08, 0, 0, 0, 0, 0x80], 0, "t").is_err());
}
#[test]
fn leaves_decode_signed_and_float_values() {
fn element(body: &[u8]) -> Element<'_> {
Element {
id: 0,
offset: 0,
body_offset: 0,
body,
}
}
assert_eq!(element(&[0xFF, 0xFE]).int("t").ok(), Some(-2));
assert_eq!(
element(&[0x40, 0xE7, 0x70, 0, 0, 0, 0, 0]).float("t").ok(),
Some(48_000.0)
);
assert_eq!(element(b"und\0\0").string("t").ok().as_deref(), Some("und"));
assert!(element(&[0; 9]).uint("t").is_err());
}
#[test]
fn children_skip_void_and_refuse_overruns() {
let body = [0xEC, 0x81, 0x00, 0xD7, 0x81, 0x05];
let ids: Vec<u32> = Children::new(&body, 0, "t")
.filter_map(|child| child.ok().map(|c| c.id))
.collect();
assert_eq!(ids, vec![ids::TRACK_NUMBER]);
let overrun = [0xD7, 0x85, 0x05];
assert!(Children::new(&overrun, 0, "t").any(|child| child.is_err()));
}
}