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