use super::{
SERDE_2026_MAGIC_PREFIX, deserialize_2026, deserialize_2026_body_from_stream, serialize_2026,
serialized_length_serde_2026,
};
use crate::allocator::Allocator;
use crate::serde::{node_from_bytes_backrefs, node_to_bytes};
use hex::FromHex;
use rstest::rstest;
use std::io::Cursor;
fn encode_varint(value: i64) -> Vec<u8> {
let mut buf = Vec::new();
super::varint::write_varint(&mut buf, value).unwrap();
buf
}
const TEST_MAX_ATOM_LEN: usize = 1 << 20;
#[rstest]
#[case("00")] #[case("80")] #[case("01")] #[case("0a")] #[case("8568656c6c6f")] #[case("8b68656c6c6f20776f726c64")] #[case(
"b8400102030405060708091011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465"
)] #[case("ff0100")] #[case("ff0000")] #[case("ff0101")] #[case("ff010a")] #[case("ff83666f6f83626172")] #[case("ff83666f6fff8362617280")] #[case("ffff0102ff0304")] #[case("ff01ff02ff03ff04ff05ff0680")] #[case("ff83666f6ffe02")] #[case("ff01ff0101")] #[case("ffff2a2a2a")] #[case("ff01ff02ff0301")] #[case("ff01ff02ff0300")] #[case("ff01ff02ff0304")] #[case("ff01ff02ff0103")] #[case("ffff0102ff0102")] #[case("ffff0102ffff0102ff0102")] #[case("ffff0102ffff010200")] #[case("ffff010aff010a")] #[case("ff01ff01ff0100")] #[case("ff01ff01ff0101")] #[case("ffff01ff0203ff01ff0203")] #[case("ffff0102ffff0102ffff010200")] #[case("ff846c6f6e67ff86737472696e67ff826f66fffe0bff8474657874fffe1780")] #[case("ff83666f6ffffe01fffe01fffe01fffe01fffe01fffe0180")] fn test_round_trip(#[case] hex: &str) {
let bytes = Vec::from_hex(hex).unwrap();
let mut allocator = Allocator::new();
let node = node_from_bytes_backrefs(&mut allocator, &bytes).unwrap();
let canonical = node_to_bytes(&allocator, node).unwrap();
let blobs: Vec<(&str, u32, Vec<u8>)> =
vec![("fast", 0, serialize_2026(&allocator, node, 0).unwrap())];
for (label, level, blob) in &blobs {
let mut a2 = Allocator::new();
let n2 = deserialize_2026(&mut a2, blob, TEST_MAX_ATOM_LEN, false).unwrap();
assert_eq!(
node_to_bytes(&a2, n2).unwrap(),
canonical,
"{label}: tree mismatch for {hex}"
);
let blob2 = serialize_2026(&a2, n2, *level).unwrap();
assert_eq!(
blob, &blob2,
"{label}: double round-trip mismatch for {hex}"
);
}
}
#[rstest]
#[case(&[0x7f], "negative atom group count")]
#[case(&[0xff], "0xFF invalid varint prefix")]
#[case(&[0x80], "truncated multibyte varint")]
#[case(&[0x80, 0x80], "truncated 3-byte varint")]
#[case(&[], "empty input")]
#[case(&[0x01, 0x01, 0x41], "valid atom table, truncated before instruction count")]
#[case(&[0x01, 0x01, 0x41, 0x02, 0x02], "instruction count=2 but only 1 instruction follows")]
#[case(&[0x01, 0x01, 0x41, 0x01, 0x70], "instruction refs atom index 110, only 1 exists")]
#[case(&[0x01, 0x01, 0x41, 0x00], "zero instructions with non-empty atom table")]
#[case(&[0x00, 0x00], "zero groups and zero instructions")]
#[case(&[0x02, 0x01, 0x41, 0x01, 0x42], "two groups claimed, only one provided")]
fn test_deserialize_rejects_malformed(#[case] data: &[u8], #[case] _desc: &str) {
let mut allocator = Allocator::new();
assert!(
deserialize_2026_body_from_stream(
&mut allocator,
&mut Cursor::new(data),
TEST_MAX_ATOM_LEN,
false
)
.is_err(),
"should reject: {_desc}"
);
}
#[test]
fn test_strict_rejects_overlong_varints() {
let mut allocator = Allocator::new();
let overlong_group_count = [0x80, 0x01, 0x01, b'A', 0x01, 0x02];
assert!(
deserialize_2026_body_from_stream(
&mut allocator,
&mut Cursor::new(&overlong_group_count),
TEST_MAX_ATOM_LEN,
true
)
.is_err()
);
let decoded = deserialize_2026_body_from_stream(
&mut allocator,
&mut Cursor::new(&overlong_group_count),
TEST_MAX_ATOM_LEN,
false,
)
.unwrap();
assert_eq!(allocator.atom(decoded).as_ref(), b"A");
}
#[test]
fn test_magic_prefix() {
assert_eq!(
SERDE_2026_MAGIC_PREFIX,
[0xfd, 0xff, b'2', b'0', b'2', b'6']
);
let mut allocator = Allocator::new();
let node = allocator.new_atom(b"hello").unwrap();
let bytes = serialize_2026(&allocator, node, 0).unwrap();
assert!(bytes.starts_with(&SERDE_2026_MAGIC_PREFIX));
}
#[test]
fn test_backrefs_decoder_rejects_serde_2026() {
let mut allocator = Allocator::new();
let node = allocator.new_atom(b"hello").unwrap();
let prefixed = serialize_2026(&allocator, node, 0).unwrap();
let mut a2 = Allocator::new();
assert!(node_from_bytes_backrefs(&mut a2, &prefixed).is_err());
}
#[test]
fn test_serialized_length() {
let mut allocator = Allocator::new();
let node = allocator.new_atom(b"hello").unwrap();
let bytes = serialize_2026(&allocator, node, 0).unwrap();
assert_eq!(
serialized_length_serde_2026(&bytes, TEST_MAX_ATOM_LEN, false).unwrap(),
bytes.len() as u64
);
let left = allocator.new_atom(b"left").unwrap();
let right = allocator.new_atom(b"right").unwrap();
let pair = allocator.new_pair(left, right).unwrap();
let bytes = serialize_2026(&allocator, pair, 0).unwrap();
assert_eq!(
serialized_length_serde_2026(&bytes, TEST_MAX_ATOM_LEN, false).unwrap(),
bytes.len() as u64
);
let a = allocator.new_atom(b"shared").unwrap();
let p1 = allocator.new_pair(a, a).unwrap();
let b = allocator.new_atom(b"other").unwrap();
let p2 = allocator.new_pair(p1, b).unwrap();
let root = allocator.new_pair(p2, p1).unwrap();
let bytes = serialize_2026(&allocator, root, 0).unwrap();
assert_eq!(
serialized_length_serde_2026(&bytes, TEST_MAX_ATOM_LEN, false).unwrap(),
bytes.len() as u64
);
let mut padded = bytes.clone();
padded.extend_from_slice(b"trailing garbage");
assert_eq!(
serialized_length_serde_2026(&padded, TEST_MAX_ATOM_LEN, false).unwrap(),
bytes.len() as u64
);
assert!(serialized_length_serde_2026(b"\x80", TEST_MAX_ATOM_LEN, false).is_err());
assert!(serialized_length_serde_2026(b"", TEST_MAX_ATOM_LEN, false).is_err());
}
fn mk_malformed_blob(group_count: i64, instruction_count: i64, atom_table: &[u8]) -> Vec<u8> {
let mut blob = Vec::new();
blob.extend_from_slice(&SERDE_2026_MAGIC_PREFIX);
blob.extend_from_slice(&encode_varint(group_count));
blob.extend_from_slice(atom_table);
blob.extend_from_slice(&encode_varint(instruction_count));
blob
}
#[rstest]
#[case::instruction_count_zero(mk_malformed_blob(1, 0, &[0x01, b'A']))]
#[case::group_length_zero(mk_malformed_blob(1, 1, &[0x00]))]
#[case::multi_atom_group_count_zero(mk_malformed_blob(1, 1, &[0x7f, 0x00, b'A']))]
fn test_serialized_length_rejects_what_deserialize_rejects(#[case] blob: Vec<u8>) {
let mut a = Allocator::new();
assert!(
deserialize_2026(&mut a, &blob, TEST_MAX_ATOM_LEN, false).is_err(),
"deserialize must reject"
);
assert!(
serialized_length_serde_2026(&blob, TEST_MAX_ATOM_LEN, false).is_err(),
"serialized_length must reject (mirrors deserialize)"
);
}
#[test]
fn deserializer_rejects_unbounded_instruction_count() {
let mut blob = Vec::new();
blob.extend_from_slice(&encode_varint(0)); blob.extend_from_slice(&encode_varint(1_i64 << 54)); assert!(
blob.len() < 16,
"PoC blob stays tiny ({} bytes)",
blob.len()
);
let mut a = Allocator::new();
let result = deserialize_2026_body_from_stream(
&mut a,
&mut Cursor::new(&blob),
TEST_MAX_ATOM_LEN,
false,
);
assert!(
result.is_err(),
"instruction_count must be rejected before pre-allocation"
);
}
#[test]
fn deserializer_rejects_unbounded_group_count() {
let mut blob = Vec::new();
blob.extend_from_slice(&encode_varint(1_i64 << 54));
let mut a = Allocator::new();
let result = deserialize_2026_body_from_stream(
&mut a,
&mut Cursor::new(&blob),
TEST_MAX_ATOM_LEN,
false,
);
assert!(
result.is_err(),
"group_count must be rejected before pre-allocation"
);
}
#[test]
fn deserializer_rejects_unbounded_per_group_count() {
let mut blob = Vec::new();
blob.extend_from_slice(&encode_varint(1)); blob.extend_from_slice(&encode_varint(-3)); blob.extend_from_slice(&encode_varint(1_i64 << 54));
let mut a = Allocator::new();
let result = deserialize_2026_body_from_stream(
&mut a,
&mut Cursor::new(&blob),
TEST_MAX_ATOM_LEN,
false,
);
assert!(
result.is_err(),
"per-group count must be rejected before pre-allocation"
);
}
#[test]
fn test_write_atom_table_groups_by_length() {
use super::ser::{SerializerState, write_atom_table};
use super::varint::read_varint;
use std::io::{Cursor, Read};
let mut a = Allocator::new();
let foo = a.new_atom(b"foo").unwrap();
let bar = a.new_atom(b"bar").unwrap();
let baz = a.new_atom(b"baz").unwrap();
let hello = a.new_atom(b"hello").unwrap();
let p = a.new_pair(foo, bar).unwrap();
let q = a.new_pair(baz, hello).unwrap();
let root = a.new_pair(p, q).unwrap();
let state = SerializerState::new(&a, root).unwrap();
let mut buf = Vec::new();
write_atom_table(&mut buf, &state.tree, &state.sorted_no_nil).unwrap();
let mut cursor = Cursor::new(&buf[..]);
let group_count = read_varint(&mut cursor, false).unwrap();
assert_eq!(group_count, 2, "expected 2 length-groups (3, 5)");
let mut total_atoms = 0usize;
let mut total_bytes = 0usize;
let mut saw_repeated_3 = false;
let mut saw_singleton_5 = false;
for _ in 0..group_count {
let length_val = read_varint(&mut cursor, false).unwrap();
if length_val < 0 {
let len = (-length_val) as usize;
let count = read_varint(&mut cursor, false).unwrap() as usize;
assert!(len > 0 && count > 1);
let mut bytes = vec![0u8; len * count];
cursor.read_exact(&mut bytes).unwrap();
total_atoms += count;
total_bytes += bytes.len();
if len == 3 && count == 3 {
saw_repeated_3 = true;
}
} else {
let len = length_val as usize;
let mut bytes = vec![0u8; len];
cursor.read_exact(&mut bytes).unwrap();
total_atoms += 1;
total_bytes += bytes.len();
if len == 5 {
saw_singleton_5 = true;
}
}
}
assert!(
saw_repeated_3,
"expected the three 3-byte atoms to share a group"
);
assert!(
saw_singleton_5,
"expected the 5-byte atom as a singleton group"
);
assert_eq!(total_atoms, 4);
assert_eq!(total_bytes, 3 * 3 + 5);
assert_eq!(
cursor.position() as usize,
buf.len(),
"all bytes of the atom table should be consumed"
);
}