From 5c927ffdabf62fc7e21c822f058ddc19fd332a80 Mon Sep 17 00:00:00 2001 From: ruben Date: Wed, 2 Apr 2025 18:56:07 +0200 Subject: [PATCH 1/4] add duvet spec --- .duvet/config.toml | 3 + .duvet/snapshot.txt | 179 ++ .../www.rfc-editor.org/rfc/rfc9204.txt | 2133 +++++++++++++++++ 3 files changed, 2315 insertions(+) create mode 100644 .duvet/specifications/www.rfc-editor.org/rfc/rfc9204.txt diff --git a/.duvet/config.toml b/.duvet/config.toml index 8482911b..283d3881 100644 --- a/.duvet/config.toml +++ b/.duvet/config.toml @@ -6,6 +6,9 @@ pattern = "h3/**/*.rs" [[specification]] source = "https://www.rfc-editor.org/rfc/rfc9114" +[[specification]] +source = "https://www.rfc-editor.org/rfc/rfc9204.html" + [report.html] enabled = true issue-link = "https://github.com/hyperium/h3/issues" diff --git a/.duvet/snapshot.txt b/.duvet/snapshot.txt index 9f069074..560c52f8 100644 --- a/.duvet/snapshot.txt +++ b/.duvet/snapshot.txt @@ -865,3 +865,182 @@ SPECIFICATION: https://www.rfc-editor.org/rfc/rfc9114 TEXT[!MUST,exception]: values of N (that is, 0x21, 0x40, ..., through 0x3ffffffffffffffe) TEXT[!MUST,exception]: MUST NOT be assigned by IANA and MUST NOT appear in the listing of TEXT[!MUST,exception]: assigned values. + +SPECIFICATION: https://www.rfc-editor.org/rfc/rfc9204.html + SECTION: [Encoder](#section-2.1) + TEXT[!MAY]: An encoder MAY insert any entry in the dynamic table it chooses; it + TEXT[!MAY]: is not limited to field lines it is compressing. + TEXT[!MUST]: An encoder MUST emit field representations in the order + TEXT[!MUST]: they appear in the input field section. + + SECTION: [Limits on Dynamic Table Insertions](#section-2.1.1) + TEXT[!MUST]: If the dynamic table does not contain enough room for a new entry + TEXT[!MUST]: without evicting other entries, and the entries that would be evicted + TEXT[!MUST]: are not evictable, the encoder MUST NOT insert that entry into the + TEXT[!MUST]: dynamic table (including duplicates of existing entries). + + SECTION: [Blocked Streams](#section-2.1.2) + TEXT[!MUST]: An encoder MUST limit the number of streams that could + TEXT[!MUST]: become blocked to the value of SETTINGS_QPACK_BLOCKED_STREAMS at all + TEXT[!MUST]: times. + TEXT[!MUST]: If a decoder encounters more blocked streams than it promised + TEXT[!MUST]: to support, it MUST treat this as a connection error of type + TEXT[!MUST]: QPACK_DECOMPRESSION_FAILED. + + SECTION: [Avoiding Flow-Control Deadlocks](#section-2.1.3) + TEXT[!SHOULD]: To avoid these deadlocks, an encoder SHOULD NOT write an instruction + TEXT[!SHOULD]: unless sufficient stream and connection flow-control credit is + TEXT[!SHOULD]: available for the entire instruction. + + SECTION: [Decoder](#section-2.2) + TEXT[!MUST]: The decoder MUST emit field lines in the order their representations + TEXT[!MUST]: appear in the encoded field section. + + SECTION: [Blocked Decoding](#section-2.2.1) + TEXT[!SHOULD]: While blocked, encoded field section data SHOULD remain in the + TEXT[!SHOULD]: blocked stream's flow-control window. + TEXT[!MUST]: If it encounters a Required Insert + TEXT[!MUST]: Count smaller than expected, it MUST treat this as a connection error + TEXT[!MUST]: of type QPACK_DECOMPRESSION_FAILED; see Section 2.2.3. + TEXT[!MAY]: If it + TEXT[!MAY]: encounters a Required Insert Count larger than expected, it MAY treat + TEXT[!MAY]: this as a connection error of type QPACK_DECOMPRESSION_FAILED. + + SECTION: [Completed Processing of a Field Section](#section-2.2.2.1) + TEXT[!MUST]: After the decoder finishes decoding a field section encoded using + TEXT[!MUST]: representations containing dynamic table references, it MUST emit a + TEXT[!MUST]: Section Acknowledgment instruction (Section 4.4.1). + + SECTION: [Abandonment of a Stream](#section-2.2.2.2) + TEXT[!MAY]: A decoder with a maximum dynamic table + TEXT[!MAY]: capacity (Section 3.2.3) equal to zero MAY omit sending Stream + TEXT[!MAY]: Cancellations, because the encoder cannot have any dynamic table + TEXT[!MAY]: references. + + SECTION: [Invalid References](#section-2.2.3) + TEXT[!MUST]: If the decoder encounters a reference in a field line representation + TEXT[!MUST]: to a dynamic table entry that has already been evicted or that has an + TEXT[!MUST]: absolute index greater than or equal to the declared Required Insert + TEXT[!MUST]: Count (Section 4.5.1), it MUST treat this as a connection error of + TEXT[!MUST]: type QPACK_DECOMPRESSION_FAILED. + TEXT[!MUST]: If the decoder encounters a reference in an encoder instruction to a + TEXT[!MUST]: dynamic table entry that has already been evicted, it MUST treat this + TEXT[!MUST]: as a connection error of type QPACK_ENCODER_STREAM_ERROR. + + SECTION: [Static Table](#section-3.1) + TEXT[!MUST]: When the decoder encounters an invalid static table index in a field + TEXT[!MUST]: line representation, it MUST treat this as a connection error of type + TEXT[!MUST]: QPACK_DECOMPRESSION_FAILED. + TEXT[!MUST]: If this index is received on the encoder + TEXT[!MUST]: stream, this MUST be treated as a connection error of type + TEXT[!MUST]: QPACK_ENCODER_STREAM_ERROR. + + SECTION: [Dynamic Table](#section-3.2) + TEXT[!MUST]: Therefore, duplicate entries MUST NOT + TEXT[!MUST]: be treated as an error by the decoder. + + SECTION: [Dynamic Table Capacity and Eviction](#section-3.2.2) + TEXT[!MUST]: The + TEXT[!MUST]: encoder MUST NOT cause a dynamic table entry to be evicted unless + TEXT[!MUST]: that entry is evictable; see Section 2.1.1. + TEXT[!MUST]: It is an error if the encoder attempts to add an + TEXT[!MUST]: entry that is larger than the dynamic table capacity; the decoder + TEXT[!MUST]: MUST treat this as a connection error of type + TEXT[!MUST]: QPACK_ENCODER_STREAM_ERROR. + + SECTION: [Maximum Dynamic Table Capacity](#section-3.2.3) + TEXT[!MUST]: The encoder MUST NOT set a dynamic table capacity that exceeds this + TEXT[!MUST]: maximum, but it can choose to use a lower dynamic table capacity; see + TEXT[!MUST]: Section 4.3.1. + TEXT[!MAY]: When the client's 0-RTT value of the + TEXT[!MAY]: SETTING is zero, the server MAY set it to a non-zero value in its + TEXT[!MAY]: SETTINGS frame. + TEXT[!MUST]: If the remembered value is non-zero, the server MUST + TEXT[!MUST]: send the same non-zero value in its SETTINGS frame. + TEXT[!MUST]: When the maximum table capacity is zero, the encoder MUST NOT insert + TEXT[!MUST]: entries into the dynamic table and MUST NOT send any encoder + TEXT[!MUST]: instructions on the encoder stream. + + SECTION: [Prefixed Integers](#section-4.1.1) + TEXT[!MUST]: QPACK implementations MUST be able to decode integers up to and + TEXT[!MUST]: including 62 bits long. + + SECTION: [Encoder and Decoder Streams](#section-4.2) + TEXT[!MUST]: Each endpoint + TEXT[!MUST]: MUST initiate, at most, one encoder stream and, at most, one decoder + TEXT[!MUST]: stream. + TEXT[!MUST]: Receipt of a second instance of either stream type MUST be + TEXT[!MUST]: treated as a connection error of type H3_STREAM_CREATION_ERROR. + TEXT[!MUST]: The sender MUST NOT close either of these streams, and the receiver + TEXT[!MUST]: MUST NOT request that the sender close either of these streams. + TEXT[!MUST]: Closure of either unidirectional stream type MUST be treated as a + TEXT[!MUST]: connection error of type H3_CLOSED_CRITICAL_STREAM. + TEXT[!MAY]: An endpoint MAY avoid creating an encoder stream if it will not be + TEXT[!MAY]: used (for example, if its encoder does not wish to use the dynamic + TEXT[!MAY]: table or if the maximum size of the dynamic table permitted by the + TEXT[!MAY]: peer is zero). + TEXT[!MAY]: An endpoint MAY avoid creating a decoder stream if its decoder sets + TEXT[!MAY]: the maximum capacity of the dynamic table to zero. + TEXT[!MUST]: An endpoint MUST allow its peer to create an encoder stream and a + TEXT[!MUST]: decoder stream even if the connection's settings prevent their use. + + SECTION: [Set Dynamic Table Capacity](#section-4.3.1) + TEXT[!MUST]: The new capacity MUST be lower than or equal to the limit described + TEXT[!MUST]: in Section 3.2.3. + TEXT[!MUST]: The decoder MUST treat a new dynamic table capacity + TEXT[!MUST]: value that exceeds this limit as a connection error of type + TEXT[!MUST]: QPACK_ENCODER_STREAM_ERROR. + TEXT[!MUST]: This MUST NOT cause the eviction of entries that + TEXT[!MUST]: are not evictable; see Section 2.1.1. + + SECTION: [Section Acknowledgment](#section-4.4.1) + TEXT[!MUST]: If an encoder receives a Section Acknowledgment instruction referring + TEXT[!MUST]: to a stream on which every encoded field section with a non-zero + TEXT[!MUST]: Required Insert Count has already been acknowledged, this MUST be + TEXT[!MUST]: treated as a connection error of type QPACK_DECODER_STREAM_ERROR. + + SECTION: [Insert Count Increment](#section-4.4.3) + TEXT[!MUST]: An encoder that receives an Increment field equal to zero, or one + TEXT[!MUST]: that increases the Known Received Count beyond what the encoder has + TEXT[!MUST]: sent, MUST treat this as a connection error of type + TEXT[!MUST]: QPACK_DECODER_STREAM_ERROR. + + SECTION: [Required Insert Count](#section-4.5.1.1) + TEXT[!MUST]: If the decoder encounters a value + TEXT[!MUST]: of EncodedInsertCount that could not have been produced by a + TEXT[!MUST]: conformant encoder, it MUST treat this as a connection error of type + TEXT[!MUST]: QPACK_DECOMPRESSION_FAILED. + + SECTION: [Base](#section-4.5.1.2) + TEXT[!MUST]: The value of Base MUST NOT be negative. + TEXT[!MUST]: An endpoint MUST treat a field block + TEXT[!MUST]: with a Sign bit of 1 as invalid if the value of Required Insert Count + TEXT[!MUST]: is less than or equal to the value of Delta Base. + + SECTION: [Literal Field Line with Name Reference](#section-4.5.4) + TEXT[!MUST]: When + TEXT[!MUST]: the 'N' bit is set, the encoded field line MUST always be encoded + TEXT[!MUST]: with a literal representation. + TEXT[!MUST]: In particular, when a peer sends a + TEXT[!MUST]: field line that it received represented as a literal field line with + TEXT[!MUST]: the 'N' bit set, it MUST use a literal representation to forward this + TEXT[!MUST]: field line. + + SECTION: [Never-Indexed Literals](#section-7.1.3) + TEXT[!MUST]: An intermediary MUST NOT re-encode a value that uses a literal + TEXT[!MUST]: representation with the 'N' bit set with another representation that + TEXT[!MUST]: would index it. + TEXT[!MUST]: If QPACK is used for re-encoding, a literal + TEXT[!MUST]: representation with the 'N' bit set MUST be used. + TEXT[!MUST]: If HPACK is used + TEXT[!MUST]: for re-encoding, the never-indexed literal representation (see + TEXT[!MUST]: Section 6.2.3 of [RFC7541]) MUST be used. + + SECTION: [Implementation Limits](#section-7.4) + TEXT[!SHOULD]: These limits SHOULD be large + TEXT[!SHOULD]: enough to process the largest individual field the HTTP + TEXT[!SHOULD]: implementation can be configured to accept. + TEXT[!MUST]: If an implementation encounters a value larger than it is able to + TEXT[!MUST]: decode, this MUST be treated as a stream error of type + TEXT[!MUST]: QPACK_DECOMPRESSION_FAILED if on a request stream or a connection + TEXT[!MUST]: error of the appropriate type if on the encoder or decoder stream. diff --git a/.duvet/specifications/www.rfc-editor.org/rfc/rfc9204.txt b/.duvet/specifications/www.rfc-editor.org/rfc/rfc9204.txt new file mode 100644 index 00000000..d9aa0f68 --- /dev/null +++ b/.duvet/specifications/www.rfc-editor.org/rfc/rfc9204.txt @@ -0,0 +1,2133 @@ + + + + +Internet Engineering Task Force (IETF) C. Krasic +Request for Comments: 9204 +Category: Standards Track M. Bishop +ISSN: 2070-1721 Akamai Technologies + A. Frindell, Ed. + Facebook + June 2022 + + + QPACK: Field Compression for HTTP/3 + +Abstract + + This specification defines QPACK: a compression format for + efficiently representing HTTP fields that is to be used in HTTP/3. + This is a variation of HPACK compression that seeks to reduce head- + of-line blocking. + +Status of This Memo + + This is an Internet Standards Track document. + + This document is a product of the Internet Engineering Task Force + (IETF). It represents the consensus of the IETF community. It has + received public review and has been approved for publication by the + Internet Engineering Steering Group (IESG). Further information on + Internet Standards is available in Section 2 of RFC 7841. + + Information about the current status of this document, any errata, + and how to provide feedback on it may be obtained at + https://www.rfc-editor.org/info/rfc9204. + +Copyright Notice + + Copyright (c) 2022 IETF Trust and the persons identified as the + document authors. All rights reserved. + + This document is subject to BCP 78 and the IETF Trust's Legal + Provisions Relating to IETF Documents + (https://trustee.ietf.org/license-info) in effect on the date of + publication of this document. Please review these documents + carefully, as they describe your rights and restrictions with respect + to this document. Code Components extracted from this document must + include Revised BSD License text as described in Section 4.e of the + Trust Legal Provisions and are provided without warranty as described + in the Revised BSD License. + +Table of Contents + + 1. Introduction + 1.1. Conventions and Definitions + 1.2. Notational Conventions + 2. Compression Process Overview + 2.1. Encoder + 2.1.1. Limits on Dynamic Table Insertions + 2.1.2. Blocked Streams + 2.1.3. Avoiding Flow-Control Deadlocks + 2.1.4. Known Received Count + 2.2. Decoder + 2.2.1. Blocked Decoding + 2.2.2. State Synchronization + 2.2.3. Invalid References + 3. Reference Tables + 3.1. Static Table + 3.2. Dynamic Table + 3.2.1. Dynamic Table Size + 3.2.2. Dynamic Table Capacity and Eviction + 3.2.3. Maximum Dynamic Table Capacity + 3.2.4. Absolute Indexing + 3.2.5. Relative Indexing + 3.2.6. Post-Base Indexing + 4. Wire Format + 4.1. Primitives + 4.1.1. Prefixed Integers + 4.1.2. String Literals + 4.2. Encoder and Decoder Streams + 4.3. Encoder Instructions + 4.3.1. Set Dynamic Table Capacity + 4.3.2. Insert with Name Reference + 4.3.3. Insert with Literal Name + 4.3.4. Duplicate + 4.4. Decoder Instructions + 4.4.1. Section Acknowledgment + 4.4.2. Stream Cancellation + 4.4.3. Insert Count Increment + 4.5. Field Line Representations + 4.5.1. Encoded Field Section Prefix + 4.5.2. Indexed Field Line + 4.5.3. Indexed Field Line with Post-Base Index + 4.5.4. Literal Field Line with Name Reference + 4.5.5. Literal Field Line with Post-Base Name Reference + 4.5.6. Literal Field Line with Literal Name + 5. Configuration + 6. Error Handling + 7. Security Considerations + 7.1. Probing Dynamic Table State + 7.1.1. Applicability to QPACK and HTTP + 7.1.2. Mitigation + 7.1.3. Never-Indexed Literals + 7.2. Static Huffman Encoding + 7.3. Memory Consumption + 7.4. Implementation Limits + 8. IANA Considerations + 8.1. Settings Registration + 8.2. Stream Type Registration + 8.3. Error Code Registration + 9. References + 9.1. Normative References + 9.2. Informative References + Appendix A. Static Table + Appendix B. Encoding and Decoding Examples + B.1. Literal Field Line with Name Reference + B.2. Dynamic Table + B.3. Speculative Insert + B.4. Duplicate Instruction, Stream Cancellation + B.5. Dynamic Table Insert, Eviction + Appendix C. Sample Single-Pass Encoding Algorithm + Acknowledgments + Authors' Addresses + +1. Introduction + + The QUIC transport protocol ([QUIC-TRANSPORT]) is designed to support + HTTP semantics, and its design subsumes many of the features of + HTTP/2 ([HTTP/2]). HTTP/2 uses HPACK ([RFC7541]) for compression of + the header and trailer sections. If HPACK were used for HTTP/3 + ([HTTP/3]), it would induce head-of-line blocking for field sections + due to built-in assumptions of a total ordering across frames on all + streams. + + QPACK reuses core concepts from HPACK, but is redesigned to allow + correctness in the presence of out-of-order delivery, with + flexibility for implementations to balance between resilience against + head-of-line blocking and optimal compression ratio. The design + goals are to closely approach the compression ratio of HPACK with + substantially less head-of-line blocking under the same loss + conditions. + +1.1. Conventions and Definitions + + The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", + "SHOULD", "SHOULD NOT", "RECOMMENDED", "NOT RECOMMENDED", "MAY", and + "OPTIONAL" in this document are to be interpreted as described in + BCP 14 [RFC2119] [RFC8174] when, and only when, they appear in all + capitals, as shown here. + + The following terms are used in this document: + + HTTP fields: Metadata sent as part of an HTTP message. The term + encompasses both header and trailer fields. Colloquially, the + term "headers" has often been used to refer to HTTP header fields + and trailer fields; this document uses "fields" for generality. + + HTTP field line: A name-value pair sent as part of an HTTP field + section. See Sections 6.3 and 6.5 of [HTTP]. + + HTTP field value: Data associated with a field name, composed from + all field line values with that field name in that section, + concatenated together with comma separators. + + Field section: An ordered collection of HTTP field lines associated + with an HTTP message. A field section can contain multiple field + lines with the same name. It can also contain duplicate field + lines. An HTTP message can include both header and trailer + sections. + + Representation: An instruction that represents a field line, + possibly by reference to the dynamic and static tables. + + Encoder: An implementation that encodes field sections. + + Decoder: An implementation that decodes encoded field sections. + + Absolute Index: A unique index for each entry in the dynamic table. + + Base: A reference point for relative and post-Base indices. + Representations that reference dynamic table entries are relative + to a Base. + + Insert Count: The total number of entries inserted in the dynamic + table. + + Note that QPACK is a name, not an abbreviation. + +1.2. Notational Conventions + + Diagrams in this document use the format described in Section 3.1 of + [RFC2360], with the following additional conventions: + + x (A) Indicates that x is A bits long. + + x (A+) Indicates that x uses the prefixed integer encoding defined + in Section 4.1.1, beginning with an A-bit prefix. + + x ... Indicates that x is variable length and extends to the end of + the region. + +2. Compression Process Overview + + Like HPACK, QPACK uses two tables for associating field lines + ("headers") to indices. The static table (Section 3.1) is predefined + and contains common header field lines (some of them with an empty + value). The dynamic table (Section 3.2) is built up over the course + of the connection and can be used by the encoder to index both header + and trailer field lines in the encoded field sections. + + QPACK defines unidirectional streams for sending instructions from + encoder to decoder and vice versa. + +2.1. Encoder + + An encoder converts a header or trailer section into a series of + representations by emitting either an indexed or a literal + representation for each field line in the list; see Section 4.5. + Indexed representations achieve high compression by replacing the + literal name and possibly the value with an index to either the + static or dynamic table. References to the static table and literal + representations do not require any dynamic state and never risk head- + of-line blocking. References to the dynamic table risk head-of-line + blocking if the encoder has not received an acknowledgment indicating + the entry is available at the decoder. + + An encoder MAY insert any entry in the dynamic table it chooses; it + is not limited to field lines it is compressing. + + QPACK preserves the ordering of field lines within each field + section. An encoder MUST emit field representations in the order + they appear in the input field section. + + QPACK is designed to place the burden of optional state tracking on + the encoder, resulting in relatively simple decoders. + +2.1.1. Limits on Dynamic Table Insertions + + Inserting entries into the dynamic table might not be possible if the + table contains entries that cannot be evicted. + + A dynamic table entry cannot be evicted immediately after insertion, + even if it has never been referenced. Once the insertion of a + dynamic table entry has been acknowledged and there are no + outstanding references to the entry in unacknowledged + representations, the entry becomes evictable. Note that references + on the encoder stream never preclude the eviction of an entry, + because those references are guaranteed to be processed before the + instruction evicting the entry. + + If the dynamic table does not contain enough room for a new entry + without evicting other entries, and the entries that would be evicted + are not evictable, the encoder MUST NOT insert that entry into the + dynamic table (including duplicates of existing entries). In order + to avoid this, an encoder that uses the dynamic table has to keep + track of each dynamic table entry referenced by each field section + until those representations are acknowledged by the decoder; see + Section 4.4.1. + +2.1.1.1. Avoiding Prohibited Insertions + + To ensure that the encoder is not prevented from adding new entries, + the encoder can avoid referencing entries that are close to eviction. + Rather than reference such an entry, the encoder can emit a Duplicate + instruction (Section 4.3.4) and reference the duplicate instead. + + Determining which entries are too close to eviction to reference is + an encoder preference. One heuristic is to target a fixed amount of + available space in the dynamic table: either unused space or space + that can be reclaimed by evicting non-blocking entries. To achieve + this, the encoder can maintain a draining index, which is the + smallest absolute index (Section 3.2.4) in the dynamic table that it + will emit a reference for. As new entries are inserted, the encoder + increases the draining index to maintain the section of the table + that it will not reference. If the encoder does not create new + references to entries with an absolute index lower than the draining + index, the number of unacknowledged references to those entries will + eventually become zero, allowing them to be evicted. + + <-- Newer Entries Older Entries --> + (Larger Indices) (Smaller Indices) + +--------+---------------------------------+----------+ + | Unused | Referenceable | Draining | + | Space | Entries | Entries | + +--------+---------------------------------+----------+ + ^ ^ ^ + | | | + Insertion Point Draining Index Dropping + Point + + Figure 1: Draining Dynamic Table Entries + +2.1.2. Blocked Streams + + Because QUIC does not guarantee order between data on different + streams, a decoder might encounter a representation that references a + dynamic table entry that it has not yet received. + + Each encoded field section contains a Required Insert Count + (Section 4.5.1), the lowest possible value for the Insert Count with + which the field section can be decoded. For a field section encoded + using references to the dynamic table, the Required Insert Count is + one larger than the largest absolute index of all referenced dynamic + table entries. For a field section encoded with no references to the + dynamic table, the Required Insert Count is zero. + + When the decoder receives an encoded field section with a Required + Insert Count greater than its own Insert Count, the stream cannot be + processed immediately and is considered "blocked"; see Section 2.2.1. + + The decoder specifies an upper bound on the number of streams that + can be blocked using the SETTINGS_QPACK_BLOCKED_STREAMS setting; see + Section 5. An encoder MUST limit the number of streams that could + become blocked to the value of SETTINGS_QPACK_BLOCKED_STREAMS at all + times. If a decoder encounters more blocked streams than it promised + to support, it MUST treat this as a connection error of type + QPACK_DECOMPRESSION_FAILED. + + Note that the decoder might not become blocked on every stream that + risks becoming blocked. + + An encoder can decide whether to risk having a stream become blocked. + If permitted by the value of SETTINGS_QPACK_BLOCKED_STREAMS, + compression efficiency can often be improved by referencing dynamic + table entries that are still in transit, but if there is loss or + reordering, the stream can become blocked at the decoder. An encoder + can avoid the risk of blocking by only referencing dynamic table + entries that have been acknowledged, but this could mean using + literals. Since literals make the encoded field section larger, this + can result in the encoder becoming blocked on congestion or flow- + control limits. + +2.1.3. Avoiding Flow-Control Deadlocks + + Writing instructions on streams that are limited by flow control can + produce deadlocks. + + A decoder might stop issuing flow-control credit on the stream that + carries an encoded field section until the necessary updates are + received on the encoder stream. If the granting of flow-control + credit on the encoder stream (or the connection as a whole) depends + on the consumption and release of data on the stream carrying the + encoded field section, a deadlock might result. + + More generally, a stream containing a large instruction can become + deadlocked if the decoder withholds flow-control credit until the + instruction is completely received. + + To avoid these deadlocks, an encoder SHOULD NOT write an instruction + unless sufficient stream and connection flow-control credit is + available for the entire instruction. + +2.1.4. Known Received Count + + The Known Received Count is the total number of dynamic table + insertions and duplications acknowledged by the decoder. The encoder + tracks the Known Received Count in order to identify which dynamic + table entries can be referenced without potentially blocking a + stream. The decoder tracks the Known Received Count in order to be + able to send Insert Count Increment instructions. + + A Section Acknowledgment instruction (Section 4.4.1) implies that the + decoder has received all dynamic table state necessary to decode the + field section. If the Required Insert Count of the acknowledged + field section is greater than the current Known Received Count, the + Known Received Count is updated to that Required Insert Count value. + + An Insert Count Increment instruction (Section 4.4.3) increases the + Known Received Count by its Increment parameter. See Section 2.2.2.3 + for guidance. + +2.2. Decoder + + As in HPACK, the decoder processes a series of representations and + emits the corresponding field sections. It also processes + instructions received on the encoder stream that modify the dynamic + table. Note that encoded field sections and encoder stream + instructions arrive on separate streams. This is unlike HPACK, where + encoded field sections (header blocks) can contain instructions that + modify the dynamic table, and there is no dedicated stream of HPACK + instructions. + + The decoder MUST emit field lines in the order their representations + appear in the encoded field section. + +2.2.1. Blocked Decoding + + Upon receipt of an encoded field section, the decoder examines the + Required Insert Count. When the Required Insert Count is less than + or equal to the decoder's Insert Count, the field section can be + processed immediately. Otherwise, the stream on which the field + section was received becomes blocked. + + While blocked, encoded field section data SHOULD remain in the + blocked stream's flow-control window. This data is unusable until + the stream becomes unblocked, and releasing the flow control + prematurely makes the decoder vulnerable to memory exhaustion + attacks. A stream becomes unblocked when the Insert Count becomes + greater than or equal to the Required Insert Count for all encoded + field sections the decoder has started reading from the stream. + + When processing encoded field sections, the decoder expects the + Required Insert Count to equal the lowest possible value for the + Insert Count with which the field section can be decoded, as + prescribed in Section 2.1.2. If it encounters a Required Insert + Count smaller than expected, it MUST treat this as a connection error + of type QPACK_DECOMPRESSION_FAILED; see Section 2.2.3. If it + encounters a Required Insert Count larger than expected, it MAY treat + this as a connection error of type QPACK_DECOMPRESSION_FAILED. + +2.2.2. State Synchronization + + The decoder signals the following events by emitting decoder + instructions (Section 4.4) on the decoder stream. + +2.2.2.1. Completed Processing of a Field Section + + After the decoder finishes decoding a field section encoded using + representations containing dynamic table references, it MUST emit a + Section Acknowledgment instruction (Section 4.4.1). A stream may + carry multiple field sections in the case of intermediate responses, + trailers, and pushed requests. The encoder interprets each + Section Acknowledgment instruction as acknowledging the earliest + unacknowledged field section containing dynamic table references sent + on the given stream. + +2.2.2.2. Abandonment of a Stream + + When an endpoint receives a stream reset before the end of a stream + or before all encoded field sections are processed on that stream, or + when it abandons reading of a stream, it generates a Stream + Cancellation instruction; see Section 4.4.2. This signals to the + encoder that all references to the dynamic table on that stream are + no longer outstanding. A decoder with a maximum dynamic table + capacity (Section 3.2.3) equal to zero MAY omit sending Stream + Cancellations, because the encoder cannot have any dynamic table + references. An encoder cannot infer from this instruction that any + updates to the dynamic table have been received. + + The Section Acknowledgment and Stream Cancellation instructions + permit the encoder to remove references to entries in the dynamic + table. When an entry with an absolute index lower than the Known + Received Count has zero references, then it is considered evictable; + see Section 2.1.1. + +2.2.2.3. New Table Entries + + After receiving new table entries on the encoder stream, the decoder + chooses when to emit Insert Count Increment instructions; see + Section 4.4.3. Emitting this instruction after adding each new + dynamic table entry will provide the timeliest feedback to the + encoder, but could be redundant with other decoder feedback. By + delaying an Insert Count Increment instruction, the decoder might be + able to coalesce multiple Insert Count Increment instructions or + replace them entirely with Section Acknowledgments; see + Section 4.4.1. However, delaying too long may lead to compression + inefficiencies if the encoder waits for an entry to be acknowledged + before using it. + +2.2.3. Invalid References + + If the decoder encounters a reference in a field line representation + to a dynamic table entry that has already been evicted or that has an + absolute index greater than or equal to the declared Required Insert + Count (Section 4.5.1), it MUST treat this as a connection error of + type QPACK_DECOMPRESSION_FAILED. + + If the decoder encounters a reference in an encoder instruction to a + dynamic table entry that has already been evicted, it MUST treat this + as a connection error of type QPACK_ENCODER_STREAM_ERROR. + +3. Reference Tables + + Unlike in HPACK, entries in the QPACK static and dynamic tables are + addressed separately. The following sections describe how entries in + each table are addressed. + +3.1. Static Table + + The static table consists of a predefined list of field lines, each + of which has a fixed index over time. Its entries are defined in + Appendix A. + + All entries in the static table have a name and a value. However, + values can be empty (that is, have a length of 0). Each entry is + identified by a unique index. + + Note that the QPACK static table is indexed from 0, whereas the HPACK + static table is indexed from 1. + + When the decoder encounters an invalid static table index in a field + line representation, it MUST treat this as a connection error of type + QPACK_DECOMPRESSION_FAILED. If this index is received on the encoder + stream, this MUST be treated as a connection error of type + QPACK_ENCODER_STREAM_ERROR. + +3.2. Dynamic Table + + The dynamic table consists of a list of field lines maintained in + first-in, first-out order. A QPACK encoder and decoder share a + dynamic table that is initially empty. The encoder adds entries to + the dynamic table and sends them to the decoder via instructions on + the encoder stream; see Section 4.3. + + The dynamic table can contain duplicate entries (i.e., entries with + the same name and same value). Therefore, duplicate entries MUST NOT + be treated as an error by the decoder. + + Dynamic table entries can have empty values. + +3.2.1. Dynamic Table Size + + The size of the dynamic table is the sum of the size of its entries. + + The size of an entry is the sum of its name's length in bytes, its + value's length in bytes, and 32 additional bytes. The size of an + entry is calculated using the length of its name and value without + Huffman encoding applied. + +3.2.2. Dynamic Table Capacity and Eviction + + The encoder sets the capacity of the dynamic table, which serves as + the upper limit on its size. The initial capacity of the dynamic + table is zero. The encoder sends a Set Dynamic Table Capacity + instruction (Section 4.3.1) with a non-zero capacity to begin using + the dynamic table. + + Before a new entry is added to the dynamic table, entries are evicted + from the end of the dynamic table until the size of the dynamic table + is less than or equal to (table capacity - size of new entry). The + encoder MUST NOT cause a dynamic table entry to be evicted unless + that entry is evictable; see Section 2.1.1. The new entry is then + added to the table. It is an error if the encoder attempts to add an + entry that is larger than the dynamic table capacity; the decoder + MUST treat this as a connection error of type + QPACK_ENCODER_STREAM_ERROR. + + A new entry can reference an entry in the dynamic table that will be + evicted when adding this new entry into the dynamic table. + Implementations are cautioned to avoid deleting the referenced name + or value if the referenced entry is evicted from the dynamic table + prior to inserting the new entry. + + Whenever the dynamic table capacity is reduced by the encoder + (Section 4.3.1), entries are evicted from the end of the dynamic + table until the size of the dynamic table is less than or equal to + the new table capacity. This mechanism can be used to completely + clear entries from the dynamic table by setting a capacity of 0, + which can subsequently be restored. + +3.2.3. Maximum Dynamic Table Capacity + + To bound the memory requirements of the decoder, the decoder limits + the maximum value the encoder is permitted to set for the dynamic + table capacity. In HTTP/3, this limit is determined by the value of + SETTINGS_QPACK_MAX_TABLE_CAPACITY sent by the decoder; see Section 5. + The encoder MUST NOT set a dynamic table capacity that exceeds this + maximum, but it can choose to use a lower dynamic table capacity; see + Section 4.3.1. + + For clients using 0-RTT data in HTTP/3, the server's maximum table + capacity is the remembered value of the setting or zero if the value + was not previously sent. When the client's 0-RTT value of the + SETTING is zero, the server MAY set it to a non-zero value in its + SETTINGS frame. If the remembered value is non-zero, the server MUST + send the same non-zero value in its SETTINGS frame. If it specifies + any other value, or omits SETTINGS_QPACK_MAX_TABLE_CAPACITY from + SETTINGS, the encoder must treat this as a connection error of type + QPACK_DECODER_STREAM_ERROR. + + For clients not using 0-RTT data (whether 0-RTT is not attempted or + is rejected) and for all HTTP/3 servers, the maximum table capacity + is 0 until the encoder processes a SETTINGS frame with a non-zero + value of SETTINGS_QPACK_MAX_TABLE_CAPACITY. + + When the maximum table capacity is zero, the encoder MUST NOT insert + entries into the dynamic table and MUST NOT send any encoder + instructions on the encoder stream. + +3.2.4. Absolute Indexing + + Each entry possesses an absolute index that is fixed for the lifetime + of that entry. The first entry inserted has an absolute index of 0; + indices increase by one with each insertion. + +3.2.5. Relative Indexing + + Relative indices begin at zero and increase in the opposite direction + from the absolute index. Determining which entry has a relative + index of 0 depends on the context of the reference. + + In encoder instructions (Section 4.3), a relative index of 0 refers + to the most recently inserted value in the dynamic table. Note that + this means the entry referenced by a given relative index will change + while interpreting instructions on the encoder stream. + + +-----+---------------+-------+ + | n-1 | ... | d | Absolute Index + + - - +---------------+ - - - + + | 0 | ... | n-d-1 | Relative Index + +-----+---------------+-------+ + ^ | + | V + Insertion Point Dropping Point + + n = count of entries inserted + d = count of entries dropped + + Figure 2: Example Dynamic Table Indexing - Encoder Stream + + Unlike in encoder instructions, relative indices in field line + representations are relative to the Base at the beginning of the + encoded field section; see Section 4.5.1. This ensures that + references are stable even if encoded field sections and dynamic + table updates are processed out of order. + + In a field line representation, a relative index of 0 refers to the + entry with absolute index equal to Base - 1. + + Base + | + V + +-----+-----+-----+-----+-------+ + | n-1 | n-2 | n-3 | ... | d | Absolute Index + +-----+-----+ - +-----+ - + + | 0 | ... | n-d-3 | Relative Index + +-----+-----+-------+ + + n = count of entries inserted + d = count of entries dropped + In this example, Base = n - 2 + + Figure 3: Example Dynamic Table Indexing - Relative Index in + Representation + +3.2.6. Post-Base Indexing + + Post-Base indices are used in field line representations for entries + with absolute indices greater than or equal to Base, starting at 0 + for the entry with absolute index equal to Base and increasing in the + same direction as the absolute index. + + Post-Base indices allow an encoder to process a field section in a + single pass and include references to entries added while processing + this (or other) field sections. + + Base + | + V + +-----+-----+-----+-----+-----+ + | n-1 | n-2 | n-3 | ... | d | Absolute Index + +-----+-----+-----+-----+-----+ + | 1 | 0 | Post-Base Index + +-----+-----+ + + n = count of entries inserted + d = count of entries dropped + In this example, Base = n - 2 + + Figure 4: Example Dynamic Table Indexing - Post-Base Index in + Representation + +4. Wire Format + +4.1. Primitives + +4.1.1. Prefixed Integers + + The prefixed integer from Section 5.1 of [RFC7541] is used heavily + throughout this document. The format from [RFC7541] is used + unmodified. Note, however, that QPACK uses some prefix sizes not + actually used in HPACK. + + QPACK implementations MUST be able to decode integers up to and + including 62 bits long. + +4.1.2. String Literals + + The string literal defined by Section 5.2 of [RFC7541] is also used + throughout. This string format includes optional Huffman encoding. + + HPACK defines string literals to begin on a byte boundary. They + begin with a single bit flag, denoted as 'H' in this document + (indicating whether the string is Huffman encoded), followed by the + string length encoded as a 7-bit prefix integer, and finally the + indicated number of bytes of data. When Huffman encoding is enabled, + the Huffman table from Appendix B of [RFC7541] is used without + modification and the indicated length is the size of the string after + encoding. + + This document expands the definition of string literals by permitting + them to begin other than on a byte boundary. An "N-bit prefix string + literal" begins mid-byte, with the first (8-N) bits allocated to a + previous field. The string uses one bit for the Huffman flag, + followed by the length of the encoded string as a (N-1)-bit prefix + integer. The prefix size, N, can have a value between 2 and 8, + inclusive. The remainder of the string literal is unmodified. + + A string literal without a prefix length noted is an 8-bit prefix + string literal and follows the definitions in [RFC7541] without + modification. + +4.2. Encoder and Decoder Streams + + QPACK defines two unidirectional stream types: + + * An encoder stream is a unidirectional stream of type 0x02. It + carries an unframed sequence of encoder instructions from encoder + to decoder. + + * A decoder stream is a unidirectional stream of type 0x03. It + carries an unframed sequence of decoder instructions from decoder + to encoder. + + HTTP/3 endpoints contain a QPACK encoder and decoder. Each endpoint + MUST initiate, at most, one encoder stream and, at most, one decoder + stream. Receipt of a second instance of either stream type MUST be + treated as a connection error of type H3_STREAM_CREATION_ERROR. + + The sender MUST NOT close either of these streams, and the receiver + MUST NOT request that the sender close either of these streams. + Closure of either unidirectional stream type MUST be treated as a + connection error of type H3_CLOSED_CRITICAL_STREAM. + + An endpoint MAY avoid creating an encoder stream if it will not be + used (for example, if its encoder does not wish to use the dynamic + table or if the maximum size of the dynamic table permitted by the + peer is zero). + + An endpoint MAY avoid creating a decoder stream if its decoder sets + the maximum capacity of the dynamic table to zero. + + An endpoint MUST allow its peer to create an encoder stream and a + decoder stream even if the connection's settings prevent their use. + +4.3. Encoder Instructions + + An encoder sends encoder instructions on the encoder stream to set + the capacity of the dynamic table and add dynamic table entries. + Instructions adding table entries can use existing entries to avoid + transmitting redundant information. The name can be transmitted as a + reference to an existing entry in the static or the dynamic table or + as a string literal. For entries that already exist in the dynamic + table, the full entry can also be used by reference, creating a + duplicate entry. + +4.3.1. Set Dynamic Table Capacity + + An encoder informs the decoder of a change to the dynamic table + capacity using an instruction that starts with the '001' 3-bit + pattern. This is followed by the new dynamic table capacity + represented as an integer with a 5-bit prefix; see Section 4.1.1. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 0 | 0 | 1 | Capacity (5+) | + +---+---+---+-------------------+ + + Figure 5: Set Dynamic Table Capacity + + The new capacity MUST be lower than or equal to the limit described + in Section 3.2.3. In HTTP/3, this limit is the value of the + SETTINGS_QPACK_MAX_TABLE_CAPACITY parameter (Section 5) received from + the decoder. The decoder MUST treat a new dynamic table capacity + value that exceeds this limit as a connection error of type + QPACK_ENCODER_STREAM_ERROR. + + Reducing the dynamic table capacity can cause entries to be evicted; + see Section 3.2.2. This MUST NOT cause the eviction of entries that + are not evictable; see Section 2.1.1. Changing the capacity of the + dynamic table is not acknowledged as this instruction does not insert + an entry. + +4.3.2. Insert with Name Reference + + An encoder adds an entry to the dynamic table where the field name + matches the field name of an entry stored in the static or the + dynamic table using an instruction that starts with the '1' 1-bit + pattern. The second ('T') bit indicates whether the reference is to + the static or dynamic table. The 6-bit prefix integer + (Section 4.1.1) that follows is used to locate the table entry for + the field name. When T=1, the number represents the static table + index; when T=0, the number is the relative index of the entry in the + dynamic table. + + The field name reference is followed by the field value represented + as a string literal; see Section 4.1.2. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 1 | T | Name Index (6+) | + +---+---+-----------------------+ + | H | Value Length (7+) | + +---+---------------------------+ + | Value String (Length bytes) | + +-------------------------------+ + + Figure 6: Insert Field Line -- Indexed Name + +4.3.3. Insert with Literal Name + + An encoder adds an entry to the dynamic table where both the field + name and the field value are represented as string literals using an + instruction that starts with the '01' 2-bit pattern. + + This is followed by the name represented as a 6-bit prefix string + literal and the value represented as an 8-bit prefix string literal; + see Section 4.1.2. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 0 | 1 | H | Name Length (5+) | + +---+---+---+-------------------+ + | Name String (Length bytes) | + +---+---------------------------+ + | H | Value Length (7+) | + +---+---------------------------+ + | Value String (Length bytes) | + +-------------------------------+ + + Figure 7: Insert Field Line -- New Name + +4.3.4. Duplicate + + An encoder duplicates an existing entry in the dynamic table using an + instruction that starts with the '000' 3-bit pattern. This is + followed by the relative index of the existing entry represented as + an integer with a 5-bit prefix; see Section 4.1.1. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 0 | 0 | 0 | Index (5+) | + +---+---+---+-------------------+ + + Figure 8: Duplicate + + The existing entry is reinserted into the dynamic table without + resending either the name or the value. This is useful to avoid + adding a reference to an older entry, which might block inserting new + entries. + +4.4. Decoder Instructions + + A decoder sends decoder instructions on the decoder stream to inform + the encoder about the processing of field sections and table updates + to ensure consistency of the dynamic table. + +4.4.1. Section Acknowledgment + + After processing an encoded field section whose declared Required + Insert Count is not zero, the decoder emits a Section Acknowledgment + instruction. The instruction starts with the '1' 1-bit pattern, + followed by the field section's associated stream ID encoded as a + 7-bit prefix integer; see Section 4.1.1. + + This instruction is used as described in Sections 2.1.4 and 2.2.2. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 1 | Stream ID (7+) | + +---+---------------------------+ + + Figure 9: Section Acknowledgment + + If an encoder receives a Section Acknowledgment instruction referring + to a stream on which every encoded field section with a non-zero + Required Insert Count has already been acknowledged, this MUST be + treated as a connection error of type QPACK_DECODER_STREAM_ERROR. + + The Section Acknowledgment instruction might increase the Known + Received Count; see Section 2.1.4. + +4.4.2. Stream Cancellation + + When a stream is reset or reading is abandoned, the decoder emits a + Stream Cancellation instruction. The instruction starts with the + '01' 2-bit pattern, followed by the stream ID of the affected stream + encoded as a 6-bit prefix integer. + + This instruction is used as described in Section 2.2.2. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 0 | 1 | Stream ID (6+) | + +---+---+-----------------------+ + + Figure 10: Stream Cancellation + +4.4.3. Insert Count Increment + + The Insert Count Increment instruction starts with the '00' 2-bit + pattern, followed by the Increment encoded as a 6-bit prefix integer. + This instruction increases the Known Received Count (Section 2.1.4) + by the value of the Increment parameter. The decoder should send an + Increment value that increases the Known Received Count to the total + number of dynamic table insertions and duplications processed so far. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 0 | 0 | Increment (6+) | + +---+---+-----------------------+ + + Figure 11: Insert Count Increment + + An encoder that receives an Increment field equal to zero, or one + that increases the Known Received Count beyond what the encoder has + sent, MUST treat this as a connection error of type + QPACK_DECODER_STREAM_ERROR. + +4.5. Field Line Representations + + An encoded field section consists of a prefix and a possibly empty + sequence of representations defined in this section. Each + representation corresponds to a single field line. These + representations reference the static table or the dynamic table in a + particular state, but they do not modify that state. + + Encoded field sections are carried in frames on streams defined by + the enclosing protocol. + +4.5.1. Encoded Field Section Prefix + + Each encoded field section is prefixed with two integers. The + Required Insert Count is encoded as an integer with an 8-bit prefix + using the encoding described in Section 4.5.1.1. The Base is encoded + as a Sign bit ('S') and a Delta Base value with a 7-bit prefix; see + Section 4.5.1.2. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | Required Insert Count (8+) | + +---+---------------------------+ + | S | Delta Base (7+) | + +---+---------------------------+ + | Encoded Field Lines ... + +-------------------------------+ + + Figure 12: Encoded Field Section + +4.5.1.1. Required Insert Count + + Required Insert Count identifies the state of the dynamic table + needed to process the encoded field section. Blocking decoders use + the Required Insert Count to determine when it is safe to process the + rest of the field section. + + The encoder transforms the Required Insert Count as follows before + encoding: + + if ReqInsertCount == 0: + EncInsertCount = 0 + else: + EncInsertCount = (ReqInsertCount mod (2 * MaxEntries)) + 1 + + Here MaxEntries is the maximum number of entries that the dynamic + table can have. The smallest entry has empty name and value strings + and has the size of 32. Hence, MaxEntries is calculated as: + + MaxEntries = floor( MaxTableCapacity / 32 ) + + MaxTableCapacity is the maximum capacity of the dynamic table as + specified by the decoder; see Section 3.2.3. + + This encoding limits the length of the prefix on long-lived + connections. + + The decoder can reconstruct the Required Insert Count using an + algorithm such as the following. If the decoder encounters a value + of EncodedInsertCount that could not have been produced by a + conformant encoder, it MUST treat this as a connection error of type + QPACK_DECOMPRESSION_FAILED. + + TotalNumberOfInserts is the total number of inserts into the + decoder's dynamic table. + + FullRange = 2 * MaxEntries + if EncodedInsertCount == 0: + ReqInsertCount = 0 + else: + if EncodedInsertCount > FullRange: + Error + MaxValue = TotalNumberOfInserts + MaxEntries + + # MaxWrapped is the largest possible value of + # ReqInsertCount that is 0 mod 2 * MaxEntries + MaxWrapped = floor(MaxValue / FullRange) * FullRange + ReqInsertCount = MaxWrapped + EncodedInsertCount - 1 + + # If ReqInsertCount exceeds MaxValue, the Encoder's value + # must have wrapped one fewer time + if ReqInsertCount > MaxValue: + if ReqInsertCount <= FullRange: + Error + ReqInsertCount -= FullRange + + # Value of 0 must be encoded as 0. + if ReqInsertCount == 0: + Error + + For example, if the dynamic table is 100 bytes, then the Required + Insert Count will be encoded modulo 6. If a decoder has received 10 + inserts, then an encoded value of 4 indicates that the Required + Insert Count is 9 for the field section. + +4.5.1.2. Base + + The Base is used to resolve references in the dynamic table as + described in Section 3.2.5. + + To save space, the Base is encoded relative to the Required Insert + Count using a one-bit Sign ('S' in Figure 12) and the Delta Base + value. A Sign bit of 0 indicates that the Base is greater than or + equal to the value of the Required Insert Count; the decoder adds the + value of Delta Base to the Required Insert Count to determine the + value of the Base. A Sign bit of 1 indicates that the Base is less + than the Required Insert Count; the decoder subtracts the value of + Delta Base from the Required Insert Count and also subtracts one to + determine the value of the Base. That is: + + if Sign == 0: + Base = ReqInsertCount + DeltaBase + else: + Base = ReqInsertCount - DeltaBase - 1 + + A single-pass encoder determines the Base before encoding a field + section. If the encoder inserted entries in the dynamic table while + encoding the field section and is referencing them, Required Insert + Count will be greater than the Base, so the encoded difference is + negative and the Sign bit is set to 1. If the field section was not + encoded using representations that reference the most recent entry in + the table and did not insert any new entries, the Base will be + greater than the Required Insert Count, so the encoded difference + will be positive and the Sign bit is set to 0. + + The value of Base MUST NOT be negative. Though the protocol might + operate correctly with a negative Base using post-Base indexing, it + is unnecessary and inefficient. An endpoint MUST treat a field block + with a Sign bit of 1 as invalid if the value of Required Insert Count + is less than or equal to the value of Delta Base. + + An encoder that produces table updates before encoding a field + section might set Base to the value of Required Insert Count. In + such a case, both the Sign bit and the Delta Base will be set to + zero. + + A field section that was encoded without references to the dynamic + table can use any value for the Base; setting Delta Base to zero is + one of the most efficient encodings. + + For example, with a Required Insert Count of 9, a decoder receives a + Sign bit of 1 and a Delta Base of 2. This sets the Base to 6 and + enables post-Base indexing for three entries. In this example, a + relative index of 1 refers to the fifth entry that was added to the + table; a post-Base index of 1 refers to the eighth entry. + +4.5.2. Indexed Field Line + + An indexed field line representation identifies an entry in the + static table or an entry in the dynamic table with an absolute index + less than the value of the Base. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 1 | T | Index (6+) | + +---+---+-----------------------+ + + Figure 13: Indexed Field Line + + This representation starts with the '1' 1-bit pattern, followed by + the 'T' bit, indicating whether the reference is into the static or + dynamic table. The 6-bit prefix integer (Section 4.1.1) that follows + is used to locate the table entry for the field line. When T=1, the + number represents the static table index; when T=0, the number is the + relative index of the entry in the dynamic table. + +4.5.3. Indexed Field Line with Post-Base Index + + An indexed field line with post-Base index representation identifies + an entry in the dynamic table with an absolute index greater than or + equal to the value of the Base. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 0 | 0 | 0 | 1 | Index (4+) | + +---+---+---+---+---------------+ + + Figure 14: Indexed Field Line with Post-Base Index + + This representation starts with the '0001' 4-bit pattern. This is + followed by the post-Base index (Section 3.2.6) of the matching field + line, represented as an integer with a 4-bit prefix; see + Section 4.1.1. + +4.5.4. Literal Field Line with Name Reference + + A literal field line with name reference representation encodes a + field line where the field name matches the field name of an entry in + the static table or the field name of an entry in the dynamic table + with an absolute index less than the value of the Base. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 0 | 1 | N | T |Name Index (4+)| + +---+---+---+---+---------------+ + | H | Value Length (7+) | + +---+---------------------------+ + | Value String (Length bytes) | + +-------------------------------+ + + Figure 15: Literal Field Line with Name Reference + + This representation starts with the '01' 2-bit pattern. The + following bit, 'N', indicates whether an intermediary is permitted to + add this field line to the dynamic table on subsequent hops. When + the 'N' bit is set, the encoded field line MUST always be encoded + with a literal representation. In particular, when a peer sends a + field line that it received represented as a literal field line with + the 'N' bit set, it MUST use a literal representation to forward this + field line. This bit is intended for protecting field values that + are not to be put at risk by compressing them; see Section 7.1 for + more details. + + The fourth ('T') bit indicates whether the reference is to the static + or dynamic table. The 4-bit prefix integer (Section 4.1.1) that + follows is used to locate the table entry for the field name. When + T=1, the number represents the static table index; when T=0, the + number is the relative index of the entry in the dynamic table. + + Only the field name is taken from the dynamic table entry; the field + value is encoded as an 8-bit prefix string literal; see + Section 4.1.2. + +4.5.5. Literal Field Line with Post-Base Name Reference + + A literal field line with post-Base name reference representation + encodes a field line where the field name matches the field name of a + dynamic table entry with an absolute index greater than or equal to + the value of the Base. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 0 | 0 | 0 | 0 | N |NameIdx(3+)| + +---+---+---+---+---+-----------+ + | H | Value Length (7+) | + +---+---------------------------+ + | Value String (Length bytes) | + +-------------------------------+ + + Figure 16: Literal Field Line with Post-Base Name Reference + + This representation starts with the '0000' 4-bit pattern. The fifth + bit is the 'N' bit as described in Section 4.5.4. This is followed + by a post-Base index of the dynamic table entry (Section 3.2.6) + encoded as an integer with a 3-bit prefix; see Section 4.1.1. + + Only the field name is taken from the dynamic table entry; the field + value is encoded as an 8-bit prefix string literal; see + Section 4.1.2. + +4.5.6. Literal Field Line with Literal Name + + The literal field line with literal name representation encodes a + field name and a field value as string literals. + + 0 1 2 3 4 5 6 7 + +---+---+---+---+---+---+---+---+ + | 0 | 0 | 1 | N | H |NameLen(3+)| + +---+---+---+---+---+-----------+ + | Name String (Length bytes) | + +---+---------------------------+ + | H | Value Length (7+) | + +---+---------------------------+ + | Value String (Length bytes) | + +-------------------------------+ + + Figure 17: Literal Field Line with Literal Name + + This representation starts with the '001' 3-bit pattern. The fourth + bit is the 'N' bit as described in Section 4.5.4. The name follows, + represented as a 4-bit prefix string literal, then the value, + represented as an 8-bit prefix string literal; see Section 4.1.2. + +5. Configuration + + QPACK defines two settings for the HTTP/3 SETTINGS frame: + + SETTINGS_QPACK_MAX_TABLE_CAPACITY (0x01): The default value is zero. + See Section 3.2 for usage. This is the equivalent of the + SETTINGS_HEADER_TABLE_SIZE from HTTP/2. + + SETTINGS_QPACK_BLOCKED_STREAMS (0x07): The default value is zero. + See Section 2.1.2. + +6. Error Handling + + The following error codes are defined for HTTP/3 to indicate failures + of QPACK that prevent the stream or connection from continuing: + + QPACK_DECOMPRESSION_FAILED (0x0200): The decoder failed to interpret + an encoded field section and is not able to continue decoding that + field section. + + QPACK_ENCODER_STREAM_ERROR (0x0201): The decoder failed to interpret + an encoder instruction received on the encoder stream. + + QPACK_DECODER_STREAM_ERROR (0x0202): The encoder failed to interpret + a decoder instruction received on the decoder stream. + +7. Security Considerations + + This section describes potential areas of security concern with + QPACK: + + * Use of compression as a length-based oracle for verifying guesses + about secrets that are compressed into a shared compression + context. + + * Denial of service resulting from exhausting processing or memory + capacity at a decoder. + +7.1. Probing Dynamic Table State + + QPACK reduces the encoded size of field sections by exploiting the + redundancy inherent in protocols like HTTP. The ultimate goal of + this is to reduce the amount of data that is required to send HTTP + requests or responses. + + The compression context used to encode header and trailer fields can + be probed by an attacker who can both define fields to be encoded and + transmitted and observe the length of those fields once they are + encoded. When an attacker can do both, they can adaptively modify + requests in order to confirm guesses about the dynamic table state. + If a guess is compressed into a shorter length, the attacker can + observe the encoded length and infer that the guess was correct. + + This is possible even over the Transport Layer Security Protocol + ([TLS]) and the QUIC Transport Protocol ([QUIC-TRANSPORT]), because + while TLS and QUIC provide confidentiality protection for content, + they only provide a limited amount of protection for the length of + that content. + + | Note: Padding schemes only provide limited protection against + | an attacker with these capabilities, potentially only forcing + | an increased number of guesses to learn the length associated + | with a given guess. Padding schemes also work directly against + | compression by increasing the number of bits that are + | transmitted. + + Attacks like CRIME ([CRIME]) demonstrated the existence of these + general attacker capabilities. The specific attack exploited the + fact that DEFLATE ([RFC1951]) removes redundancy based on prefix + matching. This permitted the attacker to confirm guesses a character + at a time, reducing an exponential-time attack into a linear-time + attack. + +7.1.1. Applicability to QPACK and HTTP + + QPACK mitigates, but does not completely prevent, attacks modeled on + CRIME ([CRIME]) by forcing a guess to match an entire field line + rather than individual characters. An attacker can only learn + whether a guess is correct or not, so the attacker is reduced to a + brute-force guess for the field values associated with a given field + name. + + Therefore, the viability of recovering specific field values depends + on the entropy of values. As a result, values with high entropy are + unlikely to be recovered successfully. However, values with low + entropy remain vulnerable. + + Attacks of this nature are possible any time that two mutually + distrustful entities control requests or responses that are placed + onto a single HTTP/3 connection. If the shared QPACK compressor + permits one entity to add entries to the dynamic table, and the other + to refer to those entries while encoding chosen field lines, then the + attacker (the second entity) can learn the state of the table by + observing the length of the encoded output. + + For example, requests or responses from mutually distrustful entities + can occur when an intermediary either: + + * sends requests from multiple clients on a single connection toward + an origin server, or + + * takes responses from multiple origin servers and places them on a + shared connection toward a client. + + Web browsers also need to assume that requests made on the same + connection by different web origins ([RFC6454]) are made by mutually + distrustful entities. Other scenarios involving mutually distrustful + entities are also possible. + +7.1.2. Mitigation + + Users of HTTP that require confidentiality for header or trailer + fields can use values with entropy sufficient to make guessing + infeasible. However, this is impractical as a general solution + because it forces all users of HTTP to take steps to mitigate + attacks. It would impose new constraints on how HTTP is used. + + Rather than impose constraints on users of HTTP, an implementation of + QPACK can instead constrain how compression is applied in order to + limit the potential for dynamic table probing. + + An ideal solution segregates access to the dynamic table based on the + entity that is constructing the message. Field values that are added + to the table are attributed to an entity, and only the entity that + created a particular value can extract that value. + + To improve compression performance of this option, certain entries + might be tagged as being public. For example, a web browser might + make the values of the Accept-Encoding header field available in all + requests. + + An encoder without good knowledge of the provenance of field values + might instead introduce a penalty for many field lines with the same + field name and different values. This penalty could cause a large + number of attempts to guess a field value to result in the field not + being compared to the dynamic table entries in future messages, + effectively preventing further guesses. + + This response might be made inversely proportional to the length of + the field value. Disabling access to the dynamic table for a given + field name might occur for shorter values more quickly or with higher + probability than for longer values. + + This mitigation is most effective between two endpoints. If messages + are re-encoded by an intermediary without knowledge of which entity + constructed a given message, the intermediary could inadvertently + merge compression contexts that the original encoder had specifically + kept separate. + + | Note: Simply removing entries corresponding to the field from + | the dynamic table can be ineffectual if the attacker has a + | reliable way of causing values to be reinstalled. For example, + | a request to load an image in a web browser typically includes + | the Cookie header field (a potentially highly valued target for + | this sort of attack), and websites can easily force an image to + | be loaded, thereby refreshing the entry in the dynamic table. + +7.1.3. Never-Indexed Literals + + Implementations can also choose to protect sensitive fields by not + compressing them and instead encoding their value as literals. + + Refusing to insert a field line into the dynamic table is only + effective if doing so is avoided on all hops. The never-indexed + literal bit (see Section 4.5.4) can be used to signal to + intermediaries that a particular value was intentionally sent as a + literal. + + An intermediary MUST NOT re-encode a value that uses a literal + representation with the 'N' bit set with another representation that + would index it. If QPACK is used for re-encoding, a literal + representation with the 'N' bit set MUST be used. If HPACK is used + for re-encoding, the never-indexed literal representation (see + Section 6.2.3 of [RFC7541]) MUST be used. + + The choice to mark that a field value should never be indexed depends + on several factors. Since QPACK does not protect against guessing an + entire field value, short or low-entropy values are more readily + recovered by an adversary. Therefore, an encoder might choose not to + index values with low entropy. + + An encoder might also choose not to index values for fields that are + considered to be highly valuable or sensitive to recovery, such as + the Cookie or Authorization header fields. + + On the contrary, an encoder might prefer indexing values for fields + that have little or no value if they were exposed. For instance, a + User-Agent header field does not commonly vary between requests and + is sent to any server. In that case, confirmation that a particular + User-Agent value has been used provides little value. + + Note that these criteria for deciding to use a never-indexed literal + representation will evolve over time as new attacks are discovered. + +7.2. Static Huffman Encoding + + There is no currently known attack against a static Huffman encoding. + A study has shown that using a static Huffman encoding table created + an information leakage; however, this same study concluded that an + attacker could not take advantage of this information leakage to + recover any meaningful amount of information (see [PETAL]). + +7.3. Memory Consumption + + An attacker can try to cause an endpoint to exhaust its memory. + QPACK is designed to limit both the peak and stable amounts of memory + allocated by an endpoint. + + QPACK uses the definition of the maximum size of the dynamic table + and the maximum number of blocking streams to limit the amount of + memory the encoder can cause the decoder to consume. In HTTP/3, + these values are controlled by the decoder through the settings + parameters SETTINGS_QPACK_MAX_TABLE_CAPACITY and + SETTINGS_QPACK_BLOCKED_STREAMS, respectively (see Section 3.2.3 and + Section 2.1.2). The limit on the size of the dynamic table takes + into account the size of the data stored in the dynamic table, plus a + small allowance for overhead. The limit on the number of blocked + streams is only a proxy for the maximum amount of memory required by + the decoder. The actual maximum amount of memory will depend on how + much memory the decoder uses to track each blocked stream. + + A decoder can limit the amount of state memory used for the dynamic + table by setting an appropriate value for the maximum size of the + dynamic table. In HTTP/3, this is realized by setting an appropriate + value for the SETTINGS_QPACK_MAX_TABLE_CAPACITY parameter. An + encoder can limit the amount of state memory it uses by choosing a + smaller dynamic table size than the decoder allows and signaling this + to the decoder (see Section 4.3.1). + + A decoder can limit the amount of state memory used for blocked + streams by setting an appropriate value for the maximum number of + blocked streams. In HTTP/3, this is realized by setting an + appropriate value for the SETTINGS_QPACK_BLOCKED_STREAMS parameter. + Streams that risk becoming blocked consume no additional state memory + on the encoder. + + An encoder allocates memory to track all dynamic table references in + unacknowledged field sections. An implementation can directly limit + the amount of state memory by only using as many references to the + dynamic table as it wishes to track; no signaling to the decoder is + required. However, limiting references to the dynamic table will + reduce compression effectiveness. + + The amount of temporary memory consumed by an encoder or decoder can + be limited by processing field lines sequentially. A decoder + implementation does not need to retain a complete list of field lines + while decoding a field section. An encoder implementation does not + need to retain a complete list of field lines while encoding a field + section if it is using a single-pass algorithm. Note that it might + be necessary for an application to retain a complete list of field + lines for other reasons; even if QPACK does not force this to occur, + application constraints might make this necessary. + + While the negotiated limit on the dynamic table size accounts for + much of the memory that can be consumed by a QPACK implementation, + data that cannot be immediately sent due to flow control is not + affected by this limit. Implementations should limit the size of + unsent data, especially on the decoder stream where flexibility to + choose what to send is limited. Possible responses to an excess of + unsent data might include limiting the ability of the peer to open + new streams, reading only from the encoder stream, or closing the + connection. + +7.4. Implementation Limits + + An implementation of QPACK needs to ensure that large values for + integers, long encoding for integers, or long string literals do not + create security weaknesses. + + An implementation has to set a limit for the values it accepts for + integers, as well as for the encoded length; see Section 4.1.1. In + the same way, it has to set a limit to the length it accepts for + string literals; see Section 4.1.2. These limits SHOULD be large + enough to process the largest individual field the HTTP + implementation can be configured to accept. + + If an implementation encounters a value larger than it is able to + decode, this MUST be treated as a stream error of type + QPACK_DECOMPRESSION_FAILED if on a request stream or a connection + error of the appropriate type if on the encoder or decoder stream. + +8. IANA Considerations + + This document makes multiple registrations in the registries defined + by [HTTP/3]. The allocations created by this document are all + assigned permanent status and list a change controller of the IETF + and a contact of the HTTP working group (ietf-http-wg@w3.org). + +8.1. Settings Registration + + This document specifies two settings. The entries in the following + table are registered in the "HTTP/3 Settings" registry established in + [HTTP/3]. + + +==========================+======+===============+=========+ + | Setting Name | Code | Specification | Default | + +==========================+======+===============+=========+ + | QPACK_MAX_TABLE_CAPACITY | 0x01 | Section 5 | 0 | + +--------------------------+------+---------------+---------+ + | QPACK_BLOCKED_STREAMS | 0x07 | Section 5 | 0 | + +--------------------------+------+---------------+---------+ + + Table 1: Additions to the HTTP/3 Settings Registry + + For formatting reasons, the setting names here are abbreviated by + removing the 'SETTINGS_' prefix. + +8.2. Stream Type Registration + + This document specifies two stream types. The entries in the + following table are registered in the "HTTP/3 Stream Types" registry + established in [HTTP/3]. + + +======================+======+===============+========+ + | Stream Type | Code | Specification | Sender | + +======================+======+===============+========+ + | QPACK Encoder Stream | 0x02 | Section 4.2 | Both | + +----------------------+------+---------------+--------+ + | QPACK Decoder Stream | 0x03 | Section 4.2 | Both | + +----------------------+------+---------------+--------+ + + Table 2: Additions to the HTTP/3 Stream Types Registry + +8.3. Error Code Registration + + This document specifies three error codes. The entries in the + following table are registered in the "HTTP/3 Error Codes" registry + established in [HTTP/3]. + + +============================+========+=============+===============+ + | Name | Code |Description | Specification | + +============================+========+=============+===============+ + | QPACK_DECOMPRESSION_FAILED | 0x0200 |Decoding of a| Section 6 | + | | |field section| | + | | |failed | | + +----------------------------+--------+-------------+---------------+ + | QPACK_ENCODER_STREAM_ERROR | 0x0201 |Error on the | Section 6 | + | | |encoder | | + | | |stream | | + +----------------------------+--------+-------------+---------------+ + | QPACK_DECODER_STREAM_ERROR | 0x0202 |Error on the | Section 6 | + | | |decoder | | + | | |stream | | + +----------------------------+--------+-------------+---------------+ + + Table 3: Additions to the HTTP/3 Error Codes Registry + +9. References + +9.1. Normative References + + [HTTP] Fielding, R., Ed., Nottingham, M., Ed., and J. Reschke, + Ed., "HTTP Semantics", STD 97, RFC 9110, + DOI 10.17487/RFC9110, June 2022, + . + + [HTTP/3] Bishop, M., Ed., "HTTP/3", RFC 9114, DOI 10.17487/RFC9114, + June 2022, . + + [QUIC-TRANSPORT] + Iyengar, J., Ed. and M. Thomson, Ed., "QUIC: A UDP-Based + Multiplexed and Secure Transport", RFC 9000, + DOI 10.17487/RFC9000, May 2021, + . + + [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate + Requirement Levels", BCP 14, RFC 2119, + DOI 10.17487/RFC2119, March 1997, + . + + [RFC2360] Scott, G., "Guide for Internet Standards Writers", BCP 22, + RFC 2360, DOI 10.17487/RFC2360, June 1998, + . + + [RFC7541] Peon, R. and H. Ruellan, "HPACK: Header Compression for + HTTP/2", RFC 7541, DOI 10.17487/RFC7541, May 2015, + . + + [RFC8174] Leiba, B., "Ambiguity of Uppercase vs Lowercase in RFC + 2119 Key Words", BCP 14, RFC 8174, DOI 10.17487/RFC8174, + May 2017, . + +9.2. Informative References + + [CRIME] Wikipedia, "CRIME", May 2015, . + + [HTTP/2] Thomson, M., Ed. and C. Benfield, Ed., "HTTP/2", RFC 9113, + DOI 10.17487/RFC9113, June 2022, + . + + [PETAL] Tan, J. and J. Nahata, "PETAL: Preset Encoding + Table Information Leakage", April 2013, + . + + [RFC1951] Deutsch, P., "DEFLATE Compressed Data Format Specification + version 1.3", RFC 1951, DOI 10.17487/RFC1951, May 1996, + . + + [RFC6454] Barth, A., "The Web Origin Concept", RFC 6454, + DOI 10.17487/RFC6454, December 2011, + . + + [TLS] Rescorla, E., "The Transport Layer Security (TLS) Protocol + Version 1.3", RFC 8446, DOI 10.17487/RFC8446, August 2018, + . + +Appendix A. Static Table + + This table was generated by analyzing actual Internet traffic in 2018 + and including the most common header fields, after filtering out some + unsupported and non-standard values. Due to this methodology, some + of the entries may be inconsistent or appear multiple times with + similar but not identical values. The order of the entries is + optimized to encode the most common header fields with the smallest + number of bytes. + + +=======+==================================+=======================+ + | Index | Name | Value | + +=======+==================================+=======================+ + | 0 | :authority | | + +-------+----------------------------------+-----------------------+ + | 1 | :path | / | + +-------+----------------------------------+-----------------------+ + | 2 | age | 0 | + +-------+----------------------------------+-----------------------+ + | 3 | content-disposition | | + +-------+----------------------------------+-----------------------+ + | 4 | content-length | 0 | + +-------+----------------------------------+-----------------------+ + | 5 | cookie | | + +-------+----------------------------------+-----------------------+ + | 6 | date | | + +-------+----------------------------------+-----------------------+ + | 7 | etag | | + +-------+----------------------------------+-----------------------+ + | 8 | if-modified-since | | + +-------+----------------------------------+-----------------------+ + | 9 | if-none-match | | + +-------+----------------------------------+-----------------------+ + | 10 | last-modified | | + +-------+----------------------------------+-----------------------+ + | 11 | link | | + +-------+----------------------------------+-----------------------+ + | 12 | location | | + +-------+----------------------------------+-----------------------+ + | 13 | referer | | + +-------+----------------------------------+-----------------------+ + | 14 | set-cookie | | + +-------+----------------------------------+-----------------------+ + | 15 | :method | CONNECT | + +-------+----------------------------------+-----------------------+ + | 16 | :method | DELETE | + +-------+----------------------------------+-----------------------+ + | 17 | :method | GET | + +-------+----------------------------------+-----------------------+ + | 18 | :method | HEAD | + +-------+----------------------------------+-----------------------+ + | 19 | :method | OPTIONS | + +-------+----------------------------------+-----------------------+ + | 20 | :method | POST | + +-------+----------------------------------+-----------------------+ + | 21 | :method | PUT | + +-------+----------------------------------+-----------------------+ + | 22 | :scheme | http | + +-------+----------------------------------+-----------------------+ + | 23 | :scheme | https | + +-------+----------------------------------+-----------------------+ + | 24 | :status | 103 | + +-------+----------------------------------+-----------------------+ + | 25 | :status | 200 | + +-------+----------------------------------+-----------------------+ + | 26 | :status | 304 | + +-------+----------------------------------+-----------------------+ + | 27 | :status | 404 | + +-------+----------------------------------+-----------------------+ + | 28 | :status | 503 | + +-------+----------------------------------+-----------------------+ + | 29 | accept | */* | + +-------+----------------------------------+-----------------------+ + | 30 | accept | application/dns- | + | | | message | + +-------+----------------------------------+-----------------------+ + | 31 | accept-encoding | gzip, deflate, br | + +-------+----------------------------------+-----------------------+ + | 32 | accept-ranges | bytes | + +-------+----------------------------------+-----------------------+ + | 33 | access-control-allow-headers | cache-control | + +-------+----------------------------------+-----------------------+ + | 34 | access-control-allow-headers | content-type | + +-------+----------------------------------+-----------------------+ + | 35 | access-control-allow-origin | * | + +-------+----------------------------------+-----------------------+ + | 36 | cache-control | max-age=0 | + +-------+----------------------------------+-----------------------+ + | 37 | cache-control | max-age=2592000 | + +-------+----------------------------------+-----------------------+ + | 38 | cache-control | max-age=604800 | + +-------+----------------------------------+-----------------------+ + | 39 | cache-control | no-cache | + +-------+----------------------------------+-----------------------+ + | 40 | cache-control | no-store | + +-------+----------------------------------+-----------------------+ + | 41 | cache-control | public, max- | + | | | age=31536000 | + +-------+----------------------------------+-----------------------+ + | 42 | content-encoding | br | + +-------+----------------------------------+-----------------------+ + | 43 | content-encoding | gzip | + +-------+----------------------------------+-----------------------+ + | 44 | content-type | application/dns- | + | | | message | + +-------+----------------------------------+-----------------------+ + | 45 | content-type | application/ | + | | | javascript | + +-------+----------------------------------+-----------------------+ + | 46 | content-type | application/json | + +-------+----------------------------------+-----------------------+ + | 47 | content-type | application/x-www- | + | | | form-urlencoded | + +-------+----------------------------------+-----------------------+ + | 48 | content-type | image/gif | + +-------+----------------------------------+-----------------------+ + | 49 | content-type | image/jpeg | + +-------+----------------------------------+-----------------------+ + | 50 | content-type | image/png | + +-------+----------------------------------+-----------------------+ + | 51 | content-type | text/css | + +-------+----------------------------------+-----------------------+ + | 52 | content-type | text/html; | + | | | charset=utf-8 | + +-------+----------------------------------+-----------------------+ + | 53 | content-type | text/plain | + +-------+----------------------------------+-----------------------+ + | 54 | content-type | text/ | + | | | plain;charset=utf-8 | + +-------+----------------------------------+-----------------------+ + | 55 | range | bytes=0- | + +-------+----------------------------------+-----------------------+ + | 56 | strict-transport-security | max-age=31536000 | + +-------+----------------------------------+-----------------------+ + | 57 | strict-transport-security | max-age=31536000; | + | | | includesubdomains | + +-------+----------------------------------+-----------------------+ + | 58 | strict-transport-security | max-age=31536000; | + | | | includesubdomains; | + | | | preload | + +-------+----------------------------------+-----------------------+ + | 59 | vary | accept-encoding | + +-------+----------------------------------+-----------------------+ + | 60 | vary | origin | + +-------+----------------------------------+-----------------------+ + | 61 | x-content-type-options | nosniff | + +-------+----------------------------------+-----------------------+ + | 62 | x-xss-protection | 1; mode=block | + +-------+----------------------------------+-----------------------+ + | 63 | :status | 100 | + +-------+----------------------------------+-----------------------+ + | 64 | :status | 204 | + +-------+----------------------------------+-----------------------+ + | 65 | :status | 206 | + +-------+----------------------------------+-----------------------+ + | 66 | :status | 302 | + +-------+----------------------------------+-----------------------+ + | 67 | :status | 400 | + +-------+----------------------------------+-----------------------+ + | 68 | :status | 403 | + +-------+----------------------------------+-----------------------+ + | 69 | :status | 421 | + +-------+----------------------------------+-----------------------+ + | 70 | :status | 425 | + +-------+----------------------------------+-----------------------+ + | 71 | :status | 500 | + +-------+----------------------------------+-----------------------+ + | 72 | accept-language | | + +-------+----------------------------------+-----------------------+ + | 73 | access-control-allow-credentials | FALSE | + +-------+----------------------------------+-----------------------+ + | 74 | access-control-allow-credentials | TRUE | + +-------+----------------------------------+-----------------------+ + | 75 | access-control-allow-headers | * | + +-------+----------------------------------+-----------------------+ + | 76 | access-control-allow-methods | get | + +-------+----------------------------------+-----------------------+ + | 77 | access-control-allow-methods | get, post, options | + +-------+----------------------------------+-----------------------+ + | 78 | access-control-allow-methods | options | + +-------+----------------------------------+-----------------------+ + | 79 | access-control-expose-headers | content-length | + +-------+----------------------------------+-----------------------+ + | 80 | access-control-request-headers | content-type | + +-------+----------------------------------+-----------------------+ + | 81 | access-control-request-method | get | + +-------+----------------------------------+-----------------------+ + | 82 | access-control-request-method | post | + +-------+----------------------------------+-----------------------+ + | 83 | alt-svc | clear | + +-------+----------------------------------+-----------------------+ + | 84 | authorization | | + +-------+----------------------------------+-----------------------+ + | 85 | content-security-policy | script-src 'none'; | + | | | object-src 'none'; | + | | | base-uri 'none' | + +-------+----------------------------------+-----------------------+ + | 86 | early-data | 1 | + +-------+----------------------------------+-----------------------+ + | 87 | expect-ct | | + +-------+----------------------------------+-----------------------+ + | 88 | forwarded | | + +-------+----------------------------------+-----------------------+ + | 89 | if-range | | + +-------+----------------------------------+-----------------------+ + | 90 | origin | | + +-------+----------------------------------+-----------------------+ + | 91 | purpose | prefetch | + +-------+----------------------------------+-----------------------+ + | 92 | server | | + +-------+----------------------------------+-----------------------+ + | 93 | timing-allow-origin | * | + +-------+----------------------------------+-----------------------+ + | 94 | upgrade-insecure-requests | 1 | + +-------+----------------------------------+-----------------------+ + | 95 | user-agent | | + +-------+----------------------------------+-----------------------+ + | 96 | x-forwarded-for | | + +-------+----------------------------------+-----------------------+ + | 97 | x-frame-options | deny | + +-------+----------------------------------+-----------------------+ + | 98 | x-frame-options | sameorigin | + +-------+----------------------------------+-----------------------+ + + Table 4: Static Table + + Any line breaks that appear within field names or values are due to + formatting. + +Appendix B. Encoding and Decoding Examples + + The following examples represent a series of exchanges between an + encoder and a decoder. The exchanges are designed to exercise most + QPACK instructions and highlight potentially common patterns and + their impact on dynamic table state. The encoder sends three encoded + field sections containing one field line each, as well as two + speculative inserts that are not referenced. + + The state of the encoder's dynamic table is shown, along with its + current size. Each entry is shown with the Absolute Index of the + entry (Abs), the current number of outstanding encoded field sections + with references to that entry (Ref), along with the name and value. + Entries above the 'acknowledged' line have been acknowledged by the + decoder. + +B.1. Literal Field Line with Name Reference + + The encoder sends an encoded field section containing a literal + representation of a field with a static name reference. + + Data | Interpretation + | Encoder's Dynamic Table + + Stream: 0 + 0000 | Required Insert Count = 0, Base = 0 + 510b 2f69 6e64 6578 | Literal Field Line with Name Reference + 2e68 746d 6c | Static Table, Index=1 + | (:path=/index.html) + + Abs Ref Name Value + ^-- acknowledged --^ + Size=0 + +B.2. Dynamic Table + + The encoder sets the dynamic table capacity, inserts a header with a + dynamic name reference, then sends a potentially blocking, encoded + field section referencing this new entry. The decoder acknowledges + processing the encoded field section, which implicitly acknowledges + all dynamic table insertions up to the Required Insert Count. + + Stream: Encoder + 3fbd01 | Set Dynamic Table Capacity=220 + c00f 7777 772e 6578 | Insert With Name Reference + 616d 706c 652e 636f | Static Table, Index=0 + 6d | (:authority=www.example.com) + c10c 2f73 616d 706c | Insert With Name Reference + 652f 7061 7468 | Static Table, Index=1 + | (:path=/sample/path) + + Abs Ref Name Value + ^-- acknowledged --^ + 0 0 :authority www.example.com + 1 0 :path /sample/path + Size=106 + + Stream: 4 + 0381 | Required Insert Count = 2, Base = 0 + 10 | Indexed Field Line With Post-Base Index + | Absolute Index = Base(0) + Index(0) = 0 + | (:authority=www.example.com) + 11 | Indexed Field Line With Post-Base Index + | Absolute Index = Base(0) + Index(1) = 1 + | (:path=/sample/path) + + Abs Ref Name Value + ^-- acknowledged --^ + 0 1 :authority www.example.com + 1 1 :path /sample/path + Size=106 + + Stream: Decoder + 84 | Section Acknowledgment (stream=4) + + Abs Ref Name Value + 0 0 :authority www.example.com + 1 0 :path /sample/path + ^-- acknowledged --^ + Size=106 + +B.3. Speculative Insert + + The encoder inserts a header into the dynamic table with a literal + name. The decoder acknowledges receipt of the entry. The encoder + does not send any encoded field sections. + + Stream: Encoder + 4a63 7573 746f 6d2d | Insert With Literal Name + 6b65 790c 6375 7374 | (custom-key=custom-value) + 6f6d 2d76 616c 7565 | + + Abs Ref Name Value + 0 0 :authority www.example.com + 1 0 :path /sample/path + ^-- acknowledged --^ + 2 0 custom-key custom-value + Size=160 + + Stream: Decoder + 01 | Insert Count Increment (1) + + Abs Ref Name Value + 0 0 :authority www.example.com + 1 0 :path /sample/path + 2 0 custom-key custom-value + ^-- acknowledged --^ + Size=160 + +B.4. Duplicate Instruction, Stream Cancellation + + The encoder duplicates an existing entry in the dynamic table, then + sends an encoded field section referencing the dynamic table entries + including the duplicated entry. The packet containing the encoder + stream data is delayed. Before the packet arrives, the decoder + cancels the stream and notifies the encoder that the encoded field + section was not processed. + + Stream: Encoder + 02 | Duplicate (Relative Index = 2) + | Absolute Index = + | Insert Count(3) - Index(2) - 1 = 0 + + Abs Ref Name Value + 0 0 :authority www.example.com + 1 0 :path /sample/path + 2 0 custom-key custom-value + ^-- acknowledged --^ + 3 0 :authority www.example.com + Size=217 + + Stream: 8 + 0500 | Required Insert Count = 4, Base = 4 + 80 | Indexed Field Line, Dynamic Table + | Absolute Index = Base(4) - Index(0) - 1 = 3 + | (:authority=www.example.com) + c1 | Indexed Field Line, Static Table Index = 1 + | (:path=/) + 81 | Indexed Field Line, Dynamic Table + | Absolute Index = Base(4) - Index(1) - 1 = 2 + | (custom-key=custom-value) + + Abs Ref Name Value + 0 0 :authority www.example.com + 1 0 :path /sample/path + 2 1 custom-key custom-value + ^-- acknowledged --^ + 3 1 :authority www.example.com + Size=217 + + Stream: Decoder + 48 | Stream Cancellation (Stream=8) + + Abs Ref Name Value + 0 0 :authority www.example.com + 1 0 :path /sample/path + 2 0 custom-key custom-value + ^-- acknowledged --^ + 3 0 :authority www.example.com + Size=217 + +B.5. Dynamic Table Insert, Eviction + + The encoder inserts another header into the dynamic table, which + evicts the oldest entry. The encoder does not send any encoded field + sections. + + Stream: Encoder + 810d 6375 7374 6f6d | Insert With Name Reference + 2d76 616c 7565 32 | Dynamic Table, Relative Index = 1 + | Absolute Index = + | Insert Count(4) - Index(1) - 1 = 2 + | (custom-key=custom-value2) + + Abs Ref Name Value + 1 0 :path /sample/path + 2 0 custom-key custom-value + ^-- acknowledged --^ + 3 0 :authority www.example.com + 4 0 custom-key custom-value2 + Size=215 + +Appendix C. Sample Single-Pass Encoding Algorithm + + Pseudocode for single-pass encoding, excluding handling of + duplicates, non-blocking mode, available encoder stream flow control + and reference tracking. + + # Helper functions: + # ==== + # Encode an integer with the specified prefix and length + encodeInteger(buffer, prefix, value, prefixLength) + + # Encode a dynamic table insert instruction with optional static + # or dynamic name index (but not both) + encodeInsert(buffer, staticNameIndex, dynamicNameIndex, fieldLine) + + # Encode a static index reference + encodeStaticIndexReference(buffer, staticIndex) + + # Encode a dynamic index reference relative to Base + encodeDynamicIndexReference(buffer, dynamicIndex, base) + + # Encode a literal with an optional static name index + encodeLiteral(buffer, staticNameIndex, fieldLine) + + # Encode a literal with a dynamic name index relative to Base + encodeDynamicLiteral(buffer, dynamicNameIndex, base, fieldLine) + + # Encoding Algorithm + # ==== + base = dynamicTable.getInsertCount() + requiredInsertCount = 0 + for line in fieldLines: + staticIndex = staticTable.findIndex(line) + if staticIndex is not None: + encodeStaticIndexReference(streamBuffer, staticIndex) + continue + + dynamicIndex = dynamicTable.findIndex(line) + if dynamicIndex is None: + # No matching entry. Either insert+index or encode literal + staticNameIndex = staticTable.findName(line.name) + if staticNameIndex is None: + dynamicNameIndex = dynamicTable.findName(line.name) + + if shouldIndex(line) and dynamicTable.canIndex(line): + encodeInsert(encoderBuffer, staticNameIndex, + dynamicNameIndex, line) + dynamicIndex = dynamicTable.add(line) + + if dynamicIndex is None: + # Could not index it, literal + if dynamicNameIndex is not None: + # Encode literal with dynamic name, possibly above Base + encodeDynamicLiteral(streamBuffer, dynamicNameIndex, + base, line) + requiredInsertCount = max(requiredInsertCount, + dynamicNameIndex) + else: + # Encodes a literal with a static name or literal name + encodeLiteral(streamBuffer, staticNameIndex, line) + else: + # Dynamic index reference + assert(dynamicIndex is not None) + requiredInsertCount = max(requiredInsertCount, dynamicIndex) + # Encode dynamicIndex, possibly above Base + encodeDynamicIndexReference(streamBuffer, dynamicIndex, base) + + # encode the prefix + if requiredInsertCount == 0: + encodeInteger(prefixBuffer, 0x00, 0, 8) + encodeInteger(prefixBuffer, 0x00, 0, 7) + else: + wireRIC = ( + requiredInsertCount + % (2 * getMaxEntries(maxTableCapacity)) + ) + 1; + encodeInteger(prefixBuffer, 0x00, wireRIC, 8) + if base >= requiredInsertCount: + encodeInteger(prefixBuffer, 0x00, + base - requiredInsertCount, 7) + else: + encodeInteger(prefixBuffer, 0x80, + requiredInsertCount - base - 1, 7) + + return encoderBuffer, prefixBuffer + streamBuffer + +Acknowledgments + + The IETF QUIC Working Group received an enormous amount of support + from many people. + + The compression design team did substantial work exploring the + problem space and influencing the initial draft version of this + document. The contributions of design team members Roberto Peon, + Martin Thomson, and Dmitri Tikhonov are gratefully acknowledged. + + The following people also provided substantial contributions to this + document: + + * Bence Beky + * Alessandro Ghedini + * Ryan Hamilton + * Robin Marx + * Patrick McManus + * 奥 一穂 (Kazuho Oku) + * Lucas Pardue + * Biren Roy + * Ian Swett + + This document draws heavily on the text of [RFC7541]. The indirect + input of those authors is also gratefully acknowledged. + + Buck Krasic's contribution was supported by Google during his + employment there. + + A portion of Mike Bishop's contribution was supported by Microsoft + during his employment there. + +Authors' Addresses + + Charles 'Buck' Krasic + Email: krasic@acm.org + + + Mike Bishop + Akamai Technologies + Email: mbishop@evequefou.be + + + Alan Frindell (editor) + Facebook + Email: afrind@fb.com From e0ef7a9becf24bad145feb5631ec01adf544d6c5 Mon Sep 17 00:00:00 2001 From: ruben Date: Sun, 6 Apr 2025 01:03:11 +0200 Subject: [PATCH 2/4] some progress, some things tried out. will see which direction this will go --- examples/server.rs | 7 ++- h3/Cargo.toml | 2 + h3/src/connection.rs | 5 ++ h3/src/qpack/decoder2/decoder.rs | 45 +++++++++++++++ .../qpack/decoder2/decoder_stream_handler.rs | 51 +++++++++++++++++ h3/src/qpack/decoder2/dynamic_table.rs | 56 +++++++++++++++++++ h3/src/qpack/decoder2/mod.rs | 6 ++ h3/src/qpack/mod.rs | 2 + h3/src/server/connection.rs | 22 ++++++-- h3/src/server/request.rs | 18 ++++-- 10 files changed, 199 insertions(+), 15 deletions(-) create mode 100644 h3/src/qpack/decoder2/decoder.rs create mode 100644 h3/src/qpack/decoder2/decoder_stream_handler.rs create mode 100644 h3/src/qpack/decoder2/dynamic_table.rs create mode 100644 h3/src/qpack/decoder2/mod.rs diff --git a/examples/server.rs b/examples/server.rs index a38c873a..9cab51ca 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -1,4 +1,4 @@ -use std::{net::SocketAddr, path::PathBuf, sync::Arc}; +use std::{future::Future, net::SocketAddr, path::PathBuf, sync::Arc}; use bytes::{Bytes, BytesMut}; use http::StatusCode; @@ -153,12 +153,13 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn handle_request( - resolver: RequestResolver, +async fn handle_request( + resolver: RequestResolver, serve_root: Arc>, ) -> Result<(), Box> where C: h3::quic::Connection, + F: Future> + Send + 'static, { let (req, mut stream) = resolver.resolve_request().await?; diff --git a/h3/Cargo.toml b/h3/Cargo.toml index 232da7ac..8d527735 100644 --- a/h3/Cargo.toml +++ b/h3/Cargo.toml @@ -31,6 +31,8 @@ tokio = { version = "1", features = ["sync"] } pin-project-lite = { version = "0.2", default-features = false } tracing = {version = "0.1.40", optional = true} fastrand = "2.0.1" +async-lock = "3.4.0" +ouroboros = "0.18.5" [dev-dependencies] assert_matches = "1.5.0" diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 275e4c98..2a1a3255 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -81,6 +81,10 @@ where control_send: C::SendStream, control_recv: Option>, qpack_streams: QpackStreams, + + /// QPack decoder stream handler + decoder: qpack::decoder2::decoder_stream_handler::DecoderStreamHandler, + /// Buffers incoming uni/recv streams which have yet to be claimed. /// /// This is opposed to discarding them by returning in `poll_accept_recv`, which may cause them to be missed by something else polling. @@ -307,6 +311,7 @@ where control_send: control_send, control_recv: None, qpack_streams, + decoder: todo!(), handled_connection_error: None, pending_recv_streams: Vec::with_capacity(3), got_peer_settings: false, diff --git a/h3/src/qpack/decoder2/decoder.rs b/h3/src/qpack/decoder2/decoder.rs new file mode 100644 index 00000000..97b3e3b9 --- /dev/null +++ b/h3/src/qpack/decoder2/decoder.rs @@ -0,0 +1,45 @@ +//! This module is responsible for decoding Headers + +use std::{sync::Arc, task::Poll}; + +use async_lock::futures::Read; +use ouroboros::self_referencing; +use tokio::sync::futures::Notified; + +use super::dynamic_table::RequestStreamDynamicTableForDecoder; + +/// Decoder for QPACK. Each RequestStream has a instance of this decoder +//#[self_referencing] +pub(crate) struct Decoder { + /// The dynamic table + state: Arc, + // Notifier if the request stream becomes blocked + // #[borrows(state)] + // #[not_covariant] + // blocked: Option>, +} + +impl Decoder { + /*/// Creates a new decoder + pub fn new(state: Arc) -> Self { + Self { + state, + blocked: None, + } + }*/ + fn poll_new_read( + &mut self, + cx: &mut std::task::Context, + ) -> Poll<()> { + // Poll the blocked stream + let x = self.state.table.read(); + todo!() + } + /*/// Sets the notifier for the blocked stream + pub fn set_blocked<'b>( mut self) { + self.state + .blocked_streams + .fetch_add(1, std::sync::atomic::Ordering::Relaxed); + self.blocked = Some(self.state.event.notified()); + }*/ +} diff --git a/h3/src/qpack/decoder2/decoder_stream_handler.rs b/h3/src/qpack/decoder2/decoder_stream_handler.rs new file mode 100644 index 00000000..93e4233b --- /dev/null +++ b/h3/src/qpack/decoder2/decoder_stream_handler.rs @@ -0,0 +1,51 @@ +//! This module is responsible for handling decoder_send and encoder_recv streams + +use std::task::{ready, Poll}; + +use crate::{error::internal_error::InternalConnectionError, quic, stream::BufRecvStream}; + +pub(crate) struct DecoderStreamHandler +where + C: quic::Connection, + B: bytes::Buf, +{ + decoder_send: C::SendStream, + pub(crate) encoder_recv: Option>, + table: super::dynamic_table::MainDynamicTableForDecoder, + // future: F, + +} + +impl DecoderStreamHandler +where + C: quic::Connection, + B: bytes::Buf, +{ + /// Creates a new decoder stream handler + pub fn new(decoder_send: C::SendStream) -> Self { + Self { + decoder_send, + encoder_recv: None, + table: super::dynamic_table::MainDynamicTableForDecoder::new(), + } + } + /// Returns a Decoder with a link to the same dynamic table + pub fn get_decoder(&self) -> super::decoder::Decoder { + // super::decoder::Decoder::new(self.table.shared()) + todo!() + } + + /// Listen for incoming data on the encoder_recv stream + pub fn poll_incoming_encoder_instructions( + &mut self, + cx: &mut std::task::Context, + ) -> Poll> { + // Poll the encoder_recv stream for incoming data + /*ready!(self.encoder_recv.poll_read(cx)).map_err(|e| { + todo!("Handle error: {}", e); + })?; + + */ + todo!("Handle incoming encoder instructions"); + } +} diff --git a/h3/src/qpack/decoder2/dynamic_table.rs b/h3/src/qpack/decoder2/dynamic_table.rs new file mode 100644 index 00000000..198ab9b7 --- /dev/null +++ b/h3/src/qpack/decoder2/dynamic_table.rs @@ -0,0 +1,56 @@ +//! Dynamic Table for QPACK encoder +//! Instructions for this dynamic table are received from the peer encoder + +use std::{ + collections::BTreeMap, + sync::{atomic::AtomicUsize, Arc}, +}; + +use async_lock::RwLock; +use tokio::sync::Notify; + +use crate::qpack::HeaderField; + +/// Dynamic Table for QPACK decoder +/// This decoder holds information relevant to the request-streams in an RWLock +pub(super) struct MainDynamicTableForDecoder { + /// The dynamic information relevant to the request-streams + shared: Arc, +} + +impl MainDynamicTableForDecoder { + /// Creates a new dynamic table for the decoder + pub fn new() -> Self { + Self { + shared: Arc::new(RequestStreamDynamicTableForDecoder { + table: RwLock::new(SharedDynamicTableForDecoder { + // TODO: Try out if HashMap is faster + table: BTreeMap::new(), + }), + blocked_streams: AtomicUsize::new(0), + event: Notify::new(), + }), + } + } + /// Returns a reference to the shared dynamic table + pub fn shared(&self) -> Arc { + self.shared.clone() + } + +} + +/// Struct, which holds the dynamic table for the request-streams +pub(crate) struct RequestStreamDynamicTableForDecoder { + /// The dynamic table + pub(super) table: RwLock, + /// Blocked streams + pub(super) blocked_streams: AtomicUsize, + /// Event, which is triggered when the table is updated + pub(super) event: Notify, +} + +/// Shared dynamic table for the decoder +pub(crate) struct SharedDynamicTableForDecoder { + /// The dynamic table + table: BTreeMap, +} diff --git a/h3/src/qpack/decoder2/mod.rs b/h3/src/qpack/decoder2/mod.rs new file mode 100644 index 00000000..98b55bc7 --- /dev/null +++ b/h3/src/qpack/decoder2/mod.rs @@ -0,0 +1,6 @@ +//! Internal module for the QPACK decoder. + + +pub(crate) mod dynamic_table; +pub(crate) mod decoder_stream_handler; +pub(crate) mod decoder; \ No newline at end of file diff --git a/h3/src/qpack/mod.rs b/h3/src/qpack/mod.rs index 2e33747c..68c3bdde 100644 --- a/h3/src/qpack/mod.rs +++ b/h3/src/qpack/mod.rs @@ -21,6 +21,8 @@ mod prefix_string; #[cfg(test)] mod tests; +pub(crate) mod decoder2; + #[derive(Debug)] pub enum Error { Encoder(EncoderError), diff --git a/h3/src/server/connection.rs b/h3/src/server/connection.rs index a9ba582a..9b724232 100644 --- a/h3/src/server/connection.rs +++ b/h3/src/server/connection.rs @@ -4,7 +4,7 @@ use std::{ collections::HashSet, - future::poll_fn, + future::{poll_fn, Future}, option::Option, result::Result, task::{ready, Context, Poll}, @@ -96,7 +96,10 @@ where { #[cfg(feature = "i-implement-a-third-party-backend-and-opt-into-breaking-changes")] /// Create a [`RequestResolver`] to handle an incoming request. - pub fn create_resolver(&self, stream: FrameStream) -> RequestResolver { + pub fn create_resolver( + &self, + stream: FrameStream, + ) -> RequestResolver>> { self.create_resolver_internal(stream) } @@ -120,7 +123,10 @@ where /// This method returns a [`RequestResolver`] which can be used to read the request and send the response. /// This method will return `None` when the connection receives a GOAWAY frame and all requests have been completed. #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] - pub async fn accept(&mut self) -> Result>, ConnectionError> { + pub async fn accept( + &mut self, + ) -> Result>>>, ConnectionError> + { // Accept the incoming stream let stream = match poll_fn(|cx| self.poll_accept_request_stream_internal(cx)).await? { Some(s) => FrameStream::new(BufRecvStream::new(s)), @@ -131,11 +137,10 @@ where return Ok(None); } }; - + self.inner.send_grease_frame = false; let resolver = self.create_resolver_internal(stream); // send the grease frame only once - self.inner.send_grease_frame = false; Ok(Some(resolver)) } @@ -143,13 +148,18 @@ where fn create_resolver_internal( &self, stream: FrameStream, - ) -> RequestResolver { + ) -> RequestResolver>> { RequestResolver { frame_stream: stream, request_end_send: self.request_end_send.clone(), send_grease_frame: self.inner.send_grease_frame, max_field_section_size: self.max_field_section_size, shared: self.inner.shared.clone(), + future: async { + // Wait for the request to finish + todo!(); + Ok(()) + }, } } diff --git a/h3/src/server/request.rs b/h3/src/server/request.rs index ecc20868..3d26d854 100644 --- a/h3/src/server/request.rs +++ b/h3/src/server/request.rs @@ -1,4 +1,4 @@ -use std::{convert::TryFrom, sync::Arc}; +use std::{convert::TryFrom, future::Future, sync::Arc}; use bytes::Buf; use http::{Request, StatusCode}; @@ -19,7 +19,7 @@ use crate::{ frame::{Frame, PayloadLen}, headers::Header, }, - qpack, + qpack::{self, decoder2}, quic::{self, SendStream, StreamId}, shared_state::{ConnectionState, SharedState}, }; @@ -27,11 +27,12 @@ use crate::{ use super::{connection::RequestEnd, stream::RequestStream}; /// Helper struct to await the request headers and return a `Request` object -pub struct RequestResolver +pub struct RequestResolver where C: quic::Connection, C::BidiStream: quic::SendStream, B: Buf, + F: Future>, { #[doc(hidden)] // TODO: make this private @@ -40,29 +41,34 @@ where pub(super) send_grease_frame: bool, pub(super) max_field_section_size: u64, pub(super) shared: Arc, + pub(super) future: F, + // pub(super) decoder: decoder2::decoder::Decoder, } -impl ConnectionState for RequestResolver +impl ConnectionState for RequestResolver where C: quic::Connection, B: Buf, + F: Future>, { fn shared_state(&self) -> &SharedState { &self.shared } } -impl CloseStream for RequestResolver +impl CloseStream for RequestResolver where C: quic::Connection, B: Buf, + F: Future>, { } -impl RequestResolver +impl RequestResolver where C: quic::Connection, B: Buf, + F: Future>, { /// Returns a future to await the request headers and return a `Request` object #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] From 6f01853864852decdb9d2c0b001a0586f27de3f4 Mon Sep 17 00:00:00 2001 From: ruben Date: Sat, 19 Apr 2025 22:01:09 +0200 Subject: [PATCH 3/4] progress --- examples/server.rs | 5 +- h3/Cargo.toml | 2 - h3/src/qpack/decoder2/decoder.rs | 39 +++----- .../qpack/decoder2/decoder_stream_handler.rs | 93 ++++++++++++++----- h3/src/qpack/decoder2/dynamic_table.rs | 49 ++++++---- h3/src/server/connection.rs | 11 +-- h3/src/server/request.rs | 13 +-- 7 files changed, 128 insertions(+), 84 deletions(-) diff --git a/examples/server.rs b/examples/server.rs index 9cab51ca..78b6b5ee 100644 --- a/examples/server.rs +++ b/examples/server.rs @@ -153,13 +153,12 @@ async fn main() -> Result<(), Box> { Ok(()) } -async fn handle_request( - resolver: RequestResolver, +async fn handle_request( + resolver: RequestResolver, serve_root: Arc>, ) -> Result<(), Box> where C: h3::quic::Connection, - F: Future> + Send + 'static, { let (req, mut stream) = resolver.resolve_request().await?; diff --git a/h3/Cargo.toml b/h3/Cargo.toml index 8d527735..232da7ac 100644 --- a/h3/Cargo.toml +++ b/h3/Cargo.toml @@ -31,8 +31,6 @@ tokio = { version = "1", features = ["sync"] } pin-project-lite = { version = "0.2", default-features = false } tracing = {version = "0.1.40", optional = true} fastrand = "2.0.1" -async-lock = "3.4.0" -ouroboros = "0.18.5" [dev-dependencies] assert_matches = "1.5.0" diff --git a/h3/src/qpack/decoder2/decoder.rs b/h3/src/qpack/decoder2/decoder.rs index 97b3e3b9..3e296703 100644 --- a/h3/src/qpack/decoder2/decoder.rs +++ b/h3/src/qpack/decoder2/decoder.rs @@ -2,44 +2,33 @@ use std::{sync::Arc, task::Poll}; -use async_lock::futures::Read; -use ouroboros::self_referencing; -use tokio::sync::futures::Notified; +use tokio::sync::Notify; use super::dynamic_table::RequestStreamDynamicTableForDecoder; /// Decoder for QPACK. Each RequestStream has a instance of this decoder -//#[self_referencing] +#[derive(Debug, Clone)] pub(crate) struct Decoder { /// The dynamic table - state: Arc, - // Notifier if the request stream becomes blocked - // #[borrows(state)] - // #[not_covariant] - // blocked: Option>, + state: RequestStreamDynamicTableForDecoder, } impl Decoder { - /*/// Creates a new decoder - pub fn new(state: Arc) -> Self { - Self { - state, - blocked: None, - } - }*/ - fn poll_new_read( - &mut self, - cx: &mut std::task::Context, - ) -> Poll<()> { + /// Creates a new decoder + pub fn new(state: RequestStreamDynamicTableForDecoder) -> Self { + Self { state } + } + fn poll_new_read(&mut self, cx: &mut std::task::Context) -> Poll<()> { // Poll the blocked stream - let x = self.state.table.read(); + let x = self.state.inner.table.read(); todo!() } - /*/// Sets the notifier for the blocked stream - pub fn set_blocked<'b>( mut self) { + /// Sets the notifier for the blocked stream + pub fn set_blocked(mut self) { self.state + .inner .blocked_streams .fetch_add(1, std::sync::atomic::Ordering::Relaxed); - self.blocked = Some(self.state.event.notified()); - }*/ + // self.blocked = Some(self.state.event.clone()); + } } diff --git a/h3/src/qpack/decoder2/decoder_stream_handler.rs b/h3/src/qpack/decoder2/decoder_stream_handler.rs index 93e4233b..e4fa0767 100644 --- a/h3/src/qpack/decoder2/decoder_stream_handler.rs +++ b/h3/src/qpack/decoder2/decoder_stream_handler.rs @@ -2,7 +2,16 @@ use std::task::{ready, Poll}; -use crate::{error::internal_error::InternalConnectionError, quic, stream::BufRecvStream}; +use tokio::sync::mpsc; +use tracing::event; + +use crate::{ + error::internal_error::InternalConnectionError, + quic::{self, RecvStream}, + stream::BufRecvStream, +}; + +use super::{decoder, dynamic_table::MainDynamicTableForDecoder}; pub(crate) struct DecoderStreamHandler where @@ -10,10 +19,10 @@ where B: bytes::Buf, { decoder_send: C::SendStream, - pub(crate) encoder_recv: Option>, + pub(crate) encoder_recv: BufRecvStream, table: super::dynamic_table::MainDynamicTableForDecoder, - // future: F, - + /// When decoder events are triggered, this channel is used to notify the decoder + decoder_event: mpsc::Receiver<()>, } impl DecoderStreamHandler @@ -22,30 +31,72 @@ where B: bytes::Buf, { /// Creates a new decoder stream handler - pub fn new(decoder_send: C::SendStream) -> Self { + pub fn new(decoder_send: C::SendStream, encoder_recv: BufRecvStream) -> Self { + let (table, event) = super::dynamic_table::MainDynamicTableForDecoder::new(); + Self { decoder_send, - encoder_recv: None, - table: super::dynamic_table::MainDynamicTableForDecoder::new(), + encoder_recv, + table: table, + decoder_event: event, } } /// Returns a Decoder with a link to the same dynamic table pub fn get_decoder(&self) -> super::decoder::Decoder { - // super::decoder::Decoder::new(self.table.shared()) - todo!() + super::decoder::Decoder::new(self.table.shared()) + } + + /// Function to handle the encoder streams + async fn handle_qpack_decoder(self) -> Result<(), InternalConnectionError> { + let Self { + decoder_send, + encoder_recv, + table, + decoder_event, + } = self; + + Err(tokio::select! { + err = recv_encoder_instruction(table, encoder_recv) => err, + err = send_decoder_instructions(decoder_send, decoder_event) => err, + }) + } +} + +/// Function to handle the encoder_recv stream +/// +/// This function is responsible for receiving the encoder instructions from the peer +/// The future resolves only in the event of an error +async fn recv_encoder_instruction( + table: MainDynamicTableForDecoder, + encoder_recv: S, +) -> InternalConnectionError +where + S: RecvStream, +{ + loop { + // Wait for encoder instructions from peer + + // Decode the instructions + + // Update the dynamic table } +} + +/// Function to handle the decoder_send stream +/// +/// This function is responsible for sending the decoder instructions to the peer +/// The future resolves only in the event of an error +async fn send_decoder_instructions( + decoder_send: S, + decoder_event: mpsc::Receiver<()>, +) -> InternalConnectionError +where + S: quic::SendStream, + B: bytes::Buf, +{ + loop { + // Wait for decoder events - /// Listen for incoming data on the encoder_recv stream - pub fn poll_incoming_encoder_instructions( - &mut self, - cx: &mut std::task::Context, - ) -> Poll> { - // Poll the encoder_recv stream for incoming data - /*ready!(self.encoder_recv.poll_read(cx)).map_err(|e| { - todo!("Handle error: {}", e); - })?; - - */ - todo!("Handle incoming encoder instructions"); + // Send the decoder instructions } } diff --git a/h3/src/qpack/decoder2/dynamic_table.rs b/h3/src/qpack/decoder2/dynamic_table.rs index 198ab9b7..21bfba6e 100644 --- a/h3/src/qpack/decoder2/dynamic_table.rs +++ b/h3/src/qpack/decoder2/dynamic_table.rs @@ -6,41 +6,57 @@ use std::{ sync::{atomic::AtomicUsize, Arc}, }; -use async_lock::RwLock; -use tokio::sync::Notify; +use tokio::sync::{mpsc, Notify, RwLock}; use crate::qpack::HeaderField; /// Dynamic Table for QPACK decoder /// This decoder holds information relevant to the request-streams in an RWLock +#[derive(Debug)] pub(super) struct MainDynamicTableForDecoder { /// The dynamic information relevant to the request-streams - shared: Arc, + shared: RequestStreamDynamicTableForDecoder, } impl MainDynamicTableForDecoder { /// Creates a new dynamic table for the decoder - pub fn new() -> Self { - Self { - shared: Arc::new(RequestStreamDynamicTableForDecoder { - table: RwLock::new(SharedDynamicTableForDecoder { - // TODO: Try out if HashMap is faster - table: BTreeMap::new(), - }), - blocked_streams: AtomicUsize::new(0), - event: Notify::new(), - }), - } + pub fn new() -> (Self, mpsc::Receiver<()>) { + let (sender, receiver) = mpsc::channel(10); + + ( + Self { + shared: RequestStreamDynamicTableForDecoder { + inner: Arc::new(InnnerDecoder { + table: RwLock::new(SharedDynamicTableForDecoder { + // TODO: Try out if HashMap is faster + table: BTreeMap::new(), + }), + blocked_streams: AtomicUsize::new(0), + event: Notify::new(), + }), + decoder_event: sender, + }, + }, + receiver, + ) } /// Returns a reference to the shared dynamic table - pub fn shared(&self) -> Arc { + pub fn shared(&self) -> RequestStreamDynamicTableForDecoder { self.shared.clone() } - } /// Struct, which holds the dynamic table for the request-streams +#[derive(Debug, Clone)] pub(crate) struct RequestStreamDynamicTableForDecoder { + /// The dynamic table + pub(super) inner: Arc, + /// Send decoder events on the decoder stream + pub(super) decoder_event: mpsc::Sender<()>, +} + +#[derive(Debug)] +pub(crate) struct InnnerDecoder { /// The dynamic table pub(super) table: RwLock, /// Blocked streams @@ -50,6 +66,7 @@ pub(crate) struct RequestStreamDynamicTableForDecoder { } /// Shared dynamic table for the decoder +#[derive(Debug)] pub(crate) struct SharedDynamicTableForDecoder { /// The dynamic table table: BTreeMap, diff --git a/h3/src/server/connection.rs b/h3/src/server/connection.rs index 9b724232..5a70550e 100644 --- a/h3/src/server/connection.rs +++ b/h3/src/server/connection.rs @@ -99,7 +99,7 @@ where pub fn create_resolver( &self, stream: FrameStream, - ) -> RequestResolver>> { + ) -> RequestResolver { self.create_resolver_internal(stream) } @@ -125,7 +125,7 @@ where #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] pub async fn accept( &mut self, - ) -> Result>>>, ConnectionError> + ) -> Result>, ConnectionError> { // Accept the incoming stream let stream = match poll_fn(|cx| self.poll_accept_request_stream_internal(cx)).await? { @@ -148,18 +148,13 @@ where fn create_resolver_internal( &self, stream: FrameStream, - ) -> RequestResolver>> { + ) -> RequestResolver { RequestResolver { frame_stream: stream, request_end_send: self.request_end_send.clone(), send_grease_frame: self.inner.send_grease_frame, max_field_section_size: self.max_field_section_size, shared: self.inner.shared.clone(), - future: async { - // Wait for the request to finish - todo!(); - Ok(()) - }, } } diff --git a/h3/src/server/request.rs b/h3/src/server/request.rs index 3d26d854..5eed6266 100644 --- a/h3/src/server/request.rs +++ b/h3/src/server/request.rs @@ -27,12 +27,11 @@ use crate::{ use super::{connection::RequestEnd, stream::RequestStream}; /// Helper struct to await the request headers and return a `Request` object -pub struct RequestResolver +pub struct RequestResolver where C: quic::Connection, C::BidiStream: quic::SendStream, B: Buf, - F: Future>, { #[doc(hidden)] // TODO: make this private @@ -41,34 +40,30 @@ where pub(super) send_grease_frame: bool, pub(super) max_field_section_size: u64, pub(super) shared: Arc, - pub(super) future: F, // pub(super) decoder: decoder2::decoder::Decoder, } -impl ConnectionState for RequestResolver +impl ConnectionState for RequestResolver where C: quic::Connection, B: Buf, - F: Future>, { fn shared_state(&self) -> &SharedState { &self.shared } } -impl CloseStream for RequestResolver +impl CloseStream for RequestResolver where C: quic::Connection, B: Buf, - F: Future>, { } -impl RequestResolver +impl RequestResolver where C: quic::Connection, B: Buf, - F: Future>, { /// Returns a future to await the request headers and return a `Request` object #[cfg_attr(feature = "tracing", instrument(skip_all, level = "trace"))] From 48690a564ebcf73b288aeb8a4b5a92d6bdee8397 Mon Sep 17 00:00:00 2001 From: ruben Date: Sun, 20 Apr 2025 15:17:34 +0200 Subject: [PATCH 4/4] some duvet citations --- .duvet/exceptions/rfc9204/4.2.toml | 24 ++++++++++++++++++++++++ .duvet/snapshot.txt | 22 +++++++++++----------- h3/src/connection.rs | 21 +++++++++++++++++++++ 3 files changed, 56 insertions(+), 11 deletions(-) create mode 100644 .duvet/exceptions/rfc9204/4.2.toml diff --git a/.duvet/exceptions/rfc9204/4.2.toml b/.duvet/exceptions/rfc9204/4.2.toml new file mode 100644 index 00000000..9e31fe9c --- /dev/null +++ b/.duvet/exceptions/rfc9204/4.2.toml @@ -0,0 +1,24 @@ +[[exception]] +target = "https://www.rfc-editor.org/rfc/rfc9204.html#section-4.2" +quote = ''' +An endpoint MAY avoid creating an encoder stream if it will not be +used (for example, if its encoder does not wish to use the dynamic +table or if the maximum size of the dynamic table permitted by the +peer is zero). +''' +reason = ''' +H3 creates the streams even if they are not used. +See https://github.com/hyperium/h3/pull/242 +''' + + +[[exception]] +target = "https://www.rfc-editor.org/rfc/rfc9204.html#section-4.2" +quote = ''' +An endpoint MAY avoid creating a decoder stream if its decoder sets +the maximum capacity of the dynamic table to zero. +''' +reason = ''' +H3 creates the streams even if they are not used. +See https://github.com/hyperium/h3/pull/242 +''' diff --git a/.duvet/snapshot.txt b/.duvet/snapshot.txt index 560c52f8..66022a75 100644 --- a/.duvet/snapshot.txt +++ b/.duvet/snapshot.txt @@ -966,21 +966,21 @@ SPECIFICATION: https://www.rfc-editor.org/rfc/rfc9204.html TEXT[!MUST]: including 62 bits long. SECTION: [Encoder and Decoder Streams](#section-4.2) - TEXT[!MUST]: Each endpoint - TEXT[!MUST]: MUST initiate, at most, one encoder stream and, at most, one decoder - TEXT[!MUST]: stream. - TEXT[!MUST]: Receipt of a second instance of either stream type MUST be - TEXT[!MUST]: treated as a connection error of type H3_STREAM_CREATION_ERROR. + TEXT[!MUST,implementation]: Each endpoint + TEXT[!MUST,implementation]: MUST initiate, at most, one encoder stream and, at most, one decoder + TEXT[!MUST,implementation]: stream. + TEXT[!MUST,implementation]: Receipt of a second instance of either stream type MUST be + TEXT[!MUST,implementation]: treated as a connection error of type H3_STREAM_CREATION_ERROR. TEXT[!MUST]: The sender MUST NOT close either of these streams, and the receiver TEXT[!MUST]: MUST NOT request that the sender close either of these streams. TEXT[!MUST]: Closure of either unidirectional stream type MUST be treated as a TEXT[!MUST]: connection error of type H3_CLOSED_CRITICAL_STREAM. - TEXT[!MAY]: An endpoint MAY avoid creating an encoder stream if it will not be - TEXT[!MAY]: used (for example, if its encoder does not wish to use the dynamic - TEXT[!MAY]: table or if the maximum size of the dynamic table permitted by the - TEXT[!MAY]: peer is zero). - TEXT[!MAY]: An endpoint MAY avoid creating a decoder stream if its decoder sets - TEXT[!MAY]: the maximum capacity of the dynamic table to zero. + TEXT[!MAY,exception]: An endpoint MAY avoid creating an encoder stream if it will not be + TEXT[!MAY,exception]: used (for example, if its encoder does not wish to use the dynamic + TEXT[!MAY,exception]: table or if the maximum size of the dynamic table permitted by the + TEXT[!MAY,exception]: peer is zero). + TEXT[!MAY,exception]: An endpoint MAY avoid creating a decoder stream if its decoder sets + TEXT[!MAY,exception]: the maximum capacity of the dynamic table to zero. TEXT[!MUST]: An endpoint MUST allow its peer to create an encoder stream and a TEXT[!MUST]: decoder stream even if the connection's settings prevent their use. diff --git a/h3/src/connection.rs b/h3/src/connection.rs index 2a1a3255..c9bd5e7b 100644 --- a/h3/src/connection.rs +++ b/h3/src/connection.rs @@ -197,6 +197,11 @@ where let mut decoder_send = Option::take(&mut self.qpack_streams.decoder_send); let mut encoder_send = Option::take(&mut self.qpack_streams.encoder_send); + //= https://www.rfc-editor.org/rfc/rfc9204.html#section-4.2 + //# Each endpoint + //# MUST initiate, at most, one encoder stream and, at most, one decoder + //# stream. + let (control, ..) = future::join3( stream::write( &mut self.control_send, @@ -455,6 +460,14 @@ where } enc @ AcceptedRecvStream::Encoder(_) => { if let Some(_prev) = self.qpack_streams.encoder_recv.replace(enc) { + //= https://www.rfc-editor.org/rfc/rfc9204.html#section-4.2 + //# Receipt of a second instance of either stream type MUST be + //# treated as a connection error of type H3_STREAM_CREATION_ERROR. + + //= https://www.rfc-editor.org/rfc/rfc9204.html#section-4.2 + //# An endpoint MUST allow its peer to create an encoder stream and a + //# decoder stream even if the connection's settings prevent their use. + return Err(self.handle_connection_error(InternalConnectionError::new( Code::H3_STREAM_CREATION_ERROR, "got two encoder streams".to_string(), @@ -463,6 +476,14 @@ where } dec @ AcceptedRecvStream::Decoder(_) => { if let Some(_prev) = self.qpack_streams.decoder_recv.replace(dec) { + //= https://www.rfc-editor.org/rfc/rfc9204.html#section-4.2 + //# Receipt of a second instance of either stream type MUST be + //# treated as a connection error of type H3_STREAM_CREATION_ERROR. + + //= https://www.rfc-editor.org/rfc/rfc9204.html#section-4.2 + //# An endpoint MUST allow its peer to create an encoder stream and a + //# decoder stream even if the connection's settings prevent their use. + return Err(self.handle_connection_error(InternalConnectionError::new( Code::H3_STREAM_CREATION_ERROR, "got two decoder streams".to_string(),