code wiki / (root) / websocket_handshake.nx

websocket_handshake.nx source

↩ module page · 80 lines · 2626 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 25import "syscalls.nx" 26import "nx_sha1.nx" 27import "base64.nx" 28 29// RFC 6455 GUID -- 36 bytes. 30func ws_guid() -> *u8 { 31 return "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" 32} 33 34const WS_GUID_LEN: i64 = 36 35const WS_SHA1_BYTES: i64 = 20 36const WS_ACCEPT_LEN: i64 = 28 37 38// Compute the Sec-WebSocket-Accept value. `key` is the value of 39// the client's Sec-WebSocket-Key header (raw ASCII bytes). Out 40// must have room for 28 base64 chars. 41func ws_accept(key: *u8, key_len: i64, out: *u8) -> i64 { 42 // Build key || GUID in a scratch buffer. 43 let scratch: *u8 = sys_mmap(128) 44 var i: i64 = 0 45 while i < key_len { 46 scratch[i] = key[i] 47 i = i + 1 48 } 49 let guid: *u8 = ws_guid() 50 var j: i64 = 0 51 while j < WS_GUID_LEN { 52 scratch[key_len + j] = guid[j] 53 j = j + 1 54 } 55 56 // SHA-1 of the concatenation -> 20 bytes. 57 let hash: *u8 = sys_mmap(32) 58 sha1(scratch, key_len + WS_GUID_LEN, hash) 59 60 // Base64-encode the hash -> 28 chars. 61 b64_encode(hash, WS_SHA1_BYTES, out) 62 return WS_ACCEPT_LEN 63} 64 65// Compile-only smoke. RFC 6455 §1.3 worked example: 66// client key: "dGhlIHNhbXBsZSBub25jZQ==" 67// accept: "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" 68func main() -> i64 { 69 let out: *u8 = sys_mmap(64) 70 let n: i64 = ws_accept("dGhlIHNhbXBsZSBub25jZQ==", 24, out) 71 if n != WS_ACCEPT_LEN { return 1 } 72 73 let expected: *u8 = "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" 74 var i: i64 = 0 75 while i < WS_ACCEPT_LEN { 76 if out[i] != expected[i] { return 10 + i } 77 i = i + 1 78 } 79 return 0 80}