Skip to main content

mlt_core/dump/
walker.rs

1//! Shared scaffolding for the tag-specific annotating walkers.
2//!
3//! [`Walker`] records an annotated [`Region`] per field while the per-tag
4//! `walker01` / `walker02` modules mirror their wire layout.
5//! Tile and layer framing is shared - only the layer body differs per tag.
6
7use super::UNANNOTATED;
8use super::model::{BitField, BlobInfo, DumpTree, Region, RegionKind};
9use crate::codecs::varint::parse_varint;
10use crate::utils::{parse_u8, take};
11use crate::{MltError, MltRefResult, MltResult, Parser};
12
13/// Walk a whole tile buffer, producing an annotated [`DumpTree`].
14///
15/// The returned tree references offsets into `buf`; keep `buf` alive to render it.
16/// A walk that bails still hands back everything it annotated, sealed into a tree whose
17/// leaves partition the buffer, alongside the error that stopped it.
18#[must_use]
19pub fn annotate_tile(buf: &[u8]) -> (DumpTree, Option<MltError>) {
20    let mut w = Walker {
21        buf,
22        out: Vec::new(),
23        depth: 0,
24        open_containers: Vec::new(),
25        parser: Parser::default(),
26    };
27    let err = w.walk_tile().err();
28    if err.is_some() {
29        w.seal_partial();
30    }
31    let tree = DumpTree {
32        buf_len: buf.len(),
33        regions: w.out,
34    };
35    (tree, err)
36}
37
38pub(super) struct Walker<'a> {
39    pub(super) buf: &'a [u8],
40    pub(super) out: Vec<Region>,
41    pub(super) depth: usize,
42    /// Containers opened but not yet closed, innermost last.
43    open_containers: Vec<usize>,
44    /// Throwaway budget for the authoritative stream-header parsers.
45    pub(super) parser: Parser,
46}
47
48impl<'a> Walker<'a> {
49    /// Absolute offset of `s`, which must be a subslice of the tile buffer.
50    pub(super) fn off(&self, s: &'a [u8]) -> usize {
51        self.buf
52            .subslice_range(s)
53            .expect("annotated slice must come from the tile buffer")
54            .start
55    }
56
57    /// Open a container region spanning children; returns its index for [`Walker::close`].
58    pub(super) fn open(&mut self, at: &'a [u8], label: impl Into<String>) -> usize {
59        let idx = self.out.len();
60        self.out.push(Region {
61            offset: self.off(at),
62            len: 0,
63            depth: self.depth,
64            label: label.into(),
65            value: None,
66            bits: Vec::new(),
67            kind: RegionKind::Meta,
68            container: true,
69            blob: None,
70        });
71        self.depth += 1;
72        self.open_containers.push(idx);
73        idx
74    }
75
76    /// Close the container opened at `idx`, ending it at `after`.
77    pub(super) fn close(&mut self, idx: usize, after: &'a [u8]) {
78        self.depth -= 1;
79        // Popped outside the assert: `debug_assert_eq!` drops its arguments in release, which
80        // would leave every closed container open for `seal_partial` to stretch.
81        let closed = self.open_containers.pop();
82        debug_assert_eq!(closed, Some(idx), "containers must close innermost first");
83        let start = self.out[idx].offset;
84        self.out[idx].len = self.off(after) - start;
85    }
86
87    /// Rename the container opened at `idx`.
88    #[cfg(feature = "unstable-v2")]
89    pub(super) fn relabel(&mut self, idx: usize, label: impl Into<String>) {
90        self.out[idx].label = label.into();
91    }
92
93    pub(super) fn leaf(
94        &mut self,
95        before: &'a [u8],
96        after: &'a [u8],
97        label: impl Into<String>,
98        value: Option<String>,
99    ) {
100        self.out.push(Region {
101            offset: self.off(before),
102            len: before.len() - after.len(),
103            depth: self.depth,
104            label: label.into(),
105            value,
106            bits: Vec::new(),
107            kind: RegionKind::Meta,
108            container: false,
109            blob: None,
110        });
111    }
112
113    /// Record a leaf metadata region carrying a bit-level breakdown.
114    pub(super) fn leaf_bits(
115        &mut self,
116        before: &'a [u8],
117        after: &'a [u8],
118        label: impl Into<String>,
119        value: Option<String>,
120        bits: Vec<BitField>,
121    ) {
122        self.out.push(Region {
123            offset: self.off(before),
124            len: before.len() - after.len(),
125            depth: self.depth,
126            label: label.into(),
127            value,
128            bits,
129            kind: RegionKind::Meta,
130            container: false,
131            blob: None,
132        });
133    }
134
135    /// Parse one field with a real primitive, record a leaf region, return the tail.
136    pub(super) fn field<T>(
137        &mut self,
138        before: &'a [u8],
139        label: &str,
140        parse: impl FnOnce(&'a [u8]) -> MltRefResult<'a, T>,
141        render: impl FnOnce(&T) -> Option<String>,
142    ) -> MltResult<(&'a [u8], T)> {
143        let (after, val) = parse(before)?;
144        let value = render(&val);
145        self.leaf(before, after, label, value);
146        Ok((after, val))
147    }
148
149    /// Parse one packed byte, record it with a bit-level breakdown, return the tail.
150    pub(super) fn byte_field(
151        &mut self,
152        before: &'a [u8],
153        label: &str,
154        value: impl FnOnce(u8) -> String,
155        bits: impl FnOnce(u8) -> Vec<BitField>,
156    ) -> MltResult<(&'a [u8], u8)> {
157        let (after, byte) = parse_u8(before)?;
158        self.leaf_bits(before, after, label, Some(value(byte)), bits(byte));
159        Ok((after, byte))
160    }
161
162    /// Record `len` bytes at `at` as a data blob (no decodable metadata).
163    pub(super) fn raw_blob(&mut self, at: &'a [u8], len: usize, label: impl Into<String>) {
164        self.blob(at, len, label.into(), None);
165    }
166
167    /// Record `len` bytes at `at` as a stream payload.
168    pub(super) fn stream_blob(
169        &mut self,
170        at: &'a [u8],
171        len: usize,
172        label: impl Into<String>,
173        info: BlobInfo,
174    ) {
175        self.blob(at, len, label.into(), Some(info));
176    }
177
178    fn blob(&mut self, at: &'a [u8], len: usize, label: String, blob: Option<BlobInfo>) {
179        self.out.push(Region {
180            offset: self.off(at),
181            len,
182            depth: self.depth,
183            label,
184            value: None,
185            bits: Vec::new(),
186            kind: RegionKind::DataBlob,
187            container: false,
188            blob,
189        });
190    }
191
192    /// Turn the regions of a bailed-out walk into a valid tree.
193    ///
194    /// A container left open keeps `len == 0`, which hides everything inside it from a
195    /// span query such as [`super::filter_layer`], and the bytes past the annotated
196    /// prefix would otherwise leave the leaves not partitioning the buffer.
197    fn seal_partial(&mut self) {
198        let end = self
199            .out
200            .iter()
201            .filter(|r| !r.container)
202            .map(|r| r.offset + r.len)
203            .max()
204            .unwrap_or(0);
205
206        for idx in self.open_containers.drain(..) {
207            let offset = self.out[idx].offset;
208            debug_assert!(
209                offset <= end,
210                "a container opens at the cursor, never past it"
211            );
212            self.out[idx].len = end.saturating_sub(offset);
213        }
214
215        if end < self.buf.len() {
216            self.out.push(Region {
217                offset: end,
218                len: self.buf.len() - end,
219                depth: 0,
220                label: UNANNOTATED.to_string(),
221                value: None,
222                bits: Vec::new(),
223                kind: RegionKind::DataBlob,
224                container: false,
225                blob: None,
226            });
227        }
228    }
229
230    fn walk_tile(&mut self) -> MltResult<()> {
231        let mut input = self.buf;
232        let mut idx = 0;
233        while !input.is_empty() {
234            input = self.walk_layer(input, idx)?;
235            idx += 1;
236        }
237        Ok(())
238    }
239
240    /// Mirror [`crate::decoder::Layer`]`::from_bytes`: `[varint size][u8 tag][value]`.
241    fn walk_layer(&mut self, input: &'a [u8], idx: usize) -> MltResult<&'a [u8]> {
242        let ci = self.open(input, format!("layer[{idx}]"));
243
244        let (input, size) = self.field(
245            input,
246            "size",
247            |i| parse_varint::<u32>(i),
248            |v| Some(format!("{v} (varint) - tag + body")),
249        )?;
250        let (input, tag) = self.field(input, "tag", parse_u8, |t| Some(tag_label(*t)))?;
251
252        let body_len = size.checked_sub(1).ok_or(MltError::ZeroLayerSize)?;
253        let (rest, body) = take(input, body_len)?;
254
255        match tag {
256            1 => self.walk_layer01(body)?,
257            #[cfg(feature = "unstable-v2")]
258            2 => self.walk_layer02(body)?,
259            _ => self.raw_blob(body, body.len(), format!("value (Unknown tag 0x{tag:02X})")),
260        }
261
262        self.close(ci, rest);
263        Ok(rest)
264    }
265}
266
267/// How the parser will read this layer tag.
268fn tag_label(tag: u8) -> String {
269    match tag {
270        1 => "0x01 -> Tag01".into(),
271        #[cfg(feature = "unstable-v2")]
272        2 => "0x02 -> Tag02".into(),
273        other => format!("0x{other:02X} -> Unknown"),
274    }
275}