code wiki / (root) / websocket_frame.nx

websocket_frame.nx source

↩ module page · 249 lines · 8878 B

1// websocket_frame.nx -- RFC 6455 WebSocket frame parser + builder. 2// 3// Parses and emits individual frames. The handshake (HTTP 4// Upgrade + Sec-WebSocket-Key + Sec-WebSocket-Accept) goes in a 5// separate websocket_handshake.nx that will build on sha1.nx + 6// base64.nx + this module. 7// 8// Frame layout (RFC 6455 ยง5.2): 9// 10// 0 1 2 3 11// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 12// +-+-+-+-+-------+-+-------------+-------------------------------+ 13// |F|R|R|R| opcode|M| Payload len | Extended payload length | 14// |I|S|S|S| (4) |A| (7) | (16/64) | 15// |N|V|V|V| |S| | (if payload len==126/127) | 16// | |1|2|3| |K| | | 17// +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + 18// | Extended payload length continued, if payload len == 127 | 19// + - - - - - - - - - - - - - - - +-------------------------------+ 20// | |Masking-key, if MASK set to 1 | 21// +-------------------------------+-------------------------------+ 22// | Masking-key (continued) | Payload Data | 23// +-------------------------------- - - - - - - - - - - - - - - - + 24// 25// Opcodes: 26// 0x0 continuation frame 27// 0x1 text frame (UTF-8) 28// 0x2 binary frame 29// 0x8 close 30// 0x9 ping 31// 0xA pong 32// 33// Invariants: 34// WS1 Client -> server frames MUST be masked (RFC). We set 35// this flag on encode per the `masked` arg; callers MUST 36// supply a random 4-byte mask when sending from a client. 37// WS2 Server -> client frames MUST NOT be masked. 38// WS3 Payload length encoding: 7-bit inline (0..125), or 39// 126 + 16-bit extended, or 127 + 64-bit extended. 40// WS4 Masking is reversible -- apply mask twice = identity. 41 42import "syscalls.nx" 43 44const WS_ERR_SHORT: i64 = -1 45const WS_ERR_LONG: i64 = -2 46 47const WS_OP_CONT: i64 = 0x0 48const WS_OP_TEXT: i64 = 0x1 49const WS_OP_BINARY: i64 = 0x2 50const WS_OP_CLOSE: i64 = 0x8 51const WS_OP_PING: i64 = 0x9 52const WS_OP_PONG: i64 = 0xA 53 54struct WsFrame { 55 fin: i64, 56 rsv1: i64, 57 rsv2: i64, 58 rsv3: i64, 59 opcode: i64, 60 masked: i64, 61 mask_key: i64, // 32-bit mask (host byte order) 62 payload_off: i64, // offset into original buffer 63 payload_len: i64, 64 // Total byte length of this frame in the buffer (header + payload) 65 // so caller can advance to the next frame. 66 frame_len: i64, 67} 68 69// Parse one frame starting at buf[off]. Returns 0 on success, 70// WS_ERR_SHORT if caller needs to read more bytes. 71func ws_parse_frame(buf: *u8, n: i64, off: i64, f: *WsFrame) -> i64 { 72 if n < off + 2 { return WS_ERR_SHORT } 73 let b0: i64 = buf[off] 74 let b1: i64 = buf[off + 1] 75 76 f.fin = (b0 >> 7) & 0x1 77 f.rsv1 = (b0 >> 6) & 0x1 78 f.rsv2 = (b0 >> 5) & 0x1 79 f.rsv3 = (b0 >> 4) & 0x1 80 f.opcode = b0 & 0xF 81 f.masked = (b1 >> 7) & 0x1 82 83 let len7: i64 = b1 & 0x7F 84 var cur: i64 = off + 2 85 var payload_len: i64 = len7 86 87 if len7 == 126 { 88 if n < cur + 2 { return WS_ERR_SHORT } 89 payload_len = (buf[cur] << 8) | buf[cur + 1] 90 cur = cur + 2 91 } 92 if len7 == 127 { 93 if n < cur + 8 { return WS_ERR_SHORT } 94 // 64-bit BE length. We assemble high-to-low. 95 let b2: i64 = buf[cur] 96 let b3: i64 = buf[cur + 1] 97 let b4: i64 = buf[cur + 2] 98 let b5: i64 = buf[cur + 3] 99 let b6: i64 = buf[cur + 4] 100 let b7: i64 = buf[cur + 5] 101 let b8: i64 = buf[cur + 6] 102 let b9: i64 = buf[cur + 7] 103 payload_len = (b2 << 56) | (b3 << 48) | (b4 << 40) | (b5 << 32) 104 | (b6 << 24) | (b7 << 16) | (b8 << 8) | b9 105 cur = cur + 8 106 } 107 f.payload_len = payload_len 108 109 if f.masked == 1 { 110 if n < cur + 4 { return WS_ERR_SHORT } 111 let m0: i64 = buf[cur] 112 let m1: i64 = buf[cur + 1] 113 let m2: i64 = buf[cur + 2] 114 let m3: i64 = buf[cur + 3] 115 f.mask_key = (m0 << 24) | (m1 << 16) | (m2 << 8) | m3 116 cur = cur + 4 117 } else { 118 f.mask_key = 0 119 } 120 121 f.payload_off = cur 122 f.frame_len = cur + payload_len - off 123 124 if n < off + f.frame_len { return WS_ERR_SHORT } 125 return 0 126} 127 128// Apply the 4-byte mask to payload in place. Same operation 129// masks or unmasks (XOR is self-inverse). 130func ws_apply_mask(buf: *u8, off: i64, n: i64, mask_key: i64) -> i64 { 131 let m0: i64 = (mask_key >> 24) & 0xFF 132 let m1: i64 = (mask_key >> 16) & 0xFF 133 let m2: i64 = (mask_key >> 8) & 0xFF 134 let m3: i64 = mask_key & 0xFF 135 var i: i64 = 0 136 while i < n { 137 let idx: i64 = i % 4 138 var m: i64 = 0 139 if idx == 0 { m = m0 } 140 if idx == 1 { m = m1 } 141 if idx == 2 { m = m2 } 142 if idx == 3 { m = m3 } 143 buf[off + i] = buf[off + i] ^ m 144 i = i + 1 145 } 146 return 0 147} 148 149// Build a frame header at out[0]. Payload is the caller's job to 150// append immediately after (plus mask application if masked=1). 151// Returns header length or WS_ERR_SHORT/WS_ERR_LONG. 152func ws_build_header(out: *u8, cap: i64, 153 fin: i64, opcode: i64, 154 masked: i64, mask_key: i64, 155 payload_len: i64) -> i64 { 156 if cap < 2 { return WS_ERR_SHORT } 157 if payload_len < 0 { return WS_ERR_LONG } 158 var off: i64 = 0 159 var b0: i64 = opcode & 0xF 160 if fin == 1 { b0 = b0 | 0x80 } 161 out[off] = b0 162 off = off + 1 163 164 var b1: i64 = 0 165 if masked == 1 { b1 = b1 | 0x80 } 166 if payload_len < 126 { 167 b1 = b1 | (payload_len & 0x7F) 168 out[off] = b1 169 off = off + 1 170 } else { 171 if payload_len < 65536 { 172 b1 = b1 | 126 173 out[off] = b1 174 off = off + 1 175 if cap < off + 2 { return WS_ERR_SHORT } 176 out[off] = (payload_len >> 8) & 0xFF 177 out[off + 1] = payload_len & 0xFF 178 off = off + 2 179 } else { 180 b1 = b1 | 127 181 out[off] = b1 182 off = off + 1 183 if cap < off + 8 { return WS_ERR_SHORT } 184 out[off] = (payload_len >> 56) & 0xFF 185 out[off + 1] = (payload_len >> 48) & 0xFF 186 out[off + 2] = (payload_len >> 40) & 0xFF 187 out[off + 3] = (payload_len >> 32) & 0xFF 188 out[off + 4] = (payload_len >> 24) & 0xFF 189 out[off + 5] = (payload_len >> 16) & 0xFF 190 out[off + 6] = (payload_len >> 8) & 0xFF 191 out[off + 7] = payload_len & 0xFF 192 off = off + 8 193 } 194 } 195 if masked == 1 { 196 if cap < off + 4 { return WS_ERR_SHORT } 197 out[off] = (mask_key >> 24) & 0xFF 198 out[off + 1] = (mask_key >> 16) & 0xFF 199 out[off + 2] = (mask_key >> 8) & 0xFF 200 out[off + 3] = mask_key & 0xFF 201 off = off + 4 202 } 203 return off 204} 205 206// Compile-only smoke. 207func main() -> i64 { 208 // Parse a minimal server-sent text frame: FIN=1, opcode=1, 209 // masked=0, payload_len=5, payload="hello". 210 let raw: *u8 = sys_mmap(64) 211 raw[0] = 0x81 // FIN + opcode=1 212 raw[1] = 5 // len, no mask 213 raw[2] = 0x68; raw[3] = 0x65; raw[4] = 0x6C 214 raw[5] = 0x6C; raw[6] = 0x6F // 'hello' 215 216 let f_raw: *u8 = sys_mmap(128) 217 let f: *WsFrame = f_raw as *WsFrame 218 if ws_parse_frame(raw, 7, 0, f) != 0 { return 1 } 219 if f.fin != 1 { return 2 } 220 if f.opcode != WS_OP_TEXT { return 3 } 221 if f.masked != 0 { return 4 } 222 if f.payload_len != 5 { return 5 } 223 if f.payload_off != 2 { return 6 } 224 if f.frame_len != 7 { return 7 } 225 226 // Build an equivalent frame header. 227 let out: *u8 = sys_mmap(64) 228 let hlen: i64 = ws_build_header(out, 64, 1, WS_OP_TEXT, 0, 0, 5) 229 if hlen != 2 { return 8 } 230 if out[0] != 0x81 { return 9 } 231 if out[1] != 5 { return 10 } 232 233 // Masked client frame: apply + unapply mask. 234 let pl: *u8 = sys_mmap(16) 235 pl[0] = 0x68; pl[1] = 0x65; pl[2] = 0x6C; pl[3] = 0x6C; pl[4] = 0x6F 236 let key: i64 = 0x12345678 237 ws_apply_mask(pl, 0, 5, key) 238 ws_apply_mask(pl, 0, 5, key) // self-inverse 239 if pl[0] != 0x68 { return 11 } 240 if pl[4] != 0x6F { return 12 } 241 242 // 16-bit extended length. 243 let hlen2: i64 = ws_build_header(out, 64, 1, WS_OP_BINARY, 0, 0, 1000) 244 if hlen2 != 4 { return 13 } 245 if out[1] != 126 { return 14 } 246 if out[2] != 0x03 { return 15 } // 1000 >> 8 = 3 247 if out[3] != 0xE8 { return 16 } // 1000 & 0xFF = 232 248 return 0 249}