File size: 5,063 Bytes
4ae5926 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 | // errant_ffi.rs β Rust FFI bindings to liberrant.a
//
// Safe Rust wrappers around the C linear type checker.
// The unsafe blocks are contained here; callers use ErrantResult directly.
//
// liberrant is compiled by build.rs from errant/runtime/errant.c.
// Zero external dependencies in C (SHA-256 inline, no openssl).
use std::ffi::{CStr, CString};
use std::os::raw::{c_char, c_int};
// ββ Raw C layout (must match errant.h exactly) βββββββββββββββββββββββββββββ
const ERROR_MAX: usize = 512;
const HASH_HEX: usize = 65;
const OUTPUT_MAX: usize = 4096;
#[repr(C)]
struct CEResult {
ok: bool,
fallback: bool,
error: [c_char; ERROR_MAX],
worm_hash: [c_char; HASH_HEX],
output: [c_char; OUTPUT_MAX],
steps: c_int,
ruptures: c_int,
lin_consumed: c_int,
lin_leaked: c_int,
}
extern "C" {
fn errant_verify_named(op_names: *const *const c_char, count: c_int) -> CEResult;
fn errant_sha256_hex(data: *const u8, len: usize, hex_out: *mut c_char);
fn errant_version() -> *const c_char;
}
// ββ Safe Rust result βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
#[derive(Debug, Clone)]
pub struct ErrantResult {
pub ok: bool,
pub fallback: bool,
pub error: String,
pub worm_hash: String,
pub output: String,
pub steps: i32,
pub ruptures: i32,
pub lin_consumed: i32,
pub lin_leaked: i32,
}
impl ErrantResult {
pub fn verdict(&self) -> &'static str {
if self.ok { "EVIDENCE" } else { "SILENCE" }
}
}
fn c_str_to_string(buf: &[c_char]) -> String {
unsafe {
CStr::from_ptr(buf.as_ptr())
.to_string_lossy()
.into_owned()
}
}
fn convert(raw: CEResult) -> ErrantResult {
ErrantResult {
ok: raw.ok,
fallback: raw.fallback,
error: c_str_to_string(&raw.error),
worm_hash: c_str_to_string(&raw.worm_hash),
output: c_str_to_string(&raw.output),
steps: raw.steps,
ruptures: raw.ruptures,
lin_consumed: raw.lin_consumed,
lin_leaked: raw.lin_leaked,
}
}
// ββ Public API βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
/// Verify a sequence of ERRANT opcode names.
/// Input: &["PUSH_LIN", "HASH", "SEAL"] β same format bridge.mjs sends.
/// Returns: ErrantResult with EVIDENCE/SILENCE + WORM hash.
pub fn verify_named(ops: &[&str]) -> ErrantResult {
// Build null-terminated C strings
let c_strings: Vec<CString> = ops
.iter()
.map(|s| CString::new(*s).unwrap_or_default())
.collect();
let c_ptrs: Vec<*const c_char> = c_strings.iter().map(|s| s.as_ptr()).collect();
let raw = unsafe {
errant_verify_named(c_ptrs.as_ptr(), c_ptrs.len() as c_int)
};
convert(raw)
}
/// Verify from a whitespace-separated opcode string.
/// Convenience wrapper: "PUSH_LIN HASH SEAL" β same as verify_named.
pub fn verify_source(source: &str) -> ErrantResult {
let ops: Vec<&str> = source.split_whitespace().collect();
verify_named(&ops)
}
/// Compute SHA-256 hex of arbitrary bytes.
/// Returns lowercase 64-char hex string.
pub fn sha256_hex(data: &[u8]) -> String {
let mut buf = [0i8; HASH_HEX];
unsafe {
errant_sha256_hex(data.as_ptr(), data.len(), buf.as_mut_ptr());
}
c_str_to_string(&buf)
}
/// Returns the liberrant version string.
pub fn version() -> String {
unsafe { CStr::from_ptr(errant_version()).to_string_lossy().into_owned() }
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn sha256_empty_fips_vector() {
let h = sha256_hex(b"");
assert_eq!(h, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
}
#[test]
fn metamine_omega_path_evidence() {
let r = verify_named(&["ORIGIN", "SEED", "RUPTURE", "ECHO", "SEAL"]);
assert!(r.ok, "METAMINE path should be EVIDENCE, got: {}", r.error);
assert!(!r.worm_hash.is_empty());
}
#[test]
fn dup_on_lin_is_silence() {
let r = verify_named(&["PUSH_LIN", "DUP_UN", "SEAL"]);
assert!(!r.ok, "DUP_UN on lin must be SILENCE");
}
#[test]
fn lin_leaked_at_halt_is_silence() {
let r = verify_named(&["PUSH_LIN", "MOVE", "HALT"]);
assert!(!r.ok, "Unconsumed lin at HALT must be SILENCE");
}
#[test]
fn forge_chain_evidence() {
let r = verify_named(&["SEED", "SEED", "FORGE", "SEAL"]);
assert!(r.ok, "FORGE chain should be EVIDENCE");
}
}
|