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}