use super::*; use std::io::{self, Cursor, Read, Write}; const VERSIONS: [Version; 2] = [Version::V201501280, Version::V201512200]; fn empty(version: Version) -> Document { Document { version, world: World::default(), } } fn rich(version: Version) -> Document { let plane = Plane { p0: [1.0, -0.0, 3.0], u: [4.0, 5.0, 6.0], v: [7.0, 8.0, 9.0], polygon: Some(vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]]), ..Plane::default() }; let mut d = Document { version, world: World { computed: true, brushes: vec![Brush { planes: vec![plane], }], patches: vec![Patch9 { anchors_u: 3, anchors_v: 3, quads_u: 2, quads_v: 2, anchors: vec![[1.0, 2.0, 3.0]; 9], computed: Some(PatchComputed { points: vec![[4.0, 5.0, 6.0]; 9], normals: vec![[0.0, 1.0, 0.0]; 9], }), }], ..World::default() }, }; if version == Version::V201512200 { d.world.string_properties = vec!["unused".into(), "name".into()]; d.world.uv_properties = vec!["uv".into()]; d.world.brushes[0].planes[0].strings = vec![Property { index: 1, value: String::new(), }]; d.world.brushes[0].planes[0].uv = vec![Property { index: 0, value: Uv { u: [1.0, 2.0, 3.0], v: [6.0, 7.0, 8.0], u_scale: 4.0, u_shift: 5.0, v_scale: 9.0, v_shift: 10.0, }, }]; } d } #[test] fn exact_empty_headers() { for version in VERSIONS { let mut expected = b"Mb3World".to_vec(); expected.extend_from_slice(&(version as u32).to_le_bytes()); expected.extend_from_slice(&[0; 9]); if version == Version::V201512200 { expected.extend_from_slice(&[0; 2]); } let d = empty(version); assert_eq!(encode(&d, &Limits::default()).unwrap(), expected); assert_eq!(decode(&expected, &Limits::default()).unwrap(), d); } } #[test] fn rich_semantic_and_bit_exact_roundtrips() { for version in VERSIONS { let d = rich(version); let data = encode(&d, &Limits::default()).unwrap(); let decoded = decode(&data, &Limits::default()).unwrap(); assert_eq!(decoded, d); assert_eq!( decoded.world.brushes[0].planes[0].p0[1].to_bits(), (-0.0f32).to_bits() ); assert_eq!(encode(&decoded, &Limits::default()).unwrap(), data); } } #[test] fn rejects_every_prefix_truncation() { for version in VERSIONS { let data = encode(&rich(version), &Limits::default()).unwrap(); for n in 0..data.len() { assert!( decode(&data[..n], &Limits::default()).is_err(), "version {version:?}, prefix {n}" ); } } } #[test] fn rejects_header_flags_versions_trailing_and_reserved_widths() { for version in VERSIONS { let data = encode(&empty(version), &Limits::default()).unwrap(); for (at, value) in [(0, b'x'), (8, 0), (12, 0xe0), (12, 6)] { let mut bad = data.clone(); bad[at] = value; assert!(decode(&bad, &Limits::default()).is_err()); } let mut extra = data; extra.push(0); assert!(matches!( decode(&extra, &Limits::default()).unwrap_err().kind, ErrorKind::TrailingData )); } for flags in [3, 12, 0xf0] { let mut data = encode(&empty(Version::V201512200), &Limits::default()).unwrap(); data[15..19].copy_from_slice(&1i32.to_le_bytes()); data.splice(19..19, [flags, 0]); assert!(decode(&data, &Limits::default()).is_err()); } } #[test] fn counts_rejected_before_allocation() { for count in [-1i32, i32::MAX] { let mut data = encode(&empty(Version::V201512200), &Limits::default()).unwrap(); data[15..19].copy_from_slice(&count.to_le_bytes()); assert!(decode(&data, &Limits::default()).is_err()); } let mut old = encode(&empty(Version::V201501280), &Limits::default()).unwrap(); old[13..17].copy_from_slice(&1i32.to_le_bytes()); old.extend_from_slice(&[1, 0, 128]); assert!(matches!( decode(&old, &Limits::default()).unwrap_err().kind, ErrorKind::NegativeCount )); } #[test] fn plane_width_boundaries_and_legacy_repair() { for version in VERSIONS { for n in [255, 256, 32767, 32768, 65535, 65536] { let mut d = empty(version); d.world.brushes.push(Brush { planes: vec![Plane::default(); n], }); let data = encode(&d, &Limits::default()).unwrap(); let flag_at = if version == Version::V201501280 { 21 } else { 19 }; let expected = if n <= 255 { 0 } else if n <= if version == Version::V201501280 { 32767 } else { 65535 } { 1 } else { 2 }; assert_eq!(data[flag_at], expected); assert_eq!(decode(&data, &Limits::default()).unwrap(), d); } } } #[test] fn string_and_uv_table_width_boundaries_preserve_unused_names() { for n in [255, 256, 65535, 65536] { let mut d = empty(Version::V201512200); d.world.string_properties = (0..n).map(|i| format!("s{i}")).collect(); d.world.uv_properties = (0..n).map(|i| format!("u{i}")).collect(); let data = encode(&d, &Limits::default()).unwrap(); let width = if n <= 255 { 0 } else if n <= 65535 { 1 } else { 2 }; assert_eq!(data[12], width << 1 | width << 3); assert_eq!(decode(&data, &Limits::default()).unwrap(), d); } } #[test] fn sparse_index_and_sentinel_width_boundaries() { for n in [255, 256, 65535, 65536] { let mut d = empty(Version::V201512200); d.world.string_properties = (0..n).map(|i| format!("p{i}")).collect(); d.world.uv_properties = d.world.string_properties.clone(); d.world.brushes.push(Brush { planes: vec![Plane { strings: vec![Property { index: n - 1, value: String::new(), }], uv: vec![Property { index: n - 1, value: Uv { u: [1.0; 3], v: [2.0; 3], u_scale: 3.0, u_shift: 4.0, v_scale: 5.0, v_shift: 6.0, }, }], ..Plane::default() }], }); let data = encode(&d, &Limits::default()).unwrap(); assert_eq!(decode(&data, &Limits::default()).unwrap(), d); } } #[test] fn shared_contour_width_and_legacy_repair() { for version in VERSIONS { for n in [256, 32768, 65536] { let mut d = empty(version); d.world.computed = true; d.world.brushes.push(Brush { planes: vec![ Plane { polygon: Some(vec![[1.0, 2.0, 3.0]; n]), ..Plane::default() }, Plane { polygon: Some(vec![[4.0, 5.0, 6.0]]), ..Plane::default() }, ], }); let data = encode(&d, &Limits::default()).unwrap(); assert_eq!(decode(&data, &Limits::default()).unwrap(), d); } } } #[test] fn patch_dimensions_and_tessellation_widths() { for version in VERSIONS { for n in [255, 256, 32768, 65536] { let mut d = empty(version); d.world.patches.push(Patch9 { anchors_u: n, anchors_v: 0, quads_u: n, quads_v: 2, ..Patch9::default() }); assert_eq!( decode(&encode(&d, &Limits::default()).unwrap(), &Limits::default()).unwrap(), d ); } } } #[test] fn strict_strings_on_read_and_write() { for text in ["é", "a\0b"] { let mut d = empty(Version::V201512200); d.world.string_properties.push(text.into()); assert!(encode(&d, &Limits::default()).is_err()); } for tail in [vec![1, b'a'], vec![1, 0xff, 0]] { let mut data = b"Mb3World".to_vec(); data.extend_from_slice(&201512200i32.to_le_bytes()); data.push(0); data.extend(tail); assert!(decode(&data, &Limits::default()).is_err()); } let mut d = empty(Version::V201512200); d.world.string_properties = vec!["same".into(), "same".into()]; assert_eq!( decode(&encode(&d, &Limits::default()).unwrap(), &Limits::default()).unwrap(), d ); } fn sparse_wire(entries: &[u8]) -> Vec { let mut data = b"Mb3World".to_vec(); data.extend_from_slice(&201512200i32.to_le_bytes()); data.extend_from_slice(b"\0\x02a\0b\0\0"); data.extend_from_slice(&1i32.to_le_bytes()); data.extend_from_slice(&[0, 1]); data.extend_from_slice(entries); data.push(255); data.extend_from_slice(&[0; 36]); data.extend_from_slice(&0i32.to_le_bytes()); data } #[test] fn binary_properties_sorted_but_duplicates_and_bad_indices_rejected() { let d = decode(&sparse_wire(b"\x01B\0\0A\0"), &Limits::default()).unwrap(); assert_eq!( d.world.brushes[0].planes[0].strings, vec![ Property { index: 0, value: "A".into() }, Property { index: 1, value: "B".into() } ] ); for entries in [b"\0a\0\0b\0".as_slice(), b"\x02a\0"] { assert!(decode(&sparse_wire(entries), &Limits::default()).is_err()); } let mut d = d; d.world.brushes[0].planes[0].strings.reverse(); assert!(matches!( encode(&d, &Limits::default()).unwrap_err().kind, ErrorKind::UnsortedProperties )); d.world.brushes[0].planes[0].strings = vec![ Property { index: 0, value: "a".into() }; 2 ]; assert!(matches!( encode(&d, &Limits::default()).unwrap_err().kind, ErrorKind::DuplicateProperty(0) )); d.world.brushes[0].planes[0].strings[0].index = 9; assert!(matches!( encode(&d, &Limits::default()).unwrap_err().kind, ErrorKind::PropertyIndex(9) )); } #[test] fn legacy_downgrade_refuses_properties() { let mut d = rich(Version::V201512200); d.version = Version::V201501280; assert!(matches!( encode(&d, &Limits::default()).unwrap_err().kind, ErrorKind::LegacyProperties )); d.world.string_properties.clear(); d.world.uv_properties.clear(); assert!(matches!( encode(&d, &Limits::default()).unwrap_err().kind, ErrorKind::LegacyProperties )); } #[test] fn refuses_inconsistent_computed_arrays() { for version in VERSIONS { for mode in 0..5 { let mut d = rich(version); match mode { 0 => d.world.computed = false, 1 => d.world.brushes[0].planes[0].polygon = None, 2 => d.world.patches[0].computed = None, 3 => { d.world.patches[0].anchors.pop(); } _ => { d.world.patches[0].computed.as_mut().unwrap().normals.pop(); } } assert!(encode(&d, &Limits::default()).is_err()); } } } #[test] fn overflow_and_impossible_dimensions_are_errors() { for version in VERSIONS { let mut d = empty(version); d.world.computed = true; d.world.patches.push(Patch9 { anchors_u: 7, anchors_v: 7, quads_u: i32::MAX as u32, quads_v: i32::MAX as u32, anchors: vec![[0.0; 3]; 49], computed: Some(PatchComputed::default()), }); assert!(encode(&d, &Limits::default()).is_err()); d.world.computed = false; d.world.patches[0].computed = None; d.world.patches[0].quads_u = u32::MAX; assert!(matches!( encode(&d, &Limits::default()).unwrap_err().kind, ErrorKind::CountOverflow )); } } #[test] fn nonfinite_and_finite_extremes() { for f in [f32::NAN, f32::INFINITY, f32::NEG_INFINITY] { let mut d = rich(Version::V201512200); d.world.brushes[0].planes[0].p0[0] = f; assert!(matches!( encode(&d, &Limits::default()).unwrap_err().kind, ErrorKind::NonFinite )); let mut data = sparse_wire(&[]); // The final 40 bytes are the plane vectors and the patch count. let pos = data.len() - 40; data[pos..pos + 4].copy_from_slice(&f.to_le_bytes()); assert!(matches!( decode(&data, &Limits::default()).unwrap_err().kind, ErrorKind::NonFinite )); } let mut d = rich(Version::V201501280); d.world.brushes[0].planes[0].p0 = [f32::MAX, f32::MIN_POSITIVE, f32::from_bits(1)]; assert_eq!( decode(&encode(&d, &Limits::default()).unwrap(), &Limits::default()).unwrap(), d ); } #[test] fn each_resource_limit_is_enforced() { let d = rich(Version::V201512200); let data = encode(&d, &Limits::default()).unwrap(); for limits in [ Limits { max_bytes: data.len() - 1, ..Limits::default() }, Limits { max_items: 1, ..Limits::default() }, Limits { max_string_bytes: 1, ..Limits::default() }, ] { assert!(decode(&data, &limits).is_err()); assert!(encode(&d, &limits).is_err()); } assert!( decode( &data, &Limits { max_owned_bytes: 1, ..Limits::default() } ) .is_err() ); let mut input = Cursor::new(&data); assert!( read( &mut input, &Limits { max_bytes: 20, ..Limits::default() } ) .is_err() ); assert_eq!(input.position(), 21); } #[test] fn exact_string_and_item_limits() { let mut d = empty(Version::V201512200); d.world.string_properties.push("abcd".into()); let limits = Limits { max_items: 1, max_string_bytes: 4, ..Limits::default() }; let data = encode(&d, &limits).unwrap(); assert_eq!(decode(&data, &limits).unwrap(), d); assert!( decode( &data, &Limits { max_string_bytes: 3, ..limits } ) .is_err() ); } struct Choppy { bytes: Cursor>, interrupt: bool, } impl Read for Choppy { fn read(&mut self, b: &mut [u8]) -> io::Result { if self.interrupt { self.interrupt = false; return Err(io::ErrorKind::Interrupted.into()); } let n = b.len().min(1); self.bytes.read(&mut b[..n]) } } impl Write for Choppy { fn write(&mut self, b: &[u8]) -> io::Result { if self.interrupt { self.interrupt = false; return Err(io::ErrorKind::Interrupted.into()); } self.bytes.write(&b[..b.len().min(1)]) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } #[test] fn streaming_short_reads_writes_and_interruptions() { let d = rich(Version::V201512200); let data = encode(&d, &Limits::default()).unwrap(); assert_eq!( read( Choppy { bytes: Cursor::new(data.clone()), interrupt: true }, &Limits::default() ) .unwrap(), d ); let mut target = Choppy { bytes: Cursor::new(Vec::new()), interrupt: true, }; write(&mut target, &d, &Limits::default()).unwrap(); assert_eq!(target.bytes.into_inner(), data); } #[test] fn invalid_models_do_not_touch_writer() { let mut d = rich(Version::V201501280); d.world.patches[0].anchors.clear(); let mut target = vec![1, 2, 3]; assert!(write(&mut target, &d, &Limits::default()).is_err()); assert_eq!(target, [1, 2, 3]); } struct Broken { written: usize, } impl Write for Broken { fn write(&mut self, b: &[u8]) -> io::Result { if self.written >= 7 { return Err(io::ErrorKind::BrokenPipe.into()); } let n = b.len().min(7 - self.written); self.written += n; Ok(n) } fn flush(&mut self) -> io::Result<()> { Ok(()) } } impl Read for Broken { fn read(&mut self, _: &mut [u8]) -> io::Result { Err(io::ErrorKind::BrokenPipe.into()) } } #[test] fn io_errors_and_offsets_propagate() { let e = write( Broken { written: 0 }, &empty(Version::V201501280), &Limits::default(), ) .unwrap_err(); assert_eq!(e.offset, 7); assert!(matches!(e.kind, ErrorKind::Io(_))); assert!(matches!( read(Broken { written: 0 }, &Limits::default()) .unwrap_err() .kind, ErrorKind::Io(_) )); assert!(matches!( write( [0u8; 0].as_mut_slice(), &empty(Version::V201501280), &Limits::default() ) .unwrap_err() .kind, ErrorKind::Io(_) )); } #[test] fn deterministic_mutation_smoke_no_panics_or_unbounded_allocations() { let limits = Limits { max_bytes: 2048, max_string_bytes: 64, max_items: 256, max_owned_bytes: 32768, }; let seeds: Vec<_> = VERSIONS .iter() .map(|v| encode(&rich(*v), &limits).unwrap()) .collect(); let mut state = 0x37ab12cdu32; for i in 0..5000 { let mut data = seeds[i % seeds.len()].clone(); for _ in 0..1 + i % 8 { state = state.wrapping_mul(1664525).wrapping_add(1013904223); let at = state as usize % data.len(); data[at] ^= (state >> 24) as u8; } if let Ok(d) = decode(&data, &limits) { let canonical = encode(&d, &limits).unwrap(); assert_eq!(decode(&canonical, &limits).unwrap(), d); assert_eq!( encode(&decode(&canonical, &limits).unwrap(), &limits).unwrap(), canonical ); } } } #[test] fn valid_nonminimal_widths_are_normalized_and_patch_flags_are_strict() { let canonical = encode(&empty(Version::V201512200), &Limits::default()).unwrap(); let mut wide = canonical.clone(); wide[12] = 2; // S uses u16 rather than u8, while its count is zero. wide.insert(13, 0); let d = decode(&wide, &Limits::default()).unwrap(); assert_eq!(encode(&d, &Limits::default()).unwrap(), canonical); for version in VERSIONS { let mut d = empty(version); d.world.patches.push(Patch9::default()); let data = encode(&d, &Limits::default()).unwrap(); let flag_at = if version == Version::V201501280 { 21 } else { 23 }; for flags in [3, 12, 0x80] { let mut bad = data.clone(); bad[flag_at] = flags; assert!(decode(&bad, &Limits::default()).is_err()); } } } #[test] fn reader_rejects_computed_grid_arithmetic_overflow() { for version in VERSIONS { let mut data = b"Mb3World".to_vec(); data.extend_from_slice(&(version as u32).to_le_bytes()); data.push(1); if version == Version::V201512200 { data.extend_from_slice(&[0, 0]); } data.extend_from_slice(&0i32.to_le_bytes()); data.extend_from_slice(&1i32.to_le_bytes()); data.extend_from_slice(&[8, 7, 7]); data.extend_from_slice(&i32::MAX.to_le_bytes()); data.extend_from_slice(&i32::MAX.to_le_bytes()); data.extend_from_slice(&[0; 49 * 12]); assert!(matches!( decode(&data, &Limits::default()).unwrap_err().kind, ErrorKind::CountOverflow )); } }