code wiki / (root) / nx_compare_receipt_inspect_lib_t138.nx

nx_compare_receipt_inspect_lib_t138.nx source

↩ module page · 372 lines · 16963 B

1// Private read-only inspection extension of the existing Compare receipt owner. 2// Never appends, truncates, renames, replays or marks an intent completed. 3// The shared sidecar lock may be created; evidence files are only read. 4import "nx_compare_receipt_lib_t138.nx" 5import "nx_fio.nx" 6const RGI_E_INPUT: i64 = 0-201 7const RGI_E_INTENT: i64 = 0-202 8const RGI_E_LOCK: i64 = 0-203 9const RGI_E_OBSERVE: i64 = 0-204 10const RGI_E_RELEASE: i64 = 0-205 11const RGI_PREPARED: i64 = 1 12const RGI_PUBLISHED: i64 = 2 13const RGI_COMPLETED: i64 = 3 14const RGI_WRITE_FAILED: i64 = 4 15const RGI_DURABILITY_INCOMPLETE: i64 = 5 16struct RgiIntent { 17 body: *u8 18 bodylen: i64 19 row: *u8 20 rowlen: i64 21 target: *u8 22 targetcap: i64 23 digest: *u8 24 state: i64 25} 26struct RgiObservation { 27 state: i64 28 target_rc: i64 29 target_match: i64 30 target_bytes: i64 31 journal_rc: i64 32 journal_bytes: i64 33 exact_rows: i64 34 torn_tail: i64 35 complete: i64 36 target_hex: *u8 37 journal_hex: *u8 38} 39// x86-64 stat mode layout shared by nx_artifactdrift/nx_filecopy; only regular evidence files. 40const RGI_STAT_MODE_OFFSET:i64=24 41const RGI_STAT_TYPE_MASK:i64=0xf000 42const RGI_STAT_REGULAR:i64=0x8000 43func rgi_regular(path:*u8)->i64 { 44 let st:*u8=rgr_alloc(STATFS_BUF_BYTES) 45 if (st as i64)<=0 {return RGI_E_OBSERVE} 46 let rc:i64=sys_fstatat(path,st) 47 var result:i64=rc 48 if rc==0 { 49 let mode:i64=(st[RGI_STAT_MODE_OFFSET] as i64)+256*(st[RGI_STAT_MODE_OFFSET+1] as i64) 50 if (mode & RGI_STAT_TYPE_MASK)!=RGI_STAT_REGULAR {result=RGI_E_OBSERVE} 51 } 52 let freed:i64=rgr_free(st,STATFS_BUF_BYTES) 53 if result==0 {result=freed} 54 return result 55} 56func rgi_equal(a: *u8, an: i64, b: *u8, bn: i64) -> i64 { 57 if an!=bn {return 0} 58 var i:i64=0 59 while i<an {if a[i]!=b[i] {return 0};i=i+1} 60 return 1 61} 62func rgi_literal(a:*u8,n:i64,b:*u8)->i64 {return rgi_equal(a,n,b,rgr_len(b))} 63func rgi_unsigned(a:*u8,n:i64)->i64 { 64 if n<1 {return 0} 65 var i:i64=0;var v:i64=0 66 while i<n { 67 let c:i64=(a[i] as i64)-48 68 if c<0 || c>9 {return 0} 69 if v>(RGR_I64_MAX-c)/10 {return 0} 70 v=v*10+c;i=i+1 71 } 72 return 1 73} 74func rgi_decimal_value(a:*u8,n:i64)->i64 { 75 if rgi_unsigned(a,n)==0 {return 0-1} 76 var i:i64=0;var v:i64=0 77 while i<n {v=v*10+(a[i] as i64)-48;i=i+1} 78 return v 79} 80// One LF-terminated line. No successful unterminated last line. 81func rgi_line(body:*u8,n:i64,cursor:*i64,span:*i64)->i64 { 82 let start:i64=cursor[0] 83 var end:i64=start 84 while end<n && body[end]!=(10 as u8) {end=end+1} 85 if end>=n {return 0} 86 span[0]=start;span[1]=end-start;cursor[0]=end+1 87 return 1 88} 89func rgi_value(line:*u8,n:i64,label:*u8)->i64 { 90 let k:i64=rgr_len(label) 91 if n<k {return 0-1} 92 if rgi_equal(line,k,label,k)==0 {return 0-1} 93 return k 94} 95func rgi_parse(body:*u8,n:i64,journal:*u8,path:*u8,out:*RgiIntent)->i64 { 96 out.body=body;out.bodylen=n;out.row=0 as *u8;out.rowlen=0 97 out.target=0 as *u8;out.targetcap=0;out.digest=0 as *u8;out.state=0 98 let cursor:*i64=rgr_alloc(16) as *i64 99 let span:*i64=rgr_alloc(16) as *i64 100 if (cursor as i64)<=0 || (span as i64)<=0 { 101 if (cursor as i64)>0 {rgr_free(cursor as *u8,16)} 102 if (span as i64)>0 {rgr_free(span as *u8,16)} 103 return RGI_E_INTENT 104 } 105 cursor[0]=0 106 var ok:i64=1;var index:i64=0 107 while ok==1 && index<8 { 108 if rgi_line(body,n,cursor,span)==0 {ok=0} else { 109 let line:*u8=(body as i64+span[0]) as *u8 110 let len:i64=span[1] 111 if index==0 {ok=rgi_literal(line,len,"schema=1" as *u8)} 112 if index==1 {ok=rgi_literal(line,len,"state=PREPARED" as *u8);out.state=RGI_PREPARED} 113 if index==2 { 114 let k:i64=rgi_value(line,len,"id=" as *u8) 115 if k<0 {ok=0} else { 116 let id:*u8=(line as i64+k) as *u8 117 let idn:i64=len-k 118 if idn<1 {ok=0} 119 var i:i64=0 120 while i<idn { 121 let c:i64=id[i] as i64 122 if !((c>=48 && c<=57)||(c>=65 && c<=90)||(c>=97 && c<=122)||c==45) {ok=0} 123 i=i+1 124 } 125 let jn:i64=rgr_len(journal) 126 let pn:i64=rgr_len(path) 127 if pn!=jn+8+idn {ok=0} else { 128 if rgi_equal(path,jn,journal,jn)==0 {ok=0} 129 if rgi_literal((path as i64+jn) as *u8,8,".intent." as *u8)==0 {ok=0} 130 if rgi_equal((path as i64+jn+8) as *u8,idn,id,idn)==0 {ok=0} 131 } 132 } 133 } 134 if index==3 { 135 let k:i64=rgi_value(line,len,"artifact_sha256=" as *u8) 136 if k<0 || len-k!=64 {ok=0} else { 137 out.digest=rgr_alloc(32) 138 if (out.digest as i64)<=0 {ok=0} else { 139 var i:i64=0 140 while i<64 { 141 let c:i64=line[k+i] as i64 142 var v:i64=0-1 143 if c>=48 && c<=57 {v=c-48} 144 if c>=97 && c<=102 {v=c-87} 145 if v<0 {ok=0;v=0} 146 if i%2==0 {out.digest[i/2]=(v*16) as u8} else {out.digest[i/2]=((out.digest[i/2] as i64)+v) as u8} 147 i=i+1 148 } 149 } 150 } 151 } 152 if index>=4 && index<=6 { 153 var label:*u8="reserve_extra_bytes=" as *u8 154 if index==5 {label="available_bavail_bytes=" as *u8} 155 if index==6 {label="required_data_bytes=" as *u8} 156 let k:i64=rgi_value(line,len,label) 157 if k<0 {ok=0} else {ok=rgi_unsigned((line as i64+k) as *u8,len-k)} 158 } 159 if index==7 { 160 let k:i64=rgi_value(line,len,"completion_row=" as *u8) 161 if k<0 {ok=0} else { 162 out.row=(line as i64+k) as *u8;out.rowlen=len-k+1 163 var start:i64=0;var pos:i64=0;var field:i64=0 164 var rowbytes:i64=0;var previous:i64=0-1;var shrank:i64=0 165 let rn:i64=len-k 166 while pos<=rn && ok==1 { 167 if pos==rn || out.row[pos]==(9 as u8) { 168 let value:*u8=(out.row as i64+start) as *u8 169 let vn:i64=pos-start 170 if field==0 || field==2 {ok=rgi_unsigned(value,vn)} 171 if field==2 {rowbytes=rgi_decimal_value(value,vn)} 172 if field==1 { 173 if vn<1 {ok=0} else { 174 var ci:i64=0 175 while ci<vn {if value[ci]<(32 as u8) {ok=0};ci=ci+1} 176 out.targetcap=vn+1;out.target=rgr_alloc(out.targetcap) 177 if (out.target as i64)<=0 {ok=0} else { 178 ci=0;while ci<vn {out.target[ci]=value[ci];ci=ci+1} 179 out.target[vn]=0 as u8 180 } 181 } 182 } 183 if field==3 {if rgi_literal(value,vn,"-1" as *u8)==0 {ok=rgi_unsigned(value,vn);previous=rgi_decimal_value(value,vn)}} 184 if field==4 {ok=rgi_literal(value,vn,"SHRANK" as *u8);shrank=1} 185 if field>4 {ok=0} 186 field=field+1;start=pos+1 187 } 188 pos=pos+1 189 } 190 if field!=4 && field!=5 {ok=0} 191 var expected_shrank:i64=0 192 if previous>=0 && rowbytes<previous {expected_shrank=1} 193 if shrank!=expected_shrank {ok=0} 194 } 195 } 196 } 197 index=index+1 198 } 199 while ok==1 && cursor[0]<n { 200 if rgi_line(body,n,cursor,span)==0 {ok=0} else { 201 let line:*u8=(body as i64+span[0]) as *u8 202 let len:i64=span[1] 203 if out.state==RGI_PREPARED { 204 if rgi_literal(line,len,"state=PUBLISHED" as *u8)==1 {out.state=RGI_PUBLISHED} else { 205 if rgi_literal(line,len,"state=WRITE_FAILED_BEFORE_RENAME" as *u8)==1 {out.state=RGI_WRITE_FAILED} else { 206 if rgi_literal(line,len,"state=PUBLISHED_DURABILITY_INCOMPLETE" as *u8)==1 {out.state=RGI_DURABILITY_INCOMPLETE} else {ok=0} 207 } 208 } 209 } else { 210 if out.state==RGI_PUBLISHED && rgi_literal(line,len,"state=COMPLETED" as *u8)==1 {out.state=RGI_COMPLETED} else {ok=0} 211 } 212 } 213 } 214 rgr_free(cursor as *u8,16);rgr_free(span as *u8,16) 215 if ok==0 {return RGI_E_INTENT} 216 return 0 217} 218func rgi_intent_free(intent:*RgiIntent)->i64 { 219 if (intent.target as i64)>0 {rgr_free(intent.target,intent.targetcap)} 220 if (intent.digest as i64)>0 {rgr_free(intent.digest,32)} 221 return 0 222} 223// Existing fio region primitives reject premature EOF/read/close errors. No journal-sized allocation. 224func rgi_stream(path:*u8,row:*u8,rowlen:i64,digest:*u8,obs:*i64)->i64 { 225 obs[0]=0-1;obs[1]=0;obs[2]=0 226 let regular:i64=rgi_regular(path) 227 if regular!=0 {return regular} 228 let region:*NxFileReadRegion=rgr_alloc(__size_of(NxFileReadRegion)) as *NxFileReadRegion 229 let fs:*i64=rgr_alloc(STATFS_BUF_BYTES) as *i64 230 if (region as i64)<=0 || (fs as i64)<=0 { 231 if (region as i64)>0 {rgr_free(region as *u8,__size_of(NxFileReadRegion))} 232 if (fs as i64)>0 {rgr_free(fs as *u8,STATFS_BUF_BYTES)} 233 return RGI_E_OBSERVE 234 } 235 fio_region_init(region) 236 var rc:i64=fio_region_open(path,region) 237 if rc==0 {rc=sys_statfs(path,fs)} 238 let block:i64=fs[STATFS_I_BSIZE] 239 rgr_free(fs as *u8,STATFS_BUF_BYTES) 240 let ws:i64=sha256_workspace_bytes() 241 var memory:*u8=0 as *u8;var allocated:i64=0 242 if rc==0 { 243 if block<=0 || block>RGR_I64_MAX-ws || region.length>SHA256_SIGNED_MAX/SHA256_BITS_PER_BYTE {rc=RGI_E_OBSERVE} else { 244 allocated=block+ws;memory=rgr_alloc(allocated) 245 if (memory as i64)<=0 {rc=RGI_E_OBSERVE} else {rc=sha256_init_workspace(memory,ws)} 246 } 247 } 248 var linepos:i64=0;var same:i64=1 249 while rc==0 && region.read_bytes<region.length { 250 let buf:*u8=(memory as i64+ws) as *u8 251 let got:i64=fio_region_next(region,buf,block) 252 if got<0 {rc=got} else { 253 sha256_update(memory as *Sha256,buf,got) 254 if rowlen>0 { 255 var i:i64=0 256 while i<got { 257 let c:u8=buf[i] 258 if c==(10 as u8) { 259 if same==1 && linepos==rowlen-1 {obs[1]=obs[1]+1} 260 linepos=0;same=1 261 } else { 262 if linepos>=rowlen-1 {same=0} else {if c!=row[linepos] {same=0}} 263 linepos=linepos+1 264 } 265 i=i+1 266 } 267 } 268 } 269 } 270 let closed:i64=fio_region_close(region) 271 if rc==0 {rc=closed} 272 if rc==0 {sha256_final(memory as *Sha256,digest);obs[0]=region.length;if linepos>0 {obs[2]=1}} 273 if (memory as i64)>0 {let freed:i64=rgr_free(memory,allocated);if rc==0 {rc=freed}} 274 rgr_free(region as *u8,__size_of(NxFileReadRegion)) 275 return rc 276} 277func rgi_inspect_locked(journal:*u8,path:*u8,out:*RgiObservation)->i64 { 278 if rgi_regular(path)!=0 {return RGI_E_INTENT} 279 let region:*NxFileReadRegion=rgr_alloc(__size_of(NxFileReadRegion)) as *NxFileReadRegion 280 if (region as i64)<=0 {return RGI_E_INTENT} 281 fio_region_init(region) 282 var rc:i64=fio_region_open(path,region) 283 if rc!=0 {rgr_free(region as *u8,__size_of(NxFileReadRegion));return RGI_E_INTENT} 284 let n:i64=region.length 285 if n<1 || n>=RGR_I64_MAX {fio_region_close(region);rgr_free(region as *u8,__size_of(NxFileReadRegion));return RGI_E_INTENT} 286 let body:*u8=rgr_alloc(n+1) 287 if (body as i64)<=0 {fio_region_close(region);rgr_free(region as *u8,__size_of(NxFileReadRegion));return RGI_E_INTENT} 288 let read:i64=fio_region_next(region,body,n) 289 let closed:i64=fio_region_close(region) 290 rgr_free(region as *u8,__size_of(NxFileReadRegion)) 291 if read!=n || closed!=0 {rgr_free(body,n+1);return RGI_E_INTENT} 292 body[n]=0 as u8 293 let parsed:*RgiIntent=rgr_alloc(__size_of(RgiIntent)) as *RgiIntent 294 if (parsed as i64)<=0 {rgr_free(body,n+1);return RGI_E_INTENT} 295 rc=rgi_parse(body,n,journal,path,parsed) 296 if rc==0 { 297 out.state=parsed.state 298 let actual:*u8=rgr_alloc(32) 299 let observed:*i64=rgr_alloc(24) as *i64 300 if (actual as i64)<=0 || (observed as i64)<=0 {rc=RGI_E_OBSERVE} else { 301 out.target_rc=rgi_stream(parsed.target,0 as *u8,0,actual,observed) 302 if out.target_rc==0 { 303 out.target_bytes=observed[0] 304 out.target_match=rgi_equal(actual,32,parsed.digest,32) 305 ja_hex(actual,out.target_hex) 306 } 307 out.journal_rc=rgi_stream(journal,parsed.row,parsed.rowlen,actual,observed) 308 if out.journal_rc==0 { 309 out.journal_bytes=observed[0];out.exact_rows=observed[1];out.torn_tail=observed[2] 310 ja_hex(actual,out.journal_hex) 311 } 312 if out.target_rc!=0 || out.journal_rc!=0 {rc=RGI_E_OBSERVE} 313 if rc==0 {out.complete=1} 314 } 315 if (actual as i64)>0 {rgr_free(actual,32)} 316 if (observed as i64)>0 {rgr_free(observed as *u8,24)} 317 } 318 rgi_intent_free(parsed);rgr_free(parsed as *u8,__size_of(RgiIntent));rgr_free(body,n+1) 319 return rc 320} 321func rgi_inspect(journal:*u8,path:*u8,out:*RgiObservation)->i64 { 322 out.state=0;out.target_rc=RGI_E_OBSERVE;out.target_match=0;out.target_bytes=0-1 323 out.journal_rc=RGI_E_OBSERVE;out.journal_bytes=0-1;out.exact_rows=0;out.torn_tail=0;out.complete=0 324 out.target_hex=rgr_alloc(65);out.journal_hex=rgr_alloc(65) 325 if (out.target_hex as i64)<=0 || (out.journal_hex as i64)<=0 {return RGI_E_OBSERVE} 326 out.target_hex[0]=0 as u8;out.journal_hex[0]=0 as u8 327 if rgr_field(journal)==0 || rgr_field(path)==0 {return RGI_E_INPUT} 328 let lock:i64=ja_lock(journal) 329 if lock<0 {return RGI_E_LOCK} 330 let rc:i64=rgi_inspect_locked(journal,path,out) 331 if ja_unlock(lock)!=0 {out.complete=0;return RGI_E_RELEASE} 332 return rc 333} 334func rgi_observation_free(out:*RgiObservation)->i64 { 335 if (out.target_hex as i64)>0 {rgr_free(out.target_hex,65);out.target_hex=0 as *u8} 336 if (out.journal_hex as i64)>0 {rgr_free(out.journal_hex,65);out.journal_hex=0 as *u8} 337 return 0 338} 339func rgi_status(out:*RgiObservation,rc:i64)->*u8 { 340 if rc==RGI_E_INTENT {return "MALFORMED_OR_UNREADABLE_INTENT" as *u8} 341 if rc!=0 || out.complete!=1 {return "OBSERVATION_UNAVAILABLE" as *u8} 342 if out.torn_tail==1 {return "TORN_JOURNAL_NEEDS_INTERVENTION" as *u8} 343 if out.exact_rows>1 {return "DUPLICATE_ROWS_AMBIGUOUS" as *u8} 344 if out.target_match==0 {return "CURRENT_TARGET_DIFFERS_OR_SUPERSEDED" as *u8} 345 if out.state==RGI_COMPLETED { 346 if out.exact_rows==1 {return "COMPLETED_CURRENT_BYTES_CONFIRMED" as *u8} 347 return "COMPLETED_RECEIPT_NOT_OBSERVED_IN_CURRENT_JOURNAL" as *u8 348 } 349 if out.state==RGI_PREPARED {return "BYTES_MATCH_PUBLICATION_UNPROVEN" as *u8} 350 if out.state==RGI_WRITE_FAILED {return "WRITE_FAILED_BEFORE_RENAME_RECORDED" as *u8} 351 if out.exact_rows==1 {return "ROW_PRESENT_TRANSACTION_LINK_AMBIGUOUS" as *u8} 352 return "PUBLISHED_RECEIPT_NOT_OBSERVED_IN_CURRENT_JOURNAL" as *u8 353} 354func rgi_command(journal:*u8,path:*u8)->i64 { 355 let out:*RgiObservation=rgr_alloc(__size_of(RgiObservation)) as *RgiObservation 356 if (out as i64)<=0 {return RGI_E_OBSERVE} 357 let rc:i64=rgi_inspect(journal,path,out) 358 ja_puts("NX-COMPARE-RECEIPT-INSPECT schema=1 read_only=1 replay=0 archive_scan=0 rc=" as *u8);ja_pn(rc) 359 ja_puts(" observed_at=" as *u8);ja_pn(sys_now_realtime_sec()) 360 ja_puts(" state=" as *u8);ja_pn(out.state) 361 ja_puts(" status=" as *u8);ja_puts(rgi_status(out,rc)) 362 ja_puts(" target_match=" as *u8);ja_pn(out.target_match) 363 ja_puts(" exact_rows=" as *u8);ja_pn(out.exact_rows) 364 ja_puts(" torn_tail=" as *u8);ja_pn(out.torn_tail) 365 ja_puts(" target_bytes=" as *u8);ja_pn(out.target_bytes) 366 ja_puts(" journal_bytes=" as *u8);ja_pn(out.journal_bytes) 367 ja_puts("\nactual_target_sha256=" as *u8);if (out.target_hex as i64)>0 {ja_puts(out.target_hex)} 368 ja_puts("\nobserved_journal_sha256=" as *u8);if (out.journal_hex as i64)>0 {ja_puts(out.journal_hex)} 369 ja_puts("\nNo intent or journal data changed. Row identity lacks transaction offset/generation; inspection does not authorize replay.\n" as *u8) 370 rgi_observation_free(out);rgr_free(out as *u8,__size_of(RgiObservation)) 371 return rc 372}