1use std::cell::Cell;
5use std::io::SeekFrom;
6
7use crate::{CodecError, ReadSeek};
8
9use super::arena::DecodeArena;
10use super::budget::{DecodeBudget, DepthGuard};
11use super::depth::DecodeDepth;
12use super::error::{ResourceDimension, ResourceFailure, ResourceLimit};
13use super::policy::{
14 DecodePolicy, DECOMPRESSED_PER_EXPAND_BASE, DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE,
15};
16use super::space::{ByteRange, SpaceId};
17use super::view::{u64_from_index, View};
18
19#[derive(Clone, Copy)]
20enum LimitScope {
21 Global,
22 PerExpand,
23}
24
25const RESERVE_CLAMP: usize = 8 * 1024 * 1024;
27
28#[derive(Debug)]
30pub struct DecodeContext<'a> {
31 pub(super) arena: &'a DecodeArena,
32 depth: DecodeDepth,
33 pub(super) budget: DecodeBudget,
34 pub(super) derived_spaces: Cell<usize>,
35 pub(super) companions: Option<&'a dyn super::companion::Companions>,
36}
37
38impl<'a> DecodeContext<'a> {
39 pub fn new(arena: &'a DecodeArena, policy: &DecodePolicy, depth: DecodeDepth) -> Self {
41 Self {
42 arena,
43 depth,
44 budget: DecodeBudget::new(*policy, 0),
45 derived_spaces: Cell::new(0),
46 companions: None,
47 }
48 }
49
50 pub fn for_loaded_input(
55 arena: &'a DecodeArena,
56 policy: &DecodePolicy,
57 input_bytes: u64,
58 ) -> Result<Self, CodecError> {
59 let ctx = Self::new(arena, policy, DecodeDepth::Full);
60 ctx.budget
61 .charge_input(input_bytes, "admit loaded input length")?;
62 Ok(ctx)
63 }
64
65 pub fn read_root(
69 reader: &mut dyn ReadSeek,
70 arena: &'a DecodeArena,
71 policy: &DecodePolicy,
72 depth: DecodeDepth,
73 ) -> Result<(Self, View<'a>), CodecError> {
74 if reader.seek(SeekFrom::End(0)).is_ok() {
75 reader.rewind().map_err(CodecError::Io)?;
76 }
77 let ctx = Self::new(arena, policy, depth);
78 let mut buffer = Vec::new();
79 ctx.complete_input(reader, &mut buffer)?;
80 let bytes = arena.alloc(buffer.into_boxed_slice());
81 Ok((ctx, View::over_space(bytes, SpaceId::ROOT)))
82 }
83
84 pub fn from_root_bytes(
88 bytes: &'a [u8],
89 arena: &'a DecodeArena,
90 policy: &DecodePolicy,
91 ) -> Result<(Self, View<'a>), CodecError> {
92 Self::from_root_bytes_at(bytes, arena, policy, DecodeDepth::Full)
93 }
94
95 pub fn from_root_bytes_at(
97 bytes: &'a [u8],
98 arena: &'a DecodeArena,
99 policy: &DecodePolicy,
100 depth: DecodeDepth,
101 ) -> Result<(Self, View<'a>), CodecError> {
102 Self::from_bytes_limit(bytes, arena, policy, depth).map_err(Into::into)
103 }
104
105 pub fn from_root_bytes_limit(
107 bytes: &'a [u8],
108 arena: &'a DecodeArena,
109 policy: &DecodePolicy,
110 ) -> Result<(Self, View<'a>), ResourceLimit> {
111 Self::from_bytes_limit(bytes, arena, policy, DecodeDepth::Full)
112 }
113
114 fn from_bytes_limit(
115 bytes: &'a [u8],
116 arena: &'a DecodeArena,
117 policy: &DecodePolicy,
118 depth: DecodeDepth,
119 ) -> Result<(Self, View<'a>), ResourceLimit> {
120 let length = u64_from_index(bytes.len());
121 if length > policy.limits.max_input_bytes {
122 return Err(root_limit(
123 ResourceFailure::BudgetExceeded,
124 policy.limits.max_input_bytes,
125 length,
126 ));
127 }
128 let ctx = DecodeContext {
129 arena,
130 depth,
131 budget: DecodeBudget::new(*policy, length),
132 derived_spaces: Cell::new(0),
133 companions: None,
134 };
135 Ok((ctx, View::over_space(bytes, SpaceId::ROOT)))
136 }
137
138 pub fn policy(&self) -> &DecodePolicy {
140 self.budget.policy()
141 }
142
143 pub fn depth(&self) -> DecodeDepth {
145 self.depth
146 }
147
148 pub fn container_only(&self) -> bool {
150 self.depth == DecodeDepth::Container
151 }
152
153 fn decompression_allowance(&self) -> u64 {
154 self.budget.decompression_allowance()
155 }
156
157 #[must_use]
165 pub fn per_expand_allowance(&self) -> u64 {
166 self.budget.proportional_allowance(
167 self.budget
168 .policy()
169 .limits
170 .max_decompressed_bytes_per_expand,
171 DECOMPRESSED_PER_EXPAND_BASE,
172 DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE,
173 )
174 }
175
176 pub fn retain_bytes(&self, bytes: u64, operation: &'static str) -> Result<(), CodecError> {
182 self.budget
183 .retain_bytes(bytes, operation)
184 .map_err(Into::into)
185 }
186
187 #[must_use]
197 pub fn retained_bytes_available(&self) -> u64 {
198 self.budget.retained_bytes_available()
199 }
200
201 pub(super) fn allocate_space(&self) -> Result<SpaceId, CodecError> {
202 let used = self.derived_spaces.get();
203 self.budget.record(super::transaction::Event::Space(used));
204 let index = used.checked_add(1).ok_or_else(|| {
206 CodecError::NotImplemented("decode address spaces are exhausted".into())
207 })?;
208 self.derived_spaces.set(index);
209 Ok(SpaceId::from_index(index))
210 }
211
212 fn fuse(
214 &self,
215 reason: ResourceFailure,
216 scope: LimitScope,
217 amount: u64,
218 operation: &'static str,
219 ) -> CodecError {
220 let limit = match scope {
221 LimitScope::Global => self.decompression_allowance(),
222 LimitScope::PerExpand => self.per_expand_allowance(),
223 };
224 self.budget.refuse(
225 ResourceDimension::DecompressedBytes,
226 reason,
227 limit,
228 self.budget.decompressed_used(),
229 amount,
230 operation,
231 )
232 }
233
234 pub fn alloc_filled<T: Clone>(
240 &self,
241 count: usize,
242 value: T,
243 operation: &'static str,
244 ) -> Result<Vec<T>, CodecError> {
245 let mut values = Vec::new();
246 values.try_reserve_exact(count).map_err(|_| {
247 self.budget
248 .retained_allocation_failed(u64_from_index(count), operation)
249 })?;
250 values.resize(count, value);
251 Ok(values)
252 }
253
254 pub fn charge_entities(&self, count: u64, operation: &'static str) -> Result<(), CodecError> {
256 self.budget.charge_entities(count, operation)
257 }
258
259 pub fn admit_entities(
264 &self,
265 current: u64,
266 admitted: &mut u64,
267 operation: &'static str,
268 ) -> Result<(), CodecError> {
269 if current < *admitted {
270 *admitted = current;
271 return Ok(());
272 }
273 let additional = current - *admitted;
274 self.charge_entities(additional, operation)?;
275 *admitted = current;
276 Ok(())
277 }
278
279 pub fn enter_nested(&self, operation: &'static str) -> Result<DepthGuard<'_>, CodecError> {
281 self.enter_nested_limit(operation).map_err(Into::into)
282 }
283
284 pub fn enter_nested_limit(
286 &self,
287 operation: &'static str,
288 ) -> Result<DepthGuard<'_>, ResourceLimit> {
289 self.budget.enter_nested(operation)
290 }
291
292 pub fn resource_refusal(&self) -> Option<ResourceLimit> {
294 self.budget.fused()
295 }
296
297 pub fn begin_expand(&self, spec: ExpandSpec) -> Result<ExpandWriter<'_, 'a>, CodecError> {
302 self.budget.record(super::transaction::Event::Expand(spec));
303 self.admit_expand(spec)?;
304 let mut buffer: Vec<u8> = Vec::new();
305 let reserve = match spec {
306 ExpandSpec::Exact(size) => match usize::try_from(size) {
309 Ok(size) => size.min(RESERVE_CLAMP),
310 Err(_) => RESERVE_CLAMP,
311 },
312 ExpandSpec::Unknown => 0,
313 };
314 if reserve > 0 {
315 buffer.try_reserve(reserve).map_err(|_| {
316 self.fuse(
317 ResourceFailure::AllocationFailed,
318 LimitScope::PerExpand,
319 u64_from_index(reserve),
320 "begin_expand",
321 )
322 })?;
323 }
324 Ok(ExpandWriter {
325 ctx: self,
326 spec,
327 buffer,
328 })
329 }
330
331 pub(super) fn admit_expand(&self, spec: ExpandSpec) -> Result<(), CodecError> {
332 if let Some(limit) = self.budget.fused() {
333 return Err(CodecError::ResourceLimit(limit));
334 }
335 if let ExpandSpec::Exact(size) = spec {
336 let per_expand = self.per_expand_allowance();
337 if size > per_expand {
338 return Err(self.fuse(
339 ResourceFailure::BudgetExceeded,
340 LimitScope::PerExpand,
341 size,
342 "begin_expand",
343 ));
344 }
345 if self
346 .decompression_allowance()
347 .checked_sub(self.budget.decompressed_used())
348 .is_none_or(|remaining| size > remaining)
349 {
350 return Err(self.fuse(
351 ResourceFailure::BudgetExceeded,
352 LimitScope::Global,
353 size,
354 "begin_expand",
355 ));
356 }
357 }
358 Ok(())
359 }
360
361 pub fn concat_views(&self, inputs: &[View<'_>]) -> Result<View<'a>, CodecError> {
363 if let Some(limit) = self.budget.fused() {
364 return Err(CodecError::ResourceLimit(limit));
365 }
366 if inputs.is_empty() {
367 return Err(CodecError::Malformed(
368 "cannot concatenate an empty view list".into(),
369 ));
370 }
371 let total = inputs.iter().try_fold(0usize, |total, view| {
372 total.checked_add(view.window().len()).ok_or_else(|| {
373 self.budget.refuse(
374 ResourceDimension::RetainedBytes,
375 ResourceFailure::BudgetExceeded,
376 self.budget.policy().limits.max_retained_bytes,
377 u64_from_index(total),
378 u64_from_index(view.window().len()),
379 "concat_views",
380 )
381 })
382 })?;
383 self.retain_bytes(u64_from_index(total), "concat_views")?;
384 let mut buffer = Vec::new();
385 buffer.try_reserve_exact(total).map_err(|_| {
386 self.budget
387 .retained_allocation_failed(u64_from_index(total), "concat_views")
388 })?;
389 for view in inputs {
390 buffer.extend_from_slice(view.window());
391 }
392 let bytes = self.arena.alloc(buffer.into_boxed_slice());
393 let space = self.allocate_space()?;
394 Ok(View::over_space(bytes, space))
395 }
396
397 pub fn concat_retained(
399 &self,
400 inputs: &[Vec<u8>],
401 operation: &'static str,
402 ) -> Result<Vec<u8>, CodecError> {
403 if let Some(limit) = self.budget.fused() {
404 return Err(CodecError::ResourceLimit(limit));
405 }
406 if inputs.is_empty() {
407 return Err(CodecError::Malformed(
408 "cannot concatenate an empty buffer list".into(),
409 ));
410 }
411 let total = inputs.iter().try_fold(0_usize, |total, input| {
412 total.checked_add(input.len()).ok_or_else(|| {
413 CodecError::NotImplemented("retained concatenation exceeds usize".into())
414 })
415 })?;
416 let total_bytes = crate::decode::u64_from_index(total);
417 self.retain_bytes(total_bytes, operation)?;
418 let mut buffer = Vec::new();
419 buffer.try_reserve_exact(total).map_err(|_| {
420 self.budget
421 .retained_allocation_failed(total_bytes, operation)
422 })?;
423 for input in inputs {
424 buffer.extend_from_slice(input);
425 }
426 Ok(buffer)
427 }
428
429 pub fn register_slice<'v>(
440 &self,
441 parent: View<'v>,
442 range: ByteRange,
443 ) -> Result<View<'v>, CodecError> {
444 if let Some(limit) = self.budget.fused() {
445 return Err(CodecError::ResourceLimit(limit));
446 }
447 let start = usize::try_from(range.start).ok();
448 let end = usize::try_from(range.end).ok();
449 let child = start
450 .zip(end)
451 .and_then(|(start, end)| parent.child(start, end))
452 .ok_or_else(|| {
453 CodecError::malformed(format_args!(
454 "stored slice [{}, {}) escapes parent space {}",
455 range.start,
456 range.end,
457 parent.space().index()
458 ))
459 })?;
460
461 let space = self.allocate_space()?;
462 Ok(View::over_space(child.window(), space))
463 }
464
465 pub fn finish_session(self) -> Result<(), CodecError> {
470 if let Some(limit) = self.budget.fused() {
471 return Err(CodecError::ResourceLimit(limit));
472 }
473 Ok(())
474 }
475}
476
477fn root_limit(reason: ResourceFailure, limit: u64, used: u64) -> ResourceLimit {
478 if used <= limit {
479 return ResourceLimit {
480 dimension: ResourceDimension::InputBytes,
481 reason,
482 limit,
483 used,
484 additional: 0,
485 operation: "read_root",
486 };
487 }
488 let additional = used - limit;
489 ResourceLimit {
490 dimension: ResourceDimension::InputBytes,
491 reason,
492 limit,
493 used,
494 additional,
495 operation: "read_root",
496 }
497}
498
499#[derive(Debug, Clone, Copy, PartialEq, Eq)]
501pub enum ExpandSpec {
502 Exact(u64),
504 Unknown,
506}
507
508#[derive(Debug)]
510pub struct ExpandWriter<'ctx, 'a> {
511 ctx: &'ctx DecodeContext<'a>,
512 spec: ExpandSpec,
513 buffer: Vec<u8>,
514}
515
516impl<'a> ExpandWriter<'_, 'a> {
517 pub fn write(&mut self, data: &[u8]) -> Result<(), CodecError> {
519 let len = u64_from_index(data.len());
520 let new_written = self.written().checked_add(len).ok_or_else(|| {
521 self.ctx.fuse(
522 ResourceFailure::BudgetExceeded,
523 LimitScope::PerExpand,
524 len,
525 "expand_write",
526 )
527 })?;
528 match self.spec {
529 ExpandSpec::Exact(size) if new_written > size => {
530 return Err(CodecError::malformed(format_args!(
531 "expansion exceeded declared exact size {size}"
532 )))
533 }
534 _ => {}
535 }
536 let per_expand = self.ctx.per_expand_allowance();
537 if new_written > per_expand {
538 return Err(self.ctx.fuse(
539 ResourceFailure::BudgetExceeded,
540 LimitScope::PerExpand,
541 len,
542 "expand_write",
543 ));
544 }
545 self.ctx.budget.charge_decompressed(len, "expand_write")?;
546 self.buffer.try_reserve(data.len()).map_err(|_| {
547 self.ctx.fuse(
548 ResourceFailure::AllocationFailed,
549 LimitScope::PerExpand,
550 len,
551 "expand_write",
552 )
553 })?;
554 self.buffer.extend_from_slice(data);
555 Ok(())
556 }
557
558 pub fn finalize(self) -> Result<View<'a>, CodecError> {
560 self.check_exact()?;
561 let bytes = self.ctx.arena.alloc(self.buffer.into_boxed_slice());
562 let space = self.ctx.allocate_space()?;
563 Ok(View::over_space(bytes, space))
564 }
565
566 pub fn finalize_owned(self) -> Result<Vec<u8>, CodecError> {
569 self.check_exact()?;
570 Ok(self.buffer)
571 }
572
573 fn check_exact(&self) -> Result<(), CodecError> {
574 if let ExpandSpec::Exact(size) = self.spec {
575 if self.written() != size {
576 return Err(CodecError::malformed(format_args!(
577 "expansion produced {} of declared exact {size} bytes",
578 self.written()
579 )));
580 }
581 }
582 Ok(())
583 }
584
585 pub fn written(&self) -> u64 {
587 u64_from_index(self.buffer.len())
588 }
589}
590
591#[cfg(test)]
592mod tests {
593 use super::{ByteRange, DecodeArena, DecodeContext, DecodeDepth, DecodePolicy};
594
595 use std::io::{self, Cursor, Read, Seek, SeekFrom};
596
597 struct RewindFails(Cursor<Vec<u8>>);
598
599 impl Read for RewindFails {
600 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
601 self.0.read(buffer)
602 }
603 }
604
605 impl Seek for RewindFails {
606 fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
607 if position == SeekFrom::Start(0) {
608 Err(io::Error::new(
609 io::ErrorKind::PermissionDenied,
610 "rewind denied",
611 ))
612 } else {
613 self.0.seek(position)
614 }
615 }
616 }
617
618 #[test]
619 fn root_reader_preserves_bytes_across_fixed_chunk_boundaries() {
620 let mut bytes = vec![0x5a_u8; 8193];
621 bytes[8192] = 0x7f;
622 let mut reader = Cursor::new(bytes.clone());
623 let arena = DecodeArena::new();
624 let (_, root) = DecodeContext::read_root(
625 &mut reader,
626 &arena,
627 &DecodePolicy::default(),
628 DecodeDepth::Full,
629 )
630 .expect("root is admitted");
631 assert_eq!(root.window(), bytes.as_slice());
632 }
633
634 #[test]
635 fn root_reader_propagates_failed_rewind_after_a_successful_size_probe() {
636 let arena = DecodeArena::new();
637 let mut reader = RewindFails(Cursor::new(b"not empty".to_vec()));
638 assert!(matches!(
639 DecodeContext::read_root(
640 &mut reader,
641 &arena,
642 &DecodePolicy::default(),
643 DecodeDepth::Full
644 ),
645 Err(crate::CodecError::Io(error)) if error.kind() == io::ErrorKind::PermissionDenied
646 ));
647 }
648
649 #[test]
650 fn exhausted_space_ids_refuse_registration_without_reusing_an_id() {
651 let arena = DecodeArena::new();
652 let (ctx, root) = DecodeContext::from_root_bytes(b"x", &arena, &DecodePolicy::default())
653 .expect("test input fits the policy");
654 ctx.derived_spaces.set(usize::MAX - 1);
655 let range = ByteRange { start: 0, end: 1 };
656 let view = ctx
657 .register_slice(root, range)
658 .expect("maximum space id is allocatable");
659 assert_eq!(view.space().index(), usize::MAX);
660 let error = ctx
661 .register_slice(root, range)
662 .expect_err("space IDs are exhausted");
663 assert!(matches!(error, crate::CodecError::NotImplemented(_)));
664 assert!(ctx.register_slice(root, range).is_err());
665 }
666}