1use std::cell::Cell;
5
6use crate::{CodecError, ReadSeek};
7
8use super::arena::DecodeArena;
9use super::budget::{DecodeBudget, DepthGuard, ScopedReservation, WorkBudget};
10use super::error::{
11 ErrorContext, LimitScope, ResourceDimension, ResourceFailure, ResourceLimit, SourceLocation,
12};
13use super::policy::{
14 DecodePolicy, DECOMPRESSED_PER_EXPAND_BASE, DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE,
15};
16use super::space::{ByteRange, SpaceId};
17use super::view::View;
18
19const RESERVE_CLAMP: u64 = 8 * 1024 * 1024;
22
23#[derive(Debug)]
25pub struct DecodeContext<'a> {
26 arena: &'a DecodeArena,
27 policy: DecodePolicy,
28 container_only: bool,
29 budget: DecodeBudget,
30 next_space: Cell<u32>,
31}
32
33impl<'a> DecodeContext<'a> {
34 pub fn read_root(
38 reader: &mut dyn ReadSeek,
39 arena: &'a DecodeArena,
40 policy: &DecodePolicy,
41 ) -> Result<(Self, View<'a>), CodecError> {
42 let max = policy.limits.max_input_bytes;
43 let cap = max.saturating_add(1);
44 let mut buffer: Vec<u8> = Vec::new();
45 let mut chunk = [0u8; 8192];
46 loop {
47 let remaining = cap.saturating_sub(buffer.len() as u64);
48 if remaining == 0 {
49 break;
50 }
51 let want = usize::try_from(remaining.min(chunk.len() as u64)).unwrap_or(chunk.len());
52 let read = reader.read(&mut chunk[..want]).map_err(CodecError::Io)?;
53 if read == 0 {
54 break;
55 }
56 buffer
57 .try_reserve(read)
58 .map_err(|_| root_error(ResourceFailure::AllocationFailed, max, read as u64))?;
59 buffer.extend_from_slice(&chunk[..read]);
60 }
61 if buffer.len() as u64 > max {
62 return Err(root_error(
63 ResourceFailure::BudgetExceeded,
64 max,
65 buffer.len() as u64,
66 ));
67 }
68 let bytes = arena.alloc(buffer.into_boxed_slice());
69 Self::from_bytes(bytes, arena, policy)
70 }
71
72 pub fn from_root_bytes(
75 bytes: &'a [u8],
76 arena: &'a DecodeArena,
77 policy: &DecodePolicy,
78 ) -> Result<(Self, View<'a>), CodecError> {
79 Self::from_bytes(bytes, arena, policy)
80 }
81
82 fn from_bytes(
83 bytes: &'a [u8],
84 arena: &'a DecodeArena,
85 policy: &DecodePolicy,
86 ) -> Result<(Self, View<'a>), CodecError> {
87 let length = bytes.len() as u64;
88 if length > policy.limits.max_input_bytes {
89 return Err(root_error(
90 ResourceFailure::BudgetExceeded,
91 policy.limits.max_input_bytes,
92 length,
93 ));
94 }
95 let ctx = DecodeContext {
96 arena,
97 policy: *policy,
98 container_only: false,
99 budget: DecodeBudget::new(*policy, length),
100 next_space: Cell::new(1),
101 };
102 Ok((ctx, View::over_space(bytes, SpaceId::ROOT)))
103 }
104
105 pub fn policy(&self) -> &DecodePolicy {
107 &self.policy
108 }
109
110 pub fn container_only(&self) -> bool {
112 self.container_only
113 }
114
115 pub fn set_container_only(&mut self, value: bool) {
117 self.container_only = value;
118 }
119
120 fn decompression_allowance(&self) -> u64 {
121 self.budget.decompression_allowance()
122 }
123
124 fn per_expand_allowance(&self) -> u64 {
125 let proportional = DECOMPRESSED_PER_EXPAND_BASE.saturating_add(
126 DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE.saturating_mul(self.budget.input_bytes()),
127 );
128 self.policy
129 .limits
130 .max_decompressed_bytes_per_expand
131 .min(proportional)
132 }
133
134 fn allocate_space(&self) -> SpaceId {
135 let index = self.next_space.get();
136 self.next_space.set(index.saturating_add(1));
137 SpaceId::from_index(index)
138 }
139
140 fn charge_decompressed(
141 &self,
142 scope: LimitScope,
143 amount: u64,
144 operation: &'static str,
145 location: Option<SourceLocation>,
146 ) -> Result<(), CodecError> {
147 debug_assert_eq!(scope, LimitScope::Global);
148 self.budget.charge_decompressed(amount, operation, location)
149 }
150
151 fn fuse(
153 &self,
154 reason: ResourceFailure,
155 scope: LimitScope,
156 amount: u64,
157 operation: &'static str,
158 location: Option<SourceLocation>,
159 ) -> CodecError {
160 let limit = match scope {
161 LimitScope::Global => self.decompression_allowance(),
162 LimitScope::PerExpand => self.per_expand_allowance(),
163 };
164 self.budget.refuse(
165 ResourceDimension::DecompressedBytes,
166 reason,
167 scope,
168 limit,
169 self.budget.decompressed_used(),
170 amount,
171 operation,
172 location,
173 )
174 }
175
176 pub fn reserve_scoped(
178 &self,
179 bytes: u64,
180 operation: &'static str,
181 location: Option<SourceLocation>,
182 ) -> Result<ScopedReservation<'_>, CodecError> {
183 self.budget.reserve_scoped(bytes, operation, location)
184 }
185
186 pub fn charge_retained(
188 &self,
189 bytes: u64,
190 operation: &'static str,
191 location: Option<SourceLocation>,
192 ) -> Result<(), CodecError> {
193 self.budget.charge_retained(bytes, operation, location)
194 }
195
196 pub fn copy_retained(
198 &self,
199 bytes: &[u8],
200 operation: &'static str,
201 location: Option<SourceLocation>,
202 ) -> Result<Vec<u8>, CodecError> {
203 self.charge_retained(bytes.len() as u64, operation, location)?;
204 let mut copy = Vec::new();
205 copy.try_reserve_exact(bytes.len()).map_err(|_| {
206 self.fuse(
207 ResourceFailure::AllocationFailed,
208 LimitScope::Global,
209 bytes.len() as u64,
210 operation,
211 location,
212 )
213 })?;
214 copy.extend_from_slice(bytes);
215 Ok(copy)
216 }
217
218 pub fn charge_entities(&self, count: u64, operation: &'static str) -> Result<(), CodecError> {
220 self.budget.charge_entities(count, operation)
221 }
222
223 pub fn charge_collection_items(
225 &self,
226 count: u64,
227 operation: &'static str,
228 ) -> Result<(), CodecError> {
229 self.budget.charge_collection_items(count, operation)
230 }
231
232 pub fn enter_nested(
234 &self,
235 operation: &'static str,
236 location: Option<SourceLocation>,
237 ) -> Result<DepthGuard<'_>, CodecError> {
238 self.budget.enter_nested(operation, location)
239 }
240
241 pub fn charge_work(&self, units: u64, operation: &'static str) -> Result<(), CodecError> {
243 self.budget.charge_work(units, operation)
244 }
245
246 pub fn work_budget(&self, local_limit: u64) -> WorkBudget<'_> {
248 WorkBudget::for_session(local_limit, &self.budget)
249 }
250
251 pub fn begin_expand(
256 &self,
257 source: View<'_>,
258 spec: ExpandSpec,
259 ) -> Result<ExpandWriter<'_, 'a>, CodecError> {
260 if let Some(limit) = self.budget.fused() {
261 return Err(CodecError::ResourceLimit(limit));
262 }
263 if let ExpandSpec::Exact(size) = spec {
264 let per_expand = self.per_expand_allowance();
265 if size > per_expand {
266 return Err(self.fuse(
267 ResourceFailure::BudgetExceeded,
268 LimitScope::PerExpand,
269 size,
270 "begin_expand",
271 Some(source.location()),
272 ));
273 }
274 if size
275 > self
276 .decompression_allowance()
277 .saturating_sub(self.budget.decompressed_used())
278 {
279 return Err(self.fuse(
280 ResourceFailure::BudgetExceeded,
281 LimitScope::Global,
282 size,
283 "begin_expand",
284 Some(source.location()),
285 ));
286 }
287 }
288 let mut buffer: Vec<u8> = Vec::new();
289 let reserve = match spec {
290 ExpandSpec::Exact(size) => size.min(RESERVE_CLAMP),
291 ExpandSpec::Unknown => 0,
292 };
293 if reserve > 0 {
294 let reserve = usize::try_from(reserve).unwrap_or(usize::MAX);
295 buffer.try_reserve(reserve).map_err(|_| {
296 self.fuse(
297 ResourceFailure::AllocationFailed,
298 LimitScope::PerExpand,
299 reserve as u64,
300 "begin_expand",
301 Some(source.location()),
302 )
303 })?;
304 }
305 Ok(ExpandWriter {
306 ctx: self,
307 spec,
308 location: source.location(),
309 buffer,
310 written: 0,
311 })
312 }
313
314 pub fn concat_views(&self, inputs: &[View<'_>]) -> Result<View<'a>, CodecError> {
316 if let Some(limit) = self.budget.fused() {
317 return Err(CodecError::ResourceLimit(limit));
318 }
319 let location = inputs.first().copied().map(View::location);
320 let total = inputs.iter().try_fold(0usize, |total, view| {
321 total.checked_add(view.window().len()).ok_or_else(|| {
322 self.budget.refuse(
323 ResourceDimension::RetainedBytes,
324 ResourceFailure::BudgetExceeded,
325 LimitScope::Global,
326 self.policy.limits.max_retained_bytes,
327 total as u64,
328 view.window().len() as u64,
329 "concat_views",
330 location,
331 )
332 })
333 })?;
334 let reservation = self.reserve_scoped(total as u64, "concat_views", location)?;
335 let mut buffer = Vec::new();
336 buffer.try_reserve_exact(total).map_err(|_| {
337 self.budget.refuse(
338 ResourceDimension::MaterializedBytes,
339 ResourceFailure::AllocationFailed,
340 LimitScope::Global,
341 self.policy.limits.max_materialized_bytes,
342 0,
343 total as u64,
344 "concat_views",
345 location,
346 )
347 })?;
348 for view in inputs {
349 buffer.extend_from_slice(view.window());
350 }
351 let bytes = self.arena.alloc(buffer.into_boxed_slice());
352 reservation.commit()?;
353 let space = self.allocate_space();
354 Ok(View::over_space(bytes, space))
355 }
356
357 pub fn register_slice<'v>(
368 &self,
369 parent: View<'v>,
370 range: ByteRange,
371 ) -> Result<View<'v>, CodecError> {
372 if let Some(limit) = self.budget.fused() {
373 return Err(CodecError::ResourceLimit(limit));
374 }
375 let start = usize::try_from(range.start).ok();
376 let end = usize::try_from(range.end).ok();
377 let child = start
378 .zip(end)
379 .and_then(|(start, end)| parent.child(start, end))
380 .ok_or_else(|| {
381 CodecError::Malformed(format!(
382 "stored slice [{}, {}) escapes parent space {}",
383 range.start,
384 range.end,
385 parent.space().index()
386 ))
387 })?;
388 let space = self.allocate_space();
389 Ok(View::over_space(child.window(), space))
390 }
391
392 pub fn finish_session(self) -> Result<(), CodecError> {
397 if let Some(limit) = self.budget.fused() {
398 return Err(CodecError::ResourceLimit(limit));
399 }
400 Ok(())
401 }
402}
403
404fn root_error(reason: ResourceFailure, limit: u64, used: u64) -> CodecError {
406 CodecError::ResourceLimit(ResourceLimit {
407 dimension: ResourceDimension::InputBytes,
408 reason,
409 scope: LimitScope::Global,
410 limit,
411 used,
412 additional: used.saturating_sub(limit),
413 context: ErrorContext {
414 operation: "read_root",
415 location: None,
416 },
417 })
418}
419
420#[derive(Debug, Clone, Copy, PartialEq, Eq)]
422pub enum ExpandSpec {
423 Exact(u64),
425 Unknown,
427}
428
429#[derive(Debug)]
431pub struct ExpandWriter<'ctx, 'a> {
432 ctx: &'ctx DecodeContext<'a>,
433 spec: ExpandSpec,
434 location: SourceLocation,
435 buffer: Vec<u8>,
436 written: u64,
437}
438
439impl<'a> ExpandWriter<'_, 'a> {
440 pub fn write(&mut self, data: &[u8]) -> Result<(), CodecError> {
442 let len = data.len() as u64;
443 let new_written = self.written.saturating_add(len);
444 match self.spec {
445 ExpandSpec::Exact(size) if new_written > size => {
446 return Err(CodecError::Malformed(format!(
447 "expansion exceeded declared exact size {size}"
448 )))
449 }
450 _ => {}
451 }
452 let per_expand = self.ctx.per_expand_allowance();
453 if new_written > per_expand {
454 return Err(self.ctx.fuse(
455 ResourceFailure::BudgetExceeded,
456 LimitScope::PerExpand,
457 len,
458 "expand_write",
459 Some(self.location),
460 ));
461 }
462 self.ctx.charge_decompressed(
463 LimitScope::Global,
464 len,
465 "expand_write",
466 Some(self.location),
467 )?;
468 self.buffer.try_reserve(data.len()).map_err(|_| {
469 self.ctx.fuse(
470 ResourceFailure::AllocationFailed,
471 LimitScope::PerExpand,
472 len,
473 "expand_write",
474 Some(self.location),
475 )
476 })?;
477 self.buffer.extend_from_slice(data);
478 self.written = new_written;
479 Ok(())
480 }
481
482 pub fn finalize(self) -> Result<View<'a>, CodecError> {
484 if let ExpandSpec::Exact(size) = self.spec {
485 if self.written != size {
486 return Err(CodecError::Malformed(format!(
487 "expansion produced {} of declared exact {size} bytes",
488 self.written
489 )));
490 }
491 }
492 let bytes = self.ctx.arena.alloc(self.buffer.into_boxed_slice());
493 let space = self.ctx.allocate_space();
494 Ok(View::over_space(bytes, space))
495 }
496
497 pub fn written(&self) -> u64 {
499 self.written
500 }
501}