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