Skip to main content

omnist/
limits.rs

1//! Runtime-configurable safety limits (omnist-spec section 2.4, D-9 to D-13).
2//!
3//! The spec requires every implementation to enforce a *finite* limit on
4//! nesting depth, node count and integer digits, lets it pick the numbers
5//! (D-10), and requires it to document them (D-11). It also says an
6//! implementation SHOULD enforce a finite maximum input size in bytes (D-23),
7//! which this port does. This module is where this port documents them and
8//! where a caller changes them.
9//!
10//! | Limit | Default | Ceiling | Field |
11//! |---|---|---|---|
12//! | Nesting depth | [`DEFAULT_MAX_DEPTH`] (200) | [`MAX_DEPTH_CEILING`] (250) | [`Limits::max_depth`] |
13//! | Node count (containers) | [`DEFAULT_MAX_NODES`] (1 000 000) | [`MAX_NODES_CEILING`] (10 000 000) | [`Limits::max_nodes`] |
14//! | Integer digits | [`DEFAULT_MAX_INT_DIGITS`] (4 300) | [`MAX_INT_DIGITS_CEILING`] (43 000) | [`Limits::max_int_digits`] |
15//! | Input size, bytes | [`DEFAULT_MAX_INPUT_BYTES`] (64 MiB) | [`MAX_INPUT_BYTES_CEILING`] (1 GiB) | [`Limits::max_input_bytes`] |
16//!
17//! The default of each of the first three is the spec's reference default;
18//! the spec names no default for the input size (D-24), so 64 MiB is this
19//! port's own choice: it is the cap that bounds parse cost, and the section
20//! on the input size below says how it was chosen. The ceilings are
21//! this port's own (the spec recommends none for these three): a limit is a
22//! denial-of-service bound, not a tuning knob without an upper end, and the
23//! depth ceiling in particular keeps the recursive readers inside a thread's
24//! stack: measured with a 2 MB thread stack, the JSON, OML, YAML and XML
25//! readers and `Doc` handle 300 levels in a debug build and 1 000 in a
26//! release build, but overflow at 400 in debug, so 250 is safe in both
27//! profiles. A caller on a smaller stack than 2 MB must lower the depth.
28//!
29//! As for [`crate::formats::yaml::YamlReadOptions`], `0` selects the default
30//! (a zero or unset value never widens a limit) and a value above its
31//! ceiling is *refused* by [`Limits::validate`], never clamped.
32//!
33//! A node is a container (an edge list), as D-9 counts them: keys and scalar
34//! values are not nodes, so a flat mapping of any number of scalar entries is
35//! one node. A document whose deepest leaf sits at depth `max_depth` is
36//! accepted and one level deeper is refused; likewise for the other two
37//! (a count or digit length equal to the limit is accepted).
38//!
39//! Exceeding a limit raises `document.limit.depth`, `document.limit.nodes`,
40//! `document.limit.int-digits` or `document.limit.input-size` as a
41//! [`crate::error::DocumentError`] (E-11): depth and node count are
42//! properties of the document as a whole and carry path `$`, an over-long
43//! integer carries the Document path of that integer, and an input over the
44//! size limit carries `$` too (it is a property of the input as a whole).
45//!
46//! ## The input size (D-23, D-24)
47//!
48//! The size is **bytes of the input as received, not characters**: `\u{e9}`
49//! counts as two, and a leading byte-order mark counts as three, because the
50//! length is taken before the mark is stripped (D-15) and before any
51//! decoding or parsing. The check is the first thing every reader does, so
52//! `document.limit.input-size` is reported ahead of `parse.invalid-encoding`,
53//! of a doubled-BOM error and of every other diagnostic. An input of exactly
54//! the maximum is accepted. It applies to all five formats, to a YAML input
55//! with no alias included, and to [`crate::document::Doc::from_format`] for
56//! any registered format; the CLI also stops reading a file or stdin at the
57//! maximum plus one byte instead of buffering the rest (`--max-input-bytes`).
58//!
59//! The default, 64 MiB, is chosen to bound the cost of the slowest codec
60//! library, not to promise that an input that large parses quickly: the
61//! spec says a cap bounds parse cost without making any parse fast, and
62//! asks an implementation to measure its slowest codec at the size it
63//! chooses (D-24). `docs/limitations.md` records the measurement and the
64//! behaviour change: before 0.9.0-alpha a reader took any size.
65//! Only Documents read from text or bytes are bounded: an OSD schema text is
66//! not (D-25 asks for a schema-size bound but gives it no code), and a
67//! Document built from native values has no input bytes.
68//!
69//! The limits apply to every route into the model: the `*_with` readers of
70//! each format ([`crate::oml::read_oml_with`],
71//! [`crate::formats::json::read_json_with`],
72//! [`crate::formats::yaml::YamlReadOptions::with_limits`],
73//! [`crate::formats::toml::read_toml_with`],
74//! [`crate::formats::xml::read_xml_with`]) and the Document constructors
75//! [`crate::document::Doc::of_with`] and [`crate::document::Doc::from_raw_with`].
76//! A [`crate::document::Doc`] remembers the limits it was built under, so a
77//! later `add`/`set` on it enforces the same ones.
78
79use crate::error::{DocumentError, OmnistError, ParseError};
80use num_bigint::BigInt;
81
82/// The default maximum input size in bytes (spec D-23, D-24): 64 MiB. The
83/// spec names no default; this port's is chosen in the module documentation.
84pub const DEFAULT_MAX_INPUT_BYTES: u64 = 64 * 1024 * 1024;
85/// The largest maximum input size [`Limits::validate`] accepts: 1 GiB.
86pub const MAX_INPUT_BYTES_CEILING: u64 = 1 << 30;
87
88/// The reference default for the maximum nesting depth (spec section 2.4).
89pub const DEFAULT_MAX_DEPTH: u32 = 200;
90/// The largest maximum nesting depth [`Limits::validate`] accepts.
91pub const MAX_DEPTH_CEILING: u32 = 250;
92/// The reference default for the maximum node count (spec section 2.4).
93pub const DEFAULT_MAX_NODES: u32 = 1_000_000;
94/// The largest maximum node count [`Limits::validate`] accepts.
95pub const MAX_NODES_CEILING: u32 = 10_000_000;
96/// The reference default for the maximum integer digits (spec section 2.4).
97pub const DEFAULT_MAX_INT_DIGITS: u32 = 4_300;
98/// The largest maximum integer digit count [`Limits::validate`] accepts.
99pub const MAX_INT_DIGITS_CEILING: u32 = 43_000;
100
101/// The safety limits of spec section 2.4 (nesting depth, node count, integer
102/// digits) and the maximum input size of D-23, taken by the `*_with` readers
103/// and, for the first three, by [`crate::document::Doc::from_raw_with`].
104///
105/// ```
106/// use omnist::limits::Limits;
107/// use omnist::formats::json::read_json_with;
108///
109/// let limits = Limits::default().with_max_depth(3);
110/// assert!(read_json_with(r#"{"a": {"b": {"c": 1}}}"#, &limits).is_ok());
111/// let err = read_json_with(r#"{"a": {"b": {"c": {"d": 1}}}}"#, &limits).unwrap_err();
112/// assert!(err.to_string().contains("maximum depth (3)"));
113/// ```
114#[derive(Debug, Clone, Copy, PartialEq, Eq)]
115#[non_exhaustive]
116pub struct Limits {
117    /// The maximum nesting depth, counted from the Document root (the root
118    /// is depth 0). A leaf at exactly this depth is accepted, one level
119    /// deeper is refused with `document.limit.depth`.
120    ///
121    /// `0` selects [`DEFAULT_MAX_DEPTH`] (200); values above
122    /// [`MAX_DEPTH_CEILING`] (250) are rejected by [`Limits::validate`].
123    pub max_depth: u32,
124    /// The maximum number of nodes (containers) one Document may hold. A
125    /// document with exactly this many is accepted, one more is refused with
126    /// `document.limit.nodes`.
127    ///
128    /// `0` selects [`DEFAULT_MAX_NODES`] (1 000 000); values above
129    /// [`MAX_NODES_CEILING`] (10 000 000) are rejected by [`Limits::validate`].
130    pub max_nodes: u32,
131    /// The maximum number of decimal digits in an `integer` literal, sign
132    /// excluded. A literal of exactly this many digits is accepted, one more
133    /// is refused with `document.limit.int-digits`.
134    ///
135    /// `0` selects [`DEFAULT_MAX_INT_DIGITS`] (4 300); values above
136    /// [`MAX_INT_DIGITS_CEILING`] (43 000) are rejected by
137    /// [`Limits::validate`].
138    pub max_int_digits: u32,
139    /// The maximum input size in bytes (D-23), counted on the input as
140    /// received: before a leading BOM is stripped and before decoding. An
141    /// input of exactly this many bytes is accepted, one more is refused
142    /// with `document.limit.input-size` at `$`, ahead of every other
143    /// diagnostic. Only the readers use it; a Document built from native
144    /// values or a [`crate::document::RawNode`] has no input bytes.
145    ///
146    /// `0` selects [`DEFAULT_MAX_INPUT_BYTES`] (64 MiB); values above
147    /// [`MAX_INPUT_BYTES_CEILING`] (1 GiB) are rejected by
148    /// [`Limits::validate`].
149    pub max_input_bytes: u64,
150}
151
152impl Default for Limits {
153    fn default() -> Self {
154        Limits {
155            max_depth: DEFAULT_MAX_DEPTH,
156            max_nodes: DEFAULT_MAX_NODES,
157            max_int_digits: DEFAULT_MAX_INT_DIGITS,
158            max_input_bytes: DEFAULT_MAX_INPUT_BYTES,
159        }
160    }
161}
162
163impl Limits {
164    /// These limits with [`Limits::max_depth`] set to `n` (the struct is
165    /// `#[non_exhaustive]`, so a downstream crate builds it from
166    /// [`Default`] with these).
167    #[must_use]
168    pub fn with_max_depth(mut self, n: u32) -> Self {
169        self.max_depth = n;
170        self
171    }
172
173    /// These limits with [`Limits::max_nodes`] set to `n`.
174    #[must_use]
175    pub fn with_max_nodes(mut self, n: u32) -> Self {
176        self.max_nodes = n;
177        self
178    }
179
180    /// These limits with [`Limits::max_int_digits`] set to `n`.
181    #[must_use]
182    pub fn with_max_int_digits(mut self, n: u32) -> Self {
183        self.max_int_digits = n;
184        self
185    }
186
187    /// These limits with [`Limits::max_input_bytes`] set to `n`.
188    #[must_use]
189    pub fn with_max_input_bytes(mut self, n: u64) -> Self {
190        self.max_input_bytes = n;
191        self
192    }
193
194    /// The maximum input size these limits select: the configured value, or
195    /// [`DEFAULT_MAX_INPUT_BYTES`] when it is `0`.
196    pub fn effective_max_input_bytes(&self) -> u64 {
197        if self.max_input_bytes == 0 {
198            DEFAULT_MAX_INPUT_BYTES
199        } else {
200            self.max_input_bytes
201        }
202    }
203
204    /// D-23 for a caller that holds the input as bytes (the CLI): validates
205    /// the limits, then refuses an input of `len` bytes that is over the
206    /// maximum with `document.limit.input-size` at `$`, and accepts one of
207    /// exactly the maximum. `hint` says how to raise it, in the caller's own
208    /// terms. Call it before decoding the bytes.
209    pub fn check_input_len(&self, len: u64, hint: &str) -> Result<(), DocumentError> {
210        self.validate()?;
211        let max = self.effective_max_input_bytes();
212        if len > max {
213            return Err(input_size_error(max, hint));
214        }
215        Ok(())
216    }
217
218    /// The maximum depth these limits select: the configured value, or
219    /// [`DEFAULT_MAX_DEPTH`] when it is `0`.
220    pub fn effective_max_depth(&self) -> u32 {
221        if self.max_depth == 0 {
222            DEFAULT_MAX_DEPTH
223        } else {
224            self.max_depth
225        }
226    }
227
228    /// The maximum node count these limits select: the configured value, or
229    /// [`DEFAULT_MAX_NODES`] when it is `0`.
230    pub fn effective_max_nodes(&self) -> u32 {
231        if self.max_nodes == 0 {
232            DEFAULT_MAX_NODES
233        } else {
234            self.max_nodes
235        }
236    }
237
238    /// The maximum integer digit count these limits select: the configured
239    /// value, or [`DEFAULT_MAX_INT_DIGITS`] when it is `0`.
240    pub fn effective_max_int_digits(&self) -> u32 {
241        if self.max_int_digits == 0 {
242            DEFAULT_MAX_INT_DIGITS
243        } else {
244            self.max_int_digits
245        }
246    }
247
248    /// Checks the limits: each must not exceed its ceiling
249    /// ([`MAX_DEPTH_CEILING`], [`MAX_NODES_CEILING`],
250    /// [`MAX_INT_DIGITS_CEILING`], [`MAX_INPUT_BYTES_CEILING`]). Every
251    /// `*_with` entry point calls this
252    /// first and returns its error unchanged (a [`DocumentError`] with path
253    /// `$` and no taxonomy code: it is API misuse, not a document defect).
254    pub fn validate(&self) -> Result<(), DocumentError> {
255        for (name, value, ceiling, default) in [
256            (
257                "max_depth",
258                u64::from(self.max_depth),
259                u64::from(MAX_DEPTH_CEILING),
260                u64::from(DEFAULT_MAX_DEPTH),
261            ),
262            (
263                "max_nodes",
264                u64::from(self.max_nodes),
265                u64::from(MAX_NODES_CEILING),
266                u64::from(DEFAULT_MAX_NODES),
267            ),
268            (
269                "max_int_digits",
270                u64::from(self.max_int_digits),
271                u64::from(MAX_INT_DIGITS_CEILING),
272                u64::from(DEFAULT_MAX_INT_DIGITS),
273            ),
274            (
275                "max_input_bytes",
276                self.max_input_bytes,
277                MAX_INPUT_BYTES_CEILING,
278                DEFAULT_MAX_INPUT_BYTES,
279            ),
280        ] {
281            if value > ceiling {
282                return Err(DocumentError::new(
283                    "$",
284                    format!(
285                        "{name} {value} exceeds the ceiling {ceiling} (0 selects the default \
286                         {default})"
287                    ),
288                ));
289            }
290        }
291        Ok(())
292    }
293
294    /// Validated, widened form the readers carry around.
295    pub(crate) fn resolve(&self) -> Result<Resolved, DocumentError> {
296        self.validate()?;
297        Ok(self.resolve_validated())
298    }
299
300    /// [`Limits::resolve`] for limits already checked by [`Limits::validate`]
301    /// (the YAML options validate everything up front).
302    pub(crate) fn resolve_validated(&self) -> Resolved {
303        Resolved::from_limits(self)
304    }
305}
306
307/// [`Limits`] after validation and default substitution, in the `usize`
308/// the readers compare against.
309#[derive(Debug, Clone, Copy, PartialEq, Eq)]
310pub(crate) struct Resolved {
311    pub(crate) max_depth: usize,
312    pub(crate) max_nodes: usize,
313    pub(crate) max_int_digits: usize,
314    pub(crate) max_input_bytes: u64,
315}
316
317/// The D-23 refusal: `document.limit.input-size` at `$` (the size is a
318/// property of the input as a whole, E-4a). `hint` says how to raise the
319/// maximum, in the caller's own terms (a library option, a CLI flag).
320pub fn input_size_error(max_input_bytes: u64, hint: &str) -> DocumentError {
321    DocumentError::with_code(
322        "$",
323        "document.limit.input-size",
324        format!("input exceeds the maximum input size ({max_input_bytes} bytes); {hint}"),
325    )
326}
327
328/// How to raise the maximum from the library, for the refusal's message.
329const RAISE_HINT: &str = "raise Limits::max_input_bytes to read it";
330
331impl Resolved {
332    /// The defaults, as a `const` so constructors need no `Result`.
333    pub(crate) const DEFAULT: Resolved = Resolved {
334        max_depth: DEFAULT_MAX_DEPTH as usize,
335        max_nodes: DEFAULT_MAX_NODES as usize,
336        max_int_digits: DEFAULT_MAX_INT_DIGITS as usize,
337        max_input_bytes: DEFAULT_MAX_INPUT_BYTES,
338    };
339
340    fn from_limits(l: &Limits) -> Resolved {
341        Resolved {
342            max_depth: l.effective_max_depth() as usize,
343            max_nodes: l.effective_max_nodes() as usize,
344            max_int_digits: l.effective_max_int_digits() as usize,
345            max_input_bytes: l.effective_max_input_bytes(),
346        }
347    }
348
349    /// D-23: refuses an input of more than the maximum number of bytes and
350    /// accepts one of exactly that many. `len` is the length of the input as
351    /// received, taken before any BOM strip, decoding or parsing; every
352    /// reader calls this first.
353    pub(crate) fn check_input_size(&self, len: usize) -> Result<(), DocumentError> {
354        if len as u64 > self.max_input_bytes {
355            return Err(input_size_error(self.max_input_bytes, RAISE_HINT));
356        }
357        Ok(())
358    }
359
360    /// The `document.limit.depth` error. Depth is a property of the whole
361    /// document, so its path is `$` (E-11, and the `document-model/limits`
362    /// vectors).
363    pub(crate) fn depth_error(&self) -> DocumentError {
364        DocumentError::with_code(
365            "$",
366            "document.limit.depth",
367            format!("nesting exceeds the maximum depth ({})", self.max_depth),
368        )
369    }
370
371    /// The `document.limit.nodes` error, at `$` for the same reason.
372    pub(crate) fn nodes_error(&self) -> DocumentError {
373        DocumentError::with_code(
374            "$",
375            "document.limit.nodes",
376            format!(
377                "document exceeds the maximum node count ({})",
378                self.max_nodes
379            ),
380        )
381    }
382
383    /// Lifts a reader's own limit error, raised with a text position because
384    /// the scanner has no Document path (`document.limit.depth` /
385    /// `document.limit.nodes`), to the `$` Document-path form E-11 requires;
386    /// any other parse error is returned as it is.
387    pub(crate) fn lift_parse_error(&self, e: ParseError) -> OmnistError {
388        match e.code.as_str() {
389            "document.limit.depth" => self.depth_error().into(),
390            "document.limit.nodes" => self.nodes_error().into(),
391            _ => e.into(),
392        }
393    }
394
395    /// A Document constructor reports depth and node-count violations at the
396    /// path where it noticed them; a reader reports them at `$` (they are
397    /// properties of the whole input, E-11).
398    pub(crate) fn whole_document(&self, mut e: DocumentError) -> DocumentError {
399        if matches!(
400            e.code.as_deref(),
401            Some("document.limit.depth" | "document.limit.nodes")
402        ) {
403            e.path = "$".to_string();
404        }
405        e
406    }
407
408    /// The `document.limit.int-digits` error for the integer at `path`.
409    pub(crate) fn int_digits_error(&self, path: &str) -> DocumentError {
410        DocumentError::with_code(
411            path,
412            "document.limit.int-digits",
413            crate::formats::int_cap::over_cap_message("", self.max_int_digits),
414        )
415    }
416}
417
418/// Whether `i` has more than `max` decimal digits (sign excluded). Cheap for
419/// every ordinary value: the bit length bounds the digit count from above,
420/// and only an integer that could exceed `max` is rendered.
421pub(crate) fn int_exceeds(i: &BigInt, max: usize) -> bool {
422    // digits(n) <= floor(bits * log10(2)) + 1, and 30103/100000 > log10(2).
423    let upper = usize::try_from(i.bits().saturating_mul(30_103) / 100_000).unwrap_or(usize::MAX);
424    if upper.saturating_add(1) <= max {
425        return false;
426    }
427    // digits(n) >= floor((bits - 1) * log10(2)) + 1, and 30102/100000 < log10(2):
428    // a number whose lower bound is already over `max` is over it, and is not
429    // rendered (rendering a multi-megabit integer in decimal is superlinear).
430    let lower = usize::try_from(i.bits().saturating_sub(1).saturating_mul(30_102) / 100_000)
431        .unwrap_or(usize::MAX);
432    if lower.saturating_add(1) > max {
433        return true;
434    }
435    i.magnitude().to_str_radix(10).len() > max
436}
437
438/// Stands in, inside a reader's intermediate tree, for an integer literal
439/// whose digit run is over the cap, so the reader never pays the
440/// superlinear digit-string-to-integer conversion for it.
441///
442/// The placeholder is `10^max`, which has `max + 1` digits, so it is itself
443/// over the cap: the Document constructor that finally receives the tree
444/// ([`crate::document::Doc::of_with`] / [`crate::document::Doc::from_raw_with`])
445/// refuses it with `document.limit.int-digits` *at the right Document path*,
446/// which a reader still scanning text cannot compute (the path depends on
447/// how many times a label repeats in its node, known only once the node is
448/// complete). A genuine literal can never equal it: it would have had to
449/// pass the cap, and `10^max` does not.
450pub(crate) struct IntGuard {
451    max: usize,
452    placeholder: Option<BigInt>,
453}
454
455impl IntGuard {
456    pub(crate) fn new(max: usize) -> IntGuard {
457        IntGuard {
458            max,
459            placeholder: None,
460        }
461    }
462
463    /// The maximum digit count this guard enforces.
464    pub(crate) fn max(&self) -> usize {
465        self.max
466    }
467
468    /// The stand-in for an over-cap literal; remembers that one was seen.
469    pub(crate) fn placeholder(&mut self) -> BigInt {
470        let max = self.max;
471        self.placeholder
472            .get_or_insert_with(|| {
473                BigInt::from(10u8).pow(u32::try_from(max).expect("max is a validated u32"))
474            })
475            .clone()
476    }
477
478    /// Whether any over-cap literal was replaced by a placeholder.
479    pub(crate) fn seen(&self) -> bool {
480        self.placeholder.is_some()
481    }
482
483    /// For a reader whose intermediate tree can silently drop a value (a
484    /// duplicate key keeping its last value) *after* an over-cap literal was
485    /// replaced: if the finished Document built without complaint but a
486    /// placeholder was seen, the literal was dropped from the tree, and the
487    /// limit still applies to the input (D-13, MUST NOT silently accept). The
488    /// path of a dropped value is no longer known, so it is `$`.
489    pub(crate) fn finish(&self, resolved: &Resolved) -> Result<(), DocumentError> {
490        if self.seen() {
491            return Err(resolved.int_digits_error("$"));
492        }
493        Ok(())
494    }
495}
496
497#[cfg(test)]
498mod int_exceeds_tests {
499    use super::*;
500
501    /// The bit-length bounds agree with the rendered digit count at every
502    /// size around the default cap, for powers of two and their neighbours.
503    #[test]
504    fn int_exceeds_matches_the_rendered_digit_count() {
505        let max = DEFAULT_MAX_INT_DIGITS as usize;
506        for bits in 14_000u32..14_400 {
507            for delta in [-1i32, 0, 1] {
508                let n = (BigInt::from(1u8) << bits) + delta;
509                assert_eq!(
510                    int_exceeds(&n, max),
511                    n.magnitude().to_str_radix(10).len() > max,
512                    "bits {bits} delta {delta}"
513                );
514            }
515        }
516        assert!(!int_exceeds(&BigInt::from(0u8), max));
517        assert!(!int_exceeds(&BigInt::from(10u8).pow(4299), max));
518        assert!(int_exceeds(&BigInt::from(10u8).pow(4300), max));
519    }
520}