# flare.qpack.dynamic

> A module of flare_mlake's API.

QPACK dynamic table + encoder/decoder stream instructions (RFC 9204).

The static-only codec in :mod:`flare.qpack.codec` covers field sections
that reference the RFC 9204 Appendix A static table or carry literals.
This module adds the **dynamic table**: the per-connection insert log a
peer streams over the unidirectional QPACK *encoder stream* (type 0x02),
the *decoder stream* (type 0x03) acknowledgements that flow back, and the
field-section wire shapes that reference dynamic entries by relative /
post-base index.

Sans-I/O: pure state + pure byte-in/byte-out functions. The h3 layer
owns the QUIC streams and feeds the instruction bytes here; this module
never touches a socket.

Pieces:

- :struct:`QpackDynamicTable` -- the insert-ordered entry log with
  capacity-bounded eviction and absolute / relative index resolution
  (RFC 9204 section 3.2).
- Encoder-stream instruction codec (section 4.3): Set Dynamic Table
  Capacity, Insert With Name Reference (static or dynamic), Insert With
  Literal Name, Duplicate. :func:`apply_encoder_instructions` replays a
  byte stream into a table.
- Decoder-stream instruction codec (section 4.4): Section
  Acknowledgment, Stream Cancellation, Insert Count Increment.
- Field-section dynamic codec (section 4.5):
  :func:`encode_field_section_dynamic` /
  :func:`decode_field_section_dynamic`, including the Required Insert
  Count encoding of section 4.5.1.1.
- :struct:`QpackEncoder` / :struct:`QpackDecoder` -- thin owners that
  pair a table with the stream bookkeeping (Known Received Count, the
  Insert Count Increment to emit).

Eviction follows the encoder (evict the oldest entry to make
room) and does not track per-entry reference counts -- a decoder mirror
trusts the peer encoder not to evict an entry a not-yet-acknowledged
field section still references (RFC 9204 section 2.2 makes that the
encoder's responsibility). Blocked-stream handling (a field section
referencing inserts that have not arrived) is surfaced as a decode error;
the HTTP/3 server advertises 0 blocked streams, so it treats that error
(like every undecodable field section) as a connection error of type
``QPACK_DECOMPRESSION_FAILED`` (RFC 9204 section 2.1.2). Stream-parking
for a non-zero budget is the upgrade path.

References: RFC 9204 sections 2-4; RFC 7541 Appendix B (Huffman).

## Structs

### `QpackDynamicTable`

```mojo
struct QpackDynamicTable
```

Insert-ordered, capacity-bounded dynamic table (RFC 9204 3.2).

``entries[0]`` is the oldest live entry. ``dropped`` counts entries
evicted over the table's lifetime, so the absolute index of
``entries[0]`` is ``dropped`` and the next insert lands at absolute
index ``insert_count()`` (== ``dropped + len(entries)``).

Implements: `Copyable`, `Movable`

Fields:

- `entries: List[HpackHeader]`
- `capacity: UInt64`
- `max_capacity: UInt64`: The ceiling :meth:`set_capacity` may raise ``capacity`` to: the SETTINGS_QPACK_MAX_TABLE_CAPACITY this side advertised (RFC 9204 sec 3.2.3).
- `size: UInt64`
- `dropped: UInt64`

#### `__init__`

```mojo
def __init__(out self, capacity: UInt64 = UInt64(0))
```

#### `insert_count`

```mojo
def insert_count(self) -> UInt64
```

Total inserts ever (the absolute index of the next insert).

#### `max_entries`

```mojo
def max_entries(self) -> UInt64
```

RFC 9204 section 3.2.2 MaxEntries = floor(capacity / 32).

#### `set_capacity`

```mojo
def set_capacity(mut self, cap: UInt64)
```

Set the table capacity, evicting the oldest entries until the size fits (RFC 9204 section 4.3.1).

Raises ``QPACK_ENCODER_STREAM_ERROR`` above :attr:`max_capacity`
(sec 4.3.1). Any value was accepted, so a peer could grow our
dynamic table past the limit we advertised, which is 0 by
default, and fill it with inserts of any size.

#### `insert`

```mojo
def insert(mut self, var h: HpackHeader) -> Bool
```

Insert ``h`` after evicting to make room. Returns False if the entry cannot fit even in an empty table (RFC 9204 section 3.2.2 -- such an insert is a no-op the caller treats as an error).

#### `get_abs`

```mojo
def get_abs(self, abs_index: UInt64) -> HpackHeader
```

Resolve an absolute index to its entry.

#### `find`

```mojo
def find(self, h: HpackHeader) -> Int
```

Absolute index of a full (name, value) match, or -1.

#### `find_name`

```mojo
def find_name(self, name: String) -> Int
```

Absolute index of a name match, or -1.

### `DecoderInstruction`

```mojo
struct DecoderInstruction
```

A parsed decoder-stream instruction: ``kind`` is one of the ``DEC_INSTR_*`` constants, ``value`` is the stream id or increment, ``offset`` is the cursor past the instruction.

Implements: `Copyable`, `Movable`

Fields:

- `kind: Int`
- `value: UInt64`
- `offset: Int`

### `QpackDecoder`

```mojo
struct QpackDecoder
```

Owns the inbound dynamic table + the Insert Count Increment owed back to the peer on the decoder stream.

Implements: `Copyable`, `Movable`

Fields:

- `table: QpackDynamicTable`
- `pending_increment: Int`

#### `__init__`

```mojo
def __init__(out self, capacity: UInt64 = UInt64(0))
```

#### `feed_encoder_stream`

```mojo
def feed_encoder_stream(mut self, buf: Span[UInt8]) -> Int
```

Apply encoder-stream bytes; accumulate the Insert Count Increment to send. Returns the inserts applied.

#### `take_increment`

```mojo
def take_increment(mut self, mut out: List[UInt8]) -> Bool
```

Emit a pending Insert Count Increment onto the decoder stream (if any). Returns whether anything was written.

#### `decode`

```mojo
def decode(self, buf: Span[UInt8], max_field_section_size: UInt64 = UInt64(65536)) -> List[HpackHeader]
```

Decode a field section against the dynamic table, capped at ``max_field_section_size`` decoded bytes.

### `QpackEncoder`

```mojo
struct QpackEncoder
```

Owns the outbound dynamic table; ``insert`` appends an encoder-stream instruction and updates the local table mirror.

The encoder does not track acknowledgments yet: it has no Known
Received Count, never sees Section Acknowledgment or Insert Count
Increment instructions, and is not told the peer's
``SETTINGS_QPACK_BLOCKED_STREAMS``. Until it does, every entry is
unacknowledged, so (RFC 9204 sections 2.1.1 and 2.1.2):

* :meth:`encode` references no dynamic entry (Required Insert Count
  0), so no stream can block a decoder that allows none; and
* :meth:`insert` never evicts: an entry that does not fit in the
  free space is refused, so no entry a decoder may still need
  disappears.

Implements: `Copyable`, `Movable`

Fields:

- `table: QpackDynamicTable`

#### `__init__`

```mojo
def __init__(out self, capacity: UInt64 = UInt64(0))
```

#### `set_capacity`

```mojo
def set_capacity(mut self, cap: UInt64, mut enc_stream: List[UInt8])
```

#### `insert`

```mojo
def insert(mut self, name: String, value: String, mut enc_stream: List[UInt8]) -> Bool
```

Insert (name, value), emitting an Insert With Literal Name instruction. Returns False, emitting nothing, if the entry does not fit in the table's free space: inserting it would evict an entry that is not known to be unreferenced (QPACK-06).

#### `encode`

```mojo
def encode(self, headers: List[HpackHeader], mut out: List[UInt8])
```

Encode a field section against no dynamic entries (Required Insert Count 0): static references and literals only, until acknowledgments are tracked (QPACK-06).

## Functions

### `entry_size`

```mojo
def entry_size(h: HpackHeader) -> UInt64
```

RFC 9204 section 3.2.1 entry size in bytes.

### `encode_required_insert_count`

```mojo
def encode_required_insert_count(ric: UInt64, max_entries: UInt64) -> UInt64
```

Encode the Required Insert Count for the field-section prefix.

### `decode_required_insert_count`

```mojo
def decode_required_insert_count(enc: UInt64, total_inserts: UInt64, max_entries: UInt64) -> UInt64
```

Decode the Required Insert Count given the wire value, the decoder's current insert count, and MaxEntries (RFC 9204 4.5.1.1).

### `encode_set_capacity`

```mojo
def encode_set_capacity(mut out: List[UInt8], cap: UInt64)
```

Set Dynamic Table Capacity: ``001xxxxx`` (5-bit prefix).

### `encode_insert_with_name_ref`

```mojo
def encode_insert_with_name_ref(mut out: List[UInt8], is_static: Bool, name_index: Int, value: String)
```

Insert With Name Reference: ``1Txxxxxx`` (T static flag, 6-bit name index) followed by the value string literal.

### `encode_insert_with_literal_name`

```mojo
def encode_insert_with_literal_name(mut out: List[UInt8], name: String, value: String)
```

Insert With Literal Name: ``01Hxxxxx`` (5-bit name length) then the name + value string literals.

### `encode_duplicate`

```mojo
def encode_duplicate(mut out: List[UInt8], rel_index: Int)
```

Duplicate: ``000xxxxx`` (5-bit relative index).

### `apply_encoder_instructions`

```mojo
def apply_encoder_instructions(mut table: QpackDynamicTable, buf: Span[UInt8]) -> Int
```

Replay a complete encoder-stream byte buffer into ``table``. Returns the number of inserts applied (for the Insert Count Increment the decoder owes back). Raises on a malformed / truncated instruction; use :func:`apply_encoder_instructions_partial` when the buffer may end mid-instruction at a chunk boundary.

### `apply_encoder_instructions_partial`

```mojo
def apply_encoder_instructions_partial(mut table: QpackDynamicTable, buf: Span[UInt8]) -> Tuple[Int, Int]
```

Like :func:`apply_encoder_instructions` but tolerant of a chunk boundary mid-instruction: applies every whole instruction and returns ``(inserts_applied, bytes_consumed)`` so the caller can keep the unconsumed tail and retry once more bytes arrive.

An instruction cut short by the chunk boundary (an untagged
parse error) stops consumption and is retried. A real encoder-stream
error (a reference to a missing or evicted entry, a capacity above the
advertised limit, a literal that is not UTF-8, an insert that does not
fit) is tagged ``QPACK_ENCODER_STREAM_ERROR`` and is re-raised, so the
connection fails instead of stalling.

### `encode_section_ack`

```mojo
def encode_section_ack(mut out: List[UInt8], stream_id: Int)
```

Section Acknowledgment: ``1xxxxxxx`` (7-bit stream id).

### `encode_stream_cancel`

```mojo
def encode_stream_cancel(mut out: List[UInt8], stream_id: Int)
```

Stream Cancellation: ``01xxxxxx`` (6-bit stream id).

### `encode_insert_count_increment`

```mojo
def encode_insert_count_increment(mut out: List[UInt8], increment: Int)
```

Insert Count Increment: ``00xxxxxx`` (6-bit increment).

### `parse_decoder_instruction`

```mojo
def parse_decoder_instruction(buf: Span[UInt8], offset: Int) -> DecoderInstruction
```

Parse one decoder-stream instruction at ``offset``.

### `encode_field_section_dynamic`

```mojo
def encode_field_section_dynamic(headers: List[HpackHeader], table: QpackDynamicTable, mut out: List[UInt8])
```

Encode a field section that may reference ``table`` entries.

For each header: a full dynamic match becomes an indexed (relative)
line; a dynamic name match becomes a literal-with-name-reference;
otherwise it falls back to the static-table path / literals. The
Required Insert Count is the largest referenced absolute index + 1
(0 when no dynamic entry is referenced), and Base is set equal to it
(delta_base 0), so every reference is pre-base.

### `decode_field_section_dynamic`

```mojo
def decode_field_section_dynamic(buf: Span[UInt8], table: QpackDynamicTable, max_field_section_size: UInt64 = UInt64(65536)) -> List[HpackHeader]
```

Decode a field section against ``table``, resolving dynamic indexed / name-reference / post-base lines (RFC 9204 section 4.5). Static-only inputs (Required Insert Count 0) decode without touching the table: every dynamic reference must resolve to an absolute index below the Required Insert Count (and a Sign-1 Delta Base below it), otherwise the section is a ``QPACK_DECOMPRESSION_FAILED`` error.

The decoded size (``name + value + 32`` per line, RFC 9114 section
4.2.2) is capped at ``max_field_section_size`` and checked as each
line is decoded, so a one-byte reference to a large dynamic-table entry
repeated thousands of times is refused after the first few instead of
being expanded in full. The caller's limit on the compressed section
does not do this.

## Aliases

### `DEC_INSTR_SECTION_ACK`

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

### `DEC_INSTR_STREAM_CANCEL`

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

### `DEC_INSTR_INSERT_COUNT_INCREMENT`

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