code wiki / (root) / nx_base64.nx

nx_base64.nx source

↩ module page · 179 lines · 6829 B

1// base64.nx -- RFC 4648 base64 encoder + decoder. 2// 3// Canonical: this is the substrate-wide canonical Base64 (RFC 4648 4// §4 standard alphabet) per [[feedback-no-tool-proliferation-bit- 5// level]]. Variants that need URL-safe alphabet (RFC 4648 §5) are 6// candidates for ONE distinct sibling primitive nx_base64_url.nx 7// (queued) that imports THIS file's encode/decode skeleton; all 8// other consumers compose THIS file's encode/decode primitives. 9// Re-implementing the Base64 alphabet or quantum-loop inline is 10// refused. 11// 12// Used for: 13// - PEM decoding of X.509 certs (thin ASCII wrapper around DER) 14// - TLS 1.3 pre-shared key encoding 15// - HTTP Basic auth, OAuth tokens, JWT 16// - Web content (data: URIs, JSON-embedded bytes) 17// 18// Standard alphabet (RFC 4648 §4): 19// 0-25 : A-Z 20// 26-51 : a-z 21// 52-61 : 0-9 22// 62 : + 23// 63 : / 24// pad : = 25// 26// URL-safe alphabet variant (§5) swaps +/ for -_; provided as 27// b64url_encode / b64url_decode. 28// 29// Invariants: 30// B1 Input/output lengths are predictable: 31// encode(n bytes) -> 4 * ceil(n / 3) chars 32// decode(n chars) -> 3 * (n / 4) - padding bytes 33// B2 Decoder rejects invalid input (non-alphabet chars) by 34// returning a negative length. No silent skip. 35// B3 Decoder is tolerant of missing padding (RFC 4648 §3.2 36// permits this as "unpadded" variant). 37// B4 Encoder is deterministic; same input -> same output. No 38// trailing whitespace, no line breaks inserted. Callers 39// that want MIME-style 76-char wrap do it outside. 40// 41// license_tier: INDEPENDENT_REDERIVE 42// genealogy_id: international-research-sources/ietf/rfc_8446 43// 44 45// nx_safety_envelope: 46// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 47// sil_target: SIL1 48// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 49// verdict: NOT_YET_EVALUATED 50// 51// F-meta-4 refactor 2026-05-17: removed `import "nx_syscalls.nx"`. 52// This file is pure -- all functions operate on caller-provided 53// buffers + stack-local counters. Composing with any syscall layer 54// (nx_syscalls.nx OR nx_syscalls_x86_64.nx) no longer hits the 55// duplicate-symbol bug class. Unblocks crypto chain (nx_jwt -> 56// nx_jose / jwk / acme / csr) for x86_64 native runtime testing. 57 58const B64_PAD: i64 = 0x3D // '=' 59 60// Encode one 6-bit index to an ASCII char. Standard alphabet. 61func b64_enc_char(n: i64) -> i64 { 62 let v: i64 = n & 0x3F 63 if v < 26 { return 0x41 + v } // 'A'..'Z' 64 if v < 52 { return 0x61 + (v - 26) } // 'a'..'z' 65 if v < 62 { return 0x30 + (v - 52) } // '0'..'9' 66 if v == 62 { return 0x2B } // '+' 67 return 0x2F // '/' 68} 69 70// URL-safe variant: replace + / with - _ 71func b64url_enc_char(n: i64) -> i64 { 72 let v: i64 = n & 0x3F 73 if v < 26 { return 0x41 + v } 74 if v < 52 { return 0x61 + (v - 26) } 75 if v < 62 { return 0x30 + (v - 52) } 76 if v == 62 { return 0x2D } // '-' 77 return 0x5F // '_' 78} 79 80// Decode one ASCII char to 6-bit value; returns -1 if invalid. 81// Accepts either standard (+,/) or URL-safe (-,_) variants. 82func b64_dec_char(c: i64) -> i64 { 83 if c >= 0x41 { if c <= 0x5A { return c - 0x41 } } // A-Z 84 if c >= 0x61 { if c <= 0x7A { return c - 0x61 + 26 } } // a-z 85 if c >= 0x30 { if c <= 0x39 { return c - 0x30 + 52 } } // 0-9 86 if c == 0x2B { return 62 } // + 87 if c == 0x2F { return 63 } // / 88 if c == 0x2D { return 62 } // - (URL-safe) 89 if c == 0x5F { return 63 } // _ (URL-safe) 90 return -1 91} 92 93// Encode `n` bytes from `in_bytes` to `out`; returns written length. 94// Output size: 4 * ceil(n / 3) chars. Pads with '=' to full groups. 95func b64_encode(in_bytes: *u8, n: i64, out: *u8) -> i64 { 96 var pos: i64 = 0 97 var out_pos: i64 = 0 98 while pos + 3 <= n { 99 let b0: i64 = in_bytes[pos] 100 let b1: i64 = in_bytes[pos + 1] 101 let b2: i64 = in_bytes[pos + 2] 102 out[out_pos + 0] = b64_enc_char((b0 >> 2) & 0x3F) 103 out[out_pos + 1] = b64_enc_char(((b0 << 4) | (b1 >> 4)) & 0x3F) 104 out[out_pos + 2] = b64_enc_char(((b1 << 2) | (b2 >> 6)) & 0x3F) 105 out[out_pos + 3] = b64_enc_char(b2 & 0x3F) 106 pos = pos + 3 107 out_pos = out_pos + 4 108 } 109 let remain: i64 = n - pos 110 if remain == 1 { 111 let b0: i64 = in_bytes[pos] 112 out[out_pos + 0] = b64_enc_char((b0 >> 2) & 0x3F) 113 out[out_pos + 1] = b64_enc_char((b0 << 4) & 0x3F) 114 out[out_pos + 2] = B64_PAD 115 out[out_pos + 3] = B64_PAD 116 out_pos = out_pos + 4 117 } 118 if remain == 2 { 119 let b0: i64 = in_bytes[pos] 120 let b1: i64 = in_bytes[pos + 1] 121 out[out_pos + 0] = b64_enc_char((b0 >> 2) & 0x3F) 122 out[out_pos + 1] = b64_enc_char(((b0 << 4) | (b1 >> 4)) & 0x3F) 123 out[out_pos + 2] = b64_enc_char((b1 << 2) & 0x3F) 124 out[out_pos + 3] = B64_PAD 125 out_pos = out_pos + 4 126 } 127 return out_pos 128} 129 130// Grab one sextet: return 0..63 on valid, -1 on '=' or end-of-input, 131// -2 on any other invalid char. Advances *pos on success. 132func b64_grab(in_chars: *u8, n: i64, pos: *i64) -> i64 { 133 let p: i64 = *pos 134 if p >= n { return -1 } 135 let c: i64 = in_chars[p] 136 if c == B64_PAD { 137 *pos = n + 1 138 return -1 139 } 140 let v: i64 = b64_dec_char(c) 141 if v < 0 { return -2 } 142 *pos = p + 1 143 return v 144} 145 146// Decode `n` base64 chars into raw bytes. Returns bytes written on 147// success or -1 on invalid input. Tolerant of missing padding (B3). 148// Whitespace is NOT skipped. 149// 150// F-meta-4 refactor: position counter is a stack-local; no sys_mmap. 151func b64_decode(in_chars: *u8, n: i64, out: *u8) -> i64 { 152 var pos: i64 = 0 153 var out_pos: i64 = 0 154 while pos < n { 155 let s0: i64 = b64_grab(in_chars, n, &pos) 156 if s0 == -2 { return -1 } 157 if s0 < 0 { return out_pos } 158 let s1: i64 = b64_grab(in_chars, n, &pos) 159 if s1 == -2 { return -1 } 160 if s1 < 0 { return -1 } // single lonely char invalid 161 out[out_pos] = ((s0 << 2) | (s1 >> 4)) & 0xFF 162 out_pos = out_pos + 1 163 let s2: i64 = b64_grab(in_chars, n, &pos) 164 if s2 == -2 { return -1 } 165 if s2 < 0 { return out_pos } 166 out[out_pos] = ((s1 << 4) | (s2 >> 2)) & 0xFF 167 out_pos = out_pos + 1 168 let s3: i64 = b64_grab(in_chars, n, &pos) 169 if s3 == -2 { return -1 } 170 if s3 < 0 { return out_pos } 171 out[out_pos] = ((s2 << 6) | s3) & 0xFF 172 out_pos = out_pos + 1 173 } 174 return out_pos 175} 176 177// Self-test main() removed by F-meta-4 refactor (used sys_mmap; this 178// file is now syscall-free). Round-trip smoke lives in a separate 179// nx_base64_test.nx that imports a caller-chosen syscall layer.