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}