RFC 7541

HPACK: Header Compression for HTTP/2

๐Ÿ“… published May 2015, from ietf

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 (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) ๐Ÿงš

  1. 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) ๐Ÿงน
  1. 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 ๐Ÿซถ

  1. 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

8.2. Informative References

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 == 26
    • 26 + 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