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}