1	//! PCIe adapter-swap gate for bare-metal admission.
2	//!
3	//! Decision logic (whitepaper v2.5 §7.2 adapter_swap_ok):
4	//!   1. If adapter_id is in the resident cache  → no swap needed → ALLOW
5	//!   2. If adapter_id NOT in cache:
6	//!      a. Read upstream PCIe bandwidth from MMIO
7	//!      b. If bandwidth ≥ BW_THRESHOLD_MBS      → swap feasible  → ALLOW
8	//!      c. Otherwise                             → swap stalls bus → DENY
9	//!
10	//! Cache is a fixed-size ring of 16 u64 adapter IDs stored in BSS.
11	//! No heap, no alloc, no OS.
12
13	use crate::mmio;
14
15	/// Minimum upstream PCIe bandwidth (MB/s) required to permit an adapter swap.
16	/// Below this threshold the fabric is congested and the swap would stall.
17	const BW_THRESHOLD_MBS: u32 = 4096; // 4 GB/s
18
19	/// Number of adapter IDs the resident cache can hold simultaneously.
20	const CACHE_SIZE: usize = 16;
21
22	/// Sentinel meaning "slot is empty".
23	const EMPTY: u64 = 0;
24
25	/// Fixed-size adapter-ID cache (zero-initialized at boot).
26	///
27	/// Entries are 64-bit adapter IDs written by admit calls.
28	/// Eviction policy: first empty slot; if full, slot 0 is overwritten (FIFO).
29	///
30	/// # Safety
31	/// Single-threaded bare-metal — no concurrent mutation possible.
32	static mut ADAPTER_CACHE: [u64; CACHE_SIZE] = [EMPTY; CACHE_SIZE];
33
34	/// Returns true if `id` is currently in the adapter cache.
35	fn cache_contains(id: u64) -> bool {
36	    // SAFETY: single-threaded bare-metal; read-only scan via raw pointer.
37	    unsafe {
38	        let base = core::ptr::addr_of!(ADAPTER_CACHE) as *const u64;
39	        for i in 0..CACHE_SIZE {
40	            if core::ptr::read_volatile(base.add(i)) == id {
41	                return true;
42	            }
43	        }
44	        false
45	    }
46	}
47
48	/// Insert `id` into the cache.  Fills the first empty slot;
49	/// if no empty slot, overwrites slot 0 (simple FIFO eviction).
50	fn cache_insert(id: u64) {
51	    // SAFETY: single-threaded bare-metal; mutations via raw pointer.
52	    unsafe {
53	        let base = core::ptr::addr_of_mut!(ADAPTER_CACHE) as *mut u64;
54	        for i in 0..CACHE_SIZE {
55	            if core::ptr::read_volatile(base.add(i)) == EMPTY {
56	                core::ptr::write_volatile(base.add(i), id);
57	                return;
58	            }
59	        }
60	        // Cache full — evict slot 0
61	        core::ptr::write_volatile(base, id);
62	    }
63	}
64
65	/// Check whether the adapter identified by `adapter_id` can be swapped in.
66	///
67	/// Returns `true`  (ALLOW) when:
68	///   - The adapter is already resident in the gate cache, OR
69	///   - Upstream PCIe bandwidth is ≥ BW_THRESHOLD_MBS (4 GB/s).
70	///
71	/// Returns `false` (DENY) when the adapter is not cached and the PCIe
72	/// fabric is congested.
73	///
74	/// Side effect: on ALLOW from a cache-miss path, `adapter_id` is added
75	/// to the cache so subsequent requests are served without MMIO reads.
76	///
77	/// # Safety
78	/// Reads from PCIe bandwidth MMIO register (see mmio.rs).
79	pub fn adapter_swap_ok(adapter_id: u64) -> bool {
80	    if adapter_id == EMPTY {
81	        // Sentinel value 0 is reserved — treat as unknown/deny.
82	        return false;
83	    }
84
85	    if cache_contains(adapter_id) {
86	        return true;
87	    }
88
89	    // Cache miss: consult PCIe bandwidth register.
90	    // SAFETY: PCIe MMIO region assumed mapped by firmware before calling.
91	    let bw = unsafe { mmio::read_pcie_bw_mbs() };
92	    if bw >= BW_THRESHOLD_MBS {
93	        cache_insert(adapter_id);
94	        true
95	    } else {
96	        false
97	    }
98	}
99
100	/// Reset the adapter cache to empty.
101	/// Only available under the `mock` feature — used by tests to prevent
102	/// cache state from leaking between test cases.
103	#[cfg(feature = "mock")]
104	pub fn clear_adapter_cache() {
105	    unsafe {
106	        let base = core::ptr::addr_of_mut!(ADAPTER_CACHE) as *mut u64;
107	        for i in 0..CACHE_SIZE {
108	            core::ptr::write_volatile(base.add(i), EMPTY);
109	        }
110	    }
111	}
112

//! PCIe adapter-swap gate for bare-metal admission.
//!
//! Decision logic (whitepaper v2.5 §7.2 adapter_swap_ok):
//!   1. If adapter_id is in the resident cache  → no swap needed → ALLOW
//!   2. If adapter_id NOT in cache:
//!      a. Read upstream PCIe bandwidth from MMIO
//!      b. If bandwidth ≥ BW_THRESHOLD_MBS      → swap feasible  → ALLOW
//!      c. Otherwise                             → swap stalls bus → DENY
//!
//! Cache is a fixed-size ring of 16 u64 adapter IDs stored in BSS.
//! No heap, no alloc, no OS.

use crate::mmio;

/// Minimum upstream PCIe bandwidth (MB/s) required to permit an adapter swap.
/// Below this threshold the fabric is congested and the swap would stall.
const BW_THRESHOLD_MBS: u32 = 4096; // 4 GB/s

/// Number of adapter IDs the resident cache can hold simultaneously.
const CACHE_SIZE: usize = 16;

/// Sentinel meaning "slot is empty".
const EMPTY: u64 = 0;

/// Fixed-size adapter-ID cache (zero-initialized at boot).
///
/// Entries are 64-bit adapter IDs written by admit calls.
/// Eviction policy: first empty slot; if full, slot 0 is overwritten (FIFO).
///
/// # Safety
/// Single-threaded bare-metal — no concurrent mutation possible.
static mut ADAPTER_CACHE: [u64; CACHE_SIZE] = [EMPTY; CACHE_SIZE];

/// Returns true if `id` is currently in the adapter cache.
fn cache_contains(id: u64) -> bool {
    // SAFETY: single-threaded bare-metal; read-only scan via raw pointer.
    unsafe {
        let base = core::ptr::addr_of!(ADAPTER_CACHE) as *const u64;
        for i in 0..CACHE_SIZE {
            if core::ptr::read_volatile(base.add(i)) == id {
                return true;
            }
        }
        false
    }
}

/// Insert `id` into the cache.  Fills the first empty slot;
/// if no empty slot, overwrites slot 0 (simple FIFO eviction).
fn cache_insert(id: u64) {
    // SAFETY: single-threaded bare-metal; mutations via raw pointer.
    unsafe {
        let base = core::ptr::addr_of_mut!(ADAPTER_CACHE) as *mut u64;
        for i in 0..CACHE_SIZE {
            if core::ptr::read_volatile(base.add(i)) == EMPTY {
                core::ptr::write_volatile(base.add(i), id);
                return;
            }
        }
        // Cache full — evict slot 0
        core::ptr::write_volatile(base, id);
    }
}

/// Check whether the adapter identified by `adapter_id` can be swapped in.
///
/// Returns `true`  (ALLOW) when:
///   - The adapter is already resident in the gate cache, OR
///   - Upstream PCIe bandwidth is ≥ BW_THRESHOLD_MBS (4 GB/s).
///
/// Returns `false` (DENY) when the adapter is not cached and the PCIe
/// fabric is congested.
///
/// Side effect: on ALLOW from a cache-miss path, `adapter_id` is added
/// to the cache so subsequent requests are served without MMIO reads.
///
/// # Safety
/// Reads from PCIe bandwidth MMIO register (see mmio.rs).
pub fn adapter_swap_ok(adapter_id: u64) -> bool {
    if adapter_id == EMPTY {
        // Sentinel value 0 is reserved — treat as unknown/deny.
        return false;
    }

    if cache_contains(adapter_id) {
        return true;
    }

    // Cache miss: consult PCIe bandwidth register.
    // SAFETY: PCIe MMIO region assumed mapped by firmware before calling.
    let bw = unsafe { mmio::read_pcie_bw_mbs() };
    if bw >= BW_THRESHOLD_MBS {
        cache_insert(adapter_id);
        true
    } else {
        false
    }
}

/// Reset the adapter cache to empty.
/// Only available under the `mock` feature — used by tests to prevent
/// cache state from leaking between test cases.
#[cfg(feature = "mock")]
pub fn clear_adapter_cache() {
    unsafe {
        let base = core::ptr::addr_of_mut!(ADAPTER_CACHE) as *mut u64;
        for i in 0..CACHE_SIZE {
            core::ptr::write_volatile(base.add(i), EMPTY);
        }
    }
}


1	//! Memory-mapped I/O primitives for bare-metal hardware reads.
2	//!
3	//! In production (`not(feature = "mock")`):
4	//!   All reads use `core::ptr::read_volatile` on platform MMIO addresses.
5	//!   Addresses are SiFive HiFive-Unleashed-style defaults — override in
6	//!   a platform linker script or by editing the constants below.
7	//!
8	//! In test mode (`feature = "mock"`):
9	//!   All reads return values from in-memory AtomicU32 cells.
10	//!   Tests call the `mock_set_*` helpers to configure expected hardware state
11	//!   before calling `cubie_admit`.  No unsafe MMIO access occurs.
12
13	// ---------------------------------------------------------------------------
14	// Platform register addresses (production only)
15	// ---------------------------------------------------------------------------
16
17	/// eFuse base: 8 slots × 0x40 stride, each slot holds 8 × u32 = 32 bytes.
18	pub const EFUSE_BASE:         usize = 0x1000_0000;
19	pub const EFUSE_SLOT_STRIDE:  usize = 0x40;
20
21	/// NVLink telemetry base: 8 peers × 0x100 stride.
22	///   +0x00  SM_ACTIVITY  (u32, warp count — 0 = idle)
23	///   +0x04  VRAM_ALLOC   (u32, MiB — non-zero = memory pinned)
24	pub const NVLINK_BASE:              usize = 0xFF00_0000;
25	pub const NVLINK_PEER_STRIDE:       usize = 0x100;
26	pub const NVLINK_SM_ACTIVITY_OFF:   usize = 0x00;
27	pub const NVLINK_VRAM_ALLOC_OFF:    usize = 0x04;
28
29	/// PCIe upstream bandwidth register: MB/s available.
30	pub const PCIE_BASE:          usize = 0xFF10_0000;
31	pub const PCIE_BW_AVAIL_OFF:  usize = 0x00;
32
33	// ---------------------------------------------------------------------------
34	// Mock register bank (test builds only)
35	// ---------------------------------------------------------------------------
36
37	#[cfg(feature = "mock")]
38	use core::sync::atomic::{AtomicU32, Ordering::Relaxed};
39
40	/// 8 slots × 8 words = 64 u32 cells for eFuse key storage.
41	#[cfg(feature = "mock")]
42	pub static MOCK_EFUSE: [AtomicU32; 64] = {
43	    const ZERO: AtomicU32 = AtomicU32::new(0);
44	    [ZERO; 64]
45	};
46
47	/// NVLink SM activity counter per peer (8 peers).
48	#[cfg(feature = "mock")]
49	pub static MOCK_NVLINK_SM: [AtomicU32; 8] = {
50	    const ONE: AtomicU32 = AtomicU32::new(1); // default: SM active (safe)
51	    [ONE; 8]
52	};
53
54	/// NVLink allocated VRAM per peer (MiB, 8 peers).
55	#[cfg(feature = "mock")]
56	pub static MOCK_NVLINK_VRAM: [AtomicU32; 8] = {
57	    const ZERO: AtomicU32 = AtomicU32::new(0); // default: no VRAM held
58	    [ZERO; 8]
59	};
60
61	/// PCIe upstream bandwidth in MB/s.
62	#[cfg(feature = "mock")]
63	pub static MOCK_PCIE_BW: AtomicU32 = AtomicU32::new(8192); // default: 8 GB/s
64
65	// ---------------------------------------------------------------------------
66	// Mock setter helpers — call these in tests before cubie_admit
67	// ---------------------------------------------------------------------------
68
69	/// Set one 32-bit word of eFuse slot `slot` (0-7), word index `word` (0-7).
70	#[cfg(feature = "mock")]
71	pub fn mock_set_efuse_word(slot: u8, word: u8, value: u32) {
72	    let idx = (slot as usize) * 8 + (word as usize);
73	    MOCK_EFUSE[idx].store(value, Relaxed);
74	}
75
76	/// Set the full 32-byte eFuse key for slot `slot` from a byte array.
77	#[cfg(feature = "mock")]
78	pub fn mock_set_efuse_key(slot: u8, key: &[u8; 32]) {
79	    for (i, chunk) in key.chunks(4).enumerate() {
80	        let word = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
81	        mock_set_efuse_word(slot, i as u8, word);
82	    }
83	}
84
85	/// Set the SM activity counter for NVLink peer `peer`.
86	#[cfg(feature = "mock")]
87	pub fn mock_set_nvlink_sm(peer: u8, value: u32) {
88	    MOCK_NVLINK_SM[peer as usize].store(value, Relaxed);
89	}
90
91	/// Set the allocated VRAM (MiB) for NVLink peer `peer`.
92	#[cfg(feature = "mock")]
93	pub fn mock_set_nvlink_vram(peer: u8, value: u32) {
94	    MOCK_NVLINK_VRAM[peer as usize].store(value, Relaxed);
95	}
96
97	/// Set the available PCIe upstream bandwidth (MB/s).
98	#[cfg(feature = "mock")]
99	pub fn mock_set_pcie_bw(value: u32) {
100	    MOCK_PCIE_BW.store(value, Relaxed);
101	}
102
103	// ---------------------------------------------------------------------------
104	// Core volatile-read primitive (production)
105	// ---------------------------------------------------------------------------
106
107	/// Read a u32 from a memory-mapped register.
108	///
109	/// # Safety
110	/// `addr` must map to a valid, aligned MMIO register on this platform.
111	#[inline(always)]
112	#[cfg(not(feature = "mock"))]
113	pub unsafe fn read_mmio_u32(addr: usize) -> u32 {
114	    core::ptr::read_volatile(addr as *const u32)
115	}
116
117	// ---------------------------------------------------------------------------
118	// eFuse key reader
119	// ---------------------------------------------------------------------------
120
121	/// Read the 32-byte symmetric key stored in eFuse slot `slot` (0-7).
122	///
123	/// Production: volatile read from MMIO eFuse registers.
124	/// Mock: reads from MOCK_EFUSE atomic cells.
125	pub unsafe fn read_efuse_key(slot: u8) -> [u8; 32] {
126	    let mut key = [0u8; 32];
127
128	    #[cfg(feature = "mock")]
129	    {
130	        let _ = slot; // suppress unused warning only in mock path below
131	        for word_idx in 0..8usize {
132	            let cell_idx = (slot as usize) * 8 + word_idx;
133	            let word = MOCK_EFUSE[cell_idx].load(Relaxed);
134	            key[word_idx * 4..word_idx * 4 + 4].copy_from_slice(&word.to_be_bytes());
135	        }
136	    }
137
138	    #[cfg(not(feature = "mock"))]
139	    {
140	        let base = EFUSE_BASE + (slot as usize) * EFUSE_SLOT_STRIDE;
141	        for word_idx in 0..8usize {
142	            let addr = base + word_idx * 4;
143	            let word = read_mmio_u32(addr);
144	            key[word_idx * 4..word_idx * 4 + 4].copy_from_slice(&word.to_be_bytes());
145	        }
146	    }
147
148	    key
149	}
150
151	// ---------------------------------------------------------------------------
152	// NVLink telemetry readers
153	// ---------------------------------------------------------------------------
154
155	/// Read the SM activity counter for NVLink peer `peer_id`.
156	pub unsafe fn read_nvlink_sm_activity(peer_id: u8) -> u32 {
157	    #[cfg(feature = "mock")]
158	    { return MOCK_NVLINK_SM[peer_id as usize].load(Relaxed); }
159
160	    #[cfg(not(feature = "mock"))]
161	    {
162	        let addr = NVLINK_BASE
163	            + (peer_id as usize) * NVLINK_PEER_STRIDE
164	            + NVLINK_SM_ACTIVITY_OFF;
165	        read_mmio_u32(addr)
166	    }
167	}
168
169	/// Read the allocated VRAM (MiB) for NVLink peer `peer_id`.
170	pub unsafe fn read_nvlink_vram_alloc(peer_id: u8) -> u32 {
171	    #[cfg(feature = "mock")]
172	    { return MOCK_NVLINK_VRAM[peer_id as usize].load(Relaxed); }
173
174	    #[cfg(not(feature = "mock"))]
175	    {
176	        let addr = NVLINK_BASE
177	            + (peer_id as usize) * NVLINK_PEER_STRIDE
178	            + NVLINK_VRAM_ALLOC_OFF;
179	        read_mmio_u32(addr)
180	    }
181	}
182
183	// ---------------------------------------------------------------------------
184	// PCIe bandwidth reader
185	// ---------------------------------------------------------------------------
186
187	/// Read available upstream PCIe bandwidth in MB/s.
188	pub unsafe fn read_pcie_bw_mbs() -> u32 {
189	    #[cfg(feature = "mock")]
190	    { return MOCK_PCIE_BW.load(Relaxed); }
191
192	    #[cfg(not(feature = "mock"))]
193	    { read_mmio_u32(PCIE_BASE + PCIE_BW_AVAIL_OFF) }
194	}
195

//! Memory-mapped I/O primitives for bare-metal hardware reads.
//!
//! In production (`not(feature = "mock")`):
//!   All reads use `core::ptr::read_volatile` on platform MMIO addresses.
//!   Addresses are SiFive HiFive-Unleashed-style defaults — override in
//!   a platform linker script or by editing the constants below.
//!
//! In test mode (`feature = "mock"`):
//!   All reads return values from in-memory AtomicU32 cells.
//!   Tests call the `mock_set_*` helpers to configure expected hardware state
//!   before calling `cubie_admit`.  No unsafe MMIO access occurs.

// ---------------------------------------------------------------------------
// Platform register addresses (production only)
// ---------------------------------------------------------------------------

/// eFuse base: 8 slots × 0x40 stride, each slot holds 8 × u32 = 32 bytes.
pub const EFUSE_BASE:         usize = 0x1000_0000;
pub const EFUSE_SLOT_STRIDE:  usize = 0x40;

/// NVLink telemetry base: 8 peers × 0x100 stride.
///   +0x00  SM_ACTIVITY  (u32, warp count — 0 = idle)
///   +0x04  VRAM_ALLOC   (u32, MiB — non-zero = memory pinned)
pub const NVLINK_BASE:              usize = 0xFF00_0000;
pub const NVLINK_PEER_STRIDE:       usize = 0x100;
pub const NVLINK_SM_ACTIVITY_OFF:   usize = 0x00;
pub const NVLINK_VRAM_ALLOC_OFF:    usize = 0x04;

/// PCIe upstream bandwidth register: MB/s available.
pub const PCIE_BASE:          usize = 0xFF10_0000;
pub const PCIE_BW_AVAIL_OFF:  usize = 0x00;

// ---------------------------------------------------------------------------
// Mock register bank (test builds only)
// ---------------------------------------------------------------------------

#[cfg(feature = "mock")]
use core::sync::atomic::{AtomicU32, Ordering::Relaxed};

/// 8 slots × 8 words = 64 u32 cells for eFuse key storage.
#[cfg(feature = "mock")]
pub static MOCK_EFUSE: [AtomicU32; 64] = {
    const ZERO: AtomicU32 = AtomicU32::new(0);
    [ZERO; 64]
};

/// NVLink SM activity counter per peer (8 peers).
#[cfg(feature = "mock")]
pub static MOCK_NVLINK_SM: [AtomicU32; 8] = {
    const ONE: AtomicU32 = AtomicU32::new(1); // default: SM active (safe)
    [ONE; 8]
};

/// NVLink allocated VRAM per peer (MiB, 8 peers).
#[cfg(feature = "mock")]
pub static MOCK_NVLINK_VRAM: [AtomicU32; 8] = {
    const ZERO: AtomicU32 = AtomicU32::new(0); // default: no VRAM held
    [ZERO; 8]
};

/// PCIe upstream bandwidth in MB/s.
#[cfg(feature = "mock")]
pub static MOCK_PCIE_BW: AtomicU32 = AtomicU32::new(8192); // default: 8 GB/s

// ---------------------------------------------------------------------------
// Mock setter helpers — call these in tests before cubie_admit
// ---------------------------------------------------------------------------

/// Set one 32-bit word of eFuse slot `slot` (0-7), word index `word` (0-7).
#[cfg(feature = "mock")]
pub fn mock_set_efuse_word(slot: u8, word: u8, value: u32) {
    let idx = (slot as usize) * 8 + (word as usize);
    MOCK_EFUSE[idx].store(value, Relaxed);
}

/// Set the full 32-byte eFuse key for slot `slot` from a byte array.
#[cfg(feature = "mock")]
pub fn mock_set_efuse_key(slot: u8, key: &[u8; 32]) {
    for (i, chunk) in key.chunks(4).enumerate() {
        let word = u32::from_be_bytes([chunk[0], chunk[1], chunk[2], chunk[3]]);
        mock_set_efuse_word(slot, i as u8, word);
    }
}

/// Set the SM activity counter for NVLink peer `peer`.
#[cfg(feature = "mock")]
pub fn mock_set_nvlink_sm(peer: u8, value: u32) {
    MOCK_NVLINK_SM[peer as usize].store(value, Relaxed);
}

/// Set the allocated VRAM (MiB) for NVLink peer `peer`.
#[cfg(feature = "mock")]
pub fn mock_set_nvlink_vram(peer: u8, value: u32) {
    MOCK_NVLINK_VRAM[peer as usize].store(value, Relaxed);
}

/// Set the available PCIe upstream bandwidth (MB/s).
#[cfg(feature = "mock")]
pub fn mock_set_pcie_bw(value: u32) {
    MOCK_PCIE_BW.store(value, Relaxed);
}

// ---------------------------------------------------------------------------
// Core volatile-read primitive (production)
// ---------------------------------------------------------------------------

/// Read a u32 from a memory-mapped register.
///
/// # Safety
/// `addr` must map to a valid, aligned MMIO register on this platform.
#[inline(always)]
#[cfg(not(feature = "mock"))]
pub unsafe fn read_mmio_u32(addr: usize) -> u32 {
    core::ptr::read_volatile(addr as *const u32)
}

// ---------------------------------------------------------------------------
// eFuse key reader
// ---------------------------------------------------------------------------

/// Read the 32-byte symmetric key stored in eFuse slot `slot` (0-7).
///
/// Production: volatile read from MMIO eFuse registers.
/// Mock: reads from MOCK_EFUSE atomic cells.
pub unsafe fn read_efuse_key(slot: u8) -> [u8; 32] {
    let mut key = [0u8; 32];

    #[cfg(feature = "mock")]
    {
        let _ = slot; // suppress unused warning only in mock path below
        for word_idx in 0..8usize {
            let cell_idx = (slot as usize) * 8 + word_idx;
            let word = MOCK_EFUSE[cell_idx].load(Relaxed);
            key[word_idx * 4..word_idx * 4 + 4].copy_from_slice(&word.to_be_bytes());
        }
    }

    #[cfg(not(feature = "mock"))]
    {
        let base = EFUSE_BASE + (slot as usize) * EFUSE_SLOT_STRIDE;
        for word_idx in 0..8usize {
            let addr = base + word_idx * 4;
            let word = read_mmio_u32(addr);
            key[word_idx * 4..word_idx * 4 + 4].copy_from_slice(&word.to_be_bytes());
        }
    }

    key
}

// ---------------------------------------------------------------------------
// NVLink telemetry readers
// ---------------------------------------------------------------------------

/// Read the SM activity counter for NVLink peer `peer_id`.
pub unsafe fn read_nvlink_sm_activity(peer_id: u8) -> u32 {
    #[cfg(feature = "mock")]
    { return MOCK_NVLINK_SM[peer_id as usize].load(Relaxed); }

    #[cfg(not(feature = "mock"))]
    {
        let addr = NVLINK_BASE
            + (peer_id as usize) * NVLINK_PEER_STRIDE
            + NVLINK_SM_ACTIVITY_OFF;
        read_mmio_u32(addr)
    }
}

/// Read the allocated VRAM (MiB) for NVLink peer `peer_id`.
pub unsafe fn read_nvlink_vram_alloc(peer_id: u8) -> u32 {
    #[cfg(feature = "mock")]
    { return MOCK_NVLINK_VRAM[peer_id as usize].load(Relaxed); }

    #[cfg(not(feature = "mock"))]
    {
        let addr = NVLINK_BASE
            + (peer_id as usize) * NVLINK_PEER_STRIDE
            + NVLINK_VRAM_ALLOC_OFF;
        read_mmio_u32(addr)
    }
}

// ---------------------------------------------------------------------------
// PCIe bandwidth reader
// ---------------------------------------------------------------------------

/// Read available upstream PCIe bandwidth in MB/s.
pub unsafe fn read_pcie_bw_mbs() -> u32 {
    #[cfg(feature = "mock")]
    { return MOCK_PCIE_BW.load(Relaxed); }

    #[cfg(not(feature = "mock"))]
    { read_mmio_u32(PCIE_BASE + PCIE_BW_AVAIL_OFF) }
}


1	//! NVLink waiter-signature detection for bare-metal admission gate.
2	//!
3	//! Waiter signature per whitepaper v2.5 §7.2:
4	//!   zero SM activity  AND  non-zero allocated VRAM on a TP peer
5	//!   → peer is a deadlocked NVLink waiter → unsafe to admit
6	//!
7	//! Returns true  (SAFE)  when the signature is ABSENT.
8	//! Returns false (DENY)  when SM idle + VRAM held = waiter detected.
9
10	use crate::mmio;
11
12	/// Check whether the NVLink peer `peer_id` is safe (no waiter signature).
13	///
14	/// Reads two MMIO telemetry registers:
15	///   - SM_ACTIVITY : running SM warp count (0 = peer is idle)
16	///   - VRAM_ALLOC  : allocated VRAM in MiB (non-zero = memory is pinned)
17	///
18	/// The NVLink waiter signature is: `sm_activity == 0 && vram_alloc > 0`.
19	/// If that condition holds, the peer is holding VRAM while doing no work —
20	/// the classic NVLink deadlock footprint.  We deny admission.
21	///
22	/// # Safety
23	/// Reads from MMIO registers. `peer_id` must map to a valid NVLink
24	/// peer on this platform (see mmio.rs for register addresses).
25	pub fn nvlink_waiter_safe(peer_id: u8) -> bool {
26	    // SAFETY: peer_id is passed directly from CubieRequest.peer_id;
27	    // firmware callers are responsible for providing a valid peer index.
28	    let (sm, vram) = unsafe {
29	        (
30	            mmio::read_nvlink_sm_activity(peer_id),
31	            mmio::read_nvlink_vram_alloc(peer_id),
32	        )
33	    };
34
35	    // Waiter signature: idle SM + held VRAM
36	    let waiter_detected = sm == 0 && vram > 0;
37	    !waiter_detected
38	}
39

//! NVLink waiter-signature detection for bare-metal admission gate.
//!
//! Waiter signature per whitepaper v2.5 §7.2:
//!   zero SM activity  AND  non-zero allocated VRAM on a TP peer
//!   → peer is a deadlocked NVLink waiter → unsafe to admit
//!
//! Returns true  (SAFE)  when the signature is ABSENT.
//! Returns false (DENY)  when SM idle + VRAM held = waiter detected.

use crate::mmio;

/// Check whether the NVLink peer `peer_id` is safe (no waiter signature).
///
/// Reads two MMIO telemetry registers:
///   - SM_ACTIVITY : running SM warp count (0 = peer is idle)
///   - VRAM_ALLOC  : allocated VRAM in MiB (non-zero = memory is pinned)
///
/// The NVLink waiter signature is: `sm_activity == 0 && vram_alloc > 0`.
/// If that condition holds, the peer is holding VRAM while doing no work —
/// the classic NVLink deadlock footprint.  We deny admission.
///
/// # Safety
/// Reads from MMIO registers. `peer_id` must map to a valid NVLink
/// peer on this platform (see mmio.rs for register addresses).
pub fn nvlink_waiter_safe(peer_id: u8) -> bool {
    // SAFETY: peer_id is passed directly from CubieRequest.peer_id;
    // firmware callers are responsible for providing a valid peer index.
    let (sm, vram) = unsafe {
        (
            mmio::read_nvlink_sm_activity(peer_id),
            mmio::read_nvlink_vram_alloc(peer_id),
        )
    };

    // Waiter signature: idle SM + held VRAM
    let waiter_detected = sm == 0 && vram > 0;
    !waiter_detected
}


1	[package]
2	name = "cubie-bare-metal"
3	version = "0.1.0"
4	edition = "2021"
5
6	[lib]
7	# staticlib  → C-linkable .a for firmware/UEFI linkers
8	# rlib       → Rust-linkable for the test crate
9	crate-type = ["staticlib", "rlib"]
10
11	[features]
12	default = []
13	# std: enables std panic/test support for host builds.
14	# When absent the crate is fully no_std.
15	std = []
16	# mock: replaces MMIO reads with in-memory AtomicU32 cells that tests
17	# can control.  Never enable in a production firmware build.
18	mock = []
19
20	[dependencies]
21

[package]
name = "cubie-bare-metal"
version = "0.1.0"
edition = "2021"

[lib]
# staticlib  → C-linkable .a for firmware/UEFI linkers
# rlib       → Rust-linkable for the test crate
crate-type = ["staticlib", "rlib"]

[features]
default = []
# std: enables std panic/test support for host builds.
# When absent the crate is fully no_std.
std = []
# mock: replaces MMIO reads with in-memory AtomicU32 cells that tests
# can control.  Never enable in a production firmware build.
mock = []

[dependencies]


TaskUpdate

P1 code fixes done (alignment, key_slot bounds, black_box). Remaining P0/P1 items tracked separately. Verification tools deferred to post-FFI.


Updated task #13 description, status


TaskCreate

Phase 1: Create cubie-ffi crate (Cargo.toml + lib.rs)

Create cubie-ffi/Cargo.toml (cdylib, depends on cubie-bare-metal with std+mock features) and cubie-ffi/src/lib.rs (re-exports cubie_admit, adds cubie_mock_setup and cubie_create_valid_request FFI functions). Add cubie-ffi to workspace members.


Task #14 created successfully: Phase 1: Create cubie-ffi crate (Cargo.toml + lib.rs)


Phase 1: Create C header (cubie-ffi/include/cubie.h)

C header with CubieRequest struct, AdmitResult enum, function declarations, static_assert(sizeof==40).


Task #15 created successfully: Phase 1: Create C header (cubie-ffi/include/cubie.h)


Phase 1: Create Python ctypes wrapper (pipeline/cubie_gate.py)

Python ctypes wrapper: CubieRequest Structure, CubieGate class with admit(), benchmark(), create_test_request() methods. Loads libcubie_ffi.so/.dll.


Task #16 created successfully: Phase 1: Create Python ctypes wrapper (pipeline/cubie_gate.py)


Phase 1: Build and verify FFI bridge end-to-end

Build cubie-ffi (cargo build --release), verify 82 existing tests still pass, run Python smoke test calling cubie_admit via ctypes.


Task #17 created successfully: Phase 1: Build and verify FFI bridge end-to-end


Updated task #14 status


Bash

mkdir -p "C:/Users/NickV/cubie-verification/cubie-ffi/src" "C:/Users/NickV/cubie-verification/cubie-ffi/include" "C:/Users/NickV/cubie-verification/pipeline"

Create FFI crate and pipeline directories


(Bash completed with no output)


Write

[package]
name = "cubie-ffi"
version = "0.1.0"
edition = "2021"
description = "C-ABI shared library exposing cubie_admit for Python/C callers"

[lib]
crate-type = ["cdylib"]

[dependencies]
cubie-bare-metal = { path = "../bare-metal", features = ["std", "mock"] }


File created successfully at: C:\Users\NickV\cubie-verification\cubie-ffi\Cargo.toml

[package]
name = "cubie-ffi"
version = "0.1.0"
edition = "2021"
description = "C-ABI shared library exposing cubie_admit for Python/C callers"

[lib]
crate-type = ["cdylib"]

[dependencies]
cubie-bare-metal = { path = "../bare-metal", features = ["std", "mock"] }


//! FFI bridge — exposes cubie_admit and mock helpers as a C-ABI shared library.
//!
//! Build:  cargo build --release --manifest-path cubie-ffi/Cargo.toml
//! Output: target/release/cubie_ffi.dll (Windows) or libcubie_ffi.so (Linux)
//!
//! Designed for Python ctypes consumption (Intel Edge demo pipeline).

use cubie_bare_metal::{
    cubie_admit, hmac_sha256,
    CubieRequest, AdmitResult,
    mmio, pcie,
};

// -------------------------------------------------------------------------
// Re-export cubie_admit — the linker already emits it via #[no_mangle] in
// bare-metal/src/admit.rs.  We just need to ensure it's linked in.
// Force the symbol into the shared library by referencing it.
// -------------------------------------------------------------------------

/// Dummy reference to ensure cubie_admit is linked into the cdylib.
#[used]
static _FORCE_LINK: unsafe extern "C" fn(*const CubieRequest) -> AdmitResult = cubie_admit;

// -------------------------------------------------------------------------
// Mock setup — configure MMIO state for testing without real hardware.
// -------------------------------------------------------------------------

/// Configure mock MMIO state for a valid test environment.
///
/// Sets eFuse key slot 0 to the provided 32-byte key, configures NVLink
/// peer 0 as active (SM=100, VRAM=0), and sets PCIe bandwidth to 8192 MB/s.
///
/// # Safety
/// Must be called before cubie_admit in test/demo environments.
#[no_mangle]
pub extern "C" fn cubie_mock_setup(key: *const u8) {
    if key.is_null() {
        return;
    }
    let key_bytes: [u8; 32] = unsafe {
        let slice = core::slice::from_raw_parts(key, 32);
        let mut arr = [0u8; 32];
        arr.copy_from_slice(slice);
        arr
    };

    pcie::clear_adapter_cache();
    mmio::mock_set_efuse_key(0, &key_bytes);
    mmio::mock_set_nvlink_sm(0, 100);
    mmio::mock_set_nvlink_vram(0, 0);
    mmio::mock_set_pcie_bw(8192);
}

/// Configure a specific eFuse key slot (0-7) with a 32-byte key.
///
/// # Safety
/// `key` must point to at least 32 readable bytes.
#[no_mangle]
pub extern "C" fn cubie_mock_set_efuse_key(slot: u8, key: *const u8) {
    if key.is_null() || slot > 7 {
        return;
    }
    let key_bytes: [u8; 32] = unsafe {
        let slice = core::slice::from_raw_parts(key, 32);
        let mut arr = [0u8; 32];
        arr.copy_from_slice(slice);
        arr
    };
    mmio::mock_set_efuse_key(slot, &key_bytes);
}

/// Set NVLink peer SM activity and VRAM allocation.
#[no_mangle]
pub extern "C" fn cubie_mock_set_nvlink(peer: u8, sm: u32, vram: u32) {
    mmio::mock_set_nvlink_sm(peer, sm);
    mmio::mock_set_nvlink_vram(peer, vram);
}

/// Set available PCIe upstream bandwidth in MB/s.
#[no_mangle]
pub extern "C" fn cubie_mock_set_pcie_bw(bw: u32) {
    mmio::mock_set_pcie_bw(bw);
}

/// Clear the PCIe adapter cache (for test isolation).
#[no_mangle]
pub extern "C" fn cubie_clear_adapter_cache() {
    pcie::clear_adapter_cache();
}

// -------------------------------------------------------------------------
// HMAC computation — compute a valid capability_hmac for a request_hash.
// -------------------------------------------------------------------------

/// Compute HMAC-SHA256(key, request_hash) and return the first 8 bytes as u64.
///
/// This lets Python callers build valid CubieRequests without reimplementing
/// the HMAC in Python.
///
/// # Safety
/// `key` must point to at least 32 readable bytes.
#[no_mangle]
pub extern "C" fn cubie_compute_hmac(key: *const u8, request_hash: u64) -> u64 {
    if key.is_null() {
        return 0;
    }
    let key_bytes: [u8; 32] = unsafe {
        let slice = core::slice::from_raw_parts(key, 32);
        let mut arr = [0u8; 32];
        arr.copy_from_slice(slice);
        arr
    };
    let mac = hmac_sha256(&key_bytes, &request_hash.to_be_bytes());
    u64::from_be_bytes(mac[0..8].try_into().unwrap())
}

/// Create a fully-valid CubieRequest and write it to the caller's buffer.
///
/// The request passes all gates: all 6 face bools true, valid lease,
/// fresh epoch, correct HMAC for slot 0, peer 0, adapter 0x1234_5678_9ABC_DEF0.
///
/// # Safety
/// `out` must point to a writable buffer of at least `sizeof(CubieRequest)` bytes (40).
/// `key` must point to at least 32 readable bytes (same key used in cubie_mock_setup).
#[no_mangle]
pub extern "C" fn cubie_create_valid_request(out: *mut CubieRequest, key: *const u8) -> u32 {
    if out.is_null() || key.is_null() {
        return 0;
    }

    let key_bytes: [u8; 32] = unsafe {
        let slice = core::slice::from_raw_parts(key, 32);
        let mut arr = [0u8; 32];
        arr.copy_from_slice(slice);
        arr
    };

    let request_hash: u64 = 0xDEAD_BEEF_CAFE_0001;
    let mac = hmac_sha256(&key_bytes, &request_hash.to_be_bytes());
    let capability_hmac = u64::from_be_bytes(mac[0..8].try_into().unwrap());

    let req = CubieRequest {
        who_pass: true,
        what_pass: true,
        how_pass: true,
        where_pass: true,
        when_pass: true,
        why_pass: true,
        has_valid_lease: true,
        epoch_fresh: true,
        request_hash,
        capability_hmac,
        key_slot: 0,
        peer_id: 0,
        adapter_id: 0x1234_5678_9ABC_DEF0,
    };

    unsafe { core::ptr::write(out, req); }
    1 // success
}


File created successfully at: C:\Users\NickV\cubie-verification\cubie-ffi\src\lib.rs

//! FFI bridge — exposes cubie_admit and mock helpers as a C-ABI shared library.
//!
//! Build:  cargo build --release --manifest-path cubie-ffi/Cargo.toml
//! Output: target/release/cubie_ffi.dll (Windows) or libcubie_ffi.so (Linux)
//!
//! Designed for Python ctypes consumption (Intel Edge demo pipeline).

use cubie_bare_metal::{
    cubie_admit, hmac_sha256,
    CubieRequest, AdmitResult,
    mmio, pcie,
};

// -------------------------------------------------------------------------
// Re-export cubie_admit — the linker already emits it via #[no_mangle] in
// bare-metal/src/admit.rs.  We just need to ensure it's linked in.
// Force the symbol into the shared library by referencing it.
// -------------------------------------------------------------------------

/// Dummy reference to ensure cubie_admit is linked into the cdylib.
#[used]
static _FORCE_LINK: unsafe extern "C" fn(*const CubieRequest) -> AdmitResult = cubie_admit;

// -------------------------------------------------------------------------
// Mock setup — configure MMIO state for testing without real hardware.
// -------------------------------------------------------------------------

/// Configure mock MMIO state for a valid test environment.
///
/// Sets eFuse key slot 0 to the provided 32-byte key, configures NVLink
/// peer 0 as active (SM=100, VRAM=0), and sets PCIe bandwidth to 8192 MB/s.
///
/// # Safety
/// Must be called before cubie_admit in test/demo environments.
#[no_mangle]
pub extern "C" fn cubie_mock_setup(key: *const u8) {
    if key.is_null() {
        return;
    }
    let key_bytes: [u8; 32] = unsafe {
        let slice = core::slice::from_raw_parts(key, 32);
        let mut arr = [0u8; 32];
        arr.copy_from_slice(slice);
        arr
    };

    pcie::clear_adapter_cache();
    mmio::mock_set_efuse_key(0, &key_bytes);
    mmio::mock_set_nvlink_sm(0, 100);
    mmio::mock_set_nvlink_vram(0, 0);
    mmio::mock_set_pcie_bw(8192);
}

/// Configure a specific eFuse key slot (0-7) with a 32-byte key.
///
/// # Safety
/// `key` must point to at least 32 readable bytes.
#[no_mangle]
pub extern "C" fn cubie_mock_set_efuse_key(slot: u8, key: *const u8) {
    if key.is_null() || slot > 7 {
        return;
    }
    let key_bytes: [u8; 32] = unsafe {
        let slice = core::slice::from_raw_parts(key, 32);
        let mut arr = [0u8; 32];
        arr.copy_from_slice(slice);
        arr
    };
    mmio::mock_set_efuse_key(slot, &key_bytes);
}

/// Set NVLink peer SM activity and VRAM allocation.
#[no_mangle]
pub extern "C" fn cubie_mock_set_nvlink(peer: u8, sm: u32, vram: u32) {
    mmio::mock_set_nvlink_sm(peer, sm);
    mmio::mock_set_nvlink_vram(peer, vram);
}

/// Set available PCIe upstream bandwidth in MB/s.
#[no_mangle]
pub extern "C" fn cubie_mock_set_pcie_bw(bw: u32) {
    mmio::mock_set_pcie_bw(bw);
}

/// Clear the PCIe adapter cache (for test isolation).
#[no_mangle]
pub extern "C" fn cubie_clear_adapter_cache() {
    pcie::clear_adapter_cache();
}

// -------------------------------------------------------------------------
// HMAC computation — compute a valid capability_hmac for a request_hash.
// -------------------------------------------------------------------------

/// Compute HMAC-SHA256(key, request_hash) and return the first 8 bytes as u64.
///
/// This lets Python callers build valid CubieRequests without reimplementing
/// the HMAC in Python.
///
/// # Safety
/// `key` must point to at least 32 readable bytes.
#[no_mangle]
pub extern "C" fn cubie_compute_hmac(key: *const u8, request_hash: u64) -> u64 {
    if key.is_null() {
        return 0;
    }
    let key_bytes: [u8; 32] = unsafe {
        let slice = core::slice::from_raw_parts(key, 32);
        let mut arr = [0u8; 32];
        arr.copy_from_slice(slice);
        arr
    };
    let mac = hmac_sha256(&key_bytes, &request_hash.to_be_bytes());
    u64::from_be_bytes(mac[0..8].try_into().unwrap())
}

/// Create a fully-valid CubieRequest and write it to the caller's buffer.
///
/// The request passes all gates: all 6 face bools true, valid lease,
/// fresh epoch, correct HMAC for slot 0, peer 0, adapter 0x1234_5678_9ABC_DEF0.
///
/// # Safety
/// `out` must point to a writable buffer of at least `sizeof(CubieRequest)` bytes (40).
/// `key` must point to at least 32 readable bytes (same key used in cubie_mock_setup).
#[no_mangle]
pub extern "C" fn cubie_create_valid_request(out: *mut CubieRequest, key: *const u8) -> u32 {
    if out.is_null() || key.is_null() {
        return 0;
    }

    let key_bytes: [u8; 32] = unsafe {
        let slice = core::slice::from_raw_parts(key, 32);
        let mut arr = [0u8; 32];
        arr.copy_from_slice(slice);
        arr
    };

    let request_hash: u64 = 0xDEAD_BEEF_CAFE_0001;
    let mac = hmac_sha256(&key_bytes, &request_hash.to_be_bytes());
    let capability_hmac = u64::from_be_bytes(mac[0..8].try_into().unwrap());

    let req = CubieRequest {
        who_pass: true,
        what_pass: true,
        how_pass: true,
        where_pass: true,
        when_pass: true,
        why_pass: true,
        has_valid_lease: true,
        epoch_fresh: true,
        request_hash,
        capability_hmac,
        key_slot: 0,
        peer_id: 0,
        adapter_id: 0x1234_5678_9ABC_DEF0,
    };

    unsafe { core::ptr::write(out, req); }
    1 // success
}


Now add cubie-ffi to the workspace and build:


Edit

members = ["bare-metal", "bare-metal-tests"]

members = ["bare-metal", "bare-metal-tests", "cubie-ffi"]


The file C:\Users\NickV\cubie-verification\Cargo.toml has been updated successfully.

[workspace]
members = ["bare-metal", "bare-metal-tests"]
resolver = "2"

[profile.release]
opt-level = "s"
lto = true
codegen-units = 1
panic = "abort"

[profile.dev]
panic = "abort"

-members = ["bare-metal", "bare-metal-tests"]

+members = ["bare-metal", "bare-metal-tests", "cubie-ffi"]


Bash

cd "C:/Users/NickV/cubie-verification" && cargo build --release --manifest-path cubie-ffi/Cargo.toml 2>&1

Build cubie-ffi shared library


   Compiling cubie-bare-metal v0.1.0 (C:\Users\NickV\cubie-verification\bare-metal)
   Compiling cubie-ffi v0.1.0 (C:\Users\NickV\cubie-verification\cubie-ffi)
    Finished `release` profile [optimized] target(s) in 1.14s


ls -la "C:/Users/NickV/cubie-verification/target/release/cubie_ffi.dll" 2>/dev/null || ls -la "C:/Users/NickV/cubie-verification/target/release/"*cubie_ffi* 2>/dev/null

Verify DLL was produced


-rwxr-xr-x 2 NickV 197609 93184 May  6 18:06 C:/Users/NickV/cubie-verification/target/release/cubie_ffi.dll


DLL built (93 KB). Now verify existing tests still pass:


cd "C:/Users/NickV/cubie-verification" && cargo test --manifest-path bare-metal-tests/Cargo.toml 2>&1

Run all 82 existing tests


warning: function `hex_to_bytes` is never used
  --> bare-metal-tests\tests\crypto_vectors.rs:12:4
   |
12 | fn hex_to_bytes(hex: &str) -> Vec<u8> {
   |    ^^^^^^^^^^^^
   |
   = note: `#[warn(dead_code)]` (part of `#[warn(unused)]`) on by default

warning: variable does not need to be mutable
   --> bare-metal-tests\tests\admit.rs:204:9
    |
204 |     let mut req = valid_request();
    |         ----^^^
    |         |
    |         help: remove this `mut`
    |
    = note: `#[warn(unused_mut)]` (part of `#[warn(unused)]`) on by default

warning: `cubie-bare-metal-tests` (test "crypto_vectors") generated 1 warning
warning: `cubie-bare-metal-tests` (test "admit") generated 1 warning (run `cargo fix --test "admit" -p cubie-bare-metal-tests` to apply 1 suggestion)
    Finished `test` profile [unoptimized + debuginfo] target(s) in 0.07s
     Running unittests src\speedtest.rs (target\debug\deps\speedtest-cc3f724d9c0d002e.exe)

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests\admit.rs (target\debug\deps\admit-8a1640d8214de7d6.exe)

running 27 tests
test allow_all_conditions_pass ... ok
test allow_key_slot_7_with_correct_hmac ... ok
test allow_hmac_exact_match ... ok
test allow_nvlink_sm_active_vram_held ... ok
test allow_pcie_cache_hit_bypasses_bw_check ... ok
test allow_nvlink_sm_zero_no_vram ... ok
test deny_all_bools_false_hmac_correct ... ok
test deny_lease_invalid ... ok
test deny_hmac_mismatch ... ok
test deny_hmac_wrong_key_slot ... ok
test deny_hmac_wrong_request_hash ... ok
test deny_when_false ... ok
test deny_key_slot_max_u8 ... ok
test deny_key_slot_out_of_bounds ... ok
test deny_misaligned_pointer ... ok
test allow_pcie_good_bandwidth ... ok
test deny_hmac_wrong_and_nvlink_waiter ... ok
test deny_null_pointer ... ok
test deny_pcie_congested_cache_miss ... ok
test deny_what_false ... ok
test deny_how_false ... ok
test deny_epoch_stale ... ok
test deny_why_false ... ok
test deny_nvlink_waiter_sm_zero_vram_held ... ok
test deny_where_false ... ok
test deny_who_false ... ok
test deny_zero_adapter_id_sentinel ... ok

test result: ok. 27 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests\consent_hierarchy.rs (target\debug\deps\consent_hierarchy-af95974268f52704.exe)

running 26 tests
test above_threshold_flows ... ok
test all_active_granted_passes ... ok
test channel_with_inactive_source_fails ... ok
test capacity_exhausted_when_at_limit ... ok
test effective_policy_bounded_by_child ... ok
test channel_with_active_endpoints ... ok
test effective_policy_takes_min ... ok
test parent_consent_revoked_fails ... ok
test parent_inactive_full_consent_fails ... ok
test shallower_requestor_allowed ... ok
test child_consent_revoked_fails ... ok
test deeper_requestor_denied ... ok
test effective_policy_bounded_by_parent ... ok
test below_threshold_blocked ... ok
test inactive_node_denies_modification ... ok
test capacity_available_when_under ... ok
test parent_inactive_cascades_deny ... ok
test child_access_parent_both_required ... ok
test sub_policy_exceeds_capacity ... ok
test capacity_exhausted_when_over ... ok
test full_consent_implies_stabilizer_ok ... ok
test zero_distance_stabilizer_invalid ... ok
test child_access_fails_without_consent ... ok
test positive_distance_valid_syndrome_ok ... ok
test sub_policy_within_capacity ... ok
test closed_channel_blocked ... ok

test result: ok. 26 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests\crypto_vectors.rs (target\debug\deps\crypto_vectors-fb7aadcd240b5267.exe)

running 14 tests
test ct_eq_32_all_different ... ok
test ct_eq_32_equal_arrays ... ok
test ct_eq_32_single_bit_difference ... ok
test sha256_empty_string ... ok
test sha256_short_nist_abc ... ok
test sha256_64_bytes ... ok
test hmac_rfc4231_case1 ... ok
test sha256_nist_448bit ... ok
test sha256_nist_abc ... ok
test sha256_short_55_bytes ... ok
test sha256_short_empty_string ... ok
test sha256_single_byte ... ok
test hmac_rfc4231_case3 ... ok
test hmac_rfc4231_case2 ... ok

test result: ok. 14 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s

     Running tests\dual_ledger.rs (target\debug\deps\dual_ledger-8113c410c4513519.exe)

running 15 tests
test anon_id_linked_when_matching ... ok
test anon_id_not_linked_epoch_mismatch ... ok
test anon_id_not_linked_hash_mismatch ... ok
test dual_ledger_ok_when_all_valid ... ok
test dual_ledger_requires_linkage ... ok
test epoch_mismatch_breaks_linkage ... ok
test identity_leak_breaks_dual_ledger ... ok
test no_capability_no_access ... ok
test no_consent_no_access ... ok
test policy_filter_active_regardless_of_consent ... ok
test public_hides_identity_when_hashes_differ ... ok
test consent_valid_requires_both ... ok
test full_admission_requires_all_three ... ok
test policy_filter_inactive ... ok
test public_leaks_identity_when_hashes_match ... ok

test result: ok. 15 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out; finished in 0.00s


All **82/82 tests pass**. Task 14 done.


Updated task #15 status


Write

/*
 * cubie.h — C ABI header for the Cubie Trust Compiler admission gate.
 *
 * This header documents the binary interface of libcubie_ffi.so / cubie_ffi.dll.
 * All types match the Rust #[repr(C)] layout exactly.
 *
 * Build: cargo build --release --manifest-path cubie-ffi/Cargo.toml
 */

#ifndef CUBIE_H
#define CUBIE_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/* -----------------------------------------------------------------------
 * CubieRequest — 40-byte admission request (mirrors bare-metal/src/types.rs)
 *
 * Layout:
 *   [0]     who_pass           _Bool
 *   [1]     what_pass          _Bool
 *   [2]     how_pass           _Bool
 *   [3]     where_pass         _Bool
 *   [4]     when_pass          _Bool
 *   [5]     why_pass           _Bool
 *   [6]     has_valid_lease    _Bool
 *   [7]     epoch_fresh        _Bool
 *   [8-15]  request_hash       uint64_t
 *   [16-23] capability_hmac    uint64_t
 *   [24]    key_slot           uint8_t
 *   [25]    peer_id            uint8_t
 *   [26-31] (padding)          6 bytes
 *   [32-39] adapter_id         uint64_t
 * ----------------------------------------------------------------------- */
typedef struct {
    _Bool    who_pass;
    _Bool    what_pass;
    _Bool    how_pass;
    _Bool    where_pass;
    _Bool    when_pass;
    _Bool    why_pass;
    _Bool    has_valid_lease;
    _Bool    epoch_fresh;
    uint64_t request_hash;
    uint64_t capability_hmac;
    uint8_t  key_slot;
    uint8_t  peer_id;
    uint8_t  _padding[6];
    uint64_t adapter_id;
} CubieRequest;

_Static_assert(sizeof(CubieRequest) == 40, "CubieRequest must be exactly 40 bytes");

/* -----------------------------------------------------------------------
 * AdmitResult — admission decision (mirrors bare-metal/src/types.rs)
 * ----------------------------------------------------------------------- */
typedef enum {
    CUBIE_DENY  = 0,
    CUBIE_ALLOW = 1,
} AdmitResult;

/* -----------------------------------------------------------------------
 * Core admission gate — sub-microsecond decision
 * ----------------------------------------------------------------------- */

/* Evaluate a CubieRequest and return Allow (1) or Deny (0).
 * req must be non-null, 8-byte aligned, and point to a valid CubieRequest. */
AdmitResult cubie_admit(const CubieRequest *req);

/* -----------------------------------------------------------------------
 * Mock MMIO setup — for testing without real hardware
 * ----------------------------------------------------------------------- */

/* Configure default mock state: eFuse key in slot 0, NVLink safe, PCIe 8GB/s.
 * key must point to 32 bytes. */
void cubie_mock_setup(const uint8_t *key);

/* Set eFuse key for a specific slot (0-7). key must point to 32 bytes. */
void cubie_mock_set_efuse_key(uint8_t slot, const uint8_t *key);

/* Set NVLink peer SM activity and VRAM allocation. */
void cubie_mock_set_nvlink(uint8_t peer, uint32_t sm, uint32_t vram);

/* Set available PCIe upstream bandwidth in MB/s. */
void cubie_mock_set_pcie_bw(uint32_t bw);

/* Clear the PCIe adapter cache (for test isolation). */
void cubie_clear_adapter_cache(void);

/* -----------------------------------------------------------------------
 * HMAC computation — build valid requests without reimplementing crypto
 * ----------------------------------------------------------------------- */

/* Compute HMAC-SHA256(key, request_hash) and return truncated u64.
 * key must point to 32 bytes. */
uint64_t cubie_compute_hmac(const uint8_t *key, uint64_t request_hash);

/* Write a fully-valid CubieRequest to out. Returns 1 on success, 0 on null.
 * key must point to 32 bytes (same key used in cubie_mock_setup). */
uint32_t cubie_create_valid_request(CubieRequest *out, const uint8_t *key);

#ifdef __cplusplus
}
#endif

#endif /* CUBIE_H */


File created successfully at: C:\Users\NickV\cubie-verification\cubie-ffi\include\cubie.h

/*
 * cubie.h — C ABI header for the Cubie Trust Compiler admission gate.
 *
 * This header documents the binary interface of libcubie_ffi.so / cubie_ffi.dll.
 * All types match the Rust #[repr(C)] layout exactly.
 *
 * Build: cargo build --release --manifest-path cubie-ffi/Cargo.toml
 */

#ifndef CUBIE_H
#define CUBIE_H

#include <stdint.h>

#ifdef __cplusplus
extern "C" {
#endif

/* -----------------------------------------------------------------------
 * CubieRequest — 40-byte admission request (mirrors bare-metal/src/types.rs)
 *
 * Layout:
 *   [0]     who_pass           _Bool
 *   [1]     what_pass          _Bool
 *   [2]     how_pass           _Bool
 *   [3]     where_pass         _Bool
 *   [4]     when_pass          _Bool
 *   [5]     why_pass           _Bool
 *   [6]     has_valid_lease    _Bool
 *   [7]     epoch_fresh        _Bool
 *   [8-15]  request_hash       uint64_t
 *   [16-23] capability_hmac    uint64_t
 *   [24]    key_slot           uint8_t
 *   [25]    peer_id            uint8_t
 *   [26-31] (padding)          6 bytes
 *   [32-39] adapter_id         uint64_t
 * ----------------------------------------------------------------------- */
typedef struct {
    _Bool    who_pass;
    _Bool    what_pass;
    _Bool    how_pass;
    _Bool    where_pass;
    _Bool    when_pass;
    _Bool    why_pass;
    _Bool    has_valid_lease;
    _Bool    epoch_fresh;
    uint64_t request_hash;
    uint64_t capability_hmac;
    uint8_t  key_slot;
    uint8_t  peer_id;
    uint8_t  _padding[6];
    uint64_t adapter_id;
} CubieRequest;

_Static_assert(sizeof(CubieRequest) == 40, "CubieRequest must be exactly 40 bytes");

/* -----------------------------------------------------------------------
 * AdmitResult — admission decision (mirrors bare-metal/src/types.rs)
 * ----------------------------------------------------------------------- */
typedef enum {
    CUBIE_DENY  = 0,
    CUBIE_ALLOW = 1,
} AdmitResult;

/* -----------------------------------------------------------------------
 * Core admission gate — sub-microsecond decision
 * ----------------------------------------------------------------------- */

/* Evaluate a CubieRequest and return Allow (1) or Deny (0).
 * req must be non-null, 8-byte aligned, and point to a valid CubieRequest. */
AdmitResult cubie_admit(const CubieRequest *req);

/* -----------------------------------------------------------------------
 * Mock MMIO setup — for testing without real hardware
 * ----------------------------------------------------------------------- */

/* Configure default mock state: eFuse key in slot 0, NVLink safe, PCIe 8GB/s.
 * key must point to 32 bytes. */
void cubie_mock_setup(const uint8_t *key);

/* Set eFuse key for a specific slot (0-7). key must point to 32 bytes. */
void cubie_mock_set_efuse_key(uint8_t slot, const uint8_t *key);

/* Set NVLink peer SM activity and VRAM allocation. */
void cubie_mock_set_nvlink(uint8_t peer, uint32_t sm, uint32_t vram);

/* Set available PCIe upstream bandwidth in MB/s. */
void cubie_mock_set_pcie_bw(uint32_t bw);

/* Clear the PCIe adapter cache (for test isolation). */
void cubie_clear_adapter_cache(void);

/* -----------------------------------------------------------------------
 * HMAC computation — build valid requests without reimplementing crypto
 * ----------------------------------------------------------------------- */

/* Compute HMAC-SHA256(key, request_hash) and return truncated u64.
 * key must point to 32 bytes. */
uint64_t cubie_compute_hmac(const uint8_t *key, uint64_t request_hash);

/* Write a fully-valid CubieRequest to out. Returns 1 on success, 0 on null.
 * key must point to 32 bytes (same key used in cubie_mock_setup). */
uint32_t cubie_create_valid_request(CubieRequest *out, const uint8_t *key);

#ifdef __cplusplus
}
#endif

#endif /* CUBIE_H */


Updated task #16 status


"""
cubie_gate.py — Python ctypes wrapper for the Cubie Trust Compiler admission gate.

Loads the cubie_ffi shared library and exposes cubie_admit() via a CubieGate class.
Designed for the Intel Edge AI demo pipeline.

Usage:
    from pipeline.cubie_gate import CubieGate

    gate = CubieGate()
    req = gate.create_test_request()
    result = gate.admit(req)          # True = Allow, False = Deny
    stats = gate.benchmark(100_000)   # ns/call timing
"""

import ctypes
import os
import sys
import time
from ctypes import (
    Structure, c_bool, c_uint8, c_uint32, c_uint64,
    POINTER, byref, c_char,
)
from pathlib import Path

class CubieRequest(Structure):
    """
    Mirrors bare-metal/src/types.rs CubieRequest (#[repr(C)], 40 bytes).

    Layout verified by static_assert in cubie.h.
    """
    _fields_ = [
        ("who_pass",        c_bool),
        ("what_pass",       c_bool),
        ("how_pass",        c_bool),
        ("where_pass",      c_bool),
        ("when_pass",       c_bool),
        ("why_pass",        c_bool),
        ("has_valid_lease",  c_bool),
        ("epoch_fresh",     c_bool),
        ("request_hash",    c_uint64),
        ("capability_hmac", c_uint64),
        ("key_slot",        c_uint8),
        ("peer_id",         c_uint8),
        ("_padding",        c_uint8 * 6),
        ("adapter_id",      c_uint64),
    ]

    def __repr__(self):
        faces = []
        for name in ("who", "what", "how", "where", "when", "why"):
            val = getattr(self, f"{name}_pass")
            faces.append(f"{name}={'T' if val else 'F'}")
        return (
            f"CubieRequest({', '.join(faces)}, "
            f"lease={'T' if self.has_valid_lease else 'F'}, "
            f"epoch={'T' if self.epoch_fresh else 'F'}, "
            f"hash=0x{self.request_hash:016X}, "
            f"hmac=0x{self.capability_hmac:016X}, "
            f"slot={self.key_slot}, peer={self.peer_id}, "
            f"adapter=0x{self.adapter_id:016X})"
        )

# AdmitResult: 0 = Deny, 1 = Allow (repr(u32) in Rust)
CUBIE_DENY = 0
CUBIE_ALLOW = 1

# Default test key (matches bare-metal-tests/tests/admit.rs TEST_KEY)
DEFAULT_TEST_KEY = bytes([0x42] * 32)

def _find_library() -> str:
    """Locate the cubie_ffi shared library."""
    # Check relative to this file's location (project layout)
    base = Path(__file__).resolve().parent.parent

    candidates = [
        base / "target" / "release" / "cubie_ffi.dll",
        base / "target" / "release" / "libcubie_ffi.so",
        base / "target" / "release" / "libcubie_ffi.dylib",
        base / "target" / "debug" / "cubie_ffi.dll",
        base / "target" / "debug" / "libcubie_ffi.so",
        base / "target" / "debug" / "libcubie_ffi.dylib",
    ]

    # Also check CUBIE_LIB_PATH environment variable
    env_path = os.environ.get("CUBIE_LIB_PATH")
    if env_path:
        candidates.insert(0, Path(env_path))

    for path in candidates:
        if path.exists():
            return str(path)

    raise FileNotFoundError(
        f"cubie_ffi library not found. Build with: "
        f"cargo build --release --manifest-path cubie-ffi/Cargo.toml\n"
        f"Searched: {[str(c) for c in candidates]}"
    )

class CubieGate:
    """
    Python interface to the Cubie Trust Compiler admission gate.

    Wraps the native cubie_ffi shared library via ctypes.
    All MMIO is mocked (AtomicU32 cells) — no real hardware access.
    """

    def __init__(self, lib_path: str = None, key: bytes = None):
        """
        Load the shared library and configure mock MMIO state.

        Args:
            lib_path: Path to cubie_ffi.dll / libcubie_ffi.so. Auto-detected if None.
            key: 32-byte eFuse key for HMAC. Defaults to 0x42 * 32.
        """
        path = lib_path or _find_library()
        self._lib = ctypes.CDLL(path)
        self._key = key or DEFAULT_TEST_KEY

        # Bind function signatures
        self._bind_functions()

        # Initialize mock MMIO state
        key_buf = (c_uint8 * 32)(*self._key)
        self._lib.cubie_mock_setup(key_buf)

    def _bind_functions(self):
        """Set argtypes/restype for all exported functions."""
        lib = self._lib

        lib.cubie_admit.argtypes = [POINTER(CubieRequest)]
        lib.cubie_admit.restype = c_uint32

        lib.cubie_mock_setup.argtypes = [POINTER(c_uint8)]
        lib.cubie_mock_setup.restype = None

        lib.cubie_mock_set_efuse_key.argtypes = [c_uint8, POINTER(c_uint8)]
        lib.cubie_mock_set_efuse_key.restype = None

        lib.cubie_mock_set_nvlink.argtypes = [c_uint8, c_uint32, c_uint32]
        lib.cubie_mock_set_nvlink.restype = None

        lib.cubie_mock_set_pcie_bw.argtypes = [c_uint32]
        lib.cubie_mock_set_pcie_bw.restype = None

        lib.cubie_clear_adapter_cache.argtypes = []
        lib.cubie_clear_adapter_cache.restype = None

        lib.cubie_compute_hmac.argtypes = [POINTER(c_uint8), c_uint64]
        lib.cubie_compute_hmac.restype = c_uint64

        lib.cubie_create_valid_request.argtypes = [POINTER(CubieRequest), POINTER(c_uint8)]
        lib.cubie_create_valid_request.restype = c_uint32

    def admit(self, req: CubieRequest) -> bool:
        """
        Run the admission gate on a CubieRequest.

        Returns True (Allow) or False (Deny).
        """
        result = self._lib.cubie_admit(byref(req))
        return result == CUBIE_ALLOW

    def create_test_request(self, request_hash: int = 0xDEAD_BEEF_CAFE_0001) -> CubieRequest:
        """
        Create a fully-valid CubieRequest that will pass all gates.

        Uses cubie_create_valid_request from the FFI library to ensure
        the HMAC is computed by the same code path as the verifier.
        """
        req = CubieRequest()
        key_buf = (c_uint8 * 32)(*self._key)
        ok = self._lib.cubie_create_valid_request(byref(req), key_buf)
        if not ok:
            raise RuntimeError("cubie_create_valid_request failed (null pointer)")
        return req

    def compute_hmac(self, request_hash: int) -> int:
        """
        Compute HMAC-SHA256 for a request_hash using the gate's eFuse key.

        Returns the truncated 64-bit MAC value.
        """
        key_buf = (c_uint8 * 32)(*self._key)
        return self._lib.cubie_compute_hmac(key_buf, c_uint64(request_hash))

    def set_nvlink(self, peer: int, sm: int, vram: int):
        """Set NVLink peer mock state."""
        self._lib.cubie_mock_set_nvlink(c_uint8(peer), c_uint32(sm), c_uint32(vram))

    def set_pcie_bw(self, bw_mbs: int):
        """Set PCIe upstream bandwidth mock state (MB/s)."""
        self._lib.cubie_mock_set_pcie_bw(c_uint32(bw_mbs))

    def clear_cache(self):
        """Clear the PCIe adapter cache."""
        self._lib.cubie_clear_adapter_cache()

    def benchmark(self, iterations: int = 100_000) -> dict:
        """
        Benchmark cubie_admit throughput.

        Returns dict with:
            iterations: number of calls
            total_ns: total elapsed nanoseconds
            ns_per_call: average nanoseconds per call
            calls_per_sec: throughput
        """
        req = self.create_test_request()
        admit_fn = self._lib.cubie_admit
        req_ptr = byref(req)

        # Warm up
        for _ in range(1000):
            admit_fn(req_ptr)

        # Timed run
        start = time.perf_counter_ns()
        for _ in range(iterations):
            admit_fn(req_ptr)
        elapsed_ns = time.perf_counter_ns() - start

        ns_per_call = elapsed_ns / iterations
        calls_per_sec = 1_000_000_000 / ns_per_call if ns_per_call > 0 else float("inf")

        return {
            "iterations": iterations,
            "total_ns": elapsed_ns,
            "ns_per_call": round(ns_per_call, 1),
            "calls_per_sec": round(calls_per_sec),
        }

if __name__ == "__main__":
    gate = CubieGate()

    # Smoke test: valid request should be allowed
    req = gate.create_test_request()
    result = gate.admit(req)
    print(f"Valid request: {'ALLOW' if result else 'DENY'} (expected: ALLOW)")
    assert result, "Valid request was denied!"

    # Smoke test: null who_pass should deny
    req2 = gate.create_test_request()
    req2.who_pass = False
    result2 = gate.admit(req2)
    print(f"who=False:     {'ALLOW' if result2 else 'DENY'} (expected: DENY)")
    assert not result2, "Invalid request was allowed!"

    # Benchmark
    stats = gate.benchmark(100_000)
    print(f"\nBenchmark: {stats['iterations']:,} calls")
    print(f"  {stats['ns_per_call']} ns/call")
    print(f"  {stats['calls_per_sec']:,} calls/sec")
    print(f"  Total: {stats['total_ns'] / 1_000_000:.1f} ms")


File created successfully at: C:\Users\NickV\cubie-verification\pipeline\cubie_gate.py

"""
cubie_gate.py — Python ctypes wrapper for the Cubie Trust Compiler admission gate.

Loads the cubie_ffi shared library and exposes cubie_admit() via a CubieGate class.
Designed for the Intel Edge AI demo pipeline.

Usage:
    from pipeline.cubie_gate import CubieGate

    gate = CubieGate()
    req = gate.create_test_request()
    result = gate.admit(req)          # True = Allow, False = Deny
    stats = gate.benchmark(100_000)   # ns/call timing
"""

import ctypes
import os
import sys
import time
from ctypes import (
    Structure, c_bool, c_uint8, c_uint32, c_uint64,
    POINTER, byref, c_char,
)
from pathlib import Path

class CubieRequest(Structure):
    """
    Mirrors bare-metal/src/types.rs CubieRequest (#[repr(C)], 40 bytes).

    Layout verified by static_assert in cubie.h.
    """
    _fields_ = [
        ("who_pass",        c_bool),
        ("what_pass",       c_bool),
        ("how_pass",        c_bool),
        ("where_pass",      c_bool),
        ("when_pass",       c_bool),
        ("why_pass",        c_bool),
        ("has_valid_lease",  c_bool),
        ("epoch_fresh",     c_bool),
        ("request_hash",    c_uint64),
        ("capability_hmac", c_uint64),
        ("key_slot",        c_uint8),
        ("peer_id",         c_uint8),
        ("_padding",        c_uint8 * 6),
        ("adapter_id",      c_uint64),
    ]

    def __repr__(self):
        faces = []
        for name in ("who", "what", "how", "where", "when", "why"):
            val = getattr(self, f"{name}_pass")
            faces.append(f"{name}={'T' if val else 'F'}")
        return (
            f"CubieRequest({', '.join(faces)}, "
            f"lease={'T' if self.has_valid_lease else 'F'}, "
            f"epoch={'T' if self.epoch_fresh else 'F'}, "
            f"hash=0x{self.request_hash:016X}, "
            f"hmac=0x{self.capability_hmac:016X}, "
            f"slot={self.key_slot}, peer={self.peer_id}, "
            f"adapter=0x{self.adapter_id:016X})"
        )

# AdmitResult: 0 = Deny, 1 = Allow (repr(u32) in Rust)
CUBIE_DENY = 0
CUBIE_ALLOW = 1

# Default test key (matches bare-metal-tests/tests/admit.rs TEST_KEY)
DEFAULT_TEST_KEY = bytes([0x42] * 32)

def _find_library() -> str:
    """Locate the cubie_ffi shared library."""
    # Check relative to this file's location (project layout)
    base = Path(__file__).resolve().parent.parent

    candidates = [
        base / "target" / "release" / "cubie_ffi.dll",
        base / "target" / "release" / "libcubie_ffi.so",
        base / "target" / "release" / "libcubie_ffi.dylib",
        base / "target" / "debug" / "cubie_ffi.dll",
        base / "target" / "debug" / "libcubie_ffi.so",
        base / "target" / "debug" / "libcubie_ffi.dylib",
    ]

    # Also check CUBIE_LIB_PATH environment variable
    env_path = os.environ.get("CUBIE_LIB_PATH")
    if env_path:
        candidates.insert(0, Path(env_path))

    for path in candidates:
        if path.exists():
            return str(path)

    raise FileNotFoundError(
        f"cubie_ffi library not found. Build with: "
        f"cargo build --release --manifest-path cubie-ffi/Cargo.toml\n"
        f"Searched: {[str(c) for c in candidates]}"
    )

class CubieGate:
    """
    Python interface to the Cubie Trust Compiler admission gate.

    Wraps the native cubie_ffi shared library via ctypes.
    All MMIO is mocked (AtomicU32 cells) — no real hardware access.
    """

    def __init__(self, lib_path: str = None, key: bytes = None):
        """
        Load the shared library and configure mock MMIO state.

        Args:
            lib_path: Path to cubie_ffi.dll / libcubie_ffi.so. Auto-detected if None.
            key: 32-byte eFuse key for HMAC. Defaults to 0x42 * 32.
        """
        path = lib_path or _find_library()
        self._lib = ctypes.CDLL(path)
        self._key = key or DEFAULT_TEST_KEY

        # Bind function signatures
        self._bind_functions()

        # Initialize mock MMIO state
        key_buf = (c_uint8 * 32)(*self._key)
        self._lib.cubie_mock_setup(key_buf)

    def _bind_functions(self):
        """Set argtypes/restype for all exported functions."""
        lib = self._lib

        lib.cubie_admit.argtypes = [POINTER(CubieRequest)]
        lib.cubie_admit.restype = c_uint32

        lib.cubie_mock_setup.argtypes = [POINTER(c_uint8)]
        lib.cubie_mock_setup.restype = None

        lib.cubie_mock_set_efuse_key.argtypes = [c_uint8, POINTER(c_uint8)]
        lib.cubie_mock_set_efuse_key.restype = None

        lib.cubie_mock_set_nvlink.argtypes = [c_uint8, c_uint32, c_uint32]
        lib.cubie_mock_set_nvlink.restype = None

        lib.cubie_mock_set_pcie_bw.argtypes = [c_uint32]
        lib.cubie_mock_set_pcie_bw.restype = None

        lib.cubie_clear_adapter_cache.argtypes = []
        lib.cubie_clear_adapter_cache.restype = None

        lib.cubie_compute_hmac.argtypes = [POINTER(c_uint8), c_uint64]
        lib.cubie_compute_hmac.restype = c_uint64

        lib.cubie_create_valid_request.argtypes = [POINTER(CubieRequest), POINTER(c_uint8)]
        lib.cubie_create_valid_request.restype = c_uint32

    def admit(self, req: CubieRequest) -> bool:
        """
        Run the admission gate on a CubieRequest.

        Returns True (Allow) or False (Deny).
        """
        result = self._lib.cubie_admit(byref(req))
        return result == CUBIE_ALLOW

    def create_test_request(self, request_hash: int = 0xDEAD_BEEF_CAFE_0001) -> CubieRequest:
        """
        Create a fully-valid CubieRequest that will pass all gates.

        Uses cubie_create_valid_request from the FFI library to ensure
        the HMAC is computed by the same code path as the verifier.
        """
        req = CubieRequest()
        key_buf = (c_uint8 * 32)(*self._key)
        ok = self._lib.cubie_create_valid_request(byref(req), key_buf)
        if not ok:
            raise RuntimeError("cubie_create_valid_request failed (null pointer)")
        return req

    def compute_hmac(self, request_hash: int) -> int:
        """
        Compute HMAC-SHA256 for a request_hash using the gate's eFuse key.

        Returns the truncated 64-bit MAC value.
        """
        key_buf = (c_uint8 * 32)(*self._key)
        return self._lib.cubie_compute_hmac(key_buf, c_uint64(request_hash))

    def set_nvlink(self, peer: int, sm: int, vram: int):
        """Set NVLink peer mock state."""
        self._lib.cubie_mock_set_nvlink(c_uint8(peer), c_uint32(sm), c_uint32(vram))

    def set_pcie_bw(self, bw_mbs: int):
        """Set PCIe upstream bandwidth mock state (MB/s)."""
        self._lib.cubie_mock_set_pcie_bw(c_uint32(bw_mbs))

    def clear_cache(self):
        """Clear the PCIe adapter cache."""
        self._lib.cubie_clear_adapter_cache()

    def benchmark(self, iterations: int = 100_000) -> dict:
        """
        Benchmark cubie_admit throughput.

        Returns dict with:
            iterations: number of calls
            total_ns: total elapsed nanoseconds
            ns_per_call: average nanoseconds per call
            calls_per_sec: throughput
        """
        req = self.create_test_request()
        admit_fn = self._lib.cubie_admit
        req_ptr = byref(req)

        # Warm up
        for _ in range(1000):
            admit_fn(req_ptr)

        # Timed run
        start = time.perf_counter_ns()
        for _ in range(iterations):
            admit_fn(req_ptr)
        elapsed_ns = time.perf_counter_ns() - start

        ns_per_call = elapsed_ns / iterations
        calls_per_sec = 1_000_000_000 / ns_per_call if ns_per_call > 0 else float("inf")

        return {
            "iterations": iterations,
            "total_ns": elapsed_ns,
            "ns_per_call": round(ns_per_call, 1),
            "calls_per_sec": round(calls_per_sec),
        }

if __name__ == "__main__":
    gate = CubieGate()

    # Smoke test: valid request should be allowed
    req = gate.create_test_request()
    result = gate.admit(req)
    print(f"Valid request: {'ALLOW' if result else 'DENY'} (expected: ALLOW)")
    assert result, "Valid request was denied!"

    # Smoke test: null who_pass should deny
    req2 = gate.create_test_request()
    req2.who_pass = False
    result2 = gate.admit(req2)
    print(f"who=False:     {'ALLOW' if result2 else 'DENY'} (expected: DENY)")
    assert not result2, "Invalid request was allowed!"

    # Benchmark
    stats = gate.benchmark(100_000)
    print(f"\nBenchmark: {stats['iterations']:,} calls")
    print(f"  {stats['ns_per_call']} ns/call")
    print(f"  {stats['calls_per_sec']:,} calls/sec")
    print(f"  Total: {stats['total_ns'] / 1_000_000:.1f} ms")


Updated task #17 status


Now run the Python smoke test:


Bash

cd "C:/Users/NickV/cubie-verification" && python pipeline/cubie_gate.py 2>&1

Run Python FFI smoke test and benchmark


Valid request: ALLOW (expected: ALLOW)
who=False:     DENY (expected: DENY)

Benchmark: 100,000 calls
  895.1 ns/call
  1,117,157 calls/sec
  Total: 89.5 ms


FFI bridge is fully operational. **895 ns/call, 1.12M calls/sec** from Python via ctypes.


Phase 1 complete. Now Phase 2 — OpenVINO pipeline with policy evaluator, demo app, load profiles, and resource monitor.


TaskCreate

Phase 2: Create policy evaluator (pipeline/policy.py)

Admission policy evaluator that maps real workload properties (model_name, device, batch_size, nireq) to CubieRequest face booleans. Includes model registry check, config bounds, lease validation.


Task #18 created successfully: Phase 2: Create policy evaluator (pipeline/policy.py)


Phase 2: Create models.json approved model registry

JSON config listing allowed (model_name, device, max_batch, max_nireq) tuples for policy evaluation.


Task #19 created successfully: Phase 2: Create models.json approved model registry


Phase 2: Create demo app (pipeline/cubie_openvino_demo.py)

FastAPI server with POST /admit, POST /launch, GET /metrics, GET /health routes. Integrates policy.py + cubie_gate.py. Optional OpenVINO model loading on admit-allow.


Task #20 created successfully: Phase 2: Create demo app (pipeline/cubie_openvino_demo.py)


Phase 2: Create load generator and resource monitor

load_profiles.py (light/medium/heavy workload generators) and resource_monitor.py (CPU/memory/GPU sampling via psutil, CSV output). requirements.txt.


Task #21 created successfully: Phase 2: Create load generator and resource monitor


Updated task #18 status


Updated task #19 status


Write

{
  "approved_models": [
    {
      "model_name": "yolo11n",
      "devices": ["CPU", "GPU", "NPU"],
      "max_batch": 8,
      "max_nireq": 16,
      "max_streams": 4,
      "description": "YOLO v11 nano — object detection"
    },
    {
      "model_name": "yolo-v8",
      "devices": ["CPU", "GPU"],
      "max_batch": 8,
      "max_nireq": 16,
      "max_streams": 4,
      "description": "YOLO v8 — object detection (OpenVINO Model Zoo)"
    },
    {
      "model_name": "ssd_mobilenet_v2",
      "devices": ["CPU", "GPU", "NPU"],
      "max_batch": 16,
      "max_nireq": 32,
      "max_streams": 8,
      "description": "SSD MobileNet v2 — lightweight detection"
    },
    {
      "model_name": "TinyLlama/TinyLlama-1.1B-Chat-v1.0",
      "devices": ["CPU", "GPU"],
      "max_batch": 1,
      "max_nireq": 4,
      "max_streams": 1,
      "description": "TinyLlama 1.1B — text generation"
    },
    {
      "model_name": "resnet-50-tf",
      "devices": ["CPU", "GPU", "NPU"],
      "max_batch": 32,
      "max_nireq": 64,
      "max_streams": 8,
      "description": "ResNet-50 — image classification"
    }
  ],
  "allowed_keys": [
    "cubie-demo-key-001",
    "cubie-test-key-002",
    "intel-edge-eval"
  ],
  "authorized_nodes": [
    "local",
    "intel-testbed-01",
    "intel-testbed-02"
  ],
  "max_priority": 2,
  "lease_duration_sec": 3600
}


File created successfully at: C:\Users\NickV\cubie-verification\pipeline\models.json

YOLO v11 nano — object detection

YOLO v8 — object detection (OpenVINO Model Zoo)

SSD MobileNet v2 — lightweight detection

TinyLlama 1.1B — text generation

ResNet-50 — image classification


"""
policy.py — Admission policy evaluator for the Cubie Trust Compiler.

Maps real Intel Edge workload properties (model_name, device, batch_size, etc.)
to CubieRequest face booleans. This bridges the gap between OpenVINO workload
configurations and the 6-face admission model.

The 6 faces:
    who:   Is the requesting entity authenticated?
    what:  Is this model+device combination approved?
    how:   Is the execution config (batch, nireq, streams) within bounds?
    where: Is this edge node authorized for this workload type?
    when:  Is the time-bounded execution lease still valid?
    why:   Does the workload comply with governance rules (priority, quotas)?
"""

import hashlib
import json
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

from pipeline.cubie_gate import CubieGate, CubieRequest

# Default policy config path
DEFAULT_POLICY_PATH = Path(__file__).parent / "models.json"

@dataclass
class WorkloadRequest:
    """Incoming workload submission — mirrors Intel Edge Sizing Tool fields."""
    model_name: str
    device: str = "CPU"
    batch_size: int = 1
    nireq: int = 4
    num_streams: int = 1
    model_precision: str = "FP16"
    performance_mode: str = "LATENCY"
    model_priority: int = 1  # 0=LOW, 1=MEDIUM, 2=HIGH
    api_key: str = ""
    node_id: str = "local"
    lease_expiry: Optional[float] = None  # epoch seconds; None = no expiry
    elevated: bool = False

@dataclass
class PolicyResult:
    """Result of policy evaluation — includes per-face verdicts."""
    allowed: bool
    who_pass: bool
    what_pass: bool
    how_pass: bool
    where_pass: bool
    when_pass: bool
    why_pass: bool
    request_hash: int = 0
    deny_reason: str = ""
    eval_ns: int = 0

class PolicyEvaluator:
    """
    Evaluates workload requests against a policy config and the Cubie admission gate.

    The evaluator:
    1. Checks each of the 6 faces against policy rules
    2. Computes request_hash from workload config
    3. Computes capability_hmac via the gate's FIPS 198-1 HMAC
    4. Calls cubie_admit() for the hardware-backed decision
    """

    def __init__(self, gate: CubieGate, policy_path: str = None):
        self._gate = gate
        policy_file = Path(policy_path) if policy_path else DEFAULT_POLICY_PATH
        with open(policy_file, "r") as f:
            self._policy = json.load(f)

        # Build lookup structures
        self._model_index = {}
        for m in self._policy.get("approved_models", []):
            for dev in m.get("devices", []):
                key = (m["model_name"], dev)
                self._model_index[key] = m

        self._allowed_keys = set(self._policy.get("allowed_keys", []))
        self._authorized_nodes = set(self._policy.get("authorized_nodes", []))
        self._max_priority = self._policy.get("max_priority", 2)

    def evaluate(self, workload: WorkloadRequest) -> PolicyResult:
        """
        Evaluate a workload request against policy and the admission gate.

        Returns PolicyResult with per-face verdicts and overall allow/deny.
        """
        start = time.perf_counter_ns()

        # Face 1: WHO — identity/authentication
        who = workload.api_key in self._allowed_keys

        # Face 2: WHAT — model+device approval
        model_entry = self._model_index.get((workload.model_name, workload.device))
        what = model_entry is not None

        # Face 3: HOW — execution config bounds
        if model_entry:
            how = (
                workload.batch_size <= model_entry.get("max_batch", 8)
                and workload.nireq <= model_entry.get("max_nireq", 16)
                and workload.num_streams <= model_entry.get("max_streams", 4)
            )
        else:
            how = False

        # Face 4: WHERE — node authorization
        where = workload.node_id in self._authorized_nodes

        # Face 5: WHEN — lease validity
        if workload.lease_expiry is not None:
            when = workload.lease_expiry > time.time()
        else:
            when = True  # no expiry = always valid

        # Face 6: WHY — governance/policy compliance
        why = (
            workload.model_priority <= self._max_priority
            or workload.elevated
        )

        # Compute request hash from workload config fields
        config_str = f"{workload.model_name}:{workload.device}:{workload.batch_size}"
        hash_bytes = hashlib.sha256(config_str.encode()).digest()[:8]
        request_hash = int.from_bytes(hash_bytes, "big")

        # Build deny reason string
        deny_reasons = []
        if not who:
            deny_reasons.append("who: invalid API key")
        if not what:
            deny_reasons.append(f"what: {workload.model_name}+{workload.device} not approved")
        if not how:
            deny_reasons.append("how: config exceeds bounds")
        if not where:
            deny_reasons.append(f"where: node {workload.node_id} not authorized")
        if not when:
            deny_reasons.append("when: lease expired")
        if not why:
            deny_reasons.append("why: priority exceeds limit")

        # If any face fails, skip HMAC/gate call — already denied
        if deny_reasons:
            elapsed = time.perf_counter_ns() - start
            return PolicyResult(
                allowed=False,
                who_pass=who, what_pass=what, how_pass=how,
                where_pass=where, when_pass=when, why_pass=why,
                request_hash=request_hash,
                deny_reason="; ".join(deny_reasons),
                eval_ns=elapsed,
            )

        # All faces pass — build CubieRequest and call the gate
        capability_hmac = self._gate.compute_hmac(request_hash)

        req = CubieRequest()
        req.who_pass = who
        req.what_pass = what
        req.how_pass = how
        req.where_pass = where
        req.when_pass = when
        req.why_pass = why
        req.has_valid_lease = when
        req.epoch_fresh = True
        req.request_hash = request_hash
        req.capability_hmac = capability_hmac
        req.key_slot = 0
        req.peer_id = 0
        req.adapter_id = 0x1234_5678_9ABC_DEF0

        allowed = self._gate.admit(req)
        elapsed = time.perf_counter_ns() - start

        return PolicyResult(
            allowed=allowed,
            who_pass=who, what_pass=what, how_pass=how,
            where_pass=where, when_pass=when, why_pass=why,
            request_hash=request_hash,
            deny_reason="" if allowed else "gate: hardware check failed",
            eval_ns=elapsed,
        )

if __name__ == "__main__":
    gate = CubieGate()
    evaluator = PolicyEvaluator(gate)

    # Test 1: Valid workload — should ALLOW
    w1 = WorkloadRequest(
        model_name="yolo11n",
        device="GPU",
        batch_size=2,
        nireq=4,
        api_key="cubie-demo-key-001",
    )
    r1 = evaluator.evaluate(w1)
    print(f"Test 1 (valid):     {'ALLOW' if r1.allowed else 'DENY':<5}  ({r1.eval_ns} ns)  {r1.deny_reason}")

    # Test 2: Invalid device — should DENY (what_pass fails)
    w2 = WorkloadRequest(
        model_name="yolo11n",
        device="INVALID",
        batch_size=2,
        nireq=4,
        api_key="cubie-demo-key-001",
    )
    r2 = evaluator.evaluate(w2)
    print(f"Test 2 (bad device): {'ALLOW' if r2.allowed else 'DENY':<5}  ({r2.eval_ns} ns)  {r2.deny_reason}")

    # Test 3: Batch too large — should DENY (how_pass fails)
    w3 = WorkloadRequest(
        model_name="yolo11n",
        device="CPU",
        batch_size=999,
        nireq=4,
        api_key="cubie-demo-key-001",
    )
    r3 = evaluator.evaluate(w3)
    print(f"Test 3 (big batch):  {'ALLOW' if r3.allowed else 'DENY':<5}  ({r3.eval_ns} ns)  {r3.deny_reason}")

    # Test 4: Bad API key — should DENY (who_pass fails)
    w4 = WorkloadRequest(
        model_name="yolo11n",
        device="CPU",
        batch_size=2,
        nireq=4,
        api_key="unauthorized-key",
    )
    r4 = evaluator.evaluate(w4)
    print(f"Test 4 (bad key):    {'ALLOW' if r4.allowed else 'DENY':<5}  ({r4.eval_ns} ns)  {r4.deny_reason}")


File created successfully at: C:\Users\NickV\cubie-verification\pipeline\policy.py

"""
policy.py — Admission policy evaluator for the Cubie Trust Compiler.

Maps real Intel Edge workload properties (model_name, device, batch_size, etc.)
to CubieRequest face booleans. This bridges the gap between OpenVINO workload
configurations and the 6-face admission model.

The 6 faces:
    who:   Is the requesting entity authenticated?
    what:  Is this model+device combination approved?
    how:   Is the execution config (batch, nireq, streams) within bounds?
    where: Is this edge node authorized for this workload type?
    when:  Is the time-bounded execution lease still valid?
    why:   Does the workload comply with governance rules (priority, quotas)?
"""

import hashlib
import json
import time
from dataclasses import dataclass, field
from pathlib import Path
from typing import Optional

from pipeline.cubie_gate import CubieGate, CubieRequest

# Default policy config path
DEFAULT_POLICY_PATH = Path(__file__).parent / "models.json"

@dataclass
class WorkloadRequest:
    """Incoming workload submission — mirrors Intel Edge Sizing Tool fields."""
    model_name: str
    device: str = "CPU"
    batch_size: int = 1
    nireq: int = 4
    num_streams: int = 1
    model_precision: str = "FP16"
    performance_mode: str = "LATENCY"
    model_priority: int = 1  # 0=LOW, 1=MEDIUM, 2=HIGH
    api_key: str = ""
    node_id: str = "local"
    lease_expiry: Optional[float] = None  # epoch seconds; None = no expiry
    elevated: bool = False

@dataclass
class PolicyResult:
    """Result of policy evaluation — includes per-face verdicts."""
    allowed: bool
    who_pass: bool
    what_pass: bool
    how_pass: bool
    where_pass: bool
    when_pass: bool
    why_pass: bool
    request_hash: int = 0
    deny_reason: str = ""
    eval_ns: int = 0

class PolicyEvaluator:
    """
    Evaluates workload requests against a policy config and the Cubie admission gate.

    The evaluator:
    1. Checks each of the 6 faces against policy rules
    2. Computes request_hash from workload config
    3. Computes capability_hmac via the gate's FIPS 198-1 HMAC
    4. Calls cubie_admit() for the hardware-backed decision
    """

    def __init__(self, gate: CubieGate, policy_path: str = None):
        self._gate = gate
        policy_file = Path(policy_path) if policy_path else DEFAULT_POLICY_PATH
        with open(policy_file, "r") as f:
            self._policy = json.load(f)

        # Build lookup structures
        self._model_index = {}
        for m in self._policy.get("approved_models", []):
            for dev in m.get("devices", []):
                key = (m["model_name"], dev)
                self._model_index[key] = m

        self._allowed_keys = set(self._policy.get("allowed_keys", []))
        self._authorized_nodes = set(self._policy.get("authorized_nodes", []))
        self._max_priority = self._policy.get("max_priority", 2)

    def evaluate(self, workload: WorkloadRequest) -> PolicyResult:
        """
        Evaluate a workload request against policy and the admission gate.

        Returns PolicyResult with per-face verdicts and overall allow/deny.
        """
        start = time.perf_counter_ns()

        # Face 1: WHO — identity/authentication
        who = workload.api_key in self._allowed_keys

        # Face 2: WHAT — model+device approval
        model_entry = self._model_index.get((workload.model_name, workload.device))
        what = model_entry is not None

        # Face 3: HOW — execution config bounds
        if model_entry:
            how = (
                workload.batch_size <= model_entry.get("max_batch", 8)
                and workload.nireq <= model_entry.get("max_nireq", 16)
                and workload.num_streams <= model_entry.get("max_streams", 4)
            )
        else:
            how = False

        # Face 4: WHERE — node authorization
        where = workload.node_id in self._authorized_nodes

        # Face 5: WHEN — lease validity
        if workload.lease_expiry is not None:
            when = workload.lease_expiry > time.time()
        else:
            when = True  # no expiry = always valid

        # Face 6: WHY — governance/policy compliance
        why = (
            workload.model_priority <= self._max_priority
            or workload.elevated
        )

        # Compute request hash from workload config fields
        config_str = f"{workload.model_name}:{workload.device}:{workload.batch_size}"
        hash_bytes = hashlib.sha256(config_str.encode()).digest()[:8]
        request_hash = int.from_bytes(hash_bytes, "big")

        # Build deny reason string
        deny_reasons = []
        if not who:
            deny_reasons.append("who: invalid API key")
        if not what:
            deny_reasons.append(f"what: {workload.model_name}+{workload.device} not approved")
        if not how:
            deny_reasons.append("how: config exceeds bounds")
        if not where:
            deny_reasons.append(f"where: node {workload.node_id} not authorized")
        if not when:
            deny_reasons.append("when: lease expired")
        if not why:
            deny_reasons.append("why: priority exceeds limit")

        # If any face fails, skip HMAC/gate call — already denied
        if deny_reasons:
            elapsed = time.perf_counter_ns() - start
            return PolicyResult(
                allowed=False,
                who_pass=who, what_pass=what, how_pass=how,
                where_pass=where, when_pass=when, why_pass=why,
                request_hash=request_hash,
                deny_reason="; ".join(deny_reasons),
                eval_ns=elapsed,
            )

        # All faces pass — build CubieRequest and call the gate
        capability_hmac = self._gate.compute_hmac(request_hash)

        req = CubieRequest()
        req.who_pass = who
        req.what_pass = what
        req.how_pass = how
        req.where_pass = where
        req.when_pass = when
        req.why_pass = why
        req.has_valid_lease = when
        req.epoch_fresh = True
        req.request_hash = request_hash
        req.capability_hmac = capability_hmac
        req.key_slot = 0
        req.peer_id = 0
        req.adapter_id = 0x1234_5678_9ABC_DEF0

        allowed = self._gate.admit(req)
        elapsed = time.perf_counter_ns() - start

        return PolicyResult(
            allowed=allowed,
            who_pass=who, what_pass=what, how_pass=how,
            where_pass=where, when_pass=when, why_pass=why,
            request_hash=request_hash,
            deny_reason="" if allowed else "gate: hardware check failed",
            eval_ns=elapsed,
        )

if __name__ == "__main__":
    gate = CubieGate()
    evaluator = PolicyEvaluator(gate)

    # Test 1: Valid workload — should ALLOW
    w1 = WorkloadRequest(
        model_name="yolo11n",
        device="GPU",
        batch_size=2,
        nireq=4,
        api_key="cubie-demo-key-001",
    )
    r1 = evaluator.evaluate(w1)
    print(f"Test 1 (valid):     {'ALLOW' if r1.allowed else 'DENY':<5}  ({r1.eval_ns} ns)  {r1.deny_reason}")

    # Test 2: Invalid device — should DENY (what_pass fails)
    w2 = WorkloadRequest(
        model_name="yolo11n",
        device="INVALID",
        batch_size=2,
        nireq=4,
        api_key="cubie-demo-key-001",
    )
    r2 = evaluator.evaluate(w2)
    print(f"Test 2 (bad device): {'ALLOW' if r2.allowed else 'DENY':<5}  ({r2.eval_ns} ns)  {r2.deny_reason}")

    # Test 3: Batch too large — should DENY (how_pass fails)
    w3 = WorkloadRequest(
        model_name="yolo11n",
        device="CPU",
        batch_size=999,
        nireq=4,
        api_key="cubie-demo-key-001",
    )
    r3 = evaluator.evaluate(w3)
    print(f"Test 3 (big batch):  {'ALLOW' if r3.allowed else 'DENY':<5}  ({r3.eval_ns} ns)  {r3.deny_reason}")

    # Test 4: Bad API key — should DENY (who_pass fails)
    w4 = WorkloadRequest(
        model_name="yolo11n",
        device="CPU",
        batch_size=2,
        nireq=4,
        api_key="unauthorized-key",
    )
    r4 = evaluator.evaluate(w4)
    print(f"Test 4 (bad key):    {'ALLOW' if r4.allowed else 'DENY':<5}  ({r4.eval_ns} ns)  {r4.deny_reason}")


# Pipeline package for Cubie Trust Compiler Intel Edge demo.


File created successfully at: C:\Users\NickV\cubie-verification\pipeline\__init__.py

# Pipeline package for Cubie Trust Compiler Intel Edge demo.


Bash

cd "C:/Users/NickV/cubie-verification" && python -m pipeline.policy 2>&1

Test policy evaluator with 4 scenarios


Test 1 (valid):     ALLOW  (53800 ns)
Test 2 (bad device): DENY   (6400 ns)  what: yolo11n+INVALID not approved; how: config exceeds bounds
Test 3 (big batch):  DENY   (2900 ns)  how: config exceeds bounds
Test 4 (bad key):    DENY   (2000 ns)  who: invalid API key


Policy evaluator working. Now the demo app, load generator, and resource monitor:


Updated task #20 status