1use std::fmt;
3use structfs_core_store::{CodecErrorKind as Kind, CodecOperation, Error, Format, Value};
4
5#[derive(Debug, Clone)]
8pub struct Limits {
9 pub max_input_bytes: usize,
10 pub max_output_bytes: usize,
11 pub max_depth: usize,
12 pub max_nodes: usize,
13 pub max_collection_entries: usize,
14 pub max_string_bytes: usize,
15 pub max_blob_bytes: usize,
16 pub max_payload_bytes: usize,
17 pub max_allocation_bytes: usize,
18 pub max_work: usize,
19 pub max_diagnostic_bytes: usize,
23}
24impl Default for Limits {
25 fn default() -> Self {
26 Self {
27 max_input_bytes: 16 << 20,
28 max_output_bytes: 16 << 20,
29 max_depth: 64,
30 max_nodes: 262144,
31 max_collection_entries: 65536,
32 max_string_bytes: 4 << 20,
33 max_blob_bytes: 8 << 20,
34 max_payload_bytes: 16 << 20,
35 max_allocation_bytes: 64 << 20,
36 max_work: 128 << 20,
37 max_diagnostic_bytes: 256,
38 }
39 }
40}
41
42const DIAGNOSTIC_CAPTURE_BYTES: usize = 1024;
45
46#[derive(Debug)]
47pub(crate) struct Failure {
48 kind: Kind,
49 message: String,
50 location: String,
51}
52pub(crate) type Result<T> = std::result::Result<T, Failure>;
53
54fn bounded(value: impl fmt::Display, max: usize) -> String {
55 use fmt::Write;
56 struct Output {
57 text: String,
58 max: usize,
59 }
60 impl fmt::Write for Output {
61 fn write_str(&mut self, text: &str) -> fmt::Result {
62 let mut n = text.len().min(self.max - self.text.len());
63 while !text.is_char_boundary(n) {
64 n -= 1;
65 }
66 self.text.push_str(&text[..n]);
67 if n < text.len() {
68 Err(fmt::Error)
69 } else {
70 Ok(())
71 }
72 }
73 }
74 let mut output = Output {
75 text: String::new(),
76 max,
77 };
78 let _ = write!(output, "{value}");
79 output.text
80}
81impl fmt::Display for Failure {
82 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83 if !self.location.is_empty() {
84 write!(f, "{}: ", self.location)?;
85 }
86 if self.message.is_empty() {
87 write!(f, "{:?}", self.kind)
88 } else {
89 f.write_str(&self.message)
90 }
91 }
92}
93impl std::error::Error for Failure {}
94impl serde::ser::Error for Failure {
95 fn custom<T: fmt::Display>(message: T) -> Self {
96 Self::diagnostic(message)
97 }
98}
99impl serde::de::Error for Failure {
100 fn custom<T: fmt::Display>(message: T) -> Self {
101 Self::diagnostic(message)
102 }
103}
104impl Failure {
105 pub(crate) fn new(kind: Kind) -> Self {
106 Self {
107 kind,
108 message: String::new(),
109 location: String::new(),
110 }
111 }
112 fn diagnostic(message: impl fmt::Display) -> Self {
113 Self {
114 kind: Kind::TypeMismatch,
115 message: bounded(message, DIAGNOSTIC_CAPTURE_BYTES),
116 location: String::new(),
117 }
118 }
119 pub(crate) fn at(mut self, component: impl fmt::Display) -> Self {
120 self.location = bounded(format_args!("{component}{}", self.location), 256);
121 self
122 }
123 pub(crate) fn core(
124 mut self,
125 format: &Format,
126 operation: CodecOperation,
127 limits: &Limits,
128 ) -> Error {
129 let location = bounded(&self.location, limits.max_diagnostic_bytes / 4);
131 self.location.clear();
132 let message = if location.is_empty() {
133 bounded(format_args!("{}", self), limits.max_diagnostic_bytes)
134 } else {
135 bounded(
136 format_args!("{location}: {}", self),
137 limits.max_diagnostic_bytes,
138 )
139 };
140 Error::Codec {
141 kind: self.kind,
142 operation,
143 format: format.clone(),
144 message,
145 }
146 }
147}
148pub(crate) fn ensure(ok: bool, kind: Kind) -> Result<()> {
149 if ok {
150 Ok(())
151 } else {
152 Err(Failure::new(kind))
153 }
154}
155pub(crate) struct Budget<'a> {
156 pub limits: &'a Limits,
157 nodes: usize,
158 payload: usize,
159 allocation: usize,
160 work: usize,
161}
162fn add(counter: &mut usize, n: usize, max: usize) -> Result<()> {
163 *counter = counter
164 .checked_add(n)
165 .ok_or(Failure::new(Kind::ResourceLimit))?;
166 ensure(*counter <= max, Kind::ResourceLimit)
167}
168impl<'a> Budget<'a> {
169 pub fn new(limits: &'a Limits) -> Self {
170 Self {
171 limits,
172 nodes: 0,
173 payload: 0,
174 allocation: 0,
175 work: 0,
176 }
177 }
178 pub fn work(&mut self, n: usize) -> Result<()> {
179 add(&mut self.work, n, self.limits.max_work)
180 }
181 pub fn allocate(&mut self, n: usize) -> Result<()> {
182 add(&mut self.allocation, n, self.limits.max_allocation_bytes)
183 }
184 pub fn node(&mut self, depth: usize) -> Result<()> {
185 ensure(depth <= self.limits.max_depth.min(256), Kind::ResourceLimit)?;
186 add(&mut self.nodes, 1, self.limits.max_nodes)?;
187 self.allocate(128)?;
188 self.work(1)
189 }
190 pub fn entries(&mut self, n: usize) -> Result<()> {
191 ensure(n <= self.limits.max_collection_entries, Kind::ResourceLimit)?;
192 self.work(1)
193 }
194 pub fn payload(&mut self, n: usize, blob: bool) -> Result<()> {
195 ensure(
196 n <= if blob {
197 self.limits.max_blob_bytes
198 } else {
199 self.limits.max_string_bytes
200 },
201 Kind::ResourceLimit,
202 )?;
203 add(&mut self.payload, n, self.limits.max_payload_bytes)?;
204 self.allocate(n.saturating_mul(2))?;
205 self.work(n)
206 }
207 pub fn key_work(&mut self, bytes: usize, entries: usize) -> Result<()> {
208 let comparisons = (usize::BITS - entries.max(1).leading_zeros()) as usize;
211 self.work(bytes.saturating_mul(comparisons).saturating_mul(16))
212 }
213 pub fn tree(&mut self, v: &Value, depth: usize) -> Result<()> {
214 self.node(depth)?;
215 match v {
216 Value::String(s) => self.payload(s.len(), false)?,
217 Value::Bytes(b) => self.payload(b.len(), true)?,
218 Value::Array(a) => {
219 self.entries(a.len())?;
220 for v in a {
221 self.tree(v, depth + 1)?;
222 }
223 }
224 Value::Map(m) => {
225 self.entries(m.len())?;
226 for (k, v) in m {
227 self.payload(k.len(), false)?;
228 self.key_work(k.len(), m.len())?;
229 self.tree(v, depth + 1)?;
230 }
231 }
232 _ => {}
233 }
234 Ok(())
235 }
236}
237
238pub fn validate_value(value: &Value, limits: &Limits) -> std::result::Result<(), Error> {
240 Budget::new(limits)
241 .tree(value, 0)
242 .map_err(|e| e.core(&Format::VALUE, CodecOperation::Encode, limits))
243}