概括
HPACK 是一个新的可以消除 header 字段冗余,限制已知安全攻击的脆弱性和在受限的环境中有限的内存使用的压缩器。RFC7541 定义了两个表,分别是一张静态表和动态表。这两张表的索引地址空间按照 Figure 1 的地址分配。
<-------------Index Address Space----------------->
<-----Static Table----> <-----Dynamic Table----->
+-----+---------+-----+ +-----+-----------+-----+
| 1 | ... | s | | s+1 | ... | s+k |
+-----+---------+-----+ +-----+-----------+-----+
^ |
| V
Insertion Point Dropping Point
Figure 1: Index Address Space
头部字段表示 (Header Field Representation)
一个加密的头部字段可以作为一个 index(索引)或者 literal(字面值)表示。一个索引表示 (indexed representation) 定义了一个可以引用在静态表或者动态表中的实体的头部字段。一个字面值表示 (literal representation) 定义了一个特定它的名字和值的头部字段。
静态表 (Static Table)
静态表是一个静态地将频繁出现的标头字段与索引值相关联的表。静态表是有序的,只读的,经常访问的,且可以在编码和解码上下文之间共享的表。官方定义可参考 Static Table. 具体定义了哪些值可参考 Appendix A
动态表 (Dynamic Table)
动态表是一个将索引值和存储头部字段相关联的表。动态表是动态的且特定于编码和解码上下文。官方定义可参考 Dynamic table. 根据 Encoding and Decoding Contexts, 在 HTTP 里,编码和解码的动态表被端点完全独立维护的表,请求表和响应表是分开的。所以这意味着对于每端点将维护了两张动态表。下面将详细的描述下每个端点是如何分别维护这两张动态表的。
如图步骤 1:解码端发送 SETTING 帧且设置 SETTINGS_HEADER_TABLE_SIZE 参数去修改对端编码动态表的最大大小限制。具体可参考 Maximum Table Size
如图步骤 2:编码端接收了解码端的 SETTING 帧后,将更新自己的编码动态表的最大大小限制值,再发送 SETTING ACK 帧给解码端 具体可参考 SETTINGS.
如图步骤 3:如果编码端想要更新自己的编码动态表的大小,它将通过发送带有相应值的动态表大小更新的头部字段给对端具体可参考 Dynamic Table Size Update
如图步骤 4:解码端在接收到的头部块的开始是动态表大小更新的头部字段,它将更新自己的解码动态表的最大大小值,且发送 ACK 给对端,表示确认收到且修改。具体可参考 Maximum Table Size
原始类型表示 (Primitive Type Representations)
整数类型 (Integer Representation)
整数用于表示名称索引、标题字段索引或字符串长度。整数表示可以从一个字节内的任何位置开始。为了实现优化处理,整数表示总是在八位元的末尾结束。具体可参考 Integer Representation.
字符串字面值 (String Literal Representation)
头部字段名和头部字段值可以表示为字符串文字。字符串文字被编码为一个八位元序列,可以直接编码字符串文字的八位元,也可以使用 Huffman 编码。具体可参考 String Literal Representation. 字符串文字表示包含以下字段:
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| H | String Length (7+) |
+---+---------------------------+
| String Data (Length octets) |
+-------------------------------+
Figure 2: String Literal Representation
H: 一个位标志,它指示字符串的八位元是否经过霍夫曼编码。
String Length: 用于对字符串文字进行编码的八位元的数目,用带有 7 位前缀的整数进行编码。
String Data: 字符串文字的编码数据。如果 H 是' 0 ‘,那么编码的数据就是字符串文字的原始八位元。如果 H 是‘1’,那么编码的数据就是字符串文字的 Huffman 编码。
二进制格式 (Binary Format)
索引头部字段表示 (Indexed Header Field Representation)
索引头部字段表示标识静态表或动态表中的项。索引标头字段表示将导致标头字段被添加到已解码的标头列表中。索引标头字段以“1”1 位模式开始,然后是匹配标头字段的索引,表示为带有 7 位前缀的整数。不使用索引值 0。它必须被视为解码错误。具体可参考 Indexed Header Field Representation.
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 1 | Index (7+) |
+---+---------------------------+
Figure 3: Indexed Header Field
字面值头部字段表示 (Literal Header Field Representation)
文字标题字段表示包含文字标题字段值。头字段名要么作为文本提供,要么作为对现有表项的引用提供,要么来自静态表,要么来自动态表。具体可参考 Literal Header Field Representation. RFC7541 定义了下面三种文字头字段表示形式:
带增量索引的字面值头部字段 (Literal Header Field with Incremental Indexing)
带有增量索引表示的文字标头字段将导致将标头字段附加到已解码的标头列表中,并将其作为新条目插入动态表中。它以“01”2 位模式开始。具体可参考 Literal Header Field with Incremental Indexing.
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | Index (6+) |
+---+---------------------------+
| H | Value Length (7+) |
+-------------------------------+
| Value String (Length octets) |
+-------------------------------+
Figure 4: 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 5: Literal Header Field with Incremental Indexing -- New Name
没有索引的字面值头部字段 (Literal Header Field without Indexing)
没有索引表示的文字标头字段将导致在不更改动态表的情况下将标头字段附加到已解码的标头列表。它以“0000”4 位模式开始。具体可参考 Literal Header Field without Indexing.
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | Index (4+) |
+---+---------------------------+
| H | Value Length (7+) |
+-------------------------------+
| Value String (Length octets) |
+-------------------------------+
Figure 6: 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 7: Literal Header Field without Indexing -- New Name
从不索引的字面值头部字段 (Literal Header Field Never Indexed)
文本头字段的无索引表示将导致在不更改动态表的情况下将头字段附加到已解码的头列表中。中间层必须使用相同的表示法对这个头字段进行编码,它以“0001”4 位模式开始。具体可参考 Literal Header Field Never Indexed.
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | Index (4+) |
+---+---------------------------+
| H | Value Length (7+) |
+-------------------------------+
| Value String (Length octets) |
+-------------------------------+
Figure 8: 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 9: Literal Header Field Never Indexed -- New Name
动态表大小更新 (Dynamic Table Size Update)
动态表大小更新表示动态表的大小发生了变化。动态表大小更新从’ 001 ' 3 位模式开始,然后是新的最大大小,它表示为一个带有 5 位前缀的整数。新的最大尺寸必须小于或等于当时使用 HPACK 协议确定的限制。减少动态表的最大大小可能导致条目被逐出。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | Max Size (5+) |
+---+---------------------------+
Figure 10: Maximum Dynamic Table Size Change