code wiki / (root) / nx_mod_manifest_lib.nx

nx_mod_manifest_lib.nx source

↩ module page · 481 lines · 23447 B

1// nx_mod_manifest_lib.nx -- THE SOVEREIGN MOD PACKAGE MANIFEST (/compare/modding MD18 mod_manifest_emit, 2026-09-06). 2// One manifest carries the package name, version, game targets, dependencies, the licence ROW read from the ONE rights table 3// (nx_licgate_lib: id, redistribution right after the unverified cap, verified flag), one content row per member (relative 4// path, bytes, sha256), the provenance verdict per member from the ONE asset journal (nx_asset_prov_lib, MD9) and an AUDIT 5// HEAD: the nx_sign_envelope_lib hash chain h0 = genesis, h = sha256(h || canonical(record)) over the header record and 6// every content record, canonicalised by nx_canon_cid so field order cannot change the bytes. Any edit to a member's bytes, 7// to a content row, or to the header changes the head, and the verifier REFUSES BY NAME (missing, tampered, head). There is 8// exactly ONE chain function, mm_chain, and both the emitter and the verifier call it, so they cannot disagree by drift. 9// The manifest is the estate's own line format (record|field|field), deterministic (no timestamps), so two emits of the 10// same package are byte-identical. Transport is the content-put door's job; this lib only writes and reads the bytes. 11// license_tier: ORIGINAL No hw writes (Rule 26). 12import "nx_syscalls.nx" 13import "nx_sign_envelope_lib.nx" 14import "nx_asset_prov_lib.nx" 15 16const MM_MAGIC: *u8 = "nxmod" 17const MM_FORMAT_VERSION: i64 = 1 18const MM_SEP: i64 = 124 // '|' 19const MM_NL: i64 = 10 20const MM_TAB: i64 = 9 21const MM_COMMA: i64 = 44 22const MM_AT: i64 = 64 23const MM_SLASH: i64 = 47 24const MM_DOT: i64 = 46 25const MM_FILE_MODE: i64 = 420 26const MM_PATH_CAP: i64 = 1024 27const MM_FIELD_CAP: i64 = 4096 28const MM_SHA_HEX: i64 = 64 29const MM_DIGEST: i64 = 32 30const MM_REC_CAP: i64 = 8192 31const MM_MAX_FIELDS: i64 = 8 32// results of emit / parse / verify 33const MM_OK: i64 = 0 34const MM_ERR_LICENCE_UNKNOWN: i64 = 0 - 1 35const MM_ERR_TABLE_UNREADABLE: i64 = 0 - 2 36const MM_ERR_BAD_PATH: i64 = 0 - 3 37const MM_ERR_UNREADABLE_FILE: i64 = 0 - 4 38const MM_ERR_BAD_FIELD: i64 = 0 - 5 39const MM_ERR_NOT_MANIFEST: i64 = 0 - 6 40const MM_ERR_MALFORMED: i64 = 0 - 7 41const MM_ERR_NO_FILES: i64 = 0 - 8 42const MM_ERR_OUT_UNWRITABLE: i64 = 0 - 9 43const MM_ERR_CAPACITY: i64 = 0 - 10 44// verify verdicts 45const MM_V_ACCEPT: i64 = 0 46const MM_V_MISSING: i64 = 1 47const MM_V_TAMPERED: i64 = 2 48const MM_V_HEAD: i64 = 3 49// member states 50const MM_S_OK: i64 = 0 51const MM_S_TAMPERED: i64 = 1 52const MM_S_MISSING: i64 = 2 53// parsed model slots 54const MM_M_BUF: i64 = 0 55const MM_M_N: i64 = 1 56const MM_M_NAME: i64 = 2 // NUL-terminated copies in scratch 57const MM_M_VERSION: i64 = 3 58const MM_M_GAMES: i64 = 4 // csv joined 59const MM_M_DEPS: i64 = 5 // csv joined name@version 60const MM_M_LIC: i64 = 6 61const MM_M_LIC_REDIST: i64 = 7 62const MM_M_LIC_VERIFIED: i64 = 8 63const MM_M_NCONTENT: i64 = 9 64const MM_M_PATHS: i64 = 10 // *i64 -> *u8 strings 65const MM_M_BYTES: i64 = 11 // *i64 66const MM_M_SHAS: i64 = 12 // *i64 -> *u8 65-byte strings 67const MM_M_HEAD: i64 = 13 // *u8 65 68const MM_M_AUDIT_ROWS: i64 = 14 69const MM_M_NGAMES: i64 = 15 70const MM_M_NDEPS: i64 = 16 71const MM_M_NPROV: i64 = 17 72const MM_M_ERR: i64 = 18 73const MM_M_FORMAT: i64 = 19 74const MM_M_N_SLOTS: i64 = 24 75// verify result slots 76const MM_R_VERDICT: i64 = 0 77const MM_R_OK: i64 = 1 78const MM_R_TAMPERED: i64 = 2 79const MM_R_MISSING: i64 = 3 80const MM_R_HEAD_DECLARED: i64 = 4 // *u8 65 81const MM_R_HEAD_RECOMPUTED: i64 = 5 // *u8 65 -- from the DECLARED rows 82const MM_R_HEAD_OF_FILES: i64 = 6 // *u8 65 -- from the files as they ARE 83const MM_R_STATES: i64 = 7 // *i64 per member 84const MM_R_N: i64 = 8 85 86func mm_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n } 87func mm_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o + i] = s[i]; i = i + 1 } return o + i } 88func mm_catn(d: *u8, o: i64, s: *u8, n: i64) -> i64 { var i: i64 = 0; while i < n { d[o + i] = s[i]; i = i + 1 } return o + n } 89func mm_putd(d: *u8, o: i64, v: i64) -> i64 { return lg_putd(d, o, v) } 90func mm_streq(a: *u8, b: *u8) -> i64 { var i: i64 = 0; while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 } if b[i] != (0 as u8) { return 0 } return 1 } 91func mm_dup(s: *u8, n: i64) -> *u8 { let d: *u8 = sys_mmap(n + 1); var i: i64 = 0; while i < n { d[i] = s[i]; i = i + 1 } d[n] = 0 as u8; return d } 92func mm_atoi(s: *u8, n: i64) -> i64 { var v: i64 = 0; var i: i64 = 0; while i < n { let c: i64 = s[i] as i64; if c < 48 { return 0 - 1 } if c > 57 { return 0 - 1 } v = v * 10 + (c - 48); i = i + 1 } if n == 0 { return 0 - 1 } return v } 93// a field may not carry the separator, a tab or a newline -- the format's own bytes are never data 94func mm_field_ok(s: *u8) -> i64 { 95 var i: i64 = 0 96 while s[i] != (0 as u8) { 97 let c: i64 = s[i] as i64 98 if c == MM_SEP { return 0 } 99 if c == MM_NL { return 0 } 100 if c == MM_TAB { return 0 } 101 i = i + 1 102 } 103 return 1 104} 105// a member path is RELATIVE, never absolute, never climbing, never empty 106func mm_path_ok(p: *u8) -> i64 { 107 if p[0] == (0 as u8) { return 0 } 108 if (p[0] as i64) == MM_SLASH { return 0 } 109 if mm_field_ok(p) == 0 { return 0 } 110 var i: i64 = 0 111 while p[i] != (0 as u8) { 112 if (p[i] as i64) == MM_DOT { if (p[i + 1] as i64) == MM_DOT { 113 var before: i64 = 1 114 if i > 0 { if (p[i - 1] as i64) != MM_SLASH { before = 0 } } 115 var after: i64 = 0 116 if p[i + 2] == (0 as u8) { after = 1 } 117 if (p[i + 2] as i64) == MM_SLASH { after = 1 } 118 if before == 1 { if after == 1 { return 0 } } 119 } } 120 i = i + 1 121 } 122 return 1 123} 124func mm_join_path(root: *u8, rel: *u8, out: *u8) -> i64 { 125 var o: i64 = 0 126 if root[0] != (0 as u8) { 127 o = mm_cat(out, 0, root) 128 if (out[o - 1] as i64) != MM_SLASH { out[o] = MM_SLASH as u8; o = o + 1 } 129 } 130 o = mm_cat(out, o, rel) 131 out[o] = 0 as u8 132 return o 133} 134// ---- THE ONE CHAIN: header record then one record per content row; out32 is the head ---- 135func mm_chain(name: *u8, version: *u8, games: *u8, deps: *u8, lic: *u8, paths: *i64, bytes: *i64, shas: *i64, n: i64, out32: *u8) -> i64 { 136 let h: *u8 = sys_mmap(MM_DIGEST + 8) 137 let h2: *u8 = sys_mmap(MM_DIGEST + 8) 138 se_genesis(h) 139 let rec: *u8 = sys_mmap(MM_REC_CAP) 140 let keys: *i64 = sys_mmap(8 * MM_MAX_FIELDS) as *i64 141 let vals: *i64 = sys_mmap(8 * MM_MAX_FIELDS) as *i64 142 keys[0] = ("deps" as *u8) as i64; vals[0] = deps as i64 143 keys[1] = ("games" as *u8) as i64; vals[1] = games as i64 144 keys[2] = ("licence" as *u8) as i64; vals[2] = lic as i64 145 keys[3] = ("name" as *u8) as i64; vals[3] = name as i64 146 keys[4] = ("version" as *u8) as i64; vals[4] = version as i64 147 var rl: i64 = canon_encode(keys, vals, 5, rec) 148 se_chain(h, rec, rl, h2) 149 var k: i64 = 0 150 while k < MM_DIGEST { h[k] = h2[k]; k = k + 1 } 151 var i: i64 = 0 152 while i < n { 153 let bs: *u8 = sys_mmap(32) 154 var bo: i64 = mm_putd(bs, 0, bytes[i]) 155 bs[bo] = 0 as u8 156 keys[0] = ("bytes" as *u8) as i64; vals[0] = bs as i64 157 keys[1] = ("path" as *u8) as i64; vals[1] = paths[i] 158 keys[2] = ("sha256" as *u8) as i64; vals[2] = shas[i] 159 rl = canon_encode(keys, vals, 3, rec) 160 se_chain(h, rec, rl, h2) 161 k = 0 162 while k < MM_DIGEST { h[k] = h2[k]; k = k + 1 } 163 i = i + 1 164 } 165 k = 0 166 while k < MM_DIGEST { out32[k] = h[k]; k = k + 1 } 167 return n + 1 168} 169// split a csv into NUL-terminated strings; returns the count; a re-join of the parts is the canonical csv 170func mm_split_csv(csv: *u8, parts: *i64, cap: i64) -> i64 { 171 var n: i64 = 0 172 var s: i64 = 0 173 var i: i64 = 0 174 var go: i64 = 1 175 while go == 1 { 176 let c: i64 = csv[i] as i64 177 var at_end: i64 = 0 178 if c == 0 { at_end = 1 } 179 if c == MM_COMMA { at_end = 1 } 180 if at_end == 1 { 181 if i > s { if n < cap { parts[n] = (mm_dup(csv + s, i - s)) as i64; n = n + 1 } } 182 s = i + 1 183 } 184 if c == 0 { go = 0 } 185 i = i + 1 186 } 187 return n 188} 189func mm_join(parts: *i64, n: i64, out: *u8) -> i64 { 190 var o: i64 = 0 191 var i: i64 = 0 192 while i < n { if i > 0 { out[o] = MM_COMMA as u8; o = o + 1 } o = mm_cat(out, o, parts[i] as *u8); i = i + 1 } 193 out[o] = 0 as u8 194 return o 195} 196// ---- EMIT: hash every member under root, read the licence row, ask the provenance verdict, chain, write the manifest ---- 197// paths: *i64 -> relative path strings; returns the manifest byte count or a named negative error; info: [0]=total bytes, 198// [1]=redist, [2]=verified, [3]=games, [4]=deps, [5]=first bad index 199func mm_emit(outpath: *u8, name: *u8, version: *u8, games_csv: *u8, deps_csv: *u8, lic: *u8, jrnl: *u8, root: *u8, paths: *i64, n: i64, head65: *u8, info: *i64) -> i64 { 200 var q: i64 = 0 201 while q < 8 { info[q] = 0; q = q + 1 } 202 info[5] = 0 - 1 203 if n < 1 { return MM_ERR_NO_FILES } 204 if mm_field_ok(name) == 0 { return MM_ERR_BAD_FIELD } 205 if mm_field_ok(version) == 0 { return MM_ERR_BAD_FIELD } 206 if mm_field_ok(lic) == 0 { return MM_ERR_BAD_FIELD } 207 if name[0] == (0 as u8) { return MM_ERR_BAD_FIELD } 208 if version[0] == (0 as u8) { return MM_ERR_BAD_FIELD } 209 let ctx: *i64 = lg_ctx() 210 if ctx[3] < 0 { return MM_ERR_TABLE_UNREADABLE } 211 let li: i64 = lg_find_license(ctx, lic) 212 if li < 0 { return MM_ERR_LICENCE_UNKNOWN } 213 var redist: i64 = lg_lic_redist(ctx, li) 214 let verified: i64 = lg_lic_verified(ctx, li) 215 if verified == LG_UNVERIFIED { if redist > LG_COND { redist = LG_COND } } 216 info[1] = redist 217 info[2] = verified 218 let gparts: *i64 = sys_mmap(8 * 64) as *i64 219 let ng: i64 = mm_split_csv(games_csv, gparts, 64) 220 let dparts: *i64 = sys_mmap(8 * 64) as *i64 221 let nd: i64 = mm_split_csv(deps_csv, dparts, 64) 222 info[3] = ng 223 info[4] = nd 224 let gjoin: *u8 = sys_mmap(MM_FIELD_CAP) 225 mm_join(gparts, ng, gjoin) 226 let djoin: *u8 = sys_mmap(MM_FIELD_CAP) 227 mm_join(dparts, nd, djoin) 228 // hash every member 229 let bytes: *i64 = sys_mmap(8 * (n + 1)) as *i64 230 let shas: *i64 = sys_mmap(8 * (n + 1)) as *i64 231 let full: *u8 = sys_mmap(MM_PATH_CAP * 2) 232 var total: i64 = 0 233 var i: i64 = 0 234 while i < n { 235 let p: *u8 = paths[i] as *u8 236 if mm_path_ok(p) == 0 { info[5] = i; return MM_ERR_BAD_PATH } 237 mm_join_path(root, p, full) 238 let sh: *u8 = sys_mmap(MM_SHA_HEX + 1) 239 let b: i64 = pv_hash_file(full, sh) 240 if b < 0 { info[5] = i; return MM_ERR_UNREADABLE_FILE } 241 bytes[i] = b 242 shas[i] = sh as i64 243 total = total + b 244 i = i + 1 245 } 246 info[0] = total 247 let head: *u8 = sys_mmap(MM_DIGEST + 8) 248 mm_chain(name, version, gjoin, djoin, lic, paths, bytes, shas, n, head) 249 se_hex(head, head65) 250 // write the manifest 251 let cap: i64 = 512 + n * (MM_PATH_CAP + 256) + mm_len(gjoin) + mm_len(djoin) + ng * 16 + nd * 16 252 let m: *u8 = sys_mmap(cap) 253 var o: i64 = mm_cat(m, 0, MM_MAGIC); m[o] = MM_SEP as u8; o = o + 1; o = mm_putd(m, o, MM_FORMAT_VERSION); m[o] = MM_NL as u8; o = o + 1 254 o = mm_cat(m, o, "name|" as *u8); o = mm_cat(m, o, name); m[o] = MM_NL as u8; o = o + 1 255 o = mm_cat(m, o, "version|" as *u8); o = mm_cat(m, o, version); m[o] = MM_NL as u8; o = o + 1 256 i = 0 257 while i < ng { o = mm_cat(m, o, "game|" as *u8); o = mm_cat(m, o, gparts[i] as *u8); m[o] = MM_NL as u8; o = o + 1; i = i + 1 } 258 i = 0 259 while i < nd { 260 // dep rows are name|version: the csv part is name@version 261 let dp: *u8 = dparts[i] as *u8 262 var at: i64 = 0 - 1 263 var k: i64 = 0 264 while dp[k] != (0 as u8) { if (dp[k] as i64) == MM_AT { at = k } k = k + 1 } 265 o = mm_cat(m, o, "dep|" as *u8) 266 if at < 0 { o = mm_cat(m, o, dp); m[o] = MM_SEP as u8; o = o + 1; o = mm_cat(m, o, "any" as *u8) } 267 else { o = mm_catn(m, o, dp, at); m[o] = MM_SEP as u8; o = o + 1; o = mm_cat(m, o, dp + at + 1) } 268 m[o] = MM_NL as u8; o = o + 1 269 i = i + 1 270 } 271 o = mm_cat(m, o, "licence|" as *u8); o = mm_cat(m, o, lic); m[o] = MM_SEP as u8; o = o + 1; o = mm_putd(m, o, redist); m[o] = MM_SEP as u8; o = o + 1; o = mm_putd(m, o, verified); m[o] = MM_NL as u8; o = o + 1 272 i = 0 273 while i < n { 274 o = mm_cat(m, o, "content|" as *u8); o = mm_cat(m, o, paths[i] as *u8); m[o] = MM_SEP as u8; o = o + 1 275 o = mm_putd(m, o, bytes[i]); m[o] = MM_SEP as u8; o = o + 1; o = mm_cat(m, o, shas[i] as *u8); m[o] = MM_NL as u8; o = o + 1 276 i = i + 1 277 } 278 let res: *i64 = sys_mmap(8 * PV_RES_N) as *i64 279 i = 0 280 while i < n { 281 let rc: i64 = pv_verdict(jrnl, shas[i] as *u8, res) 282 o = mm_cat(m, o, "prov|" as *u8); o = mm_cat(m, o, shas[i] as *u8); m[o] = MM_SEP as u8; o = o + 1 283 o = mm_cat(m, o, pv_verdict_name(rc)); m[o] = MM_SEP as u8; o = o + 1; o = mm_cat(m, o, pv_reason_name(res[PV_RES_REASON])); m[o] = MM_NL as u8; o = o + 1 284 i = i + 1 285 } 286 o = mm_cat(m, o, "audit|" as *u8); o = mm_putd(m, o, n); m[o] = MM_SEP as u8; o = o + 1; o = mm_cat(m, o, head65); m[o] = MM_NL as u8; o = o + 1 287 if o >= cap { return MM_ERR_CAPACITY } 288 let fd: i64 = sys_openat_wr(outpath, MM_FILE_MODE) 289 if fd < 0 { return MM_ERR_OUT_UNWRITABLE } 290 var done: i64 = 0 291 while done < o { let w: i64 = sys_write(fd, m + done, o - done); if w <= 0 { break } done = done + w } 292 sys_close(fd) 293 if done != o { return MM_ERR_OUT_UNWRITABLE } 294 return o 295} 296// THE CONTRACT SYMBOL the board watches (modding MD18): the emit entry under its rung name, a thin name over mm_emit so 297// the watch flips only when the real entry exists and the command reaches it 298func mod_manifest_emit(outpath: *u8, name: *u8, version: *u8, games_csv: *u8, deps_csv: *u8, lic: *u8, jrnl: *u8, root: *u8, paths: *i64, n: i64, head65: *u8, info: *i64) -> i64 { 299 return mm_emit(outpath, name, version, games_csv, deps_csv, lic, jrnl, root, paths, n, head65, info) 300} 301// ---- PARSE: the manifest bytes into the model; returns MM_OK or a named error ---- 302func mm_fields(b: *u8, s: i64, e: i64, offs: *i64, lens: *i64) -> i64 { 303 var nf: i64 = 0 304 var p: i64 = s 305 var i: i64 = s 306 while i <= e { 307 var cut: i64 = 0 308 if i == e { cut = 1 } else { if (b[i] as i64) == MM_SEP { cut = 1 } } 309 if cut == 1 { if nf < MM_MAX_FIELDS { offs[nf] = p; lens[nf] = i - p; nf = nf + 1 } p = i + 1 } 310 i = i + 1 311 } 312 return nf 313} 314func mm_is(b: *u8, off: i64, len: i64, s: *u8) -> i64 { if mm_len(s) != len { return 0 } var i: i64 = 0; while i < len { if b[off + i] != s[i] { return 0 } i = i + 1 } return 1 } 315func mm_parse(b: *u8, n: i64, m: *i64) -> i64 { 316 var q: i64 = 0 317 while q < MM_M_N_SLOTS { m[q] = 0; q = q + 1 } 318 m[MM_M_BUF] = b as i64 319 m[MM_M_N] = n 320 m[MM_M_LIC_REDIST] = 0 - 1 321 m[MM_M_LIC_VERIFIED] = 0 - 1 322 // count content rows first so the arrays are sized from the input 323 var lines: i64 = 0 324 var i: i64 = 0 325 while i < n { if (b[i] as i64) == MM_NL { lines = lines + 1 } i = i + 1 } 326 let paths: *i64 = sys_mmap(8 * (lines + 1)) as *i64 327 let bytes: *i64 = sys_mmap(8 * (lines + 1)) as *i64 328 let shas: *i64 = sys_mmap(8 * (lines + 1)) as *i64 329 let gparts: *i64 = sys_mmap(8 * (lines + 1)) as *i64 330 let dparts: *i64 = sys_mmap(8 * (lines + 1)) as *i64 331 m[MM_M_PATHS] = paths as i64; m[MM_M_BYTES] = bytes as i64; m[MM_M_SHAS] = shas as i64 332 let offs: *i64 = sys_mmap(8 * MM_MAX_FIELDS) as *i64 333 let lens: *i64 = sys_mmap(8 * MM_MAX_FIELDS) as *i64 334 var nc: i64 = 0 335 var ng: i64 = 0 336 var nd: i64 = 0 337 var np: i64 = 0 338 var first: i64 = 1 339 var p: i64 = 0 340 while p < n { 341 var e: i64 = p 342 while e < n { if (b[e] as i64) == MM_NL { break } e = e + 1 } 343 if e > p { 344 let nf: i64 = mm_fields(b, p, e, offs, lens) 345 if first == 1 { 346 if mm_is(b, offs[0], lens[0], MM_MAGIC) == 0 { m[MM_M_ERR] = MM_ERR_NOT_MANIFEST; return MM_ERR_NOT_MANIFEST } 347 if nf < 2 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 348 m[MM_M_FORMAT] = mm_atoi(b + offs[1], lens[1]) 349 first = 0 350 } else { 351 if mm_is(b, offs[0], lens[0], "name" as *u8) == 1 { if nf < 2 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } m[MM_M_NAME] = (mm_dup(b + offs[1], lens[1])) as i64 } 352 else { if mm_is(b, offs[0], lens[0], "version" as *u8) == 1 { if nf < 2 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } m[MM_M_VERSION] = (mm_dup(b + offs[1], lens[1])) as i64 } 353 else { if mm_is(b, offs[0], lens[0], "game" as *u8) == 1 { if nf < 2 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } gparts[ng] = (mm_dup(b + offs[1], lens[1])) as i64; ng = ng + 1 } 354 else { if mm_is(b, offs[0], lens[0], "dep" as *u8) == 1 { 355 if nf < 3 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 356 let d: *u8 = sys_mmap(lens[1] + lens[2] + 2) 357 var o: i64 = mm_catn(d, 0, b + offs[1], lens[1]); d[o] = MM_AT as u8; o = o + 1; o = mm_catn(d, o, b + offs[2], lens[2]); d[o] = 0 as u8 358 dparts[nd] = d as i64; nd = nd + 1 359 } 360 else { if mm_is(b, offs[0], lens[0], "licence" as *u8) == 1 { 361 if nf < 4 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 362 m[MM_M_LIC] = (mm_dup(b + offs[1], lens[1])) as i64 363 m[MM_M_LIC_REDIST] = mm_atoi(b + offs[2], lens[2]) 364 m[MM_M_LIC_VERIFIED] = mm_atoi(b + offs[3], lens[3]) 365 } 366 else { if mm_is(b, offs[0], lens[0], "content" as *u8) == 1 { 367 if nf < 4 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 368 if lens[3] != MM_SHA_HEX { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 369 paths[nc] = (mm_dup(b + offs[1], lens[1])) as i64 370 bytes[nc] = mm_atoi(b + offs[2], lens[2]) 371 if bytes[nc] < 0 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 372 shas[nc] = (mm_dup(b + offs[3], lens[3])) as i64 373 nc = nc + 1 374 } 375 else { if mm_is(b, offs[0], lens[0], "prov" as *u8) == 1 { np = np + 1 } 376 else { if mm_is(b, offs[0], lens[0], "audit" as *u8) == 1 { 377 if nf < 3 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 378 if lens[2] != MM_SHA_HEX { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 379 m[MM_M_AUDIT_ROWS] = mm_atoi(b + offs[1], lens[1]) 380 m[MM_M_HEAD] = (mm_dup(b + offs[2], lens[2])) as i64 381 } } } } } } } } 382 } 383 } 384 p = e + 1 385 } 386 if first == 1 { m[MM_M_ERR] = MM_ERR_NOT_MANIFEST; return MM_ERR_NOT_MANIFEST } 387 if m[MM_M_NAME] == 0 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 388 if m[MM_M_VERSION] == 0 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 389 if m[MM_M_LIC] == 0 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 390 if m[MM_M_HEAD] == 0 { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 391 if nc < 1 { m[MM_M_ERR] = MM_ERR_NO_FILES; return MM_ERR_NO_FILES } 392 if m[MM_M_AUDIT_ROWS] != nc { m[MM_M_ERR] = MM_ERR_MALFORMED; return MM_ERR_MALFORMED } 393 m[MM_M_NCONTENT] = nc 394 m[MM_M_NGAMES] = ng 395 m[MM_M_NDEPS] = nd 396 m[MM_M_NPROV] = np 397 let gj: *u8 = sys_mmap(MM_FIELD_CAP) 398 mm_join(gparts, ng, gj) 399 let dj: *u8 = sys_mmap(MM_FIELD_CAP) 400 mm_join(dparts, nd, dj) 401 m[MM_M_GAMES] = gj as i64 402 m[MM_M_DEPS] = dj as i64 403 m[MM_M_ERR] = MM_OK 404 return MM_OK 405} 406// ---- VERIFY: every member under root re-hashed, the chain recomputed twice (declared rows, actual files) ---- 407func mm_verify(m: *i64, root: *u8, r: *i64) -> i64 { 408 let n: i64 = m[MM_M_NCONTENT] 409 let paths: *i64 = m[MM_M_PATHS] as *i64 410 let bytes: *i64 = m[MM_M_BYTES] as *i64 411 let shas: *i64 = m[MM_M_SHAS] as *i64 412 let states: *i64 = sys_mmap(8 * (n + 1)) as *i64 413 let abytes: *i64 = sys_mmap(8 * (n + 1)) as *i64 414 let ashas: *i64 = sys_mmap(8 * (n + 1)) as *i64 415 var q: i64 = 0 416 while q < MM_R_N { r[q] = 0; q = q + 1 } 417 r[MM_R_STATES] = states as i64 418 r[MM_R_HEAD_DECLARED] = m[MM_M_HEAD] 419 let full: *u8 = sys_mmap(MM_PATH_CAP * 2) 420 var ok: i64 = 0 421 var tam: i64 = 0 422 var mis: i64 = 0 423 var i: i64 = 0 424 while i < n { 425 mm_join_path(root, paths[i] as *u8, full) 426 let sh: *u8 = sys_mmap(MM_SHA_HEX + 1) 427 let b: i64 = pv_hash_file(full, sh) 428 if b < 0 { states[i] = MM_S_MISSING; mis = mis + 1; abytes[i] = 0; ashas[i] = ("0000000000000000000000000000000000000000000000000000000000000000" as *u8) as i64 } 429 else { 430 abytes[i] = b 431 ashas[i] = sh as i64 432 var same: i64 = 1 433 if b != bytes[i] { same = 0 } 434 if mm_streq(sh, shas[i] as *u8) == 0 { same = 0 } 435 if same == 1 { states[i] = MM_S_OK; ok = ok + 1 } else { states[i] = MM_S_TAMPERED; tam = tam + 1 } 436 } 437 i = i + 1 438 } 439 r[MM_R_OK] = ok; r[MM_R_TAMPERED] = tam; r[MM_R_MISSING] = mis 440 let h1: *u8 = sys_mmap(MM_DIGEST + 8) 441 mm_chain(m[MM_M_NAME] as *u8, m[MM_M_VERSION] as *u8, m[MM_M_GAMES] as *u8, m[MM_M_DEPS] as *u8, m[MM_M_LIC] as *u8, paths, bytes, shas, n, h1) 442 let hx1: *u8 = sys_mmap(MM_SHA_HEX + 1) 443 se_hex(h1, hx1) 444 r[MM_R_HEAD_RECOMPUTED] = hx1 as i64 445 let h2: *u8 = sys_mmap(MM_DIGEST + 8) 446 mm_chain(m[MM_M_NAME] as *u8, m[MM_M_VERSION] as *u8, m[MM_M_GAMES] as *u8, m[MM_M_DEPS] as *u8, m[MM_M_LIC] as *u8, paths, abytes, ashas, n, h2) 447 let hx2: *u8 = sys_mmap(MM_SHA_HEX + 1) 448 se_hex(h2, hx2) 449 r[MM_R_HEAD_OF_FILES] = hx2 as i64 450 var v: i64 = MM_V_ACCEPT 451 if mm_streq(hx1, m[MM_M_HEAD] as *u8) == 0 { v = MM_V_HEAD } 452 if tam > 0 { v = MM_V_TAMPERED } 453 if mis > 0 { v = MM_V_MISSING } 454 r[MM_R_VERDICT] = v 455 return v 456} 457func mm_err_name(e: i64) -> *u8 { 458 if e == MM_ERR_LICENCE_UNKNOWN { return "licence-not-in-rights-table" as *u8 } 459 if e == MM_ERR_TABLE_UNREADABLE { return "rights-table-unreadable" as *u8 } 460 if e == MM_ERR_BAD_PATH { return "member-path-not-relative-or-carries-a-separator" as *u8 } 461 if e == MM_ERR_UNREADABLE_FILE { return "member-unreadable" as *u8 } 462 if e == MM_ERR_BAD_FIELD { return "field-empty-or-carries-a-separator" as *u8 } 463 if e == MM_ERR_NOT_MANIFEST { return "not-an-nxmod-manifest" as *u8 } 464 if e == MM_ERR_MALFORMED { return "manifest-malformed" as *u8 } 465 if e == MM_ERR_NO_FILES { return "no-content-rows" as *u8 } 466 if e == MM_ERR_OUT_UNWRITABLE { return "output-unwritable" as *u8 } 467 if e == MM_ERR_CAPACITY { return "manifest-capacity" as *u8 } 468 return "unnamed" as *u8 469} 470func mm_verdict_name(v: i64) -> *u8 { 471 if v == MM_V_ACCEPT { return "ACCEPT" as *u8 } 472 if v == MM_V_MISSING { return "REFUSE-MISSING" as *u8 } 473 if v == MM_V_TAMPERED { return "REFUSE-TAMPERED" as *u8 } 474 if v == MM_V_HEAD { return "REFUSE-HEAD" as *u8 } 475 return "UNNAMED" as *u8 476} 477func mm_state_name(s: i64) -> *u8 { 478 if s == MM_S_OK { return "OK" as *u8 } 479 if s == MM_S_TAMPERED { return "TAMPERED" as *u8 } 480 return "MISSING" as *u8 481}