seam_core/limits.rs
1//! Bounds on hostile input, enforced in the core so every binding inherits
2//! them instead of each deriving its own defaults.
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub struct Limits {
6 /// The validator recurses, so this is what protects the stack.
7 pub max_depth: usize,
8 pub max_items: usize,
9 pub max_string_bytes: usize,
10 pub max_object_keys: usize,
11}
12
13impl Limits {
14 /// The highest `max_depth` the engine will honour, whatever it is handed.
15 ///
16 /// Both the JSON parser and the validator recurse, so depth is what stands
17 /// between a hostile document and the stack. A stack overflow is not a
18 /// panic that a binding can catch and turn into an error: it kills the
19 /// process, taking every other request in flight with it. Measured, that
20 /// starts happening somewhere between one and five thousand levels on the
21 /// object path, so a caller raising `max_depth` freely could disable the
22 /// one bound that is not recoverable.
23 ///
24 /// 256 is the value `PERMISSIVE` already used for trusted input, and it is
25 /// far past any document a person meant to send. A limit that can be
26 /// turned off until the process dies is not a limit.
27 pub const MAX_DEPTH: usize = 256;
28
29 pub const DEFAULT: Self = Self {
30 max_depth: 64,
31 max_items: 10_000,
32 max_string_bytes: 1 << 20,
33 max_object_keys: 1_000,
34 };
35
36 /// For trusted input. Depth stays bounded because the stack is.
37 pub const PERMISSIVE: Self = Self {
38 max_depth: 256,
39 max_items: usize::MAX,
40 max_string_bytes: usize::MAX,
41 max_object_keys: usize::MAX,
42 };
43}
44
45impl Limits {
46 /// The limits actually used, with `max_depth` held under [`Self::MAX_DEPTH`].
47 ///
48 /// Applied by the engine at both entry points rather than by each binding,
49 /// so no binding can hand the recursion a number the stack cannot take.
50 #[must_use]
51 pub const fn clamped(self) -> Self {
52 Self {
53 max_depth: if self.max_depth > Self::MAX_DEPTH {
54 Self::MAX_DEPTH
55 } else {
56 self.max_depth
57 },
58 ..self
59 }
60 }
61}
62
63// Checked when the crate compiles, not when a test runs: a preset above the
64// cap would be a contradiction in the engine's own configuration, and there is
65// no reason to let it build.
66const _: () = assert!(Limits::DEFAULT.max_depth <= Limits::MAX_DEPTH);
67const _: () = assert!(Limits::PERMISSIVE.max_depth <= Limits::MAX_DEPTH);
68
69impl Default for Limits {
70 fn default() -> Self {
71 Self::DEFAULT
72 }
73}
74
75#[cfg(test)]
76mod tests {
77 use super::*;
78
79 #[test]
80 fn max_depth_is_capped_however_it_arrives() {
81 let reckless = Limits { max_depth: 1_000_000, ..Limits::DEFAULT };
82 assert_eq!(reckless.clamped().max_depth, Limits::MAX_DEPTH);
83
84 // Everything else is the caller's business: those bound memory, and
85 // exceeding them is reported rather than fatal.
86 assert_eq!(reckless.clamped().max_items, Limits::DEFAULT.max_items);
87
88 // A limit below the cap is left alone.
89 let tight = Limits { max_depth: 4, ..Limits::DEFAULT };
90 assert_eq!(tight.clamped().max_depth, 4);
91 }
92
93 #[test]
94 fn the_default_is_the_conservative_one() {
95 assert_eq!(Limits::default(), Limits::DEFAULT);
96 assert_eq!(
97 Limits::DEFAULT.max_items.min(Limits::PERMISSIVE.max_items),
98 Limits::DEFAULT.max_items
99 );
100 }
101}