code wiki / (root) / nx_cbor.nx

nx_cbor.nx source

↩ module page · 226 lines · 7834 B

1// nx_cbor.nx -- RFC 8949 Concise Binary Object Representation, minimal 2// encoder for the substrate's machine-readable wire format. 3// 4// CBOR is the IETF-standard binary JSON-equivalent (RFC 8949). Same 5// information model as JSON, ~30-50% smaller on the wire, deterministic 6// byte-output, zero schema infrastructure. Used by COSE, CoAP, Dapr, 7// and IPLD; chosen here over Parquet/Arrow because: 8// * spans are small + irregularly-shaped (CBOR strength) 9// * we want sovereign-no-deps (one file, pure NishiLang) 10// * the same logical span can ship to JSONL (human) AND CBOR (machine) 11// 12// Spec: RFC 8949 https://www.rfc-editor.org/rfc/rfc8949.html 13// 14// Major types implemented (subset sufficient for spans): 15// 0 unsigned integer 16// 1 negative integer (encoded as -1-value, unsigned) 17// 4 array of N items 18// 5 map of N pairs 19// 20// Major types deferred (queued): 21// 2 byte string 22// 3 text string (UTF-8) 23// 6 tag 24// 7 floats / simple values 25// 26// genealogy_id: bormann_hoffman_2013_cbor + rfc_8949 27// lineage_id: substrate_native_cbor_v1 28 29// nx_safety_envelope: 30// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 31// sil_target: SIL1 32// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 33// verdict: NOT_YET_EVALUATED 34 35import "nx_syscalls.nx" 36import "nx_tier.nx" 37 38// ===== Major-type bit-pattern constants ============================ 39 40const NX_CBOR_MAJ_UINT: nx_int = 0 // 0x00 | length 41const NX_CBOR_MAJ_NINT: nx_int = 1 // 0x20 | length 42const NX_CBOR_MAJ_ARRAY: nx_int = 4 // 0x80 | length 43const NX_CBOR_MAJ_MAP: nx_int = 5 // 0xA0 | length 44 45// Length-marker thresholds (inline 0..23, 1-byte 24, 2-byte 25, 4-byte 46// 26, 8-byte 27). 47const NX_CBOR_LEN_INLINE_MAX: nx_int = 24 48const NX_CBOR_LEN_BYTE_1: nx_int = 24 49const NX_CBOR_LEN_BYTE_2: nx_int = 25 50const NX_CBOR_LEN_BYTE_4: nx_int = 26 51const NX_CBOR_LEN_BYTE_8: nx_int = 27 52 53const NX_CBOR_U8_MAX: nx_int = 256 54const NX_CBOR_U16_MAX: nx_int = 65536 55const NX_CBOR_U32_MAX: nx_int = 4294967296 56 57// Helper: 2^n via repeated multiplication (small n only). Hoisted 58// above nx_cbor_put_length because nxc2's resolver is single-pass. 59func _nx_cbor_pow2(n: nx_int) -> nx_int { 60 var v: nx_int = 1 61 var i: nx_int = 0 62 while i < n { 63 v = v * 2 64 i = i + 1 65 } 66 return v 67} 68 69// ===== Raw byte append (bounds-unchecked; caller sizes buf) ======== 70// 71// Per the substrate convention (nx_json_emit), caller pre-allocates 72// the buffer and tracks pos; we never realloc. 73 74func nx_cbor_put_byte(buf: *u8, pos: nx_int, b: nx_int) -> nx_int { 75 buf[pos] = b 76 return pos + 1 77} 78 79// ===== Length-prefix encoder (major type | length-class) ============ 80// 81// Emits the header for any major-type value with the given numeric 82// length. Returns updated pos. 83 84func nx_cbor_put_length(buf: *u8, pos: nx_int, major: nx_int, value: nx_int) -> nx_int { 85 let mhi: nx_int = major * 32 // major << 5 86 87 if value < NX_CBOR_LEN_INLINE_MAX { 88 return nx_cbor_put_byte(buf, pos, mhi + value) 89 } 90 91 if value < NX_CBOR_U8_MAX { 92 var p: nx_int = nx_cbor_put_byte(buf, pos, mhi + NX_CBOR_LEN_BYTE_1) 93 p = nx_cbor_put_byte(buf, p, value) 94 return p 95 } 96 97 if value < NX_CBOR_U16_MAX { 98 var p: nx_int = nx_cbor_put_byte(buf, pos, mhi + NX_CBOR_LEN_BYTE_2) 99 p = nx_cbor_put_byte(buf, p, (value / 256) % 256) // hi 100 p = nx_cbor_put_byte(buf, p, value % 256) // lo 101 return p 102 } 103 104 if value < NX_CBOR_U32_MAX { 105 var p: nx_int = nx_cbor_put_byte(buf, pos, mhi + NX_CBOR_LEN_BYTE_4) 106 p = nx_cbor_put_byte(buf, p, (value / 16777216) % 256) 107 p = nx_cbor_put_byte(buf, p, (value / 65536) % 256) 108 p = nx_cbor_put_byte(buf, p, (value / 256) % 256) 109 p = nx_cbor_put_byte(buf, p, value % 256) 110 return p 111 } 112 113 // 8-byte path: write 8 big-endian bytes of value. 114 var p2: nx_int = nx_cbor_put_byte(buf, pos, mhi + NX_CBOR_LEN_BYTE_8) 115 var shift: nx_int = 56 116 var v: nx_int = value 117 while shift >= 0 { 118 let byte_val: nx_int = (v / _nx_cbor_pow2(shift)) % 256 119 p2 = nx_cbor_put_byte(buf, p2, byte_val) 120 shift = shift - 8 121 } 122 return p2 123} 124 125// ===== Unsigned integer ============================================ 126 127func nx_cbor_emit_u64(buf: *u8, pos: nx_int, value: nx_int) -> nx_int { 128 if value < 0 { return pos } // caller error; bail. Use emit_i64. 129 return nx_cbor_put_length(buf, pos, NX_CBOR_MAJ_UINT, value) 130} 131 132// ===== Signed integer ============================================== 133// 134// CBOR negative ints encode as major-type-1 with payload = -1 - value. 135// e.g. -1 -> majtype=1, payload=0. Positive ints route to major 0. 136 137func nx_cbor_emit_i64(buf: *u8, pos: nx_int, value: nx_int) -> nx_int { 138 if value >= 0 { 139 return nx_cbor_put_length(buf, pos, NX_CBOR_MAJ_UINT, value) 140 } 141 let payload: nx_int = (0 - value) - 1 142 return nx_cbor_put_length(buf, pos, NX_CBOR_MAJ_NINT, payload) 143} 144 145// ===== Array header ================================================ 146 147func nx_cbor_emit_array_header(buf: *u8, pos: nx_int, n_elements: nx_int) -> nx_int { 148 return nx_cbor_put_length(buf, pos, NX_CBOR_MAJ_ARRAY, n_elements) 149} 150 151// ===== Map header ================================================== 152 153func nx_cbor_emit_map_header(buf: *u8, pos: nx_int, n_pairs: nx_int) -> nx_int { 154 return nx_cbor_put_length(buf, pos, NX_CBOR_MAJ_MAP, n_pairs) 155} 156 157// ===== Span emit (high-level) ===================================== 158// 159// Encodes one trace span as a CBOR map of 7 named fields. Keys are 160// short u8 codes so the wire stays tiny: 161// 162// 0 trace_id 163// 1 span_id 164// 2 parent_span_id 165// 3 kind 166// 4 start_ms 167// 5 end_ms 168// 6 attrs (array of [k, v] pairs, k+v both i64) 169// 170// Returns updated pos. 171 172const NX_CBOR_SPAN_KEY_TRACE: nx_int = 0 173const NX_CBOR_SPAN_KEY_SPAN: nx_int = 1 174const NX_CBOR_SPAN_KEY_PARENT: nx_int = 2 175const NX_CBOR_SPAN_KEY_KIND: nx_int = 3 176const NX_CBOR_SPAN_KEY_START: nx_int = 4 177const NX_CBOR_SPAN_KEY_END: nx_int = 5 178const NX_CBOR_SPAN_KEY_ATTRS: nx_int = 6 179const NX_CBOR_SPAN_N_PAIRS: nx_int = 7 180 181func nx_cbor_emit_span(buf: *u8, pos: nx_int, 182 trace_id: nx_int, span_id: nx_int, 183 parent: nx_int, kind: nx_int, 184 start_ms: nx_int, end_ms: nx_int, 185 attrs: *i64, n_attrs: nx_int) -> nx_int { 186 var p: nx_int = nx_cbor_emit_map_header(buf, pos, NX_CBOR_SPAN_N_PAIRS) 187 188 p = nx_cbor_emit_u64(buf, p, NX_CBOR_SPAN_KEY_TRACE) 189 p = nx_cbor_emit_i64(buf, p, trace_id) 190 191 p = nx_cbor_emit_u64(buf, p, NX_CBOR_SPAN_KEY_SPAN) 192 p = nx_cbor_emit_i64(buf, p, span_id) 193 194 p = nx_cbor_emit_u64(buf, p, NX_CBOR_SPAN_KEY_PARENT) 195 p = nx_cbor_emit_i64(buf, p, parent) 196 197 p = nx_cbor_emit_u64(buf, p, NX_CBOR_SPAN_KEY_KIND) 198 p = nx_cbor_emit_i64(buf, p, kind) 199 200 p = nx_cbor_emit_u64(buf, p, NX_CBOR_SPAN_KEY_START) 201 p = nx_cbor_emit_i64(buf, p, start_ms) 202 203 p = nx_cbor_emit_u64(buf, p, NX_CBOR_SPAN_KEY_END) 204 p = nx_cbor_emit_i64(buf, p, end_ms) 205 206 p = nx_cbor_emit_u64(buf, p, NX_CBOR_SPAN_KEY_ATTRS) 207 p = nx_cbor_emit_array_header(buf, p, n_attrs) 208 var i: nx_int = 0 209 while i < n_attrs { 210 p = nx_cbor_emit_array_header(buf, p, 2) 211 p = nx_cbor_emit_i64(buf, p, attrs[2 * i]) 212 p = nx_cbor_emit_i64(buf, p, attrs[2 * i + 1]) 213 i = i + 1 214 } 215 216 return p 217} 218 219// ===== Flush to fd ================================================= 220// 221// Helper to write the encoded bytes to a file descriptor. Returns 222// the syscall's byte count (>= 0) or negative errno. 223 224func nx_cbor_flush_fd(fd: nx_int, buf: *u8, n: nx_int) -> nx_int { 225 return sys_write(fd, buf, n) 226}