1use std::cell::Cell;
5use std::io::SeekFrom;
6
7use crate::{CodecError, ReadSeek};
8
9use super::arena::DecodeArena;
10use super::budget::{DecodeBudget, DepthGuard, ScopedReservation, WorkBudget};
11use super::error::{ResourceDimension, ResourceFailure, ResourceLimit};
12use super::policy::{
13 DecodePolicy, DECOMPRESSED_PER_EXPAND_BASE, DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE,
14};
15use super::space::{ByteRange, SpaceId};
16use super::view::{u64_from_index, View};
17
18#[derive(Clone, Copy)]
19enum LimitScope {
20 Global,
21 PerExpand,
22}
23
24const RESERVE_CLAMP: usize = 8 * 1024 * 1024;
26
27#[derive(Debug)]
29pub struct DecodeContext<'a> {
30 arena: &'a DecodeArena,
31 container_only: bool,
32 pub(super) budget: DecodeBudget,
33 derived_spaces: Cell<usize>,
34}
35
36impl<'a> DecodeContext<'a> {
37 pub fn new(arena: &'a DecodeArena, policy: &DecodePolicy, container_only: bool) -> Self {
39 Self {
40 arena,
41 container_only,
42 budget: DecodeBudget::new(*policy, 0),
43 derived_spaces: Cell::new(0),
44 }
45 }
46
47 pub fn for_loaded_input(
52 arena: &'a DecodeArena,
53 policy: &DecodePolicy,
54 input_bytes: u64,
55 ) -> Result<Self, CodecError> {
56 let ctx = Self::new(arena, policy, false);
57 ctx.budget
58 .charge_input(input_bytes, "admit loaded input length")?;
59 Ok(ctx)
60 }
61
62 pub fn read_root(
66 reader: &mut dyn ReadSeek,
67 arena: &'a DecodeArena,
68 policy: &DecodePolicy,
69 container_only: bool,
70 ) -> Result<(Self, View<'a>), CodecError> {
71 if reader.seek(SeekFrom::End(0)).is_ok() {
72 reader.rewind().map_err(CodecError::Io)?;
73 }
74 let ctx = Self::new(arena, policy, container_only);
75 let mut buffer = Vec::new();
76 ctx.complete_input(reader, &mut buffer)?;
77 let bytes = arena.alloc(&ctx, buffer.into_boxed_slice())?;
78 Ok((ctx, View::over_space(bytes, SpaceId::ROOT)))
79 }
80
81 pub fn from_root_bytes(
84 bytes: &'a [u8],
85 arena: &'a DecodeArena,
86 policy: &DecodePolicy,
87 ) -> Result<(Self, View<'a>), CodecError> {
88 Self::from_bytes(bytes, arena, policy, false)
89 }
90
91 pub fn from_root_bytes_limit(
93 bytes: &'a [u8],
94 arena: &'a DecodeArena,
95 policy: &DecodePolicy,
96 ) -> Result<(Self, View<'a>), ResourceLimit> {
97 Self::from_bytes_limit(bytes, arena, policy, false)
98 }
99
100 fn from_bytes(
101 bytes: &'a [u8],
102 arena: &'a DecodeArena,
103 policy: &DecodePolicy,
104 container_only: bool,
105 ) -> Result<(Self, View<'a>), CodecError> {
106 Self::from_bytes_limit(bytes, arena, policy, container_only).map_err(Into::into)
107 }
108
109 fn from_bytes_limit(
110 bytes: &'a [u8],
111 arena: &'a DecodeArena,
112 policy: &DecodePolicy,
113 container_only: bool,
114 ) -> Result<(Self, View<'a>), ResourceLimit> {
115 let length = u64_from_index(bytes.len());
116 if length > policy.limits.max_input_bytes {
117 return Err(root_limit(
118 ResourceFailure::BudgetExceeded,
119 policy.limits.max_input_bytes,
120 length,
121 ));
122 }
123 let ctx = DecodeContext {
124 arena,
125 container_only,
126 budget: DecodeBudget::new(*policy, length),
127 derived_spaces: Cell::new(0),
128 };
129 Ok((ctx, View::over_space(bytes, SpaceId::ROOT)))
130 }
131
132 pub fn policy(&self) -> &DecodePolicy {
134 self.budget.policy()
135 }
136
137 pub fn container_only(&self) -> bool {
139 self.container_only
140 }
141
142 fn decompression_allowance(&self) -> u64 {
143 self.budget.decompression_allowance()
144 }
145
146 fn per_expand_allowance(&self) -> u64 {
147 let policy_limit = self
148 .budget
149 .policy()
150 .limits
151 .max_decompressed_bytes_per_expand;
152 let Some(proportional) = DECOMPRESSED_PER_EXPAND_PER_INPUT_BYTE
153 .checked_mul(self.budget.input_bytes())
154 .and_then(|bytes| DECOMPRESSED_PER_EXPAND_BASE.checked_add(bytes))
155 else {
156 return policy_limit;
157 };
158 policy_limit.min(proportional)
159 }
160
161 fn allocate_space(&self) -> Result<SpaceId, CodecError> {
162 let used = self.derived_spaces.get();
163 let index = used.checked_add(1).ok_or_else(|| {
164 self.budget.refuse(
166 ResourceDimension::Codec("decode address spaces"),
167 ResourceFailure::BudgetExceeded,
168 u64_from_index(usize::MAX),
169 u64_from_index(used),
170 1,
171 "decode address spaces",
172 )
173 })?;
174 self.derived_spaces.set(index);
175 Ok(SpaceId::from_index(index))
176 }
177
178 fn fuse(
180 &self,
181 reason: ResourceFailure,
182 scope: LimitScope,
183 amount: u64,
184 operation: &'static str,
185 ) -> CodecError {
186 let limit = match scope {
187 LimitScope::Global => self.decompression_allowance(),
188 LimitScope::PerExpand => self.per_expand_allowance(),
189 };
190 self.budget.refuse(
191 ResourceDimension::DecompressedBytes,
192 reason,
193 limit,
194 self.budget.decompressed_used(),
195 amount,
196 operation,
197 )
198 }
199
200 pub fn reserve_scoped(
202 &self,
203 bytes: u64,
204 operation: &'static str,
205 ) -> Result<ScopedReservation<'_>, CodecError> {
206 self.budget.reserve_scoped(bytes, operation)
207 }
208
209 pub fn try_materialized<T>(
211 &self,
212 bytes: u64,
213 operation: &'static str,
214 allocate: impl FnOnce() -> Result<T, std::collections::TryReserveError>,
215 ) -> Result<(ScopedReservation<'_>, T), CodecError> {
216 let reservation = self.reserve_scoped(bytes, operation)?;
217 let value =
218 allocate().map_err(|_| self.budget.scoped_allocation_failed(bytes, operation))?;
219 Ok((reservation, value))
220 }
221
222 pub fn reserve_scoped_limit(
224 &self,
225 bytes: u64,
226 operation: &'static str,
227 ) -> Result<ScopedReservation<'_>, ResourceLimit> {
228 self.budget.reserve_scoped_limit(bytes, operation)
229 }
230
231 pub fn charge_retained(&self, bytes: u64, operation: &'static str) -> Result<(), CodecError> {
233 self.budget.charge_retained(bytes, operation)
234 }
235
236 pub(crate) fn retained_size_overflow_limit(&self, operation: &'static str) -> ResourceLimit {
237 self.budget.retained_size_overflow_limit(operation)
238 }
239
240 pub fn charge_retained_limit(
242 &self,
243 bytes: u64,
244 operation: &'static str,
245 ) -> Result<(), ResourceLimit> {
246 self.budget.charge_retained_limit(bytes, operation)
247 }
248
249 pub fn with_scoped_storage<T, E: From<CodecError>>(
251 &self,
252 operation: &'static str,
253 build: impl FnOnce() -> Result<T, E>,
254 ) -> Result<(T, ScopedReservation<'_>), E> {
255 let mut storage = self.reserve_scoped(0, operation)?;
256 let value = storage.with_storage(build)?;
257 Ok((value, storage))
258 }
259
260 pub fn copy_retained_text_limit(
262 &self,
263 text: &str,
264 operation: &'static str,
265 ) -> Result<String, ResourceLimit> {
266 let bytes = super::u64_from_index(text.len());
267 self.budget.charge_work_limit(bytes, operation)?;
268 self.budget.charge_retained_limit(bytes, operation)?;
269 let mut copy = String::new();
270 copy.try_reserve_exact(text.len()).map_err(|_| {
271 self.budget
272 .retained_allocation_failed_limit(bytes, operation)
273 })?;
274 copy.push_str(text);
275 Ok(copy)
276 }
277
278 pub fn copy_retained_text(
280 &self,
281 text: &str,
282 operation: &'static str,
283 ) -> Result<String, CodecError> {
284 self.copy_retained_text_limit(text, operation)
285 .map_err(Into::into)
286 }
287
288 pub fn copy_retained(
290 &self,
291 bytes: &[u8],
292 operation: &'static str,
293 ) -> Result<Vec<u8>, CodecError> {
294 self.charge_work(u64_from_index(bytes.len()), operation)?;
295 self.charge_retained(u64_from_index(bytes.len()), operation)?;
296 let mut copy = Vec::new();
297 copy.try_reserve_exact(bytes.len()).map_err(|_| {
298 self.budget
299 .retained_allocation_failed(u64_from_index(bytes.len()), operation)
300 })?;
301 copy.extend_from_slice(bytes);
302 Ok(copy)
303 }
304
305 pub fn try_reserve_retained_text(
307 &self,
308 text: &mut String,
309 additional: usize,
310 operation: &'static str,
311 ) -> Result<(), CodecError> {
312 let required = text
313 .len()
314 .checked_add(additional)
315 .ok_or_else(|| CodecError::from(self.retained_size_overflow_limit(operation)))?;
316 let growth = if required > text.capacity() {
317 required - text.capacity()
318 } else {
319 0
320 };
321 let bytes = u64_from_index(growth);
322 self.charge_retained(bytes, operation)?;
323 Self::reserve_admitted_string(text, additional, operation)
324 .map_err(|_| self.budget.retained_allocation_failed(bytes, operation))
325 }
326
327 pub fn copy_retained_lossy_utf8(
330 &self,
331 value: &[u8],
332 operation: &'static str,
333 ) -> Result<String, CodecError> {
334 self.charge_work(u64_from_index(value.len()), operation)?;
335 let mut remaining = value;
336 let mut length = 0usize;
337 loop {
338 match std::str::from_utf8(remaining) {
339 Ok(valid) => {
340 length = length
341 .checked_add(valid.len())
342 .ok_or_else(|| self.refuse_codec_limit(operation, u64::MAX, u64::MAX))?;
343 break;
344 }
345 Err(error) => {
346 length = length
347 .checked_add(error.valid_up_to())
348 .and_then(|length| length.checked_add('\u{FFFD}'.len_utf8()))
349 .ok_or_else(|| self.refuse_codec_limit(operation, u64::MAX, u64::MAX))?;
350 let Some(invalid_len) = error.error_len() else {
351 break;
352 };
353 remaining = &remaining[error.valid_up_to() + invalid_len..];
354 }
355 }
356 }
357 let mut text = String::new();
358 self.charge_work(u64_from_index(value.len()), operation)?;
359 self.charge_work(u64_from_index(length), operation)?;
360 self.try_reserve_retained_text(&mut text, length, operation)?;
361 let mut remaining = value;
362 loop {
363 match std::str::from_utf8(remaining) {
364 Ok(valid) => {
365 text.push_str(valid);
366 break;
367 }
368 Err(error) => {
369 let valid_len = error.valid_up_to();
370 self.charge_work(u64_from_index(valid_len), operation)?;
371 text.push_str(
372 std::str::from_utf8(&remaining[..valid_len])
373 .map_err(|_| CodecError::malformed("valid UTF-8 prefix changed"))?,
374 );
375 text.push('\u{FFFD}');
376 let Some(invalid_len) = error.error_len() else {
377 break;
378 };
379 remaining = &remaining[valid_len + invalid_len..];
380 }
381 }
382 }
383 Ok(text)
384 }
385
386 pub fn alloc_filled<T: Clone>(
391 &self,
392 count: usize,
393 value: T,
394 operation: &'static str,
395 ) -> Result<Vec<T>, CodecError> {
396 self.charge_work(u64_from_index(count), operation)?;
397 let mut values = self.collection_vec(count, operation)?;
398 values.resize(count, value);
399 Ok(values)
400 }
401
402 pub fn alloc_filled_admitted<T: Clone>(
409 &self,
410 count: usize,
411 value: T,
412 operation: &'static str,
413 ) -> Result<Vec<T>, CodecError> {
414 self.charge_work(u64_from_index(count), operation)?;
415 self.charge_collection_items(u64_from_index(count), operation)?;
416 let mut values = Vec::new();
417 values
418 .try_reserve_exact(count)
419 .map_err(|_| self.collection_allocation_failed(count, operation))?;
420 values.resize(count, value);
421 Ok(values)
422 }
423
424 pub fn charge_entities(&self, count: u64, operation: &'static str) -> Result<(), CodecError> {
426 self.budget.charge_entities(count, operation)
427 }
428
429 pub fn admit_entities(
434 &self,
435 current: u64,
436 admitted: &mut u64,
437 operation: &'static str,
438 ) -> Result<(), CodecError> {
439 if current < *admitted {
440 *admitted = current;
441 return Ok(());
442 }
443 let additional = current - *admitted;
444 self.charge_entities(additional, operation)?;
445 *admitted = current;
446 Ok(())
447 }
448
449 pub fn charge_collection_items(
451 &self,
452 count: u64,
453 operation: &'static str,
454 ) -> Result<(), CodecError> {
455 self.budget.charge_collection_items(count, operation)
456 }
457
458 pub fn try_collection<T>(
460 &self,
461 count: usize,
462 operation: &'static str,
463 allocate: impl FnOnce() -> Result<T, std::collections::TryReserveError>,
464 ) -> Result<T, CodecError> {
465 let count = u64_from_index(count);
466 self.charge_collection_items(count, operation)?;
467 allocate().map_err(|_| self.budget.collection_allocation_failed(count, operation))
468 }
469
470 pub(super) fn collection_allocation_failed(
472 &self,
473 count: usize,
474 operation: &'static str,
475 ) -> CodecError {
476 self.budget
477 .collection_allocation_failed(u64_from_index(count), operation)
478 }
479
480 pub fn charge_collection_items_limit(
482 &self,
483 count: u64,
484 operation: &'static str,
485 ) -> Result<(), ResourceLimit> {
486 self.budget.charge_collection_items_limit(count, operation)
487 }
488
489 pub fn enter_nested(&self, operation: &'static str) -> Result<DepthGuard<'_>, CodecError> {
491 self.enter_nested_limit(operation).map_err(Into::into)
492 }
493
494 pub fn enter_nested_limit(
496 &self,
497 operation: &'static str,
498 ) -> Result<DepthGuard<'_>, ResourceLimit> {
499 self.budget.enter_nested(operation)
500 }
501
502 pub fn charge_work(&self, units: u64, operation: &'static str) -> Result<(), CodecError> {
504 self.budget.charge_work(units, operation)
505 }
506
507 pub fn charge_work_limit(
509 &self,
510 units: u64,
511 operation: &'static str,
512 ) -> Result<(), ResourceLimit> {
513 self.budget.charge_work_limit(units, operation)
514 }
515
516 pub fn stable_sort_by<T>(
521 &self,
522 values: &mut [T],
523 mut compare: impl FnMut(&T, &T) -> std::cmp::Ordering,
524 key_bytes: impl Fn(&T) -> usize,
525 operation: &'static str,
526 ) -> Result<(), CodecError> {
527 let count = super::u64_from_index(values.len());
528 self.charge_work(count, operation)?;
529 let mut compared = |left: usize, right: usize| {
530 let work = super::u64_from_index(key_bytes(&values[left]))
531 .checked_add(super::u64_from_index(key_bytes(&values[right])))
532 .and_then(|work| work.checked_add(1))
533 .ok_or_else(|| self.refuse_codec_limit(operation, u64::MAX - 1, u64::MAX))?;
534 self.charge_work(work, operation)?;
535 Ok::<_, CodecError>(compare(&values[left], &values[right]))
536 };
537 let mut ordered = true;
538 for index in 1..values.len() {
539 if compared(index - 1, index)?.is_gt() {
540 ordered = false;
541 break;
542 }
543 }
544 if ordered {
545 return Ok(());
546 }
547 let index_bytes = super::u64_from_index(std::mem::size_of::<usize>());
548 let scratch_bytes = count
549 .checked_mul(index_bytes)
550 .and_then(|bytes| bytes.checked_mul(2))
551 .ok_or_else(|| self.refuse_codec_limit(operation, u64::MAX - 1, u64::MAX))?;
552 self.charge_work(scratch_bytes, operation)?;
553 let mut order_vec = Vec::new();
554 let mut destinations_vec = Vec::new();
555 let _scratch = if values.len() > super::sort::INLINE_SORT_LIMIT {
556 self.charge_collection_items(
557 count
558 .checked_mul(2)
559 .ok_or_else(|| self.refuse_codec_limit(operation, u64::MAX, u64::MAX))?,
560 operation,
561 )?;
562 let reservation = self.reserve_scoped(scratch_bytes, operation)?;
563 order_vec.try_reserve_exact(values.len()).map_err(|_| {
564 self.budget
565 .scoped_allocation_failed(scratch_bytes, operation)
566 })?;
567 destinations_vec
568 .try_reserve_exact(values.len())
569 .map_err(|_| {
570 self.budget
571 .scoped_allocation_failed(scratch_bytes, operation)
572 })?;
573 order_vec.extend(0..values.len());
574 destinations_vec.resize(values.len(), 0usize);
575 Some(reservation)
576 } else {
577 None
578 };
579 let mut inline_order = [0usize; super::sort::INLINE_SORT_LIMIT];
580 let mut inline_destinations = [0usize; super::sort::INLINE_SORT_LIMIT];
581 let (mut order, mut destinations) = if values.len() > super::sort::INLINE_SORT_LIMIT {
582 (order_vec.as_mut_slice(), destinations_vec.as_mut_slice())
583 } else {
584 for (index, slot) in inline_order[..values.len()].iter_mut().enumerate() {
585 *slot = index;
586 }
587 (
588 &mut inline_order[..values.len()],
589 &mut inline_destinations[..values.len()],
590 )
591 };
592 let mut width = 1usize;
594 while width < values.len() {
595 self.charge_work(
597 scratch_bytes
598 .checked_mul(2)
599 .ok_or_else(|| self.refuse_codec_limit(operation, u64::MAX - 1, u64::MAX))?,
600 operation,
601 )?;
602 let mut start = 0;
603 while start < values.len() {
604 let middle = start + width.min(values.len() - start);
605 let end = middle + width.min(values.len() - middle);
606 let (mut left, mut right) = (start, middle);
607 for destination in &mut destinations[start..end] {
608 if left < middle
609 && (right == end || !compared(order[left], order[right])?.is_gt())
610 {
611 *destination = order[left];
612 left += 1;
613 } else {
614 *destination = order[right];
615 right += 1;
616 }
617 }
618 start = end;
619 }
620 std::mem::swap(&mut order, &mut destinations);
621 width += width.min(values.len() - width);
622 }
623 let permutation = super::u64_from_index(std::mem::size_of::<T>())
625 .checked_mul(4)
626 .and_then(|bytes| bytes.checked_add(index_bytes.checked_mul(8)?))
627 .and_then(|bytes| count.checked_mul(bytes))
628 .ok_or_else(|| self.refuse_codec_limit(operation, u64::MAX - 1, u64::MAX))?;
629 self.charge_work(permutation, operation)?;
630 for (destination, &source) in order.iter().enumerate() {
631 destinations[source] = destination;
632 }
633 for index in 0..values.len() {
634 while destinations[index] != index {
635 let destination = destinations[index];
636 values.swap(index, destination);
637 destinations.swap(index, destination);
638 }
639 }
640 Ok(())
641 }
642
643 pub fn resource_refusal(&self) -> Option<ResourceLimit> {
645 self.budget.fused()
646 }
647
648 pub(crate) fn refuse_local_limit(
655 &self,
656 operation: &'static str,
657 limit: u64,
658 requested: u64,
659 ) -> ResourceLimit {
660 let (used, additional) = match requested.checked_sub(limit) {
661 Some(excess) => (limit, excess),
662 None => (requested, 0),
663 };
664 self.budget.refuse_limit(
665 ResourceDimension::Codec(operation),
666 ResourceFailure::BudgetExceeded,
667 limit,
668 used,
669 additional,
670 operation,
671 )
672 }
673
674 pub fn refuse_codec_limit(
676 &self,
677 operation: &'static str,
678 limit: u64,
679 requested: u64,
680 ) -> CodecError {
681 self.refuse_local_limit(operation, limit, requested).into()
682 }
683
684 pub fn work_budget(&self, local_limit: u64) -> WorkBudget<'_> {
686 WorkBudget::for_session(local_limit, &self.budget)
687 }
688
689 pub fn begin_expand(&self, spec: ExpandSpec) -> Result<ExpandWriter<'_, 'a>, CodecError> {
694 if let Some(limit) = self.budget.fused() {
695 return Err(CodecError::ResourceLimit(limit));
696 }
697 if let ExpandSpec::Exact(size) = spec {
698 let per_expand = self.per_expand_allowance();
699 if size > per_expand {
700 return Err(self.fuse(
701 ResourceFailure::BudgetExceeded,
702 LimitScope::PerExpand,
703 size,
704 "begin_expand",
705 ));
706 }
707 if self
708 .decompression_allowance()
709 .checked_sub(self.budget.decompressed_used())
710 .is_none_or(|remaining| size > remaining)
711 {
712 return Err(self.fuse(
713 ResourceFailure::BudgetExceeded,
714 LimitScope::Global,
715 size,
716 "begin_expand",
717 ));
718 }
719 }
720 let mut buffer: Vec<u8> = Vec::new();
721 let reserve = match spec {
722 ExpandSpec::Exact(size) => match usize::try_from(size) {
725 Ok(size) => size.min(RESERVE_CLAMP),
726 Err(_) => RESERVE_CLAMP,
727 },
728 ExpandSpec::Unknown => 0,
729 };
730 if reserve > 0 {
731 buffer.try_reserve(reserve).map_err(|_| {
732 self.fuse(
733 ResourceFailure::AllocationFailed,
734 LimitScope::PerExpand,
735 u64_from_index(reserve),
736 "begin_expand",
737 )
738 })?;
739 }
740 Ok(ExpandWriter {
741 ctx: self,
742 spec,
743 buffer,
744 })
745 }
746
747 pub fn concat_views(&self, inputs: &[View<'_>]) -> Result<View<'a>, CodecError> {
749 if let Some(limit) = self.budget.fused() {
750 return Err(CodecError::ResourceLimit(limit));
751 }
752 if inputs.is_empty() {
753 return Err(CodecError::Malformed(
754 "cannot concatenate an empty view list".into(),
755 ));
756 }
757 self.charge_work(u64_from_index(inputs.len()), "concat_views")?;
758 let total = inputs.iter().try_fold(0usize, |total, view| {
759 total.checked_add(view.window().len()).ok_or_else(|| {
760 self.budget.refuse(
761 ResourceDimension::RetainedBytes,
762 ResourceFailure::BudgetExceeded,
763 self.budget.policy().limits.max_retained_bytes,
764 u64_from_index(total),
765 u64_from_index(view.window().len()),
766 "concat_views",
767 )
768 })
769 })?;
770 self.charge_work(u64_from_index(total), "concat_views")?;
771 let reservation = self.reserve_scoped(u64_from_index(total), "concat_views")?;
772 let mut buffer = Vec::new();
773 buffer.try_reserve_exact(total).map_err(|_| {
774 self.budget.refuse(
775 ResourceDimension::MaterializedBytes,
776 ResourceFailure::AllocationFailed,
777 self.budget.policy().limits.max_materialized_bytes,
778 0,
779 u64_from_index(total),
780 "concat_views",
781 )
782 })?;
783 for view in inputs {
784 buffer.extend_from_slice(view.window());
785 }
786 let bytes = self.arena.alloc(self, buffer.into_boxed_slice())?;
787 reservation.commit()?;
788 let space = self.allocate_space()?;
789 Ok(View::over_space(bytes, space))
790 }
791
792 pub fn concat_retained(
794 &self,
795 inputs: &[Vec<u8>],
796 operation: &'static str,
797 ) -> Result<Vec<u8>, CodecError> {
798 if let Some(limit) = self.budget.fused() {
799 return Err(CodecError::ResourceLimit(limit));
800 }
801 if inputs.is_empty() {
802 return Err(CodecError::Malformed(
803 "cannot concatenate an empty buffer list".into(),
804 ));
805 }
806 let total = inputs.iter().try_fold(0_usize, |total, input| {
807 total.checked_add(input.len()).ok_or_else(|| {
808 CodecError::NotImplemented("retained concatenation exceeds usize".into())
809 })
810 })?;
811 let total_bytes = crate::decode::u64_from_index(total);
812 self.charge_work(u64_from_index(inputs.len()), operation)?;
813 self.charge_work(total_bytes, operation)?;
814 self.charge_retained(total_bytes, operation)?;
815 let mut buffer = Vec::new();
816 buffer.try_reserve_exact(total).map_err(|_| {
817 self.budget
818 .retained_allocation_failed(total_bytes, operation)
819 })?;
820 for input in inputs {
821 buffer.extend_from_slice(input);
822 }
823 Ok(buffer)
824 }
825
826 pub fn register_slice<'v>(
837 &self,
838 parent: View<'v>,
839 range: ByteRange,
840 ) -> Result<View<'v>, CodecError> {
841 if let Some(limit) = self.budget.fused() {
842 return Err(CodecError::ResourceLimit(limit));
843 }
844 let start = usize::try_from(range.start).ok();
845 let end = usize::try_from(range.end).ok();
846 let child = start
847 .zip(end)
848 .and_then(|(start, end)| parent.child(start, end))
849 .ok_or_else(|| {
850 CodecError::malformed(format_args!(
851 "stored slice [{}, {}) escapes parent space {}",
852 range.start,
853 range.end,
854 parent.space().index()
855 ))
856 })?;
857 self.charge_collection_items(1, "register borrowed space")?;
858 let space = self.allocate_space()?;
859 Ok(View::over_space(child.window(), space))
860 }
861
862 pub fn finish_session(self) -> Result<(), CodecError> {
867 if let Some(limit) = self.budget.fused() {
868 return Err(CodecError::ResourceLimit(limit));
869 }
870 Ok(())
871 }
872}
873
874fn root_limit(reason: ResourceFailure, limit: u64, used: u64) -> ResourceLimit {
875 if used <= limit {
876 return ResourceLimit {
877 dimension: ResourceDimension::InputBytes,
878 reason,
879 limit,
880 used,
881 additional: 0,
882 operation: "read_root",
883 };
884 }
885 let additional = used - limit;
886 ResourceLimit {
887 dimension: ResourceDimension::InputBytes,
888 reason,
889 limit,
890 used,
891 additional,
892 operation: "read_root",
893 }
894}
895
896#[derive(Debug, Clone, Copy, PartialEq, Eq)]
898pub enum ExpandSpec {
899 Exact(u64),
901 Unknown,
903}
904
905#[derive(Debug)]
907pub struct ExpandWriter<'ctx, 'a> {
908 ctx: &'ctx DecodeContext<'a>,
909 spec: ExpandSpec,
910 buffer: Vec<u8>,
911}
912
913impl<'a> ExpandWriter<'_, 'a> {
914 pub fn write(&mut self, data: &[u8]) -> Result<(), CodecError> {
916 let len = u64_from_index(data.len());
917 let new_written = self.written().checked_add(len).ok_or_else(|| {
918 self.ctx.fuse(
919 ResourceFailure::BudgetExceeded,
920 LimitScope::PerExpand,
921 len,
922 "expand_write",
923 )
924 })?;
925 match self.spec {
926 ExpandSpec::Exact(size) if new_written > size => {
927 return Err(CodecError::malformed(format_args!(
928 "expansion exceeded declared exact size {size}"
929 )))
930 }
931 _ => {}
932 }
933 let per_expand = self.ctx.per_expand_allowance();
934 if new_written > per_expand {
935 return Err(self.ctx.fuse(
936 ResourceFailure::BudgetExceeded,
937 LimitScope::PerExpand,
938 len,
939 "expand_write",
940 ));
941 }
942 self.ctx.budget.charge_decompressed(len, "expand_write")?;
943 self.buffer.try_reserve(data.len()).map_err(|_| {
944 self.ctx.fuse(
945 ResourceFailure::AllocationFailed,
946 LimitScope::PerExpand,
947 len,
948 "expand_write",
949 )
950 })?;
951 self.buffer.extend_from_slice(data);
952 Ok(())
953 }
954
955 pub fn finalize(self) -> Result<View<'a>, CodecError> {
957 self.check_exact()?;
958 let bytes = self
959 .ctx
960 .arena
961 .alloc(self.ctx, self.buffer.into_boxed_slice())?;
962 let space = self.ctx.allocate_space()?;
963 Ok(View::over_space(bytes, space))
964 }
965
966 pub fn finalize_owned(self) -> Result<Vec<u8>, CodecError> {
969 self.check_exact()?;
970 Ok(self.buffer)
971 }
972
973 fn check_exact(&self) -> Result<(), CodecError> {
974 if let ExpandSpec::Exact(size) = self.spec {
975 if self.written() != size {
976 return Err(CodecError::malformed(format_args!(
977 "expansion produced {} of declared exact {size} bytes",
978 self.written()
979 )));
980 }
981 }
982 Ok(())
983 }
984
985 pub fn written(&self) -> u64 {
987 u64_from_index(self.buffer.len())
988 }
989}
990
991#[cfg(test)]
992mod tests {
993 use super::{u64_from_index, ByteRange, DecodeArena, DecodeContext, DecodePolicy};
994 use crate::decode::{ResourceDimension, ResourceFailure};
995 use std::io::{self, Cursor, Read, Seek, SeekFrom};
996
997 #[test]
998 fn temporary_byte_storage_uses_its_byte_budget_and_releases_it() {
999 let arena = DecodeArena::new();
1000 let mut policy = DecodePolicy::service();
1001 policy.limits.max_materialized_bytes = 32;
1002 policy.limits.max_collection_items = 0;
1003 let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("context");
1004 let (reservation, bytes) = ctx
1005 .try_materialized(32, "test bytes", || {
1006 let mut bytes = Vec::<u8>::new();
1007 bytes.try_reserve_exact(32)?;
1008 Ok(bytes)
1009 })
1010 .expect("byte allowance");
1011 assert_eq!(bytes.capacity(), 32);
1012 drop(reservation);
1013 let (reservation, ()) = ctx
1014 .try_materialized(32, "test bytes", || Ok(()))
1015 .expect("released allowance");
1016 assert!(
1017 matches!(ctx.try_materialized(1, "test bytes", || Ok(())), Err(crate::CodecError::ResourceLimit(limit)) if limit.dimension == ResourceDimension::MaterializedBytes)
1018 );
1019 drop(reservation);
1020 }
1021
1022 #[test]
1023 fn loaded_input_processing_preserves_proportional_and_absolute_limits() {
1024 let input_bytes = 1024;
1025 let allowance = crate::decode::policy::MATERIALIZED_BASE
1026 + input_bytes * crate::decode::policy::MATERIALIZED_PER_INPUT_BYTE;
1027 let arena = DecodeArena::new();
1028 let mut policy = DecodePolicy::service();
1029 let ctx = DecodeContext::for_loaded_input(&arena, &policy, input_bytes)
1030 .expect("admitted input length");
1031 drop(
1032 ctx.reserve_scoped(allowance, "loaded input allowance")
1033 .expect("input-proportional allowance"),
1034 );
1035 assert!(
1036 matches!(ctx.reserve_scoped(allowance + 1, "loaded input allowance"),
1037 Err(crate::CodecError::ResourceLimit(limit))
1038 if limit.dimension == ResourceDimension::MaterializedBytes && limit.limit == allowance)
1039 );
1040
1041 policy.limits.max_materialized_bytes = 32;
1042 let ctx = DecodeContext::for_loaded_input(&arena, &policy, input_bytes)
1043 .expect("admitted input length");
1044 assert!(matches!(ctx.reserve_scoped(33, "absolute limit"),
1045 Err(crate::CodecError::ResourceLimit(limit))
1046 if limit.dimension == ResourceDimension::MaterializedBytes && limit.limit == 32));
1047
1048 policy.limits.max_input_bytes = input_bytes - 1;
1049 assert!(
1050 matches!(DecodeContext::for_loaded_input(&arena, &policy, input_bytes),
1051 Err(crate::CodecError::ResourceLimit(limit))
1052 if limit.dimension == ResourceDimension::InputBytes && limit.additional == input_bytes)
1053 );
1054 }
1055
1056 #[test]
1057 fn zero_collection_limit_refuses_second_empty_finalization() {
1058 let arena = DecodeArena::new();
1059 let mut policy = DecodePolicy::service();
1060 policy.limits.max_collection_items = 0;
1061 let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty root");
1062 for _ in 0..2 {
1063 let result = ctx
1064 .begin_expand(super::ExpandSpec::Exact(0))
1065 .and_then(super::ExpandWriter::finalize);
1066 assert!(
1067 matches!(result, Err(crate::CodecError::ResourceLimit(limit))
1068 if limit.dimension == ResourceDimension::CollectionItems)
1069 );
1070 }
1071 }
1072
1073 #[test]
1074 fn one_collection_slot_admits_only_one_empty_finalization() {
1075 let arena = DecodeArena::new();
1076 let mut policy = DecodePolicy::service();
1077 policy.limits.max_collection_items = 1;
1078 let (ctx, _) = DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty root");
1079 assert!(ctx
1080 .begin_expand(super::ExpandSpec::Exact(0))
1081 .expect("empty expansion")
1082 .finalize()
1083 .expect("first registry slot")
1084 .window()
1085 .is_empty());
1086 assert!(
1087 matches!(ctx.begin_expand(super::ExpandSpec::Exact(0)).expect("empty expansion")
1088 .finalize(), Err(crate::CodecError::ResourceLimit(limit))
1089 if limit.dimension == ResourceDimension::CollectionItems && limit.used == 1)
1090 );
1091 }
1092
1093 struct RewindFails(Cursor<Vec<u8>>);
1094
1095 impl Read for RewindFails {
1096 fn read(&mut self, buffer: &mut [u8]) -> io::Result<usize> {
1097 self.0.read(buffer)
1098 }
1099 }
1100
1101 impl Seek for RewindFails {
1102 fn seek(&mut self, position: SeekFrom) -> io::Result<u64> {
1103 if position == SeekFrom::Start(0) {
1104 Err(io::Error::new(
1105 io::ErrorKind::PermissionDenied,
1106 "rewind denied",
1107 ))
1108 } else {
1109 self.0.seek(position)
1110 }
1111 }
1112 }
1113
1114 #[test]
1115 fn retained_collection_refuses_slot_before_copying_the_record() {
1116 let arena = DecodeArena::new();
1117 let mut policy = DecodePolicy::service();
1118 policy.limits.max_retained_bytes = 0;
1119 let (ctx, _) =
1120 DecodeContext::from_root_bytes(&[], &arena, &policy).expect("admitted test operation");
1121 let copied = std::cell::Cell::new(false);
1122 let result = ctx.try_collect_retained_with([1_u8], "collect record", |value| {
1123 copied.set(true);
1124 Ok::<_, crate::CodecError>(value)
1125 });
1126 assert!(
1127 matches!(result, Err(crate::CodecError::ResourceLimit(limit))
1128 if limit.dimension == super::ResourceDimension::RetainedBytes && limit.additional == 1)
1129 );
1130 assert!(!copied.get());
1131 }
1132
1133 #[test]
1134 fn scoped_copy_storage_is_released_and_does_not_charge_retained_bytes() {
1135 let arena = DecodeArena::new();
1136 let mut policy = DecodePolicy::service();
1137 policy.limits.max_retained_bytes = 0;
1138 policy.limits.max_materialized_bytes = 4;
1139 let (ctx, _) =
1140 DecodeContext::from_root_bytes(&[], &arena, &policy).expect("admitted test operation");
1141 let (text, reservation) = ctx
1142 .with_scoped_storage("temporary copy", || {
1143 ctx.copy_retained_text("abcd", "temporary copy")
1144 })
1145 .expect("admitted test operation");
1146 assert_eq!(text, "abcd");
1147 drop(text);
1148 drop(reservation);
1149 assert!(ctx.reserve_scoped(4, "reuse scope").is_ok());
1150 }
1151
1152 #[test]
1153 fn scoped_copy_storage_refuses_materialized_bytes() {
1154 let arena = DecodeArena::new();
1155 let mut policy = DecodePolicy::service();
1156 policy.limits.max_materialized_bytes = 3;
1157 let (ctx, _) =
1158 DecodeContext::from_root_bytes(&[], &arena, &policy).expect("admitted test operation");
1159 assert!(matches!(ctx.with_scoped_storage("temporary copy", || {
1160 ctx.copy_retained_text("abcd", "temporary copy")
1161 }), Err(crate::CodecError::ResourceLimit(limit))
1162 if limit.dimension == super::ResourceDimension::MaterializedBytes
1163 && limit.additional == 4));
1164 }
1165
1166 #[test]
1167 fn scoped_copy_extends_existing_reservation_without_double_charging() {
1168 let arena = DecodeArena::new();
1169 let mut policy = DecodePolicy::service();
1170 policy.limits.max_materialized_bytes = 6;
1171 policy.limits.max_retained_bytes = 0;
1172 let (ctx, _) =
1173 DecodeContext::from_root_bytes(&[], &arena, &policy).expect("admitted test operation");
1174 let mut storage = ctx
1175 .reserve_scoped(2, "temporary record")
1176 .expect("admitted test operation");
1177 let text = storage
1178 .with_storage(|| ctx.copy_retained_text("abcd", "temporary text"))
1179 .expect("admitted test operation");
1180 assert_eq!(text, "abcd");
1181 assert!(
1182 matches!(ctx.reserve_scoped(1, "full scope"), Err(crate::CodecError::ResourceLimit(limit)) if limit.used == 6)
1183 );
1184 drop(text);
1185 drop(storage);
1186 }
1187
1188 #[test]
1189 fn nested_scoped_copies_hold_independent_reservations() {
1190 let arena = DecodeArena::new();
1191 let mut policy = DecodePolicy::service();
1192 policy.limits.max_materialized_bytes = 6;
1193 policy.limits.max_retained_bytes = 0;
1194 let (ctx, _) =
1195 DecodeContext::from_root_bytes(&[], &arena, &policy).expect("admitted test operation");
1196 let ((first, second, inner), outer) = ctx
1197 .with_scoped_storage("outer", || {
1198 let first = ctx.copy_retained_text("ab", "outer")?;
1199 let (second, inner) =
1200 ctx.with_scoped_storage("inner", || ctx.copy_retained_text("cd", "inner"))?;
1201 Ok::<_, crate::CodecError>((first, second, inner))
1202 })
1203 .expect("admitted test operation");
1204 assert_eq!((first.as_str(), second.as_str()), ("ab", "cd"));
1205 assert!(ctx.reserve_scoped(2, "remaining scope").is_ok());
1206 drop(first);
1207 drop(outer);
1208 assert!(ctx.reserve_scoped(4, "outer released").is_ok());
1209 drop(second);
1210 drop(inner);
1211 assert!(ctx.reserve_scoped(6, "all released").is_ok());
1212 }
1213
1214 #[test]
1215 fn root_reader_preserves_bytes_across_fixed_chunk_boundaries() {
1216 let mut bytes = vec![0x5a_u8; 8193];
1217 bytes[8192] = 0x7f;
1218 let mut reader = Cursor::new(bytes.clone());
1219 let arena = DecodeArena::new();
1220 let (_, root) =
1221 DecodeContext::read_root(&mut reader, &arena, &DecodePolicy::default(), false)
1222 .expect("root is admitted");
1223 assert_eq!(root.window(), bytes.as_slice());
1224 }
1225
1226 #[test]
1227 fn root_reader_propagates_failed_rewind_after_a_successful_size_probe() {
1228 let arena = DecodeArena::new();
1229 let mut reader = RewindFails(Cursor::new(b"not empty".to_vec()));
1230 assert!(matches!(
1231 DecodeContext::read_root(&mut reader, &arena, &DecodePolicy::default(), false),
1232 Err(crate::CodecError::Io(error)) if error.kind() == io::ErrorKind::PermissionDenied
1233 ));
1234 }
1235
1236 #[test]
1237 fn exhausted_space_ids_refuse_registration_without_reusing_an_id() {
1238 let arena = DecodeArena::new();
1239 let (ctx, root) = DecodeContext::from_root_bytes(b"x", &arena, &DecodePolicy::default())
1240 .expect("test input fits the policy");
1241 ctx.derived_spaces.set(usize::MAX - 1);
1242 let range = ByteRange { start: 0, end: 1 };
1243 let view = ctx
1244 .register_slice(root, range)
1245 .expect("maximum space id is allocatable");
1246 assert_eq!(view.space().index(), usize::MAX);
1247 let error = ctx
1248 .register_slice(root, range)
1249 .expect_err("space IDs are exhausted");
1250 let crate::CodecError::ResourceLimit(limit) = error else {
1251 panic!("exhausted IDs return a resource refusal");
1252 };
1253 assert_eq!(limit.limit, u64_from_index(usize::MAX));
1254 assert_eq!(limit.used, u64_from_index(usize::MAX));
1255 assert_eq!(limit.additional, 1);
1256 assert!(ctx.register_slice(root, range).is_err());
1257 assert!(ctx.finish_session().is_err());
1258 }
1259
1260 #[test]
1261 fn collection_reservation_reports_allocator_refusal_after_charge() {
1262 let arena = DecodeArena::new();
1263 let mut policy = DecodePolicy::service();
1264 policy.limits.max_collection_items = u64::MAX;
1265 policy.limits.max_retained_bytes = u64::MAX;
1266 let (ctx, _) = DecodeContext::from_root_bytes(b"x", &arena, &policy)
1267 .expect("test input fits the policy");
1268 let mut values = Vec::<u8>::new();
1269 let error = ctx
1270 .reserve_vec(&mut values, usize::MAX, "test collection reservation")
1271 .expect_err("a vector cannot reserve more than isize::MAX bytes");
1272 assert!(matches!(
1273 error,
1274 crate::CodecError::ResourceLimit(limit)
1275 if limit.dimension == ResourceDimension::RetainedBytes
1276 && limit.reason == ResourceFailure::AllocationFailed
1277 && limit.used == 0
1278 && limit.additional == u64_from_index(usize::MAX)
1279 && limit.operation == "test collection reservation"
1280 ));
1281 assert!(ctx.finish_session().is_err());
1282 }
1283
1284 #[test]
1285 fn try_collection_refuses_before_allocation_and_succeeds_under_service_profile() {
1286 let arena = DecodeArena::new();
1287 let mut policy = DecodePolicy::service();
1288 policy.limits.max_collection_items = 2;
1289 let (ctx, _) = DecodeContext::from_root_bytes(b"x", &arena, &policy)
1290 .expect("test input fits the policy");
1291 let mut allocated = false;
1292 let error = ctx
1293 .try_collection(3, "test collection allocation", || {
1294 allocated = true;
1295 Ok(())
1296 })
1297 .expect_err("three items exceed a limit of two");
1298 assert!(!allocated);
1299 assert!(matches!(
1300 error,
1301 crate::CodecError::ResourceLimit(limit)
1302 if limit.dimension == ResourceDimension::CollectionItems
1303 && limit.reason == ResourceFailure::BudgetExceeded
1304 && limit.operation == "test collection allocation"
1305 ));
1306
1307 let arena = DecodeArena::new();
1308 let (ctx, _) = DecodeContext::from_root_bytes(b"x", &arena, &DecodePolicy::service())
1309 .expect("test input fits the policy");
1310 let mut values = Vec::<u8>::new();
1311 ctx.try_collection(3, "test collection allocation", || values.try_reserve(3))
1312 .expect("the service profile admits three items");
1313 assert!(values.capacity() >= 3);
1314 }
1315
1316 #[test]
1317 fn try_collection_reports_allocator_refusal_after_charge() {
1318 let arena = DecodeArena::new();
1319 let mut policy = DecodePolicy::service();
1320 policy.limits.max_collection_items = u64::MAX;
1321 let (ctx, _) = DecodeContext::from_root_bytes(b"x", &arena, &policy)
1322 .expect("test input fits the policy");
1323 let mut values = Vec::<u8>::new();
1324 let error = ctx
1325 .try_collection(usize::MAX, "test collection allocation", || {
1326 values.try_reserve(usize::MAX)
1327 })
1328 .expect_err("a vector cannot reserve more than isize::MAX bytes");
1329 assert!(matches!(
1330 error,
1331 crate::CodecError::ResourceLimit(limit)
1332 if limit.dimension == ResourceDimension::CollectionItems
1333 && limit.reason == ResourceFailure::AllocationFailed
1334 && limit.operation == "test collection allocation"
1335 ));
1336 }
1337
1338 #[test]
1339 fn scoped_text_copy_refuses_before_allocation_and_releases_on_drop() {
1340 let arena = DecodeArena::new();
1341 let mut policy = DecodePolicy::service();
1342 policy.limits.max_materialized_bytes = 3;
1343 let (ctx, _) = DecodeContext::from_root_bytes(b"x", &arena, &policy)
1344 .expect("test input fits the policy");
1345 {
1346 let mut reservation = ctx
1347 .reserve_scoped(0, "test scoped text")
1348 .expect("empty reservation is admitted");
1349 let copy = ctx
1350 .copy_scoped_text("abc", &mut reservation, "test scoped text")
1351 .expect("three temporary bytes are admitted");
1352 assert_eq!(copy, "abc");
1353 }
1354 {
1355 let mut reservation = ctx
1356 .reserve_scoped(0, "test scoped text")
1357 .expect("empty reservation is admitted");
1358 assert_eq!(
1359 ctx.copy_scoped_text("def", &mut reservation, "test scoped text")
1360 .expect("the prior reservation was released"),
1361 "def"
1362 );
1363 }
1364 let mut reservation = ctx
1365 .reserve_scoped(0, "test scoped text")
1366 .expect("empty reservation is admitted");
1367 let error = ctx
1368 .copy_scoped_text("abcd", &mut reservation, "test scoped text")
1369 .expect_err("four bytes exceed the temporary limit");
1370 assert!(matches!(error, crate::CodecError::ResourceLimit(limit)
1371 if limit.dimension == ResourceDimension::MaterializedBytes
1372 && limit.operation == "test scoped text"));
1373 }
1374
1375 #[test]
1376 fn scoped_format_refuses_before_allocation_and_releases_on_drop() {
1377 let arena = DecodeArena::new();
1378 let mut policy = DecodePolicy::service();
1379 policy.limits.max_materialized_bytes = 3;
1380 let (ctx, _) =
1381 DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty root is admitted");
1382 {
1383 let (text, _reservation) = ctx
1384 .format_scoped(format_args!("x{}", 12), "test scoped format")
1385 .expect("three rendered bytes fit");
1386 assert_eq!(text, "x12");
1387 }
1388 assert_eq!(
1389 ctx.format_scoped(format_args!("y{}", 34), "test scoped format")
1390 .expect("prior reservation was released")
1391 .0,
1392 "y34"
1393 );
1394 let error = ctx
1395 .format_scoped(format_args!("z{}", 345), "test scoped format")
1396 .expect_err("four bytes exceed the materialized allowance");
1397 assert!(matches!(error, crate::CodecError::ResourceLimit(limit)
1398 if limit.dimension == ResourceDimension::MaterializedBytes
1399 && limit.operation == "test scoped format"));
1400 }
1401
1402 #[test]
1403 fn retained_format_refuses_before_string_growth() {
1404 let arena = DecodeArena::new();
1405 let mut policy = DecodePolicy::service();
1406 policy.limits.max_retained_bytes = 4;
1407 let (ctx, _) =
1408 DecodeContext::from_root_bytes(&[], &arena, &policy).expect("empty root is admitted");
1409 assert_eq!(
1410 ctx.format_retained(
1411 format_args!("{number}", number = 1234),
1412 "test retained format"
1413 )
1414 .expect("four rendered bytes fit"),
1415 "1234"
1416 );
1417 let error = ctx
1418 .format_retained(format_args!("{}", 5), "test retained format")
1419 .expect_err("one more byte exceeds the retained limit");
1420 assert!(matches!(error, crate::CodecError::ResourceLimit(limit)
1421 if limit.dimension == ResourceDimension::RetainedBytes
1422 && limit.operation == "test retained format"));
1423 }
1424 #[test]
1425 fn string_growth_charges_only_new_capacity() {
1426 let arena = DecodeArena::new();
1427 let mut policy = DecodePolicy::service();
1428 policy.limits.max_retained_bytes = 0;
1429 let (ctx, _) =
1430 DecodeContext::from_root_bytes(&[], &arena, &policy).expect("admitted test operation");
1431 let mut text = String::with_capacity(4);
1432 ctx.try_reserve_retained_text(&mut text, 4, "existing text capacity")
1433 .expect("admitted test operation");
1434 assert_eq!(text.capacity(), 4);
1435 let error = ctx
1436 .try_reserve_retained_text(&mut text, 5, "new text capacity")
1437 .expect_err("test operation refuses");
1438 assert!(matches!(error, crate::CodecError::ResourceLimit(limit)
1439 if limit.dimension == ResourceDimension::RetainedBytes
1440 && limit.used == 0 && limit.additional == 1));
1441 assert_eq!(text.capacity(), 4);
1442 }
1443
1444 #[test]
1445 fn text_append_reserves_exact_growth() {
1446 let arena = DecodeArena::new();
1447 let mut policy = DecodePolicy::service();
1448 policy.limits.max_retained_bytes = 3;
1449 let (ctx, _) =
1450 DecodeContext::from_root_bytes(&[], &arena, &policy).expect("admitted test operation");
1451 let mut text = ctx
1452 .copy_retained_text("ab", "initial text")
1453 .expect("admitted test operation");
1454 ctx.append_retained(&mut text, "c", "append text")
1455 .expect("admitted test operation");
1456 assert_eq!(text, "abc");
1457 assert_eq!(text.capacity(), 3);
1458 let error = ctx
1459 .append_retained(&mut text, "d", "append text")
1460 .expect_err("test operation refuses");
1461 assert!(matches!(error, crate::CodecError::ResourceLimit(limit)
1462 if limit.dimension == ResourceDimension::RetainedBytes
1463 && limit.used == 3 && limit.additional == 1));
1464 assert_eq!(text, "abc");
1465 }
1466}
1467
1468#[cfg(test)]
1469mod sort_tests;