Skip to main content

ebml_webm/
vint.rs

1//! EBML variable-length integer (VINT) decode — [RFC 8794](https://www.rfc-editor.org/rfc/rfc8794)
2//! (`docs/standards/registry.toml` id `rfc-8794-ebml`).
3//!
4//! A VINT's first byte encodes its total length `L` (1..=8) as a unary
5//! prefix: the position of the leading `1` bit (`VINT_MARKER`) counted from
6//! the most significant bit gives `L`. Every bit after the marker — the rest
7//! of the first byte plus all following bytes — is `VINT_DATA` (`7*L` bits).
8//!
9//! Two decodes share this shape but differ in what counts as "the value":
10//! - **Element size** ([`decode_size`]): the marker is stripped; an all-1s
11//!   `VINT_DATA` is the reserved "unknown size" sentinel ([`VintSize::unknown`]).
12//! - **Element ID** ([`decode_id`]): the marker bit is *kept* — the ID is the
13//!   raw `L` bytes read as a big-endian integer (RFC 8794 §7).
14//!
15//! These are public, low-level, and usable standalone (probe/debug tooling),
16//! per the workspace "low-level APIs stay first-class" rule.
17
18#![forbid(unsafe_code)]
19
20use crate::Error;
21
22/// Decoded element **size** VINT (marker stripped).
23#[derive(Debug, Clone, Copy, PartialEq, Eq)]
24pub struct VintSize {
25    /// Size in bytes, or the `VINT_DATA` bit pattern when [`Self::unknown`].
26    pub value: u64,
27    /// `true` when every `VINT_DATA` bit is `1` (RFC 8794 "unknown size").
28    pub unknown: bool,
29}
30
31/// Total VINT byte length `L` (1..=8) from the first byte's marker position.
32///
33/// `0x00` has no marker bit within 8 bytes — [`Error::ReservedVint`].
34const fn vint_length(first_byte: u8) -> Result<u8, Error> {
35    if first_byte == 0 {
36        return Err(Error::ReservedVint);
37    }
38    // leading_zeros() on u8 is 0..=7 here (first_byte != 0), so +1 is 1..=8.
39    Ok(first_byte.leading_zeros() as u8 + 1)
40}
41
42/// Decode an element **size** VINT at the start of `buf` (marker stripped).
43///
44/// Returns `(size, bytes_consumed)`. [`Error::Incomplete`] means `buf` is a
45/// truncated prefix — callers feeding a growing sans-io buffer should wait
46/// for more bytes and retry, not treat it as malformed.
47///
48/// # Errors
49///
50/// [`Error::Incomplete`] on a truncated buffer; [`Error::ReservedVint`] on an
51/// invalid (all-zero) leading byte.
52pub fn decode_size(buf: &[u8]) -> Result<(VintSize, usize), Error> {
53    let first = *buf.first().ok_or(Error::Incomplete)?;
54    let len = vint_length(first)? as usize;
55    if buf.len() < len {
56        return Err(Error::Incomplete);
57    }
58    let shift = 8 - len as u32; // 0..=7
59    let mask = ((1u16 << shift) - 1) as u8;
60    let mut value = u64::from(first & mask);
61    for &b in &buf[1..len] {
62        value = (value << 8) | u64::from(b);
63    }
64    let data_bits = 7 * len as u32; // 7..=56
65    let unknown = value == (1u64 << data_bits) - 1;
66    Ok((VintSize { value, unknown }, len))
67}
68
69/// Decode an element **ID** VINT at the start of `buf` (marker bits kept).
70///
71/// Returns `(id, bytes_consumed)`. `WebM` element IDs are at most 4 bytes;
72/// a longer marker yields [`Error::Unsupported`] rather than overflowing.
73///
74/// # Errors
75///
76/// [`Error::Incomplete`] on a truncated buffer; [`Error::ReservedVint`] on an
77/// invalid leading byte; [`Error::Unsupported`] for IDs longer than 4 bytes.
78pub fn decode_id(buf: &[u8]) -> Result<(u32, usize), Error> {
79    let first = *buf.first().ok_or(Error::Incomplete)?;
80    let len = vint_length(first)? as usize;
81    if len > 4 {
82        return Err(Error::Unsupported("element ID longer than 4 bytes"));
83    }
84    if buf.len() < len {
85        return Err(Error::Incomplete);
86    }
87    let mut value: u32 = 0;
88    for &b in &buf[..len] {
89        value = (value << 8) | u32::from(b);
90    }
91    Ok((value, len))
92}
93
94/// Encode an element **ID** (marker bits already included, matching
95/// [`decode_id`]'s raw representation) into `out`, using the minimal byte
96/// length that holds the value without a leading zero byte.
97///
98/// Never panics: `id == 0` has no valid EBML representation (every `ids`
99/// constant this crate writes is non-zero), but rather than panic on
100/// caller misuse this writes a single `0x00` byte — round-trips back to
101/// [`Error::ReservedVint`] on decode instead of crashing the writer.
102pub fn encode_id(id: u32, out: &mut Vec<u8>) {
103    let len = (4 - (id.leading_zeros() / 8) as usize).max(1);
104    out.extend_from_slice(&id.to_be_bytes()[4 - len..]);
105}
106
107/// Encode an element **size** VINT (marker stripped from `value`, marker bit
108/// added on write) into `out`.
109///
110/// Uses the minimal byte length `L` (1..=8) that fits `value` in `7*L` data
111/// bits. The all-1s `VINT_DATA` pattern is reserved for "unknown size"
112/// ([`decode_size`]), so a `value` that would exactly fill all-1s bumps to
113/// the next length.
114///
115/// Never panics: a `value` that doesn't fit even 8 bytes' worth of VINT data
116/// (56 bits — not reachable for any size this crate itself ever writes, but
117/// `push_frame`'s caller-supplied `track_number` is technically unbounded)
118/// saturates to the largest representable 8-byte value rather than crashing.
119pub fn encode_size(value: u64, out: &mut Vec<u8>) {
120    let mut len = 1u32;
121    while len < 8 && value >= (1u64 << (7 * len)) - 1 {
122        len += 1;
123    }
124    let max_at_len = (1u64 << (7 * len)) - 2; // reserve the all-1s "unknown" pattern
125    let value = value.min(max_at_len);
126    let marker = 1u64 << (7 * len);
127    let encoded = marker | value;
128    out.extend_from_slice(&encoded.to_be_bytes()[8 - len as usize..]);
129}
130
131/// Write the reserved "unknown size" VINT of length `len` (1..=8) into `out`
132/// — all `VINT_DATA` bits set to `1`, marker bit set.
133///
134/// Used for a `Segment` mux writes as always-unknown-size (streaming: total
135/// length isn't known upfront). `len` outside `1..=8` clamps rather than
136/// panics (this crate only ever calls it with the literal `4`; kept total
137/// for a public fn).
138pub fn encode_unknown_size(len: u8, out: &mut Vec<u8>) {
139    let len = len.clamp(1, 8);
140    // First byte: (len - 1) leading zero bits, then the marker bit and all
141    // remaining data bits set to 1 — e.g. len=4 -> 0b0001_1111 (0x1F).
142    let first = ((1u16 << (9 - len)) - 1) as u8;
143    out.push(first);
144    for _ in 1..len {
145        out.push(0xFF);
146    }
147}
148
149#[cfg(test)]
150#[path = "vint_tests.rs"]
151mod tests;