Skip to main content

mlt_core/dump/
model.rs

1//! Data model for the annotated binary dump (see [`crate::dump`]).
2
3use std::fmt::{Display, Formatter, Result as FmtResult};
4
5use crate::wire::StreamMeta;
6
7/// Whether a region is tile metadata or an opaque data payload.
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
9pub enum RegionKind {
10    /// Framing, schema, or stream-header bytes, annotated byte- and bit-for-byte.
11    Meta,
12    /// A stream payload, rendered as raw hex plus best-effort decoded values.
13    DataBlob,
14}
15
16/// How a [`RegionKind::DataBlob`] payload is decoded for display.
17///
18/// Best-effort: on any decode error the renderer falls back to raw hex.
19#[derive(Debug, Clone, Copy, PartialEq, Eq)]
20pub enum DecodeHint {
21    /// Nullability bitmap (byte-RLE -> packed bits).
22    Presence,
23    /// Boolean data stream (byte-RLE -> bools).
24    Bool,
25    /// Signed 32-bit integers (`i8`/`i32` columns).
26    I32,
27    /// Unsigned 32-bit integers (`u8`/`u32` columns, offsets, lengths).
28    U32,
29    /// Signed 64-bit integers (`i64` columns).
30    I64,
31    /// ALP offsets, put back on their frame of reference.
32    #[cfg(feature = "unstable-v2")]
33    Alp(crate::decoder::Alp),
34    /// Unsigned 64-bit integers (`u64` columns, 64-bit ids).
35    U64,
36    /// 32-bit floats.
37    F32,
38    /// 64-bit floats.
39    F64,
40    /// Opaque bytes (string / dictionary / FSST payloads); shown as hex + UTF-8 preview.
41    Bytes,
42    /// A raw LSB0 bitfield, not a stream: `ceil(num_values/8)` packed bytes.
43    #[cfg(feature = "unstable-v2")]
44    PackedBits,
45}
46
47/// One sub-field of a bit-packed byte, e.g. a nibble of `stream_type`.
48#[derive(Debug, Clone)]
49pub struct BitField {
50    /// Inclusive high bit index (7..=0, MSB first).
51    hi: u8,
52    /// Inclusive low bit index.
53    lo: u8,
54    /// The extracted field value, shifted down to bit 0.
55    raw: u64,
56    /// Human-readable meaning, e.g. `"physical = VarInt"`.
57    meaning: String,
58}
59
60impl BitField {
61    /// One field of a packed byte, located by the same mask constant the parser reads it
62    /// with, so the dump cannot drift from the wire format.
63    ///
64    /// `raw` is the masked bits shifted down to bit 0, so it renders as the field's own
65    /// value rather than its in-byte position. `mask` must be one contiguous run of bits.
66    pub fn mask(mask: u8, byte: u8, meaning: impl Into<String>) -> Self {
67        let (hi, lo) = mask_bounds(mask);
68        Self {
69            hi,
70            lo,
71            raw: u64::from((byte & mask) >> lo),
72            meaning: meaning.into(),
73        }
74    }
75
76    /// A one-bit flag, worded as a phrase instead of a `0`/`1`.
77    ///
78    /// The renderer already prefixes every bit line with `bit N = V ->`, so `when_set`
79    /// and `when_clear` should say what the bit asserts about the tile rather than
80    /// repeat its value.
81    #[must_use]
82    pub fn flag(mask: u8, byte: u8, when_set: &str, when_clear: &str) -> Self {
83        debug_assert_eq!(mask.count_ones(), 1, "a flag occupies exactly one bit");
84        let meaning = if byte & mask == 0 {
85            when_clear
86        } else {
87            when_set
88        };
89        Self::mask(mask, byte, meaning)
90    }
91
92    /// What this field means in prose, without the `bit N = V ->` prefix
93    /// [`Display`] puts in front of it.
94    #[must_use]
95    pub fn meaning(&self) -> &str {
96        &self.meaning
97    }
98}
99
100impl Display for BitField {
101    /// `bit 7 = 1 -> meaning`, or `bits 6-4 = 011 -> meaning` for a multi-bit field.
102    ///
103    /// The value is printed as wide as the field, so the leading zeros of a nibble are
104    /// not mistaken for a narrower field. The caller adds any indent and color.
105    fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
106        if self.hi == self.lo {
107            write!(f, "bit {}", self.hi)?;
108        } else {
109            write!(f, "bits {}-{}", self.hi, self.lo)?;
110        }
111        let width = usize::from(self.hi - self.lo + 1);
112        write!(f, " = {:0width$b} -> {}", self.raw, self.meaning)
113    }
114}
115
116/// Inclusive `(hi, lo)` bit indices spanned by `mask`.
117#[expect(
118    clippy::cast_possible_truncation,
119    reason = "a u8 mask spans at most 8 bits"
120)]
121fn mask_bounds(mask: u8) -> (u8, u8) {
122    debug_assert!(mask != 0, "a bit field needs at least one bit");
123    let lo = mask.trailing_zeros() as u8;
124    let hi = (u8::BITS - 1 - mask.leading_zeros()) as u8;
125    debug_assert_eq!(
126        u32::from(hi - lo) + 1,
127        mask.count_ones(),
128        "a bit field's mask must be one contiguous run of bits"
129    );
130    (hi, lo)
131}
132
133/// Stream metadata attached to a [`RegionKind::DataBlob`] so the renderer can decode it.
134#[derive(Debug, Clone, Copy)]
135pub struct BlobInfo {
136    pub meta: StreamMeta,
137    pub hint: DecodeHint,
138}
139
140/// A single annotated span of the tile buffer.
141///
142/// Emitted in pre-order. Containers bracket their children and may overlap them.
143/// Leaf regions partition the buffer exactly; the coverage test relies on this.
144#[derive(Debug, Clone)]
145pub struct Region {
146    /// Absolute byte offset into the tile buffer.
147    pub offset: usize,
148    pub len: usize,
149    /// Nesting depth, for indentation.
150    pub depth: usize,
151    /// Short label, e.g. `"column[2].type"` or `"num_values"`.
152    pub label: String,
153    /// Rendered scalar value (varint value, string, enum name), if any.
154    pub value: Option<String>,
155    /// Bit-level breakdown; empty unless this is a bit-packed byte.
156    pub bits: Vec<BitField>,
157    pub kind: RegionKind,
158    /// True for structural groups that span their children (excluded from coverage).
159    pub container: bool,
160    /// Present for `DataBlob` regions that carry decodable stream metadata.
161    pub blob: Option<BlobInfo>,
162}
163
164/// The full annotation of a tile: a flat, depth-tagged region list.
165pub struct DumpTree {
166    pub buf_len: usize,
167    /// Regions in pre-order (containers before their children).
168    pub regions: Vec<Region>,
169}