RFC 7541
HPACK: Header Compression for HTTP/2
HPACK: Header Compression for HTTP/2 ๐
Okay bestie, let's start with the receipts ๐งพ This is RFC 7541, and it's called HPACK: Header Compression for HTTP/2. It's a Standards Track document, published in May 2015. The authors are R. Peon from Google, Inc and H. Ruellan from Canon CRF, and its ISSN is 2070-1721. The header doesn't list any RFCs that it obsoletes or updates, so there's no drama there โจ
Abstract
HPACK is the star of the show ๐ It's a compression format that represents HTTP header fields efficiently, and it's built to be used in HTTP/2 ๐
Status of This Memo
This is an Internet Standards Track document ๐
It's a product of the Internet Engineering Task Force (IETF), so it's not just one person's vibe. It represents the consensus of the IETF community, got public review, and was approved for publication by the Internet Engineering Steering Group (IESG). If you want more info about Internet Standards, check out Section 2 of RFC 5741 ๐ฆ
For the current status of this document, any errata, and how to give feedback, go to http://www.rfc-editor.org/info/rfc7541 ๐ธ
Copyright Notice
Copyright (c) 2015 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, which you can find at http://trustee.ietf.org/license-info. These rules are the ones in effect on the date this document was published. Please read them carefully, because they explain your rights and restrictions when it comes to this document ๐ Code Components pulled out of this document must include the Simplified BSD License text, as described in Section 4.e of the Trust Legal Provisions. Those components are also provided without warranty, as described in the Simplified BSD License โญ
Table of Contents
1. Introduction ....................................................4
1.1. Overview ...................................................4
1.2. Conventions ................................................5
1.3. Terminology ................................................5
2. Compression Process Overview ....................................6
2.1. Header List Ordering .......................................6
2.2. Encoding and Decoding Contexts .............................6
2.3. Indexing Tables ............................................6
2.3.1. Static Table ........................................6
2.3.2. Dynamic Table .......................................6
2.3.3. Index Address Space .................................7
2.4. Header Field Representation ................................8
3. Header Block Decoding ...........................................8
3.1. Header Block Processing ....................................8
3.2. Header Field Representation Processing .....................9
4. Dynamic Table Management ........................................9
4.1. Calculating Table Size ....................................10
4.2. Maximum Table Size ........................................10
4.3. Entry Eviction When Dynamic Table Size Changes ............11
4.4. Entry Eviction When Adding New Entries ....................11
5. Primitive Type Representations .................................11
5.1. Integer Representation ....................................11
5.2. String Literal Representation .............................13
6. Binary Format ..................................................14
6.1. Indexed Header Field Representation .......................14
6.2. Literal Header Field Representation .......................15
6.2.1. Literal Header Field with Incremental Indexing .....15
6.2.2. Literal Header Field without Indexing ..............16
6.2.3. Literal Header Field Never Indexed .................17
6.3. Dynamic Table Size Update .................................18
7. Security Considerations ........................................19
7.1. Probing Dynamic Table State ...............................19
7.1.1. Applicability to HPACK and HTTP ....................20
7.1.2. Mitigation .........................................20
7.1.3. Never-Indexed Literals .............................21
7.2. Static Huffman Encoding ...................................22
7.3. Memory Consumption ........................................22
7.4. Implementation Limits .....................................23
8. References .....................................................23
8.1. Normative References ......................................23
8.2. Informative References ....................................24
Appendix A. Static Table Definition ...............................25
Appendix B. Huffman Code ..........................................27
Appendix C. Examples ..............................................33
C.1. Integer Representation Examples ............................33
C.1.1. Example 1: Encoding 10 Using a 5-Bit Prefix ............33
C.1.2. Example 2: Encoding 1337 Using a 5-Bit Prefix ..........33
C.1.3. Example 3: Encoding 42 Starting at an Octet Boundary ...34
C.2. Header Field Representation Examples .......................34
C.2.1. Literal Header Field with Indexing .....................34
C.2.2. Literal Header Field without Indexing ..................35
C.2.3. Literal Header Field Never Indexed .....................36
C.2.4. Indexed Header Field ...................................37
C.3. Request Examples without Huffman Coding ....................37
C.3.1. First Request ..........................................37
C.3.2. Second Request .........................................38
C.3.3. Third Request ..........................................39
C.4. Request Examples with Huffman Coding .......................41
C.4.1. First Request ..........................................41
C.4.2. Second Request .........................................42
C.4.3. Third Request ..........................................43
C.5. Response Examples without Huffman Coding ...................45
C.5.1. First Response .........................................45
C.5.2. Second Response ........................................46
C.5.3. Third Response .........................................47
C.6. Response Examples with Huffman Coding ......................49
C.6.1. First Response .........................................49
C.6.2. Second Response ........................................51
C.6.3. Third Response .........................................52
Acknowledgments ...................................................55
Authors' Addresses ................................................55
1. Introduction โจ
In HTTP/1.1 (see [RFC7230]), header fields are not compressed ๐ญ Web pages now need dozens to hundreds of requests, and all those repeated header fields eat up bandwidth for no reason, which measurably increases latency. Not it ๐
SPDY [SPDY] was the first to tackle this redundancy by compressing header fields with the DEFLATE [DEFLATE] format. It worked really well at representing those repeated header fields. The problem was that this approach opened up a security risk, which was shown by the CRIME (Compression Ratio Info-leak Made Easy) attack (see [CRIME]) ๐
So this specification introduces HPACK, a brand new compressor ๐ฆ It gets rid of redundant header fields, limits its exposure to known security attacks, and keeps a bounded memory requirement, so it works in constrained environments too. Potential security concerns for HPACK are covered in Section 7, so stay tuned ๐
HPACK is intentionally simple and inflexible, and that's the whole point ๐ Both traits lower the risk of interoperability or security problems caused by implementation errors. There are no extensibility mechanisms defined. If the format needs to change, the only option is to define a complete replacement ๐ฅ
1.1. Overview
HPACK treats a list of header fields as an ordered collection of name-value pairs, and that collection can include duplicate pairs ๐ Names and values are opaque sequences of octets, and the order of the header fields is kept intact after compression and decompression ๐ซถ
Encoding is guided by header field tables that map header fields to indexed values. These tables can be updated bit by bit as new header fields get encoded or decoded ๐
In encoded form, a header field is either written out literally or referenced as a header field in one of the header field tables. That means a list of header fields can be a mix of references and literal values ๐ง
Literal values are either encoded directly or encoded with a static Huffman code ๐
The encoder decides which header fields get inserted as new entries in the header field tables. The decoder carries out the table changes the encoder asks for, and it rebuilds the list of header fields along the way. This keeps decoders simple and lets them work with a wide variety of encoders โจ
Examples showing these different mechanisms in action are in Appendix C ๐
1.2. Conventions
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119 [RFC2119]. Yes, those are the official requirement words, so they're doing a lot of work here ๐
All numeric values are in network byte order. Values are unsigned unless otherwise indicated. Literal values are written in decimal or hexadecimal, whichever fits best ๐ข
1.3. Terminology
This specification uses the following terms ๐
- Header Field: A name-value pair. Both the name and the value are treated as opaque sequences of octets.
- Dynamic Table: The dynamic table (see Section 2.3.2) is a table that associates stored header fields with index values. It's dynamic and specific to an encoding or decoding context ๐ฆ
- Static Table: The static table (see Section 2.3.1) is a table that statically associates header fields that show up frequently with index values. It's ordered and read-only, it's always accessible, and it may be shared among all encoding or decoding contexts ๐
- Header List: A header list is an ordered collection of header fields that are encoded together, and it can contain duplicate header fields. A complete list of header fields in an HTTP/2 header block is a header list ๐ธ
- Header Field Representation: A header field can be represented in encoded form either as a literal or as an index (see Section 2.4) ๐
- Header Block: An ordered list of header field representations which, when decoded, yields a complete header list ๐
2. Compression Process Overview ๐
This specification doesn't describe one specific algorithm for an encoder. Instead, it spells out exactly how a decoder is expected to operate, so encoders can produce any encoding that this definition allows ๐ง
2.1. Header List Ordering
HPACK keeps the ordering of header fields inside the header list intact ๐ซถ An encoder MUST order header field representations in the header block according to their ordering in the original header list. A decoder MUST order header fields in the decoded header list according to their ordering in the header block. No shuffling allowed, periodt ๐ฅ
2.2. Encoding and Decoding Contexts
okay so bestie, to decompress a header block ๐ a decoder only needs ONE thing as its context: the dynamic table (see Section 2.3.2) โจ no other dynamic state needed, it's giving minimalism ๐ฆ
when it's bidirectional, like in HTTP ๐ธ, the encoding and decoding dynamic tables an endpoint keeps are totally separate ๐ so the request table and the response table don't share anything. completely independent, no cap ๐
2.3. Indexing Tables
HPACK uses two tables to map header fields to indexes ๐ first up is the static table (see Section 2.3.1), which is predefined and full of common header fields (most with an empty value) ๐. then there's the dynamic table (see Section 2.3.2), which is the flexible one ๐ the encoder can use it to index header fields that keep repeating in the encoded header lists ๐ซถ
and then both tables get combined into ONE single address space for index values (see Section 2.3.3) โจ very iconic ๐
2.3.1. Static Table
The static table is a predefined static list of header fields ๐ all the entries live in Appendix A ๐ง
2.3.2. Dynamic Table
okay the dynamic table is a list of header fields that runs first-in, first-out ๐ฅ the first and newest entry sits at the lowest index, and the oldest entry sits at the highest index ๐ฆ
it starts out completely empty ๐ซง entries get added as each header block is decompressed ๐
the dynamic table can have duplicate entries (aka same name AND same value) ๐ so a decoder MUST NOT treat duplicates as an error ๐ not me being like "wait that's an error" ๐ nope!!
the encoder is the one deciding how the dynamic table gets updated, so it controls how much memory the table eats โจ to keep the decoder's memory in check, the dynamic table size is strictly bounded (see Section 4.2) ๐ธ
and the decoder updates the dynamic table while it's processing a list of header field representations (see Section 3.2) ๐
2.3.3. Index Address Space
the static table and the dynamic table get merged into one index address space ๐
indices from 1 up to the length of the static table (inclusive) point at static table elements (see Section 2.3.1) ๐
indices strictly greater than the static table length point at dynamic table elements (see Section 2.3.2) ๐ and you subtract the static table length to find the dynamic table index ๐งฎโจ
indices strictly greater than the sum of both table lengths MUST be treated as a decoding error ๐จ no exceptions bestie ๐
here's the picture of the whole valid index address space, for a static table size of s and a dynamic table size of k ๐ฆ
<---------- Index Address Space ---------->
<-- Static Table --> <-- Dynamic Table -->
+---+-----------+---+ +---+-----------+---+
| 1 | ... | s | |s+1| ... |s+k|
+---+-----------+---+ +---+-----------+---+
^ |
| V
Insertion Point Dropping Point
Figure 1: Index Address Space
2.4. Header Field Representation
okay so an encoded header field can show up as either an index or a literal ๐
an indexed representation is basically a reference to an entry in either the static table or the dynamic table (see Section 6.1) ๐
a literal representation spells out the header field by giving its name and value ๐ธ the name can be literal OR a reference to an entry in either table, but the value is always literal ๐ซถ
there are three flavors of literal representation ๐
- one adds the header field as a brand new entry at the beginning of the dynamic table (see Section 6.2.1) โจ
- one does NOT add the header field to the dynamic table (see Section 6.2.2) ๐ฆ
- one also does NOT add it to the dynamic table, AND this header field always uses a literal representation, especially when an intermediary re-encodes it (see Section 6.2.3) ๐ this one is for protecting values that you don't want to put at risk by compressing them (see Section 7.1.3 for more details) ๐ซถ
picking one of these literal flavors can be guided by security considerations, so you can protect sensitive header field values (see Section 7.1) ๐ ๐
and the literal representation of a header field name or value can encode the octets directly OR using a static Huffman code (see Section 5.2) ๐ง
- Header Block Decoding โจ
3.1. Header Block Processing
a decoder processes a header block sequentially to rebuild the original header list ๐
a header block is just the concatenation of header field representations ๐ they're all described in Section 6 ๐
once a header field is decoded and added to the reconstructed header list, it CANNOT be removed ๐ซ and a header field on that list can be safely handed to the application ๐ซถ
by passing the resulting header fields to the application, a decoder can run with minimal transitory memory on top of the memory the dynamic table already needs ๐ lowkey genius ๐
3.2. Header Field Representation Processing
this section defines how a header block gets processed into a header list ๐ฆ to make sure decoding successfully produces a header list, a decoder MUST obey the following rules ๐
all the header field representations in a header block get processed in the order they appear, as specified below ๐ธ the formatting details and extra processing instructions are in Section 6 ๐
an _indexed representation_ does this: ๐
- the header field at the referenced entry in either the static table or dynamic table gets appended to the decoded header list โจ
a _literal representation_ that is _not added_ to the dynamic table does this: ๐
- the header field gets appended to the decoded header list ๐ซถ
a _literal representation_ that is _added_ to the dynamic table does these things: ๐ฅ
- the header field gets appended to the decoded header list ๐
- the header field gets inserted at the beginning of the dynamic table ๐ฆ this insertion could evict previous entries from the dynamic table (see Section 4.4) ๐งน
- Dynamic Table Management ๐
to limit the memory requirements on the decoder side, the dynamic table is constrained in size ๐
4.1. Calculating Table Size
the size of the dynamic table is the sum of the sizes of its entries ๐งฎ
the size of an entry is the sum of its name's length in octets (as defined in Section 5.2), its value's length in octets, and 32 โจ
the size of an entry is calculated using the length of its name and value WITHOUT any Huffman encoding applied ๐ โโ๏ธ
Note: the additional 32 octets account for an estimated overhead per entry ๐ for example, an entry structure that uses two 64-bit pointers to reference the name and value, plus two 64-bit integers counting the references to the name and value, would have 32 octets of overhead ๐ค
4.2. Maximum Table Size
protocols that use HPACK decide the maximum size the encoder is allowed to use for the dynamic table ๐ธ in HTTP/2, that value comes from the SETTINGS_HEADER_TABLE_SIZE setting (see Section 6.5.2 of [HTTP2]) ๐ฆ
an encoder can choose to use LESS capacity than that maximum (see Section 6.3) ๐ซถ but the chosen size MUST stay lower than or equal to the maximum the protocol sets ๐ง
a change in the maximum size of the dynamic table gets signaled through a dynamic table size update (see Section 6.3) โจ this dynamic table size update MUST occur at the beginning of the first header block after the change ๐ in HTTP/2, that follows a settings acknowledgment (see Section 6.5.3 of [HTTP2]) ๐
multiple updates to the maximum table size can happen between two header blocks ๐ if the size changes more than once in that gap, the SMALLEST maximum table size from that interval MUST be signaled in a dynamic table size update ๐ฅ the final maximum size always gets signaled too, so there are at most two dynamic table size updates ๐งฎ this makes sure the decoder can evict based on reductions in dynamic table size (see Section 4.3) ๐
this mechanism can completely clear entries from the dynamic table by setting a maximum size of 0 ๐งน and that can be restored later ๐
4.3. Entry Eviction When Dynamic Table Size Changes
whenever the maximum size for the dynamic table gets reduced, entries are evicted from the end of the dynamic table until the table size is less than or equal to the maximum size ๐๏ธโจ
4.4. Entry Eviction When Adding New Entries
before a new entry gets added to the dynamic table, entries are evicted from the end until the table size is less than or equal to (maximum size - new entry size) OR the table is empty ๐งน
if the new entry is less than or equal to the maximum size, it gets added to the table ๐ it is NOT an error to try adding an entry larger than the maximum size ๐ but that attempt causes the table to be emptied of all existing entries, leaving it empty ๐ซง
a new entry can reference the name of an entry in the dynamic table that gets evicted when this new entry goes in ๐คฏ implementations are cautioned to avoid deleting the referenced name if the referenced entry is evicted before the new entry is inserted โ ๏ธ be careful out there bestie ๐ซถ
- Primitive Type Representations ๐
HPACK encoding uses two primitive types: unsigned variable-length integers and strings of octets ๐งโจ
5.1. Integer Representation ๐ขโจ
Okay bestie, integers show up whenever we need to say a name index, a header field index, or a string length. An integer representation can start anywhere inside an octet, but it always finishes at the end of an octet so processing stays nice and optimized ๐
An integer is split into two parts: a prefix that fills the current octet, and an optional list of octets for when the value doesn't fit in the prefix. The number of bits in the prefix (called N) is a parameter of the integer representation ๐ธ
If the value is small enough, meaning strictly less than 2^N-1, it just gets encoded inside the N-bit prefix:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| ? | ? | ? | Value |
+---+---+---+-------------------+
Figure 2: Integer Value Encoded within the Prefix (Shown for N = 5)
Otherwise, all the bits of the prefix get set to 1, and the value (decreased by 2^N-1) is encoded using a list of one or more octets. The most significant bit of each octet is a continuation flag: it's set to 1 except on the last octet in the list. The remaining bits carry the decreased value ๐ฆ
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| ? | ? | ? | 1 1 1 1 1 |
+---+---+---+-------------------+
| 1 | Value-(2^N-1) LSB |
+---+---------------------------+
...
+---+---------------------------+
| 0 | Value-(2^N-1) MSB |
+---+---------------------------+
Figure 3: Integer Value Encoded after the Prefix (Shown for N = 5)
To decode, you reverse the order of the octets in the list first. Then you strip the most significant bit from each octet, concatenate the leftover bits, and add 2^N-1 to get the integer value ๐ง
The prefix size N is always between 1 and 8 bits. An integer that starts on an octet boundary will have an 8-bit prefix.
Here's the pseudocode for encoding an integer I:
if I < 2^N - 1, encode I on N bits
else
encode (2^N - 1) on N bits
I = I - (2^N - 1)
while I >= 128
encode (I % 128 + 128) on 8 bits
I = I / 128
encode I on 8 bits
And here's the pseudocode for decoding an integer I:
decode I from the next N bits
if I < 2^N - 1, return I
else
M = 0
repeat
B = next octet
I = I + (B & 127) * 2^M
M = M + 7
while B & 128 == 128
return I
Examples of integer encodings are in Appendix C.1 ๐
This integer representation allows for values of indefinite size. An encoder can also send a bunch of zero values, which wastes octets and could be used to overflow integer values, so ngl that's a sneaky one ๐ Integer encodings that go past implementation limits (in value or in octet length) MUST be treated as decoding errors. Different limits can be set for each use of integers, depending on implementation constraints โญ
5.2. String Literal Representation ๐งต
Header field names and header field values can be represented as string literals. A string literal is encoded as a sequence of octets, either by directly encoding the string's octets or by using a Huffman code (see [HUFFMAN]) ๐
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| H | String Length (7+) |
+---+---------------------------+
| String Data (Length octets) |
+-------------------------------+
Figure 4: String Literal Representation
A string literal representation has these fields:
- H: A one-bit flag indicating whether or not the octets of the string are Huffman encoded.
- String Length: The number of octets used to encode the string literal, encoded as an integer with a 7-bit prefix (see Section 5.1).
- String Data: The encoded data of the string literal. If H is '0', the encoded data is the raw octets of the string literal. If H is '1', the encoded data is the Huffman encoding of the string literal.
String literals that use Huffman encoding are encoded with the Huffman code defined in Appendix B (see examples for requests in Appendix C.4 and for responses in Appendix C.6). The encoded data is the bitwise concatenation of the codes for each octet of the string literal ๐
Huffman-encoded data doesn't always end on an octet boundary, so some padding gets added after it, up to the next octet boundary. To keep that padding from being mistaken for part of the string, the most significant bits of the code for the EOS (end-of-string) symbol are used ๐ซถ
When decoding, an incomplete code at the end of the encoded data is treated as padding and discarded. A padding strictly longer than 7 bits MUST be treated as a decoding error. A padding that doesn't correspond to the most significant bits of the code for the EOS symbol MUST be treated as a decoding error. A Huffman-encoded string literal that contains the EOS symbol MUST be treated as a decoding error ๐
6. Binary Format ๐ฆ
This section describes the detailed format of each of the different header field representations, plus the dynamic table size update instruction ๐ธ
6.1. Indexed Header Field Representation ๐ฏ
An indexed header field representation identifies an entry in either the static table or the dynamic table (see Section 2.3) ๐
An indexed header field representation causes a header field to be added to the decoded header list, as described in Section 3.2.
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 1 | Index (7+) |
+---+---------------------------+
Figure 5: Indexed Header Field
An indexed header field starts with the '1' 1-bit pattern, followed by the index of the matching header field, represented as an integer with a 7-bit prefix (see Section 5.1).
The index value of 0 is not used. It MUST be treated as a decoding error if found in an indexed header field representation ๐ โโ๏ธ
6.2. Literal Header Field Representation ๐
A literal header field representation contains a literal header field value. Header field names are provided either as a literal or by reference to an existing table entry, either from the static table or the dynamic table (see Section 2.3) ๐งธ
This specification defines three forms of literal header field representations: with indexing, without indexing, and never indexed.
6.2.1. Literal Header Field with Incremental Indexing โจ
A literal header field with incremental indexing representation results in appending a header field to the decoded header list and inserting it as a new entry into the dynamic table ๐ฆ
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | Index (6+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
Figure 6: Literal Header Field with Incremental Indexing -- Indexed Name
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | 0 |
+---+---+-----------------------+
| H | Name Length (7+) |
+---+---------------------------+
| Name String (Length octets) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
Figure 7: Literal Header Field with Incremental Indexing -- New Name
A literal header field with incremental indexing representation starts with the '01' 2-bit pattern.
If the header field name matches the name of an entry in the static table or the dynamic table, the name can be represented using the index of that entry. In this case, the index is represented as an integer with a 6-bit prefix (see Section 5.1). This value is always non-zero ๐
Otherwise, the header field name is represented as a string literal (see Section 5.2). A value 0 is used in place of the 6-bit index, followed by the header field name ๐
Either form of header field name representation is followed by the header field value, represented as a string literal (see Section 5.2) ๐
6.2.2. Literal Header Field without Indexing ๐
A literal header field without indexing representation results in appending a header field to the decoded header list without altering the dynamic table ๐
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | Index (4+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
Figure 8: Literal Header Field without Indexing -- Indexed Name
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | 0 |
+---+---+-----------------------+
| H | Name Length (7+) |
+---+---------------------------+
| Name String (Length octets) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
Figure 9: Literal Header Field without Indexing -- New Name
A literal header field without indexing representation starts with the '0000' 4-bit pattern.
If the header field name matches the name of an entry in the static table or the dynamic table, the name can be represented using the index of that entry. In this case, the index is represented as an integer with a 4-bit prefix (see Section 5.1). This value is always non-zero ๐ง
Otherwise, the header field name is represented as a string literal (see Section 5.2). A value 0 is used in place of the 4-bit index, followed by the header field name ๐ซถ
Either form of header field name representation is followed by the header field value, represented as a string literal (see Section 5.2) ๐
6.2.3. Literal Header Field Never Indexed ๐
okay so this one is the literal header field never-indexed representation, and it basically tacks a header field onto the end of the decoded header list ๐ without touching the dynamic table at all ๐. Intermediaries MUST use the same representation for encoding this header field ๐ .
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | Index (4+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
Figure 10: Literal Header Field Never Indexed -- Indexed Name
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | 0 |
+---+---+-----------------------+
| H | Name Length (7+) |
+---+---------------------------+
| Name String (Length octets) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length octets) |
+-------------------------------+
Figure 11: Literal Header Field Never Indexed -- New Name
okay so the vibe is: this representation always starts with the 0001 4-bit pattern ๐.
When a header field is represented as a literal header field never indexed, it MUST always be encoded with this specific literal representation ๐ซถ. Even more important, when a peer receives a header field that was represented as a literal header field never indexed, it MUST use the same representation to forward that header field ๐. no switching it up mid-journey, bestie ๐ .
This representation exists to protect header field values that would be put at risk by compressing them ๐ก๏ธ (see Section 7.1 for more details). And honestly? The encoding is identical to the literal header field without indexing, so if you already get that one, you're basically set โจ (see Section 6.2.2).
6.3. Dynamic Table Size Update ๐
a dynamic table size update is how you signal a change to the size of the dynamic table ๐ธ.
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | Max size (5+) |
+---+---------------------------+
Figure 12: Maximum Dynamic Table Size Change
It starts with the 001 3-bit pattern, and then comes the new maximum size, written as an integer with a 5-bit prefix (see Section 5.1) ๐ง.
The new maximum size MUST be lower than or equal to the limit determined by the protocol using HPACK โ ๏ธ. A value that goes over that limit MUST be treated as a decoding error ๐. In HTTP/2, that limit is the last value of the SETTINGS_HEADER_TABLE_SIZE parameter (see Section 6.5.2 of [HTTP2]) that the decoder sent and the encoder acknowledged (see Section 6.5.3 of [HTTP2]) ๐ฆ.
Shrinking the maximum size of the dynamic table can cause entries to get evicted ๐๏ธ (see Section 4.3). Bye bye, entries ๐.
7. Security Considerations ๐
this section is where we talk about the potential security concerns with HPACK, and it's serious business, no cap ๐ญ. There are two big ones:
- Using compression as a length-based oracle to check guesses about secrets that get compressed into a shared compression context ๐.
- Denial of service caused by exhausting the processing or memory capacity at a decoder ๐จ.
7.1. Probing Dynamic Table State ๐
HPACK shrinks header field encodings by exploiting the redundancy that's baked into protocols like HTTP ๐. The whole goal is to reduce how much data it takes to send HTTP requests or responses โจ.
The compression context used to encode header fields can be probed by an attacker who can both define header fields to be encoded and transmitted, and observe the length of those fields once they're encoded ๐ฌ. When an attacker can do both, they can tweak their requests adaptively to confirm guesses about the dynamic table state ๐ต๏ธ. If a guess compresses into a shorter length, the attacker can see the encoded length and figure out the guess was correct ๐ .
This is possible even over the Transport Layer Security (TLS) protocol (see [TLS12]), because TLS keeps content confidential but only gives a limited amount of protection for the length of that content ๐ซ .
Note: Padding schemes only give limited protection against an attacker with these capabilities. At best they only force the attacker to make more guesses before learning the length tied to a given guess ๐. Padding schemes also work directly against compression, since they increase the number of bits that get transmitted ๐.
Attacks like CRIME [CRIME] showed that these general attacker capabilities really exist ๐ฑ. That specific attack took advantage of the fact that DEFLATE [DEFLATE] removes redundancy based on prefix matching. That let the attacker confirm guesses one character at a time, which turned an exponential-time attack into a linear-time attack ๐ฅ.
7.1.1. Applicability to HPACK and HTTP ๐ง
HPACK mitigates attacks modeled on CRIME [CRIME] but doesn't completely prevent them ๐ก๏ธ. It does this by forcing a guess to match an entire header field value, rather than individual characters. Attackers can only learn whether a guess is correct or not, so they're reduced to brute-force guessing for the header field values ๐ฒ.
So how viable is recovering a specific header field value? It depends on the entropy of the values ๐งฎ. Values with high entropy are unlikely to be recovered successfully ๐. But values with low entropy? Still vulnerable ๐ฌ.
These attacks are possible any time two mutually distrustful entities control requests or responses that get placed onto a single HTTP/2 connection ๐ซ. If the shared HPACK compressor lets one entity add entries to the dynamic table and lets the other access those entries, then the state of the table can be learned ๐ง .
Having requests or responses from mutually distrustful entities happens 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 [ORIGIN] are made by mutually distrustful entities ๐.
7.1.2. Mitigation ๐ฉน
Users of HTTP who need confidentiality for header fields can use values with enough entropy to make guessing infeasible ๐. But honestly, this is impractical as a general solution, because it forces all users of HTTP to take steps to mitigate attacks ๐ฉ. It would also put new constraints on how HTTP gets used ๐ง.
So instead of putting constraints on HTTP users, an implementation of HPACK can constrain how compression is applied, to limit the potential for dynamic table probing ๐.
The ideal solution segregates access to the dynamic table based on the entity that's constructing the header fields ๐. Header field values 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 with this option, certain entries might be tagged as 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 where header fields came from might instead introduce a penalty for a header field with lots of different values ๐ฅฒ. That way, a big pile of guess attempts on a header field means it no longer gets compared to the dynamic table entries in future messages, which effectively stops further guessing ๐.
Note: Simply removing the entries for a header field from the dynamic table can be ineffectual if the attacker has a reliable way of making values get reinstalled ๐ค. For example, a request to load an image in a web browser typically includes the Cookie header field (a very juicy target for this kind of attack ๐ช), and websites can easily force an image to load, which refreshes the entry in the dynamic table ๐.
This response might be made inversely proportional to the length of the header field value ๐. Marking a header field as no longer using the dynamic table might happen more quickly, or with higher probability, for shorter values than for longer ones โฑ๏ธ.
7.1.3. Never-Indexed Literals ๐
implementations can also choose to protect sensitive header fields by not compressing them, and instead encoding their values as literals ๐.
Refusing to generate an indexed representation for a header field only works if compression is avoided on all hops ๐ฆถ. The never-indexed literal (see Section 6.2.3) 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 the never-indexed literal representation with another representation that would index it ๐ซ. If HPACK is used for re-encoding, the never-indexed literal representation MUST be used ๐ฏ.
Choosing a never-indexed literal representation for a header field depends on a few factors ๐งฉ. Since HPACK doesn't protect against guessing an entire header field value, short or low-entropy values are way easier for an adversary to recover ๐โโ๏ธ. So an encoder might choose not to index low-entropy values ๐ โโ๏ธ.
An encoder might also choose not to index values for header fields that are highly valuable or sensitive to recovery, like the Cookie or Authorization header fields ๐ช๐.
On the flip side, an encoder might prefer indexing values for header fields that wouldn't be a big deal if they got exposed ๐. For instance, the User-Agent header field doesn't usually vary between requests and gets sent to any server. In that case, confirming that a particular User-Agent value was used isn't worth much to an attacker ๐คทโโ๏ธ.
Keep in mind that these criteria for deciding on a never-indexed literal representation will keep evolving as new attacks get discovered ๐.
7.2. Static Huffman Encoding ๐ง
okay, good news: there is no currently known attack against a static Huffman encoding ๐ฅณ. A study did show that using a static Huffman encoding table created an information leakage ๐ณ. But that same study also concluded that an attacker couldn't use this leakage to recover any meaningful amount of information (see [PETAL]) ๐ โจ.
7.3. Memory Consumption โจ
Okay so, an attacker can totally try to make an endpoint run out of memory ๐ญ. HPACK is built to limit both the peak and the state amounts of memory an endpoint allocates ๐ก๏ธ.
The protocol using HPACK limits how much memory the compressor can use, by defining the maximum size of the dynamic table ๐. In HTTP/2, the decoder controls this value through the setting parameter SETTINGS_HEADER_TABLE_SIZE (see Section 6.5.2 of [HTTP2]). The limit counts both the size of the data stored in the dynamic table and a small allowance for overhead.
A decoder can cap its state memory by picking a sensible value for the maximum size of the dynamic table. In HTTP/2, you do that by setting the SETTINGS_HEADER_TABLE_SIZE parameter ๐
. An encoder can limit its own state memory by signaling a smaller dynamic table size than the decoder allows (see Section 6.3) ๐ฆ.
The temporary memory used by an encoder or decoder can be kept small by processing header fields one at a time, in order ๐ง. An implementation doesn't have to keep a complete list of header fields. Heads up though: an application might still need to keep a full header list for other reasons. HPACK doesn't force that, but application constraints might make it necessary ๐.
7.4. Implementation Limits ๐ธ
An implementation of HPACK needs to make sure that huge integer values, long integer encodings, or long string literals don't create security weaknesses ๐.
An implementation has to set a limit on the values it accepts for integers, as well as on the encoded length (see Section 5.1). Same energy for string literals: it has to set a limit on the length it accepts (see Section 5.2) ๐ฅ.
8. References ๐
8.1. Normative References
- [HTTP2] Belshe, M., Peon, R., and M. Thomson, Ed., "Hypertext Transfer Protocol Version 2 (HTTP/2)", RFC 7540, DOI 10.17487/RFC7540, May 2015,
http://www.rfc-editor.org/info/rfc7540. - [RFC2119] Bradner, S., "Key words for use in RFCs to Indicate Requirement Levels", BCP 14, RFC 2119, DOI 10.17487/RFC2119, March 1997,
http://www.rfc-editor.org/info/rfc2119. - [RFC7230] Fielding, R., Ed. and J. Reschke, Ed., "Hypertext Transfer Protocol (HTTP/1.1): Message Syntax and Routing", RFC 7230, DOI 10.17487/RFC7230, June 2014,
http://www.rfc-editor.org/info/rfc7230.
8.2. Informative References
- [CANONICAL] Schwartz, E. and B. Kallick, "Generating a canonical prefix encoding", Communications of the ACM, Volume 7 Issue 3, pp. 166-169, March 1964,
https://dl.acm.org/citation.cfm?id=363991. - [CRIME] Wikipedia, "CRIME", May 2015,
http://en.wikipedia.org/w/index.php?title=CRIME&oldid=660948120. - [DEFLATE] Deutsch, P., "DEFLATE Compressed Data Format Specification version 1.3", RFC 1951, DOI 10.17487/RFC1951, May 1996,
http://www.rfc-editor.org/info/rfc1951. - [HUFFMAN] Huffman, D., "A Method for the Construction of Minimum-Redundancy Codes", Proceedings of the Institute of Radio Engineers, Volume 40, Number 9, pp. 1098-1101, September 1952,
http://ieeexplore.ieee.org/xpl/articleDetails.jsp?arnumber=4051119. - [ORIGIN] Barth, A., "The Web Origin Concept", RFC 6454, DOI 10.17487/RFC6454, December 2011,
http://www.rfc-editor.org/info/rfc6454. - [PETAL] Tan, J. and J. Nahata, "PETAL: Preset Encoding Table Information Leakage", April 2013,
http://www.pdl.cmu.edu/PDL-FTP/associated/CMU-PDL-13-106.pdf. - [SPDY] Belshe, M. and R. Peon, "SPDY Protocol", Work in Progress, draft-mbelshe-httpbis-spdy-00, February 2012.
- [TLS12] Dierks, T. and E. Rescorla, "The Transport Layer Security (TLS) Protocol Version 1.2", RFC 5246, DOI 10.17487/RFC5246, August 2008,
http://www.rfc-editor.org/info/rfc5246.
Appendix A. Static Table Definition ๐
The static table (see Section 2.3.1) is a predefined, unchangeable list of header fields ๐.
The static table was made from the most frequent header fields used by popular websites, plus the HTTP/2-specific pseudo-header fields (see Section 8.1.2.1 of [HTTP2]) โจ. For header fields with a few frequent values, an entry was added for each of those values. For other header fields, an entry was added with an empty value.
Table 1 lists the predefined header fields that make up the static table, and gives the index of each entry ๐.
+-------+-----------------------------+---------------+
| Index | Header Name | Header Value |
+-------+-----------------------------+---------------+
| 1 | :authority | |
| 2 | :method | GET |
| 3 | :method | POST |
| 4 | :path | / |
| 5 | :path | /index.html |
| 6 | :scheme | http |
| 7 | :scheme | https |
| 8 | :status | 200 |
| 9 | :status | 204 |
| 10 | :status | 206 |
| 11 | :status | 304 |
| 12 | :status | 400 |
| 13 | :status | 404 |
| 14 | :status | 500 |
| 15 | accept-charset | |
| 16 | accept-encoding | gzip, deflate |
| 17 | accept-language | |
| 18 | accept-ranges | |
| 19 | accept | |
| 20 | access-control-allow-origin | |
| 21 | age | |
| 22 | allow | |
| 23 | authorization | |
| 24 | cache-control | |
| 25 | content-disposition | |
| 26 | content-encoding | |
| 27 | content-language | |
| 28 | content-length | |
| 29 | content-location | |
| 30 | content-range | |
| 31 | content-type | |
| 32 | cookie | |
| 33 | date | |
| 34 | etag | |
| 35 | expect | |
| 36 | expires | |
| 37 | from | |
| 38 | host | |
| 39 | if-match | |
| 40 | if-modified-since | |
| 41 | if-none-match | |
| 42 | if-range | |
| 43 | if-unmodified-since | |
| 44 | last-modified | |
| 45 | link | |
| 46 | location | |
| 47 | max-forwards | |
| 48 | proxy-authenticate | |
| 49 | proxy-authorization | |
| 50 | range | |
| 51 | referer | |
| 52 | refresh | |
| 53 | retry-after | |
| 54 | server | |
| 55 | set-cookie | |
| 56 | strict-transport-security | |
| 57 | transfer-encoding | |
| 58 | user-agent | |
| 59 | vary | |
| 60 | via | |
| 61 | www-authenticate | |
+-------+-----------------------------+---------------+
Table 1: Static Table Entries
Appendix B. Huffman Code ๐ฆ
The following Huffman code is used when encoding string literals with a Huffman coding (see Section 5.2) ๐.
This Huffman code was generated from statistics gathered from a large sample of HTTP headers. It's a canonical Huffman code (see [CANONICAL]) with some tweaking so that no symbol has a unique code length ๐ง .
Each row in the table defines the code used to represent a symbol:
- sym: The symbol to be represented. It is the decimal value of an octet, possibly prepended with its ASCII representation. A specific symbol, "EOS", is used to indicate the end of a string literal.
- code as bits: The Huffman code for the symbol represented as a base-2 integer, aligned on the most significant bit (MSB).
- code as hex: The Huffman code for the symbol, represented as a hexadecimal integer, aligned on the least significant bit (LSB).
- len: The number of bits for the code representing the symbol.
As an example, the code for the symbol 47 (corresponding to the ASCII character "/") consists of the 6 bits "0", "1", "1", "0", "0", "0". This corresponds to the value 0x18 (in hexadecimal) encoded in 6 bits ๐.
code code as bits as hex len sym aligned to MSB aligned in to LSB bits ( 0) |11111111|11000 1ff8 [13] ( 1) |11111111|11111111|1011000 7fffd8 [23] ( 2) |11111111|11111111|11111110|0010 fffffe2 [28] ( 3) |11111111|11111111|11111110|0011 fffffe3 [28] ( 4) |11111111|11111111|11111110|0100 fffffe4 [28] ( 5) |11111111|11111111|11111110|0101 fffffe5 [28] ( 6) |11111111|11111111|11111110|0110 fffffe6 [28] ( 7) |11111111|11111111|11111110|0111 fffffe7 [28] ( 8) |11111111|11111111|11111110|1000 fffffe8 [28] ( 9) |11111111|11111111|11101010 ffffea [24] ( 10) |11111111|11111111|11111111|111100 3ffffffc [30] ( 11) |11111111|11111111|11111110|1001 fffffe9 [28] ( 12) |11111111|11111111|11111110|1010 fffffea [28] ( 13) |11111111|11111111|11111111|111101 3ffffffd [30] ( 14) |11111111|11111111|11111110|1011 fffffeb [28] ( 15) |11111111|11111111|11111110|1100 fffffec [28] ( 16) |11111111|11111111|11111110|1101 fffffed [28] ( 17) |11111111|11111111|11111110|1110 fffffee [28] ( 18) |11111111|11111111|11111110|1111 fffffef [28] ( 19) |11111111|11111111|11111111|0000 ffffff0 [28] ( 20) |11111111|11111111|11111111|0001 ffffff1 [28] ( 21) |11111111|11111111|11111111|0010 ffffff2 [28] ( 22) |11111111|11111111|11111111|111110 3ffffffe [30] ( 23) |11111111|11111111|11111111|0011 ffffff3 [28] ( 24) |11111111|11111111|11111111|0100 ffffff4 [28] ( 25) |11111111|11111111|11111111|0101 ffffff5 [28] ( 26) |11111111|11111111|11111111|0110 ffffff6 [28] ( 27) |11111111|11111111|11111111|0111 ffffff7 [28] ( 28) |11111111|11111111|11111111|1000 ffffff8 [28] ( 29) |11111111|11111111|11111111|1001 ffffff9 [28] ( 30) |11111111|11111111|11111111|1010 ffffffa [28] ( 31) |11111111|11111111|11111111|1011 ffffffb [28] ' ' ( 32) |010100 14 [ 6] '!' ( 33) |11111110|00 3f8 [10] '"' ( 34) |11111110|01 3f9 [10] '#' ( 35) |11111111|1010 ffa [12] '$' ( 36) |11111111|11001 1ff9 [13] '%' ( 37) |010101 15 [ 6] '&' ( 38) |11111000 f8 [ 8] ''' ( 39) |11111111|010 7fa [11] '(' ( 40) |11111110|10 3fa [10] ')' ( 41) |11111110|11 3fb [10] '*' ( 42) |11111001 f9 [ 8] '+' ( 43) |11111111|011 7fb [11] ',' ( 44) |11111010 fa [ 8] '-' ( 45) |010110 16 [ 6] '.' ( 46) |010111 17 [ 6] '/' ( 47) |011000 18 [ 6] '0' ( 48) |00000 0 [ 5] '1' ( 49) |00001 1 [ 5] '2' ( 50) |00010 2 [ 5] '3' ( 51) |011001 19 [ 6] '4' ( 52) |011010 1a [ 6] '5' ( 53) |011011 1b [ 6] '6' ( 54) |011100 1c [ 6] '7' ( 55) |011101 1d [ 6] '8' ( 56) |011110 1e [ 6] '9' ( 57) |011111 1f [ 6] ':' ( 58) |1011100 5c [ 7] ';' ( 59) |11111011 fb [ 8] '<' ( 60) |11111111|1111100 7ffc [15] '=' ( 61) |100000 20 [ 6] '>' ( 62) |11111111|1011 ffb [12] '?' ( 63) |11111111|00 3fc [10] '@' ( 64) |11111111|11010 1ffa [13] 'A' ( 65) |100001 21 [ 6] 'B' ( 66) |1011101 5d [ 7] 'C' ( 67) |1011110 5e [ 7] 'D' ( 68) |1011111 5f [ 7] 'E' ( 69) |1100000 60 [ 7] 'F' ( 70) |1100001 61 [ 7] 'G' ( 71) |1100010 62 [ 7] 'H' ( 72) |1100011 63 [ 7] 'I' ( 73) |1100100 64 [ 7] 'J' ( 74) |1100101 65 [ 7] 'K' ( 75) |1100110 66 [ 7] 'L' ( 76) |1100111 67 [ 7] 'M' ( 77) |1101000 68 [ 7] 'N' ( 78) |1101001 69 [ 7] 'O' ( 79) |1101010 6a [ 7] 'P' ( 80) |1101011 6b [ 7] 'Q' ( 81) |1101100 6c [ 7] 'R' ( 82) |1101101 6d [ 7] 'S' ( 83) |1101110 6e [ 7] 'T' ( 84) |1101111 6f [ 7] 'U' ( 85) |1110000 70 [ 7] 'V' ( 86) |1110001 71 [ 7] 'W' ( 87) |1110010 72 [ 7] 'X' ( 88) |11111100 fc [ 8] 'Y' ( 89) |1110011 73 [ 7] 'Z' ( 90) |11111101 fd [ 8] '[' ( 91) |11111111|11011 1ffb [13] '' ( 92) |11111111|11111110|000 7fff0 [19] ']' ( 93) |11111111|11100 1ffc [13] '^' ( 94) |11111111|111100 3ffc [14] '_' ( 95) |100010 22 [ 6] '`' ( 96) |11111111|1111101 7ffd [15] 'a' ( 97) |00011 3 [ 5] 'b' ( 98) |100011 23 [ 6] 'c' ( 99) |00100 4 [ 5] 'd' (100) |100100 24 [ 6] 'e' (101) |00101 5 [ 5] 'f' (102) |100101 25 [ 6] 'g' (103) |100110 26 [ 6] 'h' (104) |100111 27 [ 6] 'i' (105) |00110 6 [ 5] 'j' (106) |1110100 74 [ 7] 'k' (107) |1110101 75 [ 7] 'l' (108) |101000 28 [ 6] 'm' (109) |101001 29 [ 6] 'n' (110) |101010 2a [ 6] 'o' (111) |00111 7 [ 5] 'p' (112) |101011 2b [ 6] 'q' (113) |1110110 76 [ 7] 'r' (114) |101100 2c [ 6] 's' (115) |01000 8 [ 5] 't' (116) |01001 9 [ 5] 'u' (117) |101101 2d [ 6] 'v' (118) |1110111 77 [ 7] 'w' (119) |1111000 78 [ 7] 'x' (120) |1111001 79 [ 7] 'y' (121) |1111010 7a [ 7] 'z' (122) |1111011 7b [ 7] '{' (123) |11111111|1111110 7ffe [15] '|' (124) |11111111|100 7fc [11] '}' (125) |11111111|111101 3ffd [14] '~' (126) |11111111|11101 1ffd [13] (127) |11111111|11111111|11111111|1100 ffffffc [28] (128) |11111111|11111110|0110 fffe6 [20] (129) |11111111|11111111|010010 3fffd2 [22] (130) |11111111|11111110|0111 fffe7 [20] (131) |11111111|11111110|1000 fffe8 [20] (132) |11111111|11111111|010011 3fffd3 [22] (133) |11111111|11111111|010100 3fffd4 [22] (134) |11111111|11111111|010101 3fffd5 [22] (135) |11111111|11111111|1011001 7fffd9 [23] (136) |11111111|11111111|010110 3fffd6 [22] (137) |11111111|11111111|1011010 7fffda [23] (138) |11111111|11111111|1011011 7fffdb [23] (139) |11111111|11111111|1011100 7fffdc [23] (140) |11111111|11111111|1011101 7fffdd [23] (141) |11111111|11111111|1011110 7fffde [23] (142) |11111111|11111111|11101011 ffffeb [24] (143) |11111111|11111111|1011111 7fffdf [23] (144) |11111111|11111111|11101100 ffffec [24] (145) |11111111|11111111|11101101 ffffed [24] (146) |11111111|11111111|010111 3fffd7 [22] (147) |11111111|11111111|1100000 7fffe0 [23] (148) |11111111|11111111|11101110 ffffee [24] (149) |11111111|11111111|1100001 7fffe1 [23] (150) |11111111|11111111|1100010 7fffe2 [23] (151) |11111111|11111111|1100011 7fffe3 [23] (152) |11111111|11111111|1100100 7fffe4 [23] (153) |11111111|11111110|11100 1fffdc [21] (154) |11111111|11111111|011000 3fffd8 [22] (155) |11111111|11111111|1100101 7fffe5 [23] (156) |11111111|11111111|011001 3fffd9 [22] (157) |11111111|11111111|1100110 7fffe6 [23] (158) |11111111|11111111|1100111 7fffe7 [23] (159) |11111111|11111111|11101111 ffffef [24] (160) |11111111|11111111|011010 3fffda [22] (161) |11111111|11111110|11101 1fffdd [21] (162) |11111111|11111110|1001 fffe9 [20] (163) |11111111|11111111|011011 3fffdb [22] (164) |11111111|11111111|011100 3fffdc [22] (165) |11111111|11111111|1101000 7fffe8 [23] (166) |11111111|11111111|1101001 7fffe9 [23] (167) |11111111|11111110|11110 1fffde [21] (168) |11111111|11111111|1101010 7fffea [23] (169) |11111111|11111111|011101 3fffdd [22]
(170) |11111111|11111111|011110 3fffde [22]
(171) |11111111|11111111|11110000 fffff0 [24]
(172) |11111111|11111110|11111 1fffdf [21]
(173) |11111111|11111111|011111 3fffdf [22]
(174) |11111111|11111111|1101011 7fffeb [23]
(175) |11111111|11111111|1101100 7fffec [23]
(176) |11111111|11111111|00000 1fffe0 [21]
(177) |11111111|11111111|00001 1fffe1 [21]
(178) |11111111|11111111|100000 3fffe0 [22]
(179) |11111111|11111111|00010 1fffe2 [21]
(180) |11111111|11111111|1101101 7fffed [23]
(181) |11111111|11111111|100001 3fffe1 [22]
(182) |11111111|11111111|1101110 7fffee [23]
(183) |11111111|11111111|1101111 7fffef [23]
(184) |11111111|11111110|1010 fffea [20]
(185) |11111111|11111111|100010 3fffe2 [22]
(186) |11111111|11111111|100011 3fffe3 [22]
(187) |11111111|11111111|100100 3fffe4 [22]
(188) |11111111|11111111|1110000 7ffff0 [23]
(189) |11111111|11111111|100101 3fffe5 [22]
(190) |11111111|11111111|100110 3fffe6 [22]
(191) |11111111|11111111|1110001 7ffff1 [23]
(192) |11111111|11111111|11111000|00 3ffffe0 [26]
(193) |11111111|11111111|11111000|01 3ffffe1 [26]
(194) |11111111|11111110|1011 fffeb [20]
(195) |11111111|11111110|001 7fff1 [19]
(196) |11111111|11111111|100111 3fffe7 [22]
(197) |11111111|11111111|1110010 7ffff2 [23]
(198) |11111111|11111111|101000 3fffe8 [22]
(199) |11111111|11111111|11110110|0 1ffffec [25]
(200) |11111111|11111111|11111000|10 3ffffe2 [26]
(201) |11111111|11111111|11111000|11 3ffffe3 [26]
(202) |11111111|11111111|11111001|00 3ffffe4 [26]
(203) |11111111|11111111|11111011|110 7ffffde [27]
(204) |11111111|11111111|11111011|111 7ffffdf [27]
(205) |11111111|11111111|11111001|01 3ffffe5 [26]
(206) |11111111|11111111|11110001 fffff1 [24]
(207) |11111111|11111111|11110110|1 1ffffed [25]
(208) |11111111|11111110|010 7fff2 [19]
(209) |11111111|11111111|00011 1fffe3 [21]
(210) |11111111|11111111|11111001|10 3ffffe6 [26]
(211) |11111111|11111111|11111100|000 7ffffe0 [27]
(212) |11111111|11111111|11111100|001 7ffffe1 [27]
(213) |11111111|11111111|11111001|11 3ffffe7 [26]
(214) |11111111|11111111|11111100|010 7ffffe2 [27]
(215) |11111111|11111111|11110010 fffff2 [24]
(216) |11111111|11111111|00100 1fffe4 [21]
(217) |11111111|11111111|00101 1fffe5 [21]
(218) |11111111|11111111|11111010|00 3ffffe8 [26]
(219) |11111111|11111111|11111010|01 3ffffe9 [26]
(220) |11111111|11111111|11111111|1101 ffffffd [28]
(221) |11111111|11111111|11111100|011 7ffffe3 [27]
(222) |11111111|11111111|11111100|100 7ffffe4 [27]
(223) |11111111|11111111|11111100|101 7ffffe5 [27]
(224) |11111111|11111110|1100 fffec [20]
(225) |11111111|11111111|11110011 fffff3 [24]
(226) |11111111|11111110|1101 fffed [20]
(227) |11111111|11111111|00110 1fffe6 [21]
(228) |11111111|11111111|101001 3fffe9 [22]
(229) |11111111|11111111|00111 1fffe7 [21]
(230) |11111111|11111111|01000 1fffe8 [21]
(231) |11111111|11111111|1110011 7ffff3 [23]
(232) |11111111|11111111|101010 3fffea [22]
(233) |11111111|11111111|101011 3fffeb [22]
(234) |11111111|11111111|11110111|0 1ffffee [25]
(235) |11111111|11111111|11110111|1 1ffffef [25]
(236) |11111111|11111111|11110100 fffff4 [24]
(237) |11111111|11111111|11110101 fffff5 [24]
(238) |11111111|11111111|11111010|10 3ffffea [26]
(239) |11111111|11111111|1110100 7ffff4 [23]
(240) |11111111|11111111|11111010|11 3ffffeb [26]
(241) |11111111|11111111|11111100|110 7ffffe6 [27]
(242) |11111111|11111111|11111011|00 3ffffec [26]
(243) |11111111|11111111|11111011|01 3ffffed [26]
(244) |11111111|11111111|11111100|111 7ffffe7 [27]
(245) |11111111|11111111|11111101|000 7ffffe8 [27]
(246) |11111111|11111111|11111101|001 7ffffe9 [27]
(247) |11111111|11111111|11111101|010 7ffffea [27]
(248) |11111111|11111111|11111101|011 7ffffeb [27]
(249) |11111111|11111111|11111111|1110 ffffffe [28]
(250) |11111111|11111111|11111101|100 7ffffec [27]
(251) |11111111|11111111|11111101|101 7ffffed [27]
(252) |11111111|11111111|11111101|110 7ffffee [27]
(253) |11111111|11111111|11111101|111 7ffffef [27]
(254) |11111111|11111111|11111110|000 7fffff0 [27]
(255) |11111111|11111111|11111011|10 3ffffee [26]
EOS (256) |11111111|11111111|11111111|111111 3fffffff [30]
That's the whole Huffman table, bestie, all 257 symbols, including the EOS ๐ง we don't mess with. ๐
Appendix C. Examples โจ
Okay so this appendix is basically the worked-examples corner ๐. It walks through integer encoding, header field representation, and whole lists of headers for both requests and responses, with and without Huffman coding. Iconic. ๐ฆ
C.1. Integer Representation Examples
Let's see how integer values actually get represented, step by step, with all the details ๐ (see Section 5.1).
C.1.1. Example 1: Encoding 10 Using a 5-Bit Prefix
So we want to encode 10 using a 5-bit prefix. ๐ธ
- 10 is less than 31 (
2^5 - 1), so it fits right in the 5-bit prefix. Easy peasy. ๐
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| X | X | X | 0 | 1 | 0 | 1 | 0 | 10 stored on 5 bits
+---+---+---+---+---+---+---+---+
C.1.2. Example 2: Encoding 1337 Using a 5-Bit Prefix
Now the main character ๐ of this section: encoding I=1337 with a 5-bit prefix.
- 1337 is greater than 31 (
2^5 - 1), so the prefix gets filled with its max value, 31. ๐ - I = 1337 - (2^5 - 1) = 1306. โจ
- I (1306) is greater than or equal to 128, so the while loop body runs:
I % 128 == 2626 + 128 == 154- 154 gets encoded in 8 bits as
10011010 - I gets set to 10 (
1306 / 128 == 10). Now I is no longer greater than or equal to 128, so the loop wraps up. ๐ฆ
- I, now 10, is encoded in 8 bits as
00001010. - And that's it, the process ends. ๐
Here's the same thing as a little diagram, periodt:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| X | X | X | 1 | 1 | 1 | 1 | 1 | Prefix = 31, I = 1306
| 1 | 0 | 0 | 1 | 1 | 0 | 1 | 0 | 1306>=128, encode(154), I=1306/128
| 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | 10<128, encode(10), done
+---+---+---+---+---+---+---+---+
C.1.3. Example 3: Encoding 42 Starting at an Octet Boundary
This one starts at an octet boundary, which means we use an 8-bit prefix. Simple but lowkey important ๐ง.
- 42 is less than 255 (
2^8 - 1), so it gets represented using the 8-bit prefix. ๐
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | 0 | 1 | 0 | 1 | 0 | 42 stored on 8 bits
+---+---+---+---+---+---+---+---+
C.2. Header Field Representation Examples
Next up, several independent examples of header field representations. Each one stands on its own, no drama ๐ .
C.2.1. Literal Header Field with Indexing
This representation uses a literal name and a literal value, and the header field gets added to the dynamic table. โจ
Header list to encode:
custom-key: custom-header
Hex dump of encoded data:
400a 6375 7374 6f6d 2d6b 6579 0d63 7573 | @.custom-key.cus
746f 6d2d 6865 6164 6572 | tom-header
Decoding process:
40 | == Literal indexed ==
0a | Literal name (len = 10)
6375 7374 6f6d 2d6b 6579 | custom-key
0d | Literal value (len = 13)
6375 7374 6f6d 2d68 6561 6465 72 | custom-header
| -> custom-key:
| custom-header
Dynamic Table (after decoding):
[ 1] (s = 55) custom-key: custom-header
Table size: 55
Decoded header list:
custom-key: custom-header
C.2.2. Literal Header Field without Indexing
This one uses an indexed name and a literal value. The header field is not added to the dynamic table, so it stays out of the way ๐ธ.
Header list to encode:
:path: /sample/path
Hex dump of encoded data:
040c 2f73 616d 706c 652f 7061 7468 | ../sample/path
Decoding process:
04 | == Literal not indexed ==
| Indexed name (idx = 4)
| :path
0c | Literal value (len = 12)
2f73 616d 706c 652f 7061 7468 | /sample/path
| -> :path: /sample/path
Dynamic table (after decoding): empty. ๐ซถ
Decoded header list:
:path: /sample/path
C.2.3. Literal Header Field Never Indexed
Here we have a literal name and a literal value. The header field is not added to the dynamic table, and it must use the same representation if an intermediary re-encodes it. Protective energy, honestly ๐ฅ.
Header list to encode:
password: secret
Hex dump of encoded data:
1008 7061 7373 776f 7264 0673 6563 7265 | ..password.secre
74 | t
Decoding process:
10 | == Literal never indexed ==
08 | Literal name (len = 8)
7061 7373 776f 7264 | password
06 | Literal value (len = 6)
7365 6372 6574 | secret
| -> password: secret
Dynamic table (after decoding): empty. ๐
Decoded header list:
password: secret
C.2.4. Indexed Header Field
This is the simplest vibe of all: the header field comes straight from the static table, no literal bytes needed ๐ง.
Header list to encode:
:method: GET
Hex dump of encoded data:
82 | .
Decoding process:
82 | == Indexed - Add ==
| idx = 2
| -> :method: GET
Dynamic table (after decoding): empty.
Decoded header list:
:method: GET
C.3. Request Examples without Huffman Coding
Okay this next bit is giving full series energy ๐บ. It shows several consecutive header lists, all corresponding to HTTP requests, sent on the same connection, so you can watch the dynamic table build up across them ๐ฆ.
C.3.1. First Request ๐
okay bestie, this is the first of three requests on the same connection, and we're starting with the simplest one ๐
the header list we want to encode is just four pseudo-headers: a GET, http, /, and www.example.com as the authority โจ
Header list to encode:
:method: GET
:scheme: http
:path: /
:authority: www.example.com
here's the hex dump of what actually goes over the wire ๐ฆ
Hex dump of encoded data:
8286 8441 0f77 7777 2e65 7861 6d70 6c65 | ...A.www.example
2e63 6f6d | .com
and here's the decoding process, step by step, so you can see exactly what the decoder does with each byte ๐ง
Decoding process:
82 | == Indexed - Add ==
| idx = 2
| -> :method: GET
86 | == Indexed - Add ==
| idx = 6
| -> :scheme: http
84 | == Indexed - Add ==
| idx = 4
| -> :path: /
41 | == Literal indexed ==
| Indexed name (idx = 1)
| :authority
0f | Literal value (len = 15)
7777 772e 6578 616d 706c 652e 636f 6d | www.example.com
| -> :authority:
| www.example.com
the first three bytes are just indexed headers, so :method, :scheme and :path each get added straight from the static table ๐ then the 41 byte is a literal with an indexed name (idx = 1, which is :authority), and the value www.example.com is 15 bytes long, so it's spelled out in plain text ๐ธ
after decoding, the dynamic table looks like this ๐
Dynamic Table (after decoding):
[ 1] (s = 57) :authority: www.example.com
Table size: 57
and the decoded header list is exactly what we started with, which is the whole point, periodt ๐ฅ
Decoded header list:
:method: GET
:scheme: http
:path: /
:authority: www.example.com
C.3.2. Second Request ๐
second request, same connection, and now we're adding a cache-control: no-cache header on top of the first four ๐ the header list to encode is:
Header list to encode:
:method: GET
:scheme: http
:path: /
:authority: www.example.com
cache-control: no-cache
hex dump time ๐
Hex dump of encoded data:
8286 84be 5808 6e6f 2d63 6163 6865 | ....X.no-cache
and the decoding process ๐ซถ
Decoding process:
82 | == Indexed - Add ==
| idx = 2
| -> :method: GET
86 | == Indexed - Add ==
| idx = 6
| -> :scheme: http
84 | == Indexed - Add ==
| idx = 4
| -> :path: /
be | == Indexed - Add ==
| idx = 62
| -> :authority:
| www.example.com
58 | == Literal indexed ==
| Indexed name (idx = 24)
| cache-control
08 | Literal value (len = 8)
6e6f 2d63 6163 6865 | no-cache
| -> cache-control: no-cache
this time the be byte is interesting โจ it's an indexed header at idx = 62, which points at :authority: www.example.com because that's sitting in the dynamic table from the first request. so the whole authority doesn't have to get sent again, iconic ๐ฆ then 58 is a literal with an indexed name (idx = 24, which is cache-control), and the 8 byte value no-cache gets spelled out ๐ธ
the dynamic table now has two entries, and the newest one goes at the top ๐
Dynamic Table (after decoding):
[ 1] (s = 53) cache-control: no-cache
[ 2] (s = 57) :authority: www.example.com
Table size: 110
and the decoded header list ๐
Decoded header list:
:method: GET
:scheme: http
:path: /
:authority: www.example.com
cache-control: no-cache
C.3.3. Third Request ๐
third and final request in this group, and it's got a bit of everything ๐ง this time the scheme is https, the path is /index.html, and there's a brand new header called custom-key with the value custom-value ๐
Header list to encode:
:method: GET
:scheme: https
:path: /index.html
:authority: www.example.com
custom-key: custom-value
the hex dump ๐
Hex dump of encoded data:
8287 85bf 400a 6375 7374 6f6d 2d6b 6579 | ....@.custom-key
0c63 7573 746f 6d2d 7661 6c75 65 | .custom-value
and the decoding process ๐ซถ
Decoding process:
82 | == Indexed - Add ==
| idx = 2
| -> :method: GET
87 | == Indexed - Add ==
| idx = 7
| -> :scheme: https
85 | == Indexed - Add ==
| idx = 5
| -> :path: /index.html
bf | == Indexed - Add ==
| idx = 63
| -> :authority:
| www.example.com
40 | == Literal indexed ==
0a | Literal name (len = 10)
6375 7374 6f6d 2d6b 6579 | custom-key
0c | Literal value (len = 12)
6375 7374 6f6d 2d76 616c 7565 | custom-value
| -> custom-key:
| custom-value
so the 40 byte here is a literal with a literal name, not an indexed one, because custom-key isn't in any table yet ๐ฆ it's 10 bytes long, and the value custom-value is 12 bytes โจ the bf byte at idx = 63 is the :authority pointer from before, same as the last request, just a different number now that the table has grown ๐ธ
the dynamic table gets the newest entry at the top ๐
Dynamic Table (after decoding):
[ 1] (s = 54) custom-key: custom-value
[ 2] (s = 53) cache-control: no-cache
[ 3] (s = 57) :authority: www.example.com
Table size: 164
and here's the final decoded header list ๐
Decoded header list:
:method: GET
:scheme: https
:path: /index.html
:authority: www.example.com
custom-key: custom-value
C.4. Request Examples with Huffman Coding ๐ธ
this section is the same three requests as the last one, but now the literal values are Huffman encoded instead of sent as plain text ๐ same header lists, same decoding logic, just with smaller bytes on the wire ๐ฆ
C.4.1. First Request โจ
same starting point as C.3.1, so the header list is still the same four pseudo-headers ๐
Header list to encode:
:method: GET
:scheme: http
:path: /
:authority: www.example.com
the hex dump is where the difference shows up ๐
Hex dump of encoded data:
8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 | ...A......:k....
ff | .
and the decoding process ๐ง
Decoding process:
82 | == Indexed - Add ==
| idx = 2
| -> :method: GET
86 | == Indexed - Add ==
| idx = 6
| -> :scheme: http
84 | == Indexed - Add ==
| idx = 4
| -> :path: /
41 | == Literal indexed ==
| Indexed name (idx = 1)
| :authority
8c | Literal value (len = 12)
| Huffman encoded:
f1e3 c2e5 f23a 6ba0 ab90 f4ff | .....:k.....
| Decoded:
| www.example.com
| -> :authority:
| www.example.com
the first three headers are the same as before ๐ the big change is the 8c byte: that's a 12 byte Huffman encoded value, and it decodes to www.example.com, which is the same thing we had in plain text back in C.3.1 ๐ so the actual value is the same, it just took fewer bytes on the wire, lowkey genius ๐ซถ
the dynamic table is identical to the non-Huffman version ๐
Dynamic Table (after decoding):
[ 1] (s = 57) :authority: www.example.com
Table size: 57
and the decoded header list ๐
Decoded header list:
:method: GET
:scheme: http
:path: /
:authority: www.example.com
C.4.2. Second Request ๐ฆ
second request, with cache-control: no-cache added ๐
Header list to encode:
:method: GET
:scheme: http
:path: /
:authority: www.example.com
cache-control: no-cache
hex dump ๐
Hex dump of encoded data:
8286 84be 5886 a8eb 1064 9cbf | ....X....d..
decoding process ๐ซถ
Decoding process:
82 | == Indexed - Add ==
| idx = 2
| -> :method: GET
86 | == Indexed - Add ==
| idx = 6
| -> :scheme: http
84 | == Indexed - Add ==
| idx = 4
| -> :path: /
be | == Indexed - Add ==
| idx = 62
| -> :authority:
| www.example.com
58 | == Literal indexed ==
| Indexed name (idx = 24)
| cache-control
86 | Literal value (len = 6)
| Huffman encoded:
a8eb 1064 9cbf | ...d..
| Decoded:
| no-cache
| -> cache-control: no-cache
the be byte points at the authority from the first request, same as before โจ and this time the cache-control value is only 6 bytes after Huffman encoding, because no-cache got squished down, ngl that's so satisfying ๐ธ
dynamic table ๐
Dynamic Table (after decoding):
[ 1] (s = 53) cache-control: no-cache
[ 2] (s = 57) :authority: www.example.com
Table size: 110
and the decoded header list ๐
Decoded header list:
:method: GET
:scheme: http
:path: /
:authority: www.example.com
cache-control: no-cache
C.4.3. Third Request ๐
last one in this group, same as C.3.3 with custom-key: custom-value on the end ๐ง
Header list to encode:
:method: GET
:scheme: https
:path: /index.html
:authority: www.example.com
custom-key: custom-value
hex dump ๐
Hex dump of encoded data:
8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925 | ....@.%.I.[.}..%
a849 e95b b8e8 b4bf | .I.[....
decoding process ๐ซถ
Decoding process:
82 | == Indexed - Add ==
| idx = 2
| -> :method: GET
87 | == Indexed - Add ==
| idx = 7
| -> :scheme: https
85 | == Indexed - Add ==
| idx = 5
| -> :path: /index.html
bf | == Indexed - Add ==
| idx = 63
| -> :authority:
| www.example.com
40 | == Literal indexed ==
88 | Literal name (len = 8)
| Huffman encoded:
25a8 49e9 5ba9 7d7f | %.I.[.}.
| Decoded:
| custom-key
89 | Literal value (len = 9)
| Huffman encoded:
25a8 49e9 5bb8 e8b4 bf | %.I.[....
| Decoded:
| custom-value
| -> custom-key:
| custom-value
here the name is a Huffman encoded literal of 8 bytes that decodes to custom-key, and the value is 9 bytes that decodes to custom-value ๐ the bf byte still points to the authority, just at its new index of 63 โจ
dynamic table ๐
Dynamic Table (after decoding):
[ 1] (s = 54) custom-key: custom-value
[ 2] (s = 53) cache-control: no-cache
[ 3] (s = 57) :authority: www.example.com
Table size: 164
and the decoded header list ๐
Decoded header list:
:method: GET
:scheme: https
:path: /index.html
:authority: www.example.com
custom-key: custom-value
C.5. Response Examples without Huffman Coding ๐
now we switch from requests to responses โจ this section shows several header lists back to back, all on the same connection, and each one stands in for an HTTP response ๐ the HTTP/2 setting parameter SETTINGS_HEADER_TABLE_SIZE is set to 256 octets, which is small enough that some evictions happen along the way, so this is where you get to watch the dynamic table actually drop stuff ๐ฅ
C.5.1. First Response
okay so this is the first response in the series ๐ here's the header list we're encoding:
:status: 302
cache-control: private
date: Mon, 21 Oct 2013 20:13:21 GMT
location: https://www.example.com
and here's the hex dump of the encoded data:
4803 3330 3258 0770 7269 7661 7465 611d | H.302X.privatea.
4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
2032 303a 3133 3a32 3120 474d 546e 1768 | 20:13:21 GMTn.h
7474 7073 3a2f 2f77 7777 2e65 7861 6d70 | ttps://www.examp
6c65 2e63 6f6d | le.com
and the decoding process, step by step, bestie ๐
48 | == Literal indexed ==
| Indexed name (idx = 8)
| :status
03 | Literal value (len = 3)
3330 32 | 302
| -> :status: 302
58 | == Literal indexed ==
| Indexed name (idx = 24)
| cache-control
07 | Literal value (len = 7)
7072 6976 6174 65 | private
| -> cache-control: private
61 | == Literal indexed ==
| Indexed name (idx = 33)
| date
1d | Literal value (len = 29)
4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
2032 303a 3133 3a32 3120 474d 54 | 20:13:21 GMT
| -> date: Mon, 21 Oct 2013
| 20:13:21 GMT
6e | == Literal indexed ==
| Indexed name (idx = 46)
| location
17 | Literal value (len = 23)
6874 7470 733a 2f2f 7777 772e 6578 616d | https://www.exam
706c 652e 636f 6d | ple.com
| -> location:
| https://www.example.com
after decoding, the dynamic table looks like this ๐ฆ
[ 1] (s = 63) location: https://www.example.com
[ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT
[ 3] (s = 52) cache-control: private
[ 4] (s = 42) :status: 302
Table size: 222
and the decoded header list is:
:status: 302
cache-control: private
date: Mon, 21 Oct 2013 20:13:21 GMT
location: https://www.example.com
C.5.2. Second Response
the (":status", "302") header field gets evicted from the dynamic table to free up space so we can add (":status", "307") ๐ฅ iconic space management, no notes.
header list to encode:
:status: 307
cache-control: private
date: Mon, 21 Oct 2013 20:13:21 GMT
location: https://www.example.com
hex dump of encoded data:
4803 3330 37c1 c0bf | H.307...
decoding process:
48 | == Literal indexed ==
| Indexed name (idx = 8)
| :status
03 | Literal value (len = 3)
3330 37 | 307
| - evict: :status: 302
| -> :status: 307
c1 | == Indexed - Add ==
| idx = 65
| -> cache-control: private
c0 | == Indexed - Add ==
| idx = 64
| -> date: Mon, 21 Oct 2013
| 20:13:21 GMT
bf | == Indexed - Add ==
| idx = 63
| -> location:
| https://www.example.com
dynamic table after decoding ๐
[ 1] (s = 42) :status: 307
[ 2] (s = 63) location: https://www.example.com
[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT
[ 4] (s = 52) cache-control: private
Table size: 222
decoded header list:
:status: 307
cache-control: private
date: Mon, 21 Oct 2013 20:13:21 GMT
location: https://www.example.com
C.5.3. Third Response
okay this one is a whole evict-fest ๐ญ several header fields get kicked out of the dynamic table while this header list is processed.
header list to encode:
:status: 200
cache-control: private
date: Mon, 21 Oct 2013 20:13:22 GMT
location: https://www.example.com
content-encoding: gzip
set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
Hex dump of encoded data:
hex dump of encoded data:
88c1 611d 4d6f 6e2c 2032 3120 4f63 7420 | ..a.Mon, 21 Oct
3230 3133 2032 303a 3133 3a32 3220 474d | 2013 20:13:22 GM
54c0 5a04 677a 6970 7738 666f 6f3d 4153 | T.Z.gzipw8foo=AS
444a 4b48 514b 425a 584f 5157 454f 5049 | DJKHQKBZXOQWEOPI
5541 5851 5745 4f49 553b 206d 6178 2d61 | UAXQWEOIU; max-a
6765 3d33 3630 303b 2076 6572 7369 6f6e | ge=3600; version
3d31 | =1
decoding process:
88 | == Indexed - Add ==
| idx = 8
| -> :status: 200
c1 | == Indexed - Add ==
| idx = 65
| -> cache-control: private
61 | == Literal indexed ==
| Indexed name (idx = 33)
| date
1d | Literal value (len = 29)
4d6f 6e2c 2032 3120 4f63 7420 3230 3133 | Mon, 21 Oct 2013
2032 303a 3133 3a32 3220 474d 54 | 20:13:22 GMT
| - evict: cache-control:
| private
| -> date: Mon, 21 Oct 2013
| 20:13:22 GMT
c0 | == Indexed - Add ==
| idx = 64
| -> location:
| https://www.example.com
5a | == Literal indexed ==
| Indexed name (idx = 26)
| content-encoding
04 | Literal value (len = 4)
677a 6970 | gzip
| - evict: date: Mon, 21 Oct
| 2013 20:13:21 GMT
| -> content-encoding: gzip
77 | == Literal indexed ==
| Indexed name (idx = 55)
| set-cookie
38 | Literal value (len = 56)
666f 6f3d 4153 444a 4b48 514b 425a 584f | foo=ASDJKHQKBZXO
5157 454f 5049 5541 5851 5745 4f49 553b | QWEOPIUAXQWEOIU;
206d 6178 2d61 6765 3d33 3630 303b 2076 | max-age=3600; v
6572 7369 6f6e 3d31 | ersion=1
| - evict: location:
| https://www.example.com
| - evict: :status: 307
| -> set-cookie: foo=ASDJKHQ
| KBZXOQWEOPIUAXQWEOIU; ma
| x-age=3600; version=1
dynamic table after decoding ๐
[ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
max-age=3600; version=1
[ 2] (s = 52) content-encoding: gzip
[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT
Table size: 215
decoded header list:
:status: 200
cache-control: private
date: Mon, 21 Oct 2013 20:13:22 GMT
location: https://www.example.com
content-encoding: gzip
set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
C.6. Response Examples with Huffman Coding
this section has the same examples as the last one, but now the literal values are Huffman-encoded ๐ the HTTP/2 setting parameter SETTINGS_HEADER_TABLE_SIZE is set to 256 octets, so some evictions happen โจ evictions are based on the length of the decoded literal values, which means they line up with the previous section exactly ๐ซถ
C.6.1. First Response ๐
Okay so this is the first response in the Huffman-coded batch, and it's giving clean, simple vibes. The server sends four header fields, and the dynamic table starts out empty. Each field gets encoded, and the table fills up as we go. Let's see what the encoder is working with first. ๐
Header list to encode:
:status: 302
cache-control: private
date: Mon, 21 Oct 2013 20:13:21 GMT
location: https://www.example.com
Hex dump of encoded data:
4882 6402 5885 aec3 771a 4b61 96d0 7abe | H.d.X...w.Ka..z.
9410 54d4 44a8 2005 9504 0b81 66e0 82a6 | ..T.D. .....f...
2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8 | -..n..)...c.....
e9ae 82ae 43d3 | ....C.
Decoding process:
48 | == Literal indexed ==
| Indexed name (idx = 8)
| :status
82 | Literal value (len = 2)
| Huffman encoded:
6402 | d.
| Decoded:
| 302
| -> :status: 302
58 | == Literal indexed ==
| Indexed name (idx = 24)
| cache-control
85 | Literal value (len = 5)
| Huffman encoded:
aec3 771a 4b | ..w.K
| Decoded:
| private
| -> cache-control: private
61 | == Literal indexed ==
| Indexed name (idx = 33)
| date
96 | Literal value (len = 22)
| Huffman encoded:
d07a be94 1054 d444 a820 0595 040b 8166 | .z...T.D. .....f
e082 a62d 1bff | ...-..
| Decoded:
| Mon, 21 Oct 2013 20:13:21
| GMT
| -> date: Mon, 21 Oct 2013
| 20:13:21 GMT
6e | == Literal indexed ==
| Indexed name (idx = 46)
| location
91 | Literal value (len = 17)
| Huffman encoded:
9d29 ad17 1863 c78f 0b97 c8e9 ae82 ae43 | .)...c.........C
d3 | .
| Decoded:
| https://www.example.com
| -> location:
| https://www.example.com
Dynamic Table (after decoding):
[ 1] (s = 63) location: https://www.example.com
[ 2] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT
[ 3] (s = 52) cache-control: private
[ 4] (s = 42) :status: 302
Table size: 222
Decoded header list:
:status: 302
cache-control: private
date: Mon, 21 Oct 2013 20:13:21 GMT
location: https://www.example.com
C.6.2. Second Response ๐ฆ
Now things get spicy ๐ถ๏ธ. The (":status", "302") entry gets evicted from the dynamic table to make room for (":status", "307"). Table space is limited, so something has to go, and the old status line is the one that gets the boot. ๐ญ
Header list to encode:
:status: 307
cache-control: private
date: Mon, 21 Oct 2013 20:13:21 GMT
location: https://www.example.com
Hex dump of encoded data:
4883 640e ffc1 c0bf | H.d.....
Decoding process:
48 | == Literal indexed ==
| Indexed name (idx = 8)
| :status
83 | Literal value (len = 3)
| Huffman encoded:
640e ff | d..
| Decoded:
| 307
| - evict: :status: 302
| -> :status: 307
c1 | == Indexed - Add ==
| idx = 65
| -> cache-control: private
c0 | == Indexed - Add ==
| idx = 64
| -> date: Mon, 21 Oct 2013
| 20:13:21 GMT
bf | == Indexed - Add ==
| idx = 63
| -> location:
| https://www.example.com
Dynamic Table (after decoding):
[ 1] (s = 42) :status: 307
[ 2] (s = 63) location: https://www.example.com
[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:21 GMT
[ 4] (s = 52) cache-control: private
Table size: 222
Decoded header list:
:status: 307
cache-control: private
date: Mon, 21 Oct 2013 20:13:21 GMT
location: https://www.example.com
C.6.3. Third Response ๐
Final boss energy ๐ฅ. Several header fields get evicted from the dynamic table while this header list is processed, so it's a whole reshuffle. Watch the table churn. ๐
Header list to encode:
:status: 200
cache-control: private
date: Mon, 21 Oct 2013 20:13:22 GMT
location: https://www.example.com
content-encoding: gzip
set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
Hex dump of encoded data:
88c1 6196 d07a be94 1054 d444 a820 0595 | ..a..z...T.D. ..
040b 8166 e084 a62d 1bff c05a 839b d9ab | ...f...-...Z....
77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b | w..........5...[
3960 d5af 2708 7f36 72c1 ab27 0fb5 291f | 9`..'..6r..'..).
9587 3160 65c0 03ed 4ee5 b106 3d50 07 | ..1`e...N...=P.
Decoding process:
88 | == Indexed - Add ==
| idx = 8
| -> :status: 200
c1 | == Indexed - Add ==
| idx = 65
| -> cache-control: private
61 | == Literal indexed ==
| Indexed name (idx = 33)
| date
96 | Literal value (len = 22)
| Huffman encoded:
d07a be94 1054 d444 a820 0595 040b 8166 | .z...T.D. .....f
e084 a62d 1bff | ...-..
| Decoded:
| Mon, 21 Oct 2013 20:13:22
| GMT
| - evict: cache-control:
| private
| -> date: Mon, 21 Oct 2013
| 20:13:22 GMT
c0 | == Indexed - Add ==
| idx = 64
| -> location:
| https://www.example.com
5a | == Literal indexed ==
| Indexed name (idx = 26)
| content-encoding
83 | Literal value (len = 3)
| Huffman encoded:
9bd9 ab | ...
| Decoded:
| gzip
| - evict: date: Mon, 21 Oct
| 2013 20:13:21 GMT
| -> content-encoding: gzip
77 | == Literal indexed ==
| Indexed name (idx = 55)
| set-cookie
ad | Literal value (len = 45)
| Huffman encoded:
94e7 821d d7f2 e6c7 b335 dfdf cd5b 3960 | .........5...[9`
d5af 2708 7f36 72c1 ab27 0fb5 291f 9587 | ..'..6r..'..)...
3160 65c0 03ed 4ee5 b106 3d50 07 | 1`e...N...=P.
| Decoded:
| foo=ASDJKHQKBZXOQWEOPIUAXQ
| WEOIU; max-age=3600; versi
| on=1
| - evict: location:
| https://www.example.com
| - evict: :status: 307
| -> set-cookie: foo=ASDJKHQ
| KBZXOQWEOPIUAXQWEOIU; ma
| x-age=3600; version=1
Dynamic Table (after decoding):
[ 1] (s = 98) set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;
max-age=3600; version=1
[ 2] (s = 52) content-encoding: gzip
[ 3] (s = 65) date: Mon, 21 Oct 2013 20:13:22 GMT
Table size: 215
Decoded header list:
:status: 200
cache-control: private
date: Mon, 21 Oct 2013 20:13:22 GMT
location: https://www.example.com
content-encoding: gzip
set-cookie: foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1
Acknowledgments ๐ซถ
This specification got a ton of substantial input from these amazing people:
- Mike Bishop, Jeff Pinner, Julian Reschke, and Martin Thomson (substantial editorial contributions).
- Johnny Graettinger (Huffman code statistics).
Authors' Addresses ๐
Roberto Peon Google, Inc
EMail: fenix@google.com
Herve Ruellan Canon CRF
EMail: herve.ruellan@crf.canon.fr
๐ฆ where this page comes from
- ietf: rfc7541 2015-05
- ๐ girlified by claude-haiku-5-5 on : a language model's rewrite of the original, under its terms