Sign in

    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
    Version
    0.10.0
    License
    Apache-2.0
    Last updated
    last month
    Downloads
    214

    #mooncat

    A native ASGI 3.0 server for MoonBit — ← uvicorn.

    Check and Test License mooncakes

    Moved on mooncakes from Lfan-ke/mooncat to moonbitstack/mooncat.

    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=12000)

    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=12000)

    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=12000), 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=12000)

    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). The receive side enforces RFC 6455 rather than trusting the peer: a set RSV bit, a reserved opcode, an unmasked client frame and a malformed control frame each fail the connection with 1002, a payload past ws.max_size with 1009 — checked before the payload is read, so a 64-bit length is never truncated into a short allocation — invalid UTF-8 in a text message with 1007, and an inbound close is echoed per §5.5.1. Proven by a real @websocket client doing a full text + binary round-trip and a clean close in CI;
    • a Config in the shape of uvicorn's, and honoured rather than merely recorded: dual_stack, reuse_addr, per-server response headers, max_connections, allow_failure, timeout_keep_alive (an idle keep-alive connection is dropped, on both keep-alive loops), limit_max_requests (the server stops itself, on the plain acceptor and the graceful one), limit_concurrency (uvicorn's 503 rather than a queue), max_head_size (uvicorn's h11_max_incomplete_event_size), date_header, proxy_headers / forwarded_allow_ips, and ws.max_size. backlog is the one field that does nothing: @socket.TcpServer calls listen() itself with a fixed depth and exposes no way to change it. Chunked request/response framing is handled by the codec.

    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 on the async-transport path (awaits a transport hook), further TLS detail (ciphers/mTLS), h2 over TLS once the async layer exposes ALPN, multi-process prefork once it exposes SO_REUSEPORT or fork, backlog once TcpServer takes one, and the remaining WebSocket knobs — ws-ping-interval / ws-ping-timeout / ws-max-queue need a reader that can be interrupted between messages and resumed, and ws-per-message-deflate needs a raw DEFLATE codec the async gzip package does not expose.

    #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.

    #What this server does not implement

    The algorithms are not here. AES, GCM, SHA-1, SHA-256, HMAC, HKDF, X25519, ECDSA and the DER codec live in mooncrypt; X.509 lives in mooncred; the levels and the sink behind the log lines are moonlog's; base64 is moonbase's.

    What stays is what belongs to a server: the TLS 1.3 and QUIC state machines, the HTTP/1.1, HTTP/2 and HTTP/3 codecs, QPACK, the WebSocket framing, the process model, and crypto.mbt — a file with no algorithm in it, binding those primitives to the one suite these protocols name (AES-128-GCM with SHA-256, P-256, X25519) so the protocol code says what it is doing rather than restating the suite on every line.

    The protocol state machines are slated to move to moonnet in turn; until then they are here.

    #Listening

    The default port is 12000, for HTTP, HTTPS, WebSocket and h2c alike — one listener tells them apart, so TLS gets no shadow port of its own. QUIC takes the same number on UDP, which is a different port space. It is a default, not a fixture: Config::new(port=...) moves it.

    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(Int, String)
    }

    A WebSocket protocol violation, carrying the RFC 6455 §7.4.1 status code the connection must be failed with (§7.1.7) and a diagnostic. Distinct from the reader's end-of-stream, which is not a violation and closes the connection without a status.

    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
    timeout_keep_alive : Int
    limit_max_requests : Int?
    limit_concurrency : Int?
    max_head_size : Int
    date_header : Bool
    proxy_headers : Bool
    forwarded_allow_ips : Array[String]
    ws : WsConfig
    }

    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 — TcpServer calls listen() itself with a fixed depth and offers no way to re-listen — so the value is recorded on the config for parity and future use but is not applied to the socket. This is a transport-capability gap, not a behavioural choice, and it is the one field here that does nothing.

    The remaining fields map one-to-one onto knobs mooncat 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, and allow_failure for whether a handler error tears the whole server down.

    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, timeout_keep_alive? : Int, limit_max_requests? : Int, limit_concurrency? : Int, max_head_size? : Int, date_header? : Bool, proxy_headers? : Bool, forwarded_allow_ips? : Array[String], ws? : WsConfig) -> Config

    Build a Config, defaulting to uvicorn's own defaults: host 127.0.0.1, port 12000, backlog 2048, a 5-second idle keep-alive timeout, a 16 KiB request-head cap, the Date header on, proxy headers trusted from 127.0.0.1, and no request-count or concurrency limit. 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.

    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.

    Http3Frame::equal

    fn Http3Frame::equal(Http3Frame, Http3Frame) -> Bool

    Http3Frame::not_equal

    fn Http3Frame::not_equal(x : Http3Frame, y : Http3Frame) -> Bool

    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.

    Http3Role::equal

    fn Http3Role::equal(Http3Role, Http3Role) -> Bool

    Http3Role::not_equal

    fn Http3Role::not_equal(x : Http3Role, y : Http3Role) -> Bool

    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::equal

    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::not_equal

    fn Http3Settings::not_equal(x : Http3Settings, y : Http3Settings) -> Bool

    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).

    Http3StreamCtx::equal

    Http3StreamCtx::not_equal

    fn Http3StreamCtx::not_equal(x : Http3StreamCtx, y : Http3StreamCtx) -> Bool

    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.

    Http3UniEvent::equal

    Http3UniEvent::not_equal

    fn Http3UniEvent::not_equal(x : Http3UniEvent, y : Http3UniEvent) -> Bool

    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).

    Http3UniStream::equal

    Http3UniStream::not_equal

    fn Http3UniStream::not_equal(x : Http3UniStream, y : Http3UniStream) -> Bool

    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

    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.

    Logger

    pub struct Logger {
    out :
    Logger

    access : Bool
    }

    How much the server says, and where it goes.

    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.

    Logger::enabled

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

    Whether a line at level would be written.

    Logger::log

    fn Logger::log(self : Logger, level :
    Level
    , 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? :
    Level
    , 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::advance_past

    fn PacketNumberSpace::advance_past(self : PacketNumberSpace, pn : Int64) -> Unit

    Skip the send counter past pn, so a packet another sender numbered in this space — the recovery loop's, which keeps its own counter — is not handed out again here. A pn this space is already past leaves it alone.

    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).

    QpackDecoderInst::equal

    QpackDecoderInst::not_equal

    fn QpackDecoderInst::not_equal(x : QpackDecoderInst, y : QpackDecoderInst) -> Bool

    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).

    QpackEncoderInst::equal

    QpackEncoderInst::not_equal

    fn QpackEncoderInst::not_equal(x : QpackEncoderInst, y : QpackEncoderInst) -> Bool

    QuicConnPhase

    pub(all) enum QuicConnPhase {
    Active
    Closing
    Draining
    } derive(Eq,
    Debug
    )

    Where a connection is in its lifecycle (RFC 9000 §10.2): exchanging packets, closing — this endpoint sent a CONNECTION_CLOSE and answers anything further with it — or draining, where the peer closed and nothing more goes out.

    QuicConnPhase::equal

    QuicConnPhase::not_equal

    fn QuicConnPhase::not_equal(x : QuicConnPhase, y : QuicConnPhase) -> Bool

    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)])
    AckEcn(largest~ : UInt64, delay~ : UInt64, first_range~ : UInt64, ranges~ : Array[(UInt64, UInt64)], ect0~ : UInt64, ect1~ : UInt64, ce~ : 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).

    QuicFrame::equal

    fn QuicFrame::equal(QuicFrame, QuicFrame) -> Bool

    QuicFrame::not_equal

    fn QuicFrame::not_equal(x : QuicFrame, y : QuicFrame) -> Bool

    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.

    QuicLevel::equal

    fn QuicLevel::equal(QuicLevel, QuicLevel) -> Bool

    QuicLevel::not_equal

    fn QuicLevel::not_equal(x : QuicLevel, y : QuicLevel) -> Bool

    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.

    QuicLongHeader::equal

    QuicLongHeader::not_equal

    fn QuicLongHeader::not_equal(x : QuicLongHeader, y : QuicLongHeader) -> Bool

    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.

    QuicLongPacketType::equal

    QuicLongPacketType::not_equal

    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).

    QuicPacketKeys::equal

    QuicPacketKeys::not_equal

    fn QuicPacketKeys::not_equal(x : QuicPacketKeys, y : QuicPacketKeys) -> Bool

    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::advance_past

    fn QuicSender::advance_past(self : QuicSender, pn : Int64) -> Unit

    Skip the send counter past pn, so a packet this sender did not build — a driver's ACK-only packet in the same space — does not collide with a number it later assigns. A pn the sender is already past leaves it alone.

    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).

    QuicServeConfig

    pub(all) struct QuicServeConfig {
    version : UInt
    idle_timeout : Int64
    close_period : Int64
    max_datagram : Int64
    max_ack_delay : Int64
    granularity : Int64
    initial_max_data : UInt64
    initial_max_stream_data : UInt64
    max_frame : UInt64
    }

    What a server gives each new connection: the QUIC version its long headers carry, the idle timeout it advertises, how long a closing or draining connection lingers, and the datagram size, timer tuning and flow-control credit the send loop starts from. Every duration is in microseconds, the unit the recovery code measures in.

    QuicServeConfig::default

    QUIC version 1, a 30-second idle timeout, a 3-second closing period, 1200-byte datagrams, a 25-millisecond ACK delay, and a mebibyte of flow-control credit on the connection and on each stream.

    QuicServeConn

    pub struct QuicServeConn[A] {
    peer : A
    cid : Bytes
    peer_cid : Bytes
    core : QuicServerConn
    sender : QuicSender
    cfg : QuicServeConfig
    out : Array[Bytes]
    received : Array[(QuicLevel, QuicFrame)]
    rx : Int64
    tx : Int64
    packets : Int
    validated : Bool
    last_rx : Int64
    idle_timeout : Int64
    phase : QuicConnPhase
    close_at : Int64
    close_frame : QuicFrame?
    close_level : QuicLevel
    hs_rx : QuicPacketKeys?
    hs_rx_secret : Bytes
    hs_tx : QuicPacketKeys?
    app_rx : QuicPacketKeys?
    app_tx : QuicPacketKeys?
    }

    One server-side connection: the peer it answers, the connection ids the two ends address each other by, the handshake and send state machines it drives, and the counters the amplification limit, the idle timer and the closing period are read from. A is the peer-address type the enclosing QuicServer was built with.

    QuicServeConn::apply_peer_params

    fn[A] QuicServeConn::apply_peer_params(self : QuicServeConn[A], params : Array[TransportParam]) -> Unit

    Apply the peer's transport parameters (RFC 9000 §18). max_idle_timeout (§18.2) is the one that changes this connection: it settles the idle timer at the smaller of the two ends' advertised values, converting the parameter's milliseconds to the timers' microseconds.

    QuicServeConn::can_send

    fn[A] QuicServeConn::can_send(self : QuicServeConn[A], n : Int) -> Bool

    Whether n more bytes may go to this peer: unrestricted once its address is validated, otherwise at most three times what it has sent this server (RFC 9000 §8.1).

    QuicServeConn::cid

    fn[A] QuicServeConn::cid(self : QuicServeConn[A]) -> Bytes

    The connection id the peer addresses this server by — the table's key.

    QuicServeConn::close

    fn[A] QuicServeConn::close(self : QuicServeConn[A], error_code : UInt64, reason : Bytes, now : Int64) -> Unit

    Close the connection (RFC 9000 §10.2): put a CONNECTION_CLOSE carrying error_code and reason on the wire at the highest level with keys, drop whatever else was queued, and enter the closing period — during which every packet the peer sends is answered with the same CONNECTION_CLOSE, and at the end of which tick forgets the connection. Closing a connection that is already closing or draining does nothing.

    QuicServeConn::core

    fn[A] QuicServeConn::core(self : QuicServeConn[A]) -> QuicServerConn

    The handshake state machine underneath, for a caller driving the TLS flights.

    QuicServeConn::idle_timeout

    fn[A] QuicServeConn::idle_timeout(self : QuicServeConn[A]) -> Int64

    The idle timeout in force, in microseconds; zero means none.

    QuicServeConn::install_app_keys

    fn[A] QuicServeConn::install_app_keys(self : QuicServeConn[A], client_secret : Bytes, server_secret : Bytes) -> Unit

    Install the Application-space (1-RTT) keys, derived from the two application traffic secrets. Until they are installed, a short-header packet is dropped and the send loop has nowhere to put stream data.

    QuicServeConn::install_handshake_keys

    fn[A] QuicServeConn::install_handshake_keys(self : QuicServeConn[A], client_secret : Bytes, server_secret : Bytes) -> Unit

    Install the Handshake-space keys, derived from the client's and this server's handshake traffic secrets (RFC 9001 §5.1). Until they are installed, a Handshake packet cannot be unprotected and is dropped; after, the connection reads and writes at that level. The client's secret is kept so an arriving Finished can be verified against it.

    QuicServeConn::largest_acked

    fn[A] QuicServeConn::largest_acked(self : QuicServeConn[A], level : QuicLevel) -> Int64?

    The largest packet number the peer has acknowledged at level, or None before its first ACK there.

    QuicServeConn::packets_received

    fn[A] QuicServeConn::packets_received(self : QuicServeConn[A]) -> Int

    Datagrams received on this connection.

    QuicServeConn::peer

    fn[A] QuicServeConn::peer(self : QuicServeConn[A]) -> A

    The peer this connection answers.

    QuicServeConn::pending

    fn[A] QuicServeConn::pending(self : QuicServeConn[A]) -> Int

    Datagrams built and waiting to go out — non-zero while the amplification limit or a closed congestion window is holding them back.

    QuicServeConn::phase

    fn[A] QuicServeConn::phase(self : QuicServeConn[A]) -> QuicConnPhase

    Where this connection is in its lifecycle.

    QuicServeConn::queue_stream

    fn[A] QuicServeConn::queue_stream(self : QuicServeConn[A], id : UInt64, bytes : Bytes) -> Unit

    Queue bytes to send on stream id; the send loop frames and paces them out through poll_out once the Application keys are installed.

    QuicServeConn::rx_bytes

    fn[A] QuicServeConn::rx_bytes(self : QuicServeConn[A]) -> Int64

    Bytes received from the peer, the numerator of the amplification limit.

    QuicServeConn::send

    fn[A] QuicServeConn::send(self : QuicServeConn[A], level : QuicLevel, frames : Array[QuicFrame]) -> Unit

    Queue frames to go out at level on the next poll_out, numbered from that level's packet-number space. An Initial packet carrying anything ack-eliciting takes its datagram to the 1200-byte floor (RFC 9000 §14.1); a level whose keys are not installed drops the frames, since there is nothing to protect them with.

    QuicServeConn::send_level

    fn[A] QuicServeConn::send_level(self : QuicServeConn[A]) -> QuicLevel

    The highest encryption level this connection can send at — Application once its 1-RTT keys are in, else Handshake, else Initial, which is always available.

    QuicServeConn::take_received

    fn[A] QuicServeConn::take_received(self : QuicServeConn[A]) -> Array[(QuicLevel, QuicFrame)]

    Take the frames received since the last call, each with the level it arrived at — where an application layer reads its streams out of.

    QuicServeConn::tx_bytes

    fn[A] QuicServeConn::tx_bytes(self : QuicServeConn[A]) -> Int64

    Bytes sent to the peer, the quantity the amplification limit caps.

    QuicServeConn::validated

    fn[A] QuicServeConn::validated(self : QuicServeConn[A]) -> Bool

    Whether the peer's address has been validated (RFC 9000 §8.1), which lifts the amplification limit.

    QuicServer

    pub struct QuicServer[A] {
    conns : Map[Bytes, QuicServeConn[A]]
    cfg : QuicServeConfig
    }

    A QUIC server: the connections it is holding, keyed by the connection id peers address each by, and the settings a new one starts from. A is the peer-address type — a socket address under the native event loop, whatever a test finds convenient under a fake sink.

    QuicServer::close

    fn[A] QuicServer::close(self : QuicServer[A], cid : Bytes, error_code : UInt64, reason : Bytes, now : Int64) -> Unit

    Close the connection cid addresses with error_code and reason, if the server holds one (RFC 9000 §10.2).

    QuicServer::conn

    fn[A] QuicServer::conn(self : QuicServer[A], cid : Bytes) -> QuicServeConn[A]?

    The connection cid addresses, or None if the server is not holding one.

    QuicServer::conn_ids

    fn[A] QuicServer::conn_ids(self : QuicServer[A]) -> Array[Bytes]

    The connection ids the server is holding.

    QuicServer::count

    fn[A] QuicServer::count(self : QuicServer[A]) -> Int

    How many connections the server is holding.

    QuicServer::new

    fn[A] QuicServer::new(cfg : QuicServeConfig) -> QuicServer[A]

    A server holding no connections, handing cfg to each one it opens.

    QuicServer::poll_out

    fn[A] QuicServer::poll_out(self : QuicServer[A], now : Int64) -> (Bytes, A)? raise

    The next datagram to put on the wire and the peer to send it to, or None when no connection has one ready.

    QuicServer::recv

    fn[A] QuicServer::recv(self : QuicServer[A], datagram : Bytes, from : A, now : Int64) -> Unit raise

    Take a datagram received at now from from: route it to the connection its Destination Connection ID names, opening one when an Initial packet arrives for an id the server does not hold, and drive that connection with it (RFC 9000 §5.2). A datagram naming no connection is dropped — RFC 9000 §10.3's stateless reset is not implemented.

    QuicServer::tick

    fn[A] QuicServer::tick(self : QuicServer[A], now : Int64) -> Unit

    Run the timers at now. An idle connection is forgotten without a CONNECTION_CLOSE, which is what RFC 9000 §10.1 asks for; a closing or draining one is forgotten when its period ends (§10.2); an active one whose probe timeout has come re-queues its oldest outstanding packet, to go out on the next poll_out (RFC 9002 §6.2.4).

    QuicServerConn

    pub struct QuicServerConn {
    initial_keys : QuicPacketKeys
    handshake :
    Handshake

    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

    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::negotiate

    Negotiate the ClientHello this connection received (RFC 8446 §4.1.1): the group and key share to run the ECDHE with, and the signature scheme to sign the CertificateVerify under, read off what the client offered. Raises the §6 alert for a ClientHello this build cannot serve, and returns Retry when the answer is a HelloRetryRequest.

    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::on_frames

    fn QuicServerConn::on_frames(self : QuicServerConn, level : QuicLevel, frames : Array[QuicFrame], pn : Int64) -> Array[
    Kind
    ] raise

    Drive an already-unprotected packet's frames at level, numbered pn: record the packet for acknowledgement and feed its CRYPTO frames into that level's reassembler, driving the handshake with whatever handshake bytes become contiguous. Returns the handshake message types processed. A driver that unprotected the packet itself — because it also wants the ACK and CONNECTION_CLOSE frames beside the CRYPTO — enters here rather than through receive_initial.

    QuicServerConn::on_handshake_frames

    fn QuicServerConn::on_handshake_frames(self : QuicServerConn, frames : Array[QuicFrame], pn : Int64, client_hs_secret : Bytes) -> Bool raise

    Drive the client's Handshake-space frames, numbered pn, already unprotected: record the packet, reassemble its CRYPTO stream, verify any Finished in it against client_hs_secret over the transcript as it stands, then fold the messages in. Returns whether the handshake is now connected with a Finished that authenticated — the frame-level half of receive_handshake, for a driver holding the decrypted packet.

    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[
    Kind
    ] 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 :
    PrivateKey
    , 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 :
    Server
    , 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.

    QuicShortHeader::equal

    QuicShortHeader::not_equal

    fn QuicShortHeader::not_equal(x : QuicShortHeader, y : QuicShortHeader) -> Bool

    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.

    RecvStreamEvent::equal

    RecvStreamEvent::not_equal

    fn RecvStreamEvent::not_equal(x : RecvStreamEvent, y : RecvStreamEvent) -> Bool

    RecvStreamState

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

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

    RecvStreamState::equal

    RecvStreamState::not_equal

    fn RecvStreamState::not_equal(x : RecvStreamState, y : RecvStreamState) -> Bool

    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.

    SendStreamEvent::equal

    SendStreamEvent::not_equal

    fn SendStreamEvent::not_equal(x : SendStreamEvent, y : SendStreamEvent) -> Bool

    SendStreamState

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

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

    SendStreamState::equal

    SendStreamState::not_equal

    fn SendStreamState::not_equal(x : SendStreamState, y : SendStreamState) -> Bool

    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).

    StreamDirection::equal

    StreamDirection::not_equal

    fn StreamDirection::not_equal(x : StreamDirection, y : StreamDirection) -> Bool

    StreamInitiator

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

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

    StreamInitiator::equal

    StreamInitiator::not_equal

    fn StreamInitiator::not_equal(x : StreamInitiator, y : StreamInitiator) -> Bool

    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.

    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.

    TransportParam::equal

    TransportParam::not_equal

    fn TransportParam::not_equal(x : TransportParam, y : TransportParam) -> Bool

    WsConfig

    pub(all) struct WsConfig {
    max_size : Int
    }

    The WebSocket knobs uvicorn exposes on its own config (--ws-max-size).

    max_size is the ceiling on a received payload, in bytes: it bounds a single frame and, across continuation frames, a reassembled message, and a peer that announces more fails the connection with 1009. It is the knob with teeth on the receive side — an unbounded length is exactly what a hostile client sends.

    uvicorn's remaining WebSocket knobs are not here rather than here-and-ignored: --ws-ping-interval / --ws-ping-timeout need a heartbeat task alongside a reader that can be interrupted between messages and resumed, --ws-max-queue needs that same reader pumping a bounded queue, and neither is expressible while a native blocking read cannot be cancelled and resumed mid-stream; --ws-per-message-deflate needs a raw DEFLATE codec, which the async library's gzip package (gzip-framed streams only) does not provide. Accepting the extension without one would corrupt every frame.

    WsConfig::new

    fn WsConfig::new(max_size? : Int) -> WsConfig

    Build a WsConfig, defaulting max_size to uvicorn's own 16 MiB.

    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.

    WsFrame::equal

    fn WsFrame::equal(WsFrame, WsFrame) -> Bool

    WsFrame::not_equal

    fn WsFrame::not_equal(x : WsFrame, y : WsFrame) -> Bool

    WsFrame::to_repr

    WsMessage

    pub(all) enum WsMessage {
    WsText(String)
    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, 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.

    A text message carries a String: §8.1 makes invalid UTF-8 a 1007 failure, so bytes that would not decode never reach this far and the app cannot be handed a lossily-mangled message.

    WsMessage::equal

    fn WsMessage::equal(WsMessage, WsMessage) -> Bool

    WsMessage::not_equal

    fn WsMessage::not_equal(x : WsMessage, y : WsMessage) -> Bool

    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::equal

    fn WsOpcode::equal(WsOpcode, WsOpcode) -> Bool

    WsOpcode::is_control

    fn WsOpcode::is_control(self : WsOpcode) -> Bool

    Whether this is a control opcode (RFC 6455 §5.5), which the spec holds to tighter rules than a data frame: at most 125 payload bytes, and never fragmented.

    WsOpcode::not_equal

    fn WsOpcode::not_equal(x : WsOpcode, y : WsOpcode) -> Bool

    WsOpcode::to_int

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

    The 4-bit wire value of an opcode.

    WsOpcode::to_repr

    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.

    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.

    ec_private

    A P-256 private key from the hex of its scalar.

    Hex is what a fixture and an example carry. A key from a file is PEM or DER, which is mooncrypt/key's to read when that package lands; until then a deployment reads its own file and hands the scalar in.

    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_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).

    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_request — None 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).

    http_date

    fn http_date(ms : Int64) -> String

    Format a Unix-epoch millisecond count as an HTTP-date in the IMF-fixdate form RFC 7231 §7.1.1.1 makes mandatory for a Date header: Sun, 06 Nov 1994 08:49:37 GMT, always GMT, always fixed-width.

    The calendar conversion is the standard civil-from-days one: shift the epoch to a March-based year so the leap day falls at the end of the era, then divide out 400-year eras. It is exact for any instant an Int64 millisecond count can name, and needs no locale and no clock but the one that produced ms.

    http_date_now

    fn http_date_now() -> String

    The current instant as an HTTP-date, reformatted only when the epoch second has moved on.

    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.

    port

    let port : Int

    The port every server in this family listens on unless told otherwise.

    Not 8000, which is uvicorn's, and not 8080: on a development machine those are taken by whatever ran last. One number of our own means every example, every README and every round of integration testing agree without anyone having to say which port they meant. HTTP, WebSocket, HTTP/2 and gRPC all share it — one listener tells them apart — and QUIC takes the same number on UDP, which is a different port space.

    proxy_rewrite

    fn proxy_rewrite(headers : Map[String, String], client : (String, Int)?, scheme : String, trusted~ : Array[String], websocket? : Bool) -> ((String, Int)?, String)

    Rewrite a request's client and scheme from a trusted proxy's forwarding headers (← uvicorn's ProxyHeadersMiddleware).

    Nothing is rewritten unless the peer itself is trusted: these headers are attacker-controlled, and believing them from an arbitrary client lets that client name any address it likes for a rate limiter, an allowlist or an audit log. Behind a proxy the peer is the proxy, which is why forwarded_allow_ips defaults to 127.0.0.1 and not to *.

    X-Forwarded-For yields the chain's leftmost entry with port 0, since a forwarded chain carries no port. X-Forwarded-Proto replaces the scheme, mapped to wss/ws for a WebSocket scope, whose scheme names differ from the https/http the header spells.

    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 frame that is neither ACK nor ACK_ECN — the ECN counts do not change which packets are acknowledged. Raises if the frame's ranges underflow the packet-number space.

    quic_amplification_factor

    let quic_amplification_factor : Int64

    The anti-amplification factor (RFC 9000 §8.1): until it has validated a peer's address, a server may send it at most this many times the bytes it has received from it.

    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, 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_effective_idle_timeout

    fn quic_effective_idle_timeout(ours : Int64, theirs : Int64) -> Int64

    The idle timeout in force on a connection (RFC 9000 §10.1): the smaller of the two endpoints' advertised max_idle_timeouts, where zero on either side means that end asks for no limit at all.

    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, ACK_ECN, 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, 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_min_initial_datagram

    let quic_min_initial_datagram : Int

    The smallest a datagram carrying an ack-eliciting Initial packet may be (RFC 9000 §14.1).

    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_now

    fn quic_now() -> Int64

    The event loop's clock, in the microseconds the recovery and idle timers are measured in. @async.now() counts milliseconds since the epoch.

    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_serve

    async fn quic_serve(addr : String, server : QuicServer[
    Addr
    ], tick_ms? : Int) -> Unit

    Bind a UDP socket at addr (e.g. "0.0.0.0:443") and serve server on it. The socket outlives the loop deliberately: closing one a task is parked in recvfrom on wedges it rather than waking it, so shutdown is cancellation of the task group, not a close.

    quic_serve_endpoint

    async fn quic_serve_endpoint(ep : QuicUdpEndpoint, server : QuicServer[
    Addr
    ], tick_ms? : Int) -> Unit

    Serve QUIC on ep until the enclosing task group is cancelled: one task reads datagrams and demultiplexes them onto connections, the other runs the idle, probe and closing-period timers every tick_ms milliseconds, and both push out whatever that left ready to send. A datagram that names no connection or fails to authenticate is dropped rather than killing the loop — an endpoint cannot tell a stray packet from an attack.

    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 by handle.shutdown() / handle.request_stop(), by reaching Config::limit_max_requests, or by a signal the runtime turns into global cancellation (see the boundary note below). All three 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.

    The acceptor honours the same ceilings the other serve paths get from run_forever: max_connections gates accepting, so a full server leaves the next client in the listen queue rather than accepting it and sitting on it, and allow_failure decides whether one bad connection is contained or brings the server down. limit_concurrency is the other kind of ceiling — a request arriving past it is answered 503 rather than queued.

    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.

    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 :
    PublicKey
    , context : String, transcript_hash : Bytes, body : Bytes) -> Bool

    Check a CertificateVerify body against the peer's ES256 public key.

    False on a scheme this server does not verify, a malformed body, or a signature that does not check out — one answer for every way of being wrong, because which way it was wrong is not the peer's business.

    tls13_certificate_verify_sign

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

    Sign a CertificateVerify over the transcript with an ES256 key (RFC 8446 §4.4.3): the message body, its scheme and signature framed by moontls, over the signed content moontls builds.

    tls13_client_hello_x25519

    fn tls13_client_hello_x25519(client_hello : Bytes) -> Bytes?

    The x25519 public key a ClientHello's key_share offers, or None when it carries none for that group.

    tls13_ecdhe_handshake_traffic_secret

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

    A handshake traffic secret straight from an ECDHE exchange and a transcript.

    is_client picks which side's secret: the two are derived from the same handshake secret under different labels (RFC 8446 §7.1), so one call serves both ends of a test and both ends of a connection.

    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 from a ClientHello's key share: the server's and the client's, in that order.

    None when the ClientHello offers no x25519 share, which is the one case a server has to answer with a HelloRetryRequest rather than a secret.

    tls13_ecdhe_shared

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

    The x25519 shared secret with a peer's public key.

    A low-order peer share makes the secret all zeroes, which mooncrypt refuses; here that becomes an empty answer, because the handshake's response to it is an alert rather than a crash.

    tls13_handshake_secret_from_ecdhe

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

    The Handshake Secret from our scalar and the peer's key share: run the ECDHE, then RFC 8446 §7.1's ladder.

    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 makes quic_transport_parameters mandatory for a QUIC server, and the parameters themselves are QUIC's — moontls carries the extension without reading it, so encoding them is this side's job.

    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?

    A server's handshake traffic secret from the ClientHello it received: pull the client's x25519 key_share and run the ECDHE with server_private over the ClientHello..ServerHello transcript.

    None if the message is not a ClientHello or offers no x25519 share — the case a server answers with a HelloRetryRequest rather than a secret.

    tls13_x25519_public

    fn tls13_x25519_public(private_key : Bytes) -> Bytes

    The ephemeral x25519 public key for a private scalar (RFC 7748 §5).

    tls_hello_quic_transport_params

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

    The QUIC transport parameters an extension list carries, in order.

    Empty when the extension is absent or its block is truncated: a QUIC endpoint treats a missing block as no parameters and falls back to the defaults RFC 9000 §18.2 gives.

    tls_selected_alpn

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

    The single ALPN protocol a server's extension list selected, or None.

    tls_server_hello_key_share

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

    The group and key a ServerHello's key_share selected.

    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_check_frame

    fn ws_check_frame(b0 : Int, b1 : Int, len : Int64, from_client~ : Bool, max_size~ : Int) -> WsOpcode raise WsError

    Check a frame's wire header against RFC 6455's receive rules and return its opcode. b0 and b1 are the first two header bytes; len is the payload length already widened out of whichever of the three length forms carried it.

    Raises WsError with the status code the connection must then be failed with (§7.1.7):

    • a set RSV1/2/3 bit, no extension having been negotiated → 1002 (§5.2);
    • a reserved opcode → 1002 (§5.2);
    • an unmasked frame, when from_client says the peer is a client → 1002 (§5.1);
    • a control frame carrying more than 125 bytes, or fragmented → 1002 (§5.5);
    • a length past max_size → 1009, the 64-bit form's sign bit included (§5.2 reserves it).

    Callers must run this before they read the payload: it is the length check that keeps a 64-bit length from being truncated into a short allocation, and keeps an honest-but-enormous one from being allocated at all.

    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, from_client? : Bool, max_size? : Int) -> (WsFrame, Int)? raise WsError

    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.

    from_client selects the receive rules of a server reading a client (the default: masking is mandatory); pass false to decode a frame travelling the other way. A frame that breaks the rules raises WsError rather than returning None, so a violation is never mistaken for a short buffer — see ws_check_frame.

    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_max_size

    let ws_max_size : Int

    The default ceiling on a received payload, in bytes (← uvicorn's --ws-max-size, 16 MiB). It bounds both a single frame and a reassembled fragmented message; past it the connection is failed with 1009 rather than allocating whatever the peer asked for.

    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
    , from_client? : Bool, max_size? : Int) -> 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), and only once ws_check_frame has passed the header does it read the masking key and the payload.

    from_client defaults to the server's position — a client's frames must be masked (§5.1) — so reading a frame the server sent needs from_client=false. Raises WsError carrying the close code for any violation, and propagates the reader's end-of-stream.

    ws_read_message

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

    Read one complete WebSocket message off reader, reassembling a fragmented message from its continuation frames (RFC 6455 §5.4) and handling interleaved control frames (§5.5): a ping is answered on writer with a pong, a pong is dropped, and a close is echoed back per §5.5.1 before the message ends with its code and reason.

    This is the server's receive path, so every frame must arrive masked. Raises WsError carrying the close code the connection is then failed with: 1002 for a framing violation (a stray continuation frame, or a new data frame arriving before the current message finished) and whatever ws_check_frame decided for a bad header, 1007 for a text message that is not valid UTF-8 (§8.1), 1009 for a reassembled message past max_size.