# flare.quic.state

> A module of flare_mlake's API.

QUIC connection + stream state machines (RFC 9000 §3 + §13).

Pure sans-I/O state model for a QUIC connection. The reactor
wrapping wraps this in UDP I/O + crypto; the codec layer here
just walks the state machines as packets / frames / timer ticks
arrive.

Public surface:

* :class:`StreamState` -- the 7-state RFC 9000 §3.1 / §3.2 stream
  state machine (IDLE / OPEN / HALF_CLOSED_LOCAL / HALF_CLOSED_
  REMOTE / RESET_SENT / RESET_RECVD / CLOSED).
* :class:`Stream` -- one bidi or uni stream's state, including
  send/recv high-water-marks for flow control.
* :class:`ConnectionState` -- the connection-level state
  (HANDSHAKE / ESTABLISHED / CLOSING / DRAINING / CLOSED) per
  RFC 9000 §10.
* :class:`Connection` -- top-level container holding
  - per-side keying state (handshake_complete flag),
  - flow-control limits,
  - per-stream :class:`Stream` instances keyed by stream-id,
  - the timer-driven idle / pto / loss-detection deadlines.
* :func:`handle_frame_buf` -- per-buffer ingestion entry point.
  Reads one wire frame from the start of ``buf``, dispatches the
  matching :trait:`FrameHandler` callback into the connection
  state machines, and returns the number of bytes consumed. The
  caller drains a packet payload by advancing its cursor and
  re-invoking the dispatcher.
* :class:`ConnectionEvents` -- the value the driver returns from
  one tick: bytes the caller should send, the next deadline at
  which to call back, plus connection-level events (handshake
  done, stream finished, connection closed).
* :func:`apply_stream` / :func:`apply_ack` / etc. -- per-typed
  payload helpers the dispatcher calls; exposed so the reactor
  layer (and tests) can drive transitions directly when they
  already have a typed payload in hand.

The state-machine layer is intentionally sans-I/O: there are no
sockets, no TLS handshake, no time source. The caller passes
``now_us`` into every entry point and receives
:class:`ConnectionEvents` carrying the next deadline; the timer
shape lives in the reactor wrapper.

References:
- RFC 9000 §3 "Stream States" + §10 "Connection Termination".
- RFC 9000 §13 "Packetization and Reliability".

## Structs

### `Connection`

```mojo
struct Connection
```

Top-level connection state.

Holds the connection-level state, the per-stream map, and the
timer-shape fields the driver uses to schedule the next tick.
The connection here only tracks the data-plane state machines.

Uses :class:`Dict` for the stream map so the driver can look
up by stream id in O(1); RFC 9000 caps stream ids at 2**62-1
so the key space is wide enough that stream-id overflow is a
non-concern.

Implements: `Copyable`, `Movable`

Fields:

- `state: Int`
- `handshake_complete: Bool`
- `idle_timeout_us: UInt64`
- `last_activity_us: UInt64`
- `max_data_send: UInt64`
- `max_data_recv: UInt64`
- `bytes_in_flight: UInt64`
- `streams: Dict[UInt64, Stream]`
- `ack_pending: Bool`
- `largest_received_packet: UInt64`
- `largest_acked_by_peer: UInt64`
- `close_error_code: UInt64`
- `close_reason: List[UInt8]`
- `peer_cids: Dict[UInt64, _PeerConnId]`: Peer-issued Source CIDs (RFC 9000 §5.1.1) keyed by sequence number, learned from NEW_CONNECTION_ID. The migration path picks a spare entry as the next active Destination CID.
- `active_dcid_seq: UInt64`: Sequence number of the peer CID currently used as the active Destination CID. ``0`` is the handshake CID (§5.1.1).
- `peer_retire_prior_to: UInt64`: Highest ``retire_prior_to`` the peer has sent; every sequence number below it is retired for good.
- `outgoing_path_challenge: List[UInt8]`: The 8-byte PATH_CHALLENGE payload we last sent while probing a new path; an inbound PATH_RESPONSE must echo it to validate the path (§8.2). Empty when no probe is outstanding.
- `path_validated: Bool`: True once the outstanding path probe was confirmed by a matching PATH_RESPONSE.
- `is_server: Bool`: True for the server role (set by :class:`QuicConnection`). A server treats a received HANDSHAKE_DONE as a PROTOCOL_VIOLATION (RFC 9000 §19.20).
- `adv_max_streams_bidi: UInt64`: Peer-initiated bidirectional streams we have allowed (``initial_max_streams_bidi`` plus any MAX_STREAMS raise); a stream id above it is a STREAM_LIMIT_ERROR (RFC 9000 §4.6).
- `adv_max_streams_uni: UInt64`: The same for peer-initiated unidirectional streams.
- `check_stream_ids: Bool`: Whether :func:`check_stream_frame_id` runs on received stream frames. Set by the server (:class:`QuicConnection`) and the client (:class:`QuicClientConnection`); off for a bare sans-I/O connection, which has no notion of which streams its driver opened.
- `next_local_bidi: UInt64`: The next locally initiated bidirectional stream id that has not been opened (client 0, 4, ...; server 1, 5, ...). The client advances it in ``open_bidi_stream``; the server opens none.
- `next_local_uni: UInt64`: The same for unidirectional streams (client 2, 6, ...; server 3, ...).
- `peer_initial_max_stream_data: UInt64`: Send limit a peer-initiated stream starts with: the peer's ``initial_max_stream_data_bidi_local`` (RFC 9000 sec 18.2). The server holds it at 0 until it has read the client's transport parameters, then raises it and every open stream's limit.
- `new_token: List[UInt8]`: The latest NEW_TOKEN token the server sent (client side, RFC 9000 sec 8.1.3), for the Initial of a later connection to it.
- `ack_freq_set: Bool`: The peer sent ACK_FREQUENCY (draft-ietf-quic-ack-frequency): ACKs follow its threshold and delay instead of going out with every burst of packets.
- `ack_freq_seq: UInt64`: The lowest ACK_FREQUENCY sequence number still applied.
- `ack_threshold: UInt64`
- `ack_max_delay_us: UInt64`
- `ack_immediate: Bool`
- `ack_unacked: Int`: Ack-eliciting packets received since the last ACK was sent.
- `ack_first_us: UInt64`
- `max_datagram_recv: UInt64`: The ``max_datagram_frame_size`` we advertised (RFC 9221 sec 3); 0 when we did not, and then any DATAGRAM frame is a PROTOCOL_VIOLATION, as is one larger than this.

## Functions

### `note_ack_eliciting`

```mojo
def note_ack_eliciting(mut conn: Connection, now_us: UInt64)
```

An ack-eliciting packet was received.

### `ack_due`

```mojo
def ack_due(conn: Connection, now_us: UInt64) -> Bool
```

Whether an owed ACK goes out now: always, unless the peer asked for fewer with ACK_FREQUENCY.

### `note_ack_sent`

```mojo
def note_ack_sent(mut conn: Connection)
```

### `new_connection`

```mojo
def new_connection(idle_timeout_us: UInt64 = UInt64(30000000), initial_max_data: UInt64 = UInt64(1048576), is_server: Bool = False, adv_max_streams_bidi: UInt64 = UInt64(100), adv_max_streams_uni: UInt64 = UInt64(3), check_stream_ids: Bool = False) -> Connection
```

Build a fresh :class:`Connection` in the HANDSHAKE state.

### `mark_local_stream_opened`

```mojo
def mark_local_stream_opened(mut conn: Connection, sid: UInt64)
```

Record that we opened local stream ``sid`` by sending on it outside ``conn.streams`` (the server's HTTP/3 control stream), so :func:`check_stream_frame_id` accepts the peer's STOP_SENDING / MAX_STREAM_DATA for it (RFC 9000 sec 19.10).

### `check_stream_frame_id`

```mojo
def check_stream_frame_id(mut conn: Connection, sid: UInt64, kind: Int)
```

RFC 9000 §4.6 / §19.4 / §19.5 / §19.8 / §19.10 / §19.13: check the stream id of a received stream frame (``kind`` is one of ``STREAM_FRAME_*``).

A peer-initiated stream above the stream limit we advertised is a
STREAM_LIMIT_ERROR; a frame for a half the stream does not have (a
send-only stream the peer initiated, a stream we initiated that is
receive-only) is a STREAM_STATE_ERROR, and so is STOP_SENDING /
MAX_STREAM_DATA / STREAM for a stream we initiated and have not
opened. Closes the connection and raises on a violation.

### `apply_stream`

```mojo
def apply_stream(mut conn: Connection, sf: StreamFrame, mut events: ConnectionEvents)
```

Apply a STREAM frame (§19.8) to the connection.

Creates the stream on first sight, advances the recv offset
high-water-mark, enforces the flow-control limit, and emits a
``finished_streams`` entry when the FIN bit closes the recv
side. Raises on flow-control violation. The full
:class:`StreamFrame` is also appended to
:attr:`ConnectionEvents.stream_chunks` so the H3 reactor
wrapper can route the payload bytes to its
per-connection :class:`flare.http3.Http3Connection` without
re-parsing the QUIC packet.

### `apply_ack`

```mojo
def apply_ack(mut conn: Connection, ack: AckFrame)
```

Apply an ACK frame to the connection-level bookkeeping.

``largest_acknowledged`` names the largest packet WE sent that
the peer received; it lives in its own packet-number space and
must NOT touch ``largest_received_packet`` (the largest packet
WE received, which seeds inbound pn reconstruction).

### `apply_connection_close`

```mojo
def apply_connection_close(mut conn: Connection, cc: ConnectionCloseFrame, mut events: ConnectionEvents)
```

Apply a CONNECTION_CLOSE frame to the connection. Marks the connection as DRAINING and stores the close reason.

### `apply_max_data`

```mojo
def apply_max_data(mut conn: Connection, m: MaxDataFrame)
```

Apply a MAX_DATA frame: monotonically advance the connection's send-side flow-control limit.

### `apply_max_stream_data`

```mojo
def apply_max_stream_data(mut conn: Connection, m: MaxStreamDataFrame)
```

Apply a MAX_STREAM_DATA frame: advance the per-stream send high-water-mark for the addressed stream (no-op if the stream has not been seen yet).

### `apply_reset_stream`

```mojo
def apply_reset_stream(mut conn: Connection, rs: ResetStreamFrame)
```

Apply a RESET_STREAM frame: transition the stream to RESET_RECVD and record the receive-half reset in ``peer_reset`` (no-op if the stream is unknown).

### `apply_stop_sending`

```mojo
def apply_stop_sending(mut conn: Connection, ss: StopSendingFrame)
```

Apply a STOP_SENDING frame: transition the stream to RESET_SENT and record the send-half reset in ``send_reset`` (no-op if the stream is unknown).

### `apply_handshake_done`

```mojo
def apply_handshake_done(mut conn: Connection, mut events: ConnectionEvents)
```

Apply a HANDSHAKE_DONE frame: flip the connection to ESTABLISHED and surface the event.

Acts only in HANDSHAKE (as :func:`mark_handshake_complete` does).
CLOSING, DRAINING and CLOSED only lead to closed (RFC 9000 §10.2),
so a HANDSHAKE_DONE that arrives after CONNECTION_CLOSE must not
make the connection usable again; an ESTABLISHED connection has
nothing left to change.

### `apply_new_connection_id`

```mojo
def apply_new_connection_id(mut conn: Connection, ncid: NewConnectionIdFrame, mut events: ConnectionEvents)
```

Apply a NEW_CONNECTION_ID frame (RFC 9000 §19.15).

Records the peer's spare Source CID + stateless-reset token in
:attr:`Connection.peer_cids`, then honors ``retire_prior_to``:
every stored CID with a lower sequence number is dropped and a
RETIRE_CONNECTION_ID is queued in
:attr:`ConnectionEvents.retire_connection_ids` (§5.1.2). If the
active DCID is among those retired, the active sequence advances
to this freshly issued CID.

Validates the wire fields at this trust boundary: CID length in
[1, 20], a 16-byte reset token, and ``retire_prior_to`` no
greater than ``sequence_number`` (a PROTOCOL_VIOLATION per
§19.15 otherwise).

### `apply_retire_connection_id`

```mojo
def apply_retire_connection_id(mut conn: Connection, rcid: RetireConnectionIdFrame, mut events: ConnectionEvents)
```

Apply a RETIRE_CONNECTION_ID frame (RFC 9000 §19.16): the peer is retiring one of the CIDs WE issued. The driver owns SCID issuance, so the sequence number is surfaced in :attr:`ConnectionEvents.retired_local_cids` for it to unroute, validate against what it issued, and replace.

### `apply_path_challenge`

```mojo
def apply_path_challenge(mut conn: Connection, pc: PathChallengeFrame, mut events: ConnectionEvents)
```

Apply a PATH_CHALLENGE frame (RFC 9000 §19.17): queue a PATH_RESPONSE echoing the exact 8-byte payload so the peer can validate the path (§8.2). Validates the fixed 8-byte length at this trust boundary.

### `apply_path_response`

```mojo
def apply_path_response(mut conn: Connection, pr: PathResponseFrame, mut events: ConnectionEvents)
```

Apply a PATH_RESPONSE frame (RFC 9000 §19.18): if it echoes our outstanding PATH_CHALLENGE byte-for-byte, the probed path is validated -- clear the probe and surface ``path_validated`` so the driver can promote the new path (§8.2). A non-matching response is ignored (a stale or spoofed echo). Validates the fixed 8-byte length at this trust boundary.

### `handle_frame_buf`

```mojo
def handle_frame_buf(mut conn: Connection, buf: Span[UInt8], now_us: UInt64, mut events: ConnectionEvents) -> Int
```

Apply one wire frame from the start of ``buf`` to the connection state machines.

Returns the number of bytes the dispatcher consumed; the
caller drains the rest of a packet payload by advancing its
cursor and re-invoking the dispatcher on the remainder.
Internally builds a small adapter implementing
:trait:`flare.quic.frame.FrameHandler` and delegates to
:func:`flare.quic.frame.parse_frame_into`.

### `dispatch_frames`

```mojo
def dispatch_frames(mut conn: Connection, payload: Span[UInt8], now_us: UInt64, mut events: ConnectionEvents, before_1rtt: Bool)
```

Apply every frame in a decrypted packet payload.

``before_1rtt`` is set for Initial and Handshake packets. A frame
those levels do not permit is a PROTOCOL_VIOLATION connection
error (RFC 9000 sec 12.4): the connection is closed and this
raises, so the caller drops the packet. The Initial keys come
from the public DCID, so without this rule anyone who saw a
client's first packet could send STREAM frames at Initial and
have the server run them as a request, no handshake needed.

### `mark_handshake_complete`

```mojo
def mark_handshake_complete(mut conn: Connection, now_us: UInt64, mut events: ConnectionEvents)
```

Explicit hook the TLS handshake adapter calls when its key schedule reaches HANDSHAKE_DONE. Mirrors the :data:`FRAME_TYPE_HANDSHAKE_DONE` path so the connection state advances regardless of which side the signal came from.

### `is_idle_timeout_expired`

```mojo
def is_idle_timeout_expired(conn: Connection, now_us: UInt64) -> Bool
```

Whether the idle timeout has elapsed since ``last_activity_us``.

Returns ``False`` if no traffic has been observed yet (the
handshake hasn't started); the reactor uses this to retire
stalled connections per RFC 9000 §10.1.

### `connection_close`

```mojo
def connection_close(mut conn: Connection, error_code: UInt64, reason: String, application: Bool = False)
```

Mark the connection as closing with the given error code + reason. The driver later emits a CONNECTION_CLOSE frame and transitions to DRAINING.

## Aliases

### `CONN_STATE_HANDSHAKE`

```mojo
comptime CONN_STATE_HANDSHAKE = Int(0)
```

### `CONN_STATE_ESTABLISHED`

```mojo
comptime CONN_STATE_ESTABLISHED = Int(1)
```

### `CONN_STATE_CLOSING`

```mojo
comptime CONN_STATE_CLOSING = Int(2)
```

### `CONN_STATE_DRAINING`

```mojo
comptime CONN_STATE_DRAINING = Int(3)
```

### `CONN_STATE_CLOSED`

```mojo
comptime CONN_STATE_CLOSED = Int(4)
```

### `MIN_ACK_DELAY_US`

```mojo
comptime MIN_ACK_DELAY_US = UInt64(1000)
```

The ``min_ack_delay`` both ends advertise (draft-ietf-quic-ack- frequency): a requested ACK delay below it is refused.

### `MAX_QUEUED_DATAGRAM_BYTES`

```mojo
comptime MAX_QUEUED_DATAGRAM_BYTES = Int(65536)
```

Most DATAGRAM payload bytes a connection queues in either direction (RFC 9221 sec 5: unreliable, so past it a datagram is dropped rather than letting a peer or a slow application pin memory).

### `STREAM_FRAME_RECV_HALF`

```mojo
comptime STREAM_FRAME_RECV_HALF = Int(0)
```

RESET_STREAM / STREAM_DATA_BLOCKED: the frame concerns the sender's half, our receiving half.

### `STREAM_FRAME_SEND_HALF`

```mojo
comptime STREAM_FRAME_SEND_HALF = Int(1)
```

STOP_SENDING / MAX_STREAM_DATA: the frame concerns our sending half.

### `STREAM_FRAME_DATA`

```mojo
comptime STREAM_FRAME_DATA = Int(2)
```

STREAM: data for our receiving half.

### `QUIC_PROTOCOL_VIOLATION`

```mojo
comptime QUIC_PROTOCOL_VIOLATION = UInt64(10)
```

RFC 9000 sec 20.1 transport error code.

### `QUIC_CONNECTION_ID_LIMIT_ERROR`

```mojo
comptime QUIC_CONNECTION_ID_LIMIT_ERROR = UInt64(9)
```

### `QUIC_FLOW_CONTROL_ERROR`

```mojo
comptime QUIC_FLOW_CONTROL_ERROR = UInt64(3)
```

### `QUIC_STREAM_LIMIT_ERROR`

```mojo
comptime QUIC_STREAM_LIMIT_ERROR = UInt64(4)
```

### `QUIC_STREAM_STATE_ERROR`

```mojo
comptime QUIC_STREAM_STATE_ERROR = UInt64(5)
```

### `LOCAL_ACTIVE_CONNECTION_ID_LIMIT`

```mojo
comptime LOCAL_ACTIVE_CONNECTION_ID_LIMIT = Int(2)
```

The ``active_connection_id_limit`` both flare endpoints advertise.
