Skip to content
Ridgun
English

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 ws lib 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:

  1. Declare import ws (or import ws as alias) at the top of your file: every lib defines its module :: ws namespace, and under --strict multi-word functions are always called with natural spaces.
  2. Chain your data pipelines left-to-right with ->: write input step one -> step two -> result, use (+ _ _ +) sequence masks to destructure tuples/lists, and omit _ = on sequential statements (or discard with -> ()).
  3. Tail-Call Optimization (TCO): all internal loops in ws are tail-recursive and compile into native hardware loops using $O(1)$ stack space.
  4. 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:

ArtifactPathPurpose
Ridgun Bundletarget/bundles/ws.rglibPortable distribution and direct import ws resolution
Compiled Archivetarget/ws.rgcType-checked intermediate artifact verified by ridgunc
ELF64 Shared Objecttarget/libws.soNative dynamic shared library with zero C or libc dependencies

Every lib declares its own namespace via module :: ws: simply write import ws (or import 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 / TypePurpose & Usage
ws accept key : String -> StringCompute RFC 6455 §1.3 Sec-WebSocket-Accept from Sec-WebSocket-Key
ws build upgrade request / ws build upgrade responseFormat HTTP/1.1 WebSocket upgrade handshake messages
ws encode frame / ws decode frameEncode and decode WsFrame (fin, opcode, masked, mask_key, payload)
ws text frame / ws ping frame / ws pong frame / ws close frameHigh-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
1fn :: ws u32 IntInt
2fn :: ws u32 xor loop Int Int Int IntInt
3fn :: ws u32 xor Int IntInt
4fn :: ws u32 and loop Int Int Int IntInt
5fn :: ws u32 and Int IntInt
6fn :: ws u32 or loop Int Int Int IntInt
7fn :: ws u32 or Int IntInt
8fn :: ws u32 not IntInt
9fn :: ws pow2 IntInt
10fn :: ws rotl32 Int IntInt
11fn :: ws byte xor Int IntInt
12fn :: ws list len [Int]Int
13fn :: ws nth [Int] IntInt
14fn :: ws take [Int] Int[Int]
15fn :: ws drop [Int] Int[Int]
16fn :: ws str to bytes loop String Int Int [Int][Int]
17fn :: ws str to bytes String[Int]
18fn :: ws bytes to str [Int]String
19fn :: ws zeroes Int[Int]
20fn :: ws u32 be4 Int[Int]
21fn :: ws sha1 pad [Int][Int]
22fn :: ws be32 at [Int] IntInt
23fn :: ws sha1 w16 loop [Int] Int [Int][Int]
24fn :: ws sha1 expand loop [Int] Int[Int]
25fn :: ws sha1 expand step [Int] Int[Int]
26fn :: ws sha1 f Int Int Int IntInt
27fn :: ws sha1 k IntInt
28fn :: ws sha1 rounds Sha1State [Int] IntSha1State
29fn :: ws sha1 round step Sha1State [Int] IntSha1State
30fn :: ws sha1 blocks Sha1State [Int]Sha1State
31fn :: ws sha1 bytes String[Int]
32fn :: ws b64 lut IntString
33fn :: ws b64 c IntString
34fn :: ws b64 enc [Int]String
35fn :: ws accept key StringString
36fn :: ws build upgrade request String String StringString
37fn :: ws build upgrade response StringString
38fn :: ws mask loop [Int] [Int] Int[Int]
39fn :: ws mask bytes [Int] [Int][Int]
40fn :: ws fin bit BoolInt
41fn :: ws len header Int Int[Int]
42fn :: ws encode frame Bool Int [Int] [Int][Int]
43fn :: ws encode frame with mask Int Bool [Int] [Int][Int]
44fn :: ws encode text String[Int]
45fn :: ws encode masked text String [Int][Int]
46fn :: ws encode ping [Int][Int]
47fn :: ws encode pong [Int][Int]
48fn :: ws encode close Int[Int]
49fn :: ws decode frame [Int]WsRes
50fn :: ws decode check min [Int] IntWsRes
51fn :: ws decode len [Int] Int Int IntWsRes
52fn :: ws decode ext len [Int] Int Bool Int Bool IntWsRes
53fn :: ws decode body [Int] Int Bool Int Bool Int IntWsRes
54fn :: ws finish frame Bool Int Bool [Int] IntWsRes

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 in ws decode frame.
  • $O(1)$ Stack Complexity via TCO: Every recursive loop in src/main/ridgun/lib/ws.rg is in tail position, so ridgunc compiles 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 check and tested by the automated suite in src/test/ridgun/test ws.rg.

The 5 Golden Rules When Programming with ws

  1. Declare import ws at the top of your file and take advantage of space/underscore equivalence so your code reads like natural prose.
  2. Align your data pipelines with =: chain ws calls into named intermediate steps and let stable format align the columns.
  3. Leverage ws's total functions inside | guards: inspect return values or unwrap ADTs via pattern matching rather than assuming external input is valid.
  4. Avoid cloning large structures when only reading: remember that under RMM2, function input arguments incur zero reference-count overhead (+0 / -0).
  5. Pair ws with test (import test): always lock in your domain invariants with assert equals or assert 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).