Internals
View SourceArchitecture
erlang_qpack consists of two main modules:
qpack- Main QPACK encoder/decoder implementing RFC 9204qpack_huffman- Huffman coding implementation per RFC 7541 Appendix B
State Record
The internal state record tracks all encoding/decoding context:
-record(qpack, {
use_dynamic, %% boolean() - dynamic table enabled
dyn_field_index, %% #{header() => AbsIndex} - O(1) exact match
dyn_name_index, %% #{binary() => AbsIndex} - O(1) name match
dyn_entries, %% [{AbsIndex, Header, Size}] - FIFO list
dyn_size, %% Current dynamic table size in bytes
dyn_max_size, %% Maximum dynamic table capacity
insert_count, %% Next absolute index for insertion
known_received_count, %% Entries acknowledged by peer
encoder_instructions, %% Pending instructions to send
last_ric, %% Required Insert Count from last encode
pending_sections %% #{StreamId => queue(RIC)} - for section ack
}).Static Table
The static table contains 99 pre-defined header fields (indices 0-98) from RFC 9204 Appendix A. It's implemented using three data structures for O(1) lookups:
- Tuple - Index to entry lookup:
element(Index + 1, ?STATIC_TABLE) - Field Map - Exact
{Name, Value}to index lookup - Name Map - Name-only to index lookup
Dynamic Table
The dynamic table uses a combination of:
- Maps for O(1) lookups by header or name
- List for FIFO eviction order
Absolute vs Relative Indexing
- Absolute Index - Monotonically increasing, starts at 0
- Relative Index -
InsertCount - AbsIndex - 1(most recent = 0) - Post-Base Index -
AbsIndex - Base(for entries after Base)
Entry Size
Per RFC 9204 Section 3.2.1:
entry_size = name_length + value_length + 32Encoding Process
- Lookup - Check static table, then dynamic table
- Decision - Use indexed, literal with name ref, or literal
- Insertion - Optionally insert into dynamic table
- Instruction Generation - Create encoder stream instructions
- Prefix - Encode Required Insert Count and Base
Required Insert Count (RIC)
The RIC indicates the minimum dynamic table state needed to decode:
RIC = max(referenced_absolute_indices) + 1Encoded as ERIC using modulo arithmetic:
ERIC = (RIC mod (2 * MaxEntries)) + 1Base Encoding
Base indicates the reference point for relative indices:
- S=1, DeltaBase=0 means Base = RIC (used by this implementation)
- S=0 would indicate pre-base references
Decoding Process
- Prefix - Decode ERIC and reconstruct RIC using modulo arithmetic
- Blocked Check - If RIC > InsertCount, decoding is blocked
- Header Lines - Decode each field line using appropriate form
- Index Resolution - Convert relative/post-base indices to absolute
Encoder Stream Instructions
| Instruction | Pattern | Description |
|---|---|---|
| Insert With Name Ref (static) | 11xxxxxx | Reference static table name |
| Insert With Name Ref (dynamic) | 10xxxxxx | Reference dynamic table name |
| Insert With Literal Name | 01Hxxxxx | Literal name and value |
| Duplicate | 000xxxxx | Duplicate existing entry |
| Set Dynamic Table Capacity | 001xxxxx | Change table size |
Decoder Stream Instructions
| Instruction | Pattern | Description |
|---|---|---|
| Section Acknowledgment | 1xxxxxxx | Acknowledge decoded section |
| Stream Cancellation | 01xxxxxx | Cancel stream |
| Insert Count Increment | 00xxxxxx | Increment known received count |
Header Field Line Encodings
| Encoding | Pattern | Description |
|---|---|---|
| Indexed (static) | 11xxxxxx | Static table reference |
| Indexed (dynamic) | 10xxxxxx | Dynamic table reference (pre-base) |
| Indexed (post-base) | 0001xxxx | Dynamic table reference (post-base) |
| Literal with name ref (static) | 0101xxxx | Static name, literal value |
| Literal with name ref (dynamic) | 0100xxxx | Dynamic name, literal value |
| Literal with post-base name ref | 0000Nxxx | Post-base name reference |
| Literal with literal name | 001xHxxx | Literal name and value |
Huffman Coding
The Huffman implementation uses a state machine decoder processing 4 bits at a time:
dec_huffman_lookup(State, Nibble) ->
{ok | more, Char | undefined, NextState}- State - Current decoder state (0-255)
- Nibble - 4-bit input value (0-15)
- Result - Whether symbol is complete, decoded character, next state
Validation
decode_safe/1 validates per RFC 7541 Section 5.2:
- Padding must be EOS prefix (all 1s)
- Padding must not exceed 7 bits
- EOS symbol must not appear in decoded output
Performance Considerations
- Static table lookups are O(1) using maps
- Dynamic table lookups are O(1) using maps
- Dynamic table eviction is O(n) but amortized across insertions
- Huffman uses a pre-computed lookup table for speed
- Binary pattern matching leverages Erlang/OTP optimizations