use crate::error::{Container, Error, Result};
use crate::mkv::ids;
pub(crate) const MAX_BODY: u64 = (1 << 56) - 2;
pub(crate) const UNKNOWN_SIZE: [u8; 8] = [0x01, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF];
fn too_large(id: u32, size: u64) -> Error {
Error::InvalidMux {
container: Container::Matroska,
reason: format!(
"element `{}` holds {size} bytes, above the EBML limit of {MAX_BODY}. Write less data \
into one output",
ids::name(id)
),
}
}
pub(crate) fn push_id(out: &mut Vec<u8>, id: u32) {
let bytes = id.to_be_bytes();
let skip = (id.leading_zeros() / 8) as usize;
out.extend_from_slice(bytes.get(skip..).unwrap_or_default());
}
pub(crate) fn push_size(out: &mut Vec<u8>, id: u32, size: u64) -> Result<()> {
if size > MAX_BODY {
return Err(too_large(id, size));
}
let mut len = 1usize;
while len < 8 && size >= (1u64 << (7 * len)) - 1 {
len += 1;
}
let bytes = (size | (1u64 << (7 * len))).to_be_bytes();
out.extend_from_slice(bytes.get(8 - len..).unwrap_or_default());
Ok(())
}
pub(crate) fn fixed_size(id: u32, size: u64) -> Result<[u8; 8]> {
if size > MAX_BODY {
return Err(too_large(id, size));
}
Ok((size | (1u64 << 56)).to_be_bytes())
}
pub(crate) fn push_element(out: &mut Vec<u8>, id: u32, parts: &[&[u8]]) -> Result<()> {
let size: u64 = parts.iter().map(|part| part.len() as u64).sum();
push_id(out, id);
push_size(out, id, size)?;
for part in parts {
out.extend_from_slice(part);
}
Ok(())
}
pub(crate) fn void(total: usize) -> Vec<u8> {
if total < 2 {
return Vec::new();
}
let mut out = Vec::with_capacity(total);
out.push(ids::VOID as u8);
if total - 2 <= 126 {
out.push(0x80 | (total - 2) as u8);
} else {
let body = total - 3;
out.push(0x40 | ((body >> 8) & 0x3F) as u8);
out.push((body & 0xFF) as u8);
}
out.resize(total, 0);
out
}
#[derive(Debug, Default)]
pub(crate) struct ElementBuf {
bytes: Vec<u8>,
}
impl ElementBuf {
pub fn new() -> Self {
Self::default()
}
pub fn as_bytes(&self) -> &[u8] {
&self.bytes
}
pub fn into_bytes(self) -> Vec<u8> {
self.bytes
}
pub fn is_empty(&self) -> bool {
self.bytes.is_empty()
}
pub fn binary(&mut self, id: u32, body: &[u8]) -> Result<()> {
push_element(&mut self.bytes, id, &[body])
}
pub fn text(&mut self, id: u32, value: &str) -> Result<()> {
self.binary(id, value.as_bytes())
}
pub fn uint(&mut self, id: u32, value: u64) -> Result<()> {
let bytes = value.to_be_bytes();
let skip = ((value.leading_zeros() / 8) as usize).min(7);
self.binary(id, bytes.get(skip..).unwrap_or_default())
}
pub fn fixed_uint(&mut self, id: u32, value: u64) -> Result<()> {
self.binary(id, &value.to_be_bytes())
}
pub fn int(&mut self, id: u32, value: i64) -> Result<()> {
let bytes = value.to_be_bytes();
let redundant = if value < 0 {
value.leading_ones()
} else {
value.leading_zeros()
};
let skip = ((redundant.saturating_sub(1) / 8) as usize).min(7);
self.binary(id, bytes.get(skip..).unwrap_or_default())
}
pub fn float(&mut self, id: u32, value: f64) -> Result<()> {
self.binary(id, &value.to_bits().to_be_bytes())
}
pub fn master(&mut self, id: u32, children: &ElementBuf) -> Result<()> {
self.binary(id, children.as_bytes())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::mkv::ebml::{Children, Vint, header, vint};
#[test]
fn sizes_take_the_shortest_form_and_skip_the_unknown_marker() {
let encode = |size: u64| {
let mut out = Vec::new();
push_size(&mut out, ids::VOID, size).expect("the size fits");
out
};
assert_eq!(encode(0), [0x80]);
assert_eq!(encode(126), [0xFE]);
assert_eq!(encode(127), [0x40, 0x7F]);
assert_eq!(encode(16_382), [0x7F, 0xFE]);
assert_eq!(encode(16_383), [0x20, 0x3F, 0xFF]);
assert_eq!(vint(&encode(MAX_BODY)), Vint::Value(MAX_BODY, 8));
let mut out = Vec::new();
assert!(push_size(&mut out, ids::VOID, MAX_BODY + 1).is_err());
}
#[test]
fn fixed_sizes_read_back() {
let bytes = fixed_size(ids::SEGMENT, 300).expect("the size fits");
assert_eq!(vint(&bytes), Vint::Value(300, 8));
assert!(fixed_size(ids::SEGMENT, MAX_BODY + 1).is_err());
}
#[test]
fn void_has_the_exact_length_at_both_size_forms() {
for total in [2usize, 3, 128, 129, 131, 300] {
let bytes = void(total);
assert_eq!(bytes.len(), total);
let parsed = header(&bytes, 0, "t").ok().flatten().expect("a header");
assert_eq!(parsed.id, ids::VOID);
assert_eq!(
parsed.len + parsed.size.expect("a known size"),
total as u64
);
}
assert!(void(1).is_empty());
}
#[test]
fn leaves_read_back_through_the_reader() {
let mut buf = ElementBuf::new();
for &value in &[0u64, 1, 255, 256, u64::MAX] {
buf.uint(ids::TRACK_NUMBER, value).expect("the value fits");
}
for &value in &[0i64, 1, -1, 127, 128, -128, -129, i64::MIN, i64::MAX] {
buf.int(ids::REFERENCE_BLOCK, value)
.expect("the value fits");
}
buf.float(ids::DURATION, 1.5).expect("the value fits");
buf.text(ids::TITLE, "t").expect("the value fits");
let bytes = buf.into_bytes();
let children: Vec<_> = Children::new(&bytes, 0, "t")
.map(|child| child.expect("the element is whole"))
.collect();
let uints: Vec<u64> = children
.iter()
.filter(|child| child.id == ids::TRACK_NUMBER)
.map(|child| child.uint("t").expect("an integer"))
.collect();
assert_eq!(uints, vec![0, 1, 255, 256, u64::MAX]);
let ints: Vec<i64> = children
.iter()
.filter(|child| child.id == ids::REFERENCE_BLOCK)
.map(|child| child.int("t").expect("an integer"))
.collect();
assert_eq!(
ints,
vec![0, 1, -1, 127, 128, -128, -129, i64::MIN, i64::MAX]
);
let lengths: Vec<usize> = children
.iter()
.filter(|child| child.id == ids::REFERENCE_BLOCK)
.map(|child| child.body.len())
.collect();
assert_eq!(lengths, vec![1, 1, 1, 1, 2, 1, 2, 8, 8]);
}
}