1#[cfg(feature = "schema")]
5use schemars::JsonSchema;
6use serde::{Deserialize, Serialize};
7
8const MIB: u64 = 1024 * 1024;
9const GIB: u64 = 1024 * MIB;
10
11#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
13#[cfg_attr(feature = "schema", derive(JsonSchema))]
14#[serde(rename_all = "snake_case")]
15#[serde(deny_unknown_fields)]
16pub enum DecodeMode {
17 Strict,
19 #[default]
21 Salvage,
22}
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
26#[cfg_attr(feature = "schema", derive(JsonSchema))]
27#[serde(deny_unknown_fields)]
28pub struct ResourceLimits {
29 pub max_input_bytes: u64,
31 pub max_decompressed_bytes_total: u64,
33 pub max_decompressed_bytes_per_expand: u64,
35 #[serde(default = "default_max_materialized_bytes")]
37 pub max_materialized_bytes: u64,
38 #[serde(default = "default_max_retained_bytes")]
40 pub max_retained_bytes: u64,
41 #[serde(default = "default_max_entities")]
43 pub max_entities: u64,
44 #[serde(default = "default_max_collection_items")]
46 pub max_collection_items: u64,
47 #[serde(default = "default_max_recursion_depth")]
49 pub max_recursion_depth: u64,
50 #[serde(default = "default_max_work_units")]
52 pub max_work_units: u64,
53}
54
55impl ResourceLimits {
56 pub const fn desktop() -> Self {
58 Self {
59 max_input_bytes: 4 * GIB,
60 max_decompressed_bytes_total: 8 * GIB,
61 max_decompressed_bytes_per_expand: 2 * GIB,
62 max_materialized_bytes: 4 * GIB,
63 max_retained_bytes: 4 * GIB,
64 max_entities: 64_000_000,
65 max_collection_items: 16_000_000,
66 max_recursion_depth: 256,
67 max_work_units: 4_000_000_000,
68 }
69 }
70
71 pub const fn service() -> Self {
73 Self {
74 max_input_bytes: 256 * MIB,
75 max_decompressed_bytes_total: GIB,
76 max_decompressed_bytes_per_expand: 256 * MIB,
77 max_materialized_bytes: 512 * MIB,
78 max_retained_bytes: 512 * MIB,
79 max_entities: 8_000_000,
80 max_collection_items: 1_000_000,
81 max_recursion_depth: 128,
82 max_work_units: 256_000_000,
83 }
84 }
85}
86
87impl Default for ResourceLimits {
88 fn default() -> Self {
89 Self::desktop()
90 }
91}
92
93#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
95#[cfg_attr(feature = "schema", derive(JsonSchema))]
96#[serde(deny_unknown_fields)]
97pub struct DecodePolicy {
98 pub mode: DecodeMode,
100 pub limits: ResourceLimits,
102}
103
104impl DecodePolicy {
105 pub const fn desktop() -> Self {
107 Self {
108 mode: DecodeMode::Salvage,
109 limits: ResourceLimits::desktop(),
110 }
111 }
112
113 pub const fn service() -> Self {
115 Self {
116 mode: DecodeMode::Salvage,
117 limits: ResourceLimits::service(),
118 }
119 }
120}
121
122impl Default for DecodePolicy {
123 fn default() -> Self {
124 Self::desktop()
125 }
126}
127
128#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
130#[cfg_attr(feature = "schema", derive(JsonSchema))]
131#[serde(deny_unknown_fields)]
132pub struct InspectOptions {
133 pub limits: ResourceLimits,
135}
136
137pub(super) const DECOMPRESSED_TOTAL_BASE: u64 = 16 * MIB;
138pub(super) const DECOMPRESSED_TOTAL_PER_INPUT_BYTE: u64 = 1000;
139pub(super) const DECOMPRESSED_PER_EXPAND_BASE: u64 = 16 * MIB;
140pub(super) const DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE: u64 = 256;
141pub(super) const MATERIALIZED_BASE: u64 = 16 * MIB;
142pub(super) const MATERIALIZED_PER_INPUT_BYTE: u64 = 1000;
143pub(super) const RETAINED_BASE: u64 = 16 * MIB;
144pub(super) const RETAINED_PER_INPUT_BYTE: u64 = 1000;
145
146const fn default_max_materialized_bytes() -> u64 {
147 ResourceLimits::desktop().max_materialized_bytes
148}
149
150const fn default_max_retained_bytes() -> u64 {
151 ResourceLimits::desktop().max_retained_bytes
152}
153
154const fn default_max_entities() -> u64 {
155 ResourceLimits::desktop().max_entities
156}
157
158const fn default_max_collection_items() -> u64 {
159 ResourceLimits::desktop().max_collection_items
160}
161
162const fn default_max_recursion_depth() -> u64 {
163 ResourceLimits::desktop().max_recursion_depth
164}
165
166const fn default_max_work_units() -> u64 {
167 ResourceLimits::desktop().max_work_units
168}
169
170#[cfg(test)]
171mod tests {
172 #![allow(clippy::unwrap_used)]
173
174 use super::{ResourceLimits, GIB, MIB};
175
176 #[test]
177 fn desktop_ceilings_are_explicit() {
178 let d = ResourceLimits::desktop();
179 assert_eq!(d.max_input_bytes, 4 * GIB);
180 assert_eq!(d.max_decompressed_bytes_total, 8 * GIB);
181 assert_eq!(d.max_decompressed_bytes_per_expand, 2 * GIB);
182 assert_eq!(d.max_materialized_bytes, 4 * GIB);
183 assert_eq!(d.max_retained_bytes, 4 * GIB);
184 assert_eq!(d.max_entities, 64_000_000);
185 assert_eq!(d.max_collection_items, 16_000_000);
186 assert_eq!(d.max_recursion_depth, 256);
187 assert_eq!(d.max_work_units, 4_000_000_000);
188 }
189
190 #[test]
191 fn service_ceilings_are_explicit() {
192 let s = ResourceLimits::service();
193 assert_eq!(s.max_input_bytes, 256 * MIB);
194 assert_eq!(s.max_decompressed_bytes_total, GIB);
195 assert_eq!(s.max_decompressed_bytes_per_expand, 256 * MIB);
196 assert_eq!(s.max_materialized_bytes, 512 * MIB);
197 assert_eq!(s.max_retained_bytes, 512 * MIB);
198 assert_eq!(s.max_entities, 8_000_000);
199 assert_eq!(s.max_collection_items, 1_000_000);
200 assert_eq!(s.max_recursion_depth, 128);
201 assert_eq!(s.max_work_units, 256_000_000);
202 }
203
204 #[test]
205 fn added_limits_default_when_deserializing_old_policy() {
206 let json = r#"{
207 "max_input_bytes": 1,
208 "max_decompressed_bytes_total": 2,
209 "max_decompressed_bytes_per_expand": 3
210 }"#;
211 let limits: ResourceLimits = serde_json::from_str(json).unwrap();
212 let desktop = ResourceLimits::desktop();
213 assert_eq!(
214 limits.max_materialized_bytes,
215 desktop.max_materialized_bytes
216 );
217 assert_eq!(limits.max_retained_bytes, desktop.max_retained_bytes);
218 assert_eq!(limits.max_entities, desktop.max_entities);
219 assert_eq!(limits.max_collection_items, desktop.max_collection_items);
220 assert_eq!(limits.max_recursion_depth, desktop.max_recursion_depth);
221 assert_eq!(limits.max_work_units, desktop.max_work_units);
222 }
223}