nx_chat_demo_unicode_t59.nx source
↩ module page · 458 lines · 19797 B
1// nx_chat_demo_api.nx -- the SAFE public front for a testable persistent-chat UI backed by C1's store.
2// v2 (2026-08-16): does the append/fetch IN-PROCESS on the seg-store, NOT by forking nx_chat_store.
3// WHY: MEASURED via curl to /mcp -- a list (read) returned in 44ms, but a post that FORKED nx_chat_store
4// to write hung to the tools-daemon exec deadline (~14s) and returned empty (exit -5), because the
5// write-path grandchild orphaned under the daemon's capture. A read fork was fine; a write fork was not.
6// In-process removes the fork entirely: this organ takes the plane lock, appends, and releases in its own
7// process -- the exact code path nx_chat_store's gate proves, just linked instead of exec'd. Fast + no orphan.
8// SECURITY BOUNDARY unchanged: only post/list, rooms forced into demo_, inputs sanitized + capped, budgets
9// from C1's conf refuse loudly. Destructive verbs do not exist here. REFEREE: nx_chat_demo_api_gate.
10// DEBT (named, not a silent twin): the append/fetch logic is duplicated from nx_chat_store; the consolidation
11// is to extract nx_chat_store_lib that BOTH this front and the CLI import (comms.plan chatstore-lib-extract).
12// license_tier: ORIGINAL No hw writes (Rule 26).
13import "nx_syscalls.nx"
14import "nx_itoa_lib.nx"
15import "nx_store_seed_lib.nx"
16import "nx_seg_store.nx"
17import "nx_utf8.nx"
18const CDA_TEXT_BAD_BOUNDARY:i64=-1
19const CDA_TEXT_TOO_LONG:i64=-2
20const CDA_TEXT_BAD_UTF8:i64=-3
21
22const CDA_EXIT_OK: i64 = 0
23const CDA_EXIT_USAGE: i64 = 2
24const CDA_EXIT_REFUSED: i64 = 3
25const CDA_ROOM_MAX: i64 = 24
26const CDA_NAME_MAX: i64 = 24
27const CDA_TEXT_MAX: i64 = 280
28const CDA_FETCH_MAX: i64 = 200
29const CDA_PFX_CAP: i64 = 256
30const CDA_ROW_OVH: i64 = 128
31const CDA_WSLACK: i64 = 65536
32const CDA_CONF_CAP: i64 = 192
33const CDA_OUTCAP: i64 = 262144
34const CDA_NL: i64 = 10
35const CDA_PIPE: i64 = 124
36const CDA_KIND_TEXT: i64 = 1
37const CDA_KIND_SYS: i64 = 4
38const CHATCONF: *u8 = "knowledge/comms/chatstore.conf"
39const CSA_ERR_CONF: i64 = 0 - 3
40const CSA_ERR_BODY: i64 = 0 - 10
41const CSA_ERR_LOCK: i64 = 0 - 4
42const CSA_ERR_MSGS: i64 = 0 - 11
43const CSA_ERR_BYTES: i64 = 0 - 12
44const CSA_ERR_CORRUPT: i64 = 0 - 5
45
46func cda_w(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 }
47func cda_wn(s: *u8, n: i64) -> i64 { sys_write(1, s, n); return 0 }
48func cda_len(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } return n }
49func cda_eq(a: *u8, b: *u8) -> i64 {
50 var i: i64 = 0
51 while a[i] != (0 as u8) { if a[i] != b[i] { return 0 } i = i + 1 }
52 if b[i] != (0 as u8) { return 0 }
53 return 1
54}
55func cda_ident(src: *u8, dst: *u8, cap: i64) -> i64 {
56 var o: i64 = 0
57 var i: i64 = 0
58 while src[i] != (0 as u8) {
59 if o >= cap { break }
60 let c: i64 = src[i] as i64
61 if c >= 48 { if c <= 57 { dst[o] = c as u8; o = o + 1 } }
62 if c >= 97 { if c <= 122 { dst[o] = c as u8; o = o + 1 } }
63 if c >= 65 { if c <= 90 { dst[o] = (c + 32) as u8; o = o + 1 } }
64 if c == 95 { dst[o] = 95 as u8; o = o + 1 }
65 i = i + 1
66 }
67 dst[o] = 0 as u8
68 return o
69}
70// cap is payload bytes; callers reserve one additional terminator byte.
71func cda_text_legacy(src: *u8, dst: *u8, cap: i64) -> i64 {
72 var o: i64 = 0
73 var i: i64 = 0
74 while src[i] != (0 as u8) {
75 if o >= cap { break }
76 let c: i64 = src[i] as i64
77 if c >= 32 { if c <= 126 { dst[o] = c as u8; o = o + 1 } }
78 i = i + 1
79 }
80 dst[o] = 0 as u8
81 return o
82}
83func cda_text(src: *u8, dst: *u8, cap: i64) -> i64 {
84 if cap < 0 { return CDA_TEXT_BAD_BOUNDARY }
85 var n:i64=0
86 while n<=cap {
87 if src[n]==(0 as u8) { break }
88 n=n+1
89 }
90 if n>cap { dst[0]=0 as u8;return CDA_TEXT_TOO_LONG }
91 if utf8_validate(src,n)!=n { dst[0]=0 as u8;return CDA_TEXT_BAD_UTF8 }
92 var i:i64=0
93 while i<n { dst[i]=src[i];i=i+1 }
94 dst[n]=0 as u8
95 return n
96}
97func cda_demoroom(clean: *u8, out: *u8) -> i64 {
98 var o: i64 = 0
99 let pre: *u8 = "demo_" as *u8
100 while pre[o] != (0 as u8) { out[o] = pre[o]; o = o + 1 }
101 var i: i64 = 0
102 while clean[i] != (0 as u8) { out[o] = clean[i]; o = o + 1; i = i + 1 }
103 out[o] = 0 as u8
104 return o
105}
106func cda_jesc(out: *u8, o: i64, s: *u8, n: i64) -> i64 {
107 var i: i64 = 0
108 while i < n {
109 let c: i64 = s[i] as i64
110 if c == 34 { out[o] = 92 as u8; o = o + 1; out[o] = 34 as u8; o = o + 1 } else {
111 if c == 92 { out[o] = 92 as u8; o = o + 1; out[o] = 92 as u8; o = o + 1 } else {
112 if c < 32 { out[o]=92 as u8;out[o+1]=117 as u8;out[o+2]=48 as u8;out[o+3]=48 as u8;o=o+4;o=o+cda_hexe((s as i64+i) as *u8,1,(out as i64+o) as *u8) } else {
113 out[o] = c as u8; o = o + 1
114 } } }
115 i = i + 1
116 }
117 return o
118}
119func cda_hexe(src: *u8, n: i64, dst: *u8) -> i64 {
120 var i: i64 = 0
121 while i < n {
122 let b: i64 = src[i] as i64
123 let hi: i64 = b / 16
124 let lo: i64 = b % 16
125 if hi < 10 { dst[i*2] = (48 + hi) as u8 } else { dst[i*2] = (87 + hi) as u8 }
126 if lo < 10 { dst[i*2+1] = (48 + lo) as u8 } else { dst[i*2+1] = (87 + lo) as u8 }
127 i = i + 1
128 }
129 return n * 2
130}
131func cda_hexv(c: i64) -> i64 {
132 if c >= 48 { if c <= 57 { return c - 48 } }
133 if c >= 97 { if c <= 102 { return c - 87 } }
134 return 0 - 1
135}
136func cda_catn(d: *u8, o: i64, v: i64) -> i64 {
137 if v == 0 { d[o] = 48 as u8; return o + 1 }
138 let tmp: *u8 = sys_mmap(24)
139 var k: i64 = 0
140 var m: i64 = v
141 while m > 0 { tmp[k] = (48 + m % 10) as u8; m = m / 10; k = k + 1 }
142 var j: i64 = k - 1
143 while j >= 0 { d[o] = tmp[j]; o = o + 1; j = j - 1 }
144 return o
145}
146func cda_cat(d: *u8, o: i64, s: *u8) -> i64 { var i: i64 = 0; while s[i] != (0 as u8) { d[o] = s[i]; o = o + 1; i = i + 1 } return o }
147func cda_confs(key: *u8, out: *u8, cap: i64) -> i64 {
148 let ol: *i64 = sts_mm(16) as *i64
149 let b: *u8 = sys_read_file(CHATCONF, ol)
150 let n: i64 = ol[0]
151 if b as i64 == 0 { return 0 - 1 }
152 if n <= 0 { return 0 - 1 }
153 let kl: i64 = cda_len(key)
154 var i: i64 = 0
155 while i < n {
156 var j: i64 = 0
157 var hit: i64 = 1
158 while j < kl {
159 if i + j >= n { hit = 0; break }
160 if b[i+j] != key[j] { hit = 0; break }
161 j = j + 1
162 }
163 if hit == 1 { if i + kl >= n { hit = 0 } else { if b[i+kl] != (CDA_PIPE as u8) { hit = 0 } } }
164 if hit == 1 {
165 var v: i64 = i + kl + 1
166 var o: i64 = 0
167 while v < n { if b[v] == (CDA_NL as u8) { break } if o < cap - 1 { out[o] = b[v]; o = o + 1 } v = v + 1 }
168 out[o] = 0 as u8
169 return o
170 }
171 while i < n { if b[i] == (CDA_NL as u8) { break } i = i + 1 }
172 i = i + 1
173 }
174 return 0 - 1
175}
176func cda_confn(key: *u8) -> i64 {
177 let v: *u8 = sts_mm(CDA_CONF_CAP)
178 let l: i64 = cda_confs(key, v, CDA_CONF_CAP)
179 if l <= 0 { return 0 - 1 }
180 return sts_atoi(v, l)
181}
182func cda_prefix(room: *u8, out: *u8) -> i64 {
183 let rl: i64 = cda_confs("store_root" as *u8, out, CDA_PFX_CAP - 96)
184 if rl <= 0 { return 0 - 2 }
185 var o: i64 = rl
186 o = ss_cat(out, o, "chat_" as *u8)
187 o = ss_cat(out, o, room)
188 o = ss_cat(out, o, "-" as *u8)
189 out[o] = 0 as u8
190 return o
191}
192func cda_rowbuild(seq: i64, kind: i64, sender: *u8, body: *u8, blen: i64, out: *u8) -> i64 {
193 var o: i64 = ss_cat(out, 0, "m|" as *u8)
194 o = ss_catn(out, o, seq)
195 o = ss_cat(out, o, "|" as *u8)
196 o = ss_catn(out, o, sys_now_realtime_ms())
197 o = ss_cat(out, o, "|" as *u8)
198 o = ss_catn(out, o, kind)
199 o = ss_cat(out, o, "|" as *u8)
200 o = ss_cat(out, o, sender)
201 o = ss_cat(out, o, "|" as *u8)
202 o = o + cda_hexe(body, blen, (out as i64 + o) as *u8)
203 out[o] = 0 as u8
204 return o
205}
206func cda_delkey(seq: i64, out: *u8) -> i64 {
207 var o: i64 = ss_cat(out, 0, "del:" as *u8)
208 o = ss_catn(out, o, seq)
209 out[o] = 0 as u8
210 return o
211}
212func cda_put_key(prefix: *u8, key: *u8, val: *u8, vlen: i64) -> i64 {
213 let w: *i64 = ss_begin_cap(vlen + CDA_WSLACK)
214 if ss_add(w, 1, key, val, vlen) < 0 { return 0 - 1 }
215 if ss_commit_cas(prefix, w, ss_next_segid(prefix), SS_CAS_ANY) != 0 { return 0 - 1 }
216 return 0
217}
218func csa_append(room: *u8, sender: *u8, body: *u8, blen: i64) -> i64 {
219 let bmax: i64 = cda_confn("body_bytes_max" as *u8)
220 if bmax < 0 { return CSA_ERR_CONF }
221 let bcliff: i64 = cda_confn("room_bytes_cliff" as *u8)
222 if bcliff < 0 { return CSA_ERR_CONF }
223 let mcliff: i64 = cda_confn("room_msgs_cliff" as *u8)
224 if mcliff < 0 { return CSA_ERR_CONF }
225 if blen > bmax { return CSA_ERR_BODY }
226 let pfx: *u8 = sts_mm(CDA_PFX_CAP)
227 if cda_prefix(room, pfx) < 0 { return CSA_ERR_CONF }
228 let lk: i64 = sts_lock(pfx)
229 if lk < 0 { return CSA_ERR_LOCK }
230 let pq: *i64 = sts_mm(16) as *i64
231 let lq: *i64 = sts_mm(16) as *i64
232 var n: i64 = 0
233 if ss_get(pfx, "q:n" as *u8, pq, lq) == 1 {
234 n = sts_atoi(pq[0] as *u8, lq[0])
235 } else {
236 // auto-open with a DEFERRED commit too (no fsync): write q:0=sysrow, q:n=1, meta:bytes in one
237 // segment. The old path used sts_seed which fsyncs, and under disk pressure that stalled past the
238 // caller's deadline BEFORE the room was created, so every retry re-stalled. Deferred fixes it.
239 let ob: *u8 = "room-open" as *u8
240 let obl: i64 = 9
241 let srow: *u8 = sts_mm(CDA_ROW_OVH + 64 + obl * 2 + 8)
242 let so: i64 = cda_rowbuild(1, CDA_KIND_SYS, sender, ob, obl, srow)
243 let k0: *u8 = sts_mm(64)
244 sts_rowkey(0, k0)
245 let c1: *u8 = sts_mm(32)
246 let cl1: i64 = ss_catn(c1, 0, 1)
247 let m0: *u8 = sts_mm(32)
248 let ml0: i64 = ss_catn(m0, 0, obl)
249 let ow: *i64 = ss_begin_cap(so + CDA_WSLACK)
250 if ss_add(ow, 1, k0, srow, so) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT }
251 if ss_add(ow, 1, "q:n" as *u8, c1, cl1) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT }
252 if ss_add(ow, 1, "meta:bytes" as *u8, m0, ml0) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT }
253 if ss_commit_deferred(pfx, ow, ss_next_segid(pfx)) != 0 { sts_unlock(lk) return CSA_ERR_CORRUPT }
254 n = 1
255 }
256 if n >= mcliff { sts_unlock(lk) return CSA_ERR_MSGS }
257 var bts: i64 = 0
258 if ss_get(pfx, "meta:bytes" as *u8, pq, lq) == 1 { bts = sts_atoi(pq[0] as *u8, lq[0]) }
259 if bts + blen > bcliff { sts_unlock(lk) return CSA_ERR_BYTES }
260 let seq: i64 = n + 1
261 let row: *u8 = sts_mm(CDA_ROW_OVH + 64 + blen * 2 + 8)
262 let rl: i64 = cda_rowbuild(seq, CDA_KIND_TEXT, sender, body, blen, row)
263 let key: *u8 = sts_mm(64)
264 sts_rowkey(n, key)
265 let cb: *u8 = sts_mm(32)
266 let cl: i64 = ss_catn(cb, 0, seq)
267 let mb: *u8 = sts_mm(32)
268 let ml: i64 = ss_catn(mb, 0, bts + blen)
269 let w: *i64 = ss_begin_cap(rl + CDA_WSLACK)
270 if ss_add(w, 1, key, row, rl) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT }
271 if ss_add(w, 1, "q:n" as *u8, cb, cl) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT }
272 if ss_add(w, 1, "meta:bytes" as *u8, mb, ml) < 0 { sts_unlock(lk) return CSA_ERR_CORRUPT }
273 // DEFERRED COMMIT (perf): write the segment + manifest to the page cache and return -- the message is
274 // visible to the next read immediately and survives process death; the fsync to disk happens on the
275 // store's background sync. We hold the plane flock across the whole read-modify-write, so no CAS is
276 // needed (nothing else can commit between our ss_get and this commit). This keeps a post FAST even
277 // when the box's disk is under pressure, instead of stalling on a synchronous fsync.
278 let rc: i64 = ss_commit_deferred(pfx, w, ss_next_segid(pfx))
279 sts_unlock(lk)
280 if rc != 0 { return CSA_ERR_CORRUPT }
281 return seq
282}
283func csa_fetch(room: *u8, since: i64, maxn: i64, outbuf: *u8, outcap: i64) -> i64 {
284 let pfx: *u8 = sts_mm(CDA_PFX_CAP)
285 if cda_prefix(room, pfx) < 0 { return 0 - 1 }
286 let pq: *i64 = sts_mm(16) as *i64
287 let lq: *i64 = sts_mm(16) as *i64
288 if ss_get(pfx, "q:n" as *u8, pq, lq) != 1 { return 0 - 1 }
289 let n: i64 = sts_atoi(pq[0] as *u8, lq[0])
290 let h: *i64 = ss_open_cached(pfx)
291 if h as i64 == 0 { return 0 - 5 }
292 let key: *u8 = sts_mm(64)
293 let dk: *u8 = sts_mm(64)
294 let pr2: *i64 = sts_mm(16) as *i64
295 let lr2: *i64 = sts_mm(16) as *i64
296 var o: i64 = 0
297 var returned: i64 = 0
298 var i: i64 = since
299 while i < n {
300 if returned >= maxn { break }
301 sts_rowkey(i, key)
302 if ss_hget(h, key, pq, lq) != 1 { return 0 - 5 }
303 let rv: *u8 = pq[0] as *u8
304 let rl: i64 = lq[0]
305 cda_delkey(i + 1, dk)
306 if ss_hget(h, dk, pr2, lr2) == 1 { } else {
307 if o + rl + 1 < outcap {
308 var k: i64 = 0
309 while k < rl { outbuf[o] = rv[k]; o = o + 1; k = k + 1 }
310 outbuf[o] = CDA_NL as u8
311 o = o + 1
312 returned = returned + 1
313 }
314 }
315 i = i + 1
316 }
317 return o
318}
319func cda_post_mode(room_in: *u8, name_in: *u8, text_in: *u8, version:i64) -> i64 {
320 let rclean: *u8 = sts_mm(64)
321 if cda_ident(room_in, rclean, CDA_ROOM_MAX) < 1 { cda_w("{\"ok\":0,\"err\":\"bad-room\"}\n" as *u8) return CDA_EXIT_REFUSED }
322 let nclean: *u8 = sts_mm(64)
323 if cda_ident(name_in, nclean, CDA_NAME_MAX) < 1 { cda_w("{\"ok\":0,\"err\":\"bad-name\"}\n" as *u8) return CDA_EXIT_REFUSED }
324 let tclean: *u8 = sts_mm(CDA_TEXT_MAX + 8)
325 var tlen:i64=0
326 if version==2 {tlen=cda_text(text_in,tclean,CDA_TEXT_MAX)} else {tlen=cda_text_legacy(text_in,tclean,CDA_TEXT_MAX)}
327 if tlen == CDA_TEXT_TOO_LONG { cda_w("{\"ok\":0,\"err\":\"text-too-long\"}\n" as *u8);return CDA_EXIT_REFUSED }
328 if tlen == CDA_TEXT_BAD_UTF8 { cda_w("{\"ok\":0,\"err\":\"invalid-utf8\"}\n" as *u8);return CDA_EXIT_REFUSED }
329 if tlen < 1 { cda_w("{\"ok\":0,\"err\":\"empty-text\"}\n" as *u8) return CDA_EXIT_REFUSED }
330 let room: *u8 = sts_mm(64)
331 cda_demoroom(rclean, room)
332 let seq: i64 = csa_append(room, nclean, tclean, tlen)
333 if seq < 0 {
334 if seq == CSA_ERR_MSGS { cda_w("{\"ok\":0,\"err\":\"room-full-messages\"}\n" as *u8) return CDA_EXIT_REFUSED }
335 if seq == CSA_ERR_BYTES { cda_w("{\"ok\":0,\"err\":\"room-full-bytes\"}\n" as *u8) return CDA_EXIT_REFUSED }
336 if seq == CSA_ERR_BODY { cda_w("{\"ok\":0,\"err\":\"body-too-big\"}\n" as *u8) return CDA_EXIT_REFUSED }
337 if seq == CSA_ERR_LOCK { cda_w("{\"ok\":0,\"err\":\"busy-retry\"}\n" as *u8) return CDA_EXIT_REFUSED }
338 cda_w("{\"ok\":0,\"err\":\"append-failed\"}\n" as *u8)
339 return CDA_EXIT_REFUSED
340 }
341 let jb: *u8 = sts_mm(256)
342 var o: i64 = cda_cat(jb, 0, "{\"ok\":1,\"room\":\"" as *u8)
343 o = cda_cat(jb, o, room)
344 o = cda_cat(jb, o, "\",\"seq\":" as *u8)
345 o = cda_catn(jb, o, seq)
346 o = cda_cat(jb, o, "}\n" as *u8)
347 cda_wn(jb, o)
348 return CDA_EXIT_OK
349}
350func cda_post(room_in:*u8,name_in:*u8,text_in:*u8)->i64 {return cda_post_mode(room_in,name_in,text_in,1)}
351func cda_post_v2(room_in:*u8,name_in:*u8,text_in:*u8)->i64 {return cda_post_mode(room_in,name_in,text_in,2)}
352func cda_atoi(b: *u8, n: i64) -> i64 { var v: i64 = 0; var i: i64 = 0; while i < n { let c: i64 = b[i] as i64; if c < 48 { break } if c > 57 { break } v = v * 10 + (c - 48); i = i + 1 } return v }
353func cda_list(room_in: *u8, since_in: *u8) -> i64 {
354 let rclean: *u8 = sts_mm(64)
355 if cda_ident(room_in, rclean, CDA_ROOM_MAX) < 1 { cda_w("{\"ok\":0,\"err\":\"bad-room\"}\n" as *u8) return CDA_EXIT_REFUSED }
356 let room: *u8 = sts_mm(64)
357 cda_demoroom(rclean, room)
358 var since: i64 = 0
359 if since_in as i64 != 0 { since = cda_atoi(since_in, cda_len(since_in)) }
360 let raw: *u8 = sts_mm(CDA_OUTCAP)
361 let rn: i64 = csa_fetch(room, since, CDA_FETCH_MAX, raw, CDA_OUTCAP)
362 let jb: *u8 = sts_mm(CDA_OUTCAP)
363 var o: i64 = cda_cat(jb, 0, "{\"ok\":1,\"room\":\"" as *u8)
364 o = cda_cat(jb, o, room)
365 o = cda_cat(jb, o, "\",\"msgs\":[" as *u8)
366 var total: i64 = 0
367 if rn > 0 {
368 var i: i64 = 0
369 var first: i64 = 1
370 while i < rn {
371 var ls: i64 = i
372 while i < rn { if raw[i] == (CDA_NL as u8) { break } i = i + 1 }
373 let le: i64 = i
374 i = i + 1
375 if le - ls > 4 { if raw[ls] == (109 as u8) { if raw[ls+1] == (CDA_PIPE as u8) {
376 var p: i64 = ls + 2
377 var seq: i64 = 0
378 while p < le { let c: i64 = raw[p] as i64; if c == CDA_PIPE { break } seq = seq * 10 + (c - 48); p = p + 1 }
379 p = p + 1
380 var ep: i64 = 0
381 while p < le { let c2: i64 = raw[p] as i64; if c2 == CDA_PIPE { break } ep = ep * 10 + (c2 - 48); p = p + 1 }
382 p = p + 1
383 var kind: i64 = 0
384 while p < le { let c3: i64 = raw[p] as i64; if c3 == CDA_PIPE { break } kind = kind * 10 + (c3 - 48); p = p + 1 }
385 p = p + 1
386 let snds: i64 = p
387 while p < le { if raw[p] == (CDA_PIPE as u8) { break } p = p + 1 }
388 let snde: i64 = p
389 p = p + 1
390 let hxs: i64 = p
391 if kind == CDA_KIND_TEXT {
392 let hxlen: i64 = le - hxs
393 let bodyb: *u8 = sts_mm(hxlen / 2 + 4)
394 var bo: i64 = 0
395 var hh: i64 = hxs
396 var okhex: i64 = 1
397 while hh + 1 < le {
398 let hi: i64 = cda_hexv(raw[hh] as i64)
399 let lo: i64 = cda_hexv(raw[hh+1] as i64)
400 if hi < 0 { okhex = 0; break }
401 if lo < 0 { okhex = 0; break }
402 bodyb[bo] = (hi * 16 + lo) as u8
403 bo = bo + 1
404 hh = hh + 2
405 }
406 if okhex == 1 {
407 if first == 0 { jb[o] = 44 as u8; o = o + 1 }
408 first = 0
409 o = cda_cat(jb, o, "{\"seq\":" as *u8)
410 o = cda_catn(jb, o, seq)
411 o = cda_cat(jb, o, ",\"t\":" as *u8)
412 o = cda_catn(jb, o, ep)
413 o = cda_cat(jb, o, ",\"from\":\"" as *u8)
414 o = cda_jesc(jb, o, (raw as i64 + snds) as *u8, snde - snds)
415 o = cda_cat(jb, o, "\",\"text\":\"" as *u8)
416 o = cda_jesc(jb, o, bodyb, bo)
417 o = cda_cat(jb, o, "\"}" as *u8)
418 total = total + 1
419 }
420 }
421 } } }
422 }
423 }
424 o = cda_cat(jb, o, "],\"total\":" as *u8)
425 o = cda_catn(jb, o, total)
426 o = cda_cat(jb, o, "}\n" as *u8)
427 cda_wn(jb, o)
428 return CDA_EXIT_OK
429}
430func cda_usage() -> i64 {
431 cda_w("{\"ok\":0,\"err\":\"usage: post <room> <name> <text> | list <room> [since]\"}\n" as *u8)
432 return CDA_EXIT_USAGE
433}
434func main(argc: i64, argv: *i64) -> i64 {
435 if argc < 2 { let u: i64 = cda_usage() sys_exit(u) return u }
436 let v: *u8 = argv[1] as *u8
437 if cda_eq(v, "post-v2" as *u8) == 1 {
438 if argc < 5 { return cda_usage() }
439 return cda_post_v2(argv[2] as *u8,argv[3] as *u8,argv[4] as *u8)
440 }
441 if cda_eq(v, "post" as *u8) == 1 {
442 if argc < 5 { let u: i64 = cda_usage() sys_exit(u) return u }
443 let r: i64 = cda_post(argv[2] as *u8, argv[3] as *u8, argv[4] as *u8)
444 sys_exit(r)
445 return r
446 }
447 if cda_eq(v, "list" as *u8) == 1 {
448 if argc < 3 { let u: i64 = cda_usage() sys_exit(u) return u }
449 var since: *u8 = 0 as *u8
450 if argc > 3 { since = argv[3] as *u8 }
451 let r: i64 = cda_list(argv[2] as *u8, since)
452 sys_exit(r)
453 return r
454 }
455 let u2: i64 = cda_usage()
456 sys_exit(u2)
457 return u2
458}