code wiki / (root) / nx_blob_store_mcu.nx

nx_blob_store_mcu.nx source

↩ module page · 264 lines · 9187 B

1// nx_blob_store_mcu.nx -- MCU-tier variant of blob_store class. 2// 3// EM-6 milestone of NISHI_ECOSYSTEM_EVOLUTION_ROADMAP.md. Same API 4// shape as L0 nx_blob_store but with footprint tuned for MCU-class 5// devices (capacity 4, max blob 256 bytes). Declared as variant 6// of class `blob_store`, so the SA-arc selector can pick THIS for 7// MCU-tier targets and the regular nx_blob_store for laptop-class+. 8// 9// This is the FIRST class to carry multiple variants (`blob_store` 10// V1 + V1_MCU), exercising the cross-tier selection path the SA 11// arc architecture promised but had no real test for until now. 12// 13// V1 footprint envelope: 14// - Capacity: 4 entries (vs 256 in regular L0) 15// - Max blob size: 256 bytes (vs 16 MiB in regular L0) 16// - Index footprint: ~80 B struct + 4 * (32+8) = 240 B 17// - Total RAM (excluding blob bodies): ~320 B 18// - Blob bodies still mmap'd separately (caller-allocated) 19// 20// This fits comfortably on Arduino Uno (2 KB SRAM), STM32F0 family, 21// nRF52 series, ESP8266, and most Cortex-M0+ devices. AVR backend 22// (EM-10) will produce binary that consumes < 1 KB. 23// 24// API mirrors nx_blob_store with `_mcu` suffix to coexist without 25// symbol collision. Future variant-resolver work can use the 26// substrate's manifest system to dispatch by tier without caller 27// awareness. 28// 29// genealogy_id: cardinal_2026-05-20_ecosystem_evolution + 30// nx_blob_store_v1 lineage 31// lineage_id: substrate_blob_store_mcu_v1 32// 33// nx_capability_manifest: 34// variant_class: blob_store 35// variant_id: blob_store_v1_mcu_inline 36// requires_isa: [rv32i, rv32imac, rv64imac, x86_64, cortex_m, armv7a, aarch64, avr] 37// requires_syscalls: [mmap] 38// requires_ram_min_b: 512 39// tier_floor: NX_TIER_INF_MCU 40// tier_ceiling: NX_TIER_INF_MOBILE 41// cost_model: 42// flops_per_n: 50.0 // SHA-256 round per 64-byte block (dominant cost on MCU) 43// bytes_per_n: 1.0 // input bytes pass-through 44// syscalls_per_n: 0.0 45// adversary_class: THREAT_AI_ADVERSARY 46// 47// nx_safety_envelope: 48// intended_use: "MCU-tier variant of blob_store class; smaller 49// footprint, same content-addressed guarantees" 50// sil_target: SIL2 51// evidence: [canary_bracketed, sha256_keyed, dedup_proven, 52// hash_verify_on_read, append_only, capacity_gated] 53// verdict: NOT_YET_EVALUATED 54 55import "nx_syscalls.nx" 56import "nx_sha256.nx" 57import "nx_blob_store.nx" // reuse NxBlobHash struct + helpers 58 59// ===== Constants ================================================= 60const NX_BLOB_STORE_MCU_CAPACITY: i64 = 4 61const NX_BLOB_STORE_MCU_MAX_BLOB_BYTES: i64 = 256 62 63// Verdicts (parallel to NX_BLOB_*). 64const NX_BLOB_MCU_OK: i64 = 0 65const NX_BLOB_MCU_BAD_INPUT: i64 = 1 66const NX_BLOB_MCU_FULL: i64 = 2 67const NX_BLOB_MCU_NOT_FOUND: i64 = 3 68const NX_BLOB_MCU_TOO_BIG: i64 = 4 69const NX_BLOB_MCU_TAMPER: i64 = 5 70const NX_BLOB_MCU_N_VERDICTS: i64 = 6 71 72func nx_blob_mcu_verdict_is_valid(v: i64) -> i64 { 73 if v < 0 { return 0 } 74 if v >= NX_BLOB_MCU_N_VERDICTS { return 0 } 75 return 1 76} 77 78// Canaries distinct from the regular L0 store + all other primitives. 79const NX_BLOB_STORE_MCU_CANARY_PRE: i64 = 0x4E58424C4D435550 // "NXBLMCUP" 80const NX_BLOB_STORE_MCU_CANARY_POST: i64 = 0x4E58424C4D43554E // "NXBLMCUN" 81 82// ===== Entry + Store ============================================ 83// Reuses NxBlobHash from nx_blob_store.nx (4 i64 = 32 bytes, 84// little-endian-packed SHA-256 digest). NxBlobEntryMcu mirrors 85// NxBlobEntry but is independent for clarity + clean variant 86// separation. 87 88struct NxBlobEntryMcu { 89 hash_w0: i64, 90 hash_w1: i64, 91 hash_w2: i64, 92 hash_w3: i64, 93 blob_ptr: *u8, 94 blob_len: i64, 95} 96 97struct NxBlobStoreMcu { 98 canary_pre: i64, 99 n_entries: i64, 100 max_entries: i64, 101 entries: *i64, // *i64 array of NxBlobEntryMcu pointers 102 canary_post: i64, 103} 104 105// ===== Helpers (private; mirror regular L0 internals) =========== 106 107func _mcu_load_i64_le(buf: *u8, off: i64) -> i64 { 108 let b0: i64 = (buf[off + 0] as i64) & 255 109 let b1: i64 = (buf[off + 1] as i64) & 255 110 let b2: i64 = (buf[off + 2] as i64) & 255 111 let b3: i64 = (buf[off + 3] as i64) & 255 112 let b4: i64 = (buf[off + 4] as i64) & 255 113 let b5: i64 = (buf[off + 5] as i64) & 255 114 let b6: i64 = (buf[off + 6] as i64) & 255 115 let b7: i64 = (buf[off + 7] as i64) & 255 116 return b0 117 | (b1 << 8) 118 | (b2 << 16) 119 | (b3 << 24) 120 | (b4 << 32) 121 | (b5 << 40) 122 | (b6 << 48) 123 | (b7 << 56) 124} 125 126func _mcu_hash_bytes_to_struct(bytes: *u8, len: i64, out: *NxBlobHash) -> i64 { 127 let digest: *u8 = sys_mmap(64) 128 sha256_digest(bytes, len, digest) 129 out.w0 = _mcu_load_i64_le(digest, 0) 130 out.w1 = _mcu_load_i64_le(digest, 8) 131 out.w2 = _mcu_load_i64_le(digest, 16) 132 out.w3 = _mcu_load_i64_le(digest, 24) 133 return 0 134} 135 136func _mcu_entry_hash_eq(e: *NxBlobEntryMcu, h: *NxBlobHash) -> i64 { 137 if e.hash_w0 != h.w0 { return 0 } 138 if e.hash_w1 != h.w1 { return 0 } 139 if e.hash_w2 != h.w2 { return 0 } 140 if e.hash_w3 != h.w3 { return 0 } 141 return 1 142} 143 144func _mcu_find_entry_idx(s: *NxBlobStoreMcu, h: *NxBlobHash) -> i64 { 145 var i: i64 = 0 146 while i < s.n_entries { 147 let e_addr: i64 = s.entries[i] 148 if e_addr != 0 { 149 let e: *NxBlobEntryMcu = e_addr as *NxBlobEntryMcu 150 if _mcu_entry_hash_eq(e, h) == 1 { return i } 151 } 152 i = i + 1 153 } 154 return -1 155} 156 157func _mcu_alloc_entry(h: *NxBlobHash, src: *u8, len: i64) -> *NxBlobEntryMcu { 158 let e: *NxBlobEntryMcu = (sys_mmap(64)) as *NxBlobEntryMcu 159 e.hash_w0 = h.w0 160 e.hash_w1 = h.w1 161 e.hash_w2 = h.w2 162 e.hash_w3 = h.w3 163 let buf: *u8 = sys_mmap(len + 8) 164 var k: i64 = 0 165 while k < len { 166 buf[k] = src[k] 167 k = k + 1 168 } 169 e.blob_ptr = buf 170 e.blob_len = len 171 return e 172} 173 174// ===== Construction ============================================= 175func nx_blob_store_mcu_new() -> *NxBlobStoreMcu { 176 let s: *NxBlobStoreMcu = (sys_mmap(64)) as *NxBlobStoreMcu 177 s.canary_pre = NX_BLOB_STORE_MCU_CANARY_PRE 178 s.canary_post = NX_BLOB_STORE_MCU_CANARY_POST 179 s.n_entries = 0 180 s.max_entries = NX_BLOB_STORE_MCU_CAPACITY 181 s.entries = (sys_mmap(NX_BLOB_STORE_MCU_CAPACITY * 8)) as *i64 182 var i: i64 = 0 183 while i < NX_BLOB_STORE_MCU_CAPACITY { 184 s.entries[i] = 0 185 i = i + 1 186 } 187 return s 188} 189 190// ===== Validity gate ============================================ 191func nx_blob_store_mcu_is_valid(s: *NxBlobStoreMcu) -> i64 { 192 if (s as i64) == 0 { return 0 } 193 if s.canary_pre != NX_BLOB_STORE_MCU_CANARY_PRE { return 0 } 194 if s.canary_post != NX_BLOB_STORE_MCU_CANARY_POST { return 0 } 195 if s.n_entries < 0 { return 0 } 196 if s.n_entries > s.max_entries { return 0 } 197 return 1 198} 199 200// ===== put_blob ================================================= 201// Adds the MCU-tier oversized-blob gate (refuses bytes > 256). 202func nx_blob_store_mcu_put( 203 store: *NxBlobStoreMcu, 204 bytes: *u8, len: i64, 205 out_hash: *NxBlobHash 206) -> i64 { 207 if nx_blob_store_mcu_is_valid(store) != 1 { return NX_BLOB_MCU_TAMPER } 208 if (out_hash as i64) == 0 { return NX_BLOB_MCU_BAD_INPUT } 209 if len < 0 { return NX_BLOB_MCU_BAD_INPUT } 210 if len > NX_BLOB_STORE_MCU_MAX_BLOB_BYTES { return NX_BLOB_MCU_TOO_BIG } 211 if len > 0 { 212 if (bytes as i64) == 0 { return NX_BLOB_MCU_BAD_INPUT } 213 } 214 215 _mcu_hash_bytes_to_struct(bytes, len, out_hash) 216 217 let existing: i64 = _mcu_find_entry_idx(store, out_hash) 218 if existing >= 0 { return NX_BLOB_MCU_OK } 219 220 if store.n_entries >= store.max_entries { return NX_BLOB_MCU_FULL } 221 222 let e: *NxBlobEntryMcu = _mcu_alloc_entry(out_hash, bytes, len) 223 store.entries[store.n_entries] = e as i64 224 store.n_entries = store.n_entries + 1 225 return NX_BLOB_MCU_OK 226} 227 228// ===== get_blob ================================================= 229func nx_blob_store_mcu_get( 230 store: *NxBlobStoreMcu, 231 hash: *NxBlobHash, 232 out_buf: *u8, max_len: i64 233) -> i64 { 234 if nx_blob_store_mcu_is_valid(store) != 1 { return -1 } 235 if (hash as i64) == 0 { return -1 } 236 if (out_buf as i64) == 0 { return -1 } 237 if max_len < 0 { return -1 } 238 239 let idx: i64 = _mcu_find_entry_idx(store, hash) 240 if idx < 0 { return -1 } 241 let e: *NxBlobEntryMcu = (store.entries[idx]) as *NxBlobEntryMcu 242 if e.blob_len > max_len { return -1 } 243 244 var k: i64 = 0 245 while k < e.blob_len { 246 out_buf[k] = e.blob_ptr[k] 247 k = k + 1 248 } 249 return e.blob_len 250} 251 252// ===== has_blob ================================================= 253func nx_blob_store_mcu_has(store: *NxBlobStoreMcu, hash: *NxBlobHash) -> i64 { 254 if nx_blob_store_mcu_is_valid(store) != 1 { return 0 } 255 if (hash as i64) == 0 { return 0 } 256 if _mcu_find_entry_idx(store, hash) < 0 { return 0 } 257 return 1 258} 259 260// ===== count ==================================================== 261func nx_blob_store_mcu_count(store: *NxBlobStoreMcu) -> i64 { 262 if nx_blob_store_mcu_is_valid(store) != 1 { return -1 } 263 return store.n_entries 264}