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(
252 &mut self,
253 value: &Value,
254 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
255 self.transaction(|checker| checker.check_value_at(value, 1))
256 }
257
258 #[inline]
276 pub fn check_values(
277 &mut self,
278 values: &MultiValues,
279 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
280 self.transaction(|checker| checker.check_values_at(values, 1))
281 }
282
283 #[inline]
301 pub fn check_container(
302 &mut self,
303 value: &ValueContainer,
304 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
305 self.transaction(|checker| match value {
306 ValueContainer::Scalar(value) => checker.check_value_at(value, 1),
307 ValueContainer::Collection(values) => checker.check_values_at(values, 1),
308 })
309 }
310
311 fn transaction<F>(&mut self, check: F) -> Result<(), MeasuredBudgetError<JsonResource, usize>>
336 where
337 F: FnOnce(&mut Self) -> Result<(), MeasuredBudgetError<JsonResource, usize>>,
338 {
339 let mut candidate = self.clone();
340 match check(&mut candidate) {
341 Ok(()) => {
342 *self = candidate;
343 Ok(())
344 }
345 Err(error) => Err(error),
346 }
347 }
348
349 #[inline]
364 fn check_value_at(
365 &mut self,
366 value: &Value,
367 depth: usize,
368 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
369 self.check_view_at(value.view(), depth)
370 }
371
372 fn check_view_at(
387 &mut self,
388 value: ValueRef<'_>,
389 depth: usize,
390 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
391 match value_ref_preflight_strategy(value) {
392 WirePreflightStrategy::Boolean => match value {
393 ValueRef::Bool(_) => self.admit(JsonMeasurement::Boolean { depth }, 1, 0),
394 _ => unreachable!("table strategy and ValueRef variant diverged"),
395 },
396 WirePreflightStrategy::BorrowedText => match value {
397 ValueRef::Char(value) => self.admit_string(depth, value.len_utf8()),
398 ValueRef::String(value) => self.admit_string(depth, value.len()),
399 #[cfg(feature = "url")]
400 ValueRef::Url(value) => self.admit_string(depth, value.as_str().len()),
401 _ => unreachable!("table strategy and ValueRef variant diverged"),
402 },
403 WirePreflightStrategy::JsonInteger => match value {
404 ValueRef::Int8(value) => self.admit_number(depth, decimal_len(value as i128)),
405 ValueRef::Int16(value) => self.admit_number(depth, decimal_len(value as i128)),
406 ValueRef::Int32(value) => self.admit_number(depth, decimal_len(value as i128)),
407 ValueRef::Int64(value) => self.admit_number(depth, decimal_len(value as i128)),
408 ValueRef::UInt8(value) => {
409 self.admit_number(depth, unsigned_decimal_len(value as u128))
410 }
411 ValueRef::UInt16(value) => {
412 self.admit_number(depth, unsigned_decimal_len(value as u128))
413 }
414 ValueRef::UInt32(value) => {
415 self.admit_number(depth, unsigned_decimal_len(value as u128))
416 }
417 ValueRef::UInt64(value) => {
418 self.admit_number(depth, unsigned_decimal_len(value as u128))
419 }
420 _ => unreachable!("table strategy and ValueRef variant diverged"),
421 },
422 WirePreflightStrategy::DecimalText => match value {
423 ValueRef::Int128(value) => self.admit_string(depth, decimal_len(value)),
424 ValueRef::UInt128(value) => self.admit_string(depth, unsigned_decimal_len(value)),
425 _ => unreachable!("table strategy and ValueRef variant diverged"),
426 },
427 WirePreflightStrategy::UnsetText => match value {
428 ValueRef::Unset(data_type) => {
429 let tag = WireDataTypeV1::from(data_type).tag();
430 self.admit_string(depth, tag.len())
431 }
432 _ => unreachable!("table strategy and ValueRef variant diverged"),
433 },
434 WirePreflightStrategy::JsonFloat => match value {
435 ValueRef::Float32(value) => self.admit_number(
436 depth,
437 if value.is_finite() {
438 json_float32_len(value)
439 } else {
440 1
441 },
442 ),
443 ValueRef::Float64(value) => self.admit_number(
444 depth,
445 if value.is_finite() {
446 json_float64_len(value)
447 } else {
448 1
449 },
450 ),
451 _ => unreachable!("table strategy and ValueRef variant diverged"),
452 },
453 WirePreflightStrategy::BigIntegerText => {
454 #[cfg(feature = "big-integer")]
455 {
456 match value {
457 ValueRef::BigInteger(value) => {
458 self.admit_string(depth, bigint_digits(value))
459 }
460 _ => unreachable!("table strategy and ValueRef variant diverged"),
461 }
462 }
463 #[cfg(not(feature = "big-integer"))]
464 {
465 unreachable!("big-integer strategy is unavailable without its feature")
466 }
467 }
468 WirePreflightStrategy::DecimalObject => {
469 #[cfg(feature = "big-decimal")]
470 {
471 match value {
472 ValueRef::BigDecimal(value) => {
473 let (coefficient, scale) = value.as_bigint_and_scale();
474 self.admit_object_with_keys(
475 depth,
476 [
477 ("coefficient", bigint_digits(coefficient.as_ref()), true),
478 ("scale", decimal_len(scale as i128), false),
479 ],
480 )
481 }
482 _ => unreachable!("table strategy and ValueRef variant diverged"),
483 }
484 }
485 #[cfg(not(feature = "big-decimal"))]
486 {
487 unreachable!("big-decimal strategy is unavailable without its feature")
488 }
489 }
490 WirePreflightStrategy::TemporalText => match value {
491 #[cfg(feature = "chrono")]
492 ValueRef::Date(_)
493 | ValueRef::Time(_)
494 | ValueRef::DateTime(_)
495 | ValueRef::Instant(_) => self.admit_string(depth, 0),
496 _ => unreachable!("table strategy and ValueRef variant diverged"),
497 },
498 WirePreflightStrategy::DurationObject => match value {
499 ValueRef::Duration(value) => self.admit_duration(depth, *value),
500 _ => unreachable!("table strategy and ValueRef variant diverged"),
501 },
502 WirePreflightStrategy::StringMap => match value {
503 ValueRef::StringMap(value) => self.check_string_map(value, depth),
504 _ => unreachable!("table strategy and ValueRef variant diverged"),
505 },
506 #[cfg(feature = "json")]
507 WirePreflightStrategy::JsonTree => match value {
508 ValueRef::Json(value) => self.check_json(value, depth),
509 _ => unreachable!("table strategy and ValueRef variant diverged"),
510 },
511 }
512 }
513
514 fn check_values_at(
529 &mut self,
530 values: &MultiValues,
531 depth: usize,
532 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
533 self.admit_array(depth, values.len())?;
534 let view = values.view();
535 for index in 0..view.len() {
536 if let Some(value) = view.get(index) {
537 self.check_view_at(value, depth + 1)?;
538 }
539 }
540 Ok(())
541 }
542
543 fn check_string_map(
559 &mut self,
560 map: &HashMap<String, String>,
561 depth: usize,
562 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
563 self.admit_object(depth, map.len())?;
564 for (key, value) in map {
565 self.admit_key(key)?;
566 self.admit_string(depth + 1, value.len())?;
567 }
568 Ok(())
569 }
570
571 #[cfg(feature = "json")]
587 fn check_json(
588 &mut self,
589 value: &JsonValue,
590 depth: usize,
591 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
592 let mut frames = vec![Frame::Visit(value, depth)];
593 while let Some(frame) = frames.pop() {
594 match frame {
595 Frame::Visit(value, depth) => match value {
596 JsonValue::Null => self.admit(JsonMeasurement::Null { depth }, 1, 0)?,
597 JsonValue::Bool(_) => self.admit(JsonMeasurement::Boolean { depth }, 1, 0)?,
598 JsonValue::Number(value) => {
599 let mut writer = JsonLengthWriter::default();
600 to_writer(&mut writer, value)
601 .expect("JSON Number serialization cannot fail");
602 self.admit_number(depth, writer.len)?;
603 }
604 JsonValue::String(value) => self.admit_string(depth, value.len())?,
605 JsonValue::Array(values) => {
606 self.admit_array(depth, values.len())?;
607 frames.push(Frame::Array(values.iter(), depth + 1));
608 }
609 JsonValue::Object(values) => {
610 self.admit_object(depth, values.len())?;
611 frames.push(Frame::Object(values.iter(), depth + 1));
612 }
613 },
614 Frame::Array(mut values, depth) => {
615 if let Some(value) = values.next() {
616 frames.push(Frame::Array(values, depth));
617 frames.push(Frame::Visit(value, depth));
618 }
619 }
620 Frame::Object(mut values, depth) => {
621 if let Some((key, value)) = values.next() {
622 self.admit_key(key)?;
623 frames.push(Frame::Object(values, depth));
624 frames.push(Frame::Visit(value, depth));
625 }
626 }
627 }
628 }
629 Ok(())
630 }
631
632 fn admit_string(
647 &mut self,
648 depth: usize,
649 bytes: usize,
650 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
651 let output = bytes
652 .checked_add(2)
653 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
654 self.admit(JsonMeasurement::String { depth, bytes }, output, bytes)
655 }
656
657 #[inline]
672 fn admit_number(
673 &mut self,
674 depth: usize,
675 bytes: usize,
676 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
677 self.admit(JsonMeasurement::Number { depth, bytes }, bytes, bytes)
678 }
679
680 fn admit_array(
694 &mut self,
695 depth: usize,
696 items: usize,
697 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
698 let output = items
699 .saturating_sub(1)
700 .checked_add(2)
701 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
702 self.admit(JsonMeasurement::Array { depth, items }, output, 0)
703 }
704
705 fn admit_object(
719 &mut self,
720 depth: usize,
721 entries: usize,
722 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
723 let output = entries
724 .checked_add(entries.saturating_sub(1))
725 .and_then(|value| value.checked_add(2))
726 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
727 self.admit(JsonMeasurement::Object { depth, entries }, output, 0)
728 }
729
730 #[inline]
744 fn admit_duration(
745 &mut self,
746 depth: usize,
747 value: Duration,
748 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
749 self.admit_object_with_keys(
750 depth,
751 [
752 ("secs", unsigned_decimal_len(value.as_secs() as u128), false),
753 (
754 "nanos",
755 unsigned_decimal_len(value.subsec_nanos() as u128),
756 false,
757 ),
758 ],
759 )
760 }
761
762 fn admit_object_with_keys<const N: usize>(
781 &mut self,
782 depth: usize,
783 fields: [(&str, usize, bool); N],
784 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
785 self.admit_object(depth, N)?;
786 for (key, value, is_string) in fields {
787 self.admit_key(key)?;
788 if is_string {
789 self.admit_string(depth + 1, value)?;
790 } else {
791 self.admit_number(depth + 1, value)?;
792 }
793 }
794 Ok(())
795 }
796
797 fn admit_key(&mut self, key: &str) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
811 self.check_point(JsonMeasurement::Key { bytes: key.len() })?;
812 let output = key
813 .len()
814 .checked_add(2)
815 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
816 self.payload_bytes = self
817 .payload_bytes
818 .checked_add(key.len())
819 .ok_or_else(|| self.quantity_error(JsonResource::PayloadBytes))?;
820 self.output_bytes = self
821 .output_bytes
822 .checked_add(output)
823 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
824 self.check_cumulative(
825 JsonResource::PayloadBytes,
826 self.payload_bytes,
827 self.limits.value_limits().max_payload_bytes(),
828 )?;
829 self.check_cumulative(
830 JsonResource::OutputBytes,
831 self.output_bytes,
832 self.limits.max_output_bytes(),
833 )
834 }
835
836 fn admit(
852 &mut self,
853 measurement: JsonMeasurement,
854 output: usize,
855 payload: usize,
856 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
857 self.check_point(measurement)?;
858 self.nodes = self
859 .nodes
860 .checked_add(1)
861 .ok_or_else(|| self.quantity_error(JsonResource::Nodes))?;
862 self.payload_bytes = self
863 .payload_bytes
864 .checked_add(payload)
865 .ok_or_else(|| self.quantity_error(JsonResource::PayloadBytes))?;
866 self.output_bytes = self
867 .output_bytes
868 .checked_add(output)
869 .ok_or_else(|| self.quantity_error(JsonResource::OutputBytes))?;
870 self.check_cumulative(
871 JsonResource::Nodes,
872 self.nodes,
873 self.limits.value_limits().max_nodes(),
874 )?;
875 self.check_cumulative(
876 JsonResource::PayloadBytes,
877 self.payload_bytes,
878 self.limits.value_limits().max_payload_bytes(),
879 )?;
880 self.check_cumulative(
881 JsonResource::OutputBytes,
882 self.output_bytes,
883 self.limits.max_output_bytes(),
884 )
885 }
886
887 #[inline]
901 fn check_point(
902 &self,
903 measurement: JsonMeasurement,
904 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
905 self.limits.value_limits().check_point(measurement)
906 }
907
908 fn check_cumulative(
925 &self,
926 resource: JsonResource,
927 observed: usize,
928 maximum: Option<usize>,
929 ) -> Result<(), MeasuredBudgetError<JsonResource, usize>> {
930 match maximum {
931 Some(maximum) if observed > maximum => {
932 Err(self.lower_bound_error(resource, observed, maximum))
933 }
934 _ => Ok(()),
935 }
936 }
937
938 #[inline]
949 fn lower_bound_error(
950 &self,
951 resource: JsonResource,
952 observed: usize,
953 maximum: usize,
954 ) -> MeasuredBudgetError<JsonResource, usize> {
955 MeasuredBudgetError::Budget(BudgetError::LimitExceeded {
956 resource,
957 observed: Observation::AtLeast(observed),
958 maximum,
959 })
960 }
961
962 #[inline]
972 fn quantity_error(&self, resource: JsonResource) -> MeasuredBudgetError<JsonResource, usize> {
973 MeasuredBudgetError::quantity(
974 resource,
975 QuantityConversionError::new(QuantityMeasurement::Usize(usize::MAX), "usize"),
976 )
977 }
978}
979
980#[cfg(any(feature = "big-integer", feature = "big-decimal"))]
991#[must_use]
992#[inline]
993fn bigint_digits(value: &BigInt) -> usize {
994 if value.sign() == Sign::NoSign {
995 1
996 } else {
997 ((value.bits().saturating_sub(1)) / 4 + 1) as usize
998 + usize::from(value.sign() == Sign::Minus)
999 }
1000}
1001
1002#[cfg(test)]
1003mod tests {
1004 use std::collections::HashMap;
1005 use std::panic::AssertUnwindSafe;
1006 use std::panic::catch_unwind;
1007
1008 use qubit_budget::MeasuredBudgetError;
1009 use qubit_budget::json::JsonEncodeLimits;
1010 use qubit_budget::json::JsonMeasurement;
1011 use qubit_budget::json::JsonResource;
1012
1013 use super::ValueWireEncodePreflight;
1014 use crate::Value;
1015
1016 fn assert_overflow(error: MeasuredBudgetError<JsonResource, usize>) {
1018 assert!(
1019 matches!(error, MeasuredBudgetError::Quantity { .. }),
1020 "expected a quantity overflow, got {error:?}",
1021 );
1022 }
1023
1024 #[test]
1025 fn test_check_value_reports_node_counter_overflow_and_rolls_back() {
1026 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1027 checker.nodes = usize::MAX;
1028
1029 let error = checker
1030 .check_value(&Value::Bool(true))
1031 .expect_err("the node counter cannot exceed usize::MAX");
1032
1033 assert_overflow(error);
1034 assert_eq!(checker.nodes, usize::MAX);
1035 assert_eq!(checker.payload_bytes, 0);
1036 assert_eq!(checker.output_bytes, 0);
1037 }
1038
1039 #[test]
1040 fn test_check_value_reports_payload_counter_overflow_and_rolls_back() {
1041 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1042 checker.payload_bytes = usize::MAX;
1043
1044 let error = checker
1045 .check_value(&Value::String("x".to_owned()))
1046 .expect_err("the payload counter cannot exceed usize::MAX");
1047
1048 assert_overflow(error);
1049 assert_eq!(checker.nodes, 0);
1050 assert_eq!(checker.payload_bytes, usize::MAX);
1051 assert_eq!(checker.output_bytes, 0);
1052 }
1053
1054 #[test]
1055 fn test_check_value_reports_key_payload_counter_overflow_and_rolls_back() {
1056 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1057 checker.payload_bytes = usize::MAX;
1058
1059 let error = checker
1060 .check_value(&Value::StringMap(HashMap::from([(
1061 "x".to_owned(),
1062 String::new(),
1063 )])))
1064 .expect_err("the key payload counter cannot exceed usize::MAX");
1065
1066 assert_overflow(error);
1067 assert_eq!(checker.nodes, 0);
1068 assert_eq!(checker.payload_bytes, usize::MAX);
1069 assert_eq!(checker.output_bytes, 0);
1070 }
1071
1072 #[test]
1073 fn test_check_value_reports_key_output_counter_overflow_and_rolls_back() {
1074 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1075 checker.output_bytes = usize::MAX - 3;
1076
1077 let error = checker
1078 .check_value(&Value::StringMap(HashMap::from([(
1079 "x".to_owned(),
1080 String::new(),
1081 )])))
1082 .expect_err("the key output counter cannot exceed usize::MAX");
1083
1084 assert_overflow(error);
1085 assert_eq!(checker.nodes, 0);
1086 assert_eq!(checker.payload_bytes, 0);
1087 assert_eq!(checker.output_bytes, usize::MAX - 3);
1088 }
1089
1090 #[test]
1091 fn test_check_value_reports_output_counter_overflow_and_rolls_back() {
1092 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1093 checker.output_bytes = usize::MAX;
1094
1095 let error = checker
1096 .check_value(&Value::Bool(true))
1097 .expect_err("the output counter cannot exceed usize::MAX");
1098
1099 assert_overflow(error);
1100 assert_eq!(checker.nodes, 0);
1101 assert_eq!(checker.payload_bytes, 0);
1102 assert_eq!(checker.output_bytes, usize::MAX);
1103 }
1104
1105 #[test]
1106 fn test_admit_helpers_report_arithmetic_overflow_without_large_values() {
1107 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1108 assert_overflow(
1109 checker
1110 .admit_string(0, usize::MAX)
1111 .expect_err("string output length must not overflow"),
1112 );
1113
1114 assert_overflow(
1115 checker
1116 .admit_array(0, usize::MAX)
1117 .expect_err("array output punctuation must not overflow"),
1118 );
1119
1120 assert_overflow(
1121 checker
1122 .admit_object(0, usize::MAX)
1123 .expect_err("object output punctuation must not overflow"),
1124 );
1125
1126 checker.payload_bytes = usize::MAX;
1127 assert_overflow(
1128 checker
1129 .admit_key("x")
1130 .expect_err("key payload accumulation must not overflow"),
1131 );
1132
1133 checker.payload_bytes = 0;
1134 checker.output_bytes = usize::MAX - 2;
1135 assert_overflow(
1136 checker
1137 .admit_key("x")
1138 .expect_err("key output accumulation must not overflow"),
1139 );
1140 }
1141
1142 #[test]
1143 fn test_admit_reports_each_counter_arithmetic_overflow() {
1144 let measurement = JsonMeasurement::Boolean { depth: 0 };
1145
1146 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1147 checker.nodes = usize::MAX;
1148 assert_overflow(
1149 checker
1150 .admit(measurement, 1, 0)
1151 .expect_err("node accumulation must not overflow"),
1152 );
1153
1154 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1155 checker.payload_bytes = usize::MAX;
1156 assert_overflow(
1157 checker
1158 .admit(measurement, 1, 1)
1159 .expect_err("payload accumulation must not overflow"),
1160 );
1161
1162 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1163 checker.output_bytes = usize::MAX;
1164 assert_overflow(
1165 checker
1166 .admit(measurement, 1, 0)
1167 .expect_err("output accumulation must not overflow"),
1168 );
1169 }
1170
1171 #[test]
1172 fn test_transaction_does_not_commit_counters_when_check_panics() {
1173 let mut checker = ValueWireEncodePreflight::new(JsonEncodeLimits::new());
1174 let result = catch_unwind(AssertUnwindSafe(|| {
1175 checker.transaction(|candidate| {
1176 candidate.nodes = 17;
1177 candidate.payload_bytes = 23;
1178 panic!("simulated preflight panic");
1179 })
1180 }));
1181
1182 assert!(result.is_err());
1183 assert_eq!(checker.nodes, 0);
1184 assert_eq!(checker.payload_bytes, 0);
1185 assert_eq!(checker.output_bytes, 0);
1186 }
1187}