code wiki / (root) / nx_websocket_frame_test.nx

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}