Skip to main content

mlt_core/dump/
walker01.rs

1//! Annotating walker for tag `0x01` (v1) tiles.
2//!
3//! Mirrors the wire layout of [`crate::decoder`], but records an annotated
4//! [`Region`] per field instead of building decoded structures.
5//! Advancement is delegated to the real parser's primitives and to the
6//! authoritative `StreamMeta::from_bytes` / `ColumnType::from_bytes`, so offsets
7//! are exact by construction.
8//! Only per-column stream sequencing is mirrored by hand; the coverage test in
9//! `tests/dump_coverage.rs` guards it.
10
11use usize_cast::IntoUsize as _;
12
13use super::model::{BitField, BlobInfo, DecodeHint, DumpTree, Region, RegionKind};
14use crate::codecs::varint::parse_varint;
15use crate::decoder::{Column, ColumnType, DictionaryType, StreamType};
16use crate::utils::{parse_string, parse_u8, take};
17use crate::wire::{LogicalEncoding, LogicalTechnique, PhysicalEncoding, StreamMeta};
18use crate::{MltError, MltRefResult, MltResult, Parser};
19
20/// Walk a whole tile buffer, producing an annotated [`DumpTree`].
21///
22/// The returned tree references offsets into `buf`; keep `buf` alive to render it.
23pub fn annotate_tile(buf: &[u8]) -> MltResult<DumpTree> {
24    let mut w = Walker {
25        buf,
26        out: Vec::new(),
27        depth: 0,
28        parser: Parser::default(),
29    };
30    w.walk_tile()?;
31    Ok(DumpTree {
32        buf_len: buf.len(),
33        regions: w.out,
34    })
35}
36
37struct Walker<'a> {
38    buf: &'a [u8],
39    out: Vec<Region>,
40    depth: usize,
41    /// Throwaway budget for the authoritative `StreamMeta::from_bytes` calls.
42    parser: Parser,
43}
44
45impl<'a> Walker<'a> {
46    /// Absolute offset of a tail slice against the base buffer.
47    fn off(&self, s: &'a [u8]) -> usize {
48        (s.as_ptr() as usize) - (self.buf.as_ptr() as usize)
49    }
50
51    /// Open a container region spanning children; returns its index for [`Walker::close`].
52    fn open(&mut self, at: &'a [u8], label: String) -> usize {
53        let idx = self.out.len();
54        self.out.push(Region {
55            offset: self.off(at),
56            len: 0,
57            depth: self.depth,
58            label,
59            value: None,
60            bits: Vec::new(),
61            kind: RegionKind::Meta,
62            container: true,
63            blob: None,
64        });
65        self.depth += 1;
66        idx
67    }
68
69    /// Close the container opened at `idx`, setting its length up to `after`.
70    fn close(&mut self, idx: usize, after: &'a [u8]) {
71        self.depth -= 1;
72        let start = self.out[idx].offset;
73        self.out[idx].len = self.off(after) - start;
74    }
75
76    fn leaf(&mut self, before: &'a [u8], after: &'a [u8], label: String, value: Option<String>) {
77        self.out.push(Region {
78            offset: self.off(before),
79            len: before.len() - after.len(),
80            depth: self.depth,
81            label,
82            value,
83            bits: Vec::new(),
84            kind: RegionKind::Meta,
85            container: false,
86            blob: None,
87        });
88    }
89
90    /// Record a leaf metadata region carrying a bit-level breakdown.
91    fn leaf_bits(
92        &mut self,
93        before: &'a [u8],
94        after: &'a [u8],
95        label: String,
96        value: Option<String>,
97        bits: Vec<BitField>,
98    ) {
99        self.out.push(Region {
100            offset: self.off(before),
101            len: before.len() - after.len(),
102            depth: self.depth,
103            label,
104            value,
105            bits,
106            kind: RegionKind::Meta,
107            container: false,
108            blob: None,
109        });
110    }
111
112    /// Parse one field with a real primitive, record a leaf region, return the tail.
113    fn field<T>(
114        &mut self,
115        before: &'a [u8],
116        label: &str,
117        parse: impl FnOnce(&'a [u8]) -> MltRefResult<'a, T>,
118        render: impl FnOnce(&T) -> Option<String>,
119    ) -> MltResult<(&'a [u8], T)> {
120        let (after, val) = parse(before)?;
121        let value = render(&val);
122        self.leaf(before, after, label.to_string(), value);
123        Ok((after, val))
124    }
125
126    /// Record a raw byte range as a data blob (no decodable metadata).
127    fn raw_blob(&mut self, before: &'a [u8], after: &'a [u8], label: String) {
128        self.out.push(Region {
129            offset: self.off(before),
130            len: before.len() - after.len(),
131            depth: self.depth,
132            label,
133            value: None,
134            bits: Vec::new(),
135            kind: RegionKind::DataBlob,
136            container: false,
137            blob: None,
138        });
139    }
140
141    fn walk_tile(&mut self) -> MltResult<()> {
142        let mut input = self.buf;
143        let mut idx = 0;
144        while !input.is_empty() {
145            input = self.walk_layer(input, idx)?;
146            idx += 1;
147        }
148        Ok(())
149    }
150
151    /// Mirror [`crate::decoder::Layer::from_bytes`]: `[varint size][u8 tag][value]`.
152    fn walk_layer(&mut self, input: &'a [u8], idx: usize) -> MltResult<&'a [u8]> {
153        let start = input;
154        let ci = self.open(start, format!("layer[{idx}]"));
155
156        let (input, size) = self.field(
157            input,
158            "size",
159            |i| parse_varint::<u32>(i),
160            |v| Some(format!("{v} (varint) — tag + body")),
161        )?;
162        let (input, tag) = self.field(input, "tag", parse_u8, |t| {
163            Some(match t {
164                1 => "0x01 → Tag01".to_string(),
165                other => format!("0x{other:02X} → Unknown"),
166            })
167        })?;
168
169        let body_len = size.checked_sub(1).ok_or(MltError::ZeroLayerSize)?;
170        let (rest, body) = take(input, body_len)?;
171
172        if tag == 1 {
173            self.walk_layer01(body)?;
174        } else {
175            let end = &body[body.len()..];
176            self.raw_blob(body, end, format!("value (Unknown tag 0x{tag:02X})"));
177        }
178
179        self.close(ci, rest);
180        Ok(rest)
181    }
182
183    /// Mirror [`crate::decoder::Layer01::from_bytes`].
184    /// `body` must be consumed fully.
185    fn walk_layer01(&mut self, input: &'a [u8]) -> MltResult<()> {
186        let (input, _name) = self.field(input, "name", parse_string, |s| Some(format!("{s:?}")))?;
187        let (input, _extent) = self.field(
188            input,
189            "extent",
190            |i| parse_varint::<u32>(i),
191            |v| Some(v.to_string()),
192        )?;
193        let (input, column_count) = self.field(
194            input,
195            "column_count",
196            |i| parse_varint::<u32>(i),
197            |v| Some(v.to_string()),
198        )?;
199
200        let (mut input, columns) = self.walk_schema(input, column_count)?;
201
202        if !columns.is_empty() {
203            let di = self.open(input, "column data".to_string());
204            for (ci, col) in columns.iter().enumerate() {
205                input = self.walk_column_data(input, ci, col)?;
206            }
207            self.close(di, input);
208        }
209
210        // A well-formed layer consumes its whole body; record any trailing bytes.
211        if !input.is_empty() {
212            let end = &input[input.len()..];
213            self.raw_blob(input, end, "trailing bytes".to_string());
214        }
215        Ok(())
216    }
217
218    /// Mirror `parse_columns_meta`: `column_count` column definitions.
219    fn walk_schema(
220        &mut self,
221        mut input: &'a [u8],
222        column_count: u32,
223    ) -> MltResult<(&'a [u8], Vec<Column<'a>>)> {
224        let si = self.open(input, "schema".to_string());
225        if input.len() < column_count.into_usize() {
226            return Err(MltError::BufferUnderflow(column_count, input.len()));
227        }
228        let mut cols = Vec::with_capacity(column_count.into_usize());
229        for i in 0..column_count {
230            let (rest, col) = self.walk_column_def(input, i)?;
231            input = rest;
232            cols.push(col);
233        }
234        self.close(si, input);
235        Ok((input, cols))
236    }
237
238    /// Mirror `Column::from_bytes` (plus inline `SharedDict` children), split into
239    /// `[type u8][optional name]` (and child defs).
240    fn walk_column_def(&mut self, input: &'a [u8], i: u32) -> MltResult<(&'a [u8], Column<'a>)> {
241        let ci = self.open(input, format!("column[{i}]"));
242
243        // Column-type byte, with the optional-flag bit broken out.
244        let (after_ty, typ) = ColumnType::from_bytes(input)?;
245        let byte = typ as u8;
246        let bits = vec![
247            BitField {
248                hi: 7,
249                lo: 1,
250                raw: u64::from(byte >> 1),
251                meaning: format!("base type = {typ:?}"),
252            },
253            BitField {
254                hi: 0,
255                lo: 0,
256                raw: u64::from(byte & 1),
257                meaning: format!("optional = {}", typ.is_optional()),
258            },
259        ];
260        self.leaf_bits(
261            input,
262            after_ty,
263            "type".to_string(),
264            Some(format!("0x{byte:02X} {typ:?}")),
265            bits,
266        );
267        let mut input = after_ty;
268
269        let name = if typ.has_name() {
270            let (rest, name) =
271                self.field(input, "name", parse_string, |s| Some(format!("{s:?}")))?;
272            input = rest;
273            Some(name)
274        } else {
275            None
276        };
277
278        let mut children = Vec::new();
279        if typ == ColumnType::SharedDict {
280            let (rest, child_count) = self.field(
281                input,
282                "child_count",
283                |i| parse_varint::<u32>(i),
284                |v| Some(v.to_string()),
285            )?;
286            input = rest;
287            if input.len() < child_count.into_usize() {
288                return Err(MltError::BufferUnderflow(child_count, input.len()));
289            }
290            children.reserve(child_count.into_usize());
291            for j in 0..child_count {
292                let (rest, child) = self.walk_column_def(input, j)?;
293                input = rest;
294                children.push(child);
295            }
296        }
297
298        self.close(ci, input);
299        Ok((
300            input,
301            Column {
302                typ,
303                name,
304                children,
305            },
306        ))
307    }
308
309    fn walk_column_data(
310        &mut self,
311        input: &'a [u8],
312        ci: usize,
313        col: &Column<'a>,
314    ) -> MltResult<&'a [u8]> {
315        use ColumnType as C;
316        let typ = col.typ;
317        let name_suffix = col.name.map(|n| format!(" {n:?}")).unwrap_or_default();
318        let gi = self.open(input, format!("column[{ci}] {typ:?}{name_suffix}"));
319
320        let mut input = input;
321        match typ {
322            C::Id | C::OptId => {
323                input = self.walk_optional(input, typ)?;
324                input = self.walk_stream(input, false, "id", |_| DecodeHint::U32)?.0;
325            }
326            C::LongId | C::OptLongId => {
327                input = self.walk_optional(input, typ)?;
328                input = self.walk_stream(input, false, "id", |_| DecodeHint::U64)?.0;
329            }
330            C::Geometry => {
331                input = self.walk_geometry(input)?;
332            }
333            C::Bool | C::OptBool => {
334                input = self.walk_optional(input, typ)?;
335                input = self
336                    .walk_stream(input, true, "data", |_| DecodeHint::Bool)?
337                    .0;
338            }
339            C::I8 | C::OptI8 | C::I32 | C::OptI32 => {
340                input = self.walk_optional(input, typ)?;
341                input = self
342                    .walk_stream(input, false, "data", |_| DecodeHint::I32)?
343                    .0;
344            }
345            C::U8 | C::OptU8 | C::U32 | C::OptU32 => {
346                input = self.walk_optional(input, typ)?;
347                input = self
348                    .walk_stream(input, false, "data", |_| DecodeHint::U32)?
349                    .0;
350            }
351            C::I64 | C::OptI64 => {
352                input = self.walk_optional(input, typ)?;
353                input = self
354                    .walk_stream(input, false, "data", |_| DecodeHint::I64)?
355                    .0;
356            }
357            C::U64 | C::OptU64 => {
358                input = self.walk_optional(input, typ)?;
359                input = self
360                    .walk_stream(input, false, "data", |_| DecodeHint::U64)?
361                    .0;
362            }
363            C::F32 | C::OptF32 => {
364                input = self.walk_optional(input, typ)?;
365                input = self
366                    .walk_stream(input, false, "data", |_| DecodeHint::F32)?
367                    .0;
368            }
369            C::F64 | C::OptF64 => {
370                input = self.walk_optional(input, typ)?;
371                input = self
372                    .walk_stream(input, false, "data", |_| DecodeHint::F64)?
373                    .0;
374            }
375            C::Str | C::OptStr => {
376                input = self.walk_str(input, typ)?;
377            }
378            C::SharedDict => {
379                input = self.walk_shared_dict(input, col)?;
380            }
381        }
382
383        self.close(gi, input);
384        Ok(input)
385    }
386
387    /// Mirror `parse_optional`: a boolean presence stream iff the column is optional.
388    fn walk_optional(&mut self, input: &'a [u8], typ: ColumnType) -> MltResult<&'a [u8]> {
389        if typ.is_optional() {
390            Ok(self
391                .walk_stream(input, true, "present", |_| DecodeHint::Presence)?
392                .0)
393        } else {
394            Ok(input)
395        }
396    }
397
398    /// Mirror `parse_geometry_column`: `[varint stream_count]` + meta stream + rest.
399    fn walk_geometry(&mut self, input: &'a [u8]) -> MltResult<&'a [u8]> {
400        let (mut input, stream_count) = self.field(
401            input,
402            "stream_count",
403            |i| parse_varint::<u32>(i),
404            |v| Some(v.to_string()),
405        )?;
406        if stream_count == 0 {
407            return Err(MltError::GeometryWithoutStreams);
408        }
409        input = self.walk_stream(input, false, "meta", geom_hint)?.0;
410        for j in 0..stream_count - 1 {
411            input = self
412                .walk_stream(input, false, &format!("stream[{j}]"), geom_hint)?
413                .0;
414        }
415        Ok(input)
416    }
417
418    /// Mirror `parse_str_column`: `[varint stream_count]`, optional presence, then
419    /// 2–5 data/offset/length streams.
420    fn walk_str(&mut self, input: &'a [u8], typ: ColumnType) -> MltResult<&'a [u8]> {
421        let (mut input, stream_count) = self.field(
422            input,
423            "stream_count",
424            |i| parse_varint::<u32>(i),
425            |v| Some(v.to_string()),
426        )?;
427        let mut remaining = stream_count.into_usize();
428        if typ.is_optional() {
429            if remaining == 0 {
430                return Err(MltError::UnsupportedStringStreamCount(remaining));
431            }
432            input = self
433                .walk_stream(input, true, "present", |_| DecodeHint::Presence)?
434                .0;
435            remaining -= 1;
436        }
437        for j in 0..remaining {
438            input = self
439                .walk_stream(input, false, &format!("stream[{j}]"), auto_hint)?
440                .0;
441        }
442        Ok(input)
443    }
444
445    /// Mirror `parse_shared_dict_column` + `parse_shared_dict_children`.
446    fn walk_shared_dict(&mut self, input: &'a [u8], col: &Column<'a>) -> MltResult<&'a [u8]> {
447        let (mut input, _stream_count) = self.field(
448            input,
449            "stream_count",
450            |i| parse_varint::<u32>(i),
451            |v| Some(v.to_string()),
452        )?;
453
454        // Dictionary streams: read until the DATA(Single|Shared) stream.
455        let mut taken = 0usize;
456        loop {
457            let (rest, meta) =
458                self.walk_stream(input, false, &format!("dict_stream[{taken}]"), auto_hint)?;
459            input = rest;
460            taken += 1;
461            if matches!(
462                meta.stream_type,
463                StreamType::Data(DictionaryType::Single | DictionaryType::Shared)
464            ) {
465                break;
466            }
467            if taken >= 5 {
468                return Err(MltError::UnsupportedStringStreamCount(taken + 1));
469            }
470        }
471
472        // Children: each `[varint stream_count][optional present][data stream]`.
473        for (j, child) in col.children.iter().enumerate() {
474            let cci = self.open(input, format!("child[{j}] {:?}", child.typ));
475            let (rest, _sc) = self.field(
476                input,
477                "stream_count",
478                |i| parse_varint::<u32>(i),
479                |v| Some(v.to_string()),
480            )?;
481            input = rest;
482            if child.typ.is_optional() {
483                input = self
484                    .walk_stream(input, true, "present", |_| DecodeHint::Presence)?
485                    .0;
486            }
487            input = self.walk_stream(input, false, "data", auto_hint)?.0;
488            self.close(cci, input);
489        }
490        Ok(input)
491    }
492
493    /// Walk one stream: the annotated header (via the authoritative
494    /// [`StreamMeta::from_bytes`]) followed by the payload blob.
495    fn walk_stream(
496        &mut self,
497        input: &'a [u8],
498        is_bool: bool,
499        label: &str,
500        hint: impl FnOnce(StreamType) -> DecodeHint,
501    ) -> MltResult<(&'a [u8], StreamMeta)> {
502        let si = self.open(input, label.to_string());
503
504        // Authoritative parse — drives advancement and gives us `meta`/`byte_length`.
505        let (after_hdr, (meta, byte_length)) =
506            StreamMeta::from_bytes(input, is_bool, &mut self.parser)?;
507
508        // Re-walk the consumed header bytes to annotate each field.
509        let hi = self.open(input, "header".to_string());
510        let mut c = input;
511
512        let (c1, st_byte) = parse_u8(c)?;
513        self.leaf_bits(
514            c,
515            c1,
516            "stream_type".to_string(),
517            Some(format!("0x{st_byte:02X} {:?}", meta.stream_type)),
518            stream_type_bits(meta.stream_type, st_byte),
519        );
520        c = c1;
521
522        let (c2, enc_byte) = parse_u8(c)?;
523        self.leaf_bits(
524            c,
525            c2,
526            "encoding".to_string(),
527            Some(format!(
528                "0x{enc_byte:02X} logical={:?} physical={:?}",
529                meta.encoding.logical, meta.encoding.physical
530            )),
531            encoding_bits(enc_byte),
532        );
533        c = c2;
534
535        (c, _) = self.field(
536            c,
537            "num_values",
538            |i| parse_varint::<u32>(i),
539            |v| Some(v.to_string()),
540        )?;
541        (c, _) = self.field(
542            c,
543            "byte_length",
544            |i| parse_varint::<u32>(i),
545            |v| Some(v.to_string()),
546        )?;
547
548        match meta.encoding.logical {
549            LogicalEncoding::Rle(_) | LogicalEncoding::DeltaRle(_) if !is_bool => {
550                (c, _) = self.field(
551                    c,
552                    "runs",
553                    |i| parse_varint::<u32>(i),
554                    |v| Some(v.to_string()),
555                )?;
556                (c, _) = self.field(
557                    c,
558                    "num_rle_values",
559                    |i| parse_varint::<u32>(i),
560                    |v| Some(v.to_string()),
561                )?;
562            }
563            LogicalEncoding::Morton(_)
564            | LogicalEncoding::MortonDelta(_)
565            | LogicalEncoding::MortonRle(_) => {
566                (c, _) = self.field(
567                    c,
568                    "bits",
569                    |i| parse_varint::<u32>(i),
570                    |v| Some(v.to_string()),
571                )?;
572                (c, _) = self.field(
573                    c,
574                    "shift",
575                    |i| parse_varint::<u32>(i),
576                    |v| Some(v.to_string()),
577                )?;
578            }
579            _ => {}
580        }
581        self.close(hi, c);
582
583        // Consistency guard: the hand re-walk must land exactly on the authoritative tail.
584        if self.off(c) != self.off(after_hdr) {
585            return Err(MltError::NotImplemented("stream header re-walk desync"));
586        }
587
588        let (rest, _payload) = take(after_hdr, byte_length)?;
589        self.out.push(Region {
590            offset: self.off(after_hdr),
591            len: byte_length.into_usize(),
592            depth: self.depth,
593            label: "data".to_string(),
594            value: None,
595            bits: Vec::new(),
596            kind: RegionKind::DataBlob,
597            container: false,
598            blob: Some(BlobInfo {
599                meta,
600                hint: hint(meta.stream_type),
601            }),
602        });
603
604        self.close(si, rest);
605        Ok((rest, meta))
606    }
607}
608
609/// Decode hint for opaque string / dictionary streams, keyed by stream type.
610fn auto_hint(st: StreamType) -> DecodeHint {
611    match st {
612        StreamType::Present => DecodeHint::Presence,
613        StreamType::Offset(_) | StreamType::Length(_) => DecodeHint::U32,
614        StreamType::Data(_) => DecodeHint::Bytes,
615    }
616}
617
618/// Decode hint for geometry streams: vertex/data are signed (zigzag / componentwise
619/// / morton), while offsets and lengths are unsigned counts.
620fn geom_hint(st: StreamType) -> DecodeHint {
621    match st {
622        StreamType::Present => DecodeHint::Presence,
623        StreamType::Offset(_) | StreamType::Length(_) => DecodeHint::U32,
624        StreamType::Data(_) => DecodeHint::I32,
625    }
626}
627
628/// Bit breakdown of the `stream_type` byte: category nibble + subtype nibble.
629fn stream_type_bits(st: StreamType, byte: u8) -> Vec<BitField> {
630    let category = match st {
631        StreamType::Present => "Present",
632        StreamType::Data(_) => "Data",
633        StreamType::Offset(_) => "Offset",
634        StreamType::Length(_) => "Length",
635    };
636    let subtype = match st {
637        StreamType::Present => "—".to_string(),
638        StreamType::Data(d) => format!("{d:?}"),
639        StreamType::Offset(o) => format!("{o:?}"),
640        StreamType::Length(l) => format!("{l:?}"),
641    };
642    vec![
643        BitField {
644            hi: 7,
645            lo: 4,
646            raw: u64::from(byte >> 4),
647            meaning: format!("category = {category}"),
648        },
649        BitField {
650            hi: 3,
651            lo: 0,
652            raw: u64::from(byte & 0x0F),
653            meaning: format!("subtype = {subtype}"),
654        },
655    ]
656}
657
658/// Bit breakdown of the `encoding` byte: logical1 (7-5), logical2 (4-2), physical (1-0).
659fn encoding_bits(byte: u8) -> Vec<BitField> {
660    let l1 = byte >> 5;
661    let l2 = (byte >> 2) & 0x7;
662    let ph = byte & 0x3;
663    let name_lt = |v: u8| {
664        LogicalTechnique::try_from(v).map_or_else(|_| format!("invalid({v})"), |t| format!("{t:?}"))
665    };
666    let name_ph = PhysicalEncoding::try_from(ph)
667        .map_or_else(|_| format!("invalid({ph})"), |p| format!("{p:?}"));
668    vec![
669        BitField {
670            hi: 7,
671            lo: 5,
672            raw: u64::from(l1),
673            meaning: format!("logical1 = {}", name_lt(l1)),
674        },
675        BitField {
676            hi: 4,
677            lo: 2,
678            raw: u64::from(l2),
679            meaning: format!("logical2 = {}", name_lt(l2)),
680        },
681        BitField {
682            hi: 1,
683            lo: 0,
684            raw: u64::from(ph),
685            meaning: format!("physical = {name_ph}"),
686        },
687    ]
688}