verit 0.2.0

Exavian Veritate — zero-copy, self-describing, schema-evolvable binary serialization, safe on untrusted bytes, no unsafe, byte-identical across independent implementations.
Documentation
//! Proof obligation 1: every type round-trips through encode → zero-copy read.

use verit::{encode, Dt, Error, Message, Ref, Resolver, Schema, SchemaBuilder, SchemaMode, Value};

fn kitchen_schema() -> Schema {
    SchemaBuilder::new()
        .add_enum("Color", vec![(0, "Red"), (1, "Green"), (2, "Blue")])
        .add_struct("Point", vec![(1, "x", Dt::F64), (2, "y", Dt::F64)])
        .add_struct(
            "Everything",
            vec![
                (1, "b", Dt::Bool),
                (2, "u8v", Dt::U8),
                (3, "u16v", Dt::U16),
                (4, "u32v", Dt::U32),
                (5, "u64v", Dt::U64),
                (6, "i8v", Dt::I8),
                (7, "i16v", Dt::I16),
                (8, "i32v", Dt::I32),
                (9, "i64v", Dt::I64),
                (10, "f32v", Dt::F32),
                (11, "f64v", Dt::F64),
                (12, "name", Dt::Str),
                (13, "blob", Dt::Bytes),
                (14, "color", Dt::named("Color")),
                (15, "origin", Dt::named("Point")),
                (16, "nums", Dt::list(Dt::U32)),
                (17, "tags", Dt::list(Dt::Str)),
                (18, "points", Dt::list(Dt::named("Point"))),
                (19, "matrix", Dt::list(Dt::list(Dt::U8))),
            ],
        )
        .build("Everything")
        .unwrap()
}

fn everything() -> Value {
    Value::Struct(vec![
        (1, Value::Bool(true)),
        (2, Value::U8(200)),
        (3, Value::U16(60_000)),
        (4, Value::U32(4_000_000_000)),
        (5, Value::U64(u64::MAX - 1)),
        (6, Value::I8(-100)),
        (7, Value::I16(-30_000)),
        (8, Value::I32(-2_000_000_000)),
        (9, Value::I64(i64::MIN + 1)),
        (10, Value::F32(3.5)),
        (11, Value::F64(-2.25)),
        (12, Value::str("héllo wörld 🚀")),
        (13, Value::Bytes(vec![0xDE, 0xAD, 0xBE, 0xEF])),
        (14, Value::Enum(2)),
        (
            15,
            Value::Struct(vec![(1, Value::F64(1.5)), (2, Value::F64(-0.5))]),
        ),
        (
            16,
            Value::List(vec![Value::U32(1), Value::U32(2), Value::U32(3)]),
        ),
        (
            17,
            Value::List(vec![Value::str("a"), Value::str(""), Value::str("ccc")]),
        ),
        (
            18,
            Value::List(vec![
                Value::Struct(vec![(1, Value::F64(1.0)), (2, Value::F64(2.0))]),
                Value::Struct(vec![(1, Value::F64(3.0))]), // y absent
            ]),
        ),
        (
            19,
            Value::List(vec![
                Value::List(vec![Value::U8(1), Value::U8(2)]),
                Value::List(vec![]),
            ]),
        ),
    ])
}

#[test]
fn roundtrip_every_type() {
    let schema = kitchen_schema();
    let bytes = encode(&schema, &everything(), SchemaMode::HashOnly).unwrap();
    let msg = Message::parse(&bytes).unwrap();
    assert_eq!(msg.schema_id(), schema.id());
    assert!(!msg.has_inline_schema());

    let resolver = Resolver::identity(&schema).unwrap();
    let root = msg.root(&resolver).unwrap();

    assert_eq!(root.get_bool(1).unwrap(), Some(true));
    assert_eq!(root.get_u8(2).unwrap(), Some(200));
    assert_eq!(root.get_u16(3).unwrap(), Some(60_000));
    assert_eq!(root.get_u32(4).unwrap(), Some(4_000_000_000));
    assert_eq!(root.get_u64(5).unwrap(), Some(u64::MAX - 1));
    assert_eq!(root.get_i8(6).unwrap(), Some(-100));
    assert_eq!(root.get_i16(7).unwrap(), Some(-30_000));
    assert_eq!(root.get_i32(8).unwrap(), Some(-2_000_000_000));
    assert_eq!(root.get_i64(9).unwrap(), Some(i64::MIN + 1));
    assert_eq!(root.get_f32(10).unwrap(), Some(3.5));
    assert_eq!(root.get_f64(11).unwrap(), Some(-2.25));
    assert_eq!(root.get_str(12).unwrap(), Some("héllo wörld 🚀"));
    assert_eq!(
        root.get_bytes(13).unwrap(),
        Some(&[0xDE, 0xAD, 0xBE, 0xEF][..])
    );
    assert_eq!(root.get_enum(14).unwrap(), Some(2));

    let origin = root.get_struct(15).unwrap().unwrap();
    assert_eq!(origin.get_f64(1).unwrap(), Some(1.5));
    assert_eq!(origin.get_f64(2).unwrap(), Some(-0.5));

    let nums = root.get_list(16).unwrap().unwrap();
    assert_eq!(nums.len(), 3);
    assert!(matches!(nums.get(0).unwrap(), Ref::U32(1)));
    assert!(matches!(nums.get(2).unwrap(), Ref::U32(3)));
    assert!(matches!(nums.get(3), Err(Error::IndexOutOfBounds)));

    let tags = root.get_list(17).unwrap().unwrap();
    let collected: Vec<&str> = tags
        .iter()
        .map(|r| match r.unwrap() {
            Ref::Str(s) => s,
            other => panic!("expected string element, got {}", other.kind()),
        })
        .collect();
    assert_eq!(collected, vec!["a", "", "ccc"]);

    let points = root.get_list(18).unwrap().unwrap();
    assert_eq!(points.len(), 2);
    let p0 = match points.get(0).unwrap() {
        Ref::Struct(s) => s,
        other => panic!("expected struct element, got {}", other.kind()),
    };
    assert_eq!(p0.get_f64(1).unwrap(), Some(1.0));
    assert_eq!(p0.get_f64(2).unwrap(), Some(2.0));
    let p1 = match points.get(1).unwrap() {
        Ref::Struct(s) => s,
        other => panic!("expected struct element, got {}", other.kind()),
    };
    assert_eq!(p1.get_f64(1).unwrap(), Some(3.0));
    assert_eq!(p1.get_f64(2).unwrap(), None, "absent field in list element");

    let matrix = root.get_list(19).unwrap().unwrap();
    assert_eq!(matrix.len(), 2);
    let row0 = match matrix.get(0).unwrap() {
        Ref::List(l) => l,
        other => panic!("expected list element, got {}", other.kind()),
    };
    assert_eq!(row0.len(), 2);
    assert!(matches!(row0.get(1).unwrap(), Ref::U8(2)));
    let row1 = match matrix.get(1).unwrap() {
        Ref::List(l) => l,
        other => panic!("expected list element, got {}", other.kind()),
    };
    assert_eq!(row1.len(), 0);
    assert!(row1.is_empty());
}

#[test]
fn absent_fields_read_none() {
    let schema = kitchen_schema();
    // Only field 4 set; everything else absent.
    let value = Value::Struct(vec![(4, Value::U32(7))]);
    let bytes = encode(&schema, &value, SchemaMode::HashOnly).unwrap();
    let msg = Message::parse(&bytes).unwrap();
    let resolver = Resolver::identity(&schema).unwrap();
    let root = msg.root(&resolver).unwrap();
    assert_eq!(root.get_u32(4).unwrap(), Some(7));
    assert_eq!(root.get_bool(1).unwrap(), None);
    assert_eq!(root.get_str(12).unwrap(), None);
    assert_eq!(root.get_struct(15).unwrap().map(|_| ()), None);
    assert_eq!(root.get_list(16).unwrap().map(|_| ()), None);
}

#[test]
fn open_enum_roundtrips_unknown_values() {
    let schema = kitchen_schema();
    let value = Value::Struct(vec![(14, Value::Enum(999))]);
    let bytes = encode(&schema, &value, SchemaMode::HashOnly).unwrap();
    let msg = Message::parse(&bytes).unwrap();
    let resolver = Resolver::identity(&schema).unwrap();
    let root = msg.root(&resolver).unwrap();
    assert_eq!(root.get_enum(14).unwrap(), Some(999));
}

#[test]
fn unknown_field_id_fails_encode() {
    let schema = kitchen_schema();
    let value = Value::Struct(vec![(99, Value::U32(1))]);
    assert!(matches!(
        encode(&schema, &value, SchemaMode::HashOnly),
        Err(Error::UnknownFieldId(99))
    ));
}

#[test]
fn duplicate_field_fails_encode() {
    let schema = kitchen_schema();
    let value = Value::Struct(vec![(4, Value::U32(1)), (4, Value::U32(2))]);
    assert!(matches!(
        encode(&schema, &value, SchemaMode::HashOnly),
        Err(Error::DuplicateField(4))
    ));
}

#[test]
fn type_mismatch_fails_encode() {
    let schema = kitchen_schema();
    let value = Value::Struct(vec![(12, Value::U32(1))]); // field 12 is a string
    assert!(matches!(
        encode(&schema, &value, SchemaMode::HashOnly),
        Err(Error::TypeMismatch { .. })
    ));
}

#[test]
fn garbage_input_is_rejected_not_panicked() {
    assert!(matches!(Message::parse(b"nope"), Err(Error::Truncated)));
    // A full-length (>= 32-byte header) buffer with the wrong magic.
    assert!(matches!(
        Message::parse(b"XXXX0123456789abcdef0123456789ab"),
        Err(Error::BadMagic)
    ));
    // Valid header, root offset pointing into nowhere: reads must error, not panic.
    let schema = kitchen_schema();
    let value = Value::Struct(vec![(12, Value::str("hi"))]);
    let mut bytes = encode(&schema, &value, SchemaMode::HashOnly).unwrap();
    bytes[24..28].copy_from_slice(&u32::MAX.to_le_bytes()); // corrupt root offset (now at 24)
    let msg = Message::parse(&bytes).unwrap();
    let resolver = Resolver::identity(&schema).unwrap();
    let root = msg.root(&resolver).unwrap();
    assert!(root.get_str(12).is_err());
}

/// the wire spec §1: the wire version is in the magic, and undefined header bits are
/// refused — a message can never be silently misread as a different version or
/// with semantics this build doesn't implement.
#[test]
fn version_and_header_are_enforced() {
    let schema = kitchen_schema();
    let value = Value::Struct(vec![(12, Value::str("hi"))]);
    let good = encode(&schema, &value, SchemaMode::HashOnly).unwrap();
    // Baseline: the untouched message parses.
    assert!(Message::parse(&good).is_ok());

    // A "VRT" message with a different version digit → UnsupportedVersion,
    // distinct from BadMagic, and reporting both the found and supported byte.
    let mut wrong_ver = good.clone();
    wrong_ver[3] = b'3';
    assert!(matches!(
        Message::parse(&wrong_ver),
        Err(Error::UnsupportedVersion {
            found: b'3',
            supported: b'2'
        })
    ));

    // Not a Veritate message at all → BadMagic.
    let mut not_vrt = good.clone();
    not_vrt[0..3].copy_from_slice(b"ZZZ");
    assert!(matches!(Message::parse(&not_vrt), Err(Error::BadMagic)));

    // An undefined flag bit set → MalformedHeader (bit 0 is the only one defined).
    let mut bad_flag = good.clone();
    bad_flag[4] = 0b0000_0010; // bit 1, undefined in v2
    assert!(matches!(
        Message::parse(&bad_flag),
        Err(Error::MalformedHeader(_))
    ));

    // A nonzero reserved field → MalformedHeader.
    let mut bad_reserved = good.clone();
    bad_reserved[6] = 1;
    assert!(matches!(
        Message::parse(&bad_reserved),
        Err(Error::MalformedHeader(_))
    ));
}