code wiki / (root) / nx_journal_log_test.nx

nx_journal_log_test.nx source

↩ module page · 195 lines · 7876 B

1// nx_journal_log_test.nx -- smoke for nx_journal_log ST-3 MVP. 2// 3// Exercises: 4// 1. Allocation + canary + initial state 5// 2. Append + read roundtrip on 3 distinct payloads 6// 3. Count increments correctly 7// 4. read returns correct schema_id per entry 8// 5. read with oversized buffer; read out-of-range seq_no -> -1 9// 6. Empty log -> verify_chain returns -1 (trivially intact); 10// latest_hash returns NX_JOURNAL_NOT_FOUND 11// 7. Filled log -> verify_chain returns -1 (chain intact) 12// 8. latest_hash deterministic across two journals with same 13// payload sequence (cross-journal hash determinism) 14// 9. TAMPER detection: stomp entry K's blob_w0 directly -> 15// verify_chain returns K+1 (next entry's prev_hash no longer 16// matches the recomputed hash of the tampered entry) 17// 10. Canary tamper on log struct -> all ops refuse 18// 11. Bad-input gates (null log / null payload / negative len / 19// out-of-range seq_no) 20 21import "nx_syscalls.nx" 22import "nx_blob_store.nx" 23import "nx_journal_log.nx" 24 25func _fill_pattern(buf: *u8, len: i64, seed: i64) -> i64 { 26 var i: i64 = 0 27 while i < len { 28 buf[i] = (((i * 17) + seed) & 255) as u8 29 i = i + 1 30 } 31 return 0 32} 33 34func main() -> i64 { 35 let store: *NxBlobStore = nx_blob_store_new() 36 let j: *NxJournalLog = nx_journal_log_new(store) 37 38 // ----- 1. Initial state ----- 39 if nx_journal_log_is_valid(j) != 1 { return 1 } 40 if nx_journal_log_count(j) != 0 { return 2 } 41 42 // ----- 2. Empty log: verify_chain = -1, latest_hash = NOT_FOUND ----- 43 if nx_journal_log_verify_chain(j) != -1 { return 3 } 44 let lw0: *i64 = (sys_mmap(8)) as *i64 45 let lw1: *i64 = (sys_mmap(8)) as *i64 46 let lw2: *i64 = (sys_mmap(8)) as *i64 47 let lw3: *i64 = (sys_mmap(8)) as *i64 48 if nx_journal_log_latest_hash(j, lw0, lw1, lw2, lw3) 49 != NX_JOURNAL_NOT_FOUND { return 4 } 50 51 // ----- 3. Append 3 distinct payloads ----- 52 let p1: *u8 = sys_mmap(64) 53 _fill_pattern(p1, 32, 11) 54 let seq1: i64 = nx_journal_log_append(j, p1, 32, 1001) 55 if seq1 != 0 { return 5 } 56 if nx_journal_log_count(j) != 1 { return 6 } 57 58 let p2: *u8 = sys_mmap(64) 59 _fill_pattern(p2, 32, 22) 60 let seq2: i64 = nx_journal_log_append(j, p2, 32, 1002) 61 if seq2 != 1 { return 7 } 62 if nx_journal_log_count(j) != 2 { return 8 } 63 64 let p3: *u8 = sys_mmap(64) 65 _fill_pattern(p3, 32, 33) 66 let seq3: i64 = nx_journal_log_append(j, p3, 32, 1003) 67 if seq3 != 2 { return 9 } 68 if nx_journal_log_count(j) != 3 { return 10 } 69 70 // ----- 4. Read each back; verify payload + schema_id ----- 71 let out1: *u8 = sys_mmap(64) 72 let sid1: *i64 = (sys_mmap(8)) as *i64 73 let n1: i64 = nx_journal_log_read(j, 0, out1, 64, sid1) 74 if n1 != 32 { return 11 } 75 if *sid1 != 1001 { return 12 } 76 var k1: i64 = 0 77 while k1 < 32 { 78 if (out1[k1] as i64) != (p1[k1] as i64) { return 13 } 79 k1 = k1 + 1 80 } 81 82 let out2: *u8 = sys_mmap(64) 83 let sid2: *i64 = (sys_mmap(8)) as *i64 84 let n2: i64 = nx_journal_log_read(j, 1, out2, 64, sid2) 85 if n2 != 32 { return 14 } 86 if *sid2 != 1002 { return 15 } 87 var k2: i64 = 0 88 while k2 < 32 { 89 if (out2[k2] as i64) != (p2[k2] as i64) { return 16 } 90 k2 = k2 + 1 91 } 92 93 let out3: *u8 = sys_mmap(64) 94 let sid3: *i64 = (sys_mmap(8)) as *i64 95 let n3: i64 = nx_journal_log_read(j, 2, out3, 64, sid3) 96 if n3 != 32 { return 17 } 97 if *sid3 != 1003 { return 18 } 98 99 // ----- 5. Read out-of-range + oversized buffer ----- 100 if nx_journal_log_read(j, -1, out1, 64, (0 as i64) as *i64) != -1 { return 19 } 101 if nx_journal_log_read(j, 3, out1, 64, (0 as i64) as *i64) != -1 { return 20 } 102 if nx_journal_log_read(j, 999, out1, 64, (0 as i64) as *i64) != -1 { return 21 } 103 let tiny_buf: *u8 = sys_mmap(8) 104 if nx_journal_log_read(j, 0, tiny_buf, 8, (0 as i64) as *i64) != -1 { return 22 } 105 106 // ----- 6. verify_chain on filled log: -1 (intact) ----- 107 if nx_journal_log_verify_chain(j) != -1 { return 23 } 108 109 // ----- 7. latest_hash returns non-zero ----- 110 if nx_journal_log_latest_hash(j, lw0, lw1, lw2, lw3) 111 != NX_JOURNAL_OK { return 24 } 112 // At least one of the four words must be non-zero. 113 if (*lw0 == 0) { 114 if (*lw1 == 0) { 115 if (*lw2 == 0) { 116 if (*lw3 == 0) { return 25 } 117 } 118 } 119 } 120 121 // ----- 8. Cross-journal hash determinism ----- 122 // Build a second journal with the same 3 payloads in the same 123 // order; latest_hash should match (modulo ts_us differences -- 124 // entry_hash INCLUDES ts_us so cross-journal hashes will differ 125 // by ts. Instead assert STRUCTURAL determinism: count + schemas 126 // + payloads + chain-intact all match). 127 let store2: *NxBlobStore = nx_blob_store_new() 128 let j2: *NxJournalLog = nx_journal_log_new(store2) 129 nx_journal_log_append(j2, p1, 32, 1001) 130 nx_journal_log_append(j2, p2, 32, 1002) 131 nx_journal_log_append(j2, p3, 32, 1003) 132 if nx_journal_log_count(j2) != 3 { return 26 } 133 if nx_journal_log_verify_chain(j2) != -1 { return 27 } 134 // Reading j2 entries returns same payloads. 135 let out2_b: *u8 = sys_mmap(64) 136 let sid2_b: *i64 = (sys_mmap(8)) as *i64 137 let n2_b: i64 = nx_journal_log_read(j2, 1, out2_b, 64, sid2_b) 138 if n2_b != 32 { return 28 } 139 if *sid2_b != 1002 { return 29 } 140 141 // ----- 9. TAMPER detection via direct entry mutation ----- 142 // Stomp entry 1's blob_w0 -- entry_hash(1) changes, so entry 2's 143 // prev_w0 (which holds the OLD entry_hash(1)) no longer matches 144 // the recomputed entry_hash(1). verify_chain catches at K=2. 145 let entry1: *NxJournalEntry = (j.entries[1]) as *NxJournalEntry 146 let original_blob_w0: i64 = entry1.blob_w0 147 entry1.blob_w0 = 0xCAFEBABE 148 let broken_at: i64 = nx_journal_log_verify_chain(j) 149 if broken_at != 2 { return 30 } 150 // Restore so subsequent asserts work. 151 entry1.blob_w0 = original_blob_w0 152 if nx_journal_log_verify_chain(j) != -1 { return 31 } 153 154 // Also: stomp prev_w0 of an entry directly -- entry_hash changes 155 // -> next entry's prev_hash mismatches. 156 let entry0: *NxJournalEntry = (j.entries[0]) as *NxJournalEntry 157 let orig_prev_w0: i64 = entry0.prev_w0 158 entry0.prev_w0 = 0xDEADBEEF 159 let broken_at2: i64 = nx_journal_log_verify_chain(j) 160 if broken_at2 != 1 { return 32 } 161 entry0.prev_w0 = orig_prev_w0 162 if nx_journal_log_verify_chain(j) != -1 { return 33 } 163 164 // ----- 10. Canary tamper on the log struct itself ----- 165 let tamper_store: *NxBlobStore = nx_blob_store_new() 166 let tj: *NxJournalLog = nx_journal_log_new(tamper_store) 167 nx_journal_log_append(tj, p1, 32, 100) 168 tj.canary_post = 0xDEADBEEF 169 if nx_journal_log_is_valid(tj) != 0 { return 34 } 170 // All ops refuse. 171 if nx_journal_log_append(tj, p1, 32, 100) 172 != (0 - NX_JOURNAL_TAMPER) { return 35 } 173 if nx_journal_log_count(tj) != -1 { return 36 } 174 let scratch_buf: *u8 = sys_mmap(64) 175 if nx_journal_log_read(tj, 0, scratch_buf, 64, (0 as i64) as *i64) != -1 { return 37 } 176 177 // ----- 11. Bad-input gates ----- 178 let null_payload: *u8 = (0 as i64) as *u8 179 let null_log: *NxJournalLog = (0 as i64) as *NxJournalLog 180 if nx_journal_log_append(null_log, p1, 32, 0) 181 != (0 - NX_JOURNAL_TAMPER) { return 38 } 182 if nx_journal_log_append(j, null_payload, 16, 0) 183 != (0 - NX_JOURNAL_BAD_INPUT) { return 39 } 184 if nx_journal_log_append(j, p1, -1, 0) 185 != (0 - NX_JOURNAL_BAD_INPUT) { return 40 } 186 // Empty payload (len=0) IS allowed (composes with empty-blob 187 // dedup in L0). 188 let empty_buf: *u8 = sys_mmap(8) 189 let seq_empty: i64 = nx_journal_log_append(j, empty_buf, 0, 9999) 190 if seq_empty != 3 { return 41 } 191 if nx_journal_log_count(j) != 4 { return 42 } 192 if nx_journal_log_verify_chain(j) != -1 { return 43 } 193 194 return 0 195}