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    pub(super) fn relabel(&mut self, idx: usize, label: impl Into<String>) {
89        self.out[idx].label = label.into();
90    }
91
92    pub(super) fn leaf(
93        &mut self,
94        before: &'a [u8],
95        after: &'a [u8],
96        label: impl Into<String>,
97        value: Option<String>,
98    ) {
99        self.out.push(Region {
100            offset: self.off(before),
101            len: before.len() - after.len(),
102            depth: self.depth,
103            label: label.into(),
104            value,
105            bits: Vec::new(),
106            kind: RegionKind::Meta,
107            container: false,
108            blob: None,
109        });
110    }
111
112    /// Record a leaf metadata region carrying a bit-level breakdown.
113    pub(super) fn leaf_bits(
114        &mut self,
115        before: &'a [u8],
116        after: &'a [u8],
117        label: impl Into<String>,
118        value: Option<String>,
119        bits: Vec<BitField>,
120    ) {
121        self.out.push(Region {
122            offset: self.off(before),
123            len: before.len() - after.len(),
124            depth: self.depth,
125            label: label.into(),
126            value,
127            bits,
128            kind: RegionKind::Meta,
129            container: false,
130            blob: None,
131        });
132    }
133
134    /// Parse one field with a real primitive, record a leaf region, return the tail.
135    pub(super) fn field<T>(
136        &mut self,
137        before: &'a [u8],
138        label: &str,
139        parse: impl FnOnce(&'a [u8]) -> MltRefResult<'a, T>,
140        render: impl FnOnce(&T) -> Option<String>,
141    ) -> MltResult<(&'a [u8], T)> {
142        let (after, val) = parse(before)?;
143        let value = render(&val);
144        self.leaf(before, after, label, value);
145        Ok((after, val))
146    }
147
148    /// Parse one packed byte, record it with a bit-level breakdown, return the tail.
149    pub(super) fn byte_field(
150        &mut self,
151        before: &'a [u8],
152        label: &str,
153        value: impl FnOnce(u8) -> String,
154        bits: impl FnOnce(u8) -> Vec<BitField>,
155    ) -> MltResult<(&'a [u8], u8)> {
156        let (after, byte) = parse_u8(before)?;
157        self.leaf_bits(before, after, label, Some(value(byte)), bits(byte));
158        Ok((after, byte))
159    }
160
161    /// Record `len` bytes at `at` as a data blob (no decodable metadata).
162    pub(super) fn raw_blob(&mut self, at: &'a [u8], len: usize, label: impl Into<String>) {
163        self.blob(at, len, label.into(), None);
164    }
165
166    /// Record `len` bytes at `at` as a stream payload.
167    pub(super) fn stream_blob(
168        &mut self,
169        at: &'a [u8],
170        len: usize,
171        label: impl Into<String>,
172        info: BlobInfo,
173    ) {
174        self.blob(at, len, label.into(), Some(info));
175    }
176
177    fn blob(&mut self, at: &'a [u8], len: usize, label: String, blob: Option<BlobInfo>) {
178        self.out.push(Region {
179            offset: self.off(at),
180            len,
181            depth: self.depth,
182            label,
183            value: None,
184            bits: Vec::new(),
185            kind: RegionKind::DataBlob,
186            container: false,
187            blob,
188        });
189    }
190
191    /// Turn the regions of a bailed-out walk into a valid tree.
192    ///
193    /// A container left open keeps `len == 0`, which hides everything inside it from a
194    /// span query such as [`super::filter_layer`], and the bytes past the annotated
195    /// prefix would otherwise leave the leaves not partitioning the buffer.
196    fn seal_partial(&mut self) {
197        let end = self
198            .out
199            .iter()
200            .filter(|r| !r.container)
201            .map(|r| r.offset + r.len)
202            .max()
203            .unwrap_or(0);
204
205        for idx in self.open_containers.drain(..) {
206            let offset = self.out[idx].offset;
207            debug_assert!(
208                offset <= end,
209                "a container opens at the cursor, never past it"
210            );
211            self.out[idx].len = end.saturating_sub(offset);
212        }
213
214        if end < self.buf.len() {
215            self.out.push(Region {
216                offset: end,
217                len: self.buf.len() - end,
218                depth: 0,
219                label: UNANNOTATED.to_string(),
220                value: None,
221                bits: Vec::new(),
222                kind: RegionKind::DataBlob,
223                container: false,
224                blob: None,
225            });
226        }
227    }
228
229    fn walk_tile(&mut self) -> MltResult<()> {
230        let mut input = self.buf;
231        let mut idx = 0;
232        while !input.is_empty() {
233            input = self.walk_layer(input, idx)?;
234            idx += 1;
235        }
236        Ok(())
237    }
238
239    /// Mirror [`crate::decoder::Layer`]`::from_bytes`: `[varint size][u8 tag][value]`.
240    fn walk_layer(&mut self, input: &'a [u8], idx: usize) -> MltResult<&'a [u8]> {
241        let ci = self.open(input, format!("layer[{idx}]"));
242
243        let (input, size) = self.field(
244            input,
245            "size",
246            |i| parse_varint::<u32>(i),
247            |v| Some(format!("{v} (varint) - tag + body")),
248        )?;
249        let (input, tag) = self.field(input, "tag", parse_u8, |t| Some(tag_label(*t)))?;
250
251        let body_len = size.checked_sub(1).ok_or(MltError::ZeroLayerSize)?;
252        let (rest, body) = take(input, body_len)?;
253
254        // The name is only known once the body has been read, so the layer takes its label
255        // afterwards, reading `layer[0] "water"` the way a column reads `column[0] Str "name"`.
256        match tag {
257            1 => {
258                let name = self.walk_layer01(body)?;
259                self.relabel(ci, format!("layer[{idx}] {name:?}"));
260            }
261            #[cfg(feature = "unstable-v2")]
262            2 => {
263                let name = self.walk_layer02(body)?;
264                self.relabel(ci, format!("layer[{idx}] {name:?}"));
265            }
266            _ => self.raw_blob(body, body.len(), format!("value (Unknown tag 0x{tag:02X})")),
267        }
268
269        self.close(ci, rest);
270        Ok(rest)
271    }
272}
273
274/// How the parser will read this layer tag.
275fn tag_label(tag: u8) -> String {
276    match tag {
277        1 => "0x01 -> Tag01".into(),
278        #[cfg(feature = "unstable-v2")]
279        2 => "0x02 -> Tag02".into(),
280        other => format!("0x{other:02X} -> Unknown"),
281    }
282}