mooncat

mooncat — a native ASGI 3.0 server for MoonBit (← uvicorn), built on moonbitlang/async and the moonasgi SEAM.

asgi
server
uvicorn
http
websocket
moonbit
native
Download zip
Author
Version
0.7.0
License
Apache-2.0
Last updated
13 hours ago
Downloads
37

#mooncat

A native ASGI 3.0 server for MoonBit — ← uvicorn.

Check and Test License mooncakes

mooncat runs a moonasgi application over a real network socket. It sits between moonbitlang/async's native HTTP transport and the ASGI SEAM: it accepts connections, turns each request into a Scope + Receive + Send, and drives your app — exactly the role uvicorn plays for Python.

flowchart LR net["moonbitlang/async<br/>(sockets · TLS · HTTP/1.1)"] --> cat["**mooncat**<br/>accept loop"] cat -->|"Scope / Receive / Send"| asgi(["moonasgi SEAM"]) asgi --> app["your app<br/>(moonapi, …)"]

#Quickstart

// An ASGI app: respond 200 with a text body.
let app : @moonasgi.AsgiApp = (_scope, _receive, send) => {
send(@moonasgi.Event::HttpResponseStart(
status=200, headers=[("content-type", "text/plain")]))
send(@moonasgi.Event::HttpResponseBody(
body=b"Hello from mooncat!", more_body=false))
}

// Serve it (inside an async context / task group):
@mooncat.serve(app, host="127.0.0.1", port=8000)

serve blocks in a keep-alive accept loop until its task is cancelled. Each request is bridged faithfully: the method/path/query/headers become an http Scope, the request body streams through Receive, and HttpResponseStart / HttpResponseBody events are written back via the connection.

#HTTPS / TLS

serve_tls serves the same ASGI app over TLS (← uvicorn's --ssl-certfile / --ssl-keyfile):

@mooncat.serve_tls(app,
certificate_file="certs/cert.pem", // PEM cert (OpenSSL platforms)
private_key_file="certs/key.pem", // PEM key (OpenSSL platforms)
pfx_file="certs/dev.pfx", // PKCS#12 (Windows / SChannel)
port=8443)

Each accepted connection completes a real TLS handshake (@tls.Tls), then a self-built, transport-agnostic HTTP/1.1 codec — request-line + header parser and a response writer that frames single-shot replies with Content-Length and streamed replies with Transfer-Encoding: chunked — drives the moonasgi app over the encrypted stream, with keep-alive and the lifespan protocol intact. Proven end-to-end by a real @http TLS client doing GET over https:// and asserting 200 + body in CI. Regenerate the throwaway localhost test cert with scripts/gen_test_cert.sh.

Design boundaries (honest, not stubs):

  • The certificate is supplied as PEM on OpenSSL platforms (Linux/macOS) and as a PKCS#12 .pfx on Windows (SChannel), because that is what each moonbitlang/async TLS backend accepts; serve_tls selects the right form at compile time.
  • moonbitlang/async exposes its server-side TLS constructor as #internal ("for internal testing only") — it is the only such entry point, and the async suite itself serves HTTPS through it — so mooncat opts into that one alert (warnings = "-alert_internal" in moon.pkg) and will migrate the moment a public API lands.
  • WebSocket-over-TLS (wss://) is not bridged: the async websocket upgrade needs an @http.ServerConnection, which is welded to @socket.Tcp and cannot wrap a @tls.Tls stream. Plaintext serve keeps full WebSocket support; this is a transport-capability boundary, not a behavioural choice.

#Process model

serve_graceful runs the app under uvicorn's process model — a graceful shutdown and a --reload file watcher:

let handle = @mooncat.ShutdownHandle::new()
g.spawn(() => @mooncat.serve_graceful(app, @mooncat.Config::new(port=8000), handle~))
// … later, from anywhere:
handle.shutdown() // stop accepting → drain in-flight → lifespan shutdown → close

handle.shutdown() blocks until the port is free again. The sequence is fixed and verified end-to-end in CI: a slow request that is still in flight when shutdown fires is allowed to finish (the client still sees 200) before lifespan shutdown runs, and the listener is closed only afterwards — a real socket integration test pins the ordering, and a mutation test (running lifespan shutdown before the drain) turns it red. A shutdown can also come from a signal: wire @signal.set_global_cancellation_signals and mooncat runs lifespan shutdown + close under protect_from_cancel on the way down.

serve_graceful drives each connection through the same request path serve does — it builds a real @http.ServerConnection over the accepted socket and reads requests off it directly, rather than owning a private codec. So WebSocket upgrades work under graceful serve too: a ws:// route gets the full frame↔Event bridge, verified in CI by a real @websocket client doing a text + binary round-trip against a serve_graceful server.

reload_watch(dir, on_reload) watches a source tree with @fs.Watcher and fires on_reload on each change — hook it to a ShutdownHandle to turn a file save into a graceful restart.

Multi-worker boundary (honest, not a stub): uvicorn's --workers forks N OS processes that share the port via SO_REUSEPORT. moonbitlang/async exposes neither SO_REUSEPORT nor a fork primitive, and its event loop permits only one outstanding accept per listener (a second concurrent accept on the same handle aborts), so N in-process acceptor tasks aren't expressible. mooncat serves from one acceptor that spawns a concurrent handler per connection — the same concurrency a single uvicorn worker gives on its one event loop. Multi-process fan-out lands when the async layer exposes SO_REUSEPORT or fork.

#HTTP/2 (h2c)

serve_h2c serves the same ASGI app over HTTP/2 cleartext — the prior-knowledge, no-TLS HTTP/2 profile a client reaches with curl --http2-prior-knowledge or a gRPC client:

@mooncat.serve_h2c(app, host="127.0.0.1", port=8000)

The transport is moonrpc's self-built HTTP/2 stack — the same RFC 7540 frame layer and RFC 7541 HPACK engine that carries real gRPC there. mooncat binds it to ASGI: it reads the client connection preface + SETTINGS, HPACK-decodes each request's HEADERS block into an Http Scope (the :method / :path / :scheme / :authority pseudo-headers plus the ordinary headers), streams request DATA to the app as the Receive body, and encodes the response — a HEADERS frame with the :status pseudo-header, then DATA — back over the connection. Requests multiplex on their own stream ids, and the response DATA is split to the peer's maximum frame size and clamped to the connection- and stream-level send windows, resuming on WINDOW_UPDATE (RFC 7540 §6.9).

Proven end to end in CI by a real HTTP/2 (h2c) client built on @socket.Tcp and the same frame + HPACK codecs: it GETs a route and reads 200 + body, POSTs a body the app echoes, drives two multiplexed streams on one connection, and — with a deliberately small advertised window — checks the server clamps each DATA frame to the granted window. The same greet chain runs over h2c too: a real moonapi app answers a genuine HTTP/2 GET, the router resolving the path decoded out of the HPACK block.

h2c rather than h2-over-TLS because the moonbitlang/async TLS layer exposes no ALPN, so the protocol can't be negotiated on a TLS connection yet; h2c is the direct, ALPN-free path. HTTP/3 (QUIC) is a separate later self-build.

#Status

v0 — HTTP/1.1 request → ASGI Scope/Receive/Send → response is working and verified by a real socket round-trip in CI (a server task answers a live @http.get). Landed and CI-verified alongside it:

  • the lifespan protocol — the app runs once under a Lifespan scope, startup is driven before the listener binds and shutdown on the way out, even under cancellation;
  • the full WebSocket frame↔Event bridge — a Connection: upgrade + Upgrade: websocket handshake becomes a websocket Scope driven through the moonasgi SEAM: receive() emits websocket.connect then real inbound text/binary frames as WebSocketReceive (streamed through the Message-as-Reader, so fragments reassemble) and a peer close as WebSocketDisconnect(code); send() turns WebSocketAccept into the deferred 101 handshake, WebSocketSendText/WebSocketSendBytes into message frames, and WebSocketClose(code, reason) into a close frame. Rejecting before accept answers 403; ping/pong are auto-handled at the protocol layer (as in uvicorn). Proven by a real @websocket client doing a full text + binary round-trip and a clean close in CI;
  • a Config exposing the HTTP/1.1 transport knobs the async server honours — dual_stack, reuse_addr, per-server response headers, max_connections, and allow_failure — on top of host/port/backlog. Keep-alive and chunked request/response framing are handled automatically by the async transport.

mooncat also hosts a real moonapi app end to end: a CI integration test builds a moonapi App with a typed /greet/:name route, serves it through serve, and a real @http client GETs it and asserts the JSON body and 200 (and a 404 on a route miss). That's the server half of the suite's greet chain — the two repos cooperating over a live socket, not just compiling together.

HTTPS/TLS serving, the graceful-shutdown + --reload process model, and HTTP/2 (h2c) serving landed too — see the sections above. Roadmap, transliterated from uvicorn feature-by-feature: subprotocol echo into the 101 response (awaits a transport hook), the self-built HTTP/1.1 parser knobs (Expect: 100-continue, buffer limits), further TLS detail (ciphers/mTLS), h2 over TLS once the async layer exposes ALPN, and multi-process prefork once it exposes SO_REUSEPORT or fork.

#Native only

The moonbitlang/async HTTP server is native-only (Linux/macOS) — there is no JS server backend — so mooncat builds and runs on the native target. CI covers ubuntu + macos.

#License

Apache-2.0.

FlowError

pub suberror FlowError {
FlowError(String)
}

A flow-control violation: the peer sent past the limit we advertised (RFC 9000 §4.1), or our own accounting was asked to exceed the peer's limit.

Http3ConnError

pub suberror Http3ConnError {
Http3ConnError(String)
}

A connection-level HTTP/3 error (RFC 9114 §8): a broken control stream or a stream a peer must not have opened.

Http3FrameError

pub suberror Http3FrameError {
Http3FrameError(String)
}

A malformed HTTP/3 frame (a protocol error, distinct from an incomplete read).

LifespanError

pub suberror LifespanError {
LifespanError(String)
}

Raised when an application reports lifespan.startup.failed or lifespan.shutdown.failed, carrying the failure message the app supplied.

QpackError

pub suberror QpackError {
QpackError(String)
}

A malformed or unsupported QPACK field section.

QuicAckError

pub suberror QuicAckError {
QuicAckError(String)
}

A malformed ACK frame's ranges.

QuicPayloadError

pub suberror QuicPayloadError {
QuicPayloadError(String)
}

A truncated or unparsable frame in a packet payload.

ReassemblyError

pub suberror ReassemblyError {
ReassemblyError(String)
}

A conflicting retransmission: a fragment overlaps already-buffered data with different bytes, or contradicts a known final size (RFC 9000 §2.2, §4.5).

StreamStateError

pub suberror StreamStateError {
StreamStateError(String)
}

An illegal stream-state transition.

WsError

pub suberror WsError {
WsError(String)
}

A WebSocket protocol violation detected while framing (a reserved opcode), distinct from the reader's end-of-stream.

AckRangeSet

pub struct AckRangeSet {
ranges : Array[(UInt64, UInt64)]
}

The set of received packet numbers, held as ascending, non-overlapping, non-adjacent inclusive ranges [lo, hi].

AckRangeSet::add

fn AckRangeSet::add(self : AckRangeSet, pn : UInt64) -> Unit

Record that packet number pn was received, coalescing it with any range it touches or bridges.

AckRangeSet::contains

fn AckRangeSet::contains(self : AckRangeSet, pn : UInt64) -> Bool

Whether packet number pn has been received.

AckRangeSet::is_empty

fn AckRangeSet::is_empty(self : AckRangeSet) -> Bool

Whether nothing has been received.

AckRangeSet::largest

fn AckRangeSet::largest(self : AckRangeSet) -> UInt64?

The largest packet number received, or None when empty.

AckRangeSet::new

An empty set (nothing received yet).

AckRangeSet::to_ack_fields

fn AckRangeSet::to_ack_fields(self : AckRangeSet) -> (UInt64, UInt64, Array[(UInt64, UInt64)])?

The ACK frame fields for the current set (RFC 9000 §19.3): the Largest Acknowledged, the First ACK Range (how many contiguous packets below the largest are acked), and the descending (Gap, ACK Range Length) list. None when nothing has been received.

Config

pub(all) struct Config {
host : String
port : Int
backlog : Int
dual_stack : Bool
reuse_addr : Bool
server_headers : Map[String, String]
max_connections : Int?
allow_failure : Bool
graceful_timeout : Int?
root_path : String
logger : Logger
}

Server configuration (← uvicorn Config): the bind address, the listen backlog, and the HTTP/1.1 transport knobs the moonbitlang/async server exposes. Constructed once and handed to serve_config.

backlog mirrors uvicorn's listen(2) backlog. The current moonbitlang/async transport does not expose a backlog setter on its TCP listener, so the value is recorded on the config for parity and future use but is not yet applied to the socket — this is a transport-capability gap, not a behavioural choice.

The remaining fields map one-to-one onto knobs the async server does honour: dual_stack / reuse_addr on the listening socket, server_headers stamped onto every response (uvicorn's Server: header lives here), max_connections for the parallel-client ceiling (uvicorn's limit_concurrency), and allow_failure for whether a handler error tears the whole server down. Keep-alive and chunked request/response framing are handled automatically by the async server (each connection loops over multiple requests, and Content-Length / Transfer-Encoding are chosen by the sender), so they need no explicit knob — mirroring uvicorn's default keep-alive behaviour.

Config::bind

fn Config::bind(self : Config) -> String

The host:port string used to resolve the listen address.

Config::new

fn Config::new(host? : String, port? : Int, backlog? : Int, dual_stack? : Bool, reuse_addr? : Bool, server_headers? : Map[String, String], max_connections? : Int?, allow_failure? : Bool, graceful_timeout? : Int?, root_path? : String, logger? : Logger) -> Config

Build a Config, defaulting to uvicorn's own defaults: host 127.0.0.1, port 8000, backlog 2048. Transport knobs default to the async server's own defaults: reuse_addr on (uvicorn sets SO_REUSEADDR), single-stack binding, no extra response headers, an unbounded connection ceiling, and allow_failure on so a single failing handler never crashes the listener.

graceful_timeout bounds how long serve_graceful waits for in-flight requests to drain before it runs lifespan shutdown anyway (← uvicorn's timeout_graceful_shutdown); None waits until the last request finishes, as uvicorn does by default.

EcdsaPrivateKey

pub(all) struct EcdsaPrivateKey {
d :
BigInt

}

An ECDSA P-256 private key: the scalar d. The ES256 signing key.

EcdsaPrivateKey::from_hex

fn EcdsaPrivateKey::from_hex(d_hex : String) -> EcdsaPrivateKey

Build a P-256 private key from its hex-encoded scalar.

EcdsaPrivateKey::public_key

The public key Q = d·G for this private key.

EcdsaPublicKey

An ECDSA P-256 public key: the curve point (x, y). The ES256 verification key.

EcdsaPublicKey::from_hex

fn EcdsaPublicKey::from_hex(x_hex : String, y_hex : String) -> EcdsaPublicKey

Build a P-256 public key from the hex-encoded affine coordinates — e.g. the two halves of openssl's uncompressed pub point after its 04 prefix.

Http3Conn

pub struct Http3Conn {
role : Http3Role
local_settings : Http3Settings
peer_settings : Http3Settings?
control_seen : Bool
}

One endpoint of an HTTP/3 connection: its role, the settings it advertises, and — once the peer opens its control stream — the peer's settings.

Http3Conn::accept_uni_stream

fn Http3Conn::accept_uni_stream(self : Http3Conn, bytes : Bytes) -> Http3UniEvent raise

Accept a peer's unidirectional stream from its opening bytes: classify it and, for the control stream, decode and keep the peer's SETTINGS. A second control stream, a control stream not opening with SETTINGS, and a server receiving a push stream are all connection errors (RFC 9114 §6.2).

Http3Conn::new

fn Http3Conn::new(role : Http3Role, local_settings : Http3Settings) -> Http3Conn

A connection that will advertise local_settings.

Http3Conn::open_control_stream

fn Http3Conn::open_control_stream(self : Http3Conn) -> Bytes

The bytes to write when opening our control stream: the control-stream type then our SETTINGS frame, which a control stream must send first (RFC 9114 §6.2.1).

Http3Conn::peer_settings

fn Http3Conn::peer_settings(self : Http3Conn) -> Http3Settings?

The peer's advertised settings, once its control stream has been accepted.

Http3Conn::settings_established

fn Http3Conn::settings_established(self : Http3Conn) -> Bool

Whether the peer's control-stream SETTINGS have been received (connection setup complete).

Http3Frame

pub(all) enum Http3Frame {
Data(Bytes)
Headers(Bytes)
CancelPush(UInt64)
Settings(Array[(UInt64, UInt64)])
PushPromise(UInt64, Bytes)
GoAway(UInt64)
MaxPushId(UInt64)
Reserved(UInt64, Bytes)
ReservedHttp2(UInt64)
} derive(Eq,
Debug
)

A decoded HTTP/3 frame. Data/Headers/PushPromise carry their opaque payloads; Settings carries the decoded (identifier, value) pairs. Reserved is any unknown or grease frame type (RFC 9114 §7.2.8 / §9), which a receiver ignores; ReservedHttp2 is one of the frame types reused from HTTP/2 (0x02, 0x06, 0x08, 0x09), which RFC 9114 §7.2 requires be treated as a connection error.

Http3Request

pub(all) struct Http3Request {
meth : String
scheme : String
authority : String
path : String
headers : Array[(String, String)]
body : Bytes
}

A decoded HTTP/3 request: the pseudo-headers pulled out, the remaining header fields, and the body.

Http3Response

pub(all) struct Http3Response {
status : String
headers : Array[(String, String)]
body : Bytes
}

An HTTP/3 response: the status, the header fields, and the body.

Http3Response::encode

fn Http3Response::encode(self : Http3Response) -> Bytes

Encode the response as an HTTP/3 response stream (a HEADERS frame with :status, then DATA).

Http3Role

pub(all) enum Http3Role {
ClientSide
ServerSide
} derive(Eq,
Debug
)

Which side of the connection an endpoint is (RFC 9114 §3.1): a server must not receive a push stream, so the role decides whether one is a protocol violation.

Http3Settings

pub(all) struct Http3Settings {
qpack_max_table_capacity : UInt64
max_field_section_size : UInt64
qpack_blocked_streams : UInt64
} derive(Eq,
Debug
)

An endpoint's HTTP/3 settings.

Http3Settings::from_pairs

fn Http3Settings::from_pairs(pairs : Array[(UInt64, UInt64)]) -> Http3Settings

Read settings from received (identifier, value) pairs, taking the value for each known identifier (0 when absent) and ignoring unknown ones (RFC 9114 §7.2.4.1 grease tolerance).

Http3Settings::new

fn Http3Settings::new(qpack_max_table_capacity : UInt64, max_field_section_size : UInt64, qpack_blocked_streams : UInt64) -> Http3Settings

Settings with the given QPACK table capacity, maximum field section size, and blocked-stream limit.

Http3Settings::to_pairs

fn Http3Settings::to_pairs(self : Http3Settings) -> Array[(UInt64, UInt64)]

The (identifier, value) pairs to carry in a SETTINGS frame.

Http3StreamCtx

pub(all) enum Http3StreamCtx {
ControlCtx
RequestCtx
PushCtx
} derive(Eq,
Debug
)

The stream a frame was received on, for the purpose of frame-placement rules (RFC 9114 §6.2).

Http3UniEvent

pub(all) enum Http3UniEvent {
ControlEstablished(Http3Settings)
QpackEncoder
QpackDecoder
Push(UInt64)
Reserved(UInt64)
} derive(Eq,
Debug
)

The result of accepting a peer's unidirectional stream: the control stream carries the peer's SETTINGS, the QPACK streams carry table instructions, a push stream carries its id, and an unrecognized type is reserved/greased.

Http3UniStream

pub(all) enum Http3UniStream {
ControlStream
PushStream(UInt64)
QpackEncoderStream
QpackDecoderStream
ReservedStream(UInt64)
} derive(Eq,
Debug
)

An HTTP/3 unidirectional stream's type (RFC 9114 §6.2, RFC 9204 §4.2).

Lifespan

Drives an ASGI application's lifespan protocol (← uvicorn LifespanOn).

A single long-lived invocation of the app runs under a Lifespan scope. The server and that invocation exchange messages through two async queues: the server pushes lifespan.startup / lifespan.shutdown onto inbox (which backs the app's Receive), and the app pushes its *.complete / *.failed replies onto outbox (which the app's Send writes to). state seeds the lifespan scope and is shared into request scopes by the caller.

Lifespan::new

fn Lifespan::new(app : async (
Scope
, async () ->
Event
, async (
Event
) -> Unit) -> Unit) -> Lifespan

Create a lifespan driver for app, with empty unbounded message queues and empty lifespan state.

Lifespan::shutdown

async fn Lifespan::shutdown(self : Lifespan, task :
Task
[Unit]) -> Unit

Run ASGI lifespan shutdown: push lifespan.shutdown and await the app's reply, or return immediately if the app invocation has already finished. Raises LifespanError if the app reports lifespan.shutdown.failed.

Lifespan::spawn

Spawn the application under a Lifespan scope as a task in g, returning its handle. The task blocks on receive() until startup() / shutdown() push signals; a lifespan-aware app therefore stays parked for the server's whole life, while an app that ignores the lifespan scope simply returns at once (detected via the returned task in startup / shutdown).

Lifespan::startup

async fn Lifespan::startup(self : Lifespan, task :
Task
[Unit]) -> Unit

Run ASGI lifespan startup: push lifespan.startup and await the app's reply. Returns once the app sends lifespan.startup.complete, or once the app returns without lifespan support (its task finishing wins the race, and startup is treated as a no-op, matching uvicorn's lifespan="auto"). Raises LifespanError if the app reports lifespan.startup.failed.

LogLevel

pub(all) enum LogLevel {
Trace
Debug
Info
Warning
Error
Critical
} derive(Eq)

How much the server says. Ordered, so a level is enabled when it is at least as severe as the configured one — the same arrangement as Python's logging.

Logger

pub(all) struct Logger {
level : LogLevel
access : Bool
write : (String) -> Unit
}

Where log lines go. Writing through a sink rather than straight to stdout is what lets a test read what the server said, and what lets an embedder route the lines into its own logging.

Logger::access_line

fn Logger::access_line(self : Logger, client : String, verb : String, target : String, http_version : String, status : Int) -> Unit

Write the one-line-per-request access log (uvicorn's uvicorn.access), in the same shape: peer, request line, status.

Kept separate from log because it is switched separately: a service behind a proxy that already logs requests wants the server's own messages and not a second copy of the access log.

Logger::enabled

fn Logger::enabled(self : Logger, level : LogLevel) -> Bool

Whether a line at level would be written.

Logger::log

fn Logger::log(self : Logger, level : LogLevel, message : String) -> Unit

Write one server-lifecycle line (uvicorn's uvicorn.error logger, which carries ordinary startup messages as well as failures).

Logger::new

fn Logger::new(level? : LogLevel, access? : Bool, write? : (String) -> Unit) -> Logger

A logger printing to stdout at level, with the access log on — uvicorn's defaults. access off silences the per-request lines while keeping the server's own.

Logger::silent

fn Logger::silent() -> Logger

A logger that says nothing, for an embedder that does its own reporting or a test that would rather not have output.

NewReno

pub struct NewReno {
cwnd : Int64
ssthresh : Int64
bytes_in_flight : Int64
max_datagram_size : Int64
}

A NewReno congestion controller for one connection (RFC 9002 §7).

NewReno::can_send

fn NewReno::can_send(self : NewReno, bytes : Int64) -> Bool

Whether bytes more may be sent without exceeding the congestion window.

NewReno::in_flight

fn NewReno::in_flight(self : NewReno) -> Int64

The bytes currently in flight.

NewReno::new

fn NewReno::new(max_datagram_size : Int64) -> NewReno

A fresh controller (RFC 9002 §7.2): the initial window is min(10·max_datagram_size, max(2·max_datagram_size, 14720)), the slow-start threshold is effectively unbounded, and nothing is in flight.

NewReno::on_ack

fn NewReno::on_ack(self : NewReno, acked_bytes : Int64) -> Unit

Acknowledge acked_bytes of newly acknowledged data (RFC 9002 §7.3.1–§7.3.2): take it out of flight, then grow the window — by the acknowledged bytes in slow start, or by max_datagram_size · acked_bytes / cwnd in congestion avoidance.

NewReno::on_congestion

fn NewReno::on_congestion(self : NewReno) -> Unit

Enter a recovery period on a congestion signal (RFC 9002 §7.3.2): halve the window to the new slow-start threshold, floored at the minimum window of two datagrams.

NewReno::on_packet_lost

fn NewReno::on_packet_lost(self : NewReno, lost_bytes : Int64) -> Unit

Take lost_bytes of a declared-lost packet out of flight (RFC 9002 §7.3): the window reduction itself is a single on_congestion per recovery period.

NewReno::on_sent

fn NewReno::on_sent(self : NewReno, bytes : Int64) -> Unit

Record bytes of a packet sent into flight.

NewReno::ssthresh

fn NewReno::ssthresh(self : NewReno) -> Int64

The slow-start threshold (bytes).

NewReno::window

fn NewReno::window(self : NewReno) -> Int64

The congestion window (bytes).

PacketNumberSpace

pub struct PacketNumberSpace {
next_pn : Int64
received : AckRangeSet
largest_acked : Int64?
ack_eliciting_pending : Bool
}

One packet-number space's send/receive numbering and acknowledgement state.

PacketNumberSpace::ack_pending

fn PacketNumberSpace::ack_pending(self : PacketNumberSpace) -> Bool

Whether an ACK is owed — an ack-eliciting packet has arrived since the last ACK was built.

PacketNumberSpace::build_ack

fn PacketNumberSpace::build_ack(self : PacketNumberSpace, ack_delay : UInt64) -> QuicFrame?

Build the ACK frame acknowledging everything received so far with the given ack_delay, and clear the pending flag. None when nothing has been received.

PacketNumberSpace::largest_acked

fn PacketNumberSpace::largest_acked(self : PacketNumberSpace) -> Int64?

The largest packet number the peer has acknowledged, or None.

PacketNumberSpace::new

A fresh space: next packet number 0, nothing received or acknowledged.

PacketNumberSpace::next_packet_number

fn PacketNumberSpace::next_packet_number(self : PacketNumberSpace) -> Int64

Allocate the next packet number to send, advancing the counter.

PacketNumberSpace::on_ack_received

fn PacketNumberSpace::on_ack_received(self : PacketNumberSpace, largest : Int64) -> Unit

Record that the peer acknowledged up to largest, advancing the high-water mark (an older ACK never lowers it).

PacketNumberSpace::on_packet_received

fn PacketNumberSpace::on_packet_received(self : PacketNumberSpace, pn : Int64, ack_eliciting : Bool) -> Unit

Record that packet number pn arrived. ack_eliciting marks a packet that must be acknowledged (RFC 9000 §13.2.1); a pure-ACK packet is recorded but does not itself oblige a new ACK.

QpackDecoderInst

pub(all) enum QpackDecoderInst {
SectionAck(stream_id~ : Int)
StreamCancel(stream_id~ : Int)
InsertCountIncrement(increment~ : Int)
} derive(Eq,
Debug
)

A decoder-stream instruction (RFC 9204 §4.4).

QpackDynamicTable

pub(all) struct QpackDynamicTable {
entries : Array[(Bytes, Bytes)]
used : Int
capacity : Int
insert_count : Int
dropped : Int
}

A QPACK dynamic table: the live entries, the bytes they occupy, the capacity, the count of entries ever inserted (the next absolute index), and how many have been evicted.

QpackDynamicTable::apply

fn QpackDynamicTable::apply(self : QpackDynamicTable, inst : QpackEncoderInst) -> Bool raise QpackError

Apply an encoder-stream instruction to the table (RFC 9204 §4.3): set the capacity, or insert an entry whose name comes from the static table, a dynamic entry (by relative index), or a literal. Returns whether the resulting insert fit (always true for a capacity change). Raises if a referenced name index is out of range.

QpackDynamicTable::get

fn QpackDynamicTable::get(self : QpackDynamicTable, abs_index : Int) -> (Bytes, Bytes)?

The entry at absolute index abs_index, or None if it has been evicted or never existed (RFC 9204 §3.2.4).

QpackDynamicTable::get_relative

fn QpackDynamicTable::get_relative(self : QpackDynamicTable, rel : Int) -> (Bytes, Bytes)?

The entry at encoder-stream relative index rel (0 = most recently inserted, RFC 9204 §3.2.5).

QpackDynamicTable::insert

fn QpackDynamicTable::insert(self : QpackDynamicTable, name : Bytes, value : Bytes) -> Bool

Insert an entry, evicting as needed. Returns false (adding nothing) when the entry cannot fit even in an empty table (RFC 9204 §3.2.2).

QpackDynamicTable::new

fn QpackDynamicTable::new(capacity : Int) -> QpackDynamicTable

A fresh, empty dynamic table with the given capacity in bytes.

QpackDynamicTable::set_capacity

fn QpackDynamicTable::set_capacity(self : QpackDynamicTable, capacity : Int) -> Unit

Set the table capacity, evicting whatever no longer fits (RFC 9204 §3.2.3).

QpackEncoderInst

pub(all) enum QpackEncoderInst {
SetCapacity(Int)
InsertNameRef(is_static~ : Bool, index~ : Int, value~ : Bytes)
InsertLiteralName(name~ : Bytes, value~ : Bytes)
Duplicate(index~ : Int)
} derive(Eq,
Debug
)

An encoder-stream instruction (RFC 9204 §4.3).

QuicConnection

pub struct QuicConnection {
is_server : Bool
initial_space : PacketNumberSpace
handshake_space : PacketNumberSpace
application_space : PacketNumberSpace
initial_crypto : StreamReassembler
handshake_crypto : StreamReassembler
application_crypto : StreamReassembler
}

A QUIC connection's per-space numbering/ACK state and per-level CRYPTO streams.

QuicConnection::build_ack

fn QuicConnection::build_ack(self : QuicConnection, level : QuicLevel, ack_delay : UInt64) -> QuicFrame?

Build the ACK frame owed at level, or None if nothing has been received there.

QuicConnection::crypto_stream

fn QuicConnection::crypto_stream(self : QuicConnection, level : QuicLevel) -> StreamReassembler

The CRYPTO reassembler for level.

QuicConnection::is_server

fn QuicConnection::is_server(self : QuicConnection) -> Bool

Whether this endpoint is the server.

QuicConnection::new

fn QuicConnection::new(is_server : Bool) -> QuicConnection

A fresh connection (client or server) with empty spaces and CRYPTO streams.

QuicConnection::next_packet_number

fn QuicConnection::next_packet_number(self : QuicConnection, level : QuicLevel) -> Int64

Allocate the next packet number to send at level.

QuicConnection::on_ack_received

fn QuicConnection::on_ack_received(self : QuicConnection, level : QuicLevel, largest : Int64) -> Unit

Record the peer's acknowledgement of up to largest at level.

QuicConnection::on_crypto_frame

fn QuicConnection::on_crypto_frame(self : QuicConnection, level : QuicLevel, offset : UInt64, data : Bytes) -> Bytes raise ReassemblyError

Feed a CRYPTO frame's data at offset and level into that level's handshake stream, returning the newly contiguous TLS handshake bytes now readable (empty while an earlier gap is still outstanding). CRYPTO streams carry no fin.

QuicConnection::on_packet_received

fn QuicConnection::on_packet_received(self : QuicConnection, level : QuicLevel, pn : Int64, ack_eliciting : Bool) -> Unit

Record that a packet numbered pn arrived at level; ack_eliciting marks one that must be acknowledged.

QuicConnection::space

The packet-number space for level.

QuicFrame

pub(all) enum QuicFrame {
Padding(Int)
Ping
Crypto(offset~ : UInt64, data~ : Bytes)
Stream(id~ : UInt64, offset~ : UInt64, fin~ : Bool, data~ : Bytes)
Ack(largest~ : UInt64, delay~ : UInt64, first_range~ : UInt64, ranges~ : Array[(UInt64, UInt64)])
ResetStream(id~ : UInt64, error_code~ : UInt64, final_size~ : UInt64)
StopSending(id~ : UInt64, error_code~ : UInt64)
NewToken(token~ : Bytes)
MaxData(UInt64)
MaxStreamData(id~ : UInt64, max~ : UInt64)
MaxStreams(bidi~ : Bool, max~ : UInt64)
DataBlocked(UInt64)
StreamDataBlocked(id~ : UInt64, max~ : UInt64)
StreamsBlocked(bidi~ : Bool, max~ : UInt64)
NewConnectionId(seq~ : UInt64, retire_prior_to~ : UInt64, conn_id~ : Bytes, reset_token~ : Bytes)
RetireConnectionId(UInt64)
PathChallenge(Bytes)
PathResponse(Bytes)
HandshakeDone
ConnectionClose(error_code~ : UInt64, frame_type~ : UInt64?, reason~ : Bytes)
} derive(Eq,
Debug
)

A decoded QUIC frame. Padding collapses a run of zero bytes to its length; Stream carries application data on a stream, with a byte offset and a fin flag marking the end of the stream (RFC 9000 §19.8).

QuicLevel

pub(all) enum QuicLevel {
Initial
Handshake
Application
} derive(Eq,
Debug
)

A QUIC encryption level / packet-number space (RFC 9001 §4.1.1). 0-RTT shares the Application space, so the three long-lived spaces are Initial, Handshake, and Application.

QuicLongHeader

pub(all) struct QuicLongHeader {
packet_type : QuicLongPacketType
type_specific : Int
version : UInt
dcid : Bytes
scid : Bytes
} derive(Eq,
Debug
)

A long packet header's invariant fields: its type, the type-specific low four bits of the first byte (the packet-number length for Initial/Handshake/0-RTT, unused by Retry), the 32-bit version, and the destination and source connection IDs.

QuicLongPacketType

pub(all) enum QuicLongPacketType {
Initial
ZeroRtt
Handshake
Retry
} derive(Eq,
Debug
)

The four long-header packet types (RFC 9000 §17.2), in the encoding of the first byte's type bits: Initial 0, 0-RTT 1, Handshake 2, Retry 3.

QuicPacketKeys

pub(all) struct QuicPacketKeys {
key : Bytes
iv : Bytes
hp : Bytes
} derive(Eq,
Debug
)

The AEAD packet-protection material for one direction: the 16-byte AES-128 key, the 12-byte IV, and the 16-byte header-protection key (RFC 9001 §5.1, §5.4).

QuicRecovery

pub struct QuicRecovery {
rtt : RttEstimator
tracker : SentPacketTracker
cc : NewReno
max_ack_delay : Int64
granularity : Int64
packet_threshold : Int64
pto_count : Int
last_sent_time : Int64
}

A sender's recovery state for one packet-number space (RFC 9002).

QuicRecovery::can_send

fn QuicRecovery::can_send(self : QuicRecovery, bytes : Int64) -> Bool

Whether bytes more may be sent without exceeding the congestion window.

QuicRecovery::in_flight

fn QuicRecovery::in_flight(self : QuicRecovery) -> Int64

The bytes currently in flight.

QuicRecovery::new

fn QuicRecovery::new(max_datagram_size : Int64, max_ack_delay : Int64, granularity : Int64) -> QuicRecovery

A fresh recovery state: max_datagram_size sizes the congestion window, max_ack_delay caps a peer's reported ACK delay, and granularity floors the timers — all microseconds except the datagram size in bytes. The packet-reordering threshold is kPacketThreshold (3).

QuicRecovery::on_ack_received

fn QuicRecovery::on_ack_received(self : QuicRecovery, frame : QuicFrame, now : Int64, ack_delay : Int64) -> Array[Int64] raise

Process a received ACK frame at now with the peer's reported ack_delay (RFC 9002 §5–§7): sample the RTT off the largest newly acknowledged packet, free the acknowledged bytes in the congestion window, then run loss detection over both thresholds. A non-empty loss is one congestion signal (halving the window) and frees the lost bytes. Returns the packet numbers declared lost — the frames to retransmit. Raises on a malformed ACK frame.

QuicRecovery::on_packet_sent

fn QuicRecovery::on_packet_sent(self : QuicRecovery, pn : Int64, now : Int64, size : Int64) -> Unit

Record an ack-eliciting packet numbered pn sent at now (microseconds) carrying size bytes: track it for acknowledgement and charge the congestion window.

QuicRecovery::on_pto

fn QuicRecovery::on_pto(self : QuicRecovery) -> Unit

Handle a probe timeout (RFC 9002 §6.2.4): back off the timer for the next arming. The caller sends probe packets — retransmitting outstanding frames or new data.

QuicRecovery::outstanding

fn QuicRecovery::outstanding(self : QuicRecovery) -> Array[Int64]

The packet numbers still outstanding.

QuicRecovery::pto

fn QuicRecovery::pto(self : QuicRecovery) -> Int64

The probe timeout (microseconds).

QuicRecovery::pto_count

fn QuicRecovery::pto_count(self : QuicRecovery) -> Int

The number of consecutive probe timeouts without an acknowledgement (the backoff exponent).

QuicRecovery::pto_deadline

fn QuicRecovery::pto_deadline(self : QuicRecovery) -> Int64?

The time (microseconds) at which the probe timeout fires, armed from the last ack-eliciting packet sent and backed off by 2^pto_count (RFC 9002 §6.2.1). None when nothing is outstanding — the timer is disarmed.

QuicRecovery::smoothed_rtt

fn QuicRecovery::smoothed_rtt(self : QuicRecovery) -> Int64

The smoothed RTT (microseconds).

QuicRecovery::window

fn QuicRecovery::window(self : QuicRecovery) -> Int64

The current congestion window (bytes).

QuicSendFlow

pub struct QuicSendFlow {
connection : SendFlow
streams : Map[UInt64, SendFlow]
initial_max_stream_data : UInt64
}

The send-side flow control for a whole connection: the connection-wide window and a window per stream, each created at the peer's initial maximum on first use.

QuicSendFlow::connection_available

fn QuicSendFlow::connection_available(self : QuicSendFlow) -> UInt64

The connection-wide bytes still sendable across all streams.

QuicSendFlow::new

fn QuicSendFlow::new(initial_max_data : UInt64, initial_max_stream_data : UInt64) -> QuicSendFlow

A fresh send-flow state at the peer's initial connection and per-stream maxima.

QuicSendFlow::on_max_data

fn QuicSendFlow::on_max_data(self : QuicSendFlow, new_max : UInt64) -> Unit

Raise the connection-wide limit from a MAX_DATA frame.

QuicSendFlow::on_max_stream_data

fn QuicSendFlow::on_max_stream_data(self : QuicSendFlow, id : UInt64, new_max : UInt64) -> Unit

Raise stream id's limit from a MAX_STREAM_DATA frame.

QuicSendFlow::record_stream_sent

fn QuicSendFlow::record_stream_sent(self : QuicSendFlow, id : UInt64, n : UInt64) -> Unit raise FlowError

Account for sending n bytes on stream id, debiting both the stream and the connection window. Raises if either window would be exceeded — checked before debiting, so neither is left partially charged on failure (RFC 9000 §4.1).

QuicSendFlow::stream_window

fn QuicSendFlow::stream_window(self : QuicSendFlow, id : UInt64) -> UInt64

The bytes that may be sent on stream id right now: the smaller of the connection-wide and the stream's available windows (RFC 9000 §4.1).

QuicSender

pub struct QuicSender {
sched : QuicStreamScheduler
recovery : QuicRecovery
data : Map[UInt64, Bytes]
in_packet : Map[Int64, Array[StreamSend]]
retransmit : Array[StreamSend]
next_pn : Int64
}

A connection's send state for one packet-number space.

QuicSender::new

fn QuicSender::new(initial_max_data : UInt64, initial_max_stream_data : UInt64, max_datagram_size : Int64, max_ack_delay : Int64, granularity : Int64, max_frame : UInt64) -> QuicSender

A fresh sender: initial_max_data/initial_max_stream_data are the peer's advertised flow limits, max_datagram_size sizes the congestion window, max_ack_delay/granularity tune the timers (microseconds), and max_frame caps a STREAM frame's payload.

QuicSender::on_ack

fn QuicSender::on_ack(self : QuicSender, frame : QuicFrame, now : Int64) -> Unit raise

Process a received ACK frame at now: clear acknowledged packets from the recovery state and re-queue the stream data of any packet the ACK reveals as lost, to be retransmitted by a later poll_send (RFC 9002 §6). Raises on a malformed ACK.

QuicSender::on_max_data

fn QuicSender::on_max_data(self : QuicSender, new_max : UInt64) -> Unit

Raise the connection-wide send limit from a received MAX_DATA frame.

QuicSender::on_pto_timeout

fn QuicSender::on_pto_timeout(self : QuicSender, now : Int64) -> Bool

Handle the probe timeout firing at now (RFC 9002 §6.2.4). If the PTO deadline has passed with packets still outstanding, re-queue the oldest outstanding packet's stream data as a probe — a later poll_send puts it back on the wire — and back off the timer for the next arming. Unlike loss detection, this neither declares packets lost nor reduces the congestion window. Returns whether a probe was armed.

QuicSender::outstanding

fn QuicSender::outstanding(self : QuicSender) -> Array[Int64]

The packet numbers still outstanding (sent, not yet acknowledged or declared lost).

QuicSender::poll_send

fn QuicSender::poll_send(self : QuicSender, now : Int64) -> (Int64, Array[QuicFrame])? raise

The next packet to send at now, or None when the congestion window is closed or nothing is queued (RFC 9002 §7). Retransmissions go first, then freshly scheduled stream data; the packet is recorded in the recovery state and its stream sends remembered for retransmission.

QuicSender::queue_fin

fn QuicSender::queue_fin(self : QuicSender, id : UInt64) -> Unit

Mark stream id finished.

QuicSender::queue_stream

fn QuicSender::queue_stream(self : QuicSender, id : UInt64, bytes : Bytes) -> Unit

Queue bytes of application data to send on stream id.

QuicSender::window

fn QuicSender::window(self : QuicSender) -> Int64

The current congestion window (bytes).

QuicServerConn

pub struct QuicServerConn {
initial_keys : QuicPacketKeys
handshake : TlsServerHandshake
conn : QuicConnection
}

A QUIC server connection mid-handshake: the Initial keys, the packet-number/CRYPTO bookkeeping, and the TLS handshake it is driving.

QuicServerConn::client_hello

fn QuicServerConn::client_hello(self : QuicServerConn) -> Bytes

The raw ClientHello the connection received (empty before one arrives): the source a server runs the ECDHE key_share from to derive its handshake secrets.

QuicServerConn::handshake_state

fn QuicServerConn::handshake_state(self : QuicServerConn) -> TlsServerState

The current TLS handshake state.

QuicServerConn::initial_ack_pending

fn QuicServerConn::initial_ack_pending(self : QuicServerConn) -> Bool

Whether an ACK is owed in the Initial space.

QuicServerConn::new

fn QuicServerConn::new(dcid : Bytes) -> QuicServerConn

A server connection for a client whose Destination Connection ID is dcid (the Initial secret and keys derive from it, RFC 9001 §5.2).

QuicServerConn::receive_handshake

fn QuicServerConn::receive_handshake(self : QuicServerConn, packet : Bytes, client_hs_keys : QuicPacketKeys, client_hs_secret : Bytes) -> Bool raise

Receive the client's Handshake-space flight — in this no-client-authentication path, the client Finished (RFC 8446 §4.4.4): unprotect the Handshake packet with the client handshake keys, reassemble its CRYPTO stream, verify the Finished verify_data against client_hs_secret over the transcript through the server Finished, then fold it in to advance the handshake to CONNECTED. Returns whether the handshake is now connected with a Finished that authenticated. Raises if the packet fails to authenticate.

QuicServerConn::receive_initial

fn QuicServerConn::receive_initial(self : QuicServerConn, packet : Bytes) -> Array[Int] raise

Receive a client Initial packet: unprotect it, record its number for acknowledgement, reassemble its CRYPTO frames, and drive the handshake with the contiguous handshake bytes. Returns the handshake message types processed. Raises if the packet fails to authenticate.

QuicServerConn::send_handshake_flight

fn QuicServerConn::send_handshake_flight(self : QuicServerConn, server_hs_secret : Bytes, encrypted_extensions : Bytes, certificate : Bytes, certificate_verify : Bytes, out_dcid : Bytes, out_scid : Bytes, pn : Int64) -> Bytes raise

Send the server's Handshake-space flight (RFC 8446 §4, RFC 9001 §5): encode EncryptedExtensions, Certificate, and CertificateVerify, fold them into the transcript in order, compute the server Finished as HMAC(finished_key, transcript hash throughCertificateVerify) over server_hs_secret, fold the Finished too, and advance the state past the whole flight (RECVD_CH → WAIT_FINISHED). The four messages form one contiguous CRYPTO stream, protected into a Handshake packet with the handshake-space keys quic_packet_keys(server_hs_secret), addressed to out_dcid from out_scid at Handshake packet number pn. server_hs_secret is the server handshake traffic secret the key schedule derives once ECDHE completes.

QuicServerConn::send_handshake_flight_signed

fn QuicServerConn::send_handshake_flight_signed(self : QuicServerConn, server_hs_secret : Bytes, encrypted_extensions : Bytes, certificate : Bytes, signing_key : EcdsaPrivateKey, out_dcid : Bytes, out_scid : Bytes, pn : Int64) -> Bytes raise

Send the server's Handshake-space flight with a real CertificateVerify (RFC 8446 §4.4.3): like send_handshake_flight, but instead of taking the CertificateVerify body it signs the transcript through the Certificate with the certificate's ES256 key. Encode EncryptedExtensions and Certificate, fold them, sign the transcript hash then standing (the hash through the Certificate) into the CertificateVerify body, fold it, then the server Finished. A peer holding the certificate's public key can verify the signature and authenticate the server — the real handshake flight, no placeholder CertificateVerify.

QuicServerConn::send_server_hello

fn QuicServerConn::send_server_hello(self : QuicServerConn, server_hello : TlsServerHello, out_dcid : Bytes, out_scid : Bytes, pn : Int64) -> Bytes

Build the server's ServerHello response: encode it, fold it into the transcript, and protect it into an Initial packet addressed to out_dcid (the client's source connection id) from out_scid, at Initial packet number pn (RFC 9001 §5.3). The state stays at RECVD_CH — the server flight is not complete until the Handshake-space messages (EncryptedExtensions, Certificate, CertificateVerify, Finished) are sent by send_handshake_flight, which advances it to WAIT_FINISHED.

QuicServerConn::transcript_hash

fn QuicServerConn::transcript_hash(self : QuicServerConn) -> Bytes

The transcript hash over the handshake messages processed so far.

QuicShortHeader

pub(all) struct QuicShortHeader {
spin : Bool
key_phase : Bool
pn_length : Int
dcid : Bytes
} derive(Eq,
Debug
)

A short header's fields: the latency spin bit, the key-phase bit, the packet-number length (1–4 bytes, from the low two bits of the first byte plus one), and the destination connection ID.

QuicStreamScheduler

pub struct QuicStreamScheduler {
flow : QuicSendFlow
streams : Map[UInt64, StreamOut]
order : Array[UInt64]
cursor : Int
max_frame : UInt64
}

A round-robin send scheduler over a connection's streams, bounded by flow and a maximum per-frame payload.

QuicStreamScheduler::new

fn QuicStreamScheduler::new(flow : QuicSendFlow, max_frame : UInt64) -> QuicStreamScheduler

A fresh scheduler over flow with frames capped at max_frame bytes.

QuicStreamScheduler::next

The next scheduling decision, or None when no stream can send (all drained or flow-control blocked). Round-robins fairly across the streams and debits the flow control for the bytes scheduled. Raises only on a flow-control accounting error, which the bounds here preclude.

QuicStreamScheduler::on_max_data

fn QuicStreamScheduler::on_max_data(self : QuicStreamScheduler, new_max : UInt64) -> Unit

Raise the connection-wide send limit from a MAX_DATA frame.

QuicStreamScheduler::on_max_stream_data

fn QuicStreamScheduler::on_max_stream_data(self : QuicStreamScheduler, id : UInt64, new_max : UInt64) -> Unit

Raise stream id's send limit from a MAX_STREAM_DATA frame.

QuicStreamScheduler::queue

fn QuicStreamScheduler::queue(self : QuicStreamScheduler, id : UInt64, n : UInt64) -> Unit

Queue n more bytes of application data to send on stream id.

QuicStreamScheduler::queue_fin

fn QuicStreamScheduler::queue_fin(self : QuicStreamScheduler, id : UInt64) -> Unit

Mark stream id finished: a FIN will ride the frame that drains it, or a FIN-only frame.

QuicUdpEndpoint

pub struct QuicUdpEndpoint {
server :
UdpServer

}

A QUIC UDP endpoint: a bound datagram socket.

QuicUdpEndpoint::bind

async fn QuicUdpEndpoint::bind(addr : String) -> QuicUdpEndpoint

Bind an endpoint to addr (e.g. "0.0.0.0:443", or "127.0.0.1:0" for an OS-assigned port). The bound address is available from local_addr.

QuicUdpEndpoint::close

fn QuicUdpEndpoint::close(self : QuicUdpEndpoint) -> Unit

Close the endpoint's socket.

QuicUdpEndpoint::local_addr

The address the endpoint is bound to (the concrete port when bound to port 0).

QuicUdpEndpoint::recv_datagram

async fn QuicUdpEndpoint::recv_datagram(self : QuicUdpEndpoint, max? : Int) -> (Bytes,
Addr
)

Receive the next datagram and its sender's address, copying up to max bytes.

QuicUdpEndpoint::send_datagram

async fn QuicUdpEndpoint::send_datagram(self : QuicUdpEndpoint, data : Bytes, to :
Addr
) -> Unit

Send data as a single datagram to to.

RecvFlow

pub struct RecvFlow {
received : UInt64
consumed : UInt64
limit : UInt64
window : UInt64
}

The receive side of one flow-control window: the peer may send up to the limit we advertised; we grow it as the application consumes data, keeping window bytes of headroom available.

RecvFlow::consume

fn RecvFlow::consume(self : RecvFlow, n : UInt64) -> Unit

Account for the application consuming n more bytes, freeing window space.

RecvFlow::extend_limit

fn RecvFlow::extend_limit(self : RecvFlow) -> UInt64

Extend the advertised limit to consumed + window and return the new value — the MAX_DATA / MAX_STREAM_DATA to send.

RecvFlow::limit

fn RecvFlow::limit(self : RecvFlow) -> UInt64

The current advertised limit.

RecvFlow::new

fn RecvFlow::new(window : UInt64) -> RecvFlow

A receive window of window bytes (the initial advertised limit).

RecvFlow::record_received

fn RecvFlow::record_received(self : RecvFlow, highest_offset : UInt64) -> Unit raise FlowError

Record that data up to absolute byte offset highest_offset has arrived; raises if it passes the limit we advertised (a flow-control violation).

RecvFlow::should_extend

fn RecvFlow::should_extend(self : RecvFlow) -> Bool

Whether the limit should be extended: the limit we could advertise (consumed plus a full window) is at least half a window beyond the current one, so an update is worth sending (RFC 9000 §4.1 auto-tuning; avoids a MAX_DATA per byte).

RecvStream

type RecvStream

One receive stream: its reassembly buffer, per-stream flow window, and highest byte offset seen (used to charge connection-level flow control incrementally).

RecvStreamEvent

pub(all) enum RecvStreamEvent {
Receive(fin~ : Bool)
AllReceived
AppRead
ReceiveReset
AppReadReset
} derive(Eq,
Debug
)

A receive-half event: a STREAM frame arriving (with fin fixing the final size), all data up to the final size received, the application reading all data, a RESET_STREAM arriving, or the application being notified of that reset.

RecvStreamState

pub(all) enum RecvStreamState {
Recv
SizeKnown
DataReceived
DataRead
ResetReceived
ResetRead
} derive(Eq,
Debug
)

The receive half's state (RFC 9000 §3.2).

RttEstimator

pub struct RttEstimator {
latest_rtt : Int64
min_rtt : Int64
smoothed_rtt : Int64
rttvar : Int64
has_sample : Bool
}

An endpoint's RTT estimate for one packet-number space (RFC 9002 §5).

RttEstimator::latest

fn RttEstimator::latest(self : RttEstimator) -> Int64

The latest RTT sample (microseconds).

RttEstimator::min

fn RttEstimator::min(self : RttEstimator) -> Int64

The minimum RTT seen (microseconds).

RttEstimator::new

A fresh estimator with no samples yet.

RttEstimator::pto

fn RttEstimator::pto(self : RttEstimator, max_ack_delay : Int64, granularity : Int64) -> Int64

The Probe Timeout duration (RFC 9002 §6.2.1): smoothed_rtt + max(4·rttvar, granularity) +max_ack_delay. Before the first sample it is the initial RTT-based 2·initial_rtt; here, with no sample, it falls back to granularity + max_ack_delay.

RttEstimator::smoothed

fn RttEstimator::smoothed(self : RttEstimator) -> Int64

The smoothed RTT (microseconds).

RttEstimator::update

fn RttEstimator::update(self : RttEstimator, latest_rtt : Int64, ack_delay : Int64, max_ack_delay : Int64) -> Unit

Fold in a new RTT sample (RFC 9002 §5.3). latest_rtt is the measured round trip and ack_delay the peer's reported delay before sending the ACK, both in microseconds; ack_delay is capped at max_ack_delay and subtracted only when doing so keeps the sample at or above min_rtt. The first sample seeds the estimate; later ones update the EWMA.

RttEstimator::variation

fn RttEstimator::variation(self : RttEstimator) -> Int64

The RTT variation, the mean deviation estimate (microseconds).

SendFlow

pub struct SendFlow {
sent : UInt64
limit : UInt64
}

The send side of one flow-control window: bytes we have sent toward the peer's advertised limit.

SendFlow::available

fn SendFlow::available(self : SendFlow) -> UInt64

How many more bytes may be sent right now (0 when blocked).

SendFlow::is_blocked

fn SendFlow::is_blocked(self : SendFlow) -> Bool

Whether the window is exhausted (a STREAM_DATA_BLOCKED / DATA_BLOCKED condition).

SendFlow::new

fn SendFlow::new(initial_max : UInt64) -> SendFlow

A send window with the peer's initial maximum.

SendFlow::record_sent

fn SendFlow::record_sent(self : SendFlow, n : UInt64) -> Unit raise FlowError

Account for sending n bytes; raises if that would exceed the peer's limit.

SendFlow::sent

fn SendFlow::sent(self : SendFlow) -> UInt64

Total bytes sent so far.

SendFlow::update_limit

fn SendFlow::update_limit(self : SendFlow, new_max : UInt64) -> Unit

Adopt a new limit from a MAX_DATA / MAX_STREAM_DATA frame; the limit only ever increases (an old, smaller value is ignored — RFC 9000 §4.1).

SendStreamEvent

pub(all) enum SendStreamEvent {
Write(fin~ : Bool)
AllAcked
ResetStream
ResetAcked
} derive(Eq,
Debug
)

A send-half event: writing a STREAM frame (with fin on the last), all sent data being acknowledged, sending a RESET_STREAM, or that reset being acknowledged.

SendStreamState

pub(all) enum SendStreamState {
Ready
Send
DataSent
DataRecvd
ResetSent
ResetRecvd
} derive(Eq,
Debug
)

The send half's state (RFC 9000 §3.1).

SentPacket

pub(all) struct SentPacket {
pn : Int64
time_sent : Int64
size : Int64
}

A sent, not-yet-acknowledged ack-eliciting packet (RFC 9002 §A.1): its number, the send time in microseconds, and the bytes it counts toward the congestion window.

SentPacketTracker

pub struct SentPacketTracker {
outstanding : Array[SentPacket]
largest_acked : Int64
}

The outstanding ack-eliciting packets in one packet-number space and the largest packet number acknowledged so far (-1 before any ACK).

SentPacketTracker::detect_lost

fn SentPacketTracker::detect_lost(self : SentPacketTracker, threshold : Int64) -> Array[Int64]

Declare lost every outstanding packet sent at least threshold packets before the largest acknowledged (RFC 9002 §6.1.1, kPacketThreshold = 3): a packet pn is lost when largest_acked - pn >= threshold. Returns the lost packet numbers and stops tracking them. Nothing is lost before the first ACK.

SentPacketTracker::detect_lost_packets

fn SentPacketTracker::detect_lost_packets(self : SentPacketTracker, now : Int64, packet_threshold : Int64, time_threshold : Int64) -> Array[SentPacket]

Declare lost every outstanding packet meeting either the packet threshold (§6.1.1) or the time threshold (§6.1.2), returning the lost records — with their sizes, so the caller can take them out of the congestion window — and removing them from the outstanding set.

SentPacketTracker::detect_lost_time

fn SentPacketTracker::detect_lost_time(self : SentPacketTracker, now : Int64, time_threshold : Int64) -> Array[Int64]

Declare lost every outstanding packet sent before the largest acknowledged and older than time_threshold microseconds at now (RFC 9002 §6.1.2): lost when largest_acked > pn and now - time_sent >= time_threshold. Returns the lost packet numbers and stops tracking them.

SentPacketTracker::largest_acked

fn SentPacketTracker::largest_acked(self : SentPacketTracker) -> Int64

The largest acknowledged packet number, or -1 before any ACK.

SentPacketTracker::new

A fresh tracker with nothing outstanding.

SentPacketTracker::on_ack

fn SentPacketTracker::on_ack(self : SentPacketTracker, acked : Array[Int64]) -> Int64

Process an ACK acknowledging the packet numbers acked: drop them from the outstanding set and advance largest_acked. Returns the bytes newly taken out of flight.

SentPacketTracker::on_ack_ranges

fn SentPacketTracker::on_ack_ranges(self : SentPacketTracker, ranges : Array[(Int64, Int64)]) -> Int64

Process an ACK whose acknowledged packets are the ascending inclusive ranges: drop every outstanding packet that falls in a range and advance largest_acked. Returns the bytes newly taken out of flight — the range-based path a real ACK frame drives.

SentPacketTracker::on_sent

fn SentPacketTracker::on_sent(self : SentPacketTracker, pn : Int64, time_sent : Int64, size : Int64) -> Unit

Record that an ack-eliciting packet numbered pn was sent at time_sent (microseconds) carrying size bytes in flight.

SentPacketTracker::outstanding

fn SentPacketTracker::outstanding(self : SentPacketTracker) -> Array[Int64]

The packet numbers still outstanding (sent, not yet acknowledged or declared lost).

SentPacketTracker::time_of

fn SentPacketTracker::time_of(self : SentPacketTracker, pn : Int64) -> Int64?

The send time (microseconds) of the outstanding packet numbered pn, or None if it is not currently outstanding.

ShutdownHandle

pub struct ShutdownHandle {
request :
Queue
[Unit]
done :
Queue
[Unit]
}

A handle for driving a graceful shutdown of serve_graceful from outside the serving task (← uvicorn's Server.should_exit / handle_exit). Hand one to serve_graceful, then call shutdown() from any other task to stop the server: it stops accepting, waits for in-flight requests to drain, runs the ASGI lifespan shutdown, and closes the listener, in that order.

Two async queues carry the handshake. request receives the shutdown trigger (a signal delivered through the runtime's global cancellation reaches the server the same way — see serve_graceful). done is posted once the server has finished the whole shutdown sequence, so shutdown() can block until the port is actually free.

ShutdownHandle::new

Create an idle shutdown handle with empty unbounded signal queues.

ShutdownHandle::request_stop

async fn ShutdownHandle::request_stop(self : ShutdownHandle) -> Unit

Request a graceful shutdown without waiting for it to finish.

ShutdownHandle::shutdown

async fn ShutdownHandle::shutdown(self : ShutdownHandle) -> Unit

Request a graceful shutdown and block until the server has drained in-flight requests, run lifespan shutdown, and closed the listener. Returns once the listen port is free again.

StreamDirection

pub(all) enum StreamDirection {
Bidirectional
Unidirectional
} derive(Eq,
Debug
)

Whether a stream carries data one way or both (RFC 9000 §2.1).

StreamInitiator

pub(all) enum StreamInitiator {
Client
Server
} derive(Eq,
Debug
)

Which endpoint opened a stream (RFC 9000 §2.1).

StreamManager

pub struct StreamManager {
streams : Map[UInt64, RecvStream]
conn_flow : RecvFlow
conn_bytes : UInt64
stream_window : UInt64
}

The set of receive streams plus the connection-level receive window.

StreamManager::connection_max_data_update

fn StreamManager::connection_max_data_update(self : StreamManager) -> UInt64?

Whether the connection-level receive limit should be extended, and the new MAX_DATA value to advertise (RFC 9000 §4.1). Call extend counterpart on the returned flow if non-None.

StreamManager::new

fn StreamManager::new(conn_window : UInt64, stream_window : UInt64) -> StreamManager

A manager advertising conn_window of connection-level credit and stream_window per stream.

StreamManager::on_stream_frame

fn StreamManager::on_stream_frame(self : StreamManager, id : UInt64, offset : UInt64, data : Bytes, fin : Bool) -> Bytes raise

Route a STREAM frame for id carrying data at offset (with fin on the last fragment): enforce the stream and connection flow-control limits, reassemble, and return the contiguous bytes now deliverable, charging that read against both windows.

StreamManager::read

fn StreamManager::read(self : StreamManager, id : UInt64) -> Bytes

Deliver any further contiguous bytes now readable on id (empty if the stream is unknown or nothing new is contiguous), charging them against both windows.

StreamManager::stream_count

fn StreamManager::stream_count(self : StreamManager) -> Int

The number of streams currently tracked.

StreamOut

pub(all) struct StreamOut {
pending : UInt64
offset : UInt64
fin_queued : Bool
fin_sent : Bool
}

A stream's queued output: bytes still to frame, the next offset to send from, and whether a FIN is queued and has been emitted.

StreamReassembler

pub struct StreamReassembler {
consumed : UInt64
intervals : Array[(UInt64, Bytes)]
final_size : UInt64?
}

A stream reassembly buffer. consumed is the next byte offset the reader has not yet taken; intervals are the buffered runs beyond it, sorted and non-overlapping, each starting at or after consumed; final_size is the stream's total length once a fin has been seen.

StreamReassembler::consumed

fn StreamReassembler::consumed(self : StreamReassembler) -> UInt64

The next contiguous byte offset the reader has consumed.

StreamReassembler::final_size

fn StreamReassembler::final_size(self : StreamReassembler) -> UInt64?

The stream's final size, if a fin has been received.

StreamReassembler::insert

fn StreamReassembler::insert(self : StreamReassembler, offset : UInt64, data : Bytes, fin : Bool) -> Unit raise ReassemblyError

Accept a fragment carrying data at offset, with fin marking it as the last. A fragment wholly before the read cursor is ignored; one that overlaps buffered bytes with a different value, or that contradicts a known final size, raises.

StreamReassembler::is_complete

fn StreamReassembler::is_complete(self : StreamReassembler) -> Bool

Whether the whole stream has arrived and been read (the read cursor reached the final size).

StreamReassembler::new

A fresh reassembler positioned at the start of the stream.

StreamReassembler::read

fn StreamReassembler::read(self : StreamReassembler) -> Bytes

Take the contiguous run of bytes starting at the read cursor, advancing it past what is returned. Empty when the next expected offset has not arrived.

StreamSend

pub(all) struct StreamSend {
stream : UInt64
offset : UInt64
length : UInt64
fin : Bool
}

A scheduling decision: send length bytes on stream at offset, ending the stream when fin.

TlsCert

pub(all) struct TlsCert {
certificate_file : String
private_key_file : String
pfx_file : String
}

TLS certificate material for HTTPS serving (← uvicorn's ssl_certfile / ssl_keyfile). The backend the moonbitlang/async TLS layer uses is platform-specific, so both forms are carried:

  • certificate_file + private_key_file — PEM files, used by the OpenSSL backend (Linux / macOS, the CI platforms);
  • pfx_file — a PKCS#12 bundle, used by the SChannel backend (Windows).

serve_tls picks the right pair at compile time. Supply whichever your deployment targets; a self-signed certs/ pair for tests is generated with scripts/gen_test_cert.sh.

TlsClientHello

pub(all) struct TlsClientHello {
random : Bytes
session_id : Bytes
cipher_suites : Array[Int]
extensions : Array[TlsExtension]
} derive(Eq,
Debug
)

A ClientHello (RFC 8446 §4.1.2), less the fixed legacy fields.

TlsClientState

pub(all) enum TlsClientState {
WaitServerHello
WaitEncryptedExtensions
WaitCertCertReq
WaitCert
WaitCertVerify
WaitFinished
Connected
} derive(Eq,
Debug
)

A client's handshake state (RFC 8446 A.1), after the ClientHello has been sent.

TlsClientState::new

The initial state, having just sent the ClientHello (START -> WAIT_SH; sending the ClientHello is the client's output action, not a received message).

TlsExtension

pub(all) struct TlsExtension {
ext_type : Int
data : Bytes
} derive(Eq,
Debug
)

A TLS extension: a two-byte type and its opaque data (RFC 8446 §4.2).

TlsServerHandshake

pub struct TlsServerHandshake {
state : TlsServerState
transcript : TranscriptHash
buffer : Bytes
client_hello : Bytes
}

A server-side TLS 1.3 handshake in progress.

TlsServerHandshake::client_hello

fn TlsServerHandshake::client_hello(self : TlsServerHandshake) -> Bytes

The raw ClientHello message the handshake received (empty until one arrives). A server needs it to pull the client's key_share and run the ECDHE that derives the handshake secrets.

TlsServerHandshake::feed

fn TlsServerHandshake::feed(self : TlsServerHandshake, bytes : Bytes) -> Array[Int] raise

Feed received CRYPTO bytes: process every complete handshake message now available — add it to the transcript and drive the state machine — buffering any trailing partial message. Returns the handshake types processed, in order.

TlsServerHandshake::is_connected

fn TlsServerHandshake::is_connected(self : TlsServerHandshake) -> Bool

Whether the handshake has completed.

TlsServerHandshake::new

A fresh handshake, awaiting the ClientHello.

TlsServerHandshake::record_sent

fn TlsServerHandshake::record_sent(self : TlsServerHandshake, message : Bytes) -> Unit

Fold a handshake message the server sends (ServerHello, EncryptedExtensions, Certificate, ...) into the transcript, keeping it in message order.

TlsServerHandshake::sent_flight

fn TlsServerHandshake::sent_flight(self : TlsServerHandshake, request_client_cert : Bool) -> Unit raise

Advance the state machine past the server's own flight (after the ClientHello), waiting for a client certificate when request_client_cert is set.

TlsServerHandshake::state

The current handshake state.

TlsServerHandshake::transcript_hash

fn TlsServerHandshake::transcript_hash(self : TlsServerHandshake) -> Bytes

The running transcript hash over every message seen so far, in order.

TlsServerHello

pub(all) struct TlsServerHello {
random : Bytes
session_id : Bytes
cipher_suite : Int
extensions : Array[TlsExtension]
} derive(Eq,
Debug
)

A ServerHello (RFC 8446 §4.1.3): a single chosen cipher suite, no compression.

TlsServerState

pub(all) enum TlsServerState {
Start
RecvdClientHello
WaitCert
WaitCertVerify
WaitFinished
Connected
} derive(Eq,
Debug
)

A server's handshake state (RFC 8446 A.2).

TlsServerState::new

The initial state, awaiting the ClientHello.

TranscriptHash

pub struct TranscriptHash {
messages :
Buffer

}

A running transcript hash over the handshake messages seen so far.

TranscriptHash::add

fn TranscriptHash::add(self : TranscriptHash, message : Bytes) -> Unit

Append a handshake message (its full HandshakeType + Length + body encoding) to the transcript.

TranscriptHash::hash

fn TranscriptHash::hash(self : TranscriptHash) -> Bytes

The SHA-256 transcript hash over every message added so far (RFC 8446 §4.4.1).

TranscriptHash::new

A fresh, empty transcript.

TransportParam

pub(all) struct TransportParam {
id : UInt64
value : Bytes
} derive(Eq,
Debug
)

A transport parameter: an identifier and its raw value bytes. Integer parameters carry a varint-encoded value; use transport_param_int / transport_param_as_int to build and read those.

WsFrame

pub(all) struct WsFrame {
fin : Bool
opcode : WsOpcode
payload : Bytes
} derive(Eq,
Debug
)

A decoded WebSocket frame (RFC 6455 §5.2): its FIN bit, opcode, and already-unmasked payload.

WsMessage

pub(all) enum WsMessage {
WsText(Bytes)
WsBinary(Bytes)
WsClose(Int, Bytes)
} derive(Eq,
Debug
)

A complete WebSocket application message read off a raw stream: a text or binary message with its reassembled payload bytes, or a close with its code and reason bytes. Control frames never surface here — ws_read_message handles them — matching what uvicorn hands the ASGI app. Text is carried as bytes; the UTF-8 decode to a String happens at the ASGI boundary.

WsOpcode

pub(all) enum WsOpcode {
Continuation
Text
Binary
Close
Ping
Pong
} derive(Eq,
Debug
)

A WebSocket frame opcode (RFC 6455 §5.2): the data opcodes (continuation / text / binary) and the control opcodes (close / ping / pong).

WsOpcode::to_int

fn WsOpcode::to_int(self : WsOpcode) -> Int

The 4-bit wire value of an opcode.

ack_fields_to_ranges

fn ack_fields_to_ranges(largest : UInt64, first_range : UInt64, pairs : Array[(UInt64, UInt64)]) -> Array[(UInt64, UInt64)] raise QuicAckError

Reconstruct the acknowledged packet-number ranges from an ACK frame's fields (the inverse of to_ack_fields), as ascending inclusive ranges — the receiver side that marks sent packets acknowledged.

aes128_encrypt_block

fn aes128_encrypt_block(schedule : Array[Int], block : Bytes) -> Bytes

Encrypt one 16-byte block under the expanded key (FIPS-197 §5.1 Cipher). The state is column-major: byte k is row k % 4, column k / 4.

aes128_gcm_open

fn aes128_gcm_open(key : Bytes, nonce : Bytes, packet : Bytes, aad : Bytes) -> Bytes?

AEAD_AES_128_GCM open: verify the trailing 16-byte tag of packet and, on success, return the decrypted plaintext; None if authentication fails. The tag comparison runs over all bytes to avoid a length-dependent early exit.

aes128_gcm_seal

fn aes128_gcm_seal(key : Bytes, nonce : Bytes, plaintext : Bytes, aad : Bytes) -> Bytes

AEAD_AES_128_GCM seal: encrypt plaintext under key and the 12-byte nonce with additional data aad, returning ciphertext || tag (16-byte tag appended).

aes128_key_schedule

fn aes128_key_schedule(key : Bytes) -> Array[Int]

The 11 AES-128 round keys as 176 flattened bytes (FIPS-197 §5.2 KeyExpansion). Round r occupies bytes 16*r ..< 16*r+16.

alpn_h3

let alpn_h3 : String

The HTTP/3 ALPN protocol identifier (RFC 9114 §3.1).

base64_decode

fn base64_decode(data : Bytes) -> Bytes

Decode standard base64, ignoring padding and any non-alphabet bytes (RFC 4648 §4).

base64_encode

fn base64_encode(data : Bytes) -> Bytes

Encode data as standard base64 with = padding (RFC 4648 §4).

decode_client_hello

fn decode_client_hello(body : BytesView) -> TlsClientHello?

Decode a ClientHello from a handshake body (RFC 8446 §4.1.2).

decode_server_hello

fn decode_server_hello(body : BytesView) -> TlsServerHello?

Decode a ServerHello from a handshake body (RFC 8446 §4.1.3).

decode_transport_params

fn decode_transport_params(b : BytesView) -> Array[TransportParam]?

Decode a transport-parameter block, preserving order and duplicates so the caller can apply RFC 9000 §7.4's rules. Returns None on a truncated parameter.

der_bit_string

fn der_bit_string(content : Bytes) -> Bytes

A DER BIT STRING (tag 0x03) with no unused trailing bits: a leading 0x00 count then content.

der_explicit

fn der_explicit(tag_num : Int, content : Bytes) -> Bytes

A context-specific constructed value [tag_num] wrapping content (tag 0xA0 | tag_num) — the EXPLICIT tagging X.509 uses for the certificate version and extensions.

der_integer

fn der_integer(magnitude : Bytes) -> Bytes

A DER INTEGER (tag 0x02) from a big-endian magnitude: strip redundant leading zero bytes, then prepend one 0x00 if the top bit is set, so the value stays non-negative (X.690 §8.3).

der_length

fn der_length(n : Int) -> Bytes

Encode a definite length (X.690 §8.1.3): the short form is the single byte for lengths under 128; the long form is 0x80 | n followed by the length in n big-endian bytes.

der_null

fn der_null() -> Bytes

A DER NULL (tag 0x05, empty).

der_octet_string

fn der_octet_string(content : Bytes) -> Bytes

A DER OCTET STRING (tag 0x04).

der_oid

fn der_oid(arcs : Array[Int]) -> Bytes

A DER OBJECT IDENTIFIER (tag 0x06) from its arcs (X.690 §8.19): the first byte encodes 40·arc1 + arc2, and each later arc is base-128 big-endian with the high bit set on every byte but the last.

der_sequence

fn der_sequence(elements : Array[Bytes]) -> Bytes

A DER SEQUENCE (tag 0x30): the concatenated elements.

der_set

fn der_set(elements : Array[Bytes]) -> Bytes

A DER SET (tag 0x31): the concatenated elements.

der_tlv

fn der_tlv(tag : Int, value : Bytes) -> Bytes

A tag-length-value: the tag byte, the definite length of value, then value.

der_utc_time

fn der_utc_time(s : String) -> Bytes

A DER UTCTime (tag 0x17), the YYMMDDHHMMSSZ form X.509 validity uses before 2050.

der_utf8_string

fn der_utf8_string(s : String) -> Bytes

A DER UTF8String (tag 0x0c).

ecdsa_p256_sha256_sign

fn ecdsa_p256_sha256_sign(msg : Bytes, key : EcdsaPrivateKey) -> Bytes

ECDSA P-256 sign with SHA-256 — the ES256 signing primitive (FIPS 186-4 §6.4.1) with the deterministic nonce of RFC 6979. Returns the raw r || s (two 32-byte big-endian integers). Deterministic, so the same message and key always produce the same signature.

ecdsa_p256_sha256_verify

fn ecdsa_p256_sha256_verify(msg : Bytes, sig : Bytes, key : EcdsaPublicKey) -> Bool

ECDSA P-256 verify with SHA-256 — the ES256 primitive (FIPS 186-4 §6.4.2). sig is the raw r || s (two 32-byte big-endian integers). Returns whether the signature is valid for msg under key: r, s in range, then u1·G + u2·Q has x-coordinate ≡ r (mod n).

encode_client_hello

fn encode_client_hello(ch : TlsClientHello) -> Bytes

Encode a ClientHello as a full handshake message. legacy_version is 0x0303 and legacy_compression_methods is the single null method, per RFC 8446 §4.1.2.

encode_frame

fn encode_frame(f : QuicFrame) -> Bytes

Encode a frame to its wire bytes.

encode_long_header

fn encode_long_header(h : QuicLongHeader) -> Bytes

Encode a long header: first byte (1 header-form, 1 fixed bit, 2-bit type, 4 type-specific bits), the version big-endian, then each connection ID prefixed by its one-byte length.

encode_server_hello

fn encode_server_hello(sh : TlsServerHello) -> Bytes

Encode a ServerHello as a full handshake message.

encode_short_header

fn encode_short_header(h : QuicShortHeader) -> Bytes

Encode a short header: first byte (0 header-form, 1 fixed bit, spin, two reserved zero bits, key phase, and the 2-bit packet-number length minus one), then the destination connection ID with no length prefix. The (protected) packet number follows, encoded separately.

encode_transport_params

fn encode_transport_params(params : Array[TransportParam]) -> Bytes

Encode a transport-parameter block: each parameter as its id varint, a length varint, then the value bytes (RFC 9000 §18).

h3_settings_max_field_section_size

let h3_settings_max_field_section_size : UInt64

SETTINGS_MAX_FIELD_SECTION_SIZE (RFC 9114 §7.2.4.1).

h3_settings_qpack_blocked_streams

let h3_settings_qpack_blocked_streams : UInt64

SETTINGS_QPACK_BLOCKED_STREAMS (RFC 9204 §5).

h3_settings_qpack_max_table_capacity

let h3_settings_qpack_max_table_capacity : UInt64

SETTINGS_QPACK_MAX_TABLE_CAPACITY (RFC 9204 §5).

hkdf_expand

fn hkdf_expand(prk : Bytes, info : Bytes, length : Int) -> Bytes

HKDF-Expand (RFC 5869 §2.3): T(i) = HMAC(PRK, T(i-1) || info || i), with the output the concatenation of the T(i) truncated to length. length may not exceed 255 * HashLen (RFC 5869 caps the counter at one byte).

hkdf_expand_label

fn hkdf_expand_label(secret : Bytes, label : Bytes, context : Bytes, length : Int) -> Bytes

HKDF-Expand-Label (RFC 8446 §7.1): expand under the structured HkdfLabel

struct { uint16 length; opaque label<7..255>; opaque context<0..255>; }

where the label is prefixed with "tls13 ". QUIC reuses this verbatim (RFC 9001).

hkdf_extract

fn hkdf_extract(salt : Bytes, ikm : Bytes) -> Bytes

HKDF-Extract (RFC 5869 §2.2): PRK = HMAC(salt, IKM). An empty salt is replaced by a string of HashLen zero bytes, per the RFC.

hmac_sha256

fn hmac_sha256(key : Bytes, msg : Bytes) -> Bytes

HMAC-SHA256 of msg under key: H((K0 ^ opad) || H((K0 ^ ipad) || msg)), where K0 is key hashed down to 32 bytes if it exceeds the 64-byte block, then right-padded with zeros to the block size (RFC 2104 §2).

http3_check_frame

fn http3_check_frame(frame : Http3Frame, ctx : Http3StreamCtx) -> Unit raise Http3FrameError

Raise Http3FrameError (H3_FRAME_UNEXPECTED) when frame is not permitted on ctx.

http3_decode_control_stream

fn http3_decode_control_stream(input : BytesView) -> Http3Settings? raise Http3FrameError

Decode an endpoint's control stream (RFC 9114 §6.2.1): its type prefix must be the control stream, and its first frame a SETTINGS frame, whose settings are returned. None if the stream is not a control stream or does not open with SETTINGS. Raises on a malformed frame.

http3_decode_message

fn http3_decode_message(bytes : Bytes) -> (Array[(String, String)], Bytes) raise Http3FrameError

Decode an HTTP/3 request/response stream back into its header list and body: the first HEADERS frame gives the headers, the DATA frames concatenate into the body, and a second HEADERS frame (trailers) and unknown frames are ignored (RFC 9114 §4.1). Raises if there is no HEADERS frame or a frame is malformed.

http3_decode_stream_type

fn http3_decode_stream_type(input : BytesView) -> (Http3UniStream, Int)?

Decode a unidirectional stream's opening bytes: the type varint (and a push id for a push stream). Returns the stream kind and bytes consumed, or None on a partial read.

http3_encode_control_stream

fn http3_encode_control_stream(settings : Array[(UInt64, UInt64)]) -> Bytes

Encode a control stream (RFC 9114 §6.2.1): the control-stream type, then a SETTINGS frame with settings, which a control stream must send first.

http3_encode_message

fn http3_encode_message(headers : Array[(String, String)], body : Bytes) -> Bytes

Encode a message body as an HTTP/3 request/response stream: a HEADERS frame with the QPACK-encoded headers (static table only), then a DATA frame when body is non-empty.

http3_encode_stream_type

fn http3_encode_stream_type(stream : Http3UniStream) -> Bytes

Encode a unidirectional stream's opening bytes: the type varint, plus the push id for a push stream (RFC 9114 §6.2.2).

http3_frame_allowed

fn http3_frame_allowed(frame : Http3Frame, ctx : Http3StreamCtx) -> Bool

Whether frame is permitted on a stream of kind ctx (RFC 9114 §7.2).

http3_frame_decode

fn http3_frame_decode(input : BytesView) -> (Http3Frame, Int)? raise Http3FrameError

Decode one HTTP/3 frame from the front of input. Returns Some((frame, consumed)) on a complete frame, None when more bytes are needed (a partial frame — the type, the length, or the payload has not fully arrived), and raises on a malformed frame (a truncated SETTINGS pair or a required varint field that is absent).

http3_frame_decode_all

fn http3_frame_decode_all(input : Bytes) -> (Array[Http3Frame], Int) raise Http3FrameError

Decode every complete frame at the front of input, returning the frames and the number of octets consumed (the tail, a partial frame, is left for the next read). A stream reader feeds accumulated bytes and keeps the unconsumed remainder.

http3_frame_encode

fn http3_frame_encode(frame : Http3Frame) -> Bytes

Encode one HTTP/3 frame: its type, its payload length, then the payload.

http3_parse_request

fn http3_parse_request(bytes : Bytes) -> Http3Request? raise Http3FrameError

Decode an HTTP/3 request stream into a request, separating the pseudo-headers from the regular ones (RFC 9114 §4.3.1). None when the required :method and :path are absent. Raises on a malformed stream.

http3_parse_request_strict

fn http3_parse_request_strict(bytes : Bytes) -> Http3Request? raise Http3FrameError

Parse a request stream, rejecting any frame the protocol does not allow on a request stream (RFC 9114 §7.2): a control-only frame such as SETTINGS is an H3_FRAME_UNEXPECTED connection error here, not silently ignored. Otherwise as http3_parse_requestNone when the required :method / :path are absent. This is the request path with frame-placement validation applied.

http3_request

fn http3_request(meth : String, scheme : String, authority : String, path : String, headers : Array[(String, String)], body : Bytes) -> Bytes

Encode an HTTP/3 request: the :method, :scheme, :authority, and :path pseudo- headers (RFC 9114 §4.3.1), then headers, then the body.

http3_response

fn http3_response(status : String, headers : Array[(String, String)], body : Bytes) -> Bytes

Encode an HTTP/3 response: the :status pseudo-header (RFC 9114 §4.3.2), then headers, then the body.

http3_serve

fn http3_serve(request_bytes : Bytes, handler : (Http3Request) -> Http3Response) -> Bytes raise Http3FrameError

Serve one HTTP/3 request stream: decode the request, apply handler, and encode its response stream. A request missing its pseudo-headers is answered with a 400. Raises on a malformed request stream.

http3_settings_decode

fn http3_settings_decode(payload : Bytes) -> Array[(UInt64, UInt64)] raise Http3FrameError

Decode a SETTINGS payload into its (identifier, value) pairs. A trailing partial pair (an identifier with no value, or a truncated varint) is a frame error.

http3_settings_encode

fn http3_settings_encode(pairs : Array[(UInt64, UInt64)]) -> Bytes

Encode a SETTINGS payload: each pair as its identifier varint then its value varint (RFC 9114 §7.2.4).

parse_frame

fn parse_frame(b : BytesView) -> (QuicFrame, Int)?

Parse one frame at the start of b, returning it and the bytes it occupied, or None on a truncated frame or a type this brick does not yet decode. A PADDING frame absorbs the whole run of leading zero bytes.

parse_long_header

fn parse_long_header(b : BytesView) -> (QuicLongHeader, Int)?

Parse a long header at the start of b, returning it and the number of bytes it occupied, or None if the first byte is not a long header or b is truncated before the header ends.

parse_short_header

fn parse_short_header(b : BytesView, dcid_len : Int) -> (QuicShortHeader, Int)?

Parse a short header at the start of b, given the length of the destination connection ID this endpoint issued. Returns the header and the bytes consumed (first byte plus the connection ID), or None if the first byte is a long header or b is shorter than that.

qpack_decode_decoder_inst

fn qpack_decode_decoder_inst(input : BytesView) -> (QpackDecoderInst, Int)?

Decode one decoder-stream instruction from the front of input (RFC 9204 §4.4). Returns (instruction, bytes-consumed), or None on a partial read.

qpack_decode_encoder_inst

fn qpack_decode_encoder_inst(input : BytesView) -> (QpackEncoderInst, Int)? raise QpackError

Decode one encoder-stream instruction from the front of input (RFC 9204 §4.3), dispatching on the pattern in the first byte's high bits. Returns (instruction,bytes-consumed), or None when the instruction has not fully arrived.

qpack_decode_field_section

fn qpack_decode_field_section(input : Bytes) -> Array[(String, String)] raise QpackError

Decode a QPACK field section (encoded over the static table) back into its header list. The prefix's insert count and base are read and required to be zero (this decoder resolves static references only); a field line that references the dynamic table is reported as unsupported.

qpack_decode_field_section_dyn

fn qpack_decode_field_section_dyn(input : Bytes, table : QpackDynamicTable, max_entries : Int) -> Array[(String, String)] raise QpackError

Decode a QPACK field section that may reference the dynamic table, given max_entries (from the negotiated maximum table capacity). Reads the section prefix, then every field line — static or dynamic, indexed or literal, pre- or post-base — into the header list. Raises if a reference is out of range or the section is malformed.

qpack_decode_required_insert_count

fn qpack_decode_required_insert_count(encoded : Int, max_entries : Int, total_inserts : Int) -> Int raise QpackError

Reconstruct the absolute Required Insert Count from its encoded form, max_entries, and the number of inserts the decoder has processed (RFC 9204 §4.5.1.1).

qpack_decode_section_prefix

fn qpack_decode_section_prefix(input : BytesView, max_entries : Int, total_inserts : Int) -> (Int, Int, Int)? raise QpackError

Decode a field-section prefix (RFC 9204 §4.5.1), given max_entries and the decoder's total inserts so far. Returns (req_insert_count, base, bytes-consumed), or None on a partial read.

qpack_decode_string

fn qpack_decode_string(input : BytesView, prefix_bits : Int) -> (Bytes, Int)? raise QpackError

Decode a QPACK string literal whose length uses a prefix_bits-prefix integer and whose H bit sits at position prefix_bits. Returns (octets, bytes-consumed), or None when the string has not fully arrived.

qpack_encode_decoder_inst

fn qpack_encode_decoder_inst(inst : QpackDecoderInst) -> Bytes

Encode a decoder-stream instruction (RFC 9204 §4.4).

qpack_encode_dynamic

fn qpack_encode_dynamic(headers : Array[(String, String)], table : QpackDynamicTable, max_entries : Int, huffman? : Bool) -> (Bytes, Bytes)

Encode headers with dynamic indexing over table (the encoder's view, mutated by the inserts). Returns the encoder-stream instruction block and the field section. max_entries comes from the negotiated maximum table capacity.

qpack_encode_encoder_inst

fn qpack_encode_encoder_inst(inst : QpackEncoderInst, huffman? : Bool) -> Bytes

Encode an encoder-stream instruction (RFC 9204 §4.3). huffman chooses whether the literal strings of the insert instructions are Huffman-coded.

qpack_encode_field_section

fn qpack_encode_field_section(headers : Array[(String, String)], huffman? : Bool) -> Bytes

Encode a header list as a QPACK field section over the static table only: a zero-insert-count / zero-base prefix, then one field line per header — an indexed static entry for an exact match, a static name reference plus a literal value when the name is known, or a fully literal name and value otherwise. huffman chooses whether literal strings are Huffman-coded.

qpack_encode_section_prefix

fn qpack_encode_section_prefix(req_insert_count : Int, base : Int, max_entries : Int) -> Bytes

Encode a field-section prefix (RFC 9204 §4.5.1): the Required Insert Count transmitted modulo 2*max_entries, then the Base as a sign bit (0 when base >= req_insert_count) plus the delta.

qpack_encode_string

fn qpack_encode_string(data : Bytes, prefix_bits : Int, pattern : Int, huffman : Bool) -> Bytes

Encode a QPACK string literal: the H bit at position prefix_bits (set when the data is Huffman-coded), pattern in the bits above it, the octet length as a prefix_bits-prefix integer, then the (raw or Huffman) octets.

qpack_entry_size

fn qpack_entry_size(name : Bytes, value : Bytes) -> Int

The size an entry occupies in the table: its name and value octets plus 32 (RFC 9204 §3.2.1).

qpack_int_decode

fn qpack_int_decode(input : BytesView, prefix_bits : Int) -> (Int, Int)?

Decode an RFC 7541 §5.1 prefix_bits-prefix integer from the front of input (its first byte holds the value in its low prefix_bits, any flags in the high bits are ignored). Returns (value, bytes-consumed), or None when a continuation byte has not yet arrived (a partial read).

qpack_int_encode

fn qpack_int_encode(value : Int, prefix_bits : Int, flags : Int) -> Bytes

Encode value as an RFC 7541 §5.1 prefix_bits-prefix integer, OR-ing flags into the high 8 - prefix_bits bits of the first byte (the representation's pattern).

qpack_max_entries

fn qpack_max_entries(max_capacity : Int) -> Int

The number of entries that fit in a table of max_capacity bytes at the 32-byte minimum entry size — MaxEntries in RFC 9204 §4.5.1.1.

qpack_static_find

fn qpack_static_find(name : String, value : String) -> Int?

The index of the first entry whose name and value both match, for an indexed field line; None if no entry matches exactly.

qpack_static_find_name

fn qpack_static_find_name(name : String) -> Int?

The index of the first entry whose name matches (regardless of value), for a literal field line with a name reference; None if no entry has the name.

qpack_static_get

fn qpack_static_get(index : Int) -> (String, String)?

The (name, value) entry at index, or None if index is out of range.

qpack_static_len

fn qpack_static_len() -> Int

The number of entries in the QPACK static table (99).

quic_ack_frame_ranges

fn quic_ack_frame_ranges(frame : QuicFrame) -> Array[(Int64, Int64)] raise

The acknowledged packet-number ranges an ACK frame conveys, as ascending inclusive (low, high) pairs (RFC 9000 §19.3), or an empty list for a non-ACK frame. Raises if the frame's ranges underflow the packet-number space.

quic_application_keys

fn quic_application_keys(master_secret : Bytes, transcript_hash : Bytes, is_client : Bool) -> QuicPacketKeys

The QUIC Application (1-RTT) AEAD key/iv/hp for this endpoint, derived from the application traffic secret over the ClientHello..server Finished transcript.

quic_client_initial_secret

fn quic_client_initial_secret(dcid : Bytes) -> Bytes

The client's Initial secret, HKDF-Expand-Label(initial, "client in", "", 32).

quic_encode_payload

fn quic_encode_payload(frames : Array[QuicFrame]) -> Bytes

Encode a list of frames into a packet payload — their wire encodings concatenated.

quic_frame_is_ack_eliciting

fn quic_frame_is_ack_eliciting(frame : QuicFrame) -> Bool

Whether a single frame is ack-eliciting (RFC 9000 §13.2.1): every frame except PADDING, ACK, and CONNECTION_CLOSE obliges the peer to acknowledge the packet.

quic_handshake_keys

fn quic_handshake_keys(handshake_secret : Bytes, transcript_hash : Bytes, is_client : Bool) -> QuicPacketKeys

The QUIC Handshake-space AEAD key/iv/hp for this endpoint, derived from the handshake traffic secret over the ClientHello..ServerHello transcript (RFC 9001 §5.2).

quic_handshake_pn_offset

fn quic_handshake_pn_offset(packet : Bytes) -> Int raise QuicPayloadError

The offset of the packet-number field in a received Handshake packet, walking its long header: first byte, version, the two connection ids, and the length field (RFC 9000 §17.2.4 — no token field, unlike Initial).

quic_header_protect

fn quic_header_protect(packet : Bytes, pn_offset : Int, pn_length : Int, hp_key : Bytes) -> Bytes

Apply header protection in place-by-copy: XOR the first byte's form-appropriate low bits and the pn_length packet-number bytes at pn_offset with the mask. The 16-byte sample is taken at pn_offset + 4 (RFC 9001 §5.4.2), the fixed position that assumes the largest packet-number field.

quic_header_unprotect

fn quic_header_unprotect(packet : Bytes, pn_offset : Int, hp_key : Bytes) -> (Bytes, Int)

Remove header protection: recover the first byte, read the packet-number length from its low two bits, unmask that many packet-number bytes, and return the unprotected packet together with the recovered length (RFC 9001 §5.4.1).

quic_hp_mask

fn quic_hp_mask(hp_key : Bytes, sample : Bytes) -> Bytes

The header-protection mask: the first five bytes of AES-128-ECB(hp_key, sample), where sample is 16 bytes of packet ciphertext (RFC 9001 §5.4.2/§5.4.3).

quic_initial_pn_offset

fn quic_initial_pn_offset(packet : Bytes) -> Int raise QuicPayloadError

The offset of the packet-number field in a received Initial packet, walking its long header: first byte, version, the two connection ids, the token, and the length field (RFC 9000 §17.2.2).

quic_initial_secret

fn quic_initial_secret(dcid : Bytes) -> Bytes

The Initial secret shared by both endpoints: HKDF-Extract(initial_salt, DCID), where DCID is the Destination Connection ID from the client's first packet.

quic_key_update

fn quic_key_update(secret : Bytes) -> Bytes

The next-generation 1-RTT traffic secret, HKDF-Expand-Label(secret, "quic ku", "",Hash.length) (RFC 9001 §6.1). Applying it again advances another generation.

quic_loss_time_threshold

fn quic_loss_time_threshold(rtt : RttEstimator, granularity : Int64) -> Int64

The loss time threshold (RFC 9002 §6.1.2): max(kTimeThreshold · max(smoothed_rtt,latest_rtt), granularity) with kTimeThreshold = 9/8, in microseconds.

quic_next_keys

fn quic_next_keys(secret : Bytes) -> (Bytes, QuicPacketKeys)

Roll to the next key generation: the updated traffic secret and the AEAD key/iv/hp derived from it.

quic_packet_keys

fn quic_packet_keys(secret : Bytes) -> QuicPacketKeys

Derive the key/iv/hp triple from a direction's secret (RFC 9001 §5.1): the labels are "quic key", "quic iv", and "quic hp", at the AEAD_AES_128_GCM lengths.

quic_pad_payload

fn quic_pad_payload(payload : Bytes, min_size : Int) -> Bytes

Pad payload out to at least min_size bytes with PADDING frames (zero octets); a payload already that long is returned unchanged (RFC 9000 §14.1 — an Initial packet's payload is padded so the datagram reaches the 1200-byte minimum).

quic_parse_payload

fn quic_parse_payload(payload : Bytes) -> Array[QuicFrame] raise QuicPayloadError

Parse a decrypted packet payload into its frames, consuming the whole payload. A frame that does not parse, or that consumes no bytes, is a payload error.

quic_payload_is_ack_eliciting

fn quic_payload_is_ack_eliciting(frames : Array[QuicFrame]) -> Bool

Whether a payload's frames make the packet ack-eliciting — true if any frame is.

quic_pn_decode

fn quic_pn_decode(largest_pn : Int64, truncated_pn : Int64, pn_nbits : Int) -> Int64

Recover the full packet number from a truncated_pn of pn_nbits bits, given the largest full packet number already received (RFC 9000 A.3): pick the value congruent to truncated_pn that is closest to the next expected number, resolving wraparound with the half-window rule.

quic_pn_encode

fn quic_pn_encode(full_pn : Int64, largest_acked : Int64?) -> Bytes

Encode full_pn as its truncated big-endian packet number, using the shortest length that is unambiguous given largest_acked (RFC 9000 A.2).

quic_pn_len

fn quic_pn_len(full_pn : Int64, largest_acked : Int64?) -> Int

The number of bytes (1–4) needed to encode full_pn given the largest packet number the peer has acknowledged (RFC 9000 §17.1 / A.2): enough bytes to cover twice the number of unacknowledged packets, so wraparound is unambiguous. With no acknowledgement yet, the count is full_pn + 1.

quic_protect_handshake

fn quic_protect_handshake(version : UInt, dcid : Bytes, scid : Bytes, packet_number : Int64, pn_length : Int, payload : Bytes, key : Bytes, iv : Bytes, hp : Bytes) -> Bytes

Protect a QUIC Handshake packet: assemble the long header, AEAD-seal payload with the unprotected header as associated data, then apply header protection (RFC 9001 §5.3–§5.4). sealed_len in the length field accounts for the 16-byte tag.

quic_protect_initial

fn quic_protect_initial(version : UInt, dcid : Bytes, scid : Bytes, token : Bytes, packet_number : Int64, pn_length : Int, payload : Bytes, key : Bytes, iv : Bytes, hp : Bytes) -> Bytes

Protect a QUIC Initial packet: assemble the long header, AEAD-seal payload with the unprotected header as associated data (RFC 9001 §5.3), then apply header protection (§5.4). sealed_len in the length field accounts for the 16-byte tag.

quic_protect_short

fn quic_protect_short(spin : Bool, key_phase : Bool, dcid : Bytes, packet_number : Int64, pn_length : Int, payload : Bytes, key : Bytes, iv : Bytes, hp : Bytes) -> Bytes

Protect a QUIC 1-RTT packet: assemble the short header, AEAD-seal payload with the unprotected header as associated data, then apply header protection (RFC 9001 §5.3–§5.4).

quic_recv_handshake

fn quic_recv_handshake(packet : Bytes, keys : QuicPacketKeys) -> (Array[QuicFrame], Int64)? raise QuicPayloadError

Receive a protected Handshake packet with the handshake-space keys: remove protection (the protected long-header trailer — header protection over the packet number, AEAD over the payload — is shared with the Initial path), parse the payload, and return its frames and packet number. None if authentication fails.

quic_recv_initial

fn quic_recv_initial(packet : Bytes, keys : QuicPacketKeys) -> (Array[QuicFrame], Int64)? raise QuicPayloadError

Receive a protected Initial packet with keys: remove protection, parse the payload, and return its frames and packet number. None if authentication fails.

quic_recv_short

fn quic_recv_short(packet : Bytes, dcid_len : Int, keys : QuicPacketKeys) -> (Array[QuicFrame], Int64)? raise QuicPayloadError

Receive a protected 1-RTT packet with the application-space keys, given the length of the destination connection id this endpoint issued: remove protection, parse the payload, and return its frames and packet number. None if authentication fails.

quic_retry_integrity_tag

fn quic_retry_integrity_tag(retry_without_tag : Bytes, odcid : Bytes) -> Bytes

The Retry integrity tag for a Retry packet (everything up to but not including the tag) given the original Destination Connection ID. The AEAD associated data is the Retry Pseudo-Packet: the ODCID length byte, the ODCID, then the Retry packet.

quic_retry_verify

fn quic_retry_verify(retry_packet : Bytes, odcid : Bytes) -> Bool

Verify a full Retry packet (ending in its 16-byte integrity tag): recompute the tag over the leading bytes and compare, accumulating the difference so the check does not exit early on the first mismatched byte.

quic_send_handshake

fn quic_send_handshake(version : UInt, dcid : Bytes, scid : Bytes, packet_number : Int64, pn_length : Int, frames : Array[QuicFrame], keys : QuicPacketKeys) -> Bytes

Build a protected Handshake packet carrying frames: encode them into a payload and protect it with the handshake-space keys (RFC 9001 §5.3–§5.4).

quic_send_initial

fn quic_send_initial(version : UInt, dcid : Bytes, scid : Bytes, token : Bytes, packet_number : Int64, pn_length : Int, frames : Array[QuicFrame], keys : QuicPacketKeys) -> Bytes

Build a protected Initial packet carrying frames: encode them into a payload and protect it with keys (RFC 9001 §5.3, §5.4).

quic_send_short

fn quic_send_short(spin : Bool, key_phase : Bool, dcid : Bytes, packet_number : Int64, pn_length : Int, frames : Array[QuicFrame], keys : QuicPacketKeys) -> Bytes

Build a protected 1-RTT packet carrying frames: encode them into a payload and protect it with the application-space keys (RFC 9001 §5.3–§5.4).

quic_server_initial_secret

fn quic_server_initial_secret(dcid : Bytes) -> Bytes

The server's Initial secret, HKDF-Expand-Label(initial, "server in", "", 32).

quic_unprotect_initial

fn quic_unprotect_initial(packet : Bytes, pn_offset : Int, key : Bytes, iv : Bytes, hp : Bytes) -> (Bytes, Int64)?

Remove protection from a received Initial packet whose packet-number field starts at pn_offset: strip header protection, reconstruct the packet number and nonce, and AEAD-open the payload with the recovered header as associated data. Returns the plaintext payload and packet number, or None if authentication fails.

quic_unprotect_short

fn quic_unprotect_short(packet : Bytes, dcid_len : Int, key : Bytes, iv : Bytes, hp : Bytes) -> (Bytes, Int64)?

Remove protection from a received 1-RTT packet, given the length of the destination connection id this endpoint issued: the packet-number field starts right after the first byte and the connection id, and the payload runs to the end (RFC 9000 §17.3). The protected long-header trailer strip (header protection over the packet number, AEAD over the payload) is shared with the Initial/Handshake paths. None if it fails.

quic_varint_decode

fn quic_varint_decode(b : BytesView) -> (UInt64, Int)?

Decode a QUIC variable-length integer at the start of b, returning its value and the number of bytes it occupied, or None when b is shorter than the length its first byte's prefix declares.

quic_varint_encode

fn quic_varint_encode(v : UInt64) -> Bytes

Encode v as a QUIC variable-length integer (RFC 9000 §16) in the shortest form that holds it: the two most-significant bits of the first byte select the length (00 → 1 byte / 6-bit value, 01 → 2 / 14-bit, 10 → 4 / 30-bit, 11 → 8 / 62-bit) and the remaining bits carry the value big-endian. v must be below 2^62 (QUIC's varint ceiling).

recv_stream_next

fn recv_stream_next(state : RecvStreamState, event : RecvStreamEvent) -> RecvStreamState raise StreamStateError

The next receive state for event in state (RFC 9000 §3.2). A stream may be reset from any pre-terminal state; the read events apply only once the data or reset has fully arrived.

reload_watch

async fn reload_watch(path : String, on_reload : async () -> Unit) -> Unit

Watch path for source changes and fire on_reload on each batch of events (← uvicorn's --reload file watcher). Backed by @fs.Watcher, which debounces and reports child-file events, so a save to any file under path triggers one reload. on_reload is where a supervisor re-execs the server; wiring it to a ShutdownHandle turns a file save into a graceful restart. Loops until its task is cancelled, always closing the watcher.

send_stream_next

fn send_stream_next(state : SendStreamState, event : SendStreamEvent) -> SendStreamState raise StreamStateError

The next send state for event in state (RFC 9000 §3.1). A stream may be reset from any pre-terminal state; writing after a terminal or reset state is illegal.

serve

async fn serve(app : async (
Scope
, async () ->
Event
, async (
Event
) -> Unit) -> Unit, host? : String, port? : Int, backlog? : Int) -> Unit

Serve a moonasgi ASGI application over native HTTP/1.1 + WebSocket (← uvicorn). Convenience wrapper over serve_config that builds a Config from host / port / backlog. Blocks in a keep-alive accept loop until the running task is cancelled.

serve_config

async fn serve_config(app : async (
Scope
, async () ->
Event
, async (
Event
) -> Unit) -> Unit, config : Config) -> Unit

Serve a moonasgi ASGI application under an explicit Config.

Runs the full lifespan protocol around the accept loop: the app is invoked once under a Lifespan scope and startup() is driven before the listener is bound, and shutdown() is driven on the way out — even when the serving task is cancelled, via protect_from_cancel. Each accepted request is bridged by dispatch, which diverts WebSocket upgrades to the echo path.

serve_graceful

async fn serve_graceful(app : async (
Scope
, async () ->
Event
, async (
Event
) -> Unit) -> Unit, config : Config, handle? : ShutdownHandle) -> Unit

Serve a moonasgi application with a uvicorn-style process model: a graceful shutdown path over a single acceptor that spawns a concurrent handler per connection.

The lifespan protocol runs as in serve_config — startup before the listener binds, shutdown on the way out. A single acceptor task then drives accepted connections through the same dispatch path serve uses (over a hand-built @http.ServerConnection), spawning one handler task per connection so requests are served concurrently — and, because it is the real ServerConnection, WebSocket upgrades bridge here too.

Shutdown is triggered either by handle.shutdown() / handle.request_stop() or by a signal the runtime turns into global cancellation (see the boundary note below). Both converge on one sequence, run under protect_from_cancel so a signal can't abort it midway: stop accepting (cancel the acceptor), drain in-flight requests (bounded by Config::graceful_timeout), run the ASGI lifespan shutdown, then close the listener.

Multi-worker boundary

uvicorn's --workers forks N OS processes that each bind the same port with SO_REUSEPORT for multi-core parallelism. moonbitlang/async exposes neither SO_REUSEPORT on TcpServer nor a fork primitive, and its event loop allows only one outstanding accept per listener handle (wait_read guards on a single waiter), so even N in-process acceptor tasks on one shared listener aren't expressible — a second concurrent accept on the same listener aborts. mooncat therefore serves from one acceptor that spawns a concurrent handler per connection, which is exactly the concurrency a single uvicorn worker provides on its single event loop. Multi-process fan-out is a transport limit, not a behavioural choice; it lands when the async layer exposes SO_REUSEPORT or a fork primitive.

Signal boundary

The only signal hook moonbitlang/async exposes is @signal.set_global_cancellation_signals, which cancels the whole task tree on SIGINT/SIGTERM. mooncat catches that cancellation and still runs lifespan shutdown and closes the listener under protect_from_cancel; but a signal also cancels the in-flight handler tasks, so drain-before-close is only fully honoured on the programmatic ShutdownHandle path. That matches uvicorn's own escalation: a first signal drains, a second forces exit.

serve_h2c

async fn serve_h2c(app : async (
Scope
, async () ->
Event
, async (
Event
) -> Unit) -> Unit, host? : String, port? : Int) -> Unit

Serve a moonasgi ASGI application over HTTP/2 cleartext (h2c) — the prior-knowledge, no-TLS HTTP/2 profile (RFC 7540 §3.4) — reusing moonrpc's self-built HTTP/2 + HPACK engine as the transport. Convenience wrapper over serve_h2c_config that builds a Config from host / port. Blocks in the accept loop until the running task is cancelled.

h2c rather than h2-over-TLS because the moonbitlang/async TLS layer exposes no ALPN, so the protocol can't be negotiated on a TLS connection yet; h2c is the direct, ALPN-free path a client reaches with prior knowledge (as curl --http2-prior-knowledge or a gRPC client does).

serve_h2c_config

async fn serve_h2c_config(app : async (
Scope
, async () ->
Event
, async (
Event
) -> Unit) -> Unit, config : Config) -> Unit

Serve a moonasgi ASGI application over h2c under an explicit Config. Mirrors serve_config / serve_tls_config: the ASGI lifespan protocol runs around the accept loop (startup before the listener binds, shutdown on the way out — even under cancellation, guarded by protect_from_cancel), and each accepted connection is driven by drive_h2c over the self-built HTTP/2 transport.

serve_tls

async fn serve_tls(app : async (
Scope
, async () ->
Event
, async (
Event
) -> Unit) -> Unit, certificate_file~ : String, private_key_file~ : String, pfx_file~ : String, host? : String, port? : Int, backlog? : Int) -> Unit

Serve a moonasgi ASGI application over native HTTP/1.1 over TLS (HTTPS, ← uvicorn's --ssl-certfile/--ssl-keyfile). Convenience wrapper over serve_tls_config that builds the Config from host/port/backlog and the certificate paths.

Runs the full ASGI lifespan protocol around the accept loop (startup before the listener binds, shutdown on exit — even under cancellation), then, for every accepted connection, completes a TLS handshake and drives the app through the encrypted HTTP/1.1 codec.

WebSocket-over-TLS (wss://) is not bridged here: the async websocket upgrade requires an @http.ServerConnection, which is welded to @socket.Tcp and cannot wrap a @tls.Tls stream. Plaintext serve retains full WebSocket support; this is a transport-capability boundary, not a behavioural choice (README §TLS).

serve_tls_config

async fn serve_tls_config(app : async (
Scope
, async () ->
Event
, async (
Event
) -> Unit) -> Unit, config : Config, cert : TlsCert) -> Unit

Serve a moonasgi ASGI application over HTTPS under an explicit Config and TlsCert. Mirrors serve_config: lifespan startup is driven before the listener binds and shutdown on the way out (guarded by protect_from_cancel so it still runs when the serving task is cancelled), and each accepted connection is handled by handle_https_conn.

sha1

fn sha1(msg : Bytes) -> Bytes

The SHA-1 digest of msg (20 bytes).

sha256

fn sha256(msg : Bytes) -> Bytes

The SHA-256 digest of msg (32 bytes).

stream_direction

fn stream_direction(id : UInt64) -> StreamDirection

The directionality of stream id (bit 1).

stream_id_of

fn stream_id_of(initiator : StreamInitiator, direction : StreamDirection, seq : UInt64) -> UInt64

The stream ID for the seq-th (0-based) stream of the given initiator and direction.

stream_initiator

fn stream_initiator(id : UInt64) -> StreamInitiator

The endpoint that initiated stream id (bit 0).

stream_is_bidi

fn stream_is_bidi(id : UInt64) -> Bool

Whether stream id is bidirectional.

stream_is_client_initiated

fn stream_is_client_initiated(id : UInt64) -> Bool

Whether stream id was opened by the client.

stream_is_locally_initiated

fn stream_is_locally_initiated(id : UInt64, is_server : Bool) -> Bool

Whether stream id was opened by this endpoint, given whether it is the server.

stream_is_writable

fn stream_is_writable(id : UInt64, is_server : Bool) -> Bool

Whether this endpoint may send on stream id: it may send on a stream it opened, or on a bidirectional stream its peer opened; it may not send on a peer-opened unidirectional (receive-only) stream (RFC 9000 §2.1, §3).

stream_sequence

fn stream_sequence(id : UInt64) -> UInt64

The stream's sequence number within its type (the ID with its low two bits removed).

tls13_certificate_verify_check

fn tls13_certificate_verify_check(key : EcdsaPublicKey, context : String, transcript_hash : Bytes, body : Bytes) -> Bool

Verify a CertificateVerify message body against the peer's ES256 public key over the transcript (RFC 8446 §4.4.3): parse the SignatureScheme and signature, then check the ECDSA signature over the §4.4.3 signed content. False on a wrong scheme, a malformed body, or an invalid signature.

tls13_certificate_verify_content

fn tls13_certificate_verify_content(context : String, transcript_hash : Bytes) -> Bytes

The content a CertificateVerify signs (RFC 8446 §4.4.3): 64 octets of 0x20, the context string, a single 0x00 separator, then the transcript_hash through the Certificate.

tls13_certificate_verify_sign

fn tls13_certificate_verify_sign(key : EcdsaPrivateKey, context : String, transcript_hash : Bytes) -> Bytes

Sign a CertificateVerify over the transcript with an ES256 key (RFC 8446 §4.4.3): returns the message body — the SignatureScheme (ecdsa_secp256r1_sha256) and the two-byte-length- prefixed raw r||s signature over the §4.4.3 signed content.

tls13_client_ap_traffic_secret

fn tls13_client_ap_traffic_secret(master_secret : Bytes, transcript_hash : Bytes) -> Bytes

The client application (1-RTT) traffic secret, Derive-Secret(Master Secret,"c ap traffic", ClientHello..server Finished).

tls13_client_hs_traffic_secret

fn tls13_client_hs_traffic_secret(handshake_secret : Bytes, transcript_hash : Bytes) -> Bytes

The client handshake traffic secret, Derive-Secret(Handshake Secret, "c hs traffic",ClientHello..ServerHello) (RFC 8446 §7.1).

tls13_cv_context_client

let tls13_cv_context_client : String

The client's CertificateVerify context string (RFC 8446 §4.4.3).

tls13_cv_context_server

let tls13_cv_context_server : String

The server's CertificateVerify context string (RFC 8446 §4.4.3).

tls13_decode_certificate

fn tls13_decode_certificate(body : BytesView) -> (Bytes, Array[Bytes])?

Decode a Certificate message body into its certificate_request_context and the list of DER certificates (RFC 8446 §4.4.2), skipping per-entry extensions. None on a truncated message.

tls13_decode_encrypted_extensions

fn tls13_decode_encrypted_extensions(body : BytesView) -> Array[TlsExtension]?

Decode an EncryptedExtensions body back into its extension list, or None if truncated.

tls13_derive_secret

fn tls13_derive_secret(secret : Bytes, label : Bytes, transcript_hash : Bytes) -> Bytes

Derive-Secret(Secret, Label, Messages) (RFC 8446 §7.1): HKDF-Expand-Label with the transcript hash as context and the hash length as output. transcript_hash is the Transcript-Hash of the messages (use tls13_transcript_hash, or the empty-string hash for the "derived" steps).

tls13_early_secret

fn tls13_early_secret(psk : Bytes) -> Bytes

The Early Secret: HKDF-Extract(0, PSK), with an all-zero PSK when none is used.

tls13_ecdhe_handshake_traffic_secret

fn tls13_ecdhe_handshake_traffic_secret(private_key : Bytes, peer_public : Bytes, transcript_hash : Bytes, is_client : Bool) -> Bytes

An endpoint's handshake traffic secret from the ECDHE exchange and the ClientHello.. ServerHello transcript hash (RFC 8446 §7.1): the client secret when is_client, otherwise the server secret. Both peers derive matching secrets because they share the same DHE.

tls13_ecdhe_secrets_from_client_hello

fn tls13_ecdhe_secrets_from_client_hello(client_hello : Bytes, server_private : Bytes, transcript_hash : Bytes) -> (Bytes, Bytes)?

Both handshake traffic secrets a server derives from a received ClientHello and its own ephemeral private key (RFC 8446 §7.1), as (server_secret, client_secret): pull the client's x25519 key_share, run the ECDHE with server_private over the ClientHello.. ServerHello transcript_hash, and derive each side's secret. None if the message is not a ClientHello or offers no x25519 share. A server protects its own flight with the server secret and verifies the client Finished with the client secret — deriving both from the wire ClientHello, never from the peer's copy.

tls13_ecdhe_shared

fn tls13_ecdhe_shared(private_key : Bytes, peer_public : Bytes) -> Bytes

The x25519 ECDHE shared secret from an endpoint's private key and the peer's public key.

tls13_encode_certificate

fn tls13_encode_certificate(certificate_request_context : Bytes, certs : Array[Bytes]) -> Bytes

Encode a Certificate message body (RFC 8446 §4.4.2): the certificate_request_context (empty for a server certificate sent without a CertificateRequest), then the certificate_list — each entry a three-byte-length DER certificate and a two-byte-length extensions block (empty here).

tls13_encrypted_extensions_body

fn tls13_encrypted_extensions_body(exts : Array[TlsExtension]) -> Bytes

The EncryptedExtensions message body (RFC 8446 §4.3.1): an extension list.

tls13_finished_key

fn tls13_finished_key(base_key : Bytes) -> Bytes

A Finished key: HKDF-Expand-Label(BaseKey, "finished", "", Hash.length) (RFC 8446 §4.4.4), the key that MACs the Finished message's verify_data.

tls13_finished_verify

fn tls13_finished_verify(base_key : Bytes, transcript_hash : Bytes, verify_data : Bytes) -> Bool

Whether a received Finished's verify_data is the one expected for base_key over transcript_hash — a constant set membership, the handshake authentication check.

tls13_finished_verify_data

fn tls13_finished_verify_data(base_key : Bytes, transcript_hash : Bytes) -> Bytes

The Finished verify_data (RFC 8446 §4.4.4): HMAC(finished_key, transcript_hash), where finished_key is HKDF-Expand-Label(base_key, "finished", "", Hash.length) over the sender's handshake traffic secret.

tls13_handshake_secret

fn tls13_handshake_secret(early_secret : Bytes, ecdhe : Bytes) -> Bytes

The Handshake Secret: HKDF-Extract over the ECDHE shared secret, salted by the Early Secret run through the "derived" step (RFC 8446 §7.1).

tls13_handshake_secret_from_ecdhe

fn tls13_handshake_secret_from_ecdhe(private_key : Bytes, peer_public : Bytes) -> Bytes

The Handshake Secret for an x25519 ECDHE exchange with no PSK (RFC 8446 §7.1): the key schedule extracts the shared secret under the Early Secret's derived salt.

tls13_master_secret

fn tls13_master_secret(handshake_secret : Bytes) -> Bytes

The Master Secret: HKDF-Extract over an all-zero IKM, salted by the Handshake Secret run through the "derived" step (RFC 8446 §7.1).

tls13_quic_encrypted_extensions

fn tls13_quic_encrypted_extensions(alpn : String, params : Array[TransportParam]) -> Bytes

A QUIC server's EncryptedExtensions body: the negotiated ALPN protocol and the server's transport parameters (RFC 9001 §8.2 — a QUIC server MUST send quic_transport_parameters).

tls13_server_ap_traffic_secret

fn tls13_server_ap_traffic_secret(master_secret : Bytes, transcript_hash : Bytes) -> Bytes

The server application (1-RTT) traffic secret, Derive-Secret(Master Secret,"s ap traffic", ClientHello..server Finished).

tls13_server_hs_secret_from_client_hello

fn tls13_server_hs_secret_from_client_hello(client_hello : Bytes, server_private : Bytes, transcript_hash : Bytes) -> Bytes?

The server's handshake traffic secret derived from a received ClientHello message alone: pull the client's x25519 key_share and run the ECDHE with the server's ephemeral server_private over the ClientHello..ServerHello transcript_hash. None if the message is not a ClientHello or offers no x25519 share.

tls13_server_hs_traffic_secret

fn tls13_server_hs_traffic_secret(handshake_secret : Bytes, transcript_hash : Bytes) -> Bytes

The server handshake traffic secret, Derive-Secret(Handshake Secret, "s hs traffic",ClientHello..ServerHello).

tls13_transcript_hash

fn tls13_transcript_hash(messages : Bytes) -> Bytes

The transcript hash over a run of handshake messages (RFC 8446 §4.4.1): here the SHA-256 of their concatenation.

tls13_x25519_public

fn tls13_x25519_public(private_key : Bytes) -> Bytes

The ephemeral x25519 public key for a private scalar: x25519(private, base_point).

tls_alpn_extension

fn tls_alpn_extension(protocols : Array[String]) -> TlsExtension

Build an ALPN extension carrying protocols — the offered list in a ClientHello, or the single selected protocol in a server's reply.

tls_client_hello

let tls_client_hello : Int

The handshake message types this layer models (RFC 8446 §4).

tls_client_hello_alpn

fn tls_client_hello_alpn(extensions : Array[TlsExtension]) -> Array[String]

The protocols a ClientHello's ALPN extension offers, in order (empty if it has none).

tls_client_hello_key_shares

fn tls_client_hello_key_shares(extensions : Array[TlsExtension]) -> Array[(Int, Bytes)]

The list of (group, key) shares a ClientHello's key_share extension offers.

tls_client_next

fn tls_client_next(state : TlsClientState, msg_type : Int) -> TlsClientState raise StreamStateError

The next client state on receiving a handshake message of msg_type (RFC 8446 A.1). A message that does not belong in the current state is an unexpected-message error.

tls_decode_alpn

fn tls_decode_alpn(view : BytesView) -> Array[String]

Decode an ALPN ProtocolNameList back into its protocol names, stopping at the declared list length or a truncated entry.

tls_decode_key_share_client

fn tls_decode_key_share_client(view : BytesView) -> Array[(Int, Bytes)]

Decode a ClientHello key_share extension body: the list of (group, key) shares.

tls_decode_key_share_entry

fn tls_decode_key_share_entry(view : BytesView) -> (Int, Bytes, Int)?

Decode one KeyShareEntry from the front of view: returns (group, key, bytes-consumed), or None on a partial read.

tls_decode_key_share_server

fn tls_decode_key_share_server(view : BytesView) -> (Int, Bytes)?

Decode a ServerHello key_share extension body: its single KeyShareEntry as (group, key).

tls_encode_alpn

fn tls_encode_alpn(protocols : Array[String]) -> Bytes

Encode an ALPN ProtocolNameList (RFC 7301 §3.1): a two-byte length of the name list, then each protocol as a one-byte length and its bytes.

tls_encode_handshake

fn tls_encode_handshake(msg_type : Int, body : Bytes) -> Bytes

Frame a handshake body: message type, then a 24-bit big-endian length (RFC 8446 §4).

tls_encode_key_share_client

fn tls_encode_key_share_client(entries : Array[(Int, Bytes)]) -> Bytes

Encode the ClientHello key_share extension body: the client_shares length, then the KeyShareEntry list.

tls_encode_key_share_entry

fn tls_encode_key_share_entry(group : Int, key : Bytes) -> Bytes

Encode one KeyShareEntry: the named group, the key length, then the key (RFC 8446 §4.2.8).

tls_encode_key_share_server

fn tls_encode_key_share_server(group : Int, key : Bytes) -> Bytes

Encode the ServerHello key_share extension body: a single KeyShareEntry.

tls_ext_alpn

let tls_ext_alpn : Int

The application_layer_protocol_negotiation extension type (RFC 7301 §3.1).

tls_ext_key_share

let tls_ext_key_share : Int

The key_share extension type (RFC 8446 §4.2.8).

tls_ext_quic_transport_params

let tls_ext_quic_transport_params : Int

The quic_transport_parameters extension type (RFC 9001 §8.2).

tls_find_extension

fn tls_find_extension(exts : Array[TlsExtension], ext_type : Int) -> TlsExtension?

Look up the first extension of a given type, or None.

tls_group_x25519

let tls_group_x25519 : Int

The x25519 named group (RFC 8446 §4.2.7).

tls_hello_quic_transport_params

fn tls_hello_quic_transport_params(extensions : Array[TlsExtension]) -> Array[TransportParam]

The QUIC transport parameters an extension list carries, in order (empty if the extension is absent or its block is truncated).

tls_key_share_extension

fn tls_key_share_extension(group : Int, key : Bytes) -> TlsExtension

Build a ServerHello key_share extension carrying key for group.

tls_parse_handshake

fn tls_parse_handshake(b : BytesView) -> (Int, Bytes)?

Parse one handshake message, returning its type and body, or None if truncated.

tls_quic_transport_params_extension

fn tls_quic_transport_params_extension(params : Array[TransportParam]) -> TlsExtension

Build a quic_transport_parameters extension carrying params — the client's block in a ClientHello, the server's in its EncryptedExtensions.

tls_selected_alpn

fn tls_selected_alpn(extensions : Array[TlsExtension]) -> String?

The single protocol a server's ALPN extension selected, or None if there is none.

tls_server_hello

let tls_server_hello : Int

ServerHello (RFC 8446 §4.1.3): the server's half of the key exchange.

tls_server_hello_key_share

fn tls_server_hello_key_share(sh : TlsServerHello) -> (Int, Bytes)?

The (group, key) of a ServerHello's key_share extension, or None if it has none.

tls_server_recv

fn tls_server_recv(state : TlsServerState, msg_type : Int) -> TlsServerState raise StreamStateError

The next server state on receiving a handshake message of msg_type (RFC 8446 A.2): the ClientHello that opens the handshake, then the client's Certificate, CertificateVerify, and Finished. An out-of-order message is an unexpected-message error.

tls_server_send_flight

fn tls_server_send_flight(state : TlsServerState, request_client_cert : Bool) -> TlsServerState raise StreamStateError

The server's own action after receiving the ClientHello: it negotiates and sends its whole flight, then waits for the client's Certificate (when request_client_cert is set) or straight for the client Finished. Sending the flight in any other state is an error.

tls_sig_ecdsa_secp256r1_sha256

let tls_sig_ecdsa_secp256r1_sha256 : Int

The ecdsa_secp256r1_sha256 (ES256) SignatureScheme (RFC 8446 §4.2.3).

tp_ack_delay_exponent

let tp_ack_delay_exponent : UInt64

The exponent the peer's ACK Delay field is scaled by.

tp_active_connection_id_limit

let tp_active_connection_id_limit : UInt64

How many connection ids this endpoint will keep active at once.

tp_initial_max_data

let tp_initial_max_data : UInt64

Connection-level flow-control credit the peer starts with.

tp_initial_max_stream_data_bidi_local

let tp_initial_max_stream_data_bidi_local : UInt64

Starting credit on bidirectional streams this endpoint opens.

tp_initial_max_stream_data_bidi_remote

let tp_initial_max_stream_data_bidi_remote : UInt64

Starting credit on bidirectional streams the peer opens.

tp_initial_max_stream_data_uni

let tp_initial_max_stream_data_uni : UInt64

Starting credit on unidirectional streams.

tp_initial_max_streams_bidi

let tp_initial_max_streams_bidi : UInt64

How many bidirectional streams the peer may open.

tp_initial_max_streams_uni

let tp_initial_max_streams_uni : UInt64

How many unidirectional streams the peer may open.

tp_initial_source_connection_id

let tp_initial_source_connection_id : UInt64

The source connection id this endpoint used on its first packet, which is what binds the handshake to the addresses it ran over.

tp_max_ack_delay

let tp_max_ack_delay : UInt64

The longest this endpoint will sit on an acknowledgement.

tp_max_idle_timeout

let tp_max_idle_timeout : UInt64

The transport-parameter identifiers this build recognizes by name (RFC 9000 §18.2). The block is still encoded and decoded generically, so unknown ids pass through.

tp_max_udp_payload_size

let tp_max_udp_payload_size : UInt64

The largest UDP payload this endpoint is willing to receive.

transport_param_as_int

fn transport_param_as_int(p : TransportParam) -> UInt64?

Read a varint-valued transport parameter's integer, or None if its value is not a single well-formed varint.

transport_param_int

fn transport_param_int(id : UInt64, value : UInt64) -> TransportParam

A transport parameter whose value is a single varint integer (RFC 9000 §18.2).

websocket_accept_key

fn websocket_accept_key(sec_websocket_key : String) -> String

The Sec-WebSocket-Accept value for a client's Sec-WebSocket-Key: base64 of the SHA-1 of the key concatenated with the GUID (RFC 6455 §4.2.2).

websocket_guid

let websocket_guid : String

The WebSocket handshake GUID appended to the client key before hashing (RFC 6455 §4.2.2).

websocket_handshake_response

fn websocket_handshake_response(sec_websocket_key : String, subprotocol : String?, extra_headers : Array[(String, String)]) -> Bytes

The 101 Switching Protocols response for a WebSocket upgrade (RFC 6455 §4.2.2): the Upgrade and Connection headers, the computed Sec-WebSocket-Accept, the selected subprotocol when one was negotiated, and any extra_headers.

websocket_select_subprotocol

fn websocket_select_subprotocol(offered : Array[String], supported : Array[String]) -> String?

Select the first client-offered subprotocol the server supports, or None if none match (RFC 6455 §4.2.2): the client's order is its preference.

ws_close_payload

fn ws_close_payload(code : Int, reason : String) -> Bytes

Build a close frame's payload (RFC 6455 §5.5.1): the 16-bit status code big-endian followed by the UTF-8 reason.

ws_decode_frame

fn ws_decode_frame(b : BytesView) -> (WsFrame, Int)?

Decode one WebSocket frame at the start of b (RFC 6455 §5.2), returning the frame (its payload unmasked) and the byte count it consumed, or None when b does not yet hold a whole frame or carries a reserved opcode.

ws_encode_frame

fn ws_encode_frame(fin : Bool, opcode : WsOpcode, payload : Bytes, mask? : Bytes) -> Bytes

Encode a WebSocket frame (RFC 6455 §5.2). A 4-byte mask sets the MASK bit and masks the payload (clients MUST mask, servers MUST NOT — pass b"" for an unmasked server frame). The payload length takes the shortest of the 7-bit (≤125), 16-bit (126), or 64-bit (127) forms.

ws_opcode_of_int

fn ws_opcode_of_int(n : Int) -> WsOpcode?

The opcode for a 4-bit wire value, or None for a reserved opcode.

ws_read_frame

async fn ws_read_frame(reader : &
Reader
) -> WsFrame

Read one WebSocket frame off a raw byte stream (RFC 6455 §5.2) — the runtime counterpart of ws_decode_frame. Reads the 2-byte prefix, then the extended length (16- or 64-bit), the masking key, and the payload as the prefix dictates, unmasking the payload. Raises WsError on a reserved opcode and propagates the reader's end-of-stream.

ws_read_message

async fn ws_read_message(reader : &
Reader
, writer : &
Writer
) -> WsMessage

Read one complete WebSocket message off reader, reassembling a fragmented message from its continuation frames (RFC 6455 §5.4) and transparently handling interleaved control frames (§5.5): a ping is answered on writer with a pong, a pong is dropped, and a close ends the message with its code and reason. Raises WsError on a framing violation (a stray continuation frame, or a new data frame arriving before the current message finished).

x25519

fn x25519(k : Bytes, u : Bytes) -> Bytes

X25519 (RFC 7748 §5): the scalar k applied to u-coordinate u, both 32-byte little-endian, returning the 32-byte little-endian result u-coordinate. The scalar is clamped and the u-coordinate's high bit masked per §5.

x25519_base

fn x25519_base() -> Bytes

The Curve25519 base point u=9 as a 32-byte little-endian scalar input, for turning a private key into a public key: x25519(private, x25519_base()).

x509_alg_ec_public_key

fn x509_alg_ec_public_key() -> Bytes

The id-ecPublicKey with the prime256v1 (P-256) curve AlgorithmIdentifier (RFC 5480).

x509_alg_ecdsa_sha256

fn x509_alg_ecdsa_sha256() -> Bytes

The ecdsa-with-SHA256 AlgorithmIdentifier (RFC 5758): a SEQUENCE of just the OID, ECDSA taking no parameters.

x509_common_name

fn x509_common_name(cn : String) -> Bytes

A Name with a single commonName attribute: RDNSequence → RelativeDistinguishedName (SET) → AttributeTypeAndValue (SEQUENCE of the commonName OID 2.5.4.3 and the UTF8String value).

x509_self_signed

fn x509_self_signed(key : EcdsaPrivateKey, serial : Bytes, common_name : String, not_before : String, not_after : String) -> Bytes

A self-signed X.509 certificate: build the TBSCertificate for key's public key, sign its DER with ES256, DER-encode the r/s signature (RFC 5280 requires the ECDSA-Sig-Value SEQUENCE, not the raw concatenation), and wrap TBS + algorithm + signature in the outer Certificate SEQUENCE.

x509_subject_public_key_info

fn x509_subject_public_key_info(pub_key : EcdsaPublicKey) -> Bytes

A SubjectPublicKeyInfo for a P-256 key: the ecPublicKey/prime256v1 algorithm and the uncompressed point as a BIT STRING.

x509_tbs_certificate

fn x509_tbs_certificate(serial : Bytes, common_name : String, not_before : String, not_after : String, pub_key : EcdsaPublicKey) -> Bytes

The TBSCertificate (RFC 5280 §4.1.2): version v3 [0] EXPLICIT INTEGER 2, the serial number, the signature algorithm, the issuer, the validity, the subject, and the SubjectPublicKeyInfo. Self-signed, so issuer and subject are the same commonName. Extensions are omitted (a valid, minimal profile).

x509_validity

fn x509_validity(not_before : String, not_after : String) -> Bytes

A Validity: notBefore and notAfter as UTCTime YYMMDDHHMMSSZ.