nx_websocket_frame_test.nx source
↩ module page · 140 lines · 6403 B
1// nx_websocket_frame_test.nx -- KAT for nx_websocket_frame.
2//
3// Audits the existing 255-LOC frame codec. The module shipped with
4// inline main() assertions but no formal test + smoke wrapper, so
5// any latent bug would be silent (same class as the nx_sort_insertion
6// canonical-without-KAT bug closed 2026-05-20). This test promotes
7// the ad-hoc main() invariants to a regression gate AND adds the
8// RFC 6455 §5.7-style canonical wire vectors that prove
9// browser-compatibility.
10//
11// Test vectors (RFC 6455 §5.7):
12// T1 Single-frame unmasked text "Hello": 0x81 0x05 H e l l o
13// T2 Single-frame masked client text "Hello" with key 0x37FA213D
14// T3 Single binary unmasked with payload_len 126 (extended 16-bit)
15// T4 Single binary unmasked with payload_len 65536 (extended 64-bit)
16// T5 Ping/pong/close opcodes round-trip through parse+build
17// T6 Self-inverse mask XOR (mask twice = identity)
18//
19// expect_exit: 0
20// license_tier: ORIGINAL
21
22import "nx_syscalls.nx"
23import "nx_websocket_frame.nx"
24
25func main() -> i64 {
26 // ---- T1: RFC 6455 §5.7 single-frame unmasked "Hello" ----
27 let raw1: *u8 = sys_mmap(32)
28 raw1[0] = 0x81 // FIN + opcode=1 (text)
29 raw1[1] = 0x05 // mask=0, len=5
30 raw1[2] = 0x48 // 'H'
31 raw1[3] = 0x65 // 'e'
32 raw1[4] = 0x6C // 'l'
33 raw1[5] = 0x6C // 'l'
34 raw1[6] = 0x6F // 'o'
35 let f1_raw: *u8 = sys_mmap(128)
36 let f1: *WsFrame = f1_raw as *WsFrame
37 if ws_parse_frame(raw1, 7, 0, f1) != 0 { return 1 }
38 if f1.fin != 1 { return 2 }
39 if f1.opcode != WS_OP_TEXT { return 3 }
40 if f1.masked != 0 { return 4 }
41 if f1.payload_len != 5 { return 5 }
42 if f1.payload_off != 2 { return 6 }
43 if f1.frame_len != 7 { return 7 }
44
45 // ---- T2: RFC 6455 §5.7 single-frame masked client "Hello" ----
46 // Wire bytes per RFC: 0x81 0x85 0x37 0xFA 0x21 0x3D 0x7F 0x9F 0x4D 0x51 0x58
47 // Masked "Hello" = bytes 0x7F 0x9F 0x4D 0x51 0x58
48 let raw2: *u8 = sys_mmap(32)
49 raw2[0] = 0x81
50 raw2[1] = 0x85 // mask=1, len=5
51 raw2[2] = 0x37; raw2[3] = 0xFA; raw2[4] = 0x21; raw2[5] = 0x3D // mask key
52 raw2[6] = 0x7F; raw2[7] = 0x9F; raw2[8] = 0x4D; raw2[9] = 0x51; raw2[10] = 0x58
53 let f2_raw: *u8 = sys_mmap(128)
54 let f2: *WsFrame = f2_raw as *WsFrame
55 if ws_parse_frame(raw2, 11, 0, f2) != 0 { return 10 }
56 if f2.fin != 1 { return 11 }
57 if f2.opcode != WS_OP_TEXT { return 12 }
58 if f2.masked != 1 { return 13 }
59 if f2.payload_len != 5 { return 14 }
60 if f2.payload_off != 6 { return 15 }
61 if f2.frame_len != 11 { return 16 }
62 // Reconstructed mask key in host-byte order: 0x37FA213D
63 if f2.mask_key != 0x37FA213D { return 17 }
64 // Unmask in place and verify the payload becomes "Hello"
65 ws_apply_mask(raw2, f2.payload_off, f2.payload_len, f2.mask_key)
66 if raw2[6] != 0x48 { return 18 } // 'H'
67 if raw2[7] != 0x65 { return 19 } // 'e'
68 if raw2[8] != 0x6C { return 20 } // 'l'
69 if raw2[9] != 0x6C { return 21 } // 'l'
70 if raw2[10] != 0x6F { return 22 } // 'o'
71
72 // ---- T3: build header with 16-bit extended length (1000 bytes) ----
73 let out3: *u8 = sys_mmap(64)
74 let h3: i64 = ws_build_header(out3, 64, 1, WS_OP_BINARY, 0, 0, 1000)
75 if h3 != 4 { return 30 } // 2-byte basic + 2-byte ext
76 if out3[0] != 0x82 { return 31 } // FIN + opcode=2 (binary)
77 if (out3[1] & 0xFF) != 126 { return 32 } // sentinel for 16-bit
78 if (out3[2] & 0xFF) != 0x03 { return 33 } // 1000 >> 8 = 3
79 if (out3[3] & 0xFF) != 0xE8 { return 34 } // 1000 & 0xFF = 232
80 // Round-trip: parse what we built (+ a dummy payload byte for frame completeness)
81 let raw3: *u8 = sys_mmap(2048)
82 var i: i64 = 0
83 while i < 4 { raw3[i] = out3[i]; i = i + 1 }
84 i = 0
85 while i < 1000 { raw3[4 + i] = (i & 0xFF) as u8; i = i + 1 }
86 let f3_raw: *u8 = sys_mmap(128)
87 let f3: *WsFrame = f3_raw as *WsFrame
88 if ws_parse_frame(raw3, 1004, 0, f3) != 0 { return 35 }
89 if f3.payload_len != 1000 { return 36 }
90 if f3.payload_off != 4 { return 37 }
91 if f3.frame_len != 1004 { return 38 }
92
93 // ---- T4: build header with 64-bit extended length (65536 bytes) ----
94 let out4: *u8 = sys_mmap(64)
95 let h4: i64 = ws_build_header(out4, 64, 1, WS_OP_BINARY, 0, 0, 65536)
96 if h4 != 10 { return 40 } // 2-byte basic + 8-byte ext
97 if (out4[1] & 0xFF) != 127 { return 41 } // sentinel for 64-bit
98 // 65536 = 0x0000000000010000
99 if (out4[2] & 0xFF) != 0x00 { return 42 }
100 if (out4[3] & 0xFF) != 0x00 { return 43 }
101 if (out4[4] & 0xFF) != 0x00 { return 44 }
102 if (out4[5] & 0xFF) != 0x00 { return 45 }
103 if (out4[6] & 0xFF) != 0x00 { return 46 }
104 if (out4[7] & 0xFF) != 0x01 { return 47 }
105 if (out4[8] & 0xFF) != 0x00 { return 48 }
106 if (out4[9] & 0xFF) != 0x00 { return 49 }
107
108 // ---- T5: control opcodes round-trip (PING / PONG / CLOSE) ----
109 let out5: *u8 = sys_mmap(16)
110 if ws_build_header(out5, 16, 1, WS_OP_PING, 0, 0, 0) != 2 { return 50 }
111 if (out5[0] & 0xFF) != 0x89 { return 51 } // FIN + opcode 0x9
112 if ws_build_header(out5, 16, 1, WS_OP_PONG, 0, 0, 0) != 2 { return 52 }
113 if (out5[0] & 0xFF) != 0x8A { return 53 } // FIN + opcode 0xA
114 if ws_build_header(out5, 16, 1, WS_OP_CLOSE, 0, 0, 0) != 2 { return 54 }
115 if (out5[0] & 0xFF) != 0x88 { return 55 } // FIN + opcode 0x8
116
117 // ---- T6: self-inverse mask XOR (mask twice = identity) ----
118 let pl: *u8 = sys_mmap(16)
119 pl[0] = 0xDE; pl[1] = 0xAD; pl[2] = 0xBE; pl[3] = 0xEF
120 pl[4] = 0x01; pl[5] = 0x02; pl[6] = 0x03; pl[7] = 0x04
121 let key: i64 = 0xCAFEBABE
122 ws_apply_mask(pl, 0, 8, key)
123 ws_apply_mask(pl, 0, 8, key) // self-inverse
124 if pl[0] != 0xDE as u8 { return 60 }
125 if pl[3] != 0xEF as u8 { return 61 }
126 if pl[7] != 0x04 as u8 { return 62 }
127
128 // ---- T7: short-input handling: ws_parse_frame returns WS_ERR_SHORT ----
129 let tiny: *u8 = sys_mmap(4)
130 tiny[0] = 0x81 // header byte alone -- caller must read more
131 let ft_raw: *u8 = sys_mmap(128)
132 let ft: *WsFrame = ft_raw as *WsFrame
133 if ws_parse_frame(tiny, 1, 0, ft) != WS_ERR_SHORT { return 70 }
134
135 // "PASS\n"
136 let ok: *u8 = sys_mmap(8)
137 ok[0]=80; ok[1]=65; ok[2]=83; ok[3]=83; ok[4]=10
138 sys_write(1, ok, 5)
139 return 0
140}