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}