Skip to main content

mlt_core/decoder/
root.rs

1use usize_cast::IntoUsize as _;
2
3use crate::LazyParsed::Raw;
4use crate::MltError::{
5    BufferUnderflow, GeometryWithoutStreams, InvalidSharedDictStreamCount, MissingGeometry,
6    MissingLayerName, MultipleGeometryColumns, MultipleIdColumns, SharedDictRequiresStreams,
7    TrailingLayerData, UnexpectedStructChildCount, UnsupportedStringStreamCount,
8};
9use crate::codecs::varint::parse_varint;
10use crate::decoder::{
11    Column, ColumnType, DictionaryType, Geometry, Id, Layer01, ParsedLayer01, RawFsstData,
12    RawGeometry, RawId, RawIdValue, RawPlainData, RawPresence, RawProperty, RawScalar,
13    RawSharedDict, RawSharedDictEncoding, RawSharedDictItem, RawStream, RawStrings,
14    RawStringsEncoding, StreamType,
15};
16use crate::errors::AsMltError as _;
17use crate::utils::{SetOptionOnce as _, parse_string};
18use crate::{Layer, Lazy, MltError, MltRefResult, MltResult, ParsedLayer};
19
20/// Default memory budget: 20 MiB.
21const DEFAULT_MAX_BYTES: u32 = 20 * 1024 * 1024;
22
23/// Stateful decoder that enforces a per-tile memory budget during decoding.
24///
25/// Pass a `Decoder` to every `raw.decode()` / `into_tile()` call and to
26/// `from_bytes`-style parsers. Each method charges the budget before
27/// performing heap allocations, so the total heap used never exceeds `max_bytes`
28/// (in bytes).
29///
30/// ```
31/// use mlt_core::Decoder;
32///
33/// // Default: 10 MiB budget.
34/// let mut dec = Decoder::default();
35///
36/// // Custom budget.
37/// let mut dec = Decoder::with_max_size(64 * 1024 * 1024);
38/// ```
39#[derive(Debug, Clone, PartialEq, Eq, Default)]
40pub struct Decoder {
41    /// Keep track of the memory used when decoding a tile: raw->parsed transition
42    budget: MemBudget,
43    /// Reusable scratch buffer for the physical u32 decode pass.
44    /// Held here so its heap allocation is reused across streams without extra cost.
45    pub(crate) buffer_u32: Vec<u32>,
46    /// Reusable scratch buffer for the physical u64 decode pass.
47    /// Held here so its heap allocation is reused across streams without extra cost.
48    pub(crate) buffer_u64: Vec<u64>,
49}
50
51impl Decoder {
52    /// Create a decoder with a custom memory budget (in bytes).
53    #[must_use]
54    pub fn with_max_size(max_bytes: u32) -> Self {
55        Self {
56            budget: MemBudget::with_max_size(max_bytes),
57            ..Default::default()
58        }
59    }
60
61    pub fn decode_all<'a>(
62        &mut self,
63        layers: impl IntoIterator<Item = Layer<'a>>,
64    ) -> MltResult<Vec<ParsedLayer<'a>>> {
65        layers
66            .into_iter()
67            .map(|l| l.decode_all(self))
68            .collect::<MltResult<_>>()
69    }
70
71    /// Allocate a `Vec<T>` with the given capacity, charging the decoder's budget for
72    /// `capacity * size_of::<T>()` bytes. Use this instead of `Vec::with_capacity` in decode paths.
73    #[inline]
74    pub(crate) fn alloc<T>(&mut self, capacity: usize) -> MltResult<Vec<T>> {
75        let bytes = capacity.checked_mul(size_of::<T>()).or_overflow()?;
76        let bytes_u32 = u32::try_from(bytes).or_overflow()?;
77        self.budget.consume(bytes_u32)?;
78        Ok(Vec::with_capacity(capacity))
79    }
80
81    /// Charge the budget for `size` raw bytes. Prefer [`consume_items`][Self::consume_items]
82    /// when charging for a known-type collection.
83    #[inline]
84    pub(crate) fn consume(&mut self, size: u32) -> MltResult<()> {
85        self.budget.consume(size)
86    }
87
88    /// Charge the budget for `count` items of type `T` (`count * size_of::<T>()` bytes).
89    #[inline]
90    pub(crate) fn consume_items<T>(&mut self, count: usize) -> MltResult<()> {
91        let bytes = count.checked_mul(size_of::<T>()).or_overflow()?;
92        self.budget.consume(u32::try_from(bytes).or_overflow()?)
93    }
94
95    #[inline]
96    pub(crate) fn adjust(&mut self, adjustment: u32) {
97        self.budget.adjust(adjustment);
98    }
99
100    /// Return the unused portion of a pre-charged allocation budget.
101    ///
102    /// Call this after fully populating a `Vec<T>` that was pre-allocated with [`Decoder::alloc`],
103    /// passing the same `alloc_size` that was given to `alloc`.
104    ///
105    /// Returns an error if the vector grew beyond `alloc_size` (malformed input caused more items
106    /// than declared). Subtracts `(alloc_size - buf.len()) * size_of::<T>()` from the budget.
107    #[inline]
108    pub(crate) fn adjust_alloc<T>(&mut self, buf: &[T], alloc_size: usize) -> MltResult<()> {
109        if buf.len() > alloc_size {
110            return Err(MltError::InvalidDecodingStreamSize(buf.len(), alloc_size));
111        }
112        // Return the unused portion of the pre-charged budget.
113        let unused = (alloc_size - buf.len()) * size_of::<T>();
114        // unused fits in u32: it's at most alloc_size * size_of::<T>(), which was checked to fit
115        // in u32 when alloc() was called. Using saturating_cast to avoid a fallible conversion.
116        #[expect(
117            clippy::cast_possible_truncation,
118            reason = "unused <= alloc_size * size_of::<T>() which was verified to fit in u32 by alloc()"
119        )]
120        self.budget.adjust(unused as u32);
121        Ok(())
122    }
123
124    #[must_use]
125    pub fn consumed(&self) -> u32 {
126        self.budget.consumed()
127    }
128
129    /// Reset the memory budget to zero, keeping scratch buffers allocated.
130    ///
131    /// Call this between tiles when reusing a single `Decoder` for multiple
132    /// decodes — the per-tile budget is enforced fresh, but the internal
133    /// `buffer_u32` / `buffer_u64` scratch space is retained so it doesn't
134    /// need to be re-allocated.
135    ///
136    /// # Safety / correctness precondition
137    ///
138    /// Only call this after dropping any decoded allocations returned from the
139    /// previous tile. Resetting the budget while earlier decoded outputs are
140    /// still alive makes the budget enforceable only per-tile and can bypass
141    /// the stronger guarantee that total live heap tracked by this decoder
142    /// never exceeds the configured maximum.
143    pub fn reset_budget(&mut self) {
144        self.budget.reset();
145    }
146}
147
148impl MemBudget {
149    /// Reset tracked usage for a new decode window.
150    ///
151    /// Callers must ensure that allocations accounted for by the previous
152    /// window are no longer live before resetting.
153    fn reset(&mut self) {
154        self.bytes_used = 0;
155    }
156}
157/// Stateful parser that enforces a memory budget during parsing (binary → raw structures).
158///
159/// The parse chain reserves memory before allocations so total heap stays within the limit.
160///
161/// ```
162/// use mlt_core::Parser;
163///
164/// # let bytes: &[u8] = &[];
165/// let mut parser = Parser::default();
166/// let layers = parser.parse_layers(bytes).expect("parse");
167///
168/// // Or with a custom limit:
169/// let mut parser = Parser::with_max_size(64 * 1024 * 1024);
170/// ```
171#[derive(Debug, Clone, PartialEq, Eq, Default)]
172pub struct Parser {
173    budget: MemBudget,
174}
175
176impl Parser {
177    /// Create a parser with a custom memory budget (in bytes).
178    #[must_use]
179    pub fn with_max_size(max_bytes: u32) -> Self {
180        Self {
181            budget: MemBudget::with_max_size(max_bytes),
182        }
183    }
184
185    /// Parse a sequence of binary layers, reserving decoded memory against this parser's budget.
186    pub fn parse_layers<'a>(&mut self, mut input: &'a [u8]) -> MltResult<Vec<Layer<'a>>> {
187        let mut result = Vec::new();
188        while !input.is_empty() {
189            let layer;
190            (input, layer) = Layer::from_bytes(input, self)?;
191            result.push(layer);
192        }
193        Ok(result)
194    }
195
196    /// Reserve `size` bytes from the parse budget. Used internally by the parse chain.
197    #[inline]
198    pub(crate) fn reserve(&mut self, size: u32) -> MltResult<()> {
199        self.budget.consume(size)
200    }
201
202    #[must_use]
203    pub fn reserved(&self) -> u32 {
204        self.budget.consumed()
205    }
206}
207
208#[derive(Debug, Clone, PartialEq, Eq)]
209struct MemBudget {
210    /// Hard ceiling: total decoded bytes may not exceed this value.
211    pub max_bytes: u32,
212    /// Running total of used bytes so far.
213    pub bytes_used: u32,
214}
215
216impl Default for MemBudget {
217    /// Create a decoder with the default 10 MiB memory budget.
218    fn default() -> Self {
219        Self::with_max_size(DEFAULT_MAX_BYTES)
220    }
221}
222
223impl MemBudget {
224    /// Create a decoder with a custom memory budget (in bytes).
225    #[must_use]
226    fn with_max_size(max_bytes: u32) -> Self {
227        Self {
228            max_bytes,
229            bytes_used: 0,
230        }
231    }
232
233    /// Adjust previous consumption by `- adjustment` bytes.  Will panic if used incorrectly.
234    #[inline]
235    fn adjust(&mut self, adjustment: u32) {
236        self.bytes_used = self.bytes_used.checked_sub(adjustment).unwrap();
237    }
238
239    /// Take `size` bytes from the allocation budget. Call this before the actual allocation.
240    #[inline]
241    fn consume(&mut self, size: u32) -> MltResult<()> {
242        let accumulator = &mut self.bytes_used;
243        let max_bytes = self.max_bytes;
244        if let Some(new_value) = accumulator
245            .checked_add(size)
246            .and_then(|v| if v > max_bytes { None } else { Some(v) })
247        {
248            *accumulator = new_value;
249            Ok(())
250        } else {
251            Err(MltError::MemoryLimitExceeded {
252                limit: max_bytes,
253                used: *accumulator,
254                requested: size,
255            })
256        }
257    }
258
259    fn consumed(&self) -> u32 {
260        self.bytes_used
261    }
262}
263
264impl<'a> Layer01<'a, Lazy> {
265    /// Parse `v01::Layer` metadata, reserving decoded memory against the parser's budget.
266    pub(crate) fn from_bytes(input: &'a [u8], parser: &mut Parser) -> MltResult<Self> {
267        let (input, layer_name) = parse_string(input)?;
268        if layer_name.is_empty() {
269            return Err(MissingLayerName);
270        }
271        let (input, extent) = parse_varint::<u32>(input)?;
272        let (input, column_count) = parse_varint::<u32>(input)?;
273
274        // Each column requires at least 1 byte (column type)
275        if input.len() < column_count.into_usize() {
276            return Err(BufferUnderflow(column_count, input.len()));
277        }
278
279        // !!!!!!!
280        // WARNING: make sure to never use `let (input, ...)` after this point: input var is reused
281        let (mut input, (col_info, prop_count)) = parse_columns_meta(input, column_count, parser)?;
282        #[cfg(fuzzing)]
283        let layer_order = col_info
284            .iter()
285            .map(|column| column.typ)
286            .map(crate::decoder::fuzzing::LayerOrdering::from)
287            .collect();
288
289        let mut properties = Vec::with_capacity(prop_count.into_usize());
290        let mut id_column: Option<Id> = None;
291        let mut geometry: Option<Geometry> = None;
292
293        for column in col_info {
294            use crate::decoder::RawProperty as RP;
295
296            let opt;
297            let value;
298            let name = column.name.unwrap_or("");
299
300            match column.typ {
301                ColumnType::Id | ColumnType::OptId => {
302                    (input, opt) = parse_optional(column.typ, input, parser)?;
303                    (input, value) = RawStream::from_bytes(input, parser)?;
304                    id_column.set_once(Raw(RawId {
305                        presence: RawPresence(opt),
306                        value: RawIdValue::Id32(value),
307                    }))?;
308                }
309                ColumnType::LongId | ColumnType::OptLongId => {
310                    (input, opt) = parse_optional(column.typ, input, parser)?;
311                    (input, value) = RawStream::from_bytes(input, parser)?;
312                    id_column.set_once(Raw(RawId {
313                        presence: RawPresence(opt),
314                        value: RawIdValue::Id64(value),
315                    }))?;
316                }
317                ColumnType::Geometry => {
318                    input = parse_geometry_column(input, &mut geometry, parser)?;
319                }
320                ColumnType::Bool | ColumnType::OptBool => {
321                    (input, opt) = parse_optional(column.typ, input, parser)?;
322                    (input, value) = RawStream::parse_bool(input, parser)?;
323                    properties.push(Raw(RP::Bool(scalar(name, opt, value))));
324                }
325                ColumnType::I8 | ColumnType::OptI8 => {
326                    (input, opt) = parse_optional(column.typ, input, parser)?;
327                    (input, value) = RawStream::from_bytes(input, parser)?;
328                    properties.push(Raw(RP::I8(scalar(name, opt, value))));
329                }
330                ColumnType::U8 | ColumnType::OptU8 => {
331                    (input, opt) = parse_optional(column.typ, input, parser)?;
332                    (input, value) = RawStream::from_bytes(input, parser)?;
333                    properties.push(Raw(RP::U8(scalar(name, opt, value))));
334                }
335                ColumnType::I32 | ColumnType::OptI32 => {
336                    (input, opt) = parse_optional(column.typ, input, parser)?;
337                    (input, value) = RawStream::from_bytes(input, parser)?;
338                    properties.push(Raw(RP::I32(scalar(name, opt, value))));
339                }
340                ColumnType::U32 | ColumnType::OptU32 => {
341                    (input, opt) = parse_optional(column.typ, input, parser)?;
342                    (input, value) = RawStream::from_bytes(input, parser)?;
343                    properties.push(Raw(RP::U32(scalar(name, opt, value))));
344                }
345                ColumnType::I64 | ColumnType::OptI64 => {
346                    (input, opt) = parse_optional(column.typ, input, parser)?;
347                    (input, value) = RawStream::from_bytes(input, parser)?;
348                    properties.push(Raw(RP::I64(scalar(name, opt, value))));
349                }
350                ColumnType::U64 | ColumnType::OptU64 => {
351                    (input, opt) = parse_optional(column.typ, input, parser)?;
352                    (input, value) = RawStream::from_bytes(input, parser)?;
353                    properties.push(Raw(RP::U64(scalar(name, opt, value))));
354                }
355                ColumnType::F32 | ColumnType::OptF32 => {
356                    (input, opt) = parse_optional(column.typ, input, parser)?;
357                    (input, value) = RawStream::from_bytes(input, parser)?;
358                    properties.push(Raw(RP::F32(scalar(name, opt, value))));
359                }
360                ColumnType::F64 | ColumnType::OptF64 => {
361                    (input, opt) = parse_optional(column.typ, input, parser)?;
362                    (input, value) = RawStream::from_bytes(input, parser)?;
363                    properties.push(Raw(RP::F64(scalar(name, opt, value))));
364                }
365                ColumnType::Str | ColumnType::OptStr => {
366                    let prop;
367                    (input, prop) = parse_str_column(input, name, column.typ, parser)?;
368                    properties.push(Raw(prop));
369                }
370                ColumnType::SharedDict => {
371                    let prop;
372                    (input, prop) = parse_shared_dict_column(input, &column, parser)?;
373                    properties.push(Raw(prop));
374                }
375            }
376        }
377        if input.is_empty() {
378            Ok(Layer01 {
379                name: layer_name,
380                extent,
381                id: id_column,
382                geometry: geometry.ok_or(MissingGeometry)?,
383                properties,
384                #[cfg(fuzzing)]
385                layer_order,
386            })
387        } else {
388            Err(TrailingLayerData(input.len()))
389        }
390    }
391
392    /// Decode all columns and transition to [`Layer01<Parsed>`].
393    ///
394    /// Consumes `self` (a `Layer01<Lazy>`) and returns a `Layer01<Parsed>` where every
395    /// column field holds its parsed value directly, enabling infallible readonly access.
396    pub fn decode_all(self, dec: &mut Decoder) -> MltResult<ParsedLayer01<'a>> {
397        Ok(Layer01 {
398            name: self.name,
399            extent: self.extent,
400            id: self.id.map(|id| id.into_parsed(dec)).transpose()?,
401            geometry: self.geometry.into_parsed(dec)?,
402            properties: self
403                .properties
404                .into_iter()
405                .map(|p| p.into_parsed(dec))
406                .collect::<MltResult<Vec<_>>>()?,
407            #[cfg(fuzzing)]
408            layer_order: self.layer_order,
409        })
410    }
411}
412
413fn parse_struct_children<'a>(
414    mut input: &'a [u8],
415    column: &Column<'a>,
416    parser: &mut Parser,
417) -> MltRefResult<'a, Vec<RawSharedDictItem<'a>>> {
418    let mut children = Vec::with_capacity(column.children.len());
419    for child in &column.children {
420        let (inp, sc) = parse_varint::<u32>(input)?;
421        let (inp, child_optional) = parse_optional(child.typ, inp, parser)?;
422        let optional_stream_count = u32::from(child_optional.is_some());
423        if let Some(data_count) = sc.checked_sub(optional_stream_count)
424            && data_count != 1
425        {
426            return Err(UnexpectedStructChildCount(data_count));
427        }
428        let (inp, child_data) = RawStream::from_bytes(inp, parser)?;
429        children.push(RawSharedDictItem {
430            name: child.name.unwrap_or(""),
431            presence: RawPresence(child_optional),
432            data: child_data,
433        });
434        input = inp;
435    }
436    Ok((input, children))
437}
438
439fn parse_optional<'a>(
440    typ: ColumnType,
441    input: &'a [u8],
442    parser: &mut Parser,
443) -> MltRefResult<'a, Option<RawStream<'a>>> {
444    if typ.is_optional() {
445        let (input, optional) = RawStream::parse_bool(input, parser)?;
446        Ok((input, Some(optional)))
447    } else {
448        Ok((input, None))
449    }
450}
451
452fn parse_geometry_column<'a>(
453    input: &'a [u8],
454    geometry: &mut Option<Geometry<'a>>,
455    parser: &mut Parser,
456) -> MltResult<&'a [u8]> {
457    let (input, stream_count) = parse_varint::<u32>(input)?;
458    if stream_count == 0 {
459        return Err(GeometryWithoutStreams);
460    }
461    // Each stream requires at least 1 byte (physical stream type)
462    let stream_count_capa = stream_count.into_usize();
463    if input.len() < stream_count_capa {
464        return Err(BufferUnderflow(stream_count, input.len()));
465    }
466    // metadata
467    let (input, meta) = RawStream::from_bytes(input, parser)?;
468    // geometry items
469    let (input, items) = RawStream::parse_multiple(input, stream_count_capa - 1, parser)?;
470    geometry.set_once(Raw(RawGeometry { meta, items }))?;
471    Ok(input)
472}
473
474fn parse_str_column<'a>(
475    mut input: &'a [u8],
476    name: &'a str,
477    typ: ColumnType,
478    parser: &mut Parser,
479) -> MltRefResult<'a, RawProperty<'a>> {
480    let mut stream_count = {
481        let stream_count_u32;
482        (input, stream_count_u32) = parse_varint::<u32>(input)?;
483        stream_count_u32.into_usize()
484    };
485    let presence;
486    (input, presence) = parse_optional(typ, input, parser)?;
487    if presence.is_some() {
488        if stream_count == 0 {
489            return Err(UnsupportedStringStreamCount(stream_count));
490        }
491        stream_count -= 1;
492    }
493    let mut str_streams = [None, None, None, None, None];
494    if stream_count > str_streams.len() {
495        return Err(UnsupportedStringStreamCount(stream_count));
496    }
497    for slot in str_streams.iter_mut().take(stream_count) {
498        let stream;
499        (input, stream) = RawStream::from_bytes(input, parser)?;
500        *slot = Some(stream);
501    }
502    let encoding = match str_streams {
503        [Some(s1), Some(s2), None, None, None] => {
504            RawStringsEncoding::plain(RawPlainData::new(s1, s2)?)
505        }
506        [Some(s1), Some(s2), Some(s3), None, None] => {
507            RawStringsEncoding::dictionary(RawPlainData::new(s1, s3)?, s2)?
508        }
509        [Some(s1), Some(s2), Some(s3), Some(s4), None] => {
510            RawStringsEncoding::fsst_plain(RawFsstData::new(s1, s2, s3, s4)?)
511        }
512        [Some(s1), Some(s2), Some(s3), Some(s4), Some(s5)] => {
513            RawStringsEncoding::fsst_dictionary(RawFsstData::new(s1, s2, s3, s4)?, s5)?
514        }
515        _ => Err(UnsupportedStringStreamCount(stream_count))?,
516    };
517    Ok((
518        input,
519        RawProperty::Str(RawStrings {
520            name,
521            presence: RawPresence(presence),
522            encoding,
523        }),
524    ))
525}
526
527fn parse_shared_dict_column<'a>(
528    mut input: &'a [u8],
529    column: &Column<'a>,
530    parser: &mut Parser,
531) -> MltRefResult<'a, RawProperty<'a>> {
532    // Read header streams until we hit the dictionary DATA(Single|Shared) stream.
533    let stream_count;
534    (input, stream_count) = parse_varint::<u32>(input)?;
535    let mut dict_streams = [None, None, None, None, None];
536    let mut streams_taken = 0_usize;
537    while streams_taken < stream_count.into_usize() {
538        let stream;
539        (input, stream) = RawStream::from_bytes(input, parser)?;
540        let is_last = matches!(
541            stream.meta.stream_type,
542            StreamType::Data(DictionaryType::Single | DictionaryType::Shared)
543        );
544        dict_streams[streams_taken] = Some(stream);
545        streams_taken += 1;
546        if is_last {
547            break;
548        } else if streams_taken >= dict_streams.len() {
549            return Err(UnsupportedStringStreamCount(streams_taken + 1));
550        }
551    }
552    let children;
553    (input, children) = parse_struct_children(input, column, parser)?;
554
555    // Validate stream_count: must equal dict_streams + children + optional_children.
556    let children_n = u32::try_from(children.len()).or_overflow()?;
557    let optional_n = children
558        .iter()
559        .filter(|c| c.presence.0.is_some())
560        .count()
561        .try_into()
562        .or_overflow()?;
563    let dict_n = u32::try_from(streams_taken).or_overflow()?;
564    let expected = crate::utils::checked_sum3(dict_n, children_n, optional_n)?;
565    // Java's encoder had a bug (fixed) that overcounted by 1: dict + 2*N + 1.
566    // Accept that value too so that files produced by older Java encoders still parse.
567    let java_legacy = expected.checked_add(1).or_overflow()?;
568    if stream_count != expected && stream_count != java_legacy {
569        return Err(InvalidSharedDictStreamCount {
570            actual: stream_count,
571            expected,
572        });
573    }
574
575    let name = column.name.unwrap_or("");
576    let encoding = match dict_streams {
577        [Some(s1), Some(s2), None, None, None] => {
578            RawSharedDictEncoding::plain(RawPlainData::new(s1, s2)?)
579        }
580        [Some(s1), Some(s2), Some(s3), Some(s4), None] => {
581            RawSharedDictEncoding::fsst_plain(RawFsstData::new(s1, s2, s3, s4)?)
582        }
583        _ => Err(SharedDictRequiresStreams(streams_taken))?,
584    };
585    Ok((
586        input,
587        RawProperty::SharedDict(RawSharedDict {
588            name,
589            encoding,
590            children,
591        }),
592    ))
593}
594
595fn parse_columns_meta<'a>(
596    mut input: &'a [u8],
597    column_count: u32,
598    parser: &mut Parser,
599) -> MltRefResult<'a, (Vec<Column<'a>>, u32)> {
600    use crate::decoder::ColumnType::{Geometry, Id, LongId, OptId, OptLongId, SharedDict};
601
602    let mut col_info = Vec::with_capacity(column_count.into_usize());
603    let mut geometries = 0;
604    let mut ids = 0;
605    for _ in 0..column_count {
606        let mut typ;
607        (input, typ) = Column::from_bytes(input, parser)?;
608        match typ.typ {
609            Geometry => geometries += 1,
610            Id | OptId | LongId | OptLongId => ids += 1,
611            SharedDict => {
612                // Yes, we need to parse children right here; otherwise this messes up the next column
613                let child_column_count;
614                (input, child_column_count) = parse_varint::<u32>(input)?;
615
616                // Each column requires at least 1 byte (ColumnType without a name)
617                let child_col_capacity = child_column_count.into_usize();
618                if input.len() < child_col_capacity {
619                    return Err(BufferUnderflow(child_column_count, input.len()));
620                }
621                let mut children = Vec::with_capacity(child_col_capacity);
622                for _ in 0..child_column_count {
623                    let child;
624                    (input, child) = Column::from_bytes(input, parser)?;
625                    children.push(child);
626                }
627                typ.children = children;
628            }
629            _ => {}
630        }
631        col_info.push(typ);
632    }
633    if geometries > 1 {
634        return Err(MultipleGeometryColumns);
635    }
636    if ids > 1 {
637        return Err(MultipleIdColumns);
638    }
639
640    Ok((input, (col_info, column_count - geometries - ids)))
641}
642
643fn scalar<'a>(name: &'a str, opt: Option<RawStream<'a>>, value: RawStream<'a>) -> RawScalar<'a> {
644    RawScalar {
645        name,
646        presence: RawPresence(opt),
647        data: value,
648    }
649}
650
651#[cfg(test)]
652mod tests {
653    use crate::{MltError, Parser};
654
655    #[test]
656    fn parse_layers_rejects_empty_layer_name() {
657        let bytes = [
658            5, // layer size: tag byte + 4-byte body
659            1, // tag 0x01
660            0, // empty layer name
661            0x80, 0x20, // extent 4096
662            0,    // column count
663        ];
664
665        assert!(matches!(
666            Parser::default().parse_layers(&bytes),
667            Err(MltError::MissingLayerName)
668        ));
669    }
670}