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}