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}