nx_ws_client_gate.nx source
↩ module page · 150 lines · 7998 B
1// nx_ws_client_gate.nx -- THE GATE FOR THE RFC 6455 CLIENT HALF, 2026-09-03.
2//
3// SUBJECT: nx_ws_build_frame_masked / nx_ws_parse_frame_server / nx_ws_verify_accept in-process, AND their
4// interoperation with the incumbent nx_websocket.nx.
5//
6// THE TOOTH THAT CARRIES THE CLAIM IS T2, AND IT NEEDS NO FIXTURE OF MINE. A frame built by the new CLIENT
7// builder is handed to the estate's EXISTING SERVER PARSER, untouched, and must come back byte-for-byte.
8// That is real interop against code I did not write and cannot bend: if my masking were wrong in any bit,
9// the incumbent's unmask would produce different bytes and the tooth would fail.
10//
11// T5 IS THE REGRESSION GUARD. The incumbent must still REFUSE an unmasked frame exactly as before. This
12// file is a sibling, not a widening, and the video /signal lane depends on that rule holding.
13//
14// Teeth, in order:
15// T1 a masked frame sets the MASK bit and does NOT leave the payload in clear.
16// T2 INTEROP: client builder -> INCUMBENT server parser recovers the payload byte-for-byte.
17// T3 the reverse direction: incumbent unmasked builder -> new client parser recovers the payload.
18// T4 NEG-CONTROL: the client parser REFUSES a masked frame (a server must not mask).
19// T5 REGRESSION: the incumbent parser still REFUSES an unmasked frame -- unchanged by this file.
20// T6 a payload crossing the 126-byte boundary round-trips (the extended-length path, not just the short).
21// T7 NEG-CONTROL: an undersized output buffer REFUSES instead of writing past it.
22// T8 accept-key verification MATCHES for the key we sent, composing the incumbent's derivation.
23// T9 NEG-CONTROL: a wrong accept value FAILS verification -- without this, T8 could pass on a stub.
24// MEASURED 9/9 GREEN 2026-09-03: 39-byte masked frame round-tripped through the incumbent parser (opcode 1,
25// 33 payload), 308-byte extended-length frame likewise, both refusals firing at -5, buffer refusal at -3.
26// license_tier: ORIGINAL No hw writes (Rule 26). expect_exit: 0
27import "nx_syscalls_x86_64.nx"
28import "nx_gate_verdict.nx"
29import "nx_ws_client.nx"
30
31const WG_CAP: i64 = 4096
32const WG_SLOT: i64 = 8
33const WG_LONG: i64 = 300
34
35func wg_eq(a: *u8, b: *u8, n: i64) -> i64 {
36 var i: i64 = 0
37 while i < n {
38 if a[i] != b[i] { return 0 }
39 i = i + 1
40 }
41 return 1
42}
43
44func main(argc: i64, argv: *i64) -> i64 {
45 let ctr: *i64 = gv_ctr()
46 gv_head("nx_ws_client gate -- the RFC 6455 client half, proven against the estate's own server parser" as *u8)
47
48 let msg: *u8 = "{\"id\":1,\"method\":\"Page.navigate\"}" as *u8
49 var mlen: i64 = 0
50 while msg[mlen] != (0 as u8) { mlen = mlen + 1 }
51
52 let mask: *u8 = sys_mmap(WSC_MASK_BYTES) as *u8
53 mask[0] = 0x37 as u8
54 mask[1] = 0xfa as u8
55 mask[2] = 0x21 as u8
56 mask[3] = 0x3d as u8
57
58 let buf: *u8 = sys_mmap(WG_CAP) as *u8
59 let n: i64 = nx_ws_build_frame_masked(buf, WG_CAP, NX_WS_OPCODE_TEXT, msg, mlen, mask)
60 gv_puts(" [T1] masked frame bytes=" as *u8); gv_num(n); gv_puts(" b1=" as *u8); gv_num(buf[1] as i64); gv_puts("\n" as *u8)
61 var t1: i64 = 0
62 if n > 0 {
63 if (buf[1] & NX_WS_MASK_BIT) != 0 {
64 var differs: i64 = 0
65 var i: i64 = 0
66 while i < mlen {
67 if buf[2 + WSC_MASK_BYTES + i] != msg[i] { differs = 1 }
68 i = i + 1
69 }
70 if differs == 1 { t1 = 1 }
71 }
72 }
73 gv_check("a-masked-frame-sets-the-MASK-bit-and-does-not-leave-the-payload-in-clear" as *u8, t1, ctr)
74
75 let op: *i64 = sys_mmap(WG_SLOT) as *i64
76 let poff: *i64 = sys_mmap(WG_SLOT) as *i64
77 let plen: *i64 = sys_mmap(WG_SLOT) as *i64
78 let consumed: i64 = nx_ws_parse_frame_inplace(buf, n, op, poff, plen)
79 gv_puts(" [T2] incumbent parser consumed=" as *u8); gv_num(consumed); gv_puts(" opcode=" as *u8); gv_num(*op); gv_puts(" plen=" as *u8); gv_num(*plen); gv_puts("\n" as *u8)
80 var t2: i64 = 0
81 if consumed == n {
82 if *op == NX_WS_OPCODE_TEXT {
83 if *plen == mlen {
84 if wg_eq((buf as i64 + *poff) as *u8, msg, mlen) == 1 { t2 = 1 }
85 }
86 }
87 }
88 gv_check("INTEROP-a-frame-built-by-the-client-half-is-recovered-byte-for-byte-by-the-INCUMBENT-server-parser" as *u8, t2, ctr)
89
90 let sbuf: *u8 = sys_mmap(WG_CAP) as *u8
91 let sn: i64 = nx_ws_build_frame(sbuf, WG_CAP, NX_WS_OPCODE_TEXT, msg, mlen)
92 let sc: i64 = nx_ws_parse_frame_server(sbuf, sn, op, poff, plen)
93 gv_puts(" [T3] server->client consumed=" as *u8); gv_num(sc); gv_puts(" plen=" as *u8); gv_num(*plen); gv_puts("\n" as *u8)
94 var t3: i64 = 0
95 if sc == sn { if *plen == mlen { if wg_eq((sbuf as i64 + *poff) as *u8, msg, mlen) == 1 { t3 = 1 } } }
96 gv_check("the-reverse-direction-incumbent-unmasked-builder-to-new-client-parser-recovers-the-payload" as *u8, t3, ctr)
97
98 let r4: i64 = nx_ws_parse_frame_server(buf, n, op, poff, plen)
99 gv_puts(" [T4] client parser on a MASKED frame -> " as *u8); gv_num(r4); gv_puts("\n" as *u8)
100 gv_check("neg-control-the-client-parser-REFUSES-a-masked-frame (a conforming server never masks)" as *u8, (r4 < 0) as i64, ctr)
101
102 let r5: i64 = nx_ws_parse_frame_inplace(sbuf, sn, op, poff, plen)
103 gv_puts(" [T5] incumbent parser on an UNMASKED frame -> " as *u8); gv_num(r5); gv_puts("\n" as *u8)
104 gv_check("regression-the-incumbent-parser-still-refuses-an-unmasked-frame-unchanged-by-this-file" as *u8, (r5 < 0) as i64, ctr)
105
106 let big: *u8 = sys_mmap(WG_LONG) as *u8
107 var b: i64 = 0
108 while b < WG_LONG {
109 big[b] = (65 + (b % 26)) as u8
110 b = b + 1
111 }
112 let bbuf: *u8 = sys_mmap(WG_CAP) as *u8
113 let bn: i64 = nx_ws_build_frame_masked(bbuf, WG_CAP, NX_WS_OPCODE_TEXT, big, WG_LONG, mask)
114 let bc: i64 = nx_ws_parse_frame_inplace(bbuf, bn, op, poff, plen)
115 gv_puts(" [T6] 300-byte payload built=" as *u8); gv_num(bn); gv_puts(" consumed=" as *u8); gv_num(bc); gv_puts(" plen=" as *u8); gv_num(*plen); gv_puts("\n" as *u8)
116 var t6: i64 = 0
117 if bc == bn { if *plen == WG_LONG { if wg_eq((bbuf as i64 + *poff) as *u8, big, WG_LONG) == 1 { t6 = 1 } } }
118 gv_check("a-payload-crossing-the-126-byte-boundary-round-trips-through-the-extended-length-path" as *u8, t6, ctr)
119
120 let tiny: *u8 = sys_mmap(WG_CAP) as *u8
121 let r7: i64 = nx_ws_build_frame_masked(tiny, 4, NX_WS_OPCODE_TEXT, msg, mlen, mask)
122 gv_puts(" [T7] undersized buffer -> " as *u8); gv_num(r7); gv_puts("\n" as *u8)
123 gv_check("neg-control-an-undersized-output-buffer-REFUSES-instead-of-writing-past-it" as *u8, (r7 < 0) as i64, ctr)
124
125 let ckey: *u8 = "dGhlIHNhbXBsZSBub25jZQ==" as *u8
126 var klen: i64 = 0
127 while ckey[klen] != (0 as u8) { klen = klen + 1 }
128 let derived: *u8 = sys_mmap(WG_CAP) as *u8
129 let dv: i64 = nx_ws_accept_key(ckey, klen, derived)
130 let dn: i64 = WSC_ACCEPT_LEN
131 let scratch: *u8 = sys_mmap(WG_CAP) as *u8
132 let ok8: i64 = nx_ws_verify_accept(ckey, klen, derived, dn, scratch)
133 gv_puts(" [T8] accept_key verdict=" as *u8); gv_num(dv); gv_puts(" verify=" as *u8); gv_num(ok8); gv_puts("\n" as *u8)
134 var t8: i64 = 0
135 if dv == NX_WS_VERDICT_OK { if ok8 == 1 { t8 = 1 } }
136 gv_check("accept-key-verification-matches-for-the-key-we-sent (composing the incumbent derivation)" as *u8, t8, ctr)
137
138 let wrong: *u8 = sys_mmap(WG_CAP) as *u8
139 var w: i64 = 0
140 while w < dn {
141 wrong[w] = derived[w]
142 w = w + 1
143 }
144 wrong[0] = (derived[0] + 1) as u8
145 let ok9: i64 = nx_ws_verify_accept(ckey, klen, wrong, dn, scratch)
146 gv_puts(" [T9] one byte flipped -> verify=" as *u8); gv_num(ok9); gv_puts("\n" as *u8)
147 gv_check("neg-control-a-wrong-accept-value-FAILS-verification (without this T8 could pass on a stub)" as *u8, (ok9 == 0) as i64, ctr)
148
149 return gv_verdict("ws_client" as *u8, ctr, "the RFC 6455 client half proven by interop against the estate's own server parser in both directions, with the incumbent's rule unchanged and the accept-key check biting" as *u8)
150}