1use crate::layout::{FieldKind, ListElementKind, OffsetTable, SchemaLayout};
62use crate::schema_canonical::{Field, Schema, TypeRepr};
63use thiserror::Error;
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq)]
70pub struct Region {
71 pub start: usize,
74 pub end: usize,
76}
77
78impl Region {
79 pub fn new(start: usize, end: usize) -> Result<Self, VerifyError> {
83 if start > end {
84 return Err(VerifyError::DegenerateRegion { start, end });
85 }
86 Ok(Self { start, end })
87 }
88
89 fn contains_span(&self, off: usize, len: usize) -> bool {
93 match off.checked_add(len) {
94 Some(end) => off >= self.start && end <= self.end,
95 None => false,
96 }
97 }
98}
99
100#[derive(Debug, Clone, Copy, PartialEq, Eq)]
105pub enum RegionTag {
106 Const,
108 In,
110 Out,
113 Scratch,
115}
116
117impl RegionTag {
118 pub fn label(self) -> &'static str {
120 match self {
121 RegionTag::Const => "const",
122 RegionTag::In => "in",
123 RegionTag::Out => "out",
124 RegionTag::Scratch => "scratch",
125 }
126 }
127}
128
129#[derive(Debug, Clone, Copy)]
146pub struct MultiRegion {
147 regions: [(RegionTag, Region); 4],
148}
149
150impl MultiRegion {
151 pub fn new(
155 const_data: (usize, usize),
156 in_buf: (usize, usize),
157 out_buf: (usize, usize),
158 scratch: (usize, usize),
159 ) -> Result<Self, VerifyError> {
160 Ok(Self {
161 regions: [
162 (RegionTag::Const, Region::new(const_data.0, const_data.1)?),
163 (RegionTag::In, Region::new(in_buf.0, in_buf.1)?),
164 (RegionTag::Out, Region::new(out_buf.0, out_buf.1)?),
165 (RegionTag::Scratch, Region::new(scratch.0, scratch.1)?),
166 ],
167 })
168 }
169
170 fn max_end(&self) -> usize {
174 self.regions.iter().map(|(_, r)| r.end).max().unwrap_or(0)
175 }
176
177 fn classify_span(&self, off: usize, len: usize) -> Option<RegionTag> {
181 for (tag, region) in &self.regions {
182 if region.contains_span(off, len) {
186 return Some(*tag);
187 }
188 }
189 None
190 }
191}
192
193#[derive(Debug, Error, Clone, PartialEq, Eq)]
198pub enum VerifyError {
199 #[error("degenerate region: start {start} > end {end}")]
201 DegenerateRegion {
202 start: usize,
204 end: usize,
206 },
207 #[error("buffer of {have} bytes is shorter than region end {end}")]
210 BufferShorterThanRegion {
211 have: usize,
213 end: usize,
215 },
216 #[error("fixed-area slot for `{field}` at {offset}..+{size} escapes region [{start}, {end})")]
219 FixedSlotOutOfRegion {
220 field: String,
222 offset: usize,
224 size: usize,
226 start: usize,
228 end: usize,
230 },
231 #[error("offset {offset}..+{len} for `{field}` ({what}) escapes region [{start}, {end})")]
235 OutOfRegion {
236 field: String,
238 what: &'static str,
240 offset: usize,
242 len: usize,
244 start: usize,
246 end: usize,
248 },
249 #[error("arithmetic overflow computing span for `{field}` ({what})")]
252 SpanOverflow {
253 field: String,
255 what: &'static str,
257 },
258 #[error("verifier does not model type `{ty}` in field `{field}`")]
262 UnsupportedType {
263 field: String,
265 ty: &'static str,
267 },
268 #[error(
274 "offset {offset}..+{len} for `{field}` ({what}) fits no arena region \
275 (const, in, out, scratch are all disjoint windows)"
276 )]
277 NoRegion {
278 field: String,
280 what: &'static str,
282 offset: usize,
284 len: usize,
286 },
287 #[error("verifier recursion depth exceeded ({depth}) at field `{field}`")]
292 DepthExceeded {
293 field: String,
295 depth: usize,
297 },
298}
299
300#[derive(Debug, Clone, Copy)]
308enum Bounds {
309 Single(Region),
313 Multi(MultiRegion),
317}
318
319impl Bounds {
320 fn required_len(&self) -> usize {
322 match self {
323 Bounds::Single(r) => r.end,
324 Bounds::Multi(m) => m.max_end(),
325 }
326 }
327
328 fn require_span(
332 &self,
333 field: &str,
334 what: &'static str,
335 off: usize,
336 len: usize,
337 ) -> Result<(), VerifyError> {
338 if off.checked_add(len).is_none() {
339 return Err(VerifyError::SpanOverflow {
340 field: field.to_string(),
341 what,
342 });
343 }
344 match self {
345 Bounds::Single(region) => {
346 if region.contains_span(off, len) {
347 Ok(())
348 } else {
349 Err(VerifyError::OutOfRegion {
350 field: field.to_string(),
351 what,
352 offset: off,
353 len,
354 start: region.start,
355 end: region.end,
356 })
357 }
358 }
359 Bounds::Multi(multi) => {
360 if multi.classify_span(off, len).is_some() {
361 Ok(())
362 } else {
363 Err(VerifyError::NoRegion {
364 field: field.to_string(),
365 what,
366 offset: off,
367 len,
368 })
369 }
370 }
371 }
372 }
373
374 fn require_fixed_slot(&self, field: &str, off: usize, size: usize) -> Result<(), VerifyError> {
379 match self {
380 Bounds::Single(region) => {
381 if region.contains_span(off, size) {
382 Ok(())
383 } else {
384 Err(VerifyError::FixedSlotOutOfRegion {
385 field: field.to_string(),
386 offset: off,
387 size,
388 start: region.start,
389 end: region.end,
390 })
391 }
392 }
393 Bounds::Multi(multi) => {
394 if multi.classify_span(off, size).is_some() {
395 Ok(())
396 } else {
397 Err(VerifyError::NoRegion {
398 field: field.to_string(),
399 what: "record fixed slot",
400 offset: off,
401 len: size,
402 })
403 }
404 }
405 }
406 }
407}
408
409const MAX_DEPTH: usize = 64;
413
414pub fn verify_record(
429 bytes: &[u8],
430 layout: &OffsetTable,
431 fields: &[Field],
432 record_base: usize,
433 region: Region,
434) -> Result<(), VerifyError> {
435 verify_record_with_bounds(bytes, layout, fields, record_base, Bounds::Single(region))
436}
437
438pub fn verify_record_multi(
449 bytes: &[u8],
450 layout: &OffsetTable,
451 fields: &[Field],
452 record_base: usize,
453 multi: MultiRegion,
454) -> Result<(), VerifyError> {
455 verify_record_with_bounds(bytes, layout, fields, record_base, Bounds::Multi(multi))
456}
457
458fn verify_record_with_bounds(
459 bytes: &[u8],
460 layout: &OffsetTable,
461 fields: &[Field],
462 record_base: usize,
463 bounds: Bounds,
464) -> Result<(), VerifyError> {
465 let required = bounds.required_len();
466 if bytes.len() < required {
467 return Err(VerifyError::BufferShorterThanRegion {
468 have: bytes.len(),
469 end: required,
470 });
471 }
472 verify_record_inner(bytes, layout, fields, record_base, bounds, 0)
473}
474
475pub fn verify_value_at(
493 bytes: &[u8],
494 ty: &TypeRepr,
495 list_element: Option<ListElementKind>,
496 root: usize,
497 region: Region,
498) -> Result<(), VerifyError> {
499 verify_value_at_with_bounds(bytes, ty, list_element, root, Bounds::Single(region))
500}
501
502pub fn verify_value_at_multi(
510 bytes: &[u8],
511 ty: &TypeRepr,
512 list_element: Option<ListElementKind>,
513 root: usize,
514 multi: MultiRegion,
515) -> Result<(), VerifyError> {
516 verify_value_at_with_bounds(bytes, ty, list_element, root, Bounds::Multi(multi))
517}
518
519fn verify_value_at_with_bounds(
520 bytes: &[u8],
521 ty: &TypeRepr,
522 list_element: Option<ListElementKind>,
523 root: usize,
524 bounds: Bounds,
525) -> Result<(), VerifyError> {
526 let required = bounds.required_len();
527 if bytes.len() < required {
528 return Err(VerifyError::BufferShorterThanRegion {
529 have: bytes.len(),
530 end: required,
531 });
532 }
533 verify_pointer_target(bytes, "<in-place root>", ty, list_element, root, bounds, 0)
534}
535
536fn verify_record_inner(
537 bytes: &[u8],
538 layout: &OffsetTable,
539 fields: &[Field],
540 record_base: usize,
541 bounds: Bounds,
542 depth: usize,
543) -> Result<(), VerifyError> {
544 if depth >= MAX_DEPTH {
545 return Err(VerifyError::DepthExceeded {
546 field: "<record>".to_string(),
547 depth: MAX_DEPTH,
548 });
549 }
550 bounds.require_fixed_slot("<root>", record_base, layout.root_size)?;
555 for fo in &layout.fields {
556 let slot_abs =
557 record_base
558 .checked_add(fo.offset)
559 .ok_or_else(|| VerifyError::SpanOverflow {
560 field: fo.name.clone(),
561 what: "fixed slot offset",
562 })?;
563 bounds.require_fixed_slot(&fo.name, slot_abs, fo.size)?;
564 let FieldKind::PointerIndirect { .. } = fo.kind else {
565 continue;
568 };
569 let declared = fields.iter().find(|f| f.name == fo.name);
573 let ty = match declared {
574 Some(f) => &f.ty,
575 None => {
576 return Err(VerifyError::UnsupportedType {
577 field: fo.name.clone(),
578 ty: "<missing schema field>",
579 });
580 }
581 };
582 let ptr = read_u32(bytes, slot_abs, &fo.name, "pointer slot", bounds)?;
586 verify_pointer_target(bytes, &fo.name, ty, fo.list_element, ptr, bounds, depth)?;
587 }
588 Ok(())
589}
590
591fn verify_pointer_target(
599 bytes: &[u8],
600 field: &str,
601 ty: &TypeRepr,
602 list_element: Option<ListElementKind>,
603 ptr: usize,
604 bounds: Bounds,
605 depth: usize,
606) -> Result<(), VerifyError> {
607 match ty {
608 TypeRepr::String => {
609 let len = read_u32(bytes, ptr, field, "length prefix", bounds)?;
611 let payload = ptr
612 .checked_add(4)
613 .ok_or_else(|| VerifyError::SpanOverflow {
614 field: field.to_string(),
615 what: "string payload start",
616 })?;
617 bounds.require_span(field, "string payload", payload, len)
618 }
619 TypeRepr::Schema { schema } => {
620 let sub_layout =
624 SchemaLayout::offsets_for(schema).map_err(|_| VerifyError::UnsupportedType {
625 field: field.to_string(),
626 ty: "Schema (unlayoutable)",
627 })?;
628 verify_record_inner(bytes, &sub_layout, &schema.fields, ptr, bounds, depth + 1)
629 }
630 TypeRepr::List { element } => {
631 verify_list_target(bytes, field, element, list_element, ptr, bounds, depth)
632 }
633 TypeRepr::Option { .. } | TypeRepr::Result { .. } | TypeRepr::Enum { .. } => {
634 verify_variant_target(bytes, field, ty, ptr, bounds, depth)
635 }
636 other => Err(VerifyError::UnsupportedType {
637 field: field.to_string(),
638 ty: type_label(other),
639 }),
640 }
641}
642
643fn verify_variant_target(
644 bytes: &[u8],
645 field: &str,
646 ty: &TypeRepr,
647 ptr: usize,
648 bounds: Bounds,
649 depth: usize,
650) -> Result<(), VerifyError> {
651 if depth >= MAX_DEPTH {
652 return Err(VerifyError::DepthExceeded {
653 field: field.to_string(),
654 depth: MAX_DEPTH,
655 });
656 }
657 bounds.require_span(field, "variant tag", ptr, 1)?;
658 let tag = bytes[ptr];
659 let Some(payload_ty) =
660 variant_payload_type(ty, tag).ok_or_else(|| VerifyError::UnsupportedType {
661 field: field.to_string(),
662 ty: type_label(ty),
663 })?
664 else {
665 return Ok(());
666 };
667 let (slot_size, slot_align) = variant_payload_slot_layout(&payload_ty);
668 let slot = align_up(
669 ptr.checked_add(1)
670 .ok_or_else(|| VerifyError::SpanOverflow {
671 field: field.to_string(),
672 what: "variant payload slot start",
673 })?,
674 slot_align,
675 )
676 .ok_or_else(|| VerifyError::SpanOverflow {
677 field: field.to_string(),
678 what: "variant payload slot alignment",
679 })?;
680 bounds.require_span(field, "variant payload slot", slot, slot_size)?;
681 match &payload_ty {
682 TypeRepr::Unit | TypeRepr::Bool | TypeRepr::Int | TypeRepr::Float => Ok(()),
683 TypeRepr::String
684 | TypeRepr::Schema { .. }
685 | TypeRepr::List { .. }
686 | TypeRepr::Option { .. }
687 | TypeRepr::Result { .. }
688 | TypeRepr::Enum { .. } => {
689 let target = read_u32(bytes, slot, field, "variant payload pointer", bounds)?;
690 verify_pointer_target(
691 bytes,
692 field,
693 &payload_ty,
694 list_kind_for_list_type(&payload_ty),
695 target,
696 bounds,
697 depth + 1,
698 )
699 }
700 TypeRepr::Closure { .. } => Err(VerifyError::UnsupportedType {
701 field: field.to_string(),
702 ty: "Closure",
703 }),
704 }
705}
706
707fn variant_payload_type(ty: &TypeRepr, tag: u8) -> Option<Option<TypeRepr>> {
708 match ty {
709 TypeRepr::Option { inner } => match tag {
710 0 => Some(None),
711 1 => Some(Some(inner.as_ref().clone())),
712 _ => None,
713 },
714 TypeRepr::Result { ok, err } => match tag {
715 0 => Some(Some(ok.as_ref().clone())),
716 1 => Some(Some(err.as_ref().clone())),
717 _ => None,
718 },
719 TypeRepr::Enum { name, variants } => variants
720 .iter()
721 .find(|variant| variant.tag == tag)
722 .map(|variant| {
723 variant.payload_schema(name).map(|schema| TypeRepr::Schema {
724 schema: Box::new(schema),
725 })
726 }),
727 _ => None,
728 }
729}
730
731fn variant_payload_slot_layout(ty: &TypeRepr) -> (usize, usize) {
732 match ty {
733 TypeRepr::Unit | TypeRepr::Bool => (1, 1),
734 TypeRepr::Int | TypeRepr::Float => (8, 8),
735 _ => (4, 4),
736 }
737}
738
739fn verify_list_target(
744 bytes: &[u8],
745 field: &str,
746 element: &TypeRepr,
747 list_element: Option<ListElementKind>,
748 ptr: usize,
749 bounds: Bounds,
750 depth: usize,
751) -> Result<(), VerifyError> {
752 let count = read_u32(bytes, ptr, field, "list length prefix", bounds)?;
753 let entries_start = ptr
754 .checked_add(4)
755 .ok_or_else(|| VerifyError::SpanOverflow {
756 field: field.to_string(),
757 what: "list entries start",
758 })?;
759 match list_element {
760 Some(ListElementKind::InlineFixed {
761 elem_size,
762 elem_align,
763 }) => {
764 let payload_start =
766 align_up(entries_start, elem_align).ok_or_else(|| VerifyError::SpanOverflow {
767 field: field.to_string(),
768 what: "inline list payload start",
769 })?;
770 let byte_len =
771 count
772 .checked_mul(elem_size)
773 .ok_or_else(|| VerifyError::SpanOverflow {
774 field: field.to_string(),
775 what: "inline list byte length",
776 })?;
777 bounds.require_span(field, "inline list payload", payload_start, byte_len)
778 }
779 Some(ListElementKind::PointerArray { .. }) => {
780 for i in 0..count {
783 let entry_off = entries_start
784 .checked_add(i.checked_mul(4).ok_or_else(|| VerifyError::SpanOverflow {
785 field: field.to_string(),
786 what: "list entry index",
787 })?)
788 .ok_or_else(|| VerifyError::SpanOverflow {
789 field: field.to_string(),
790 what: "list entry offset",
791 })?;
792 let entry_ptr = read_u32(bytes, entry_off, field, "list entry pointer", bounds)?;
793 verify_pointer_target(
796 bytes,
797 field,
798 element,
799 element_list_kind(element),
800 entry_ptr,
801 bounds,
802 depth + 1,
803 )?;
804 }
805 Ok(())
806 }
807 None => {
808 Err(VerifyError::UnsupportedType {
811 field: field.to_string(),
812 ty: "List (missing element layout)",
813 })
814 }
815 }
816}
817
818fn element_list_kind(element: &TypeRepr) -> Option<ListElementKind> {
824 let TypeRepr::List { .. } = element else {
825 return None;
828 };
829 list_kind_for_list_type(element)
830}
831
832fn list_kind_for_list_type(ty: &TypeRepr) -> Option<ListElementKind> {
833 let TypeRepr::List { .. } = ty else {
834 return None;
835 };
836 let probe = Schema {
837 name: "<probe>".to_string(),
838 generics: vec![],
839 is_tuple: false,
840 fields: vec![Field {
841 name: "f".to_string(),
842 ty: ty.clone(),
843 default: None,
844 }],
845 };
846 SchemaLayout::offsets_for(&probe)
847 .ok()
848 .and_then(|t| t.fields.into_iter().next())
849 .and_then(|fo| fo.list_element)
850}
851
852fn read_u32(
855 bytes: &[u8],
856 off: usize,
857 field: &str,
858 what: &'static str,
859 bounds: Bounds,
860) -> Result<usize, VerifyError> {
861 bounds.require_span(field, what, off, 4)?;
862 let mut buf = [0u8; 4];
863 buf.copy_from_slice(&bytes[off..off + 4]);
864 Ok(u32::from_le_bytes(buf) as usize)
865}
866
867fn align_up(off: usize, align: usize) -> Option<usize> {
870 if align <= 1 {
871 return Some(off);
872 }
873 off.checked_next_multiple_of(align)
874}
875
876fn type_label(ty: &TypeRepr) -> &'static str {
878 match ty {
879 TypeRepr::Unit => "Unit",
880 TypeRepr::Bool => "Bool",
881 TypeRepr::Int => "Int",
882 TypeRepr::Float => "Float",
883 TypeRepr::String => "String",
884 TypeRepr::List { .. } => "List",
885 TypeRepr::Option { .. } => "Option",
886 TypeRepr::Result { .. } => "Result",
887 TypeRepr::Enum { .. } => "Enum",
888 TypeRepr::Schema { .. } => "Schema",
889 TypeRepr::Closure { .. } => "Closure",
890 }
891}
892
893#[cfg(test)]
894mod tests {
895 use super::*;
896 use crate::buffer::BufferBuilder;
897 use crate::layout::SchemaLayout;
898 use crate::schema_canonical::{Field, Schema};
899
900 fn field(name: &str, ty: TypeRepr) -> Field {
901 Field {
902 name: name.into(),
903 ty,
904 default: None,
905 }
906 }
907
908 fn list(inner: TypeRepr) -> TypeRepr {
909 TypeRepr::List {
910 element: Box::new(inner),
911 }
912 }
913
914 fn user_schema() -> Schema {
917 Schema {
918 name: "User".into(),
919 generics: vec![],
920 is_tuple: false,
921 fields: vec![field("name", TypeRepr::String), field("age", TypeRepr::Int)],
922 }
923 }
924
925 fn full_region(bytes: &[u8]) -> Region {
926 Region::new(0, bytes.len()).expect("region")
927 }
928
929 #[test]
932 fn legal_string_int_record_verifies() {
933 let schema = user_schema();
934 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
935 let mut b = BufferBuilder::new(&layout, &schema.fields);
936 b.write_string("name", "ada").unwrap();
937 b.write_int("age", 36).unwrap();
938 let bytes = b.finish();
939 verify_record(&bytes, &layout, &schema.fields, 0, full_region(&bytes))
940 .expect("legal buffer must verify");
941 }
942
943 #[test]
944 fn legal_list_string_record_verifies() {
945 let schema = Schema {
946 name: "Tags".into(),
947 generics: vec![],
948 is_tuple: false,
949 fields: vec![field("tags", list(TypeRepr::String))],
950 };
951 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
952 let mut b = BufferBuilder::new(&layout, &schema.fields);
953 b.write_list_string("tags", &["a", "bb", "", "中文"])
954 .unwrap();
955 let bytes = b.finish();
956 verify_record(&bytes, &layout, &schema.fields, 0, full_region(&bytes))
957 .expect("legal list-string buffer must verify");
958 }
959
960 #[test]
961 fn legal_list_int_record_verifies() {
962 let schema = Schema {
963 name: "Nums".into(),
964 generics: vec![],
965 is_tuple: false,
966 fields: vec![field("nums", list(TypeRepr::Int))],
967 };
968 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
969 let mut b = BufferBuilder::new(&layout, &schema.fields);
970 b.write_list_int("nums", &[1, -2, 3, i64::MIN, i64::MAX])
971 .unwrap();
972 let bytes = b.finish();
973 verify_record(&bytes, &layout, &schema.fields, 0, full_region(&bytes))
974 .expect("legal list-int buffer must verify");
975 }
976
977 #[test]
978 fn legal_nested_schema_record_verifies() {
979 let addr = Schema {
980 name: "Addr".into(),
981 generics: vec![],
982 is_tuple: false,
983 fields: vec![field("city", TypeRepr::String), field("zip", TypeRepr::Int)],
984 };
985 let usr = Schema {
986 name: "Usr".into(),
987 generics: vec![],
988 is_tuple: false,
989 fields: vec![
990 field(
991 "addr",
992 TypeRepr::Schema {
993 schema: Box::new(addr.clone()),
994 },
995 ),
996 field("name", TypeRepr::String),
997 ],
998 };
999 let usr_layout = SchemaLayout::offsets_for(&usr).expect("usr layout");
1000 let addr_layout = SchemaLayout::offsets_for(&addr).expect("addr layout");
1001 let mut b = BufferBuilder::new(&usr_layout, &usr.fields);
1002 let mut sub = b.sub_record("addr", &addr_layout, &addr.fields).unwrap();
1003 sub.write_string("city", "BJ").unwrap();
1004 sub.write_int("zip", 100000).unwrap();
1005 b.finish_sub_record("addr", sub).unwrap();
1006 b.write_string("name", "Bob").unwrap();
1007 let bytes = b.finish();
1008 verify_record(&bytes, &usr_layout, &usr.fields, 0, full_region(&bytes))
1009 .expect("legal nested-schema buffer must verify");
1010 }
1011
1012 #[test]
1015 fn out_of_range_string_pointer_rejected() {
1016 let schema = user_schema();
1018 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
1019 let mut b = BufferBuilder::new(&layout, &schema.fields);
1020 b.write_string("name", "ada").unwrap();
1021 b.write_int("age", 36).unwrap();
1022 let mut bytes = b.finish();
1023 let bogus = (bytes.len() as u32 + 9999).to_le_bytes();
1025 bytes[0..4].copy_from_slice(&bogus);
1026 let region = full_region(&bytes);
1027 let err = verify_record(&bytes, &layout, &schema.fields, 0, region)
1028 .expect_err("bogus string pointer must be rejected");
1029 assert!(
1030 matches!(
1031 err,
1032 VerifyError::OutOfRegion {
1033 what: "length prefix",
1034 ..
1035 }
1036 ),
1037 "got {err:?}"
1038 );
1039 }
1040
1041 #[test]
1042 fn overlong_string_len_prefix_rejected() {
1043 let schema = user_schema();
1046 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
1047 let mut b = BufferBuilder::new(&layout, &schema.fields);
1048 b.write_string("name", "ada").unwrap();
1049 b.write_int("age", 36).unwrap();
1050 let mut bytes = b.finish();
1051 let ptr = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
1052 bytes[ptr..ptr + 4].copy_from_slice(&0xFFFF_F000u32.to_le_bytes());
1054 let region = full_region(&bytes);
1055 let err = verify_record(&bytes, &layout, &schema.fields, 0, region)
1056 .expect_err("overlong string len must be rejected");
1057 assert!(
1058 matches!(
1059 err,
1060 VerifyError::OutOfRegion {
1061 what: "string payload",
1062 ..
1063 }
1064 ),
1065 "got {err:?}"
1066 );
1067 }
1068
1069 #[test]
1070 fn cross_region_pointer_rejected() {
1071 let schema = user_schema();
1076 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
1077 let mut b = BufferBuilder::new(&layout, &schema.fields);
1078 b.write_string("name", "ada").unwrap();
1079 b.write_int("age", 36).unwrap();
1080 let bytes = b.finish();
1081 let ptr = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
1082 let region = Region::new(0, ptr + 4).expect("region");
1084 let err = verify_record(&bytes, &layout, &schema.fields, 0, region)
1085 .expect_err("payload outside region must be rejected");
1086 assert!(
1087 matches!(
1088 err,
1089 VerifyError::OutOfRegion {
1090 what: "string payload",
1091 ..
1092 }
1093 ),
1094 "got {err:?}"
1095 );
1096 }
1097
1098 #[test]
1099 fn root_record_past_region_rejected() {
1100 let schema = user_schema();
1102 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
1103 let mut b = BufferBuilder::new(&layout, &schema.fields);
1104 b.write_string("name", "ada").unwrap();
1105 b.write_int("age", 36).unwrap();
1106 let bytes = b.finish();
1107 let region = Region::new(0, 4).expect("region"); let err = verify_record(&bytes, &layout, &schema.fields, 0, region)
1109 .expect_err("undersized region must reject the root record");
1110 assert!(
1111 matches!(err, VerifyError::FixedSlotOutOfRegion { ref field, .. } if field == "<root>"),
1112 "got {err:?}"
1113 );
1114 }
1115
1116 #[test]
1117 fn list_entry_pointer_out_of_range_rejected() {
1118 let schema = Schema {
1120 name: "Tags".into(),
1121 generics: vec![],
1122 is_tuple: false,
1123 fields: vec![field("tags", list(TypeRepr::String))],
1124 };
1125 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
1126 let mut b = BufferBuilder::new(&layout, &schema.fields);
1127 b.write_list_string("tags", &["a", "bb"]).unwrap();
1128 let mut bytes = b.finish();
1129 let header = u32::from_le_bytes(bytes[0..4].try_into().unwrap()) as usize;
1131 let off0_pos = header + 4;
1132 let bogus = (bytes.len() as u32 + 5000).to_le_bytes();
1133 bytes[off0_pos..off0_pos + 4].copy_from_slice(&bogus);
1134 let region = full_region(&bytes);
1135 let err = verify_record(&bytes, &layout, &schema.fields, 0, region)
1136 .expect_err("bogus list entry pointer must reject");
1137 assert!(
1138 matches!(err, VerifyError::OutOfRegion { .. }),
1139 "got {err:?}"
1140 );
1141 }
1142
1143 #[test]
1144 fn buffer_shorter_than_region_rejected() {
1145 let schema = user_schema();
1146 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
1147 let bytes = vec![0u8; 4];
1148 let region = Region::new(0, 64).expect("region");
1149 let err = verify_record(&bytes, &layout, &schema.fields, 0, region)
1150 .expect_err("region beyond slice must reject");
1151 assert!(matches!(
1152 err,
1153 VerifyError::BufferShorterThanRegion { have: 4, end: 64 }
1154 ));
1155 }
1156
1157 #[test]
1158 fn degenerate_region_rejected() {
1159 let err = Region::new(10, 4).expect_err("inverted region");
1160 assert!(matches!(
1161 err,
1162 VerifyError::DegenerateRegion { start: 10, end: 4 }
1163 ));
1164 }
1165
1166 fn list_list_int_buffer() -> (Vec<u8>, Option<ListElementKind>, usize) {
1173 let schema = Schema {
1174 name: "Ret".into(),
1175 generics: vec![],
1176 is_tuple: false,
1177 fields: vec![field("value", list(list(TypeRepr::Int)))],
1178 };
1179 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
1180 let mut b = BufferBuilder::new(&layout, &schema.fields);
1181 let rows: Vec<crate::value::Value> = vec![
1184 crate::value::Value::List(std::sync::Arc::new(vec![
1185 crate::value::Value::Int(1),
1186 crate::value::Value::Int(2),
1187 ])),
1188 crate::value::Value::List(std::sync::Arc::new(vec![crate::value::Value::Int(3)])),
1189 crate::value::Value::List(std::sync::Arc::new(vec![])),
1190 ];
1191 crate::buffer::write_nested_scalar_list(&mut b, "value", &TypeRepr::Int, &rows)
1192 .expect("write nested list");
1193 let bytes = b.finish();
1194 let fo = &layout.fields[0];
1198 let mut slot = [0u8; 4];
1199 slot.copy_from_slice(&bytes[fo.offset..fo.offset + 4]);
1200 let header_off = u32::from_le_bytes(slot) as usize;
1201 (bytes, fo.list_element, header_off)
1202 }
1203
1204 #[test]
1205 fn inplace_list_list_verifies_clean() {
1206 let (bytes, list_element, root) = list_list_int_buffer();
1207 let region = Region::new(0, bytes.len()).expect("region");
1208 verify_value_at(
1209 &bytes,
1210 &list(list(TypeRepr::Int)),
1211 list_element,
1212 root,
1213 region,
1214 )
1215 .expect("a legal in-place List<List<Int>> root must verify");
1216 }
1217
1218 #[test]
1219 fn inplace_corrupt_inner_pointer_rejected() {
1220 let (mut bytes, list_element, root) = list_list_int_buffer();
1223 let off0_pos = root + 4;
1226 let bogus = (bytes.len() as u32 + 4096).to_le_bytes();
1227 bytes[off0_pos..off0_pos + 4].copy_from_slice(&bogus);
1228 let region = Region::new(0, bytes.len()).expect("region");
1229 let err = verify_value_at(
1230 &bytes,
1231 &list(list(TypeRepr::Int)),
1232 list_element,
1233 root,
1234 region,
1235 )
1236 .expect_err("a corrupt inner pointer must be rejected loudly");
1237 assert!(
1238 matches!(err, VerifyError::OutOfRegion { .. }),
1239 "got {err:?}"
1240 );
1241 }
1242
1243 #[test]
1244 fn inplace_root_outside_region_rejected() {
1245 let (bytes, list_element, root) = list_list_int_buffer();
1249 let region = Region::new(0, root).expect("region"); let err = verify_value_at(
1251 &bytes,
1252 &list(list(TypeRepr::Int)),
1253 list_element,
1254 root,
1255 region,
1256 )
1257 .expect_err("a root outside the region must be rejected");
1258 assert!(
1259 matches!(err, VerifyError::OutOfRegion { .. }),
1260 "got {err:?}"
1261 );
1262 }
1263
1264 fn list_string_buffer(items: &[&str]) -> (Vec<u8>, Option<ListElementKind>, usize) {
1273 let schema = Schema {
1274 name: "Ret".into(),
1275 generics: vec![],
1276 is_tuple: false,
1277 fields: vec![field("value", list(TypeRepr::String))],
1278 };
1279 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
1280 let mut b = BufferBuilder::new(&layout, &schema.fields);
1281 b.write_list_string("value", items).expect("write list str");
1282 let bytes = b.finish();
1283 let fo = &layout.fields[0];
1284 let mut slot = [0u8; 4];
1285 slot.copy_from_slice(&bytes[fo.offset..fo.offset + 4]);
1286 let header_off = u32::from_le_bytes(slot) as usize;
1287 (bytes, fo.list_element, header_off)
1288 }
1289
1290 #[test]
1291 fn inplace_list_string_verifies_clean() {
1292 let multibyte: String = [0x4E2Du32, 0x6587]
1295 .iter()
1296 .map(|c| char::from_u32(*c).unwrap())
1297 .collect();
1298 let items = ["", "x", "abc", multibyte.as_str()];
1299 let (bytes, list_element, root) = list_string_buffer(&items);
1300 let region = Region::new(0, bytes.len()).expect("region");
1301 verify_value_at(&bytes, &list(TypeRepr::String), list_element, root, region)
1302 .expect("a legal in-place List<String> root must verify");
1303 }
1304
1305 #[test]
1306 fn inplace_list_string_corrupt_entry_pointer_rejected() {
1307 let (mut bytes, list_element, root) = list_string_buffer(&["a", "bb"]);
1311 let off0_pos = root + 4;
1312 let bogus = (bytes.len() as u32 + 4096).to_le_bytes();
1313 bytes[off0_pos..off0_pos + 4].copy_from_slice(&bogus);
1314 let region = Region::new(0, bytes.len()).expect("region");
1315 let err = verify_value_at(&bytes, &list(TypeRepr::String), list_element, root, region)
1316 .expect_err("a corrupt entry pointer must be rejected");
1317 assert!(
1318 matches!(err, VerifyError::OutOfRegion { .. }),
1319 "got {err:?}"
1320 );
1321 }
1322
1323 #[test]
1324 fn inplace_list_string_overlong_str_len_rejected() {
1325 let (mut bytes, list_element, root) = list_string_buffer(&["ada", "bob"]);
1329 let mut o0 = [0u8; 4];
1332 o0.copy_from_slice(&bytes[root + 4..root + 8]);
1333 let rec0 = u32::from_le_bytes(o0) as usize;
1334 bytes[rec0..rec0 + 4].copy_from_slice(&0xFFFF_F000u32.to_le_bytes());
1335 let region = Region::new(0, bytes.len()).expect("region");
1336 let err = verify_value_at(&bytes, &list(TypeRepr::String), list_element, root, region)
1337 .expect_err("an overlong String len must be rejected");
1338 assert!(
1339 matches!(
1340 err,
1341 VerifyError::OutOfRegion {
1342 what: "string payload",
1343 ..
1344 }
1345 ),
1346 "got {err:?}"
1347 );
1348 }
1349
1350 #[test]
1351 fn inplace_list_string_outer_len_lies_rejected() {
1352 let (mut bytes, list_element, root) = list_string_buffer(&["a"]);
1356 bytes[root..root + 4].copy_from_slice(&9999u32.to_le_bytes());
1357 let region = Region::new(0, bytes.len()).expect("region");
1358 let err = verify_value_at(&bytes, &list(TypeRepr::String), list_element, root, region)
1359 .expect_err("a lying outer len must be rejected");
1360 assert!(
1361 matches!(err, VerifyError::OutOfRegion { .. }),
1362 "got {err:?}"
1363 );
1364 }
1365
1366 #[test]
1367 fn inplace_list_string_root_outside_region_rejected() {
1368 let (bytes, list_element, root) = list_string_buffer(&["a", "b"]);
1372 let region = Region::new(0, root).expect("region"); let err = verify_value_at(&bytes, &list(TypeRepr::String), list_element, root, region)
1374 .expect_err("a root outside the region must be rejected");
1375 assert!(
1376 matches!(err, VerifyError::OutOfRegion { .. }),
1377 "got {err:?}"
1378 );
1379 }
1380
1381 fn cfg_schema() -> Schema {
1388 Schema {
1389 name: "Cfg".into(),
1390 generics: vec![],
1391 is_tuple: false,
1392 fields: vec![
1393 field("flag", TypeRepr::Bool),
1394 field("name", TypeRepr::String),
1395 field("port", TypeRepr::Int),
1396 field("tags", list(TypeRepr::String)),
1397 field("nums", list(TypeRepr::Int)),
1398 ],
1399 }
1400 }
1401
1402 fn list_cfg_buffer(n: usize) -> (Vec<u8>, Option<ListElementKind>, usize) {
1409 let cfg = cfg_schema();
1410 let cfg_layout = SchemaLayout::offsets_for(&cfg).expect("cfg layout");
1411 let ret = Schema {
1412 name: "Ret".into(),
1413 generics: vec![],
1414 is_tuple: false,
1415 fields: vec![field(
1416 "value",
1417 list(TypeRepr::Schema {
1418 schema: Box::new(cfg.clone()),
1419 }),
1420 )],
1421 };
1422 let ret_layout = SchemaLayout::offsets_for(&ret).expect("ret layout");
1423 let mut b = BufferBuilder::new(&ret_layout, &ret.fields);
1424 let mut writer = b
1425 .list_record_writer("value", &cfg_layout, &cfg)
1426 .expect("list_record_writer");
1427 for i in 0..n {
1428 let mut child = writer.start_entry();
1429 child.write_bool("flag", i % 2 == 0).unwrap();
1430 child.write_string("name", &format!("cfg-{i}")).unwrap();
1431 child.write_int("port", (1000 + i) as i64).unwrap();
1432 child.write_list_string("tags", &["a", "", "bb"]).unwrap();
1433 child.write_list_int("nums", &[i as i64, -1, 7]).unwrap();
1434 writer.finish_entry(&mut b, child).expect("finish entry");
1435 }
1436 b.finish_list_record(writer).expect("finish list");
1437 let bytes = b.finish();
1438 let fo = &ret_layout.fields[0];
1439 let mut slot = [0u8; 4];
1440 slot.copy_from_slice(&bytes[fo.offset..fo.offset + 4]);
1441 let header_off = u32::from_le_bytes(slot) as usize;
1442 (bytes, fo.list_element, header_off)
1443 }
1444
1445 fn list_cfg_ty() -> TypeRepr {
1446 list(TypeRepr::Schema {
1447 schema: Box::new(cfg_schema()),
1448 })
1449 }
1450
1451 #[test]
1452 fn inplace_list_schema_verifies_clean() {
1453 let (bytes, list_element, root) = list_cfg_buffer(3);
1454 let region = Region::new(0, bytes.len()).expect("region");
1455 verify_value_at(&bytes, &list_cfg_ty(), list_element, root, region)
1456 .expect("a legal in-place List<Schema> root must verify to the field-pointer layer");
1457 }
1458
1459 #[test]
1460 fn inplace_list_schema_empty_verifies_clean() {
1461 let (bytes, list_element, root) = list_cfg_buffer(0);
1462 let region = Region::new(0, bytes.len()).expect("region");
1463 verify_value_at(&bytes, &list_cfg_ty(), list_element, root, region)
1464 .expect("an empty in-place List<Schema> must verify");
1465 }
1466
1467 #[test]
1468 fn inplace_list_schema_corrupt_entry_pointer_rejected() {
1469 let (mut bytes, list_element, root) = list_cfg_buffer(2);
1473 let off0_pos = root + 4;
1474 let bogus = (bytes.len() as u32 + 4096).to_le_bytes();
1475 bytes[off0_pos..off0_pos + 4].copy_from_slice(&bogus);
1476 let region = Region::new(0, bytes.len()).expect("region");
1477 let err = verify_value_at(&bytes, &list_cfg_ty(), list_element, root, region)
1478 .expect_err("a corrupt sub-record pointer must be rejected");
1479 assert!(
1480 matches!(
1481 err,
1482 VerifyError::OutOfRegion { .. } | VerifyError::FixedSlotOutOfRegion { .. }
1483 ),
1484 "got {err:?}"
1485 );
1486 }
1487
1488 #[test]
1489 fn inplace_list_schema_corrupt_field_string_pointer_rejected() {
1490 let (mut bytes, list_element, root) = list_cfg_buffer(1);
1495 let mut o0 = [0u8; 4];
1497 o0.copy_from_slice(&bytes[root + 4..root + 8]);
1498 let sub_base = u32::from_le_bytes(o0) as usize;
1499 let cfg = cfg_schema();
1501 let cfg_layout = SchemaLayout::offsets_for(&cfg).expect("cfg layout");
1502 let name_fo = cfg_layout
1503 .fields
1504 .iter()
1505 .find(|fo| fo.name == "name")
1506 .expect("name field");
1507 let name_slot = sub_base + name_fo.offset;
1508 let bogus = (bytes.len() as u32 + 8192).to_le_bytes();
1509 bytes[name_slot..name_slot + 4].copy_from_slice(&bogus);
1510 let region = Region::new(0, bytes.len()).expect("region");
1511 let err = verify_value_at(&bytes, &list_cfg_ty(), list_element, root, region)
1512 .expect_err("a corrupt sub-record String field pointer must be rejected");
1513 assert!(
1514 matches!(err, VerifyError::OutOfRegion { .. }),
1515 "got {err:?}"
1516 );
1517 }
1518
1519 #[test]
1520 fn inplace_list_schema_corrupt_field_list_pointer_rejected() {
1521 let (mut bytes, list_element, root) = list_cfg_buffer(1);
1525 let mut o0 = [0u8; 4];
1526 o0.copy_from_slice(&bytes[root + 4..root + 8]);
1527 let sub_base = u32::from_le_bytes(o0) as usize;
1528 let cfg = cfg_schema();
1529 let cfg_layout = SchemaLayout::offsets_for(&cfg).expect("cfg layout");
1530 let tags_fo = cfg_layout
1531 .fields
1532 .iter()
1533 .find(|fo| fo.name == "tags")
1534 .expect("tags field");
1535 let tags_slot = sub_base + tags_fo.offset;
1536 let bogus = (bytes.len() as u32 + 8192).to_le_bytes();
1537 bytes[tags_slot..tags_slot + 4].copy_from_slice(&bogus);
1538 let region = Region::new(0, bytes.len()).expect("region");
1539 let err = verify_value_at(&bytes, &list_cfg_ty(), list_element, root, region)
1540 .expect_err("a corrupt sub-record List field pointer must be rejected");
1541 assert!(
1542 matches!(err, VerifyError::OutOfRegion { .. }),
1543 "got {err:?}"
1544 );
1545 }
1546
1547 #[test]
1548 fn inplace_list_schema_outer_len_lies_rejected() {
1549 let (mut bytes, list_element, root) = list_cfg_buffer(1);
1553 bytes[root..root + 4].copy_from_slice(&9999u32.to_le_bytes());
1554 let region = Region::new(0, bytes.len()).expect("region");
1555 let err = verify_value_at(&bytes, &list_cfg_ty(), list_element, root, region)
1556 .expect_err("a lying outer len must be rejected");
1557 assert!(
1558 matches!(
1559 err,
1560 VerifyError::OutOfRegion { .. } | VerifyError::FixedSlotOutOfRegion { .. }
1561 ),
1562 "got {err:?}"
1563 );
1564 }
1565
1566 #[test]
1567 fn inplace_list_schema_root_outside_region_rejected() {
1568 let (bytes, list_element, root) = list_cfg_buffer(2);
1569 let region = Region::new(0, root).expect("region"); let err = verify_value_at(&bytes, &list_cfg_ty(), list_element, root, region)
1571 .expect_err("a root outside the region must be rejected");
1572 assert!(
1573 matches!(err, VerifyError::OutOfRegion { .. }),
1574 "got {err:?}"
1575 );
1576 }
1577
1578 fn list_str(items: &[&str]) -> crate::value::Value {
1586 crate::value::Value::List(std::sync::Arc::new(
1587 items
1588 .iter()
1589 .map(|s| crate::value::Value::String((*s).into()))
1590 .collect(),
1591 ))
1592 }
1593
1594 fn list_list_string_buffer() -> (Vec<u8>, Option<ListElementKind>, usize) {
1597 let schema = Schema {
1598 name: "Ret".into(),
1599 generics: vec![],
1600 is_tuple: false,
1601 fields: vec![field("value", list(list(TypeRepr::String)))],
1602 };
1603 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
1604 let mut b = BufferBuilder::new(&layout, &schema.fields);
1605 let rows = vec![list_str(&["a", "", "bb"]), list_str(&[]), list_str(&["zz"])];
1606 crate::buffer::write_nested_pointer_array_list(&mut b, "value", &TypeRepr::String, &rows)
1607 .expect("write nested pointer-array list");
1608 let bytes = b.finish();
1609 let fo = &layout.fields[0];
1610 let mut slot = [0u8; 4];
1611 slot.copy_from_slice(&bytes[fo.offset..fo.offset + 4]);
1612 let header_off = u32::from_le_bytes(slot) as usize;
1613 (bytes, fo.list_element, header_off)
1614 }
1615
1616 fn list_list_string_ty() -> TypeRepr {
1617 list(list(TypeRepr::String))
1618 }
1619
1620 #[test]
1621 fn inplace_list_list_string_verifies_clean() {
1622 let (bytes, le, root) = list_list_string_buffer();
1623 let region = Region::new(0, bytes.len()).expect("region");
1624 verify_value_at(&bytes, &list_list_string_ty(), le, root, region)
1625 .expect("a legal List<List<String>> must verify to the innermost String");
1626 }
1627
1628 #[test]
1629 fn inplace_list_list_string_corrupt_outer_entry_rejected() {
1630 let (mut bytes, le, root) = list_list_string_buffer();
1633 let off0 = root + 4;
1634 let bogus = (bytes.len() as u32 + 4096).to_le_bytes();
1635 bytes[off0..off0 + 4].copy_from_slice(&bogus);
1636 let region = Region::new(0, bytes.len()).expect("region");
1637 let err = verify_value_at(&bytes, &list_list_string_ty(), le, root, region)
1638 .expect_err("a corrupt outer entry must be rejected");
1639 assert!(
1640 matches!(err, VerifyError::OutOfRegion { .. }),
1641 "got {err:?}"
1642 );
1643 }
1644
1645 #[test]
1646 fn inplace_list_list_string_corrupt_inner_entry_rejected() {
1647 let (mut bytes, le, root) = list_list_string_buffer();
1652 let mut o0 = [0u8; 4];
1654 o0.copy_from_slice(&bytes[root + 4..root + 8]);
1655 let inner_header = u32::from_le_bytes(o0) as usize;
1656 let inner_off0 = inner_header + 4;
1658 let bogus = (bytes.len() as u32 + 8192).to_le_bytes();
1659 bytes[inner_off0..inner_off0 + 4].copy_from_slice(&bogus);
1660 let region = Region::new(0, bytes.len()).expect("region");
1661 let err = verify_value_at(&bytes, &list_list_string_ty(), le, root, region)
1662 .expect_err("a corrupt inner entry must be rejected");
1663 assert!(
1664 matches!(err, VerifyError::OutOfRegion { .. }),
1665 "got {err:?}"
1666 );
1667 }
1668
1669 #[test]
1670 fn inplace_list_list_string_overlong_innermost_str_rejected() {
1671 let (mut bytes, le, root) = list_list_string_buffer();
1676 let mut o0 = [0u8; 4];
1677 o0.copy_from_slice(&bytes[root + 4..root + 8]);
1678 let inner_header = u32::from_le_bytes(o0) as usize;
1679 let mut i0 = [0u8; 4];
1680 i0.copy_from_slice(&bytes[inner_header + 4..inner_header + 8]);
1681 let str_rec = u32::from_le_bytes(i0) as usize;
1682 bytes[str_rec..str_rec + 4].copy_from_slice(&0xFFFF_F000u32.to_le_bytes());
1683 let region = Region::new(0, bytes.len()).expect("region");
1684 let err = verify_value_at(&bytes, &list_list_string_ty(), le, root, region)
1685 .expect_err("an overlong innermost String must be rejected");
1686 assert!(
1687 matches!(
1688 err,
1689 VerifyError::OutOfRegion {
1690 what: "string payload",
1691 ..
1692 }
1693 ),
1694 "got {err:?}"
1695 );
1696 }
1697
1698 fn arena_with_regions(
1713 in_bytes: &[u8],
1714 out_bytes: &[u8],
1715 ) -> (Vec<u8>, MultiRegion, usize, usize) {
1716 let const_len = 16usize;
1717 let in_start = const_len;
1718 let in_len = in_bytes.len().max(4);
1719 let in_end = in_start + in_len;
1720 let out_start = in_end + 8;
1722 let out_len = out_bytes.len().max(4);
1723 let out_end = out_start + out_len;
1724 let scratch_start = out_end + 8;
1725 let scratch_len = 16usize;
1726 let arena_size = scratch_start + scratch_len;
1727
1728 let mut arena = vec![0u8; arena_size];
1729 arena[in_start..in_start + in_bytes.len()].copy_from_slice(in_bytes);
1730 arena[out_start..out_start + out_bytes.len()].copy_from_slice(out_bytes);
1731
1732 let multi = MultiRegion::new(
1733 (0, const_len),
1734 (in_start, in_end),
1735 (out_start, out_end),
1736 (scratch_start, arena_size),
1737 )
1738 .expect("multi region");
1739 (arena, multi, in_start, out_start)
1740 }
1741
1742 fn string_record(s: &str) -> Vec<u8> {
1745 let mut v = Vec::with_capacity(4 + s.len());
1746 v.extend_from_slice(&(s.len() as u32).to_le_bytes());
1747 v.extend_from_slice(s.as_bytes());
1748 v
1749 }
1750
1751 fn cross_region_cfg() -> Schema {
1755 Schema {
1756 name: "Cfg".into(),
1757 generics: vec![],
1758 is_tuple: false,
1759 fields: vec![
1760 field("name", TypeRepr::String),
1761 field("port", TypeRepr::Int),
1762 ],
1763 }
1764 }
1765
1766 #[allow(clippy::type_complexity)]
1769 fn cross_region_cfg_arena() -> (Vec<u8>, MultiRegion, OffsetTable, Schema, usize, usize) {
1770 let schema = cross_region_cfg();
1771 let layout = SchemaLayout::offsets_for(&schema).expect("cfg layout");
1772 let in_bytes = string_record("ada");
1774 let out_bytes = vec![0u8; layout.root_size];
1777 let (mut arena, multi, in_start, out_start) = arena_with_regions(&in_bytes, &out_bytes);
1778
1779 let name_fo = layout
1780 .fields
1781 .iter()
1782 .find(|fo| fo.name == "name")
1783 .expect("name fo");
1784 let port_fo = layout
1785 .fields
1786 .iter()
1787 .find(|fo| fo.name == "port")
1788 .expect("port fo");
1789 let name_string_abs = in_start;
1792 let name_slot = out_start + name_fo.offset;
1793 arena[name_slot..name_slot + 4].copy_from_slice(&(name_string_abs as u32).to_le_bytes());
1794 let port_slot = out_start + port_fo.offset;
1796 arena[port_slot..port_slot + 8].copy_from_slice(&8080i64.to_le_bytes());
1797
1798 (arena, multi, layout, schema, out_start, name_string_abs)
1799 }
1800
1801 #[test]
1802 fn multi_region_cross_region_record_verifies_clean() {
1803 let (arena, multi, layout, schema, base, _) = cross_region_cfg_arena();
1807 verify_record_multi(&arena, &layout, &schema.fields, base, multi)
1808 .expect("a legal cross-region record must verify under the multi-region map");
1809 }
1810
1811 #[test]
1812 fn multi_region_pointer_to_no_region_rejected() {
1813 let (mut arena, multi, layout, schema, base, _) = cross_region_cfg_arena();
1817 let name_fo = layout.fields.iter().find(|fo| fo.name == "name").unwrap();
1818 let name_slot = base + name_fo.offset;
1819 let in_start = 16usize;
1823 let in_record_len = string_record("ada").len();
1824 let in_len = in_record_len.max(4);
1825 let gap_off = in_start + in_len + 1; arena[name_slot..name_slot + 4].copy_from_slice(&(gap_off as u32).to_le_bytes());
1827 let err = verify_record_multi(&arena, &layout, &schema.fields, base, multi)
1828 .expect_err("a pointer into the inter-region gap must be rejected");
1829 assert!(matches!(err, VerifyError::NoRegion { .. }), "got {err:?}");
1830 }
1831
1832 #[test]
1833 fn multi_region_pointer_payload_runs_off_region_rejected() {
1834 let (mut arena, multi, layout, schema, base, name_abs) = cross_region_cfg_arena();
1839 arena[name_abs..name_abs + 4].copy_from_slice(&0xFFFF_F000u32.to_le_bytes());
1841 let err = verify_record_multi(&arena, &layout, &schema.fields, base, multi)
1842 .expect_err("an overlong String payload escaping its region must be rejected");
1843 assert!(matches!(err, VerifyError::NoRegion { .. }), "got {err:?}");
1844 }
1845
1846 #[test]
1847 fn multi_region_record_head_in_no_region_rejected() {
1848 let (arena, multi, layout, schema, _, _) = cross_region_cfg_arena();
1852 let in_start = 16usize;
1853 let in_len = string_record("ada").len().max(4);
1854 let gap_base = in_start + in_len + 1;
1855 let err = verify_record_multi(&arena, &layout, &schema.fields, gap_base, multi)
1856 .expect_err("a record head fitting no region must be rejected");
1857 assert!(matches!(err, VerifyError::NoRegion { .. }), "got {err:?}");
1858 }
1859
1860 #[allow(clippy::type_complexity)]
1867 fn cross_region_dict_of_list_cfg() -> (Vec<u8>, MultiRegion, OffsetTable, Schema, usize) {
1868 let cfg = cross_region_cfg();
1870 let cfg_layout = SchemaLayout::offsets_for(&cfg).expect("cfg layout");
1871 let outer = Schema {
1873 name: "Out".into(),
1874 generics: vec![],
1875 is_tuple: false,
1876 fields: vec![
1877 field(
1878 "servers",
1879 list(TypeRepr::Schema {
1880 schema: Box::new(cfg.clone()),
1881 }),
1882 ),
1883 field("n", TypeRepr::Int),
1884 ],
1885 };
1886 let outer_layout = SchemaLayout::offsets_for(&outer).expect("outer layout");
1887
1888 let two = 2u32;
1895 let header_rel = 0usize;
1896 let entries_rel = header_rel + 4; let cfg0_rel = entries_rel + 8;
1898 let cfg1_rel = cfg0_rel + cfg_layout.root_size;
1899 let name0_rec = string_record("alpha");
1900 let name1_rec = string_record("beta");
1901 let name0_rel = cfg1_rel + cfg_layout.root_size;
1902 let name1_rel = name0_rel + name0_rec.len();
1903 let in_len = name1_rel + name1_rec.len();
1904
1905 let name_fo = cfg_layout.fields.iter().find(|f| f.name == "name").unwrap();
1906 let port_fo = cfg_layout.fields.iter().find(|f| f.name == "port").unwrap();
1907
1908 let in_placeholder = vec![0u8; in_len];
1912 let out_placeholder = vec![0u8; outer_layout.root_size];
1913 let (mut arena, multi, in_start, out_start) =
1914 arena_with_regions(&in_placeholder, &out_placeholder);
1915
1916 let put_u32 = |arena: &mut [u8], abs: usize, v: u32| {
1918 arena[abs..abs + 4].copy_from_slice(&v.to_le_bytes());
1919 };
1920 let put_i64 = |arena: &mut [u8], abs: usize, v: i64| {
1921 arena[abs..abs + 8].copy_from_slice(&v.to_le_bytes());
1922 };
1923 put_u32(&mut arena, in_start + header_rel, two);
1925 put_u32(
1927 &mut arena,
1928 in_start + entries_rel,
1929 (in_start + cfg0_rel) as u32,
1930 );
1931 put_u32(
1932 &mut arena,
1933 in_start + entries_rel + 4,
1934 (in_start + cfg1_rel) as u32,
1935 );
1936 put_u32(
1938 &mut arena,
1939 in_start + cfg0_rel + name_fo.offset,
1940 (in_start + name0_rel) as u32,
1941 );
1942 put_i64(&mut arena, in_start + cfg0_rel + port_fo.offset, 1);
1943 put_u32(
1945 &mut arena,
1946 in_start + cfg1_rel + name_fo.offset,
1947 (in_start + name1_rel) as u32,
1948 );
1949 put_i64(&mut arena, in_start + cfg1_rel + port_fo.offset, 2);
1950 arena[in_start + name0_rel..in_start + name0_rel + name0_rec.len()]
1952 .copy_from_slice(&name0_rec);
1953 arena[in_start + name1_rel..in_start + name1_rel + name1_rec.len()]
1954 .copy_from_slice(&name1_rec);
1955
1956 let servers_fo = outer_layout
1958 .fields
1959 .iter()
1960 .find(|f| f.name == "servers")
1961 .unwrap();
1962 let n_fo = outer_layout.fields.iter().find(|f| f.name == "n").unwrap();
1963 put_u32(
1965 &mut arena,
1966 out_start + servers_fo.offset,
1967 (in_start + header_rel) as u32,
1968 );
1969 put_i64(&mut arena, out_start + n_fo.offset, 42);
1970
1971 (arena, multi, outer_layout, outer, out_start)
1972 }
1973
1974 #[test]
1975 fn multi_region_dict_of_list_cfg_verifies_clean() {
1976 let (arena, multi, layout, schema, base) = cross_region_dict_of_list_cfg();
1981 verify_record_multi(&arena, &layout, &schema.fields, base, multi).expect(
1982 "a legal cross-region Dict { servers: List<Cfg>, n: Int } must verify multi-region",
1983 );
1984 }
1985
1986 #[test]
1987 fn multi_region_dict_of_list_cfg_corrupt_subrecord_field_rejected() {
1988 let (mut arena, multi, layout, schema, base) = cross_region_dict_of_list_cfg();
1993 let cfg = cross_region_cfg();
1996 let cfg_layout = SchemaLayout::offsets_for(&cfg).expect("cfg layout");
1997 let name_fo = cfg_layout.fields.iter().find(|f| f.name == "name").unwrap();
1998 let in_start = 16usize;
1999 let entries_rel = 4usize;
2000 let cfg0_rel = entries_rel + 8;
2001 let name_slot = in_start + cfg0_rel + name_fo.offset;
2002 let bogus = (arena.len() as u32) + 4096;
2006 arena[name_slot..name_slot + 4].copy_from_slice(&bogus.to_le_bytes());
2007 let err = verify_record_multi(&arena, &layout, &schema.fields, base, multi)
2008 .expect_err("a corrupt cross-region sub-record field pointer must be rejected");
2009 assert!(matches!(err, VerifyError::NoRegion { .. }), "got {err:?}");
2010 }
2011
2012 #[test]
2013 fn multi_region_buffer_shorter_than_regions_rejected() {
2014 let multi = MultiRegion::new((0, 8), (8, 16), (16, 64), (64, 80)).expect("multi");
2017 let schema = cross_region_cfg();
2018 let layout = SchemaLayout::offsets_for(&schema).expect("layout");
2019 let short = vec![0u8; 16];
2020 let err = verify_record_multi(&short, &layout, &schema.fields, 16, multi)
2021 .expect_err("a slice shorter than the region span must be rejected");
2022 assert!(
2023 matches!(
2024 err,
2025 VerifyError::BufferShorterThanRegion { have: 16, end: 80 }
2026 ),
2027 "got {err:?}"
2028 );
2029 }
2030}