1use std::collections::HashMap;
12use std::time::Duration;
13
14#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
15use num_bigint::BigInt;
16#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
17use num_bigint::Sign;
18use qubit_budget::BudgetError;
19use qubit_budget::MeasuredBudgetError;
20use qubit_budget::Observation;
21use qubit_budget::QuantityConversionError;
22use qubit_budget::QuantityMeasurement;
23use qubit_budget::json::JsonEncodeLimits;
24use qubit_budget::json::JsonEncodeLimits as JsonEncodeLimitsU64;
25use qubit_budget::json::JsonMeasurement;
26use qubit_budget::json::JsonResource;
27use qubit_budget::json::JsonValueLimits;
28use serde_json::Value as JsonValue;
29use serde_json::to_writer;
30
31use self::internal::frame::Frame;
32use self::internal::json_length_writer::JsonLengthWriter;
33use self::internal::wire_scalar_measurement::decimal_len;
34use self::internal::wire_scalar_measurement::json_float32_len;
35use self::internal::wire_scalar_measurement::json_float64_len;
36use self::internal::wire_scalar_measurement::unsigned_decimal_len;
37use super::internal::WireDataTypeV1;
38use super::internal::WirePreflightStrategy;
39use crate::MultiValues;
40use crate::Value;
41use crate::ValueContainer;
42use crate::ValueRef;
43
44mod internal;
45
46macro_rules! define_value_ref_preflight_strategy {
47 (
48 $($arg:expr),*;
49 $(
50 (
51 [$($cfg:meta),*],
52 $variant:ident,
53 $type:ty,
54 $_data_type:expr,
55 $_materialization:ident,
56 $_json_class:ident,
57 $_number_projection:ident,
58 $_value_doc:literal,
59 $_multi_doc:literal,
60 [$($scalar_attr:meta),*],
61 [$($collection_attr:meta),*],
62 $_tag:literal,
63 $_wire_preflight:ident
64 )
65 ),+ $(,)?
66 ) => {
67 #[must_use]
77 fn value_ref_preflight_strategy(value: ValueRef<'_>) -> WirePreflightStrategy {
78 match value {
79 ValueRef::Unset(_) => WirePreflightStrategy::UnsetText,
80 $(
81 $(#[$cfg])*
82 ValueRef::$variant(_) => WireDataTypeV1::$variant.preflight_strategy(),
83 )+
84 }
85 }
86 };
87}
88
89for_each_value_type!(define_value_ref_preflight_strategy);
90
91#[derive(Debug, Clone)]
117pub struct ValueWireEncodePreflight {
118 limits: JsonEncodeLimits,
120 nodes: usize,
122 payload_bytes: usize,
124 output_bytes: usize,
126}
127
128#[inline]
140#[must_use]
141fn saturating_u64_to_usize(value: u64) -> usize {
142 usize::try_from(value).unwrap_or(usize::MAX)
143}
144
145impl ValueWireEncodePreflight {
146 #[must_use]
156 #[inline]
157 pub fn new(limits: JsonEncodeLimits) -> Self {
158 Self {
159 limits,
160 nodes: 0,
161 payload_bytes: 0,
162 output_bytes: 0,
163 }
164 }
165
166 #[must_use]
179 #[inline]
180 pub fn new_value_limits(limits: JsonValueLimits) -> Self {
181 let mut checker = Self::new(JsonEncodeLimits::new());
182 checker.limits = JsonEncodeLimits::builder().value_limits(limits).build();
183 checker
184 }
185
186 #[must_use]
200 pub fn new_u64_limits(limits: JsonEncodeLimitsU64<JsonResource, u64>) -> Self {
201 let value = limits.value_limits();
202 let mut builder = JsonEncodeLimits::builder();
203 if let Some(limit) = limits.max_output_bytes() {
204 builder = builder.max_output_bytes(saturating_u64_to_usize(limit));
205 }
206 if let Some(limit) = value.max_depth() {
207 builder = builder.max_depth(saturating_u64_to_usize(limit));
208 }
209 if let Some(limit) = value.max_nodes() {
210 builder = builder.max_nodes(saturating_u64_to_usize(limit));
211 }
212 if let Some(limit) = value.max_sequence_items() {
213 builder = builder.max_sequence_items(saturating_u64_to_usize(limit));
214 }
215 if let Some(limit) = value.max_map_entries() {
216 builder = builder.max_map_entries(saturating_u64_to_usize(limit));
217 }
218 if let Some(limit) = value.max_key_bytes() {
219 builder = builder.max_key_bytes(saturating_u64_to_usize(limit));
220 }
221 if let Some(limit) = value.max_string_bytes() {
222 builder = builder.max_string_bytes(saturating_u64_to_usize(limit));
223 }
224 if let Some(limit) = value.max_number_bytes() {
225 builder = builder.max_number_bytes(saturating_u64_to_usize(limit));
226 }
227 if let Some(limit) = value.max_payload_bytes() {
228 builder = builder.max_payload_bytes(saturating_u64_to_usize(limit));
229 }
230 Self::new(builder.build())
231 }
232
233 #[inline]
251 pub fn check_value(&mut self, value: &Value) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
252 self.transaction(|checker| checker.check_value_at(value, 1))
253 }
254
255 #[inline]
273 pub fn check_values(&mut self, values: &MultiValues) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
274 self.transaction(|checker| checker.check_values_at(values, 1))
275 }
276
277 #[inline]
295 pub fn check_container(&mut self, value: &ValueContainer) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
296 self.transaction(|checker| match value {
297 ValueContainer::Scalar(value) => checker.check_value_at(value, 1),
298 ValueContainer::Collection(values) => checker.check_values_at(values, 1),
299 })
300 }
301
302 fn transaction<F>(&mut self, check: F) -> Result<(), MeasuredBudgetError<JsonResource, usize>>
327 where
328 F: FnOnce(&mut Self) -> Result<(), MeasuredBudgetError<JsonResource, usize>>,
329 {
330 let mut candidate = self.clone();
331 match check(&mut candidate) {
332 Ok(()) => {
333 *self = candidate;
334 Ok(())
335 }
336 Err(error) => Err(error),
337 }
338 }
339
340 #[inline]
355 fn check_value_at(&mut self, value: &Value, depth: usize) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
356 self.check_view_at(value.view(), depth)
357 }
358
359 fn check_view_at(
374 &mut self,
375 value: ValueRef<'_>,
376 depth: usize,
377 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
378 match value_ref_preflight_strategy(value) {
379 WirePreflightStrategy::Boolean => match value {
380 ValueRef::Bool(_) => self.admit(JsonMeasurement::Boolean { depth }, 1, 0),
381 _ => unreachable!("table strategy and ValueRef variant diverged"),
382 },
383 WirePreflightStrategy::BorrowedText => match value {
384 ValueRef::Char(value) => self.admit_string(depth, value.len_utf8()),
385 ValueRef::String(value) => self.admit_string(depth, value.len()),
386 #[cfg(feature = "url")]
387 ValueRef::Url(value) => self.admit_string(depth, value.as_str().len()),
388 _ => unreachable!("table strategy and ValueRef variant diverged"),
389 },
390 WirePreflightStrategy::JsonInteger => match value {
391 ValueRef::Int8(value) => self.admit_number(depth, decimal_len(value as i128)),
392 ValueRef::Int16(value) => self.admit_number(depth, decimal_len(value as i128)),
393 ValueRef::Int32(value) => self.admit_number(depth, decimal_len(value as i128)),
394 ValueRef::Int64(value) => self.admit_number(depth, decimal_len(value as i128)),
395 ValueRef::UInt8(value) => self.admit_number(depth, unsigned_decimal_len(value as u128)),
396 ValueRef::UInt16(value) => self.admit_number(depth, unsigned_decimal_len(value as u128)),
397 ValueRef::UInt32(value) => self.admit_number(depth, unsigned_decimal_len(value as u128)),
398 ValueRef::UInt64(value) => self.admit_number(depth, unsigned_decimal_len(value as u128)),
399 _ => unreachable!("table strategy and ValueRef variant diverged"),
400 },
401 WirePreflightStrategy::DecimalText => match value {
402 ValueRef::Int128(value) => self.admit_string(depth, decimal_len(value)),
403 ValueRef::UInt128(value) => self.admit_string(depth, unsigned_decimal_len(value)),
404 _ => unreachable!("table strategy and ValueRef variant diverged"),
405 },
406 WirePreflightStrategy::UnsetText => match value {
407 ValueRef::Unset(data_type) => {
408 let tag = WireDataTypeV1::from(data_type).tag();
409 self.admit_string(depth, tag.len())
410 }
411 _ => unreachable!("table strategy and ValueRef variant diverged"),
412 },
413 WirePreflightStrategy::JsonFloat => match value {
414 ValueRef::Float32(value) => {
415 self.admit_number(depth, if value.is_finite() { json_float32_len(value) } else { 1 })
416 }
417 ValueRef::Float64(value) => {
418 self.admit_number(depth, if value.is_finite() { json_float64_len(value) } else { 1 })
419 }
420 _ => unreachable!("table strategy and ValueRef variant diverged"),
421 },
422 WirePreflightStrategy::BigIntegerText => {
423 #[cfg(feature = "big-integer")]
424 {
425 match value {
426 ValueRef::BigInteger(value) => self.admit_string(depth, bigint_digits(value)),
427 _ => unreachable!("table strategy and ValueRef variant diverged"),
428 }
429 }
430 #[cfg(not(feature = "big-integer"))]
431 {
432 unreachable!("big-integer strategy is unavailable without its feature")
433 }
434 }
435 WirePreflightStrategy::DecimalObject => {
436 #[cfg(feature = "big-decimal")]
437 {
438 match value {
439 ValueRef::BigDecimal(value) => {
440 let (coefficient, scale) = value.as_bigint_and_scale();
441 self.admit_object_with_keys(
442 depth,
443 [
444 ("coefficient", bigint_digits(coefficient.as_ref()), true),
445 ("scale", decimal_len(scale as i128), false),
446 ],
447 )
448 }
449 _ => unreachable!("table strategy and ValueRef variant diverged"),
450 }
451 }
452 #[cfg(not(feature = "big-decimal"))]
453 {
454 unreachable!("big-decimal strategy is unavailable without its feature")
455 }
456 }
457 WirePreflightStrategy::TemporalText => match value {
458 #[cfg(feature = "chrono")]
459 ValueRef::Date(_) | ValueRef::Time(_) | ValueRef::DateTime(_) | ValueRef::Instant(_) => {
460 self.admit_string(depth, 0)
461 }
462 _ => unreachable!("table strategy and ValueRef variant diverged"),
463 },
464 WirePreflightStrategy::DurationObject => match value {
465 ValueRef::Duration(value) => self.admit_duration(depth, *value),
466 _ => unreachable!("table strategy and ValueRef variant diverged"),
467 },
468 WirePreflightStrategy::StringMap => match value {
469 ValueRef::StringMap(value) => self.check_string_map(value, depth),
470 _ => unreachable!("table strategy and ValueRef variant diverged"),
471 },
472 #[cfg(feature = "json")]
473 WirePreflightStrategy::JsonTree => match value {
474 ValueRef::Json(value) => self.check_json(value, depth),
475 _ => unreachable!("table strategy and ValueRef variant diverged"),
476 },
477 }
478 }
479
480 fn check_values_at(
495 &mut self,
496 values: &MultiValues,
497 depth: usize,
498 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
499 self.admit_array(depth, values.len())?;
500 let view = values.view();
501 for index in 0..view.len() {
502 if let Some(value) = view.get(index) {
503 self.check_view_at(value, depth + 1)?;
504 }
505 }
506 Ok(())
507 }
508
509 fn check_string_map(
525 &mut self,
526 map: &HashMap<String, String>,
527 depth: usize,
528 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
529 self.admit_object(depth, map.len())?;
530 for (key, value) in map {
531 self.admit_key(key)?;
532 self.admit_string(depth + 1, value.len())?;
533 }
534 Ok(())
535 }
536
537 #[cfg(feature = "json")]
553 fn check_json(&mut self, value: &JsonValue, depth: usize) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
554 let mut frames = vec![Frame::Visit(value, depth)];
555 while let Some(frame) = frames.pop() {
556 match frame {
557 Frame::Visit(value, depth) => match value {
558 JsonValue::Null => self.admit(JsonMeasurement::Null { depth }, 1, 0)?,
559 JsonValue::Bool(_) => self.admit(JsonMeasurement::Boolean { depth }, 1, 0)?,
560 JsonValue::Number(value) => {
561 let mut writer = JsonLengthWriter::default();
562 to_writer(&mut writer, value).expect("JSON Number serialization cannot fail");
563 self.admit_number(depth, writer.len)?;
564 }
565 JsonValue::String(value) => self.admit_string(depth, value.len())?,
566 JsonValue::Array(values) => {
567 self.admit_array(depth, values.len())?;
568 frames.push(Frame::Array(values.iter(), depth + 1));
569 }
570 JsonValue::Object(values) => {
571 self.admit_object(depth, values.len())?;
572 frames.push(Frame::Object(values.iter(), depth + 1));
573 }
574 },
575 Frame::Array(mut values, depth) => {
576 if let Some(value) = values.next() {
577 frames.push(Frame::Array(values, depth));
578 frames.push(Frame::Visit(value, depth));
579 }
580 }
581 Frame::Object(mut values, depth) => {
582 if let Some((key, value)) = values.next() {
583 self.admit_key(key)?;
584 frames.push(Frame::Object(values, depth));
585 frames.push(Frame::Visit(value, depth));
586 }
587 }
588 }
589 }
590 Ok(())
591 }
592
593 fn admit_string(&mut self, depth: usize, bytes: usize) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
608 let output = bytes
609 .checked_add(2)
610 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
611 self.admit(JsonMeasurement::String { depth, bytes }, output, bytes)
612 }
613
614 #[inline]
629 fn admit_number(&mut self, depth: usize, bytes: usize) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
630 self.admit(JsonMeasurement::Number { depth, bytes }, bytes, bytes)
631 }
632
633 fn admit_array(&mut self, depth: usize, items: usize) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
647 let output = items
648 .saturating_sub(1)
649 .checked_add(2)
650 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
651 self.admit(JsonMeasurement::Array { depth, items }, output, 0)
652 }
653
654 fn admit_object(&mut self, depth: usize, entries: usize) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
668 let output = entries
669 .checked_add(entries.saturating_sub(1))
670 .and_then(|value| value.checked_add(2))
671 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
672 self.admit(JsonMeasurement::Object { depth, entries }, output, 0)
673 }
674
675 #[inline]
689 fn admit_duration(
690 &mut self,
691 depth: usize,
692 value: Duration,
693 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
694 self.admit_object_with_keys(
695 depth,
696 [
697 ("secs", unsigned_decimal_len(value.as_secs() as u128), false),
698 ("nanos", unsigned_decimal_len(value.subsec_nanos() as u128), false),
699 ],
700 )
701 }
702
703 fn admit_object_with_keys<const N: usize>(
722 &mut self,
723 depth: usize,
724 fields: [(&str, usize, bool); N],
725 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
726 self.admit_object(depth, N)?;
727 for (key, value, is_string) in fields {
728 self.admit_key(key)?;
729 if is_string {
730 self.admit_string(depth + 1, value)?;
731 } else {
732 self.admit_number(depth + 1, value)?;
733 }
734 }
735 Ok(())
736 }
737
738 fn admit_key(&mut self, key: &str) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
752 self.check_point(JsonMeasurement::Key { bytes: key.len() })?;
753 let output = key
754 .len()
755 .checked_add(2)
756 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
757 self.payload_bytes = self
758 .payload_bytes
759 .checked_add(key.len())
760 .ok_or_else(|| self.quantity_error(JsonResource::PayloadBytes))?;
761 self.output_bytes = self
762 .output_bytes
763 .checked_add(output)
764 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
765 self.check_cumulative(
766 JsonResource::PayloadBytes,
767 self.payload_bytes,
768 self.limits.value_limits().max_payload_bytes(),
769 )?;
770 self.check_cumulative(
771 JsonResource::OutputBytes,
772 self.output_bytes,
773 self.limits.max_output_bytes(),
774 )
775 }
776
777 fn admit(
793 &mut self,
794 measurement: JsonMeasurement,
795 output: usize,
796 payload: usize,
797 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
798 self.check_point(measurement)?;
799 self.nodes = self
800 .nodes
801 .checked_add(1)
802 .ok_or_else(|| self.quantity_error(JsonResource::Nodes))?;
803 self.payload_bytes = self
804 .payload_bytes
805 .checked_add(payload)
806 .ok_or_else(|| self.quantity_error(JsonResource::PayloadBytes))?;
807 self.output_bytes = self
808 .output_bytes
809 .checked_add(output)
810 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
811 self.check_cumulative(JsonResource::Nodes, self.nodes, self.limits.value_limits().max_nodes())?;
812 self.check_cumulative(
813 JsonResource::PayloadBytes,
814 self.payload_bytes,
815 self.limits.value_limits().max_payload_bytes(),
816 )?;
817 self.check_cumulative(
818 JsonResource::OutputBytes,
819 self.output_bytes,
820 self.limits.max_output_bytes(),
821 )
822 }
823
824 #[inline]
838 fn check_point(&self, measurement: JsonMeasurement) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
839 self.limits.value_limits().check_point(measurement)
840 }
841
842 fn check_cumulative(
859 &self,
860 resource: JsonResource,
861 observed: usize,
862 maximum: Option<usize>,
863 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
864 match maximum {
865 Some(maximum) if observed > maximum => Err(self.lower_bound_error(resource, observed, maximum)),
866 _ => Ok(()),
867 }
868 }
869
870 #[inline]
881 fn lower_bound_error(
882 &self,
883 resource: JsonResource,
884 observed: usize,
885 maximum: usize,
886 ) -> MeasuredBudgetError<JsonResource, usize> {
887 MeasuredBudgetError::Budget(BudgetError::LimitExceeded {
888 resource,
889 observed: Observation::AtLeast(observed),
890 maximum,
891 })
892 }
893
894 #[inline]
904 fn quantity_error(&self, resource: JsonResource) -> MeasuredBudgetError<JsonResource, usize> {
905 MeasuredBudgetError::quantity(
906 resource,
907 QuantityConversionError::new(QuantityMeasurement::Usize(usize::MAX), "usize"),
908 )
909 }
910}
911
912#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
923#[must_use]
924#[inline]
925fn bigint_digits(value: &BigInt) -> usize {
926 if value.sign() == Sign::NoSign {
927 1
928 } else {
929 ((value.bits().saturating_sub(1)) / 4 + 1) as usize + usize::from(value.sign() == Sign::Minus)
930 }
931}
932
933#[cfg(test)]
934mod tests {
935 use std::collections::HashMap;
936 use std::panic::AssertUnwindSafe;
937 use std::panic::catch_unwind;
938
939 use qubit_budget::MeasuredBudgetError;
940 use qubit_budget::json::JsonEncodeLimits;
941 use qubit_budget::json::JsonMeasurement;
942 use qubit_budget::json::JsonResource;
943
944 use super::ValueWireEncodePreflight;
945 use crate::Value;
946
947 fn assert_overflow(error: MeasuredBudgetError<JsonResource, usize>) {
949 assert!(
950 matches!(error, MeasuredBudgetError::Quantity { .. }),
951 "expected a quantity overflow, got {error:?}",
952 );
953 }
954
955 #[test]
956 fn test_check_value_reports_node_counter_overflow_and_rolls_back() {
957 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
958 checker.nodes = usize::MAX;
959
960 let error = checker
961 .check_value(&Value::Bool(true))
962 .expect_err("the node counter cannot exceed usize::MAX");
963
964 assert_overflow(error);
965 assert_eq!(checker.nodes, usize::MAX);
966 assert_eq!(checker.payload_bytes, 0);
967 assert_eq!(checker.output_bytes, 0);
968 }
969
970 #[test]
971 fn test_check_value_reports_payload_counter_overflow_and_rolls_back() {
972 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
973 checker.payload_bytes = usize::MAX;
974
975 let error = checker
976 .check_value(&Value::String("x".to_owned()))
977 .expect_err("the payload counter cannot exceed usize::MAX");
978
979 assert_overflow(error);
980 assert_eq!(checker.nodes, 0);
981 assert_eq!(checker.payload_bytes, usize::MAX);
982 assert_eq!(checker.output_bytes, 0);
983 }
984
985 #[test]
986 fn test_check_value_reports_key_payload_counter_overflow_and_rolls_back() {
987 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
988 checker.payload_bytes = usize::MAX;
989
990 let error = checker
991 .check_value(&Value::StringMap(HashMap::from([("x".to_owned(), String::new())])))
992 .expect_err("the key payload counter cannot exceed usize::MAX");
993
994 assert_overflow(error);
995 assert_eq!(checker.nodes, 0);
996 assert_eq!(checker.payload_bytes, usize::MAX);
997 assert_eq!(checker.output_bytes, 0);
998 }
999
1000 #[test]
1001 fn test_check_value_reports_key_output_counter_overflow_and_rolls_back() {
1002 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1003 checker.output_bytes = usize::MAX - 3;
1004
1005 let error = checker
1006 .check_value(&Value::StringMap(HashMap::from([("x".to_owned(), String::new())])))
1007 .expect_err("the key output counter cannot exceed usize::MAX");
1008
1009 assert_overflow(error);
1010 assert_eq!(checker.nodes, 0);
1011 assert_eq!(checker.payload_bytes, 0);
1012 assert_eq!(checker.output_bytes, usize::MAX - 3);
1013 }
1014
1015 #[test]
1016 fn test_check_value_reports_output_counter_overflow_and_rolls_back() {
1017 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1018 checker.output_bytes = usize::MAX;
1019
1020 let error = checker
1021 .check_value(&Value::Bool(true))
1022 .expect_err("the output counter cannot exceed usize::MAX");
1023
1024 assert_overflow(error);
1025 assert_eq!(checker.nodes, 0);
1026 assert_eq!(checker.payload_bytes, 0);
1027 assert_eq!(checker.output_bytes, usize::MAX);
1028 }
1029
1030 #[test]
1031 fn test_admit_helpers_report_arithmetic_overflow_without_large_values() {
1032 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1033 assert_overflow(
1034 checker
1035 .admit_string(0, usize::MAX)
1036 .expect_err("string output length must not overflow"),
1037 );
1038
1039 assert_overflow(
1040 checker
1041 .admit_array(0, usize::MAX)
1042 .expect_err("array output punctuation must not overflow"),
1043 );
1044
1045 assert_overflow(
1046 checker
1047 .admit_object(0, usize::MAX)
1048 .expect_err("object output punctuation must not overflow"),
1049 );
1050
1051 checker.payload_bytes = usize::MAX;
1052 assert_overflow(
1053 checker
1054 .admit_key("x")
1055 .expect_err("key payload accumulation must not overflow"),
1056 );
1057
1058 checker.payload_bytes = 0;
1059 checker.output_bytes = usize::MAX - 2;
1060 assert_overflow(
1061 checker
1062 .admit_key("x")
1063 .expect_err("key output accumulation must not overflow"),
1064 );
1065 }
1066
1067 #[test]
1068 fn test_admit_reports_each_counter_arithmetic_overflow() {
1069 let measurement = JsonMeasurement::Boolean { depth: 0 };
1070
1071 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1072 checker.nodes = usize::MAX;
1073 assert_overflow(
1074 checker
1075 .admit(measurement, 1, 0)
1076 .expect_err("node accumulation must not overflow"),
1077 );
1078
1079 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1080 checker.payload_bytes = usize::MAX;
1081 assert_overflow(
1082 checker
1083 .admit(measurement, 1, 1)
1084 .expect_err("payload accumulation must not overflow"),
1085 );
1086
1087 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1088 checker.output_bytes = usize::MAX;
1089 assert_overflow(
1090 checker
1091 .admit(measurement, 1, 0)
1092 .expect_err("output accumulation must not overflow"),
1093 );
1094 }
1095
1096 #[test]
1097 fn test_transaction_does_not_commit_counters_when_check_panics() {
1098 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1099 let result = catch_unwind(AssertUnwindSafe(|| {
1100 checker.transaction(|candidate| {
1101 candidate.nodes = 17;
1102 candidate.payload_bytes = 23;
1103 panic!("simulated preflight panic");
1104 })
1105 }));
1106
1107 assert!(result.is_err());
1108 assert_eq!(checker.nodes, 0);
1109 assert_eq!(checker.payload_bytes, 0);
1110 assert_eq!(checker.output_bytes, 0);
1111 }
1112}