64 lines
3.4 KiB
Markdown
64 lines
3.4 KiB
Markdown
# HPACK decoder
|
||
|
||
This document records the implementation constraints of Flash's RFC 7541 decoder. It is
|
||
contributor documentation, not application API documentation.
|
||
|
||
## Representation model
|
||
|
||
`HpackDecoder` accepts all RFC 7541 field representations: indexed fields, literals with
|
||
incremental indexing, literals without indexing, never-indexed literals, and dynamic-table size
|
||
updates. Prefix integers are bounded against overflow, Huffman padding and EOS are validated, and
|
||
decoded string lengths are checked while bytes are produced.
|
||
|
||
The static table is stored as 61 immutable name/value byte pairs. Encoder-oriented reverse lookup
|
||
uses fixed open-addressed integer tables built during class initialization; lookup never converts
|
||
header bytes to `String` and never calls `HashMap` on the hot path.
|
||
|
||
The dynamic table owns a bounded byte arena and a ring of primitive entry descriptors. Entry size
|
||
is `name length + value length + 32`, and eviction is oldest-first as required by RFC 7541 §4.1.
|
||
When the arena tail is too short, live entries are compacted into a contiguous prefix. This avoids
|
||
segmented views in every consumer and keeps indexed fields cheap to copy.
|
||
|
||
## Ownership and eviction safety
|
||
|
||
Views emitted by `HeaderSink` are callback-scoped. Production decoding targets a reusable
|
||
`HpackHeaderBlock` owned by the stream, which copies each name and value into its own arena.
|
||
|
||
The copy is required for correctness. Consider stream A referencing a dynamic-table entry while
|
||
its handler is running. The connection thread can then decode stream B, evict that entry, and
|
||
reuse its bytes. If stream A retained the dynamic-table view, its headers would silently change.
|
||
Per-stream storage removes that race without reference counting or synchronization.
|
||
|
||
The precise copy model is:
|
||
|
||
- HTTP/1.1 copies nothing per request but scans header bytes in the connection buffer.
|
||
- HTTP/2 copies decoded request headers into stream-owned storage because multiplexed handlers
|
||
outlive subsequent HPACK mutations.
|
||
- Novel incrementally-indexed fields are also copied once into the connection's dynamic table.
|
||
|
||
`HpackEvictionRaceTest` contains both the unsafe borrowed-view demonstration and the stable
|
||
stream-owned result.
|
||
|
||
## Header-list rejection
|
||
|
||
The decoder counts RFC header-list size cumulatively. Once the configured limit is crossed it
|
||
stops emitting fields, but continues parsing the entire block and applying dynamic-table updates.
|
||
Only after the block ends does it throw `HeaderListSizeException`. The stream layer can reject the
|
||
request while the connection's compression state remains synchronized.
|
||
|
||
## CONTINUATION assembly
|
||
|
||
`ContinuationAssembler` copies HEADERS and CONTINUATION fragments into one bounded connection
|
||
buffer. It rejects interleaving, stream-id changes, excessive continuation count, and blocks that
|
||
exceed the configured capacity. `SegmentedByteView` is intentionally not used here: RFC 9113 §6.10
|
||
requires a contiguous, non-interleaved continuation sequence, and one bounded copy makes the HPACK
|
||
decoder and all downstream views simpler.
|
||
|
||
## Verification
|
||
|
||
- RFC 7541 Appendix C.1–C.6 vectors, including dynamic-table state after every sequence.
|
||
- Invalid integer, Huffman, index, size-update, and header-list inputs.
|
||
- Ten million deterministic random blocks; only typed protocol rejections may escape.
|
||
- JMH `-prof gc`: `decodeStaticRequest` measured 102.725 ns/op and 0.001 B/op on JDK 21.0.11. The
|
||
latter is the profiler's sampling noise floor; no garbage collections occurred.
|