帧类型和格式

QUIC帧包由帧填充。它具有一个帧类型字节,它本身具有一个依赖类型的解释, 后面是依赖类型的帧首部字段。所有的帧被包含在单独的QUIC包中,且没有帧可以跨越QUIC包边界。

帧类型

帧类型字节有两种解释,导致两种帧类型:特殊帧类型,和普通帧类型。 特殊帧类型在帧类型字节中同时编码帧类型和对应的标记,而普通帧类型简单地使用帧类型字节。

当前定义的特殊帧类型如下::

+------------------+-----------------------------+
| Type-field value |     Control Frame-type      |
+------------------+-----------------------------+
|     1fdooossB    |  STREAM                     |
|     01ntllmmB    |  ACK                        |
|     001xxxxxB    |  CONGESTION_FEEDBACK        |
+------------------+-----------------------------+

当前定义的普通帧类型如下::

+------------------+-----------------------------+
| Type-field value |     Control Frame-type      |
+------------------+-----------------------------+
| 00000000B (0x00) |  PADDING                    |
| 00000001B (0x01) |  RST_STREAM                 |
| 00000010B (0x02) |  CONNECTION_CLOSE           |
| 00000011B (0x03) |  GOAWAY                     |
| 00000100B (0x04) |  WINDOW_UPDATE              |
| 00000101B (0x05) |  BLOCKED                    |
| 00000110B (0x06) |  STOP_WAITING               |
| 00000111B (0x07) |  PING                       |
+------------------+-----------------------------+

STREAM帧

STREAM帧同时被用于隐式地创建流和在流上发送数据,它的格式如下::

.    0        1       …               SLEN
+--------+--------+--------+--------+--------+
|Type (8)| Stream ID (8, 16, 24, or 32 bits) |
|        |    (Variable length SLEN bytes)   |
+--------+--------+--------+--------+--------+

  SLEN+1  SLEN+2     …                                         SLEN+OLEN
+--------+--------+--------+--------+--------+--------+--------+--------+
|   Offset (0, 16, 24, 32, 40, 48, 56, or 64 bits) (variable length)    |
|                    (Variable length: OLEN  bytes)                     |
+--------+--------+--------+--------+--------+--------+--------+--------+

  SLEN+OLEN+1   SLEN+OLEN+2
+-------------+-------------+
| Data length (0 or 16 bits)|
|  Optional(maybe 0 bytes)  |
+------------+--------------+

STREAM帧首部中的字段如下:

  • 帧类型:帧类型字节是一个包含多种标记 (1fdooossB) 的8位值:

    • 最左边的位必须被设为 1 以指明这是一个STREAM帧。
    • ‘f’ 位是FIN位。当被设置为 1 时,这个位表明发送者已经完成在流上的发送并希望 “half-close(半关闭)” (稍后将详细描述)。本文档的后面将更详细地描述。
    • ‘d’ 位表明STREAM头部中是否包含数据长度。当设为0时,这个字段表明STREAM帧扩展至包的结尾。
    • 接下来的三个’ooo’位编码Offset头部字段的长度为0,16,24,32,40,48,56,或64位长。
    • 接下来的两个 ‘ss’ 位编码流 ID头部字段的长度为 8,16,24,或32位长。
  • 流 ID:一个大小可变的流唯一的无符号ID。

  • 偏移:一个大小可变的无符号数字指定流中这块数据的字节偏移。

  • 数据长度:一个可选的16位无符号数字指定这个流帧中数据的长度。只有当包是 “全大小(full-sized)” 包时, 才应该省略长度,来避免填充破坏的风险。

一个流帧必须总是要么具有非零的数据长度,要么设置了FIN位。

ACK帧

发送ACK帧以通知端已经接收了哪些分组,以及接收器仍然认为丢失了哪些分组(可能需要重新发送丢失分组的内容)。

QUIC的ACK帧的设计不同于TCP和SCTP的SACK表示,因为QUIC ACK指示到目前为止观察到的最大分组数, 其后是丢失分组列表,或NACK,范围指示在该分组号下面接收的分组中的间隙。

为了将NACK范围限制为尚未传送给对等体的NACK范围,端周期性地发送STOP_WAITING帧, 该信号通知接收器停止等待低于指定序列号的分组,从而提高接收端“最小未ack(least unacked)”分组号。

因此,ACK帧的发送方仅报告所接收的最小未被ack和所报告的最大观察分组号之间的那些NACK范围。 框架如下::

.    0         1                                  N
+--------+----------+---------------------------------------------------+
| Type   | Received |                   Largest Observed                |
| (8)    | Entropy  |                (8, 16, 32, or 48 bits)            |
+--------+----------+---------------------------------------------------+


    N+1      N+2 N+3 N+4 N+8
+--------+--------+------------+----------+--------------------------------+
|    Ack Delay    |    Num     | Delta    |        First Timestamp         |
|     Time (16)   | Timestamp  | Largest  |           (32 bits)            |
|                 |    (8)     | Observed |                                |
+--------+--------+------------+----------+--------------------------------+


     N+9          N+11 - X
+----------+-------------------+
| Delta    | Time Since        |
| Largest  | Previous Timestamp|  <-- Repeat (NumTimestamp - 1) times
| Observed |    (16 bits)      |
+----------+-------------------+


    X                        X+1 - Y                           Y+1
+--------+-------------------------------------------------+---------+
| Number |       Missing Packet Sequence Number Delta      | Range   |
| Ranges |             (8, 16, 32, or 48 bits)             | Length  |
| (opt)  |          (repeats Number Ranges times)          | (Repeat)|
+--------+-------------------------------------------------+---------+


    Y+2                        Y+3 - Z
+--------+-----------------------------------------------------+
| Number |               Revived Packet Number                 |
| Revived|  (8, 16, 32, or 48 bits, same as Largest Observed)  |
| (opt)  |            (repeats Number Revived times)           |
+--------+-----------------------------------------------------+

ACK帧中的字段如下:

  • 帧类型(Frame Type):帧类型字节是包含各种标志的8位值(01ntllmmB)。

    • 前两位必须设置为01,表示这是一个ACK帧。
    • 'n'位表示帧是否具有任何NACK范围。
    • 't'位指示ACK帧是否已被截断。 当完整的ACK帧不适合单个QUIC数据包, 或者当NACK范围的数量超过可报告的NACK范围的最大数量(255)时,可能发生截断。 截断时,ACK帧将最大观察到的数据包数量限制为可以报告的最大数据包,即使接收方可 能已收到数据包数量大于观察到的最大数据包的数据包。
    • 两个'll'位将最大观察字段(Largest Observed field)的长度编码为1,2,4或6字节长。
    • 两个“mm”位将缺失数据包序列号(Missing Packet Sequence Number) Delta字段的长度编码为1,2,4或6个字节长。
  • 接收熵(Received Entropy):一个8位无符号值,指定所有接收数据包中直到最 大观察数据包的熵的累积散列。 熵累积将在本节后面介绍。

  • 最大观察值(Largest Observed):可变大小的无符号值,表示对等方观察到的最 大数据包编号。 当截断ACK帧时,它指示大于所接收的指定最大观测数的分组数, 但是关于那些附加接收的信息不能填入该帧(通常由于分组大小限制)。

  • 确认延迟时间(Ack Delay Time):16位无符号浮点数,具有11个明确的尾数位和5位显式指数, 指定从接收到最大观测值到发送此Ack帧之间经过的时间(以微秒为单位)。 在IEEE 754之后,比特格式被宽松地建模。例如,1微秒表示为0x1,其指数为零, 以5个高阶位表示,尾数为1,以11个低阶位表示。 当显式指数大于零时,在尾数中 假定隐含的高阶12位为1。 例如,浮动值0x800的显式指数为1,显式尾数为0,但其 有效尾数为4096(假设第12位为1)。 此外,实际指数比显式指数小1,值表示4096微秒。 任何大于可表示范围的值都被钳制为0xFFFF。

  • 时间戳段(Timestamp Section):
    • 时间戳数(Num Timestamp): 一个8位无符号值,指定此ack帧中包含的时间戳数。 在时间戳中会有许多对<packet number,timestamp>。
    • 已观察最大差值(Delta Largest Observed):一个8位无符号值,指定从第一个时间戳到观察到的最大时间 戳的数据包数量差值。 因此,分组数是观察到的最大值减去观察到的最大值。
    • 第一个时间戳(First Timestamp):一个32位无符号值描述自由最大已观察包号描述的包的到达的连接的开始, 减去 已观察最大差值(Delta Largest Observed),所得到的时间差值的微秒数
    • 已观察最大差值(重复)(Delta Largest Observed(Repeated)):(同上。)
    • 自前一个时间戳的时间(重复)(Time Since Previous Timestamp (Repeated)):一个16位的无符号值 描述了与前一个时间戳的差值。它的编码格式与 确认延迟时间(Ack Delay Time)相同。
  • 丢失数据包段(Missing Packet Section:):
    • Num范围(Num Ranges):一个可选的8位无符号值,指定最大观察值和最小未 封装之间丢失数据包范围的数量。 仅在'n'标志位为1时才出现。
    • 缺少数据包序列号增量(Missing Packet Sequence Number Delta):可变大小的数据包数量增量。 对于第一个丢失的数据包范围,它是观察到的最大数据包的增量。 对于后续的nack范围,它是范围之间接 收的数据包数。 在第一个nack范围的情况下,值0指定报告为观察到的最大数据包丢失。 在后来的nack范围的情况下,值0表示丢失的分组范围是连续的(仅当一行中超过256个分组丢失时使用)。
    • 范围长度(Range Length):一个8位无符号值,指定的值小于该范围内的连续nack数。
  • 已恢复数据包部分:

    • Num Revived:一个8位无符号值,指定通过FEC恢复的已恢复数据包的数量。 就像Num Ranges字段一样,只有'n'标志位为1时才会出现此字段。
    • 恢复的分组序列号:可变大小的无符号值,表示对等体通过FEC恢复的分组。 其长度与“最大观察”字段的长度相同。 此列表中的所有数据包编号按升序排序(最小的第一个), 并且还必须存在于NACK范围列表中。

熵积累(Entropy Accumulation)

分组子集(对于接收器或发送器已知)的熵比特被累积为8比特无符号值,并且类似地在STOP_WAITING帧和ACK帧中呈现。 如果我们将E(k)定义为分组号k中存在的FLAG_ENTROPY比特,则第k个分组的贡献C(k)被定义为左移k mod 8比特的E(k)。 然后,对于期望子集中的所有分组,累积熵是贡献C(k)的按位XOR和。

STOP_WAITING 帧

STOP_WAITING 帧用于通知对端,它不应该继续等待包号小于特定值的包。 包号以1,2,4或6字节编码,使用与封闭数据包表头的数据包编号相同的编码长度 (在QUIC Frame Packet的Public Flags字段中指定。) 这个帧如下::

.    0        1        2        3         4       5       6       7
+--------+--------+--------+--------+--------+--------+-------+-------+
|Type (8)|Sent    |   Least unacked delta (8, 16, 32, or 48 bits)     |
|        |Entropy |                 (variable length)                 |
+--------+--------+--------+--------+--------+--------+--------+------+

STOP_WAITING帧中的字段如下:

  • 帧类型(Frame Type):帧类型是一个8位的值,它必须被设置为0x06以表明这是一个STOP_WAITING帧。
  • 发送熵(Sent Entropy):一个8位无符号值,指定所有已发送数据包中的熵的累积哈希值, 该数据包的数据包数量小于最小未ack的数据包。 [有关此计算的详细信息,请参阅“ACK帧”部分中的“熵累积”部分。]
  • 最小未确认差值(Least Unacked Delta):一个可变长度的包号差值,与包首部的包号长度相同。 将它从头部的包号减去以确定最小的未确认包。结果的最小未确认包是发送者依然在等待确认的包号最小的包。 如果接收者丢失了任何比这个值小的包,接收者应该将那些包认做无可挽回的丢失。

WINDOW_UPDATE 帧

WINDOW_UPDATE 帧用于通知对端一个端点的流量控制接收窗口的增长。 流ID可以是0,表示这个WINDOW_UPDATE应用于连接级的流量控制窗口, 或者 > 0 表示指定的流应该增长它的流量控制窗口。

帧如下:

指定一个完全的字节偏移量,WINDOW_UPDATE帧的接收者可以只在那个流上至多发送那个字节数。 发送更多字节而违背流量控制将导致接收端关闭连接。

为特定流ID收到多个WINDOW_UPDATE帧时,只需要追踪最大的字节偏移即可。

流和会话窗口都以一个默认值16KB开始,但是这个值典型地在握手期间增长。 为了做到这一点,端点应该在握手中协商 SFCW (Stream Flow Control Window) 和 CFCW (Connection/Session Flow Control Window) 参数。 与每个标记关联的值应该分别是初始流窗口和初始连接窗口的字节数。

帧如下::

0         1                 4        5                 12
+--------+--------+-- ... --+-------+--------+-- ... --+-------+
|Type(8) |    Stream ID (32 bits)   |  Byte offset (64 bits)   |
+--------+--------+-- ... --+-------+--------+-- ... --+-------+

WINDOW_UPDATE帧中的字段如下:

  • 帧类型:帧类型是一个8位值,它必须被设置为0x04以表示这是一个WINDOW_UPDATE帧。
  • 流 ID:要更新流控制窗口的流的ID,或者为0来描述连接级的流控制窗口。
  • 字节偏移: 一个64位无符号整型值,表示在给定的流上可以发送的数据的完整字节偏移量。 在连接级流量控制的情况下,是在当前所有打开的流上可以发送的字节的总和。

BLOCKED 帧

BLOCKED帧用于向远端指明本端点已经准备好发送数据了(且有数据要发送), 但是当前被流量控制阻塞了。这是一个纯粹的信息帧,它对于调试极其有用。 BLOCKED帧的接收者应该简单的丢弃它(可能在打印了一条有帮助的log消息之后)。 帧如下::

.    0        1        2        3         4
+--------+--------+--------+--------+--------+
|Type(8) |          Stream ID (32 bits)      |
+--------+--------+--------+--------+--------+

BLOCKED帧中的字段如下:

  • 帧类型:帧类型是一个8位值,它必须被设置为0x05以表示这是一个BLOCKED帧。
  • 流 ID:一个32位的无符号数,表示流量控制阻塞的流。非零 流 ID 字段描述了被流量控 制阻塞的流。当这个值为0时,流 ID字段在连接级指明连接被流量控制阻塞了。

CONGESTION_FEEDBACK 帧

CONGESTION_FEEDBACK帧是一个实验性的帧,当前未使用。 这个帧的本意是在ACK帧之外提供额外的拥塞反馈信息。 CONGESTION_FEEDBACK帧必须将表示帧类型的前三位设置为001,后5位预留将来使用。

PADDING 帧

PADDING帧使用0x00字节填充一个包。当遇到该帧时,包的剩余部分需要被填充字节。 该帧包含0x00字节并扩展至QUIC包的末端。PADDING帧只有一个帧类型字段,且必须将8位的帧类型字段设为0x00。

RST_STREAM 帧

RST_STREAM帧允许异常终止一条流。当这个帧是流的创建者发出的,表示创建者希望取消这条流。 当接收端发送这个帧,表示有错误或者当前接收端不希望接收这个流,因此这个流应该被关闭。帧结构如下::

.    0        1            4      5              12     8             16
+-------+--------+-- ... ----+--------+-- ... ------+-------+-- ... ------+
|Type(8)| StreamID (32 bits) | Byte offset (64 bits)| Error code (32 bits)|
+-------+--------+-- ... ----+--------+-- ... ------+-------+-- ... ------+

RST_STREAM帧的字段如下:

  • 帧类型: 帧类型是一个8位的值,必须设置为0x01表示这是一个RST_STREAM帧。
  • 流标识符: 32位流标识符,表示将被终止的流。
  • 字节偏移: 64位无符号整型表示流数据的绝对字节偏移。
  • 错误码: 32位的QUIC错误码表示流被关闭的原因,错误码在文档后续列出。

PING 帧

PING帧用来验证对端是否仍然存活。PING帧不包含载荷。 PING帧的接收方只需要应答(ACK)包含该帧的包。 PING帧应该被用于当一条流被打开时,保持连接存活。 默认是在15秒静默后发出PING帧,这比大多数NAT超时要短得多。 PING帧只有帧类型字段,且必须将8位的帧类型字段设为0x07。

CONNECTION_CLOSE 帧

CONNECTION_CLOSE帧用来通知连接将被关闭。如果流仍然有数据在发送,那么在连接关闭时, 这些流将被隐式关闭。(理论上一个GOAWAY帧应该已经被发送了足够的时间使所有流都关闭。) 帧结构如下::

.    0        1             4        5        6       7
+--------+--------+-- ... -----+--------+--------+--------+----- ...
|Type(8) | Error code (32 bits)| Reason phrase   |  Reason phrase
|        |                     | length (16 bits)|(variable length)
+--------+--------+-- ... -----+--------+--------+--------+----- ...

CONNECTION_CLOSE帧的字段如下:

  • 帧类型: 8位的值必须设置为0x02,表示这个帧是一个CONNECTION_CLOSE帧。
  • 错误码: 32位字段包含了QUIC错误码,表明连接关闭原因。
  • 原因描述长度: 16位无符号数,表示reason phrase的长度。如果发送方除了错误码之外, 并不打算给出详细情况,那么该字段可能为0。
  • 原因描述: 可选的可读的连接关闭的原因。

GOAWAY 帧

GOAWAY帧用来通知该连接将停止使用,将来将要被终止。 任何激活的流在收到GOAWAY帧后将继续被处理,但是GOAWAY帧的发送端将不再初始化任何额外的流, 也不会接受任何新的流。帧结构如下::

.    0        1             4      5       6       7      8
+--------+--------+-- ... -----+-------+-------+-------+------+
|Type(8) | Error code (32 bits)| Last Good Stream ID (32 bits)| ->
+--------+--------+-- ... -----+-------+-------+-------+------+

      9        10       11
+--------+--------+--------+----- ...
| Reason phrase   |  Reason phrase
| length (16 bits)|(variable length)
+--------+--------+--------+----- ...

GOAWAY帧字段如下:

  • 分片类型: 8位的值必须设置为0x03表示这个帧是一个GOAWAY帧。
  • 错误码: 32位字段包含QUIC错误码,表示关闭连接的原因。
  • 上一个好的流标识符: 上一个被GOAWAY发送端接收的流标识符。如果没有流可以用来回复,则这个值为0。
  • 原因描述长度: 16位无符号数,表示reason phrase的长度。如果发送方除了错误码之外,并不打算给出详细情况,那么该字段可能为0。
  • 原因描述: 可选的可读的连接关闭的原因。