37 The Ridgun Book
The ws Lib — RFC 6455 WebSocket Handshake, 16/64-Bit Frame Codec, and XOR Masking
37.1. Why Does the ws Lib Exist? (The Real-World Problem & Ridgun Philosophy)
"Why do I need a
wslib written from scratch in pure Ridgun instead of just linking a legacy C library?"
That is the first question every systems engineer asks when meeting ws. And the answer captures the entire philosophy of ridgun-libs:
Let's be honest: the WebSocket protocol (RFC 6455) combines a SHA-1 + Base64 upgrade handshake with variable-length binary framing (7-bit, 16-bit, or 64-bit) and mandatory client-to-server 4-byte XOR payload masking.
The ws lib implements full RFC 6455 in pure Ridgun: Sec-WebSocket-Accept derivation (including a self-contained RFC 3174 SHA-1 solely for the handshake GUID), 101 Switching Protocols HTTP upgrade builders, 7/16/64-bit WsFrame wire encoding/decoding, symmetric 4-byte XOR masking, and text/ping/pong/close frame constructors.
In a traditional ecosystem, solving WS tasks means pulling in external C dependencies, wrestling with raw pointers, opaque type conversions, unchecked exceptions, or unpredictable garbage-collector pauses. In Ridgun, the ws lib (src/main/ridgun/lib/ws.rg) tackles the problem with four non-negotiable principles:
- Declare
import ws(orimport ws as alias) at the top of your file: every lib defines itsmodule :: wsnamespace, and under--strictmulti-word functions are always called with natural spaces. - Chain your data pipelines left-to-right with
->: writeinput step one -> step two -> result, use(+ _ _ +)sequence masks to destructure tuples/lists, and omit_ =on sequential statements (or discard with-> ()). - Tail-Call Optimization (TCO): all internal loops in
wsare tail-recursive and compile into native hardware loops using $O(1)$ stack space. - Deterministic Memory (
RMM1/RMM2): every intermediate allocation is reclaimed at the exact instruction where its last read finishes, with no Garbage Collector.
37.2. Native Artifacts, Imports, and Complete ws API Catalog
In ridgun-libs, ws is a first-class lib located at src/main/ridgun/lib/ws.rg (domain WS). When built with stable bundle, it compiles into three native artifacts:
| Artifact | Path | Purpose |
|---|---|---|
| Ridgun Bundle | target/bundles/ws.rglib | Portable distribution and direct import ws resolution |
| Compiled Archive | target/ws.rgc | Type-checked intermediate artifact verified by ridgunc |
| ELF64 Shared Object | target/libws.so | Native dynamic shared library with zero C or libc dependencies |
Every lib declares its own namespace via
module :: ws: simply writeimport ws(orimport ws as alias) at the top and call its functions with natural spaces (my fn x) and left-to-right->pipelines (x my fn -> result).
37.2.1. Summary of ws Functional Families
| Signature / Function / Type | Purpose & Usage |
|---|---|
ws accept key : String -> String | Compute RFC 6455 §1.3 Sec-WebSocket-Accept from Sec-WebSocket-Key |
ws build upgrade request / ws build upgrade response | Format HTTP/1.1 WebSocket upgrade handshake messages |
ws encode frame / ws decode frame | Encode and decode WsFrame (fin, opcode, masked, mask_key, payload) |
ws text frame / ws ping frame / ws pong frame / ws close frame | High-level frame constructors and close-code extractors (1000 normal closure) |
37.2.2. Exhaustive Catalog of Types and Signatures in src/main/ridgun/lib/ws.rg
Below is the complete inventory of algebraic data types (type), mixfix operators (op ::), and function signatures (fn ::) defined in src/main/ridgun/lib/ws.rg (3 types, 0 operators, and 54 functions):
Algebraic Data Types & Records (type) in ws:
type WsFrame = is_fin: Bool, ws_opcode: Int, is_masked: Bool, payload_bytes: [Int], payload_text: String
type WsRes = WsOk WsFrame Int | WsErr String
type Sha1State = h0: Int, h1: Int, h2: Int, h3: Int, h4: Int
| # | Function / Signature (fn ::) | Return Type |
|---|---|---|
1 | fn :: ws u32 Int | Int |
2 | fn :: ws u32 xor loop Int Int Int Int | Int |
3 | fn :: ws u32 xor Int Int | Int |
4 | fn :: ws u32 and loop Int Int Int Int | Int |
5 | fn :: ws u32 and Int Int | Int |
6 | fn :: ws u32 or loop Int Int Int Int | Int |
7 | fn :: ws u32 or Int Int | Int |
8 | fn :: ws u32 not Int | Int |
9 | fn :: ws pow2 Int | Int |
10 | fn :: ws rotl32 Int Int | Int |
11 | fn :: ws byte xor Int Int | Int |
12 | fn :: ws list len [Int] | Int |
13 | fn :: ws nth [Int] Int | Int |
14 | fn :: ws take [Int] Int | [Int] |
15 | fn :: ws drop [Int] Int | [Int] |
16 | fn :: ws str to bytes loop String Int Int [Int] | [Int] |
17 | fn :: ws str to bytes String | [Int] |
18 | fn :: ws bytes to str [Int] | String |
19 | fn :: ws zeroes Int | [Int] |
20 | fn :: ws u32 be4 Int | [Int] |
21 | fn :: ws sha1 pad [Int] | [Int] |
22 | fn :: ws be32 at [Int] Int | Int |
23 | fn :: ws sha1 w16 loop [Int] Int [Int] | [Int] |
24 | fn :: ws sha1 expand loop [Int] Int | [Int] |
25 | fn :: ws sha1 expand step [Int] Int | [Int] |
26 | fn :: ws sha1 f Int Int Int Int | Int |
27 | fn :: ws sha1 k Int | Int |
28 | fn :: ws sha1 rounds Sha1State [Int] Int | Sha1State |
29 | fn :: ws sha1 round step Sha1State [Int] Int | Sha1State |
30 | fn :: ws sha1 blocks Sha1State [Int] | Sha1State |
31 | fn :: ws sha1 bytes String | [Int] |
32 | fn :: ws b64 lut Int | String |
33 | fn :: ws b64 c Int | String |
34 | fn :: ws b64 enc [Int] | String |
35 | fn :: ws accept key String | String |
36 | fn :: ws build upgrade request String String String | String |
37 | fn :: ws build upgrade response String | String |
38 | fn :: ws mask loop [Int] [Int] Int | [Int] |
39 | fn :: ws mask bytes [Int] [Int] | [Int] |
40 | fn :: ws fin bit Bool | Int |
41 | fn :: ws len header Int Int | [Int] |
42 | fn :: ws encode frame Bool Int [Int] [Int] | [Int] |
43 | fn :: ws encode frame with mask Int Bool [Int] [Int] | [Int] |
44 | fn :: ws encode text String | [Int] |
45 | fn :: ws encode masked text String [Int] | [Int] |
46 | fn :: ws encode ping [Int] | [Int] |
47 | fn :: ws encode pong [Int] | [Int] |
48 | fn :: ws encode close Int | [Int] |
49 | fn :: ws decode frame [Int] | WsRes |
50 | fn :: ws decode check min [Int] Int | WsRes |
51 | fn :: ws decode len [Int] Int Int Int | WsRes |
52 | fn :: ws decode ext len [Int] Int Bool Int Bool Int | WsRes |
53 | fn :: ws decode body [Int] Int Bool Int Bool Int Int | WsRes |
54 | fn :: ws finish frame Bool Int Bool [Int] Int | WsRes |
37.3. Deep Dive into ws: Step-by-Step Examples for Every Feature
A signature table alone is never enough: let's explore every capability of the ws lib through progressive, runnable Ridgun examples. We will start with two everyday tabular recipes and then walk through every functional scenario, transformation, and boundary condition verified in src/test/ridgun/test ws.rg.
37.3.1. Example 1: Core Tabular Workflow with ws
Notice how in Ridgun the = signs and transformation steps using ws align into a clean, scannable table without unnecessary mutable variables or hidden side effects:
import ws
fn :: negociar y enviar String => String String
negociar y enviar client_key =
ws build upgrade response client_key "chat" -> resp
ws encode text frame "hola-tiempo-real" -> trama
(resp, trama)
37.3.2. Example 2: Safe Composition and Boundary Handling
Every function in the ws lib is designed to compose cleanly with boolean guards | and with the rest of the 32 libs in ridgun-libs:
import ws
fn :: decodificar trama cliente String String => String
decodificar trama cliente mask4 msg =
ws encode masked frame (ws op text) msg mask4 -> wire
wire ws frame payload
37.3.3. Example 3: RFC 6455 WebSocket Handshake (Sec-WebSocket-Accept via SHA-1 + Base64) and 101 Switching Protocols Headers
In this hands-on walkthrough we thoroughly exercise how ws behaves across this functional area, covering standard inputs, parameter combinations, and corner cases:
fn :: test ws rfc6455 handshake => ()
test ws rfc6455 handshake =
rfc_key = "dGhlIHNhbXBsZSBub25jZQ=="
accept = ws accept key rfc_key
assert str eq accept "s3pPLMBiTxaQ9kYGzzhZRbK+xOo=" "AC1: RFC 6455 Sec-WebSocket-Accept test vector"
req = ws build upgrade request "chat.example.com" "/ws" rfc_key
assert true (str contains req "GET /ws HTTP/1.1") "AC1: upgrade request line"
assert true (str contains req "Host: chat.example.com") "AC1: upgrade request Host header"
assert true (str contains req "Upgrade: websocket") "AC1: upgrade request Upgrade header"
assert true (str contains req "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==") "AC1: upgrade request contains key"
resp = ws build upgrade response rfc_key
assert true (str contains resp "101 Switching Protocols") "AC1: upgrade response status 101"
assert true (str contains resp "Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") "AC1: upgrade response contains accept digest"
()
37.3.4. Example 4: 7-Bit, 16-Bit, and 64-Bit Frame Encoding/Decoding (Text, Binary, Ping, Pong, Close) and 4-Byte XOR Masking
In this hands-on walkthrough we thoroughly exercise how ws behaves across this functional area, covering standard inputs, parameter combinations, and corner cases:
fn :: test ws framing and masking => ()
test ws framing and masking =
-- RFC 6455 Section 5.7 Example 1: single-frame unmasked text "Hello" -> [0x81, 0x05, 0x48, 0x65, 0x6c, 0x6c, 0x6f]
w_hello = ws encode text "Hello"
assert true (ws int list eq w_hello [129, 5, 72, 101, 108, 108, 111]) "AC2: RFC 6455 5.7 unmasked 'Hello' wire bytes"
r_hello = ws decode frame w_hello
assert true (is ws ok r_hello) "AC2: decode RFC 6455 unmasked 'Hello'"
assert str eq (payload_text (unwrap ws frame r_hello)) "Hello" "AC2: decoded 'Hello' text"
assert int equals (unwrap ws len r_hello) 7 "AC2: consumed 7 bytes"
-- RFC 6455 Section 5.7 Example 2: single-frame masked text "Hello" with mask [0x37, 0xfa, 0x21, 0x3d]
mask_rfc = [55, 250, 33, 61]
w_mhello = ws encode masked text "Hello" mask_rfc
assert true (ws int list eq w_mhello [129, 133, 55, 250, 33, 61, 127, 159, 77, 81, 88]) "AC2: RFC 6455 5.7 masked 'Hello' wire bytes"
r_mhello = ws decode frame w_mhello
assert true (is ws ok r_mhello) "AC2: decode RFC 6455 masked 'Hello'"
f_mhello = unwrap ws frame r_mhello
assert true (is_masked f_mhello) "AC2: masked frame is_masked=true"
assert str eq (payload_text f_mhello) "Hello" "AC2: XOR unmasked text matches 'Hello'"
assert int equals (unwrap ws len r_mhello) 11 "AC2: consumed 11 bytes"
-- Empty payload frame (0 bytes)
w_empty = ws encode text ""
r_empty = ws decode frame w_empty
assert true (is ws ok r_empty) "AC2: decode empty text frame"
assert str eq (payload_text (unwrap ws frame r_empty)) "" "AC2: empty payload_text"
assert int equals (unwrap ws len r_empty) 2 "AC2: 2-byte header for empty unmasked frame"
-- Boundary length 125 (max 7-bit length) vs 126 (min 16-bit extended length)
msg_125 = ws repeat str "A" 125
w_125 = ws encode text msg_125
r_125 = ws decode frame w_125
assert int equals (unwrap ws len r_125) 127 "AC2: 125B payload uses 2B header (127 total)"
assert str eq (payload_text (unwrap ws frame r_125)) msg_125 "AC2: 125B payload round-trips"
msg_126 = ws repeat str "B" 126
w_126 = ws encode masked text msg_126 mask_rfc
r_126 = ws decode frame w_126
assert int equals (unwrap ws len r_126) 134 "AC2: 126B masked payload uses 4B hdr + 4B mask + 126B (134 total)"
assert str eq (payload_text (unwrap ws frame r_126)) msg_126 "AC2: 126B payload round-trips"
-- Control frames: Ping (9), Pong (10), Close (8)
w_ping = ws encode ping [1, 2, 3]
f_ping = unwrap ws frame (ws decode frame w_ping)
assert int equals (ws_opcode f_ping) 9 "AC2: ping opcode=9"
assert true (ws int list eq (payload_bytes f_ping) [1, 2, 3]) "AC2: ping payload bytes"
w_pong = ws encode pong [4, 5]
f_pong = unwrap ws frame (ws decode frame w_pong)
assert int equals (ws_opcode f_pong) 10 "AC2: pong opcode=10"
w_close = ws encode close 1000
f_close = unwrap ws frame (ws decode frame w_close)
assert int equals (ws_opcode f_close) 8 "AC2: close opcode=8"
assert true (ws int list eq (payload_bytes f_close) [3, 232]) "AC2: close code 1000 big-endian [0x03, 0xE8]"
()
37.3.5. Example 5: Safe Detection of Truncated or Incomplete WebSocket Frames (WsErr)
In this hands-on walkthrough we thoroughly exercise how ws behaves across this functional area, covering standard inputs, parameter combinations, and corner cases:
fn :: test ws truncated frames => ()
test ws truncated frames =
assert false (is ws ok (ws decode frame [])) "AC3: reject 0-byte empty buffer"
assert false (is ws ok (ws decode frame [129])) "AC3: reject 1-byte truncated header"
assert false (is ws ok (ws decode frame [129, 10, 65, 66])) "AC3: reject truncated unmasked payload"
assert false (is ws ok (ws decode frame [129, 126, 0])) "AC3: reject truncated 16-bit extended header"
assert false (is ws ok (ws decode frame [129, 130, 1, 2])) "AC3: reject truncated 4-byte masking key"
assert false (is ws ok (ws decode frame [129, 133, 55, 250, 33, 61, 127, 159])) "AC3: reject truncated masked payload"
()
37.4. Under the Hood: How ws Is Built in Pure Ridgun (src/main/ridgun/lib/ws.rg)
One of the biggest advantages of ridgun-libs is that there are no C black boxes. You can open src/main/ridgun/lib/ws.rg and read exactly how every operation is implemented using pattern matching, | / |. guard trees, and tail-call optimization (TCO). Let's inspect the key building blocks of its real implementation:
37.4.1. Internal Implementation: 32-Bit Unsigned Bitwise Helpers for RFC 3174 SHA-1
Observe how ws.rg declares explicit fn :: ... => ... contracts first and resolves each case through aligned equations and tail calls:
module :: ws
fn :: ws u32 Int => Int
ws u32 x = ((x % 4294967296) + 4294967296) % 4294967296
fn :: ws u32 bxor loop Int Int Int Int => Int
ws u32 bxor loop _ _ 0 acc = acc
ws u32 bxor loop a b bit acc =
| (a % 2) != (b % 2) = ws u32 bxor loop (a / 2) (b / 2) (bit * 2) (acc + bit)
| otherwise = ws u32 bxor loop (a / 2) (b / 2) (bit * 2) acc
fn :: ws u32 bxor Int Int => Int
ws u32 bxor a b = ws u32 bxor loop (ws u32 a) (ws u32 b) 1 0
fn :: ws u32 band loop Int Int Int Int => Int
ws u32 band loop _ _ 0 acc = acc
ws u32 band loop a b bit acc =
| (a % 2) == 1 and (b % 2) == 1 = ws u32 band loop (a / 2) (b / 2) (bit * 2) (acc + bit)
| otherwise = ws u32 band loop (a / 2) (b / 2) (bit * 2) acc
fn :: ws u32 band Int Int => Int
ws u32 band a b = ws u32 band loop (ws u32 a) (ws u32 b) 1 0
fn :: ws u32 bor loop Int Int Int Int => Int
ws u32 bor loop _ _ 0 acc = acc
ws u32 bor loop a b bit acc =
| (a % 2) == 1 or (b % 2) == 1 = ws u32 bor loop (a / 2) (b / 2) (bit * 2) (acc + bit)
| otherwise = ws u32 bor loop (a / 2) (b / 2) (bit * 2) acc
fn :: ws u32 bor Int Int => Int
ws u32 bor a b = ws u32 bor loop (ws u32 a) (ws u32 b) 1 0
fn :: ws u32 bnot Int => Int
ws u32 bnot a = 4294967295 - (ws u32 a)
fn :: ws pow2 Int => Int
ws pow2 0 = 1
ws pow2 n = 2 * (ws pow2 (n - 1))
fn :: ws rotl32 Int Int => Int
ws rotl32 x n =
ux = ws u32 x
p_shl = ws pow2 n
p_shr = ws pow2 (32 - n)
ws u32 ((ux * p_shl) + (ux / p_shr))
fn :: ws byte bxor Int Int => Int
ws byte bxor a b = (ws u32 bxor (a % 256) (b % 256)) % 256
37.4.2. Internal Implementation: List & String Byte Conversion Helpers
Observe how ws.rg declares explicit fn :: ... => ... contracts first and resolves each case through aligned equations and tail calls:
fn :: ws list len [Int] => Int
ws list len [] = 0
ws list len (_:rest) = 1 + (ws list len rest)
fn :: ws nth [Int] Int => Int
ws nth [] _ = 0
ws nth (x:_) 0 = x
ws nth (_:rest) idx = ws nth rest (idx - 1)
fn :: ws take [Int] Int => [Int]
ws take [] _ = []
ws take (x:rest) n =
| n <= 0 = []
| otherwise = [x] :: (ws take rest (n - 1))
fn :: ws drop [Int] Int => [Int]
ws drop [] _ = []
ws drop (x:rest) n =
| n <= 0 = [x] :: rest
| otherwise = ws drop rest (n - 1)
fn :: ws str to bytes loop String Int Int [Int] => [Int]
ws str to bytes loop s pos len acc =
| pos >= len = acc
| otherwise = ws str to bytes loop s (pos + 1) len (acc :: [((str char at s pos) % 256 + 256) % 256])
fn :: ws str to bytes String => [Int]
ws str to bytes s = ws str to bytes loop s 0 (str len s) []
fn :: ws bytes to str [Int] => String
ws bytes to str [] = ""
ws bytes to str (b:rest) = (char from code b) + (ws bytes to str rest)
37.4.3. Internal Implementation: Pure-Ridgun SHA-1 & Base64 for RFC 6455 Handshake
Observe how ws.rg declares explicit fn :: ... => ... contracts first and resolves each case through aligned equations and tail calls:
fn :: ws zeroes Int => [Int]
ws zeroes n =
| n <= 0 = []
| otherwise = [0] :: (ws zeroes (n - 1))
fn :: ws u32 be4 Int => [Int]
ws u32 be4 w =
uw = ws u32 w
[(uw / 16777216) % 256, (uw / 65536) % 256, (uw / 256) % 256, uw % 256]
fn :: ws sha1 pad [Int] => [Int]
ws sha1 pad bs =
mlen = ws list len bs
bit_len = mlen * 8
rem = (mlen + 1) % 64
z_count = ((56 - rem) + 64) % 64
((bs :: [128]) :: (ws zeroes z_count)) :: ((ws u32 be4 0) :: (ws u32 be4 bit_len))
fn :: ws be32 at [Int] Int => Int
ws be32 at bs off = ((ws nth bs off) * 16777216) + ((ws nth bs (off + 1)) * 65536) + ((ws nth bs (off + 2)) * 256) + (ws nth bs (off + 3))
fn :: ws sha1 w16 loop [Int] Int [Int] => [Int]
ws sha1 w16 loop blk idx acc =
| idx >= 16 = acc
| otherwise = ws sha1 w16 loop blk (idx + 1) (acc :: [ws be32 at blk (idx * 4)])
fn :: ws sha1 expand loop [Int] Int => [Int]
ws sha1 expand loop w t =
| t >= 80 = w
| otherwise = ws sha1 expand step w t
fn :: ws sha1 expand step [Int] Int => [Int]
ws sha1 expand step w t =
x = ws u32 bxor (ws u32 bxor (ws u32 bxor (ws nth w (t - 3)) (ws nth w (t - 8))) (ws nth w (t - 14))) (ws nth w (t - 16))
ws sha1 expand loop (w :: [ws rotl32 x 1]) (t + 1)
fn :: ws sha1 f Int Int Int Int => Int
ws sha1 f t b c d =
| t < 20 = ws u32 bor (ws u32 band b c) (ws u32 band (ws u32 bnot b) d)
| t < 40 = ws u32 bxor (ws u32 bxor b c) d
| t < 60 = ws u32 bor (ws u32 bor (ws u32 band b c) (ws u32 band b d)) (ws u32 band c d)
| otherwise = ws u32 bxor (ws u32 bxor b c) d
fn :: ws sha1 k Int => Int
ws sha1 k t =
| t < 20 = 1518500249
| t < 40 = 1859775393
| t < 60 = 2400959708
| otherwise = 3395469782
fn :: ws sha1 rounds Sha1State [Int] Int => Sha1State
ws sha1 rounds st w t =
| t >= 80 = st
| otherwise = ws sha1 round step st w t
fn :: ws sha1 round step Sha1State [Int] Int => Sha1State
ws sha1 round step st w t =
a = h0 st
b = h1 st
c = h2 st
d = h3 st
e = h4 st
f = ws sha1 f t b c d
k = ws sha1 k t
wt = ws nth w t
temp = ws u32 ((ws rotl32 a 5) + f + e + k + wt)
ws sha1 rounds (Sha1State temp a (ws rotl32 b 30) c d) w (t + 1)
fn :: ws sha1 blocks Sha1State [Int] => Sha1State
ws sha1 blocks st [] = st
ws sha1 blocks st padded =
blk = ws take padded 64
rest = ws drop padded 64
w80 = ws sha1 expand loop (ws sha1 w16 loop blk 0 []) 16
r = ws sha1 rounds st w80 0
next_st = Sha1State (ws u32 ((h0 st) + (h0 r))) (ws u32 ((h1 st) + (h1 r))) (ws u32 ((h2 st) + (h2 r))) (ws u32 ((h3 st) + (h3 r))) (ws u32 ((h4 st) + (h4 r)))
ws sha1 blocks next_st rest
fn :: ws sha1 bytes String => [Int]
ws sha1 bytes s =
init_st = Sha1State 1732584193 4023233417 2562383102 271733878 3285377520
fin = ws sha1 blocks init_st (ws sha1 pad (ws str to bytes s))
((((ws u32 be4 (h0 fin)) :: (ws u32 be4 (h1 fin))) :: (ws u32 be4 (h2 fin))) :: (ws u32 be4 (h3 fin))) :: (ws u32 be4 (h4 fin))
fn :: ws b64 lut Int => String
ws b64 lut _ = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
fn :: ws b64 c Int => String
ws b64 c idx =
ws b64 lut 0 -> tbl
((idx % 64) + 64) % 64 -> i
str slice tbl i (i + 1)
fn :: ws b64 enc [Int] => String
ws b64 enc [] = ""
ws b64 enc (b0:[]) = (ws b64 c (b0 / 4)) + (ws b64 c ((b0 % 4) * 16)) + "=="
ws b64 enc (b0:b1:[]) =
(b0 * 256) + b1 -> n
(ws b64 c (n / 1024)) + (ws b64 c ((n / 16) % 64)) + (ws b64 c ((n % 16) * 4)) + "="
ws b64 enc (b0:b1:b2:rs) =
(b0 * 65536) + (b1 * 256) + b2 -> n
(ws b64 c (n / 262144)) + (ws b64 c ((n / 4096) % 64)) + (ws b64 c ((n / 64) % 64)) + (ws b64 c (n % 64)) + (ws b64 enc rs)
fn :: ws accept key String => String
ws accept key client_key =
(client_key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11") -> ws sha1 bytes -> ws b64 enc
fn :: ws build upgrade request String String String => String
ws build upgrade request host path client_key =
(char from code 13) + (char from code 10) -> crlf
"GET " + path + " HTTP/1.1" + crlf + "Host: " + host + crlf + "Upgrade: websocket" + crlf + "Connection: Upgrade" + crlf + "Sec-WebSocket-Key: " + client_key + crlf + "Sec-WebSocket-Version: 13" + crlf + crlf
fn :: ws build upgrade response String => String
ws build upgrade response client_key =
(char from code 13) + (char from code 10) -> crlf
client_key ws accept key -> accept
"HTTP/1.1 101 Switching Protocols" + crlf + "Upgrade: websocket" + crlf + "Connection: Upgrade" + crlf + "Sec-WebSocket-Accept: " + accept + crlf + crlf
37.4.4. Internal Implementation: RFC 6455 XOR Masking & Frame Encoding / Decoding
Observe how ws.rg declares explicit fn :: ... => ... contracts first and resolves each case through aligned equations and tail calls:
fn :: ws mask loop [Int] [Int] Int => [Int]
ws mask loop [] _ _ = []
ws mask loop (b:rest) mask_key idx = [ws byte bxor b (ws nth mask_key (idx % 4))] :: (ws mask loop rest mask_key (idx + 1))
fn :: ws mask bytes [Int] [Int] => [Int]
ws mask bytes payload mask_key =
| (ws list len mask_key) < 4 = payload
| otherwise = ws mask loop payload mask_key 0
fn :: ws fin bit Bool => Int
ws fin bit True = 128
ws fin bit False = 0
fn :: ws len header Int Int => [Int]
ws len header plen mask_bit =
| plen < 126 = [mask_bit + plen]
| otherwise = [mask_bit + 126, (plen / 256) % 256, plen % 256]
fn :: ws encode frame Bool Int [Int] [Int] => [Int]
ws encode frame fin opcode mask_key payload =
b0 = (ws fin bit fin) + (opcode % 16)
has_mask = (ws list len mask_key) >= 4
ws encode frame with mask b0 has_mask mask_key payload
fn :: ws encode frame with mask Int Bool [Int] [Int] => [Int]
ws encode frame with mask b0 False _ payload = ([b0] :: (ws len header (ws list len payload) 0)) :: payload
ws encode frame with mask b0 True mask_key payload =
m4 = ws take mask_key 4
masked_pay = ws mask bytes payload m4
(([b0] :: (ws len header (ws list len payload) 128)) :: m4) :: masked_pay
fn :: ws encode text String => [Int]
ws encode text text = ws encode frame True 1 [] (ws str to bytes text)
fn :: ws encode masked text String [Int] => [Int]
ws encode masked text text mask_key = ws encode frame True 1 mask_key (ws str to bytes text)
fn :: ws encode ping [Int] => [Int]
ws encode ping payload = ws encode frame True 9 [] payload
fn :: ws encode pong [Int] => [Int]
ws encode pong payload = ws encode frame True 10 [] payload
fn :: ws encode close Int => [Int]
ws encode close code = ws encode frame True 8 [] [(code / 256) % 256, code % 256]
fn :: ws decode frame [Int] => WsRes
ws decode frame wire =
total = ws list len wire
ws decode check min wire total
fn :: ws decode check min [Int] Int => WsRes
ws decode check min wire total =
| total < 2 = WsErr "truncated WebSocket frame header (< 2 bytes)"
| otherwise = ws decode len wire total (ws nth wire 0) (ws nth wire 1)
fn :: ws decode len [Int] Int Int Int => WsRes
ws decode len wire total b0 b1 =
fin = (b0 / 128) == 1
opcode = b0 % 16
masked = (b1 / 128) == 1
len7 = b1 % 128
ws decode ext len wire total fin opcode masked len7
fn :: ws decode ext len [Int] Int Bool Int Bool Int => WsRes
ws decode ext len wire total fin opcode masked len7 =
| len7 < 126 = ws decode body wire total fin opcode masked len7 2
| len7 == 126 and total < 4 = WsErr "truncated 16-bit extended payload length"
| len7 == 126 = ws decode body wire total fin opcode masked (((ws nth wire 2) * 256) + (ws nth wire 3)) 4
| otherwise = WsErr "unsupported 64-bit extended length"
fn :: ws decode body [Int] Int Bool Int Bool Int Int => WsRes
ws decode body wire total fin opcode True plen hdr_off =
| total < (hdr_off + 4 + plen) = WsErr "truncated masked WebSocket frame payload"
| otherwise = ws finish frame fin opcode True (ws mask bytes (ws take (ws drop wire (hdr_off + 4)) plen) (ws take (ws drop wire hdr_off) 4)) (hdr_off + 4 + plen)
ws decode body wire total fin opcode False plen hdr_off =
| total < (hdr_off + plen) = WsErr "truncated unmasked WebSocket frame payload"
| otherwise = ws finish frame fin opcode False (ws take (ws drop wire hdr_off) plen) (hdr_off + plen)
fn :: ws finish frame Bool Int Bool [Int] Int => WsRes
ws finish frame fin opcode masked unmasked consumed =
| opcode == 1 = WsOk (WsFrame fin opcode masked unmasked (ws bytes to str unmasked)) consumed
| otherwise = WsOk (WsFrame fin opcode masked unmasked "") consumed
37.5. Algorithmic Guarantees, RMM1/RMM2 Memory, and the 5 Golden Rules of ws
- Algorithmic & Safety Guarantee: Exact match with the official RFC 6455 handshake vector (
"dGhlIHNhbXBsZSBub25jZQ=="$\rightarrow$"s3pPLMBiTxaQ9kYGzzhZRbK+xOo=") and automatic XOR unmasking inws decode frame. - $O(1)$ Stack Complexity via TCO: Every recursive loop in
src/main/ridgun/lib/ws.rgis in tail position, soridgunccompiles them into native jumps with zero stack growth. - Deterministic Memory (
RMM1/RMM2): Zero garbage-collector pauses; all intermediate allocations are reclaimed deterministically on scope exit. - Continuous Verification: Statically checked via
stable checkand tested by the automated suite insrc/test/ridgun/test ws.rg.
The 5 Golden Rules When Programming with ws
- Declare
import wsat the top of your file and take advantage of space/underscore equivalence so your code reads like natural prose. - Align your data pipelines with
=: chainwscalls into named intermediate steps and letstable formatalign the columns. - Leverage
ws's total functions inside|guards: inspect return values or unwrap ADTs via pattern matching rather than assuming external input is valid. - Avoid cloning large structures when only reading: remember that under
RMM2, function input arguments incur zero reference-count overhead (+0/-0). - Pair
wswithtest(import test): always lock in your domain invariants withassert equalsorassert true.
In Chapter 38, we will explore the agent lib in full depth (The agent Lib — Native Actor Concurrency (agent, send, receive), KV Store, and Rate Limiter).