code wiki / (root) / nx_cbor_decode.nx

nx_cbor_decode.nx source

↩ module page · 218 lines · 7819 B

1// nx_cbor_decode.nx -- RFC 8949 decoder, inverse of nx_cbor.nx. 2// 3// Closes the storage roundtrip: spans encoded via nx_cbor_emit_span 4// land in nx_log_chunked, then come back through this decoder when 5// nx_trace_query (or any downstream consumer) walks the log. 6// 7// Same major-type subset as the encoder: 8// 0 unsigned integer 9// 1 negative integer 10// 4 array 11// 5 map 12// 13// Decoder is push-style: caller passes (buf, pos), each read returns 14// (value, new_pos). No malloc, no internal state. Out-of-bounds 15// reads are NOT bounds-checked here -- caller is responsible for 16// staying within the chunk_pos limit recorded in the manifest. 17// 18// genealogy_id: rfc_8949_section_3 19// lineage_id: substrate_native_cbor_decoder_v1 20 21// nx_safety_envelope: 22// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 23// sil_target: SIL1 24// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 25// verdict: NOT_YET_EVALUATED 26 27import "nx_syscalls.nx" 28import "nx_tier.nx" 29import "nx_cbor.nx" 30 31// ===== Sealed-enum: DecodeKind ==================================== 32// 33// What we read at this position. Used by nx_cbor_peek_kind so 34// callers can branch on shape before consuming the value. 35 36const NX_CBOR_DK_UINT: nx_int = 0 37const NX_CBOR_DK_NINT: nx_int = 1 38const NX_CBOR_DK_ARRAY: nx_int = 2 39const NX_CBOR_DK_MAP: nx_int = 3 40const NX_CBOR_DK_OTHER: nx_int = 4 // major 2/3/6/7 -- not yet supported 41const NX_CBOR_DK_N_KINDS: nx_int = 5 42 43func nx_cbor_dk_is_valid(k: nx_int) -> nx_int { 44 if k < 0 { return 0 } 45 if k >= NX_CBOR_DK_N_KINDS { return 0 } 46 return 1 47} 48 49// ===== Peek next major-type ======================================= 50// 51// Returns the DK_* code for the byte at pos. Does NOT advance. 52 53func nx_cbor_peek_kind(buf: *u8, pos: nx_int) -> nx_int { 54 let first: nx_int = buf[pos] 55 let major: nx_int = first / 32 56 if major == NX_CBOR_MAJ_UINT { return NX_CBOR_DK_UINT } 57 if major == NX_CBOR_MAJ_NINT { return NX_CBOR_DK_NINT } 58 if major == NX_CBOR_MAJ_ARRAY { return NX_CBOR_DK_ARRAY } 59 if major == NX_CBOR_MAJ_MAP { return NX_CBOR_DK_MAP } 60 return NX_CBOR_DK_OTHER 61} 62 63// ===== Read length-prefixed unsigned value ======================== 64// 65// Mirror of the encoder's nx_cbor_put_length. Returns the unsigned 66// payload; new_pos[0] gets the position after the value. 67// 68// Inline len 0..23 -> payload = additional-info bits, +1 byte 69// len = 24 -> payload = next 1 byte, +2 bytes total 70// len = 25 -> payload = next 2 bytes big-endian, +3 71// len = 26 -> payload = next 4 bytes big-endian, +5 72// len = 27 -> payload = next 8 bytes big-endian, +9 73 74func nx_cbor_read_length(buf: *u8, pos: nx_int, new_pos: *i64) -> nx_int { 75 let first: nx_int = buf[pos] 76 let info: nx_int = first - (first / 32) * 32 // first & 0x1F 77 78 if info < NX_CBOR_LEN_BYTE_1 { 79 new_pos[0] = pos + 1 80 return info 81 } 82 if info == NX_CBOR_LEN_BYTE_1 { 83 new_pos[0] = pos + 2 84 return buf[pos + 1] 85 } 86 if info == NX_CBOR_LEN_BYTE_2 { 87 new_pos[0] = pos + 3 88 return buf[pos + 1] * 256 + buf[pos + 2] 89 } 90 if info == NX_CBOR_LEN_BYTE_4 { 91 new_pos[0] = pos + 5 92 var v: nx_int = buf[pos + 1] 93 v = v * 256 + buf[pos + 2] 94 v = v * 256 + buf[pos + 3] 95 v = v * 256 + buf[pos + 4] 96 return v 97 } 98 if info == NX_CBOR_LEN_BYTE_8 { 99 new_pos[0] = pos + 9 100 var v8: nx_int = buf[pos + 1] 101 v8 = v8 * 256 + buf[pos + 2] 102 v8 = v8 * 256 + buf[pos + 3] 103 v8 = v8 * 256 + buf[pos + 4] 104 v8 = v8 * 256 + buf[pos + 5] 105 v8 = v8 * 256 + buf[pos + 6] 106 v8 = v8 * 256 + buf[pos + 7] 107 v8 = v8 * 256 + buf[pos + 8] 108 return v8 109 } 110 // info 28..30 are reserved; info 31 is indefinite-length (not used here). 111 new_pos[0] = pos + 1 112 return 0 113} 114 115// ===== Read a signed integer (major 0 or 1) ======================= 116 117func nx_cbor_read_i64(buf: *u8, pos: nx_int, new_pos: *i64) -> nx_int { 118 let major: nx_int = buf[pos] / 32 119 let payload: nx_int = nx_cbor_read_length(buf, pos, new_pos) 120 if major == NX_CBOR_MAJ_UINT { return payload } 121 // major 1 -> negative. encoded -1 - value -> value = -1 - payload 122 return 0 - payload - 1 123} 124 125// ===== Read array / map header ==================================== 126// 127// Returns the element count (array) or pair count (map). Caller 128// then iterates that many times to read inner values. 129 130func nx_cbor_read_array_header(buf: *u8, pos: nx_int, new_pos: *i64) -> nx_int { 131 return nx_cbor_read_length(buf, pos, new_pos) 132} 133 134func nx_cbor_read_map_header(buf: *u8, pos: nx_int, new_pos: *i64) -> nx_int { 135 return nx_cbor_read_length(buf, pos, new_pos) 136} 137 138// ===== High-level: read a span back =============================== 139// 140// Mirror of nx_cbor_emit_span. Reads the map header (7 pairs), then 141// for each key extracts the int value into the matching out_*[0] 142// slot. attrs_out is sized by caller; n_attrs_out[0] receives the 143// actual count (may be less than capacity). attrs_cap is the 144// caller's buffer capacity. 145// 146// Returns 0 on success or -1 if the map structure is malformed. 147 148func nx_cbor_read_span(buf: *u8, pos: nx_int, new_pos: *i64, 149 trace_out: *i64, span_out: *i64, 150 parent_out: *i64, kind_out: *i64, 151 start_out: *i64, end_out: *i64, 152 attrs_out: *i64, attrs_cap: nx_int, 153 n_attrs_out: *i64) -> nx_int { 154 // Must start with a map header of 7 pairs 155 if nx_cbor_peek_kind(buf, pos) != NX_CBOR_DK_MAP { return 0 - 1 } 156 let np: *i64 = (sys_mmap(8)) as *i64 157 let n_pairs: nx_int = nx_cbor_read_map_header(buf, pos, np) 158 if n_pairs != NX_CBOR_SPAN_N_PAIRS { return 0 - 1 } 159 160 var cur: nx_int = np[0] 161 var i: nx_int = 0 162 while i < n_pairs { 163 // Read key (int) 164 let key: nx_int = nx_cbor_read_i64(buf, cur, np) 165 cur = np[0] 166 167 if key == NX_CBOR_SPAN_KEY_TRACE { 168 trace_out[0] = nx_cbor_read_i64(buf, cur, np) 169 cur = np[0] 170 } 171 if key == NX_CBOR_SPAN_KEY_SPAN { 172 span_out[0] = nx_cbor_read_i64(buf, cur, np) 173 cur = np[0] 174 } 175 if key == NX_CBOR_SPAN_KEY_PARENT { 176 parent_out[0] = nx_cbor_read_i64(buf, cur, np) 177 cur = np[0] 178 } 179 if key == NX_CBOR_SPAN_KEY_KIND { 180 kind_out[0] = nx_cbor_read_i64(buf, cur, np) 181 cur = np[0] 182 } 183 if key == NX_CBOR_SPAN_KEY_START { 184 start_out[0] = nx_cbor_read_i64(buf, cur, np) 185 cur = np[0] 186 } 187 if key == NX_CBOR_SPAN_KEY_END { 188 end_out[0] = nx_cbor_read_i64(buf, cur, np) 189 cur = np[0] 190 } 191 if key == NX_CBOR_SPAN_KEY_ATTRS { 192 // Outer array: number of (k,v) attr pairs 193 let n_attrs: nx_int = nx_cbor_read_array_header(buf, cur, np) 194 cur = np[0] 195 n_attrs_out[0] = n_attrs 196 var j: nx_int = 0 197 while j < n_attrs { 198 // Each attr is an inner 2-element array: [k, v] 199 let n_inner: nx_int = nx_cbor_read_array_header(buf, cur, np) 200 cur = np[0] 201 if n_inner != 2 { return 0 - 1 } 202 let attr_k: nx_int = nx_cbor_read_i64(buf, cur, np) 203 cur = np[0] 204 let attr_v: nx_int = nx_cbor_read_i64(buf, cur, np) 205 cur = np[0] 206 if j < attrs_cap { 207 attrs_out[2 * j] = attr_k 208 attrs_out[2 * j + 1] = attr_v 209 } 210 j = j + 1 211 } 212 } 213 i = i + 1 214 } 215 216 new_pos[0] = cur 217 return 0 218}