code wiki / _hdl_build / nx_b64_cli.nx
nx_b64_cli.nx source
↩ module page · 101 lines · 5173 B
1// nx_b64_cli.nx -- base64 a file to stdout (and optionally to a file), and DECODE a base64 file
2// back to bytes. WHY THIS EXISTS: runtime/nx_base64.nx carries a complete b64_encode/b64_decode
3// pair and NO main, so the capability could not be INVOKED. The encode half shipped 2026-08-06;
4// the DECODE half stayed uninvokable -- which made base64 a one-way transport: bytes could leave
5// the estate as text but text could not become bytes again, so binary INGEST (the rigged-donor
6// FBX lane, 2026-08-23) had no sovereign door. This adds the missing verb by composing the SAME
7// lib -- one codec, two verbs.
8//
9// BUFFER-CAP LAW (operator 2026-08-14: a ceiling that has to be guessed is a defect generator):
10// the old BC_MAX_IN 8 MiB / BC_MAX_OUT 12 MiB REFUSED any file over 8 MiB -- a 73.6 MB donor FBX
11// (98 MB as base64) could never pass. For FILE work there is no guess to make: sys_read_file
12// sizes its buffer from the file, and the OUTPUT bound is ARITHMETIC from the input length
13// (encode: 4*ceil(n/3) + newline; decode: 3*n/4). Both derived, both announced, no cap remains.
14// usage: nx_b64_cli <infile> [outfile] encode (unchanged contract)
15// nx_b64_cli dec <in.b64> <outfile> decode; announces bytes_in/bytes_out
16// exit 0 ok, 1 usage, 2 unreadable/empty, 3 write-failed
17// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
18import "nx_syscalls.nx"
19import "nx_base64.nx"
20
21const BC_ENC_UNIT_IN: i64 = 3 // encode consumes 3 bytes...
22const BC_ENC_UNIT_OUT: i64 = 4 // ...and emits 4 chars; decode is the inverse
23const BC_TAIL_SLACK: i64 = 8 // newline + NUL + rounding of the final group
24const BC_VERB_DEC: i64 = 100 // 'd'
25const BC_EXIT_OK: i64 = 0
26const BC_EXIT_USAGE: i64 = 1
27const BC_EXIT_UNREADABLE: i64 = 2
28const BC_EXIT_WRITEFAIL: i64 = 3
29
30func bc_puts(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
31func bc_putn(v: i64) -> i64 {
32 if v == 0 { sys_write(1, "0" as *u8, 1); return 0 }
33 var m: i64 = v
34 if m < 0 { sys_write(1, "-" as *u8, 1); m = 0 - m }
35 let d: *u8 = sys_mmap(24)
36 var k: i64 = 0
37 while m > 0 { d[k] = (48 + (m % 10)) as u8; m = m / 10; k = k + 1 }
38 let o: *u8 = sys_mmap(24)
39 var w: i64 = 0
40 while w < k { o[w] = d[k - 1 - w]; w = w + 1 }
41 sys_write(1, o, k)
42 return 0
43}
44func bc_eq(a: *u8, b: *u8) -> i64 {
45 var i: i64 = 0
46 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
47 if b[i] != (0 as u8) { return 0 }
48 return 1
49}
50
51func main(argc: i64, argv: *i64) -> i64 {
52 if argc < 2 { bc_puts("usage: nx_b64_cli <infile> [outfile] | dec <in.b64> <outfile>\n" as *u8); return BC_EXIT_USAGE }
53
54 if bc_eq(argv[1] as *u8, "dec" as *u8) == 1 {
55 if argc < 4 { bc_puts("usage: nx_b64_cli dec <in.b64> <outfile>\n" as *u8); return BC_EXIT_USAGE }
56 let lb: *i64 = sys_mmap(16) as *i64
57 let buf: *u8 = sys_read_file(argv[2] as *u8, lb)
58 if (buf as i64) == 0 { bc_puts("REJECT: cannot read input\n" as *u8); return BC_EXIT_UNREADABLE }
59 var n: i64 = lb[0]
60 if n <= 0 { bc_puts("REJECT: empty input\n" as *u8); return BC_EXIT_UNREADABLE }
61 // tolerate a trailing newline from the encode verb / text transports
62 while n > 0 {
63 let c: i64 = (buf[n - 1] & 0xff) as i64
64 if c == 10 { n = n - 1 } else { if c == 13 { n = n - 1 } else { break } }
65 }
66 // DERIVED output bound: decode emits 3 bytes per 4 chars, never more
67 let ocap: i64 = (n / BC_ENC_UNIT_OUT) * BC_ENC_UNIT_IN + BC_TAIL_SLACK
68 let out: *u8 = sys_mmap(ocap)
69 let dn: i64 = b64_decode(buf, n, out)
70 if dn <= 0 { bc_puts("REJECT: not decodable base64\n" as *u8); return BC_EXIT_UNREADABLE }
71 let fd: i64 = sys_openat_wr(argv[3] as *u8, MODE_0644)
72 if fd < 0 { bc_puts("REJECT: cannot open outfile\n" as *u8); return BC_EXIT_WRITEFAIL }
73 let wr: i64 = sys_write(fd, out, dn)
74 sys_close(fd)
75 bc_puts("B64-DEC bytes_in=" as *u8); bc_putn(n)
76 bc_puts(" bytes_out=" as *u8); bc_putn(dn)
77 bc_puts(" wrote=" as *u8); bc_putn(wr)
78 bc_puts("\n" as *u8)
79 if wr != dn { bc_puts("REJECT: short write\n" as *u8); return BC_EXIT_WRITEFAIL }
80 return BC_EXIT_OK
81 }
82
83 let path: *u8 = argv[1] as *u8
84 let lb2: *i64 = sys_mmap(16) as *i64
85 let buf2: *u8 = sys_read_file(path, lb2)
86 if (buf2 as i64) == 0 { bc_puts("REJECT: cannot read input\n" as *u8); return BC_EXIT_UNREADABLE }
87 let n2: i64 = lb2[0]
88 if n2 <= 0 { bc_puts("REJECT: empty input\n" as *u8); return BC_EXIT_UNREADABLE }
89 // DERIVED output bound: encode emits 4 chars per 3 bytes plus the trailing newline
90 let ecap: i64 = ((n2 + BC_ENC_UNIT_IN - 1) / BC_ENC_UNIT_IN) * BC_ENC_UNIT_OUT + BC_TAIL_SLACK
91 let out2: *u8 = sys_mmap(ecap)
92 let en: i64 = b64_encode(buf2, n2, out2)
93 sys_write(1, out2, en)
94 sys_write(1, "\n" as *u8, 1)
95 if argc >= 3 {
96 let opath: *u8 = argv[2] as *u8
97 let fd2: i64 = sys_openat_wr(opath, MODE_0644)
98 if fd2 >= 0 { sys_write(fd2, out2, en); sys_close(fd2) }
99 }
100 return BC_EXIT_OK
101}