frame-core 0.2.0

Component model, lifecycle, process isolation, and WASM module host
Documentation
//! Boundary and property acceptance for `frame_core::runtime::mailbox_integer`.
//!
//! The fold must place ANY identifier bytes inside
//! `reserved_below ..= Term::SMALL_INT_MAX` — deterministically, consuming
//! every input byte (the hand-rolled derivation it replaces truncated to the
//! first eight bytes). The reserved-range parameter is validated loudly:
//! a negative bound or one beyond the mailbox integer domain is a typed
//! refusal, never a clamp.

use beamr::term::Term;
use frame_core::runtime::{RuntimeError, mailbox_integer};

/// Deterministic linear congruential generator (Numerical Recipes constants)
/// so the property sweep is reproducible without adding a dependency.
struct Lcg(u64);

impl Lcg {
    fn next(&mut self) -> u64 {
        self.0 = self
            .0
            .wrapping_mul(6_364_136_223_846_793_005)
            .wrapping_add(1_442_695_040_888_963_407);
        self.0
    }
}

fn in_range(value: i64, reserved_below: i64) -> bool {
    value >= reserved_below && value <= Term::SMALL_INT_MAX
}

#[test]
fn refuses_negative_reserved_bounds() {
    for reserved_below in [-1_i64, -42, i64::MIN] {
        let refused = mailbox_integer(b"id", reserved_below);
        assert!(
            matches!(
                refused,
                Err(RuntimeError::MailboxReservedRangeInvalid { .. })
            ),
            "reserved_below={reserved_below} must be refused"
        );
    }
}

#[test]
fn refuses_bounds_beyond_the_mailbox_integer_domain() {
    for reserved_below in [Term::SMALL_INT_MAX + 1, i64::MAX] {
        let refused = mailbox_integer(b"id", reserved_below);
        assert!(
            matches!(
                refused,
                Err(RuntimeError::MailboxReservedRangeInvalid { .. })
            ),
            "reserved_below={reserved_below} must be refused"
        );
    }
}

#[test]
fn maximal_reserved_bound_pins_the_single_remaining_value() -> Result<(), Box<dyn std::error::Error>>
{
    // With everything below SMALL_INT_MAX reserved, exactly one value remains.
    for bytes in [b"".as_slice(), b"a", b"any identifier at all"] {
        assert_eq!(
            mailbox_integer(bytes, Term::SMALL_INT_MAX)?,
            Term::SMALL_INT_MAX
        );
    }
    Ok(())
}

#[test]
fn empty_and_short_identifiers_still_fold_into_range() -> Result<(), Box<dyn std::error::Error>> {
    for bytes in [b"".as_slice(), b"\x00", b"\xff", b"abc"] {
        for reserved_below in [0_i64, 1, 3, Term::SMALL_INT_MAX - 1] {
            let value = mailbox_integer(bytes, reserved_below)?;
            assert!(
                in_range(value, reserved_below),
                "bytes={bytes:?} reserved_below={reserved_below} produced {value}"
            );
        }
    }
    Ok(())
}

#[test]
fn fold_is_deterministic() -> Result<(), Box<dyn std::error::Error>> {
    let bytes = b"0192a4c6-2b7e-4d5f-8a90-1234567890ab";
    let first = mailbox_integer(bytes, 3)?;
    let second = mailbox_integer(bytes, 3)?;
    assert_eq!(first, second);
    Ok(())
}

/// The fold consumes every byte: flipping a byte BEYOND the first eight must
/// change the result (the derivation this replaces read only bytes 0..8).
#[test]
fn fold_consumes_bytes_beyond_the_first_eight() -> Result<(), Box<dyn std::error::Error>> {
    let base = *b"0123456789abcdef";
    let mut ninth_differs = base;
    ninth_differs[8] ^= 0xff;
    let mut last_differs = base;
    last_differs[15] ^= 0xff;

    let folded_base = mailbox_integer(&base, 3)?;
    assert_ne!(folded_base, mailbox_integer(&ninth_differs, 3)?);
    assert_ne!(folded_base, mailbox_integer(&last_differs, 3)?);
    Ok(())
}

/// Property sweep at the boundaries: for reserved bounds spanning the whole
/// valid domain (both edges included) and identifier lengths 0..=64, every
/// fold lands inside `reserved_below ..= SMALL_INT_MAX` and is reproducible.
#[test]
fn property_sweep_folds_any_bytes_above_any_valid_reserved_bound()
-> Result<(), Box<dyn std::error::Error>> {
    let boundary_bounds = [
        0_i64,
        1,
        2,
        3,
        Term::SMALL_INT_MAX - 2,
        Term::SMALL_INT_MAX - 1,
        Term::SMALL_INT_MAX,
    ];
    let mut lcg = Lcg(0x5eed_5eed_5eed_5eed);
    let mut cases = 0_u32;

    for round in 0..2_000_u64 {
        // Alternate between exact boundary bounds and random interior bounds.
        let reserved_below = if round % 2 == 0 {
            boundary_bounds[usize::try_from(round / 2)? % boundary_bounds.len()]
        } else {
            i64::try_from(lcg.next() % u64::try_from(Term::SMALL_INT_MAX)?)?
        };
        let len = usize::try_from(lcg.next() % 65)?;
        let mut bytes = Vec::with_capacity(len);
        for _ in 0..len {
            bytes.push(u8::try_from(lcg.next() % 256)?);
        }
        let value = mailbox_integer(&bytes, reserved_below)?;
        assert!(
            in_range(value, reserved_below),
            "len={len} reserved_below={reserved_below} produced out-of-range {value}"
        );
        assert_eq!(
            value,
            mailbox_integer(&bytes, reserved_below)?,
            "fold must be deterministic"
        );
        cases += 1;
    }
    assert_eq!(cases, 2_000);
    Ok(())
}