fuzz: wave 3 — validate_pair two-input harness, 44 seeds, release-budget campaigns next

Target 5 (§3): compile an attacker schema (10-lane menu incl. raw JSON
bytes lane) in both modes, then hammer the hostile buffer through
validate_bytes, an independent materialize_packed/materialize_aligned,
read_field over every offset-map leaf, junk field paths, and the packed
sequential walk under the spin bound.

Invariants coded (per §3 target 5): mode agreement (aligned Ok ⇒ packed
Ok; packed-Ok/aligned-Err only for the documented ADR-006/ADR-008/
offset-indirect rejections), the materialize⇄validate_bytes verdict
lattice with verbatim error propagation, unknown-path echo, serde
round-trip of materialized output, non-finite-float Access pinning,
out-of-range enum Validation pinning, and the record-count spin bound.

44 committed seeds (menu/raw lanes × valid/valid, hostile-schema/
valid-bytes, valid-schema/hostile-bytes incl. a per-prefix truncation
sweep, NaN/Inf, enum 99, spin fixtures, mode-agreement pins), hand-
encoded against the pinned arbitrary 1.4.2 derive layout and pinned by
decode tests. The aligned maxLength-reservation offset pin (s@8..72,
tail@72, total 76) caught a fixture assumption error pre-commit.

Verification: corpus replay 29/29 green (44 new seeds decode+replay),
main crate 570 tests pass, clippy -D warnings clean (crate + shared),
wasm clean, cargo fuzz build clean. Hand-run drives (indirect pair
escape, enum-Validation, unknown discriminator, trailing garbage) all
held.
This commit is contained in:
glm-5.3-flash committed 2026-09-30 07:02:35 +00:00
1 parent 16b9023f60
commit aef8d9f6ab
49 files changed
+1037 -2

No files matched your search

+2 -1
View File
@@ -1,4 +1,4 @@
//! Fuzz invariant logic for alktype's wave-1 targets. Kept out of the
//! Fuzz invariant logic for alktype's fuzz targets. Kept out of the
//! fuzz-target binaries so the corpus replay unit tests in the release
//! verification checklist can exercise the same invariant checks
//! against every committed corpus entry (the quinn CI pattern) without
@@ -8,3 +8,4 @@ pub mod bast_compile;
pub mod data_access;
pub mod layout_build;
pub mod read_opseq;
pub mod validate_pair;
+847
View File
@@ -0,0 +1,847 @@
//! Invariants for `validate_pair` (wave 3, target 5) — the integration
//! two-input harness: schema and bytes are both adversarial. Compile
//! once per exec in the fuzzer-picked mode (plus a cross-mode compile
//! for the mode-agreement lattice), then hammer the hostile buffer
//! through `validate_bytes`, an independent `materialize_*`, leaf
//! `read_field`s over the offset map, junk field paths, and (packed
//! mode) the sequential walk under the compiled plan.
//!
//! Pinned contract (read off the `validate_bytes` implementation —
//! `engine.rs:308`: materialize then plan-validate, errors propagated
//! verbatim):
//! - no-panic for any (doc, bytes) pair, either mode;
//! - mode agreement: aligned Ok ⇒ packed Ok; packed Ok with aligned
//! Err allowed only for the documented aligned-mode rejections
//! (ADR-006 non-final inline length-prefixed, ADR-008 TUnion, the
//! record offset-indirect rejection) surfacing as `Offset`;
//! - agreement lattice: `materialize` Err ⇒ `validate_bytes` Err with
//! the identical payload; `materialize` Ok ⇒ `validate_bytes`
//! equals the plan walk over the independently materialized `Value`;
//! - `validate_bytes` Ok ⇒ every offset-map leaf `read_field` over the
//! same buffer is Ok;
//! - unknown field paths always error (Offset in aligned, Access in
//! packed) echoing `field_path` verbatim, never panic;
//! - a successful materialization round-trips through `serde_json`
//! (structural equality — `preserve_order` keeps key order);
//! - the record-count spin bound holds through materialize too: a
//! hostile count fails fast with `Access` (fixtures, target-3 form).
use alktype::bast::BastDoc;
use alktype::materialize;
use alktype::read_plan::ReadPlan;
use alktype::{AlkTypeEngine, AlkTypeError, LayoutMode};
use arbitrary::Arbitrary;
use serde_json::Value;
/// The schema menu. Lanes 0–7 are well-formed and compile in at least
/// one mode; lanes 8–9 are hostile docs (missing root, `$ref` cycle).
/// Together they cover: the classic variable-length sandwich (aligned
/// only via `maxLength`), the aligned offset-indirect pair, the record
/// (the count-loop candidate), a TUnion (aligned-rejected), the
/// non-finite-float lane, an enum (the value-range Validation lane),
/// an all-fixed shape (both modes), and the reject lanes.
const MENUS: [&str; 10] = [
// 0: variable field sandwiched between tails — packed-valid,
// aligned-rejected (ADR-006, `s` is a non-final inline
// length-prefixed field).
r##"{"$defs": {"S": {"kind": "struct", "endian": "little", "fields": [
{ "name": "a", "kind": "uint8" },
{ "name": "n", "kind": "uint32" },
{ "name": "s", "kind": "string" },
{ "name": "tail", "kind": "uint8" }]}}}"##,
// 1: the same shape with a declared `maxLength` — valid in both
// modes (aligned: fixed reservation).
r##"{"$defs": {"S": {"kind": "struct", "endian": "little", "fields": [
{ "name": "a", "kind": "uint8" },
{ "name": "n", "kind": "uint32" },
{ "name": "s", "kind": "string", "maxLength": 64 },
{ "name": "tail", "kind": "uint8" }]}}}"##,
// 2: aligned offset-indirect bytes field (the §6 candidate-2 pair,
// end-to-end: the pair at the entry, the data region elsewhere in
// the buffer).
r##"{"$defs": {"S": {"kind": "struct", "endian": "little", "fields": [
{ "name": "id", "kind": "uint32" },
{ "name": "b", "kind": "bytes", "encoding": "offset-indirect",
"maxLength": 64 }]}}}"##,
// 3: record with string values — the materializer's count-loop.
r##"{"$defs": {"S": {"kind": "struct", "endian": "little", "fields": [
{ "name": "k", "kind": "uint32" },
{ "name": "rec", "kind": { "kind": "record", "values": "string" } }]}}}"##,
// 4: TUnion under a byte discriminator — packed-valid,
// aligned-rejected (ADR-008).
r##"{"$defs": {"S": {"kind": "struct", "endian": "little", "fields": [
{ "name": "pre", "kind": "uint16" },
{ "name": "un", "kind": { "$ref": "#/$defs/U" } }],
},
"U": {"kind": "union",
"discriminator": {"kind": "byte", "offset": 0, "type": "uint8"},
"mapping": {"0": {"$ref": "#/$defs/Small"}}},
"Small": {"kind": "struct", "fields": [{ "name": "v", "kind": "uint8" }]}}}"##,
// 5: float64 — the non-finite lane (NaN/Inf bytes must surface as
// Access, never silent 0.0/Null).
r##"{"$defs": {"S": {"kind": "struct", "endian": "little", "fields": [
{ "name": "tag", "kind": "uint8" },
{ "name": "f", "kind": "float64" }]}}}"##,
// 6: enum — the value-domain lane (out-of-range wire index
// materializes but must fail the plan walk with `Validation`).
r##"{"$defs": {"S": {"kind": "struct", "endian": "little", "fields": [
{ "name": "e", "kind": { "kind": "enum", "values": ["r", "w", "x"] } },
{ "name": "t", "kind": "uint8" }]}}}"##,
// 7: all-fixed array shape — valid in both modes, truncation menu
// host.
r##"{"$defs": {"S": {"kind": "struct", "endian": "big", "fields": [
{ "name": "hdr", "kind": "uint32" },
{ "name": "vals", "kind": { "kind": "array", "element": "uint16", "count": 2 } }]}}}"##,
// 8: hostile — empty `$defs` (no root definition).
r##"{"$defs": {}}"##,
// 9: hostile — `$ref` cycle through two definitions.
r##"{"$defs": {"S": {"kind": "struct", "fields": [
{ "name": "a", "kind": {"$ref": "#/$defs/T"} }],
},
"T": {"kind": "struct", "fields": [
{ "name": "b", "kind": {"$ref": "#/$defs/S"} }]}}}"##,
];
/// Junk field paths: never declared, must always error (Offset in
/// aligned, Access in packed) echoing the path back in `field_path`.
const JUNK_PATHS: [&str; 5] = ["", "__nope__", "a.", "s.payload", "x.y.z"];
#[derive(Debug, Arbitrary)]
pub enum DocLane {
Menu(u8),
Raw(Vec<u8>),
}
/// Post-decode transform of the hostile bytes (the read_opseq shape
/// set: raw, empty, truncate at n, fixed 64-byte zero pad).
#[derive(Debug, Arbitrary, Clone, Copy)]
pub enum Shape {
Raw,
Empty,
Truncate(u8),
Padded,
}
/// The typed fuzz input. `buffer` (the take-rest field) carries the
/// hostile buffer body.
#[derive(Debug, Arbitrary)]
pub struct PairInput {
lane: DocLane,
mode: bool,
shape: Shape,
buffer: Vec<u8>,
}
pub fn fuzz_validate_pair(data: &[u8]) {
let Ok(input) = PairInput::arbitrary_take_rest(arbitrary::Unstructured::new(data)) else {
return;
};
let (doc, root_name) = match &input.lane {
DocLane::Menu(i) => {
let text = MENUS[(*i as usize) % MENUS.len()];
let doc = match serde_json::from_str::<Value>(text) {
Ok(v) => v,
Err(_) => return,
};
(doc, "S".to_string())
}
DocLane::Raw(bytes) => {
// The genuinely fused lane: the attacker's doc is bytes of
// the same provenance as the buffer. Anything serde_json
// rejects is not an alktype surface.
let doc = match serde_json::from_slice::<Value>(bytes) {
Ok(v) => v,
Err(_) => return,
};
let root = doc
.get("root")
.and_then(Value::as_str)
.unwrap_or("S")
.to_string();
(doc, root)
}
};
let mode = if input.mode { LayoutMode::Packed } else { LayoutMode::Aligned };
let packed = AlkTypeEngine::compile(&doc, &root_name, LayoutMode::Packed, None);
let aligned = AlkTypeEngine::compile(&doc, &root_name, LayoutMode::Aligned, None);
assert_mode_agreement(&packed, &aligned);
let engine = match mode {
LayoutMode::Packed => packed,
LayoutMode::Aligned => aligned,
};
let Ok(engine) = engine else {
return;
};
assert_engine_shape(&engine);
let buffer = shape_buffer(&input.shape, &input.buffer);
junk_paths(&engine, &root_name);
drive_pair(&engine, &doc, &root_name, &buffer);
if engine.mode() == LayoutMode::Packed {
packed_walk(&engine, &buffer);
}
}
/// Cross-mode gate agreement (the wave-1 invariant, re-pinned over the
/// hostile-doc lanes at the pair level): the parse layers are
/// mode-independent, so aligned Ok ⇒ packed Ok; a packed Ok with
/// aligned Err is allowed only when the aligned walk rejects for its
/// documented mode-specific reasons (ADR-006/ADR-008/record
/// offset-indirect), surfacing as `Offset`.
fn assert_mode_agreement(
packed: &Result<AlkTypeEngine, AlkTypeError>,
aligned: &Result<AlkTypeEngine, AlkTypeError>,
) {
match (packed, aligned) {
(Ok(_), Ok(_)) => {}
(Err(p), Err(a)) => {
assert_clean(p);
assert_clean(a);
}
(Ok(_), Err(a)) => match a {
AlkTypeError::Offset { reason, .. } => assert!(
reason.contains("ADR-006")
|| reason.contains("ADR-008")
|| reason.contains("offset-indirect"),
"aligned compile rejection after a packed Ok must name a \
documented aligned-mode rejection (ADR-006/ADR-008 or the \
record offset-indirect rule), got: {reason}"
),
other => panic!(
"aligned compile rejection after a packed Ok must be an \
Offset-class mode rejection, got {other:?}"
),
},
(Err(_), Ok(_)) => {
panic!("aligned Ok ⇒ packed Ok must hold (the aligned walk is a \
strict subset of the packed walkers)")
}
}
}
fn assert_clean(err: &AlkTypeError) {
match err {
AlkTypeError::Schema(msg) => {
assert!(!msg.is_empty(), "Schema error carries a message");
}
AlkTypeError::Offset { reason, .. } | AlkTypeError::Access { reason, .. } => {
assert!(!reason.is_empty(), "Offset/Access errors carry a reason");
}
AlkTypeError::Validation(_) => {}
}
}
fn assert_engine_shape(engine: &AlkTypeEngine) {
match engine.mode() {
LayoutMode::Packed => {
assert!(engine.offset_map().is_none());
assert!(engine.sequential_reader().is_some(), "packed factory");
}
LayoutMode::Aligned => {
assert!(engine.offset_map().is_some());
assert!(engine.sequential_reader().is_none(), "aligned factory");
}
}
}
/// Unknown paths must always error, echo the path, and never panic.
fn junk_paths(engine: &AlkTypeEngine, _root: &str) {
for junk in JUNK_PATHS {
let err = match engine.read_field(&[], junk) {
Ok(_) => panic!("junk path {junk:?} must never resolve"),
Err(e) => e,
};
match &err {
AlkTypeError::Offset { field_path, .. } => {
assert_eq!(field_path, junk, "aligned unknown path echoes back");
}
AlkTypeError::Access { field_path, .. } => {
assert_eq!(field_path, junk, "packed read_field echoes back");
}
other => panic!("junk path must be Offset/Access, got {other:?}"),
}
assert_clean(&err);
}
}
fn shape_buffer(shape: &Shape, bytes: &[u8]) -> Vec<u8> {
match shape {
Shape::Raw => bytes.to_vec(),
Shape::Empty => Vec::new(),
Shape::Truncate(n) => bytes[..bytes.len().min(*n as usize)].to_vec(),
Shape::Padded => {
let mut v = bytes.to_vec();
v.extend_from_slice(&[0u8; 64]);
v
}
}
}
/// The pair lane: validate_bytes and an independent materialize must
/// compose to the same result, byte-leaf reads must agree under a
/// successful validate, and the materialized `Value` must be
/// serde-safe.
fn drive_pair(engine: &AlkTypeEngine, doc: &Value, root: &str, buffer: &[u8]) {
let vb = engine.validate_bytes(buffer);
let independent: Result<Value, AlkTypeError> = match engine.mode() {
LayoutMode::Packed => {
let plan = match ReadPlan::compile(doc, root) {
Ok(p) => p,
Err(e) => panic!("a compiled engine implies an equal plan compile: {e:?}"),
};
materialize::materialize_packed(&plan, buffer)
}
LayoutMode::Aligned => {
let offset_map = engine
.offset_map()
.expect("an aligned engine carries its offset map");
let bast_doc = match BastDoc::new(doc, root) {
Ok(d) => d,
Err(e) => panic!("a compiled engine implies an equal BastDoc: {e:?}"),
};
materialize::materialize_aligned(&bast_doc, buffer, offset_map)
}
};
match independent {
Err(e_mat) => {
assert_clean(&e_mat);
let Err(e_vb) = vb else {
panic!(
"materialize Err ⇒ validate_bytes Err must hold, got \
validate_bytes Ok while materializing failed: {e_mat:?}"
)
};
assert_eq!(
format!("{e_vb:?}"),
format!("{e_mat:?}"),
"validate_bytes propagates the materializer's error verbatim"
);
}
Ok(value) => {
let plan_res = engine.validation_plan().validate(&value);
match plan_res {
Ok(()) => assert!(
vb.is_ok(),
"materialize Ok + plan Ok ⇒ validate_bytes Ok, got {vb:?}"
),
Err(e_val) => {
assert_clean(&e_val);
let Err(ref e_vb) = vb else {
panic!(
"materialize Ok + plan Err ⇒ validate_bytes Err, got Ok"
)
};
assert_eq!(
format!("{e_vb:?}"),
format!("{e_val:?}"),
"validate_bytes on a materialized value is exactly the \
plan walk's verdict"
);
}
}
// validate_bytes Ok ⇒ every offset-map leaf reads back
// through `read_field` over the same buffer.
if vb.is_ok() {
if let Some(map) = engine.offset_map() {
for (path, entry) in map.iter() {
if matches!(
entry.meta.kind,
alktype::AlkTypeKind::Struct
| alktype::AlkTypeKind::Union
| alktype::AlkTypeKind::Array
| alktype::AlkTypeKind::Record
) {
continue;
}
match engine.read_field(buffer, path) {
Ok(_) => {}
Err(e) => panic!(
"validate_bytes Ok ⇒ read_field({path}) Ok, got {e:?}"
),
}
assert!(
entry.range.end <= buffer.len(),
"a validated leaf sits inside the buffer"
);
}
}
}
// serde-safety: the output survives to_vec + parse back,
// structurally equal (preserve_order keeps key order).
let bytes = serde_json::to_vec(&value)
.expect("a materialized Value is serde-serializable");
let back = serde_json::from_slice::<Value>(&bytes)
.expect("a materialized Value survives its own JSON encoding");
assert_eq!(value, back, "materialize output serde round-trips");
}
}
}
/// The packed walk (packed mode only): plan order over a possibly
/// hostile buffer under the spin bound, errors always clean.
fn packed_walk(engine: &AlkTypeEngine, buffer: &[u8]) {
let Some(mut reader) = engine.sequential_reader() else {
return;
};
let field_count = reader.plan().fields().len();
let bound = walk_bound(buffer.len(), field_count);
let mut steps = 0usize;
let mut names = Vec::new();
loop {
steps += 1;
if steps > bound {
panic!(
"the pair walk exceeded the spin bound at step {steps} over a \
{len}-byte buffer with {fields} fields",
len = buffer.len(),
fields = field_count
);
}
match reader.read_next(buffer) {
Ok(Some((name, _))) => {
assert!(reader.position() <= buffer.len(), "cursor in bounds");
names.push(name.to_string());
}
Ok(None) => break,
Err(e) => {
assert_clean(&e);
break;
}
}
}
if names.len() == field_count {
let expected: Vec<String> =
reader.plan().fields().iter().map(|f| f.name().to_string()).collect();
assert_eq!(
names, expected,
"a full walk over a validating buffer reports fields in plan order"
);
}
}
/// Same bound formula as the read_opseq target: a successful step
/// advances the cursor, and every inner iteration verifies ≥ 4 bytes.
fn walk_bound(buffer_len: usize, field_count: usize) -> usize {
buffer_len.saturating_add(field_count).saturating_add(2)
}
#[cfg(test)]
mod corpus_replay {
use super::*;
#[test]
fn committed_corpus_replays_through_the_invariants() {
let corpus =
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("../corpus/validate_pair");
let mut count = 0usize;
for entry in std::fs::read_dir(&corpus).expect("corpus directory is committed") {
let path = entry.expect("corpus entry readable").path();
let data = std::fs::read(&path).expect("corpus entry readable");
fuzz_validate_pair(&data);
count += 1;
}
assert!(
count >= 10,
"committed seed corpus is present, found {count}"
);
}
/// Byte encodings against the pinned arbitrary 1.4.2 derive layout:
/// field order { lane: DocLane, mode: bool, shape: Shape, buffer:
/// Vec<u8> } with buffer the take-rest field. DocLane has 2
/// variants (Menu(u8), Raw(Vec<u8>)); Shape has 4 (Raw, Empty,
/// Truncate(u8), Padded). Enum variant selection is the 4-byte LE
/// multiply-shift u32; Vec<u8> elements are keep-going byte +
/// element bytes with a 0x00 stop; bools are one byte (& 1).
mod enc {
pub fn variant_u32(k: u64, count: u64) -> [u8; 4] {
let mut v = (k << 32).div_ceil(count);
while (v.wrapping_mul(count) >> 32) != k {
v += 1;
}
u32::try_from(v).expect("k < count always admits a < 2^32 v").to_le_bytes()
}
pub const LANE_MENU: u64 = 0;
pub const LANE_RAW: u64 = 1;
pub const SHAPE_RAW: u64 = 0;
pub const SHAPE_EMPTY: u64 = 1;
pub const SHAPE_TRUNCATE: u64 = 2;
pub const SHAPE_PADDED: u64 = 3;
pub fn lane_menu(index: u8) -> Vec<u8> {
let mut v = variant_u32(LANE_MENU, 2).to_vec();
v.push(index);
v
}
pub fn lane_raw(doc: &[u8]) -> Vec<u8> {
let mut v = variant_u32(LANE_RAW, 2).to_vec();
for b in doc {
v.push(0x01);
v.push(*b);
}
v.push(0x00);
v
}
pub fn shape_field(tag: u64) -> Vec<u8> {
variant_u32(tag, 4).to_vec()
}
pub fn shape_truncate(n: u8) -> Vec<u8> {
let mut v = shape_field(SHAPE_TRUNCATE);
v.push(n);
v
}
pub fn pair(lane: Vec<u8>, aligned: bool, shape: Vec<u8>, buffer: &[u8]) -> Vec<u8> {
let mut v = lane;
v.push(u8::from(aligned)); // mode: false = packed, true = aligned
v.extend_from_slice(&shape);
for b in buffer {
v.push(0x01);
v.push(*b);
}
v.push(0x00);
v
}
}
/// Buffers matching the menu schemas (little-endian where declared;
/// menu 7 is big-endian).
mod buffers {
/// Menu 0/1 packed body: u8 a | u32 n | string s | u8 tail.
pub fn sandwich_packed(s: &[u8]) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(0x11);
buf.extend_from_slice(&7u32.to_le_bytes());
buf.extend_from_slice(&(s.len() as u32).to_le_bytes());
buf.extend_from_slice(s);
buf.push(0x22);
buf
}
/// Menu 1 aligned body with `s` as a fixed `maxLength` region
/// (64 bytes reserved at the entry, 8..72): a at 0, n at 4,
/// s prefix at 8, s data inline 12..12+len, tail at 72 (the
/// offset pin test below keeps these honest).
pub fn sandwich_aligned(s: &[u8]) -> Vec<u8> {
let mut buf = vec![0u8; 12 + 64 + 1];
buf[0] = 0x11;
buf[4..8].copy_from_slice(&7u32.to_le_bytes());
buf[8..12].copy_from_slice(&(s.len() as u32).to_le_bytes());
buf[12..12 + s.len()].copy_from_slice(s);
buf[72] = 0x22;
buf
}
/// Menu 2 aligned offset-indirect body: u32 id | u32 data_off |
/// u32 data_len | data region at data_off.
pub fn indirect(data: &[u8]) -> Vec<u8> {
let mut buf = vec![0u8; 12 + data.len()];
buf[0..4].copy_from_slice(&0x0Bu32.to_le_bytes());
buf[4..8].copy_from_slice(&12u32.to_le_bytes());
buf[8..12].copy_from_slice(&(data.len() as u32).to_le_bytes());
buf[12..].copy_from_slice(data);
buf
}
/// Menu 3 packed body: u32 k | record count | entries.
pub fn record(count: u32, entries: &[(&[u8], u32)]) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&1u32.to_le_bytes());
buf.extend_from_slice(&count.to_le_bytes());
for (key, value) in entries {
buf.extend_from_slice(&(key.len() as u32).to_le_bytes());
buf.extend_from_slice(key);
buf.extend_from_slice(&value.to_le_bytes());
}
buf
}
/// Menu 4 packed body: u16 pre | disc byte | variant u8.
pub fn union(disc: u8, v: u8) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&0x0102u16.to_le_bytes());
buf.push(disc);
buf.push(v);
buf
}
/// Menu 5 body: u8 tag | f64 (chosen bit pattern).
pub fn float(bits: u64) -> Vec<u8> {
let mut buf = Vec::new();
buf.push(0x07);
buf.extend_from_slice(&bits.to_le_bytes());
buf
}
/// Menu 6 body: u32 enum index | u8 t.
pub fn enum_val(index: u32) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&index.to_le_bytes());
buf.push(0x09);
buf
}
/// Menu 7 big-endian body: u32 hdr | u16 vals[0] | u16 vals[1].
pub fn big_endian(hdr: u32, a: u16, b: u16) -> Vec<u8> {
let mut buf = Vec::new();
buf.extend_from_slice(&hdr.to_be_bytes());
buf.extend_from_slice(&a.to_be_bytes());
buf.extend_from_slice(&b.to_be_bytes());
buf
}
}
#[test]
fn decode_lands_on_the_intended_variants() {
fn decode(data: &[u8]) -> (String, bool, String, usize) {
let input = PairInput::arbitrary_take_rest(arbitrary::Unstructured::new(data))
.expect("the encoding decodes");
let lane = match &input.lane {
DocLane::Menu(i) => format!("M{i}"),
DocLane::Raw(_) => "Raw".to_string(),
};
let shape = match input.shape {
Shape::Raw => "Raw".to_string(),
Shape::Empty => "Empty".to_string(),
Shape::Truncate(n) => format!("T{n}"),
Shape::Padded => "P".to_string(),
};
(lane, input.mode, shape, input.buffer.len())
}
let seed =
enc::pair(enc::lane_menu(3), true, enc::shape_truncate(5), &[9, 9, 9]);
assert_eq!(decode(&seed), ("M3".to_string(), true, "T5".to_string(), 3));
let seed = enc::pair(
enc::lane_raw(br#"{"$defs":{}}"#),
false,
enc::shape_field(enc::SHAPE_PADDED),
&[1],
);
assert_eq!(decode(&seed), ("Raw".to_string(), false, "P".to_string(), 1));
let seed = enc::pair(enc::lane_menu(0), false, enc::shape_field(enc::SHAPE_RAW), &[]);
assert_eq!(decode(&seed), ("M0".to_string(), false, "Raw".to_string(), 0));
}
#[test]
fn valid_valid_both_modes_agree_and_validate() {
// Menu 0 packed with a complete body: validate Ok, independent
// materialize agrees, leaves read back, serde round-trips.
fuzz_validate_pair(&enc::pair(
enc::lane_menu(0),
false,
enc::shape_field(enc::SHAPE_RAW),
&buffers::sandwich_packed(b"hi"),
));
// Menu 1 (maxLength reservation) in aligned mode.
fuzz_validate_pair(&enc::pair(
enc::lane_menu(1),
true,
enc::shape_field(enc::SHAPE_RAW),
&buffers::sandwich_aligned(b"hi"),
));
// Menu 2 aligned offset-indirect: the full pair contract.
fuzz_validate_pair(&enc::pair(
enc::lane_menu(2),
true,
enc::shape_field(enc::SHAPE_RAW),
&buffers::indirect(b"data"),
));
// Menu 3 packed record with two entries.
fuzz_validate_pair(&enc::pair(
enc::lane_menu(3),
false,
enc::shape_field(enc::SHAPE_RAW),
&buffers::record(2, &[(b"k1", 5), (b"k2", 6)]),
));
// Menu 4 packed union (disc 0 → Small). Valid in packed.
fuzz_validate_pair(&enc::pair(
enc::lane_menu(4),
false,
enc::shape_field(enc::SHAPE_RAW),
&buffers::union(0, 0x33),
));
// Menu 7 big-endian fixed shape, both modes.
let be = buffers::big_endian(0x0102_0304, 0x0A0B, 0x0C0D);
fuzz_validate_pair(&enc::pair(enc::lane_menu(7), false, enc::shape_field(enc::SHAPE_RAW), &be));
fuzz_validate_pair(&enc::pair(enc::lane_menu(7), true, enc::shape_field(enc::SHAPE_RAW), &be));
}
#[test]
fn hostile_schema_with_valid_bytes_is_a_clean_rejection() {
// Menus 8/9 never compile; the byte lane must not be driven.
fuzz_validate_pair(&enc::pair(
enc::lane_menu(8),
false,
enc::shape_field(enc::SHAPE_RAW),
&buffers::sandwich_packed(b"hi"),
));
fuzz_validate_pair(&enc::pair(
enc::lane_menu(9),
true,
enc::shape_field(enc::SHAPE_RAW),
&[0xAA; 8],
));
// Raw-lane hostile doc with a valid sibling buffer.
fuzz_validate_pair(&enc::pair(
enc::lane_raw(br#"{"$defs":{}}"#),
false,
enc::shape_field(enc::SHAPE_RAW),
&buffers::sandwich_packed(b"hi"),
));
}
#[test]
fn valid_schema_with_hostile_bytes_agrees_on_rejection() {
// Truncation sweep over the packed sandwich: every prefix must
// materialize-fail with the error propagated verbatim by
// validate_bytes.
let body = buffers::sandwich_packed(b"hello");
for n in 1..body.len() {
fuzz_validate_pair(&enc::pair(
enc::lane_menu(0),
false,
enc::shape_truncate(n as u8),
&body,
));
}
// Aligned truncated at the record/union boundaries (empty +
// padded shapes over menu 2 and 3).
fuzz_validate_pair(&enc::pair(
enc::lane_menu(2),
true,
enc::shape_field(enc::SHAPE_EMPTY),
&buffers::indirect(b"data"),
));
fuzz_validate_pair(&enc::pair(
enc::lane_menu(3),
false,
enc::shape_field(enc::SHAPE_PADDED),
&buffers::record(2, &[(b"k1", 5), (b"k2", 6)]),
));
}
#[test]
fn the_aligned_sandwich_uses_the_documented_offsets() {
// Pin the aligned layout offsets of menu 1 so the
// `sandwich_aligned` fixture stays honest across engine
// changes: a (u8), n (u32 aligned to 4), s reservation
// (maxLength 64 reserved inline at the entry, 8..72), tail at
// 72. The prefix sits at the reservation start; the string
// data follows it.
let doc: serde_json::Value = serde_json::from_str(MENUS[1])
.expect("menu 1 parses");
let engine = AlkTypeEngine::compile(&doc, "S", LayoutMode::Aligned, None)
.expect("menu 1 compiles aligned");
let map = engine.offset_map().expect("aligned engine");
assert_eq!(map.get("a").expect("a").range.start, 0);
assert_eq!(map.get("n").expect("n").range.start, 4);
let s = map.get("s").expect("s");
assert_eq!(s.range, alktype::ByteRange { start: 8, end: 72 });
assert_eq!(map.get("tail").expect("tail").range.start, 72);
assert_eq!(map.total_size(), 76);
}
#[test]
fn non_finite_floats_are_access_not_silent_values() {
// NaN through materialize must be an Access error naming the
// reason — never a silent 0.0/Null in the output (the plan's
// §3 target-5 non-finite invariant, src/materialize.rs's
// number_from_f64).
fuzz_validate_pair(&enc::pair(
enc::lane_menu(5),
false,
enc::shape_field(enc::SHAPE_RAW),
&buffers::float(0x7FF8_0000_0000_0000), // f64 NaN
));
fuzz_validate_pair(&enc::pair(
enc::lane_menu(5),
true,
enc::shape_field(enc::SHAPE_RAW),
&buffers::float(0x7FF0_0000_0000_0000), // f64 Inf
));
}
#[test]
fn out_of_range_enum_is_a_validation_error_not_materialization_loss() {
// Enum index 99: bytes decode fine (u32), materialize Ok, the
// plan walk must reject with Validation — and validate_bytes
// must surface exactly that.
fuzz_validate_pair(&enc::pair(
enc::lane_menu(6),
false,
enc::shape_field(enc::SHAPE_RAW),
&buffers::enum_val(99),
));
}
#[test]
fn hostile_record_count_fails_fast_with_access() {
// The §6 candidate-1 wave-3 form: count far above the wire can
// afford. materialize (both directly and through
// validate_bytes) must fail fast with Access naming the
// record — the spin bound lives in the target's walk bound on
// the sequential side and in this fixture's fast-fail on the
// materialize side.
fuzz_validate_pair(&enc::pair(
enc::lane_menu(3),
false,
// Truncate right after the count: the first iteration has
// nothing to verify.
enc::shape_truncate(8),
&{
let mut buf =
buffers::record(0xFFFF_FFFF, &[]);
buf.truncate(8);
buf
},
));
// count = 65536 with zero everything: every iteration must
// verify ≥ 4 bytes, so it fails inside the byte budget.
fuzz_validate_pair(&enc::pair(
enc::lane_menu(3),
false,
enc::shape_field(enc::SHAPE_RAW),
&{
let mut buf = buffers::record(0x0001_0000, &[]);
buf.extend_from_slice(&[0u8; 16]);
buf
},
));
}
#[test]
fn packed_lane_rejects_read_field_and_union_aligns_reject() {
// Mode-agreement pins at the harness level: menu 4 (TUnion)
// compiles packed but must fail aligned (ADR-008); menu 0 must
// fail aligned (ADR-006); both must compile-packed with a
// valid body.
fuzz_validate_pair(&enc::pair(
enc::lane_menu(4),
true,
enc::shape_field(enc::SHAPE_RAW),
&buffers::union(0, 0x33),
));
fuzz_validate_pair(&enc::pair(
enc::lane_menu(0),
true,
enc::shape_field(enc::SHAPE_RAW),
&buffers::sandwich_packed(b"hi"),
));
}
}