code wiki / (root) / nx_websocket_handshake.nx

nx_websocket_handshake.nx source

↩ module page · 86 lines · 2803 B

1// websocket_handshake.nx -- compute Sec-WebSocket-Accept. 2// 3// RFC 6455 §4.2.2: the server's response to a WebSocket upgrade 4// request includes a Sec-WebSocket-Accept header whose value is 5// 6// base64( sha1( client_key || GUID ) ) 7// 8// where GUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" is a 9// literal defined in the RFC (solely to make accidental 10// handshake-value forgery harder -- it doesn't add security). 11// client_key is the value of the Sec-WebSocket-Key request 12// header (a 16-byte random value, base64-encoded by the client, 13// so 24 ASCII chars including "=" padding). 14// 15// Composes sha1.nx + base64.nx. The concatenation buffer is at 16// most 24 + 36 = 60 bytes. 17// 18// Invariants: 19// WH1 Caller passes the raw client key bytes (typically 24 20// ASCII characters); we don't decode them -- the spec 21// says concatenate the client-sent base64 string AS IS 22// with the GUID string and hash that. 23// WH2 Output is 28 ASCII characters (base64 of 20 bytes). 24 25// nx_safety_envelope: 26// intended_use: AUTO_APPLIED -- primitive-specific tuning queued 27// sil_target: SIL1 28// evidence: [bulk_applied_2026-05-16, see-file-comment-for-detail] 29// verdict: NOT_YET_EVALUATED 30 31import "nx_syscalls.nx" 32import "nx_sha1.nx" 33import "nx_base64.nx" 34 35// RFC 6455 GUID -- 36 bytes. 36func ws_guid() -> *u8 { 37 return "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 38} 39 40const WS_GUID_LEN: i64 = 36 41const WS_SHA1_BYTES: i64 = 20 42const WS_ACCEPT_LEN: i64 = 28 43 44// Compute the Sec-WebSocket-Accept value. `key` is the value of 45// the client's Sec-WebSocket-Key header (raw ASCII bytes). Out 46// must have room for 28 base64 chars. 47func ws_accept(key: *u8, key_len: i64, out: *u8) -> i64 { 48 // Build key || GUID in a scratch buffer. 49 let scratch: *u8 = sys_mmap(128) 50 var i: i64 = 0 51 while i < key_len { 52 scratch[i] = key[i] 53 i = i + 1 54 } 55 let guid: *u8 = ws_guid() 56 var j: i64 = 0 57 while j < WS_GUID_LEN { 58 scratch[key_len + j] = guid[j] 59 j = j + 1 60 } 61 62 // SHA-1 of the concatenation -> 20 bytes. 63 let hash: *u8 = sys_mmap(32) 64 sha1(scratch, key_len + WS_GUID_LEN, hash) 65 66 // Base64-encode the hash -> 28 chars. 67 b64_encode(hash, WS_SHA1_BYTES, out) 68 return WS_ACCEPT_LEN 69} 70 71// Compile-only smoke. RFC 6455 §1.3 worked example: 72// client key: "dGhlIHNhbXBsZSBub25jZQ==" 73// accept: "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" 74func main() -> i64 { 75 let out: *u8 = sys_mmap(64) 76 let n: i64 = ws_accept("dGhlIHNhbXBsZSBub25jZQ==", 24, out) 77 if n != WS_ACCEPT_LEN { return 1 } 78 79 let expected: *u8 = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" 80 var i: i64 = 0 81 while i < WS_ACCEPT_LEN { 82 if out[i] != expected[i] { return 10 + i } 83 i = i + 1 84 } 85 return 0 86}