1use std::cell::Cell;
5
6use crate::codec::{CodecError, DecodeResult, ReadSeek};
7use crate::report::StrictConsequence;
8
9use super::arena::DecodeArena;
10use super::error::{
11 ErrorContext, LimitScope, ResourceDimension, ResourceFailure, ResourceLimit, SourceLocation,
12};
13use super::policy::{
14 DecodeMode, DecodePolicy, DECOMPRESSED_PER_EXPAND_BASE, DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE,
15 DECOMPRESSED_TOTAL_BASE, DECOMPRESSED_TOTAL_PER_INPUT_BYTE,
16};
17use super::space::{ByteRange, SpaceId};
18use super::view::View;
19
20const RESERVE_CLAMP: u64 = 8 * 1024 * 1024;
23
24#[derive(Debug)]
26pub struct DecodeContext<'a> {
27 arena: &'a DecodeArena,
28 policy: DecodePolicy,
29 container_only: bool,
30 input_bytes: u64,
31 decompressed_bytes: Cell<u64>,
32 next_space: Cell<u32>,
33 fuse: Cell<Option<ResourceLimit>>,
34}
35
36impl<'a> DecodeContext<'a> {
37 pub fn read_root(
41 reader: &mut dyn ReadSeek,
42 arena: &'a DecodeArena,
43 policy: &DecodePolicy,
44 ) -> Result<(Self, View<'a>), CodecError> {
45 let max = policy.limits.max_input_bytes;
46 let cap = max.saturating_add(1);
47 let mut buffer: Vec<u8> = Vec::new();
48 let mut chunk = [0u8; 8192];
49 loop {
50 let remaining = cap.saturating_sub(buffer.len() as u64);
51 if remaining == 0 {
52 break;
53 }
54 let want = usize::try_from(remaining.min(chunk.len() as u64)).unwrap_or(chunk.len());
55 let read = reader.read(&mut chunk[..want]).map_err(CodecError::Io)?;
56 if read == 0 {
57 break;
58 }
59 buffer
60 .try_reserve(read)
61 .map_err(|_| root_error(ResourceFailure::AllocationFailed, max, read as u64))?;
62 buffer.extend_from_slice(&chunk[..read]);
63 }
64 if buffer.len() as u64 > max {
65 return Err(root_error(
66 ResourceFailure::BudgetExceeded,
67 max,
68 buffer.len() as u64,
69 ));
70 }
71 let bytes = arena.alloc(buffer.into_boxed_slice());
72 Self::from_bytes(bytes, arena, policy)
73 }
74
75 pub fn from_root_bytes(
78 bytes: &'a [u8],
79 arena: &'a DecodeArena,
80 policy: &DecodePolicy,
81 ) -> Result<(Self, View<'a>), CodecError> {
82 Self::from_bytes(bytes, arena, policy)
83 }
84
85 fn from_bytes(
86 bytes: &'a [u8],
87 arena: &'a DecodeArena,
88 policy: &DecodePolicy,
89 ) -> Result<(Self, View<'a>), CodecError> {
90 let length = bytes.len() as u64;
91 if length > policy.limits.max_input_bytes {
92 return Err(root_error(
93 ResourceFailure::BudgetExceeded,
94 policy.limits.max_input_bytes,
95 length,
96 ));
97 }
98 let ctx = DecodeContext {
99 arena,
100 policy: *policy,
101 container_only: false,
102 input_bytes: length,
103 decompressed_bytes: Cell::new(0),
104 next_space: Cell::new(1),
105 fuse: Cell::new(None),
106 };
107 Ok((ctx, View::over_space(bytes, SpaceId::ROOT)))
108 }
109
110 pub fn policy(&self) -> &DecodePolicy {
112 &self.policy
113 }
114
115 pub fn container_only(&self) -> bool {
117 self.container_only
118 }
119
120 pub fn set_container_only(&mut self, value: bool) {
122 self.container_only = value;
123 }
124
125 fn decompression_allowance(&self) -> u64 {
126 let proportional = DECOMPRESSED_TOTAL_BASE
127 .saturating_add(DECOMPRESSED_TOTAL_PER_INPUT_BYTE.saturating_mul(self.input_bytes));
128 self.policy
129 .limits
130 .max_decompressed_bytes_total
131 .min(proportional)
132 }
133
134 fn per_expand_allowance(&self) -> u64 {
135 let proportional = DECOMPRESSED_PER_EXPAND_BASE.saturating_add(
136 DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE.saturating_mul(self.input_bytes),
137 );
138 self.policy
139 .limits
140 .max_decompressed_bytes_per_expand
141 .min(proportional)
142 }
143
144 fn allocate_space(&self) -> SpaceId {
145 let index = self.next_space.get();
146 self.next_space.set(index.saturating_add(1));
147 SpaceId::from_index(index)
148 }
149
150 fn charge_decompressed(
151 &self,
152 scope: LimitScope,
153 amount: u64,
154 operation: &'static str,
155 location: Option<SourceLocation>,
156 ) -> Result<(), CodecError> {
157 if let Some(limit) = self.fuse.get() {
158 return Err(CodecError::ResourceLimit(limit));
159 }
160 let allowance = self.decompression_allowance();
161 let used = self.decompressed_bytes.get();
162 if amount > allowance.saturating_sub(used) {
163 return Err(self.fuse(
164 ResourceFailure::BudgetExceeded,
165 scope,
166 amount,
167 operation,
168 location,
169 ));
170 }
171 self.decompressed_bytes.set(used.saturating_add(amount));
172 Ok(())
173 }
174
175 fn fuse(
177 &self,
178 reason: ResourceFailure,
179 scope: LimitScope,
180 amount: u64,
181 operation: &'static str,
182 location: Option<SourceLocation>,
183 ) -> CodecError {
184 let limit = match scope {
185 LimitScope::Global => self.decompression_allowance(),
186 LimitScope::PerExpand => self.per_expand_allowance(),
187 };
188 let used = self.decompressed_bytes.get();
189 let resource = ResourceLimit {
190 dimension: ResourceDimension::DecompressedBytes,
191 reason,
192 scope,
193 limit,
194 used,
195 additional: amount,
196 context: ErrorContext {
197 operation,
198 location,
199 },
200 };
201 self.fuse.set(Some(resource));
202 CodecError::ResourceLimit(resource)
203 }
204
205 pub fn begin_expand(
210 &self,
211 source: View<'_>,
212 spec: ExpandSpec,
213 ) -> Result<ExpandWriter<'_, 'a>, CodecError> {
214 if let Some(limit) = self.fuse.get() {
215 return Err(CodecError::ResourceLimit(limit));
216 }
217 let mut buffer: Vec<u8> = Vec::new();
218 let reserve = match spec {
219 ExpandSpec::Exact(size) => size.min(RESERVE_CLAMP),
220 ExpandSpec::Unknown => 0,
221 };
222 if reserve > 0 {
223 let reserve = usize::try_from(reserve).unwrap_or(usize::MAX);
224 buffer.try_reserve(reserve).map_err(|_| {
225 self.fuse(
226 ResourceFailure::AllocationFailed,
227 LimitScope::PerExpand,
228 reserve as u64,
229 "begin_expand",
230 Some(source.location()),
231 )
232 })?;
233 }
234 Ok(ExpandWriter {
235 ctx: self,
236 spec,
237 location: source.location(),
238 buffer,
239 written: 0,
240 })
241 }
242
243 pub fn concat_views(&self, inputs: &[View<'_>]) -> Result<View<'a>, CodecError> {
245 if let Some(limit) = self.fuse.get() {
246 return Err(CodecError::ResourceLimit(limit));
247 }
248 let total = inputs.iter().try_fold(0usize, |total, view| {
249 total.checked_add(view.window().len()).ok_or_else(|| {
250 CodecError::Io(std::io::Error::other("concatenated view is too large"))
251 })
252 })?;
253 let mut buffer = Vec::new();
254 buffer.try_reserve_exact(total).map_err(|_| {
255 CodecError::Io(std::io::Error::other("concatenated view allocation failed"))
256 })?;
257 for view in inputs {
258 buffer.extend_from_slice(view.window());
259 }
260 let bytes = self.arena.alloc(buffer.into_boxed_slice());
261 let space = self.allocate_space();
262 Ok(View::over_space(bytes, space))
263 }
264
265 pub fn register_slice<'v>(
276 &self,
277 parent: View<'v>,
278 range: ByteRange,
279 ) -> Result<View<'v>, CodecError> {
280 if let Some(limit) = self.fuse.get() {
281 return Err(CodecError::ResourceLimit(limit));
282 }
283 let start = usize::try_from(range.start).ok();
284 let end = usize::try_from(range.end).ok();
285 let child = start
286 .zip(end)
287 .and_then(|(start, end)| parent.child(start, end))
288 .ok_or_else(|| {
289 CodecError::Malformed(format!(
290 "stored slice [{}, {}) escapes parent space {}",
291 range.start,
292 range.end,
293 parent.space().index()
294 ))
295 })?;
296 let space = self.allocate_space();
297 Ok(View::over_space(child.window(), space))
298 }
299
300 pub(crate) fn finish_inspection<T>(
305 self,
306 result: Result<T, CodecError>,
307 ) -> Result<T, CodecError> {
308 if let Some(limit) = self.fuse.get() {
309 return Err(CodecError::ResourceLimit(limit));
310 }
311 result
312 }
313
314 pub fn finish(
317 self,
318 result: Result<DecodeResult, CodecError>,
319 ) -> Result<DecodeResult, CodecError> {
320 if let Some(limit) = self.fuse.get() {
321 return Err(CodecError::ResourceLimit(limit));
322 }
323 let result = result?;
324 if self.policy.mode == DecodeMode::Strict && !result.report.container_only {
325 if let Some(loss) = result
326 .report
327 .losses
328 .iter()
329 .find(|loss| loss.code.strict_consequence() == StrictConsequence::Reject)
330 {
331 return Err(CodecError::Malformed(format!(
332 "strict mode rejects {}: {}",
333 loss.code, loss.message
334 )));
335 }
336 }
337 Ok(result)
338 }
339}
340
341fn root_error(reason: ResourceFailure, limit: u64, used: u64) -> CodecError {
343 CodecError::ResourceLimit(ResourceLimit {
344 dimension: ResourceDimension::InputBytes,
345 reason,
346 scope: LimitScope::Global,
347 limit,
348 used,
349 additional: used.saturating_sub(limit),
350 context: ErrorContext {
351 operation: "read_root",
352 location: None,
353 },
354 })
355}
356
357#[derive(Debug, Clone, Copy, PartialEq, Eq)]
359pub enum ExpandSpec {
360 Exact(u64),
362 Unknown,
364}
365
366#[derive(Debug)]
368pub struct ExpandWriter<'ctx, 'a> {
369 ctx: &'ctx DecodeContext<'a>,
370 spec: ExpandSpec,
371 location: SourceLocation,
372 buffer: Vec<u8>,
373 written: u64,
374}
375
376impl<'a> ExpandWriter<'_, 'a> {
377 pub fn write(&mut self, data: &[u8]) -> Result<(), CodecError> {
379 let len = data.len() as u64;
380 let new_written = self.written.saturating_add(len);
381 match self.spec {
382 ExpandSpec::Exact(size) if new_written > size => {
383 return Err(CodecError::Malformed(format!(
384 "expansion exceeded declared exact size {size}"
385 )))
386 }
387 _ => {}
388 }
389 let per_expand = self.ctx.per_expand_allowance();
390 if new_written > per_expand {
391 return Err(self.ctx.fuse(
392 ResourceFailure::BudgetExceeded,
393 LimitScope::PerExpand,
394 len,
395 "expand_write",
396 Some(self.location),
397 ));
398 }
399 self.ctx.charge_decompressed(
400 LimitScope::Global,
401 len,
402 "expand_write",
403 Some(self.location),
404 )?;
405 self.buffer.try_reserve(data.len()).map_err(|_| {
406 self.ctx.fuse(
407 ResourceFailure::AllocationFailed,
408 LimitScope::PerExpand,
409 len,
410 "expand_write",
411 Some(self.location),
412 )
413 })?;
414 self.buffer.extend_from_slice(data);
415 self.written = new_written;
416 Ok(())
417 }
418
419 pub fn finalize(self) -> Result<View<'a>, CodecError> {
421 if let ExpandSpec::Exact(size) = self.spec {
422 if self.written != size {
423 return Err(CodecError::Malformed(format!(
424 "expansion produced {} of declared exact {size} bytes",
425 self.written
426 )));
427 }
428 }
429 let bytes = self.ctx.arena.alloc(self.buffer.into_boxed_slice());
430 let space = self.ctx.allocate_space();
431 Ok(View::over_space(bytes, space))
432 }
433
434 pub fn written(&self) -> u64 {
436 self.written
437 }
438}