1use std::cell::{Cell, RefCell};
5use std::io::SeekFrom;
6
7use crate::{CodecError, ReadSeek};
8
9use super::arena::DecodeArena;
10use super::budget::{alloc_filled, DecodeBudget, DepthGuard, ScopedReservation, WorkBudget};
11use super::error::{
12 ErrorContext, LimitScope, ResourceDimension, ResourceFailure, ResourceLimit, SourceLocation,
13};
14use super::policy::{
15 DecodePolicy, DECOMPRESSED_PER_EXPAND_BASE, DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE,
16};
17use super::space::{
18 resolve_address, ByteRange, ResolvedAddress, SpaceDerivation, SpaceDescriptor, SpaceId,
19};
20use super::view::View;
21
22const RESERVE_CLAMP: u64 = 8 * 1024 * 1024;
24
25#[derive(Debug)]
27pub struct DecodeContext<'a> {
28 arena: &'a DecodeArena,
29 policy: DecodePolicy,
30 container_only: bool,
31 budget: DecodeBudget,
32 next_space: Cell<u32>,
33 spaces: RefCell<Vec<SpaceDescriptor>>,
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 buffer = if let Ok(size) = reader
48 .seek(SeekFrom::End(0))
49 .and_then(|size| reader.rewind().map(|()| size))
50 {
51 let reserve = size.min(cap);
52 let reserve = usize::try_from(reserve)
53 .map_err(|_| root_error(ResourceFailure::AllocationFailed, max, reserve))?;
54 let mut buffer = Vec::new();
55 buffer
56 .try_reserve(reserve)
57 .map_err(|_| root_error(ResourceFailure::AllocationFailed, max, reserve as u64))?;
58 let mut chunk = vec![0u8; 256 * 1024].into_boxed_slice();
59 while (buffer.len() as u64) < cap {
60 let remaining = cap.saturating_sub(buffer.len() as u64);
61 let want =
62 usize::try_from(remaining.min(chunk.len() as u64)).unwrap_or(chunk.len());
63 let read = reader.read(&mut chunk[..want]).map_err(CodecError::Io)?;
64 if read == 0 {
65 break;
66 }
67 buffer.extend_from_slice(&chunk[..read]);
68 }
69 buffer
70 } else {
71 let mut buffer = Vec::new();
72 let mut chunk = [0u8; 8192];
73 loop {
74 let remaining = cap.saturating_sub(buffer.len() as u64);
75 if remaining == 0 {
76 break;
77 }
78 let want =
79 usize::try_from(remaining.min(chunk.len() as u64)).unwrap_or(chunk.len());
80 let read = reader.read(&mut chunk[..want]).map_err(CodecError::Io)?;
81 if read == 0 {
82 break;
83 }
84 buffer
85 .try_reserve(read)
86 .map_err(|_| root_error(ResourceFailure::AllocationFailed, max, read as u64))?;
87 buffer.extend_from_slice(&chunk[..read]);
88 }
89 buffer
90 };
91 if buffer.len() as u64 > max {
92 return Err(root_error(
93 ResourceFailure::BudgetExceeded,
94 max,
95 buffer.len() as u64,
96 ));
97 }
98 let bytes = arena.alloc(buffer.into_boxed_slice());
99 Self::from_bytes(bytes, arena, policy)
100 }
101
102 pub fn from_root_bytes(
105 bytes: &'a [u8],
106 arena: &'a DecodeArena,
107 policy: &DecodePolicy,
108 ) -> Result<(Self, View<'a>), CodecError> {
109 Self::from_bytes(bytes, arena, policy)
110 }
111
112 fn from_bytes(
113 bytes: &'a [u8],
114 arena: &'a DecodeArena,
115 policy: &DecodePolicy,
116 ) -> Result<(Self, View<'a>), CodecError> {
117 let length = bytes.len() as u64;
118 if length > policy.limits.max_input_bytes {
119 return Err(root_error(
120 ResourceFailure::BudgetExceeded,
121 policy.limits.max_input_bytes,
122 length,
123 ));
124 }
125 let ctx = DecodeContext {
126 arena,
127 policy: *policy,
128 container_only: false,
129 budget: DecodeBudget::new(*policy, length),
130 next_space: Cell::new(1),
131 spaces: RefCell::new(vec![SpaceDescriptor {
132 id: SpaceId::ROOT,
133 label: "root".into(),
134 derivation: SpaceDerivation::Root,
135 }]),
136 };
137 Ok((ctx, View::over_space(bytes, SpaceId::ROOT)))
138 }
139
140 pub fn policy(&self) -> &DecodePolicy {
142 &self.policy
143 }
144
145 pub fn container_only(&self) -> bool {
147 self.container_only
148 }
149
150 pub fn set_container_only(&mut self, value: bool) {
152 self.container_only = value;
153 }
154
155 fn decompression_allowance(&self) -> u64 {
156 self.budget.decompression_allowance()
157 }
158
159 fn per_expand_allowance(&self) -> u64 {
160 let proportional = DECOMPRESSED_PER_EXPAND_BASE.saturating_add(
161 DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE.saturating_mul(self.budget.input_bytes()),
162 );
163 self.policy
164 .limits
165 .max_decompressed_bytes_per_expand
166 .min(proportional)
167 }
168
169 fn allocate_space(&self, label: String, derivation: SpaceDerivation) -> SpaceId {
170 let index = self.next_space.get();
171 self.next_space.set(index.saturating_add(1));
172 let id = SpaceId::from_index(index);
173 self.spaces.borrow_mut().push(SpaceDescriptor {
174 id,
175 label,
176 derivation,
177 });
178 id
179 }
180
181 pub fn space_descriptors(&self) -> Vec<SpaceDescriptor> {
183 self.spaces.borrow().clone()
184 }
185
186 pub fn resolve_location(&self, location: SourceLocation) -> ResolvedAddress {
188 resolve_address(&self.spaces.borrow(), location)
189 }
190
191 fn charge_decompressed(
192 &self,
193 scope: LimitScope,
194 amount: u64,
195 operation: &'static str,
196 location: Option<SourceLocation>,
197 ) -> Result<(), CodecError> {
198 debug_assert_eq!(scope, LimitScope::Global);
199 self.budget.charge_decompressed(amount, operation, location)
200 }
201
202 fn fuse(
204 &self,
205 reason: ResourceFailure,
206 scope: LimitScope,
207 amount: u64,
208 operation: &'static str,
209 location: Option<SourceLocation>,
210 ) -> CodecError {
211 let limit = match scope {
212 LimitScope::Global => self.decompression_allowance(),
213 LimitScope::PerExpand => self.per_expand_allowance(),
214 };
215 self.budget.refuse(
216 ResourceDimension::DecompressedBytes,
217 reason,
218 scope,
219 limit,
220 self.budget.decompressed_used(),
221 amount,
222 operation,
223 location,
224 )
225 }
226
227 pub fn reserve_scoped(
229 &self,
230 bytes: u64,
231 operation: &'static str,
232 location: Option<SourceLocation>,
233 ) -> Result<ScopedReservation<'_>, CodecError> {
234 self.budget.reserve_scoped(bytes, operation, location)
235 }
236
237 pub fn charge_retained(
239 &self,
240 bytes: u64,
241 operation: &'static str,
242 location: Option<SourceLocation>,
243 ) -> Result<(), CodecError> {
244 self.budget.charge_retained(bytes, operation, location)
245 }
246
247 pub fn copy_retained(
249 &self,
250 bytes: &[u8],
251 operation: &'static str,
252 location: Option<SourceLocation>,
253 ) -> Result<Vec<u8>, CodecError> {
254 self.charge_retained(bytes.len() as u64, operation, location)?;
255 let mut copy = Vec::new();
256 copy.try_reserve_exact(bytes.len()).map_err(|_| {
257 self.fuse(
258 ResourceFailure::AllocationFailed,
259 LimitScope::Global,
260 bytes.len() as u64,
261 operation,
262 location,
263 )
264 })?;
265 copy.extend_from_slice(bytes);
266 Ok(copy)
267 }
268
269 pub fn alloc_filled<T: Clone>(
274 &self,
275 count: usize,
276 value: T,
277 operation: &'static str,
278 ) -> Result<Vec<T>, CodecError> {
279 self.charge_collection_items(count as u64, operation)?;
280 alloc_filled(count, value, operation)
281 }
282
283 pub fn charge_entities(&self, count: u64, operation: &'static str) -> Result<(), CodecError> {
285 self.budget.charge_entities(count, operation)
286 }
287
288 pub fn admit_entities(
293 &self,
294 current: u64,
295 admitted: &mut u64,
296 operation: &'static str,
297 ) -> Result<(), CodecError> {
298 let additional = current.saturating_sub(*admitted);
299 self.charge_entities(additional, operation)?;
300 *admitted = current;
301 Ok(())
302 }
303
304 pub fn charge_collection_items(
306 &self,
307 count: u64,
308 operation: &'static str,
309 ) -> Result<(), CodecError> {
310 self.budget.charge_collection_items(count, operation)
311 }
312
313 pub fn enter_nested(
315 &self,
316 operation: &'static str,
317 location: Option<SourceLocation>,
318 ) -> Result<DepthGuard<'_>, CodecError> {
319 self.budget.enter_nested(operation, location)
320 }
321
322 pub fn charge_work(&self, units: u64, operation: &'static str) -> Result<(), CodecError> {
324 self.budget.charge_work(units, operation)
325 }
326
327 pub fn work_budget(&self, local_limit: u64) -> WorkBudget<'_> {
329 WorkBudget::for_session(local_limit, &self.budget)
330 }
331
332 pub fn begin_expand(
337 &self,
338 source: View<'_>,
339 spec: ExpandSpec,
340 ) -> Result<ExpandWriter<'_, 'a>, CodecError> {
341 self.begin_expand_as(source, spec, "expanded")
342 }
343
344 pub fn begin_expand_as(
346 &self,
347 source: View<'_>,
348 spec: ExpandSpec,
349 label: impl Into<String>,
350 ) -> Result<ExpandWriter<'_, 'a>, CodecError> {
351 if let Some(limit) = self.budget.fused() {
352 return Err(CodecError::ResourceLimit(limit));
353 }
354 if let ExpandSpec::Exact(size) = spec {
355 let per_expand = self.per_expand_allowance();
356 if size > per_expand {
357 return Err(self.fuse(
358 ResourceFailure::BudgetExceeded,
359 LimitScope::PerExpand,
360 size,
361 "begin_expand",
362 Some(source.location()),
363 ));
364 }
365 if size
366 > self
367 .decompression_allowance()
368 .saturating_sub(self.budget.decompressed_used())
369 {
370 return Err(self.fuse(
371 ResourceFailure::BudgetExceeded,
372 LimitScope::Global,
373 size,
374 "begin_expand",
375 Some(source.location()),
376 ));
377 }
378 }
379 let mut buffer: Vec<u8> = Vec::new();
380 let reserve = match spec {
381 ExpandSpec::Exact(size) => size.min(RESERVE_CLAMP),
382 ExpandSpec::Unknown => 0,
383 };
384 if reserve > 0 {
385 let reserve = usize::try_from(reserve).unwrap_or(usize::MAX);
386 buffer.try_reserve(reserve).map_err(|_| {
387 self.fuse(
388 ResourceFailure::AllocationFailed,
389 LimitScope::PerExpand,
390 reserve as u64,
391 "begin_expand",
392 Some(source.location()),
393 )
394 })?;
395 }
396 Ok(ExpandWriter {
397 ctx: self,
398 spec,
399 location: source.location(),
400 label: label.into(),
401 source_space: source.space(),
402 source_start: source.start() as u64,
403 source_end: source.end() as u64,
404 buffer,
405 written: 0,
406 })
407 }
408
409 pub fn concat_views(&self, inputs: &[View<'_>]) -> Result<View<'a>, CodecError> {
411 if let Some(limit) = self.budget.fused() {
412 return Err(CodecError::ResourceLimit(limit));
413 }
414 let location = inputs.first().copied().map(View::location);
415 let total = inputs.iter().try_fold(0usize, |total, view| {
416 total.checked_add(view.window().len()).ok_or_else(|| {
417 self.budget.refuse(
418 ResourceDimension::RetainedBytes,
419 ResourceFailure::BudgetExceeded,
420 LimitScope::Global,
421 self.policy.limits.max_retained_bytes,
422 total as u64,
423 view.window().len() as u64,
424 "concat_views",
425 location,
426 )
427 })
428 })?;
429 let reservation = self.reserve_scoped(total as u64, "concat_views", location)?;
430 let mut buffer = Vec::new();
431 buffer.try_reserve_exact(total).map_err(|_| {
432 self.budget.refuse(
433 ResourceDimension::MaterializedBytes,
434 ResourceFailure::AllocationFailed,
435 LimitScope::Global,
436 self.policy.limits.max_materialized_bytes,
437 0,
438 total as u64,
439 "concat_views",
440 location,
441 )
442 })?;
443 for view in inputs {
444 buffer.extend_from_slice(view.window());
445 }
446 let bytes = self.arena.alloc(buffer.into_boxed_slice());
447 reservation.commit()?;
448 let parents = inputs.iter().map(|view| view.space()).collect();
449 let space = self.allocate_space("concat".into(), SpaceDerivation::Concatenated { parents });
450 Ok(View::over_space(bytes, space))
451 }
452
453 pub fn register_slice<'v>(
464 &self,
465 parent: View<'v>,
466 range: ByteRange,
467 ) -> Result<View<'v>, CodecError> {
468 self.register_slice_as(parent, range, "stored")
469 }
470
471 pub fn register_slice_as<'v>(
473 &self,
474 parent: View<'v>,
475 range: ByteRange,
476 label: impl Into<String>,
477 ) -> Result<View<'v>, CodecError> {
478 if let Some(limit) = self.budget.fused() {
479 return Err(CodecError::ResourceLimit(limit));
480 }
481 let start = usize::try_from(range.start).ok();
482 let end = usize::try_from(range.end).ok();
483 let child = start
484 .zip(end)
485 .and_then(|(start, end)| parent.child(start, end))
486 .ok_or_else(|| {
487 CodecError::Malformed(format!(
488 "stored slice [{}, {}) escapes parent space {}",
489 range.start,
490 range.end,
491 parent.space().index()
492 ))
493 })?;
494 let space = self.allocate_space(
495 label.into(),
496 SpaceDerivation::StoredSlice {
497 parent: parent.space(),
498 range,
499 },
500 );
501 Ok(View::over_space(child.window(), space))
502 }
503
504 pub fn finish_session(self) -> Result<(), CodecError> {
509 if let Some(limit) = self.budget.fused() {
510 return Err(CodecError::ResourceLimit(limit));
511 }
512 Ok(())
513 }
514}
515
516fn root_error(reason: ResourceFailure, limit: u64, used: u64) -> CodecError {
518 CodecError::ResourceLimit(ResourceLimit {
519 dimension: ResourceDimension::InputBytes,
520 reason,
521 scope: LimitScope::Global,
522 limit,
523 used,
524 additional: used.saturating_sub(limit),
525 context: ErrorContext {
526 operation: "read_root",
527 location: None,
528 },
529 })
530}
531
532#[derive(Debug, Clone, Copy, PartialEq, Eq)]
534pub enum ExpandSpec {
535 Exact(u64),
537 Unknown,
539}
540
541#[derive(Debug)]
543pub struct ExpandWriter<'ctx, 'a> {
544 ctx: &'ctx DecodeContext<'a>,
545 spec: ExpandSpec,
546 location: SourceLocation,
547 label: String,
548 source_space: SpaceId,
549 source_start: u64,
550 source_end: u64,
551 buffer: Vec<u8>,
552 written: u64,
553}
554
555impl<'a> ExpandWriter<'_, 'a> {
556 pub fn write(&mut self, data: &[u8]) -> Result<(), CodecError> {
558 let len = data.len() as u64;
559 let new_written = self.written.saturating_add(len);
560 match self.spec {
561 ExpandSpec::Exact(size) if new_written > size => {
562 return Err(CodecError::Malformed(format!(
563 "expansion exceeded declared exact size {size}"
564 )))
565 }
566 _ => {}
567 }
568 let per_expand = self.ctx.per_expand_allowance();
569 if new_written > per_expand {
570 return Err(self.ctx.fuse(
571 ResourceFailure::BudgetExceeded,
572 LimitScope::PerExpand,
573 len,
574 "expand_write",
575 Some(self.location),
576 ));
577 }
578 self.ctx.charge_decompressed(
579 LimitScope::Global,
580 len,
581 "expand_write",
582 Some(self.location),
583 )?;
584 self.buffer.try_reserve(data.len()).map_err(|_| {
585 self.ctx.fuse(
586 ResourceFailure::AllocationFailed,
587 LimitScope::PerExpand,
588 len,
589 "expand_write",
590 Some(self.location),
591 )
592 })?;
593 self.buffer.extend_from_slice(data);
594 self.written = new_written;
595 Ok(())
596 }
597
598 pub fn finalize(self) -> Result<View<'a>, CodecError> {
600 if let ExpandSpec::Exact(size) = self.spec {
601 if self.written != size {
602 return Err(CodecError::Malformed(format!(
603 "expansion produced {} of declared exact {size} bytes",
604 self.written
605 )));
606 }
607 }
608 let bytes = self.ctx.arena.alloc(self.buffer.into_boxed_slice());
609 let space = self.ctx.allocate_space(
610 self.label,
611 SpaceDerivation::Expanded {
612 parent: self.source_space,
613 source_range: ByteRange {
614 start: self.source_start,
615 end: self.source_end,
616 },
617 },
618 );
619 Ok(View::over_space(bytes, space))
620 }
621
622 pub fn written(&self) -> u64 {
624 self.written
625 }
626}