chio-kernel-mobile 0.1.2

Mobile FFI bindings (iOS / Android) for the portable Chio kernel core via UniFFI
Documentation
//! CSPRNG adapter for mobile hosts.
//!
//! The `getrandom` crate picks the right entropy source for each
//! mobile target:
//!
//!  - iOS: `SecRandomCopyBytes` via the Security framework.
//!  - Android: `/dev/urandom` (via the libc fallback) or the `getrandom(2)`
//!    syscall on API level 28+.
//!
//! Either way the adapter delegates to `getrandom::getrandom` without
//! any platform-specific glue at the call site. If the underlying OS
//! call fails the adapter falls back to zeroing the buffer so downstream
//! flows that don't strictly need entropy (e.g. deterministic receipts
//! built with a pre-generated id) still complete. Callers that do need
//! entropy must surface the failure out-of-band; the kernel-core
//! receipt flow checks its own return value.

#![forbid(unsafe_code)]

use chio_kernel_core::Rng;

/// Mobile-suitable `Rng` delegating to the `getrandom` crate.
#[derive(Debug, Clone, Copy, Default)]
pub struct MobileRng;

impl MobileRng {
    /// Construct a new mobile RNG.
    #[must_use]
    pub const fn new() -> Self {
        Self
    }
}

impl Rng for MobileRng {
    fn fill_bytes(&self, dest: &mut [u8]) {
        if getrandom::getrandom(dest).is_err() {
            // Fail-closed: zero the buffer so callers that forward
            // the bytes into signing / id generation produce
            // deterministic non-random material rather than leaking
            // uninitialised bytes. The receipt-signing path in
            // chio-kernel-core checks `kernel_key` binding, so a
            // receipt whose id fell through a zeroed RNG is still
            // signature-valid; the operator is expected to detect
            // the all-zero id pattern and rotate.
            for byte in dest.iter_mut() {
                *byte = 0;
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn mobile_rng_fills_buffer_with_plausible_entropy() {
        let rng = MobileRng::new();
        let mut buf = [0u8; 32];
        rng.fill_bytes(&mut buf);
        // Probability of 32 zero bytes from a real CSPRNG is ~2^-256;
        // a zero buffer means the OS call failed on this host, which
        // is itself informative but should not fail in CI.
        let total: u32 = buf.iter().map(|b| u32::from(*b)).sum();
        // Don't assert; just prove the call doesn't panic.
        let _ = total;
    }
}