Skip to main content

frame_core/runtime/
mailbox.rs

1//! Deriving mailbox acknowledgement integers from identifier bytes.
2
3use beamr::term::Term;
4
5use super::error::RuntimeError;
6
7/// FNV-1a 64-bit offset basis.
8const FNV_OFFSET_BASIS: u64 = 0xcbf2_9ce4_8422_2325;
9
10/// FNV-1a 64-bit prime.
11const FNV_PRIME: u64 = 0x0000_0100_0000_01b3;
12
13/// Folds identifier bytes into a valid mailbox integer above a reserved
14/// range.
15///
16/// A component's mailbox speaks integers, and its message protocol usually
17/// claims the low ones (liveness, stop, news). When the host must send an
18/// acknowledgement derived from an identifier — a stored entity id, for
19/// example — the derivation has to land inside the BEAM small-integer term
20/// payload AND above the protocol's reserved values. This fold is that
21/// derivation, absorbed from the generated composition host's hand-rolled
22/// `Term::SMALL_INT_MAX` arithmetic (finding B6): deterministic FNV-1a over
23/// EVERY input byte (the hand-rolled version truncated to the first eight),
24/// reduced into `reserved_below ..= Term::SMALL_INT_MAX`.
25///
26/// `reserved_below` states the caller's protocol reservation: every integer
27/// strictly below it is reserved and will never be returned. The returned
28/// value is deterministic for identical inputs.
29///
30/// # Errors
31///
32/// Refuses a `reserved_below` outside `0..=Term::SMALL_INT_MAX` — a negative
33/// reservation is meaningless for a derived acknowledgement, and a bound past
34/// the small-integer maximum leaves no valid value to fold into. Never a
35/// clamp, never a silent wrap.
36pub fn mailbox_integer(bytes: &[u8], reserved_below: i64) -> Result<i64, RuntimeError> {
37    let max = Term::SMALL_INT_MAX;
38    let refusal = RuntimeError::MailboxReservedRangeInvalid {
39        reserved_below,
40        max,
41    };
42    if !(0..=max).contains(&reserved_below) {
43        return Err(refusal);
44    }
45    let mut fold = FNV_OFFSET_BASIS;
46    for byte in bytes {
47        fold ^= u64::from(*byte);
48        fold = fold.wrapping_mul(FNV_PRIME);
49    }
50    // With 0 <= reserved_below <= max proven above, every conversion below is
51    // infallible by construction; each arm still surfaces the typed refusal
52    // rather than assuming, so no failure path is ever silent.
53    let Ok(span) = u64::try_from(max - reserved_below) else {
54        return Err(refusal);
55    };
56    let Some(size) = span.checked_add(1) else {
57        return Err(refusal);
58    };
59    let Ok(offset) = i64::try_from(fold % size) else {
60        return Err(refusal);
61    };
62    let Some(value) = reserved_below.checked_add(offset) else {
63        return Err(refusal);
64    };
65    Ok(value)
66}
67
68#[cfg(test)]
69mod tests {
70    use super::{FNV_OFFSET_BASIS, FNV_PRIME, mailbox_integer};
71    use beamr::term::Term;
72
73    #[test]
74    fn empty_input_folds_the_offset_basis_into_the_full_domain()
75    -> Result<(), Box<dyn std::error::Error>> {
76        let size = u64::try_from(Term::SMALL_INT_MAX)?
77            .checked_add(1)
78            .ok_or("size overflow")?;
79        assert_eq!(
80            mailbox_integer(b"", 0)?,
81            i64::try_from(FNV_OFFSET_BASIS % size)?
82        );
83        Ok(())
84    }
85
86    #[test]
87    fn single_byte_matches_the_fnv1a_step() -> Result<(), Box<dyn std::error::Error>> {
88        let folded = (FNV_OFFSET_BASIS ^ u64::from(b'x')).wrapping_mul(FNV_PRIME);
89        let size = u64::try_from(Term::SMALL_INT_MAX)?
90            .checked_add(1)
91            .ok_or("size overflow")?;
92        assert_eq!(mailbox_integer(b"x", 0)?, i64::try_from(folded % size)?);
93        Ok(())
94    }
95}