vyre-foundation 0.4.1

Foundation layer: IR, type system, memory model, wire format. Zero application semantics. Part of the vyre GPU compiler.
Documentation
//! Adversarial tests for wire-format corruption detection.
//!
//! The wire decoder must reject tampered or corrupted payloads with
//! structured errors rather than panics or silent acceptance.

use vyre::ir::Program;

/// Build a minimal valid program and encode it.
fn minimal_program_bytes() -> Vec<u8> {
    let program = Program::wrapped(
        vec![vyre::ir::BufferDecl::output("out", 0, vyre::ir::DataType::U32).with_count(1)],
        [1, 1, 1],
        vec![vyre::ir::Node::Return],
    );
    program.to_wire().expect("Fix: minimal program must encode")
}

#[test]
fn wire_decoder_rejects_corrupted_checksum() {
    let mut bytes = minimal_program_bytes();
    // Corrupt a single byte in the body (after the 40-byte header).
    // The header contains: magic(4) + version(2) + flags(2) + checksum(32) = 40 bytes.
    if bytes.len() > 45 {
        bytes[45] = bytes[45].wrapping_add(1);
    }

    let result = std::panic::catch_unwind(|| Program::from_wire(&bytes));
    match result {
        Ok(Ok(_)) => panic!("wire decoder accepted a corrupted checksum (must reject)"),
        Ok(Err(e)) => {
            assert!(
                e.to_string().contains("IntegrityMismatch") || e.to_string().contains("Fix:"),
                "corrupt checksum must produce IntegrityMismatch or actionable error, got: {e}"
            );
        }
        Err(_) => panic!("wire decoder panicked on corrupt checksum instead of returning error"),
    }
}

#[test]
fn wire_decoder_rejects_truncated_body() {
    let bytes = minimal_program_bytes();
    // Truncate the body but leave the header intact — the checksum
    // will still be valid for a shorter body, but the decoder should
    // hit EOF before finishing node parsing.
    let truncated = &bytes[..bytes.len().saturating_sub(4)];

    let result = std::panic::catch_unwind(|| Program::from_wire(truncated));
    match result {
        Ok(Ok(_)) => panic!("wire decoder accepted a truncated body (must reject)"),
        Ok(Err(_)) => { /* expected structured error */ }
        Err(_) => panic!("wire decoder panicked on truncated body instead of returning error"),
    }
}

#[test]
fn wire_decoder_rejects_wrong_magic() {
    let mut bytes = minimal_program_bytes();
    // Corrupt the magic bytes at the start.
    if bytes.len() >= 4 {
        bytes[0] = b'X';
        bytes[1] = b'X';
        bytes[2] = b'X';
        bytes[3] = b'X';
    }

    let result = std::panic::catch_unwind(|| Program::from_wire(&bytes));
    match result {
        Ok(Ok(_)) => panic!("wire decoder accepted wrong magic (must reject)"),
        Ok(Err(e)) => {
            assert!(
                e.to_string().contains("MagicMismatch") || e.to_string().contains("Fix:"),
                "wrong magic must produce MagicMismatch or actionable error, got: {e}"
            );
        }
        Err(_) => panic!("wire decoder panicked on wrong magic instead of returning error"),
    }
}

#[test]
fn wire_decoder_rejects_empty_input() {
    let result = std::panic::catch_unwind(|| Program::from_wire(&[]));
    match result {
        Ok(Ok(_)) => panic!("wire decoder accepted empty input (must reject)"),
        Ok(Err(_)) => { /* expected structured error */ }
        Err(_) => panic!("wire decoder panicked on empty input instead of returning error"),
    }
}

#[test]
fn wire_decoder_rejects_header_only() {
    let bytes = minimal_program_bytes();
    // Keep only the 40-byte header, drop all body bytes.
    let header_only = &bytes[..40.min(bytes.len())];

    let result = std::panic::catch_unwind(|| Program::from_wire(header_only));
    match result {
        Ok(Ok(_)) => panic!("wire decoder accepted header-only input (must reject)"),
        Ok(Err(_)) => { /* expected structured error */ }
        Err(_) => panic!("wire decoder panicked on header-only input instead of returning error"),
    }
}