TLS & WebSocket
Discord-PXE implements the secure transport layers required before any Gateway payload can flow.
Those layers are not wrappers around an operating-system TLS library. The runtime owns its own TLS state, record buffers, transcript data, key material, HTTP upgrade request, and WebSocket accept verification.
Primary sources:
Per-shard TLS workspace
Each sharded TLS context allocates:
45 pages
45 × 4096 = 184,320 bytes
That workspace is intentionally private to one shard.
The layout includes storage for:
- wire input
- handshake accumulation
- transcript
- decrypted record data
- plaintext
- ciphertext
- ClientHello
- client/server randoms
- ECDHE parameters
- server signature
- authentication hash
- private/public/shared key material
- master secret
- key block
- client/server traffic keys
- IVs
- verification buffers
- certificate/RSA state
TLS is stateful across multiple records and handshake messages. Reusing one global workspace across concurrent shards would produce non-deterministic corruption even if every shard had its own TCP connection.
Lazy allocation
TLS pages are allocated only when a shard actually starts.
The shard descriptor stores the resulting pointer in SHARD_OFF_TLS_CTX.
If normal page allocation fails, the runtime can attempt the high-page allocator.
This keeps an unused 128-slot plan from immediately reserving tens of megabytes of TLS workspace.
Reset semantics
A TLS reset zeros the entire private workspace, returns its state to the idle state, and increments a handshake generation counter.
That generation is useful because the same allocated pages can be reused across reconnects without pretending the new handshake is the same logical TLS session.
ClientHello construction
The sharded path builds its own ClientHello from the shard's private workspace.
The process includes:
- fresh 32-byte client random
- TLS version fields
- cipher suite selection
- compression field
- SNI extension
- other required extensions
- handshake/transcript accounting
The current sharded path is based on the proven singleton TLS engine rather than a brand-new protocol implementation.
Handshake transcript
TLS Finished verification depends on the exact handshake transcript.
The sharded workspace keeps a bounded transcript buffer and explicitly appends handshake bytes.
Before appending, the implementation checks remaining capacity.
That is important because transcript overflow in C/assembly would otherwise become memory corruption, not a clean high-level exception.
Private sequence state
TLS record protection uses read and write sequence numbers.
Each shard's workspace owns separate:
write sequence high/low
read sequence high/low
write-enabled state
read-enabled state
RX byte count
TX byte count
Those values cannot be shared between shard connections.
Failure propagation
TLS failure marks the shard TLS state failed and sets a shard-level error if one has not already been recorded.
The shard lifecycle code then sees that failed state and transitions the entire shard into backoff/recovery.
This keeps TLS from trying to recover independently on top of stale TCP state.
TCP binding
The sharded TLS engine binds to the receive callback of one shard's TCP context.
That gives the transport chain a direct identity:
shard descriptor
│
├─ TCP workspace
│ │
│ └─ RX callback
│ │
└────────────▼
TLS workspace
There is no need to inspect a global socket table to guess which TLS parser should receive the bytes.
WebSocket workspace
After TLS authentication, the shard allocates a separate 4 KiB WebSocket workspace.
The workspace holds:
- readiness state
- request length
- raw WebSocket nonce
- base64 key
- accept-input buffer
- SHA-1 result
- expected base64 accept value
- HTTP upgrade request
Again, the point is ownership: each shard has its own nonce/request/accept state.
HTTP Upgrade
The WebSocket layer builds an HTTP request using a fresh random key and sends it through the shard's authenticated TLS path.
The response must include a valid HTTP 101 upgrade and the expected Sec-WebSocket-Accept value.
Code example of WebSocket accept verification
- NASM original
- Execution order
- Rust
- C
- Python
legacy_shard_ws_validate_accept64:
push rbx
push r12
push r13
mov rbx, rcx
mov r12, rdx
mov r13d, r8d
test rbx, rbx
jz .no
mov rbx, [rbx + SHARD_OFF_WS_CTX]
test rbx, rbx
jz .no
cmp dword [rbx + SHARD_WS_READY], 1
jne .no
cmp r13d, 28
jb .no
xor r9d, r9d
.scan:
mov eax, r9d
add eax, 28
cmp eax, r13d
ja .no
xor ecx, ecx
.check:
cmp ecx, 28
jae .yes
mov al, [r12+r9]
cmp al, [rbx + SHARD_WS_ACCEPT_B64 + rcx]
jne .next
inc r9d
inc ecx
jmp .check
.next:
sub r9d, ecx
inc r9d
jmp .scan
.yes:
mov eax, 1
jmp .out
.no:
xor eax, eax
.out:
pop r13
pop r12
pop rbx
ret
1. Validate the shard and WebSocket workspace pointers.
2. Require the workspace to be ready.
3. Reject HTTP headers shorter than the 28-byte Base64 accept value.
4. Scan the header bytes for the exact expected accept string.
5. Return success as soon as all 28 bytes match.
6. Return failure if the scan reaches the end without a match.
fn validate_accept(ws: &WebSocketWorkspace, headers: &[u8]) -> bool {
if !ws.ready || headers.len() < 28 {
return false;
}
headers
.windows(28)
.any(|window| window == ws.accept_b64.as_slice())
}
int validate_accept(const struct ws_ctx *ws,
const uint8_t *headers,
size_t len) {
if (!ws || !ws->ready || len < 28)
return 0;
for (size_t i = 0; i + 28 <= len; ++i) {
if (memcmp(headers + i, ws->accept_b64, 28) == 0)
return 1;
}
return 0;
}
def validate_accept(ws, headers: bytes) -> bool:
if not ws.ready or len(headers) < 28:
return False
return ws.accept_b64 in headers
A TLS connection becoming authenticated is therefore not yet enough to mark the Gateway path usable.
WebSocket framing
Once the upgrade succeeds, the runtime can process Gateway WebSocket frames.
At that point the stack is:
TCP byte stream
↓
TLS records
↓
decrypted application bytes
↓
WebSocket framing
↓
Discord Gateway payloads
Each layer has separate state and error handling.
Why TLS and WebSocket are separate workspaces
Combining them into one monolithic block would be simpler in the short term, but would blur reset boundaries.
A reconnect may need to:
- rebuild TCP
- completely restart TLS
- generate a new WebSocket nonce
- preserve a valid Discord resume session
Those are not the same lifecycle.
Acceptance behavior
For the two-shard hardware milestone, both shards must independently reach:
TCP connected
TLS authenticated
HTTP 101 verified
Gateway Hello
Identify / Resume
READY
heartbeat ACK
and the acceptance check confirms that the TLS and WebSocket workspace pointers differ between shard 0 and shard 1.
TLS workspace allocation and initialization
Each active shard owns 45 pages of TLS state.
- NASM original
- Execution order
- Rust equivalent
- C equivalent
- Python model
| Symbol / register | Meaning |
|---|---|
RBX | Owning shard descriptor. |
SHARD_OFF_TLS_CTX | Pointer field inside the shard descriptor. |
SHARD_TLS_PAGES | 45 pages. |
SHARD_TLS_BYTES | 45 × 4096 = 184,320 bytes. |
RAX | Allocation result / returned TLS workspace pointer. |
legacy_shard_tls_alloc64:
push rbx ; Preserve RBX because it will hold the shard context.
mov rbx, rcx ; RBX = owning shard descriptor.
test rbx, rbx ; Reject a null shard pointer.
jz .bad ; Null owner cannot receive TLS state.
mov rax, [rbx + SHARD_OFF_TLS_CTX] ; RAX = existing TLS workspace pointer.
test rax, rax ; Has this shard already allocated TLS state?
jnz .done ; Yes: return the existing workspace.
mov ecx, SHARD_TLS_PAGES ; ECX = 45 pages requested.
call alloc_pages64 ; Try the normal page allocator.
test rax, rax ; Did normal allocation succeed?
jnz .got ; Yes: continue with this allocation.
mov ecx, SHARD_TLS_PAGES ; Request the same number of pages again.
call alloc_high_pages64 ; Fall back to high-memory allocator.
.got:
test rax, rax ; Did either allocator succeed?
jz .bad ; No: return failure.
mov [rbx + SHARD_OFF_TLS_CTX], rax ; Publish workspace ownership in shard descriptor.
push rax ; Preserve workspace pointer across REP STOSQ setup.
mov rdi, rax ; RDI = first byte of TLS workspace.
xor eax, eax ; RAX = zero fill value.
mov ecx, SHARD_TLS_BYTES / 8 ; ECX = qword count covering all 184,320 bytes.
rep stosq ; Zero all private TLS state.
pop rax ; Restore workspace pointer for return/state write.
mov dword [rax + STLS_STATE], LEGACY_TLS_IDLE ; Initialize TLS state machine to IDLE.
.done:
pop rbx ; Restore caller's RBX.
ret ; Return workspace pointer in RAX.
.bad:
xor eax, eax ; RAX = 0 signals allocation failure.
pop rbx ; Restore caller's RBX.
ret ; Return failure.
1. Check whether this shard already owns a TLS workspace.
2. Try allocating 45 normal pages.
3. Fall back to the high-page allocator if normal allocation fails.
4. Attach the successful allocation to the shard descriptor.
5. Zero the entire 184,320-byte workspace.
6. Initialize the TLS state machine to IDLE.
fn ensure_tls<'a>(
shard: &'a mut ShardContext,
allocator: &mut PageAllocator,
) -> Option<&'a mut TlsWorkspace> {
if shard.tls.is_none() {
let mut mem = allocator
.alloc_pages(45)
.or_else(|| allocator.alloc_high_pages(45))?;
mem.zero_fill(45 * 4096);
mem.state = TlsState::Idle;
shard.tls = Some(mem);
}
shard.tls.as_mut()
}
struct tls_workspace *ensure_tls(struct shard_ctx *shard) {
if (shard->tls) return shard->tls;
void *mem = alloc_pages(45);
if (!mem) mem = alloc_high_pages(45);
if (!mem) return NULL;
memset(mem, 0, 45u * 4096u);
shard->tls = mem;
shard->tls->state = LEGACY_TLS_IDLE;
return shard->tls;
}
def ensure_tls(shard, allocator):
if shard.tls is not None:
return shard.tls
mem = allocator.alloc_pages(45)
if mem is None:
mem = allocator.alloc_high_pages(45)
if mem is None:
return None
mem.clear(45 * 4096)
mem.state = "IDLE"
shard.tls = mem
return mem
One active TLS shard consumes roughly 180 KiB before counting TCP, WebSocket, Discord, RX/TX, session, or reliable-retransmission storage. The 128-slot descriptor pool is therefore not equivalent to 128 fully materialized TLS clients.
SHARD_OFF_TLS_CTX points to a zeroed 45-page workspace with STLS_STATE = LEGACY_TLS_IDLE.
TLS transcript bounds enforcement
TLS Finished verification depends on the exact handshake transcript, so append operations have an explicit capacity check.
- NASM original
- Execution order
- Rust equivalent
- C equivalent
- Python model
| Register | Meaning |
|---|---|
RBX | Owning shard descriptor. |
R12 | Source pointer containing handshake bytes to append. |
R13D | Number of bytes requested. |
RAX | TLS workspace pointer. |
EDX | Existing transcript length. |
ECX | Remaining capacity / copy count. |
mov edx, [rax + STLS_TRANSCRIPT_LEN] ; EDX = bytes already stored in transcript.
mov ecx, LEGACY_TLS_TRANSCRIPT_CAP ; ECX = absolute transcript buffer capacity.
sub ecx, edx ; ECX = remaining free bytes.
cmp r13d, ecx ; Does requested append fit in remaining space?
ja .bad ; No: fail before touching adjacent TLS state.
lea rdi, [rax + STLS_TRANSCRIPT] ; RDI = transcript buffer base.
add rdi, rdx ; Advance RDI to current transcript end.
mov rsi, r12 ; RSI = caller's handshake-byte source.
mov ecx, r13d ; ECX = exact number of bytes to copy.
rep movsb ; Append bytes sequentially to transcript.
mov rax, [rbx + SHARD_OFF_TLS_CTX] ; Reload TLS workspace pointer after string copy.
add [rax + STLS_TRANSCRIPT_LEN], r13d ; Publish the new transcript length.
1. Read the current transcript length.
2. Compute remaining transcript capacity.
3. Reject the append if the requested bytes do not fit.
4. Advance to the current end of the transcript buffer.
5. Copy the handshake bytes.
6. Increase the stored transcript length by exactly the copied byte count.
fn append_transcript(
tls: &mut TlsWorkspace,
bytes: &[u8],
) -> bool {
let remaining = TRANSCRIPT_CAP - tls.transcript_len;
if bytes.len() > remaining {
return false;
}
let start = tls.transcript_len;
let end = start + bytes.len();
tls.transcript[start..end].copy_from_slice(bytes);
tls.transcript_len = end;
true
}
int append_transcript(struct tls_workspace *tls,
const uint8_t *bytes,
uint32_t len) {
uint32_t remaining = TRANSCRIPT_CAP - tls->transcript_len;
if (len > remaining) return 0;
memcpy(tls->transcript + tls->transcript_len, bytes, len);
tls->transcript_len += len;
return 1;
}
def append_transcript(tls, data: bytes) -> bool:
remaining = TRANSCRIPT_CAP - tls.transcript_len
if len(data) > remaining:
return False
start = tls.transcript_len
tls.transcript[start:start + len(data)] = data
tls.transcript_len += len(data)
return True
There is no managed runtime here. If the capacity test were removed or wrong, an oversized handshake transcript would overwrite adjacent cryptographic state in the same TLS workspace.
The transcript contains the new handshake bytes contiguously and STLS_TRANSCRIPT_LEN advances by exactly the appended byte count.