Skip to main content

cadmpeg_core/decode/
tree.rs

1// SPDX-License-Identifier: Apache-2.0
2//! Admission of parser-owned XML and JSON trees.
3
4use super::{u64_from_index, DecodeContext, ScopedReservation};
5use crate::CodecError;
6
7/// Bounds roxmltree 0.21.1 `NodeData`, including `NodeKind`, four node IDs,
8/// positions (`Range<usize>`), enum alignment and inline expanded names.
9const NODE_RECORD_BOUND: u64 = 128;
10/// Bounds roxmltree 0.21.1 `AttributeData` and `TempAttributeData`, including
11/// expanded names, `StringStorage`, position ranges and alignment.
12const ATTRIBUTE_RECORD_BOUND: u64 = 128;
13/// Bounds roxmltree 0.21.1 `Namespace`, including optional prefix,
14/// `StringStorage` and alignment. Namespace indices are admitted separately.
15const NAMESPACE_RECORD_BOUND: u64 = 64;
16// The built-in xml prefix and URI participate in expanded-name comparisons.
17const XML_PREFIX_BYTES: u64 = 3;
18const XML_NAMESPACE_URI_BYTES: u64 = 36;
19
20/// An XML document and the admission held until its storage is released.
21#[derive(Debug)]
22pub struct AdmittedXml<'input, 'ctx> {
23    document: roxmltree::Document<'input>,
24    _reservation: ScopedReservation<'ctx>,
25}
26
27impl<'input> AdmittedXml<'input, '_> {
28    /// Borrows the admitted tree without releasing its reservation.
29    pub fn document(&self) -> &roxmltree::Document<'input> {
30        &self.document
31    }
32}
33
34#[derive(Clone, Copy)]
35enum XmlScan<'input> {
36    Text,
37    Tag(XmlTag<'input>),
38    Comment,
39    Cdata,
40    Instruction,
41    Declaration,
42}
43
44#[derive(Clone, Copy)]
45struct XmlTag<'input> {
46    closing: bool,
47    quote: u8,
48    last: u8,
49    attributes: u64,
50    name_bytes: u64,
51    name_complete: bool,
52    attribute_name_bytes: u64,
53    namespace_key_bytes: u64,
54    prefixed_attributes: u64,
55    namespaces: u64,
56    start: usize,
57    undo_start: usize,
58    namespace_prefix: Option<&'input str>,
59    quote_start: usize,
60}
61
62/// Upper bounds for the namespace list visible at one element.
63#[derive(Clone, Copy)]
64struct XmlNamespaceScope {
65    element_depth: u64,
66    undo_start: usize,
67    count: u64,
68    max_prefix: u64,
69    max_uri: u64,
70}
71
72impl Default for XmlNamespaceScope {
73    fn default() -> Self {
74        Self {
75            element_depth: 0,
76            undo_start: 0,
77            count: 0,
78            max_prefix: XML_PREFIX_BYTES,
79            max_uri: XML_NAMESPACE_URI_BYTES,
80        }
81    }
82}
83
84/// Borrow prefix text and restore bindings on closing tags. Namespace-free
85/// children reuse their parent's bound; declarations recompute the union.
86struct XmlNamespaces<'ctx, 'input> {
87    prefixes: std::collections::HashMap<&'input str, u64>,
88    bindings: std::collections::HashSet<(&'input str, &'input str)>,
89    undo: Vec<(&'input str, Option<u64>)>,
90    scopes: Vec<XmlNamespaceScope>,
91    storage: ScopedReservation<'ctx>,
92    ctx: &'ctx DecodeContext<'ctx>,
93}
94
95impl<'ctx, 'input> XmlNamespaces<'ctx, 'input> {
96    fn new(ctx: &'ctx DecodeContext<'_>, operation: &'static str) -> Result<Self, CodecError> {
97        Ok(Self {
98            prefixes: std::collections::HashMap::new(),
99            bindings: std::collections::HashSet::new(),
100            undo: Vec::new(),
101            scopes: Vec::new(),
102            storage: ctx.reserve_scoped(0, operation)?,
103            ctx,
104        })
105    }
106
107    fn parent(&self) -> XmlNamespaceScope {
108        self.scopes.last().copied().unwrap_or_default()
109    }
110
111    fn declare(&mut self, prefix: &'input str, operation: &'static str) -> Result<(), CodecError> {
112        self.ctx.charge_work(
113            u64_from_index(prefix.len())
114                .checked_mul(6)
115                .and_then(|work| work.checked_add(1))
116                .ok_or_else(|| self.ctx.tree_overflow(operation))?,
117            operation,
118        )?;
119        let previous = self.prefixes.get(prefix).copied();
120        if previous.is_none() && self.prefixes.len() == self.prefixes.capacity() {
121            for stored in self.prefixes.keys() {
122                self.ctx.charge_work(
123                    u64_from_index(stored.len())
124                        .checked_add(1)
125                        .ok_or_else(|| self.ctx.tree_overflow(operation))?,
126                    operation,
127                )?;
128            }
129        }
130        self.storage.with_storage(|| {
131            self.ctx
132                .push_vec(&mut self.undo, (prefix, previous), operation)?;
133            self.ctx
134                .insert_hash_map(&mut self.prefixes, prefix, 0, operation)?;
135            Ok::<_, CodecError>(())
136        })
137    }
138
139    fn value(
140        &mut self,
141        prefix: &'input str,
142        uri: &'input str,
143        operation: &'static str,
144    ) -> Result<(), CodecError> {
145        self.ctx.charge_work(
146            u64_from_index(prefix.len())
147                .checked_mul(4)
148                .and_then(|work| work.checked_add(u64_from_index(uri.len()).checked_mul(2)?))
149                .and_then(|work| work.checked_add(1))
150                .ok_or_else(|| self.ctx.tree_overflow(operation))?,
151            operation,
152        )?;
153        if let Some(value) = self.prefixes.get_mut(prefix) {
154            *value = u64_from_index(uri.len());
155        }
156        if self.bindings.contains(&(prefix, uri)) {
157            return Ok(());
158        }
159        if self.bindings.len() == self.bindings.capacity() {
160            for (stored_prefix, stored_uri) in &self.bindings {
161                self.ctx.charge_work(
162                    u64_from_index(stored_prefix.len())
163                        .checked_add(u64_from_index(stored_uri.len()))
164                        .and_then(|work| work.checked_add(1))
165                        .ok_or_else(|| self.ctx.tree_overflow(operation))?,
166                    operation,
167                )?;
168            }
169        }
170        self.storage.with_storage(|| {
171            self.ctx.reserve_set(&mut self.bindings, 1, operation)?;
172            self.bindings.insert((prefix, uri));
173            Ok::<_, CodecError>(())
174        })
175    }
176
177    fn scope(
178        &self,
179        tag: &XmlTag<'_>,
180        operation: &'static str,
181    ) -> Result<XmlNamespaceScope, CodecError> {
182        let mut scope = self.parent();
183        scope.undo_start = tag.undo_start;
184        if tag.namespaces != 0 {
185            scope = XmlNamespaceScope {
186                undo_start: tag.undo_start,
187                // Local declarations include repeated default namespace bindings,
188                // which roxmltree can retain even when their prefixes are equal.
189                count: u64_from_index(self.prefixes.len())
190                    .checked_add(tag.namespaces)
191                    .ok_or_else(|| self.ctx.tree_overflow(operation))?,
192                ..XmlNamespaceScope::default()
193            };
194            for (prefix, uri_bytes) in &self.prefixes {
195                self.ctx.charge_work(1, operation)?;
196                scope.max_prefix = scope.max_prefix.max(u64_from_index(prefix.len()));
197                scope.max_uri = scope.max_uri.max(*uri_bytes);
198            }
199        }
200        Ok(scope)
201    }
202
203    fn restore(&mut self, start: usize, operation: &'static str) -> Result<(), CodecError> {
204        while self.undo.len() > start {
205            if let Some((prefix, previous)) = self.undo.pop() {
206                self.ctx.charge_work(
207                    u64_from_index(prefix.len())
208                        .checked_mul(2)
209                        .and_then(|work| work.checked_add(1))
210                        .ok_or_else(|| self.ctx.tree_overflow(operation))?,
211                    operation,
212                )?;
213                match previous {
214                    Some(previous) => {
215                        self.prefixes.insert(prefix, previous);
216                    }
217                    None => {
218                        self.prefixes.remove(prefix);
219                    }
220                }
221            }
222        }
223        Ok(())
224    }
225
226    fn finish(
227        &mut self,
228        tag: &XmlTag<'_>,
229        mut scope: XmlNamespaceScope,
230        depth: Option<u64>,
231        operation: &'static str,
232    ) -> Result<(), CodecError> {
233        if tag.closing {
234            if self
235                .scopes
236                .last()
237                .is_some_and(|parent| Some(parent.element_depth) == depth)
238            {
239                if let Some(parent) = self.scopes.pop() {
240                    self.restore(parent.undo_start, operation)?;
241                }
242            }
243        } else if tag.last == b'/' {
244            self.restore(tag.undo_start, operation)?;
245        } else if tag.namespaces != 0 {
246            scope.element_depth = depth
247                .unwrap_or(0)
248                .checked_add(1)
249                .ok_or_else(|| self.ctx.tree_overflow(operation))?;
250            self.storage
251                .with_storage(|| self.ctx.push_vec(&mut self.scopes, scope, operation))?;
252        }
253        Ok(())
254    }
255}
256
257struct XmlBound {
258    nodes: u64,
259    attributes: u64,
260    namespace_references: Option<u64>,
261    depth: u64,
262    local_comparisons: Option<u64>,
263    namespace_unique: u64,
264    namespace_bytes: u64,
265}
266
267impl XmlBound {
268    fn tag(&mut self, tag: &XmlTag<'_>, parent: XmlNamespaceScope, scope: XmlNamespaceScope) {
269        self.namespace_bytes += tag.namespace_key_bytes;
270        if tag.namespaces != 0 {
271            self.namespace_references = self
272                .namespace_references
273                .and_then(|references| references.checked_add(scope.count));
274        }
275        self.local_comparisons = self.local_comparisons.and_then(|work| {
276            let attributes = tag
277                .attribute_name_bytes
278                .checked_add(tag.attributes)?
279                .checked_mul(tag.attributes.checked_add(1)?)?;
280            let lookups = tag
281                .name_bytes
282                .checked_add(tag.attribute_name_bytes)?
283                .checked_add(tag.attributes.checked_add(1)?)?
284                .checked_mul(scope.count.checked_add(1)?)?;
285            let uris = tag
286                .prefixed_attributes
287                .checked_mul(tag.prefixed_attributes)?
288                .checked_mul(scope.max_uri.checked_add(1)?)?;
289            let namespaces = if tag.namespaces == 0 {
290                0
291            } else {
292                // Namespace inheritance scans parent names against the growing
293                // local list. Duplicate declarations compare local names too.
294                parent
295                    .count
296                    .checked_mul(parent.count.checked_add(tag.namespaces)?)?
297                    .checked_add(tag.namespaces.checked_mul(tag.namespaces)?)?
298                    .checked_mul(scope.max_prefix.checked_add(1)?)?
299            };
300            work.checked_add(attributes)?
301                .checked_add(lookups)?
302                .checked_add(uris)?
303                .checked_add(namespaces)
304        });
305    }
306
307    fn comparison_work(&self) -> Option<u64> {
308        let namespaces = self.namespace_unique.checked_add(1)?;
309        let levels = u64::from(u64::BITS - namespaces.leading_zeros()) + 1;
310        // Namespace deduplication binary-searches string pairs, then inserts
311        // u16 indices into a sorted vector. Only insertion moves are quadratic.
312        let insertion = self
313            .namespace_bytes
314            .checked_mul(levels)?
315            .checked_add(namespaces.checked_mul(namespaces)?.checked_mul(2)?)?;
316        self.local_comparisons?.checked_add(insertion)
317    }
318}
319
320/// Counts delimiters, element depth and local comparisons in one pass.
321/// Quoted values, comments, CDATA and instructions do not change nesting.
322fn xml_bound(
323    text: &str,
324    ctx: &DecodeContext<'_>,
325    operation: &'static str,
326) -> Result<XmlBound, CodecError> {
327    let bytes = text.as_bytes();
328    let mut depth = Some(0_u64);
329    let mut maximum = 0;
330    let mut bound = XmlBound {
331        nodes: 0,
332        attributes: 0,
333        namespace_references: Some(0),
334        depth: 0,
335        local_comparisons: Some(0),
336        namespace_unique: 0,
337        namespace_bytes: 0,
338    };
339    let mut namespaces = XmlNamespaces::new(ctx, operation)?;
340    let mut state = XmlScan::Text;
341    for (index, &byte) in bytes.iter().enumerate() {
342        bound.nodes += u64::from(byte == b'<');
343        bound.attributes += u64::from(byte == b'=');
344        let tail = &bytes[index..];
345        state = match state {
346            XmlScan::Text if byte == b'<' => {
347                if tail.starts_with(b"<!--") {
348                    XmlScan::Comment
349                } else if tail.starts_with(b"<![CDATA[") {
350                    XmlScan::Cdata
351                } else if tail.starts_with(b"<?") {
352                    XmlScan::Instruction
353                } else if tail.starts_with(b"<!") {
354                    XmlScan::Declaration
355                } else {
356                    XmlScan::Tag(XmlTag {
357                        closing: tail.starts_with(b"</"),
358                        quote: 0,
359                        last: byte,
360                        attributes: 0,
361                        name_bytes: 0,
362                        name_complete: false,
363                        attribute_name_bytes: 0,
364                        namespace_key_bytes: 0,
365                        prefixed_attributes: 0,
366                        namespaces: 0,
367                        start: index,
368                        undo_start: namespaces.undo.len(),
369                        namespace_prefix: None,
370                        quote_start: index,
371                    })
372                }
373            }
374            XmlScan::Tag(mut tag) => {
375                // The slash before a closing QName is not part of its key.
376                if !(tag.name_complete || byte == b'/' && tag.closing && tag.name_bytes == 0) {
377                    if byte.is_ascii_whitespace() || matches!(byte, b'/' | b'>') {
378                        tag.name_complete = true;
379                    } else {
380                        tag.name_bytes += 1;
381                    }
382                }
383                if tag.quote != 0 {
384                    if byte == tag.quote {
385                        if let Some(prefix) = tag.namespace_prefix {
386                            tag.namespace_key_bytes = tag
387                                .namespace_key_bytes
388                                .checked_add(u64_from_index(index - tag.quote_start))
389                                .ok_or_else(|| ctx.tree_overflow(operation))?;
390                            namespaces.value(prefix, &text[tag.quote_start..index], operation)?;
391                        }
392                        tag.quote = 0;
393                    }
394                } else if matches!(byte, b'\'' | b'"') {
395                    tag.quote = byte;
396                    tag.quote_start = index + 1;
397                } else if byte == b'>' {
398                    let scope = namespaces.scope(&tag, operation)?;
399                    bound.tag(&tag, namespaces.parent(), scope);
400                    namespaces.finish(&tag, scope, depth, operation)?;
401                    if tag.closing {
402                        depth = depth.and_then(|depth| depth.checked_sub(1));
403                    } else if let Some(parent_depth) = depth {
404                        let element_depth = parent_depth + 1;
405                        maximum = maximum.max(element_depth);
406                        if tag.last != b'/' {
407                            depth = Some(element_depth);
408                        }
409                    }
410                    state = XmlScan::Text;
411                    continue;
412                } else {
413                    if byte == b'=' {
414                        let mut end = index;
415                        while end > tag.start && bytes[end - 1].is_ascii_whitespace() {
416                            end -= 1;
417                        }
418                        let mut begin = end;
419                        while begin > tag.start
420                            && !bytes[begin - 1].is_ascii_whitespace()
421                            && !matches!(bytes[begin - 1], b'<' | b'>' | b'=' | b'\'' | b'"' | b'/')
422                        {
423                            begin -= 1;
424                        }
425                        let name = &text[begin..end];
426                        tag.attribute_name_bytes = tag
427                            .attribute_name_bytes
428                            .checked_add(u64_from_index(name.len()))
429                            .ok_or_else(|| ctx.tree_overflow(operation))?;
430                        tag.namespace_prefix = if name == "xmlns" {
431                            Some("")
432                        } else {
433                            name.strip_prefix("xmlns:")
434                        };
435                        if let Some(prefix) = tag.namespace_prefix {
436                            tag.namespaces += 1;
437                            tag.namespace_key_bytes = tag
438                                .namespace_key_bytes
439                                .checked_add(u64_from_index(name.len()))
440                                .ok_or_else(|| ctx.tree_overflow(operation))?;
441                            namespaces.declare(prefix, operation)?;
442                        } else {
443                            tag.prefixed_attributes += u64::from(name.contains(':'));
444                        }
445                        tag.attributes += 1;
446                    }
447                    tag.last = byte;
448                }
449                XmlScan::Tag(tag)
450            }
451            XmlScan::Comment if tail.starts_with(b"-->") => XmlScan::Text,
452            XmlScan::Cdata if tail.starts_with(b"]]>") => XmlScan::Text,
453            XmlScan::Instruction if tail.starts_with(b"?>") => XmlScan::Text,
454            XmlScan::Declaration if byte == b'>' => XmlScan::Text,
455            other => other,
456        };
457    }
458    if let XmlScan::Tag(tag) = state {
459        let scope = namespaces.scope(&tag, operation)?;
460        bound.tag(&tag, namespaces.parent(), scope);
461    }
462    bound.depth = maximum;
463    bound.namespace_unique = u64_from_index(namespaces.bindings.len());
464    Ok(bound)
465}
466
467/// Holds nesting guards through the parser call without recursing in the
468/// admission code. Guard slots and their temporary storage are admitted first.
469fn at_depth<T>(
470    ctx: &DecodeContext<'_>,
471    depth: u64,
472    operation: &'static str,
473    parse: impl FnOnce() -> Result<T, CodecError>,
474) -> Result<T, CodecError> {
475    if depth == 0 {
476        return parse();
477    }
478    ctx.charge_work(depth, operation)?;
479    ctx.charge_collection_items(depth, operation)?;
480    let count =
481        usize::try_from(depth).map_err(|_| ctx.refuse_codec_limit(operation, u64::MAX, depth))?;
482    let capacity = count
483        .checked_add(4)
484        .ok_or_else(|| ctx.refuse_codec_limit(operation, u64::MAX, depth))?;
485    let (_reservation, mut guards) = {
486        let (guards, reservation) = ctx.scoped_vector_storage(capacity, operation)?;
487        (reservation, guards)
488    };
489    for _ in 0..count {
490        guards.push(ctx.enter_nested(operation)?);
491    }
492    let result = parse();
493    drop(guards);
494    result
495}
496
497impl DecodeContext<'_> {
498    fn tree_malformed(&self, error: impl std::fmt::Display, operation: &'static str) -> CodecError {
499        match self.format_retained(format_args!("{error}"), operation) {
500            Ok(message) => CodecError::Malformed(message),
501            Err(error) => error,
502        }
503    }
504
505    fn tree_overflow(&self, operation: &'static str) -> CodecError {
506        self.refuse_codec_limit(operation, u64::MAX, u64::MAX)
507    }
508
509    /// Admits roxmltree 0.21.1 storage before parsing with DTD disabled.
510    /// For N = 2 * '<' + 2, A = '=' and S = distinct raw namespace bindings + 1,
511    /// growing vectors reserve 2 * count + their minimum capacity. Storage covers
512    /// nodes, permanent and temporary attributes (minimum 16), namespace records
513    /// and sorted indices. Namespace tree-order slots sum visible bindings at
514    /// elements with declarations; other elements reuse their parent's range.
515    /// Index storage includes old and new allocations during vector growth.
516    /// Node-sized scratch includes awaiting-subtree IDs, parent prefixes and text Cows.
517    /// Eight input lengths cover normalization buffers, joined text, shared
518    /// strings and transient copies; 32 bytes per string cover Arc headers.
519    /// The fixed 1024 bytes cover initial scratch capacities and parser state.
520    /// Work covers the scan, parser scans and quadratic attribute/namespace
521    /// comparisons, including string comparisons and namespace insertion.
522    /// Attribute comparisons use each tag's name bytes and attribute count. Namespace
523    /// work covers prefix lookups, expanded attribute names, inheritance only
524    /// at tags with declarations, and sorted namespace insertion. Text outside
525    /// tags and ordinary attribute values pay only scanning/normalization work.
526    /// Temporary node-ID, prefix,
527    /// text, attribute and namespace-index slots are admitted separately.
528    pub fn parse_xml<'input>(
529        &self,
530        text: &'input str,
531        operation: &'static str,
532    ) -> Result<AdmittedXml<'input, '_>, CodecError> {
533        let length = u64_from_index(text.len());
534        self.charge_work(length, operation)?;
535        let bound = xml_bound(text, self, operation)?;
536        let nodes = bound
537            .nodes
538            .checked_mul(2)
539            .and_then(|n| n.checked_add(2))
540            .ok_or_else(|| self.tree_overflow(operation))?;
541        let namespaces = bound
542            .namespace_unique
543            .checked_add(1)
544            .ok_or_else(|| self.tree_overflow(operation))?;
545        let namespace_references = bound
546            .namespace_references
547            .ok_or_else(|| self.tree_overflow(operation))?;
548        let capacity = |n: u64, minimum: u64| n.checked_mul(2).and_then(|n| n.checked_add(minimum));
549        let bytes = (|| {
550            let node_bytes = capacity(nodes, 4)?.checked_mul(NODE_RECORD_BOUND + 64)?;
551            let attribute_bytes =
552                capacity(bound.attributes, 16)?.checked_mul(ATTRIBUTE_RECORD_BOUND * 2)?;
553            let namespace_bytes =
554                capacity(namespaces, 4)?.checked_mul(NAMESPACE_RECORD_BOUND + 2)?;
555            let namespace_indices = namespace_references
556                .checked_mul(3)?
557                .checked_add(4)?
558                .checked_mul(2)?;
559            let strings = nodes
560                .checked_add(bound.attributes)?
561                .checked_add(namespaces)?
562                .checked_mul(32)?;
563            node_bytes
564                .checked_add(attribute_bytes)?
565                .checked_add(namespace_bytes)?
566                .checked_add(namespace_indices)?
567                .checked_add(strings)?
568                .checked_add(length.checked_mul(8)?)?
569                .checked_add(1024)
570        })()
571        .ok_or_else(|| self.tree_overflow(operation))?;
572        let work = length
573            .checked_mul(4)
574            .and_then(|n| n.checked_add(nodes))
575            .and_then(|n| n.checked_add(bound.comparison_work()?))
576            .ok_or_else(|| self.tree_overflow(operation))?;
577        self.charge_collection_items(nodes, operation)?;
578        self.charge_collection_items(bound.attributes, operation)?;
579        self.charge_collection_items(namespaces, operation)?;
580        let scratch_items = nodes
581            .checked_mul(3)
582            .and_then(|n| n.checked_add(bound.attributes))
583            .and_then(|n| n.checked_add(namespaces))
584            .and_then(|n| n.checked_add(namespace_references))
585            .ok_or_else(|| self.tree_overflow(operation))?;
586        self.charge_collection_items(scratch_items, operation)?;
587        self.charge_work(work, operation)?;
588        let reservation = self.reserve_scoped(bytes, operation)?;
589        let nodes_limit = u32::try_from(nodes).map_err(|_| self.tree_overflow(operation))?;
590        let document = at_depth(self, bound.depth, operation, || {
591            roxmltree::Document::parse_with_options(
592                text,
593                roxmltree::ParsingOptions {
594                    nodes_limit,
595                    ..roxmltree::ParsingOptions::default()
596                },
597            )
598            .map_err(|error| match error {
599                roxmltree::Error::NodesLimitReached => {
600                    self.refuse_codec_limit(operation, u64::from(nodes_limit), nodes)
601                }
602                other => self.tree_malformed(other, operation),
603            })
604        })?;
605        Ok(AdmittedXml {
606            document,
607            _reservation: reservation,
608        })
609    }
610}
611
612/// `serde_json` 1.0.151 uses a `BTreeMap` without `preserve_order`. A leaf has
613/// eleven (String, Value) slots; an internal node adds twelve pointers.
614/// 4096 bytes per potential entry cover two nodes, split/root growth and
615/// alignment, including a one-entry root where capacity rounding dominates.
616const JSON_MAP_ENTRY_BOUND: u64 = 4096;
617const RAW_VALUE_TOKEN: &[u8] = b"$serde_json::private::RawValue";
618
619#[derive(Default)]
620struct JsonStringScan {
621    active: bool,
622    escaped: bool,
623    unicode_digits: u8,
624    unicode_value: u32,
625    matched: usize,
626    possible: bool,
627}
628
629impl JsonStringScan {
630    fn decoded(&mut self, byte: Option<u8>) {
631        if byte.is_some() && byte == RAW_VALUE_TOKEN.get(self.matched).copied() {
632            self.matched += 1;
633        } else {
634            self.possible = false;
635        }
636    }
637
638    /// Returns true only when a completed string spells the raw-value token.
639    fn byte(&mut self, byte: u8) -> bool {
640        if self.unicode_digits != 0 {
641            if let Some(digit) = char::from(byte).to_digit(16) {
642                self.unicode_value = self.unicode_value * 16 + digit;
643            } else {
644                self.possible = false;
645            }
646            self.unicode_digits -= 1;
647            if self.unicode_digits == 0 {
648                self.decoded(u8::try_from(self.unicode_value).ok());
649            }
650        } else if self.escaped {
651            self.escaped = false;
652            if byte == b'u' {
653                self.unicode_digits = 4;
654                self.unicode_value = 0;
655            } else {
656                self.decoded(match byte {
657                    b'"' | b'\\' | b'/' => Some(byte),
658                    b'b' => Some(8),
659                    b'f' => Some(12),
660                    b'n' => Some(b'\n'),
661                    b'r' => Some(b'\r'),
662                    b't' => Some(b'\t'),
663                    _ => None,
664                });
665            }
666        } else if byte == b'\\' {
667            self.escaped = true;
668        } else if byte == b'"' {
669            self.active = false;
670            return self.possible && self.matched == RAW_VALUE_TOKEN.len();
671        } else {
672            self.decoded(Some(byte));
673        }
674        false
675    }
676}
677
678/// Builds an ordinary JSON tree for derived typed conversion. The Value
679/// deserializer's private raw-value carrier is not part of derived JSON data;
680/// ignored extension members must remain ordinary objects during admission.
681struct PlainJson;
682
683impl<'de> serde::de::DeserializeSeed<'de> for PlainJson {
684    type Value = serde_json::Value;
685    fn deserialize<D: serde::Deserializer<'de>>(self, parser: D) -> Result<Self::Value, D::Error> {
686        parser.deserialize_any(self)
687    }
688}
689
690impl<'de> serde::de::Visitor<'de> for PlainJson {
691    type Value = serde_json::Value;
692    fn expecting(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
693        formatter.write_str("a JSON value")
694    }
695    fn visit_unit<E: serde::de::Error>(self) -> Result<Self::Value, E> {
696        Ok(serde_json::Value::Null)
697    }
698    fn visit_bool<E: serde::de::Error>(self, value: bool) -> Result<Self::Value, E> {
699        Ok(serde_json::Value::Bool(value))
700    }
701    fn visit_i64<E: serde::de::Error>(self, value: i64) -> Result<Self::Value, E> {
702        Ok(serde_json::Value::Number(value.into()))
703    }
704    fn visit_u64<E: serde::de::Error>(self, value: u64) -> Result<Self::Value, E> {
705        Ok(serde_json::Value::Number(value.into()))
706    }
707    fn visit_f64<E: serde::de::Error>(self, value: f64) -> Result<Self::Value, E> {
708        serde_json::Number::from_f64(value)
709            .map(serde_json::Value::Number)
710            .ok_or_else(|| E::custom("JSON number is not finite"))
711    }
712    fn visit_str<E: serde::de::Error>(self, value: &str) -> Result<Self::Value, E> {
713        Ok(serde_json::Value::String(value.to_owned()))
714    }
715    fn visit_string<E: serde::de::Error>(self, value: String) -> Result<Self::Value, E> {
716        Ok(serde_json::Value::String(value))
717    }
718    fn visit_seq<A: serde::de::SeqAccess<'de>>(
719        self,
720        mut sequence: A,
721    ) -> Result<Self::Value, A::Error> {
722        let mut values = Vec::new();
723        while let Some(value) = sequence.next_element_seed(PlainJson)? {
724            values.push(value);
725        }
726        Ok(serde_json::Value::Array(values))
727    }
728    fn visit_map<A: serde::de::MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
729        let mut values = serde_json::Map::new();
730        while let Some(key) = map.next_key::<String>()? {
731            let value = map.next_value_seed(PlainJson)?;
732            drop(values.insert(key, value));
733        }
734        Ok(serde_json::Value::Object(values))
735    }
736}
737
738struct JsonBound {
739    values: u64,
740    entries: u64,
741    depth: u64,
742    bytes: u64,
743}
744
745impl DecodeContext<'_> {
746    /// Bounds `serde_json` 1.0.151 trees in one scan. Delimiters '[' '{' ',' ':'
747    /// plus one bound value slots. All array capacities together are at most
748    /// six slots per value (doubling plus minimum four). Map entry capacity is
749    /// charged per colon. Eight input lengths cover decoded
750    /// strings, growing scratch and temporary copies; 1024 covers parser state.
751    /// The `raw_value` feature can parse strings again when a first key spells
752    /// its private token, including escaped spellings. For that carrier,
753    /// opening delimiters and escapes bound replay depth and hidden delimiters;
754    /// eight input lengths per level cover simultaneous replay strings/scratch.
755    /// Work includes replay scans, map comparisons and copies before parsing.
756    fn json_bound(&self, text: &str, operation: &'static str) -> Result<JsonBound, CodecError> {
757        let length = u64_from_index(text.len());
758        self.charge_work(length, operation)?;
759        let mut values = 0_u64;
760        let mut entries = 0_u64;
761        let mut depth = 0_u64;
762        let mut maximum = 0_u64;
763        let mut openings = 0_u64;
764        let mut escapes = 0_u64;
765        let mut raw = false;
766        let mut string = JsonStringScan::default();
767        for &byte in text.as_bytes() {
768            values += u64::from(matches!(byte, b'[' | b'{' | b',' | b':'));
769            entries += u64::from(byte == b':');
770            openings += u64::from(matches!(byte, b'[' | b'{'));
771            escapes += u64::from(byte == b'\\');
772            if string.active {
773                raw |= string.byte(byte);
774                continue;
775            }
776            match byte {
777                b'"' => {
778                    string = JsonStringScan {
779                        active: true,
780                        possible: true,
781                        ..JsonStringScan::default()
782                    }
783                }
784                b'[' | b'{' => {
785                    depth += 1;
786                    maximum = maximum.max(depth);
787                }
788                b']' | b'}' if depth != 0 => depth -= 1,
789                _ => {}
790            }
791        }
792        values = values
793            .checked_add(1)
794            .ok_or_else(|| self.tree_overflow(operation))?;
795        let levels = if raw {
796            maximum = openings
797                .checked_add(escapes)
798                .ok_or_else(|| self.tree_overflow(operation))?;
799            values = values
800                .checked_add(escapes)
801                .ok_or_else(|| self.tree_overflow(operation))?;
802            entries = entries
803                .checked_add(escapes)
804                .ok_or_else(|| self.tree_overflow(operation))?;
805            maximum
806                .checked_add(1)
807                .ok_or_else(|| self.tree_overflow(operation))?
808        } else {
809            1
810        };
811        let bytes = (|| {
812            values
813                .checked_mul(6)?
814                .checked_mul(u64_from_index(std::mem::size_of::<serde_json::Value>()))?
815                .checked_add(entries.checked_mul(JSON_MAP_ENTRY_BOUND)?)?
816                .checked_add(length.checked_mul(8)?.checked_mul(levels)?)?
817                .checked_add(1024)
818        })()
819        .ok_or_else(|| self.tree_overflow(operation))?;
820        Ok(JsonBound {
821            values,
822            entries,
823            depth: maximum,
824            bytes,
825        })
826    }
827
828    fn parse_json_tree(
829        &self,
830        text: &str,
831        operation: &'static str,
832        interpret_raw: bool,
833    ) -> Result<(serde_json::Value, ScopedReservation<'_>, JsonBound), CodecError> {
834        let bound = self.json_bound(text, operation)?;
835        self.charge_collection_items(bound.values, operation)?;
836        let work = u64_from_index(text.len())
837            .checked_mul(
838                bound
839                    .entries
840                    .checked_add(1)
841                    .ok_or_else(|| self.tree_overflow(operation))?,
842            )
843            .and_then(|n| n.checked_mul(bound.depth.checked_add(1)?))
844            .ok_or_else(|| self.tree_overflow(operation))?;
845        self.charge_work(work, operation)?;
846        let reservation = self.reserve_scoped(bound.bytes, operation)?;
847        let value = at_depth(self, bound.depth, operation, || {
848            if interpret_raw {
849                serde_json::from_str(text).map_err(|error| self.tree_malformed(error, operation))
850            } else {
851                use serde::de::DeserializeSeed;
852                let mut parser = serde_json::Deserializer::from_str(text);
853                PlainJson
854                    .deserialize(&mut parser)
855                    .and_then(|value| {
856                        parser.end()?;
857                        Ok(value)
858                    })
859                    .map_err(|error| self.tree_malformed(error, operation))
860            }
861        })?;
862        Ok((value, reservation, bound))
863    }
864
865    /// Parses a value tree under collection, work, scoped storage and depth
866    /// admission. Keep the returned reservation alive with the returned Value.
867    /// `serde_json`'s own 128-level recursion ceiling also applies.
868    pub fn parse_json_value(
869        &self,
870        text: &str,
871        operation: &'static str,
872    ) -> Result<(serde_json::Value, ScopedReservation<'_>), CodecError> {
873        let (value, reservation, _) = self.parse_json_tree(text, operation, true)?;
874        Ok((value, reservation))
875    }
876
877    /// Parses types with derived Deserialize: structs, enums, vectors, maps,
878    /// strings and scalars. Custom allocating deserializers are outside this
879    /// bound. The retained typed result is admitted as twice the value-tree
880    /// storage plus `size_of::<T>()`; conversion charges one work unit per value.
881    /// Source validation with the derived deserializer preserves duplicate-field
882    /// errors that Value maps erase. Its temporary result has a separate scoped
883    /// admission of twice the tree bytes plus `size_of::<T>()`; scan and conversion
884    /// work are charged before validation. Typed trees use ordinary map members
885    /// rather than Value's private raw-value carrier. The admission remains live.
886    pub fn parse_json<T: serde::de::DeserializeOwned>(
887        &self,
888        text: &str,
889        operation: &'static str,
890    ) -> Result<T, CodecError> {
891        let (_reservation, value, bound) = {
892            let (value, reservation, bound) = self.parse_json_tree(text, operation, false)?;
893            (reservation, value, bound)
894        };
895        let retained = bound
896            .bytes
897            .checked_mul(2)
898            .and_then(|n| n.checked_add(u64_from_index(std::mem::size_of::<T>())))
899            .ok_or_else(|| self.tree_overflow(operation))?;
900        let validation_work = u64_from_index(text.len())
901            .checked_mul(bound.values)
902            .and_then(|n| n.checked_mul(bound.depth.checked_add(1)?))
903            .ok_or_else(|| self.tree_overflow(operation))?;
904        self.charge_work(validation_work, operation)?;
905        self.charge_collection_items(bound.values, operation)?;
906        {
907            let _validation = self.reserve_scoped(retained, operation)?;
908            at_depth(self, bound.depth, operation, || {
909                let validated: T = serde_json::from_str(text)
910                    .map_err(|error| self.tree_malformed(error, operation))?;
911                drop(validated);
912                Ok(())
913            })?;
914        }
915        self.charge_retained(retained, operation)?;
916        self.charge_collection_items(bound.values, operation)?;
917        self.charge_work(bound.values, operation)?;
918        at_depth(self, bound.depth, operation, || {
919            serde_json::from_value(value).map_err(|error| self.tree_malformed(error, operation))
920        })
921    }
922}
923
924#[cfg(test)]
925mod tests {
926    use super::ATTRIBUTE_RECORD_BOUND;
927    use crate::decode::{
928        u64_from_index, DecodeArena, DecodeContext, DecodePolicy, ResourceDimension,
929    };
930    use crate::CodecError;
931
932    #[test]
933    fn tree_xml_success_and_capacity_rounding() {
934        let arena = DecodeArena::new();
935        let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &DecodePolicy::default())
936            .expect("valid fixture");
937        let tree = ctx.parse_xml("<r/>", "XML tree").expect("valid fixture");
938        assert_eq!(tree.document().root_element().tag_name().name(), "r");
939        let mut policy = DecodePolicy::default();
940        policy.limits.max_materialized_bytes = 0;
941        let (limited, _) =
942            DecodeContext::from_root_bytes(b"", &arena, &policy).expect("valid fixture");
943        let CodecError::ResourceLimit(limit) = limited
944            .parse_xml("<r/>", "XML tree")
945            .expect_err("fixture must refuse")
946        else {
947            panic!("byte refusal");
948        };
949        assert!(limit.additional >= 16 * ATTRIBUTE_RECORD_BOUND);
950    }
951
952    #[test]
953    fn tree_xml_work_compares_attributes_within_each_tag() {
954        let text = format!("<r>{}</r>", "<a x='0'/>".repeat(16));
955        let arena = DecodeArena::new();
956        let mut policy = DecodePolicy::default();
957        policy.limits.max_work_units = 32 * u64_from_index(text.len());
958        let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &policy).expect("valid fixture");
959        let tree = ctx.parse_xml(&text, "XML tree").expect("valid fixture");
960        assert_eq!(tree.document().root_element().children().count(), 16);
961    }
962
963    #[test]
964    fn tree_xml_large_text_does_not_pay_attribute_comparison_work() {
965        let mut text = String::from("<r");
966        for index in 0..64 {
967            std::fmt::Write::write_fmt(&mut text, format_args!(" a{index}='0'"))
968                .expect("fixture string");
969        }
970        text.push('>');
971        text.push_str(&"ordinary text ".repeat(16_000));
972        text.push_str("</r>");
973        let arena = DecodeArena::new();
974        let mut policy = DecodePolicy::service();
975        policy.limits.max_work_units = 8 * u64_from_index(text.len());
976        let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty input");
977        let tree = ctx
978            .parse_xml(&text, "local XML comparisons")
979            .expect("text scanning is linear");
980        assert_eq!(tree.document().root_element().attributes().len(), 64);
981        drop(tree);
982        ctx.finish_session().expect("unfused session");
983    }
984
985    #[test]
986    fn tree_xml_large_attribute_values_do_not_pay_key_comparison_work() {
987        let value = "value text ".repeat(16_000);
988        let mut text = String::from("<r xmlns:p='urn:shared'");
989        for index in 0..64 {
990            std::fmt::Write::write_fmt(&mut text, format_args!(" a{index}='0'"))
991                .expect("fixture string");
992        }
993        std::fmt::Write::write_fmt(&mut text, format_args!(" data='{value}'/>"))
994            .expect("fixture string");
995        let arena = DecodeArena::new();
996        let mut policy = DecodePolicy::service();
997        policy.limits.max_work_units = 8 * u64_from_index(text.len());
998        let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty input");
999        let tree = ctx
1000            .parse_xml(&text, "XML attribute value scanning")
1001            .expect("values are not comparison keys");
1002        assert_eq!(tree.document().root_element().attributes().len(), 65);
1003        assert_eq!(
1004            tree.document().root_element().attribute("data"),
1005            Some(value.as_str())
1006        );
1007        drop(tree);
1008        ctx.finish_session().expect("unfused session");
1009    }
1010
1011    #[test]
1012    fn tree_xml_namespace_comparisons_are_separate_from_text() {
1013        let text = format!(
1014            "<r xmlns:p='urn:shared' xmlns:q='urn:shared'><s p:a='1' q:b='2'>{}</s></r>",
1015            "text ".repeat(8_000)
1016        );
1017        let arena = DecodeArena::new();
1018        let mut policy = DecodePolicy::service();
1019        policy.limits.max_work_units = 8 * u64_from_index(text.len());
1020        let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty input");
1021        let tree = ctx
1022            .parse_xml(&text, "namespace XML comparisons")
1023            .expect("local namespaces");
1024        let child = tree
1025            .document()
1026            .root_element()
1027            .first_element_child()
1028            .expect("s element");
1029        assert_eq!(child.attribute(("urn:shared", "a")), Some("1"));
1030        assert_eq!(child.attribute(("urn:shared", "b")), Some("2"));
1031        drop(tree);
1032        ctx.finish_session().expect("unfused session");
1033    }
1034
1035    #[test]
1036    fn tree_xml_repeated_sibling_namespaces_use_local_uri_bounds() {
1037        let text = format!(
1038            "<r><a xmlns:p='{}'/>{}</r>",
1039            "u".repeat(8_192),
1040            "<b xmlns:p='urn:small'><p:t p:x='1' p:y='2'/></b>".repeat(128)
1041        );
1042        let arena = DecodeArena::new();
1043        let mut policy = DecodePolicy::service();
1044        policy.limits.max_work_units = 32 * u64_from_index(text.len());
1045        let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty input");
1046        let tree = ctx
1047            .parse_xml(&text, "scoped XML namespaces")
1048            .expect("sibling namespace work is local");
1049        assert_eq!(
1050            tree.document()
1051                .root_element()
1052                .children()
1053                .filter(roxmltree::Node::is_element)
1054                .count(),
1055            129
1056        );
1057        let last = tree
1058            .document()
1059            .root_element()
1060            .last_element_child()
1061            .expect("b element");
1062        let child = last.first_element_child().expect("p:t element");
1063        assert_eq!(child.attribute(("urn:small", "x")), Some("1"));
1064        drop(tree);
1065        ctx.finish_session().expect("unfused session");
1066    }
1067
1068    #[test]
1069    fn tree_xml_sibling_namespace_storage_is_linear_in_declarations() {
1070        let text = format!(
1071            "<r>{}</r>",
1072            "<b xmlns:p='urn:shared'><p:t p:x='1'/></b>".repeat(5_000)
1073        );
1074        let arena = DecodeArena::new();
1075        let mut policy = DecodePolicy::service();
1076        policy.limits.max_collection_items = 200_000;
1077        policy.limits.max_materialized_bytes = 32 * 1024 * 1024;
1078        policy.limits.max_work_units = 16 * u64_from_index(text.len());
1079        let (ctx, _) =
1080            DecodeContext::from_root_bytes(text.as_bytes(), &arena, &policy).expect("XML root");
1081        let tree = ctx
1082            .parse_xml(&text, "linear XML namespace storage")
1083            .expect("sibling namespace ranges fit the collection and storage limits");
1084        assert_eq!(
1085            tree.document()
1086                .root_element()
1087                .children()
1088                .filter(roxmltree::Node::is_element)
1089                .count(),
1090            5_000
1091        );
1092        let last = tree
1093            .document()
1094            .root_element()
1095            .last_element_child()
1096            .expect("b element");
1097        assert_eq!(
1098            last.first_element_child()
1099                .expect("p:t element")
1100                .attribute(("urn:shared", "x")),
1101            Some("1")
1102        );
1103        drop(tree);
1104        drop(
1105            ctx.reserve_scoped(policy.limits.max_materialized_bytes, "XML storage released")
1106                .expect("all temporary storage released"),
1107        );
1108        ctx.finish_session().expect("unfused session");
1109    }
1110
1111    #[test]
1112    fn tree_xml_shadowed_namespace_bounds_restore_at_element_depth() {
1113        let text = format!(
1114            "<r xmlns:p='urn:root'>{}{}<p:last p:x='1'/></r>",
1115            "<a xmlns:p='u'>".repeat(16),
1116            "</a>".repeat(16)
1117        );
1118        let arena = DecodeArena::new();
1119        let mut policy = DecodePolicy::service();
1120        policy.limits.max_materialized_bytes = 65_536;
1121        policy.limits.max_work_units = 64 * u64_from_index(text.len());
1122        let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty input");
1123        let tree = ctx
1124            .parse_xml(&text, "shadowed XML namespaces")
1125            .expect("shadowing keeps a local namespace count");
1126        let last = tree
1127            .document()
1128            .root_element()
1129            .last_element_child()
1130            .expect("p:last element");
1131        assert_eq!(last.tag_name().namespace(), Some("urn:root"));
1132        assert_eq!(last.attribute(("urn:root", "x")), Some("1"));
1133        drop(tree);
1134        drop(
1135            ctx.reserve_scoped(policy.limits.max_materialized_bytes, "XML storage released")
1136                .expect("all temporary storage released"),
1137        );
1138        ctx.finish_session().expect("unfused session");
1139    }
1140
1141    #[test]
1142    fn tree_xml_unterminated_tag_keeps_attribute_work_bound() {
1143        let mut attributes = String::new();
1144        for index in 0..16 {
1145            std::fmt::Write::write_fmt(&mut attributes, format_args!(" x{index}='0'"))
1146                .expect("fixture string write");
1147        }
1148        let text = format!("<r{attributes}");
1149        let arena = DecodeArena::new();
1150        let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &DecodePolicy::service())
1151            .expect("empty input");
1152        let bound = super::xml_bound(&text, &ctx, "XML bound").expect("finite bound");
1153        let name_bytes = (0..16)
1154            .map(|index| format!("x{index}").len())
1155            .sum::<usize>();
1156        assert!(
1157            bound.local_comparisons.expect("finite bound")
1158                >= 17 * (u64_from_index(name_bytes) + 16)
1159        );
1160    }
1161
1162    #[test]
1163    fn tree_xml_resource_dimensions() {
1164        for dimension in [
1165            ResourceDimension::CollectionItems,
1166            ResourceDimension::MaterializedBytes,
1167            ResourceDimension::WorkUnits,
1168            ResourceDimension::RecursionDepth,
1169        ] {
1170            let mut policy = DecodePolicy::default();
1171            match dimension {
1172                ResourceDimension::CollectionItems => policy.limits.max_collection_items = 0,
1173                ResourceDimension::MaterializedBytes => policy.limits.max_materialized_bytes = 0,
1174                ResourceDimension::WorkUnits => policy.limits.max_work_units = 0,
1175                ResourceDimension::RecursionDepth => policy.limits.max_recursion_depth = 0,
1176                _ => panic!("test dimension"),
1177            }
1178            let arena = DecodeArena::new();
1179            let (ctx, _) =
1180                DecodeContext::from_root_bytes(b"", &arena, &policy).expect("valid fixture");
1181            let CodecError::ResourceLimit(limit) = ctx
1182                .parse_xml("<r/>", "XML tree")
1183                .expect_err("fixture must refuse")
1184            else {
1185                panic!("resource refusal");
1186            };
1187            assert_eq!(limit.dimension, dimension);
1188            assert_eq!(ctx.resource_refusal(), Some(limit));
1189        }
1190    }
1191
1192    #[test]
1193    fn tree_xml_malformed_and_lexical_depth() {
1194        let arena = DecodeArena::new();
1195        let mut policy = DecodePolicy::default();
1196        policy.limits.max_recursion_depth = 1;
1197        let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &policy).expect("valid fixture");
1198        assert!(matches!(
1199            ctx.parse_xml("<r>", "XML tree"),
1200            Err(CodecError::Malformed(_))
1201        ));
1202        assert!(ctx.resource_refusal().is_none());
1203        ctx.parse_xml(
1204            "<r a='>'><!-- <a> --><![CDATA[<b>]]><?pi <c> ?></r>",
1205            "XML tree",
1206        )
1207        .expect("valid fixture");
1208        assert!(
1209            matches!(ctx.parse_xml("<r><s/></r>", "XML tree"), Err(CodecError::ResourceLimit(limit)) if limit.dimension == ResourceDimension::RecursionDepth)
1210        );
1211    }
1212    #[test]
1213    fn tree_malformed_diagnostic_preserves_retained_refusal() {
1214        for json in [false, true] {
1215            let arena = DecodeArena::new();
1216            let mut policy = DecodePolicy::default();
1217            policy.limits.max_retained_bytes = 0;
1218            let (ctx, _) =
1219                DecodeContext::from_root_bytes(b"", &arena, &policy).expect("valid fixture");
1220            let error = if json {
1221                ctx.parse_json_value("[", "JSON tree")
1222                    .expect_err("fixture must refuse")
1223            } else {
1224                ctx.parse_xml("<r>", "XML tree")
1225                    .expect_err("fixture must refuse")
1226            };
1227            let CodecError::ResourceLimit(limit) = error else {
1228                panic!("diagnostic admission must refuse");
1229            };
1230            assert_eq!(limit.dimension, ResourceDimension::RetainedBytes);
1231            assert_eq!(ctx.resource_refusal(), Some(limit));
1232        }
1233    }
1234
1235    #[test]
1236    fn invalid_json_prefix_preserves_collection_refusal() {
1237        let mut policy = DecodePolicy::service();
1238        policy.limits.max_collection_items = 2;
1239        let arena = DecodeArena::new();
1240        let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &policy).expect("valid fixture");
1241        let error = ctx
1242            .parse_json_value("[0,0,0,", "JSON nodes")
1243            .expect_err("fixture must refuse");
1244        let CodecError::ResourceLimit(limit) = error else {
1245            panic!("invalid prefix must preserve its admission refusal");
1246        };
1247        assert_eq!(limit.operation, "JSON nodes");
1248        assert_eq!(Some(limit), ctx.resource_refusal());
1249    }
1250
1251    #[derive(Debug, serde::Deserialize, PartialEq)]
1252    struct JsonRecord {
1253        name: String,
1254        values: Vec<u64>,
1255    }
1256
1257    #[test]
1258    fn tree_json_value_success_and_capacity_rounding() {
1259        let arena = DecodeArena::new();
1260        let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &DecodePolicy::default())
1261            .expect("valid fixture");
1262        for text in ["[0]", "{}", "0", "\"a\""] {
1263            let (value, _scope) = ctx
1264                .parse_json_value(text, "JSON tree")
1265                .expect("valid fixture");
1266            assert_eq!(
1267                value,
1268                serde_json::from_str::<serde_json::Value>(text).expect("valid fixture")
1269            );
1270            let bound = ctx.json_bound(text, "JSON bound").expect("valid fixture");
1271            assert!(
1272                bound.bytes >= 4 * u64_from_index(std::mem::size_of::<serde_json::Value>()) + 1024
1273            );
1274        }
1275    }
1276
1277    #[test]
1278    fn tree_json_typed_success() {
1279        let ctx = crate::decode::DecodeArena::new();
1280        let (ctx, _) = DecodeContext::from_root_bytes(b"", &ctx, &DecodePolicy::default())
1281            .expect("valid fixture");
1282        let record: JsonRecord = ctx
1283            .parse_json(r#"{"name":"r","values":[0]}"#, "typed JSON tree")
1284            .expect("valid fixture");
1285        assert_eq!(
1286            record,
1287            JsonRecord {
1288                name: "r".into(),
1289                values: vec![0]
1290            }
1291        );
1292    }
1293
1294    #[test]
1295    fn tree_json_resource_dimensions() {
1296        for typed in [false, true] {
1297            for dimension in [
1298                ResourceDimension::CollectionItems,
1299                ResourceDimension::MaterializedBytes,
1300                ResourceDimension::WorkUnits,
1301                ResourceDimension::RecursionDepth,
1302                ResourceDimension::RetainedBytes,
1303            ] {
1304                if !typed && dimension == ResourceDimension::RetainedBytes {
1305                    continue;
1306                }
1307                let mut policy = DecodePolicy::default();
1308                match dimension {
1309                    ResourceDimension::CollectionItems => policy.limits.max_collection_items = 0,
1310                    ResourceDimension::MaterializedBytes => {
1311                        policy.limits.max_materialized_bytes = 0;
1312                    }
1313                    ResourceDimension::WorkUnits => policy.limits.max_work_units = 0,
1314                    ResourceDimension::RecursionDepth => policy.limits.max_recursion_depth = 0,
1315                    ResourceDimension::RetainedBytes => policy.limits.max_retained_bytes = 0,
1316                    _ => panic!("test dimension"),
1317                }
1318                let arena = DecodeArena::new();
1319                let (ctx, _) =
1320                    DecodeContext::from_root_bytes(b"", &arena, &policy).expect("valid fixture");
1321                let error = if typed {
1322                    ctx.parse_json::<Vec<u64>>("[0]", "typed JSON tree")
1323                        .expect_err("fixture must refuse")
1324                } else {
1325                    ctx.parse_json_value("[0]", "JSON tree")
1326                        .expect_err("fixture must refuse")
1327                };
1328                let CodecError::ResourceLimit(limit) = error else {
1329                    panic!("resource refusal");
1330                };
1331                assert_eq!(limit.dimension, dimension);
1332                assert_eq!(ctx.resource_refusal(), Some(limit));
1333            }
1334        }
1335    }
1336
1337    #[test]
1338    fn tree_json_malformed_preserves_parse_route() {
1339        let arena = DecodeArena::new();
1340        let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &DecodePolicy::default())
1341            .expect("valid fixture");
1342        assert!(matches!(
1343            ctx.parse_json_value("[", "JSON tree"),
1344            Err(CodecError::Malformed(_))
1345        ));
1346        assert!(matches!(
1347            ctx.parse_json::<JsonRecord>("[", "typed JSON tree"),
1348            Err(CodecError::Malformed(_))
1349        ));
1350        assert!(matches!(
1351            ctx.parse_json::<JsonRecord>("{}", "typed JSON tree"),
1352            Err(CodecError::Malformed(_))
1353        ));
1354        assert!(ctx.resource_refusal().is_none());
1355    }
1356
1357    #[test]
1358    fn tree_json_typed_preserves_ignored_raw_carrier_extensions() {
1359        let arena = DecodeArena::new();
1360        let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &DecodePolicy::default())
1361            .expect("valid fixture");
1362        let text =
1363            r#"{"name":"r","values":[],"extension":{"$serde_json::private::RawValue":"not-json"}}"#;
1364        let record: JsonRecord = ctx
1365            .parse_json(text, "typed JSON tree")
1366            .expect("valid fixture");
1367        assert_eq!(
1368            record,
1369            JsonRecord {
1370                name: "r".into(),
1371                values: Vec::new()
1372            }
1373        );
1374    }
1375
1376    #[test]
1377    fn tree_json_typed_preserves_duplicate_field_errors() {
1378        let arena = DecodeArena::new();
1379        let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &DecodePolicy::default())
1380            .expect("valid fixture");
1381        let text = r#"{"name":"a","name":"b","values":[]}"#;
1382        assert!(matches!(
1383            ctx.parse_json::<JsonRecord>(text, "typed JSON tree"),
1384            Err(CodecError::Malformed(_))
1385        ));
1386        assert!(ctx.resource_refusal().is_none());
1387        let (value, _scope) = ctx
1388            .parse_json_value(text, "JSON tree")
1389            .expect("valid fixture");
1390        assert_eq!(value["name"], "b");
1391    }
1392
1393    #[test]
1394    fn tree_json_lexical_and_raw_carrier_depth() {
1395        let arena = DecodeArena::new();
1396        let mut policy = DecodePolicy::default();
1397        policy.limits.max_recursion_depth = 1;
1398        let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &policy).expect("valid fixture");
1399        ctx.parse_json_value(r#"["[[["]"#, "JSON tree")
1400            .expect("valid fixture");
1401        let text = r#"{"$serde_json::private::RawValue":"\u005b0\u005d"}"#;
1402        assert!(
1403            matches!(ctx.parse_json_value(text, "JSON tree"), Err(CodecError::ResourceLimit(limit)) if limit.dimension == ResourceDimension::RecursionDepth)
1404        );
1405        let (ctx, _) = DecodeContext::from_root_bytes(b"", &arena, &DecodePolicy::default())
1406            .expect("valid fixture");
1407        let (value, _scope) = ctx
1408            .parse_json_value(text, "JSON tree")
1409            .expect("valid fixture");
1410        assert_eq!(value, serde_json::json!([0]));
1411        let escaped_key = r#"{"\u0024serde_json::private::RawValue":"[0]"}"#;
1412        let bound = ctx
1413            .json_bound(escaped_key, "JSON bound")
1414            .expect("valid fixture");
1415        assert!(bound.depth >= 2);
1416    }
1417}