4. ワイヤフォーマット (Wire Format)
4.1 プリミティブ (Primitives)
4.1.1 プレフィックス整数 (Prefixed Integers)
この文書は [RFC7541] のセクション 5.1 で定義されているプレフィックス整数を広範囲に使用します。[RFC7541] のフォーマットは変更なしで使用されます。ただし、QPACK は HPACK で実際には使用されていないプレフィックスサイズを使用することに注意してください。
QPACK の実装は 62 ビットまでの整数をデコードできなければなりません (MUST)。
4.1.2 文字列リテラル (String Literals)
[RFC7541] のセクション 5.2 で定義された文字列リテラルも、この文書全体で使用されます。この文字列フォーマットには、オプションの Huffman エンコーディングが含まれます。
HPACK は文字列リテラルがバイト境界から始まることを定義しています。それらは、この文書では 'H' と呼ばれる単一のビットフラグ (文字列が Huffman エンコードされているかどうかを示す) で始まり、7 ビットプレフィックス整数としてエンコードされた長さが続き、最後に指定されたバイト数のデータが続きます。Huffman エンコーディングが有効な場合、[RFC7541] の付録 B の Huffman テーブルが変更なしで使用され、長さはエンコードされた文字列のサイズを示します。
この文書は、文字列リテラルがバイト境界以外から始まることを許可することで、文字列リテラルの定義を拡張します。「N ビットプレフィックス文字列リテラル」は、バイトの途中から始まり、最初の (8-N) ビットは前のフィールドに割り当てられます。文字列は Huffman フラグに 1 ビットを使用し、(N-1) ビットプレフィックス整数としてエンコードされた長さが続きます。プレフィックスサイズ N の値は 2 から 8 の範囲 (両端を含む) です。文字列リテラルの残りの部分は変更されません。
プレフィックス長が記載されていない文字列リテラルは 8 ビットプレフィックス文字列リテラルであり、[RFC7541] の定義に従います。
4.2 エンコーダとデコーダのストリーム (Encoder and Decoder Streams)
QPACK は 2 つの単方向ストリームタイプを定義します:
-
エンコーダストリーム は、タイプ 0x02 の単方向ストリームです。エンコーダからデコーダへのエンコーダ命令のフレームなしシーケンスを運びます。
-
デコーダストリーム は、タイプ 0x03 の単方向ストリームです。デコーダからエンコーダへのデコーダ命令のフレームなしシーケンスを運びます。
HTTP/3 エンドポイントには QPACK エンコーダとデコーダが含まれます。各エンドポイントは、最大 1 つのエンコーダストリームと最大 1 つのデコーダストリームを開始しなければなりません (MUST)。いずれかのストリームタイプの 2 番目のインスタンスを受信した場合、H3_STREAM_CREATION_ERROR タイプの接続エラーとして扱わなければなりません (MUST)。
送信者はこれらのストリームのいずれかを閉じてはなりません (MUST NOT)。受信者は送信者にこれらのストリームのいずれかを閉じるよう要求してはなりません (MUST NOT)。いずれかの単方向ストリームタイプの閉鎖は、H3_CLOSED_CRITICAL_STREAM タイプの接続エラーとして扱わなければなりません (MUST)。
エンドポイントは、エンコーダストリームが使用されない場合 (たとえば、エンコーダが動的テーブルを使用したくない場合、またはピアが許可する動的テーブルの最大サイズがゼロの場合)、エンコーダストリームの作成を避けることができます (MAY)。
デコーダが動的テーブルの最大容量をゼロに設定する場合、エンドポイントはデコーダストリームの作成を避けることができます (MAY)。
接続の設定がエンコーダストリームとデコーダストリームの使用を妨げる場合でも、エンドポイントはピアがそれらを作成することを許可しなければなりません (MUST)。
4.3 エンコーダ命令 (Encoder Instructions)
エンコーダは、動的テーブルの容量を設定し、動的テーブルエントリを追加するために、エンコーダストリーム上でエンコーダ命令を送信します。テーブルエントリを追加する命令は、冗長な情報の送信を避けるために既存のエントリを使用できます。名前は、静的テーブルまたは動的テーブルの既存エントリへの参照として、または文字列リテラルとして送信できます。動的テーブルに既に存在するエントリの場合、完全なエントリを参照して使用することもでき、重複エントリを作成します。
4.3.1 動的テーブル容量の設定 (Set Dynamic Table Capacity)
エンコーダは、'001' 3 ビットパターンで始まる命令を使用して、動的テーブル容量の変更をデコーダに通知します。これに続いて、5 ビットプレフィックスを持つ整数として表される新しい動的テーブル容量が続きます。セクション 4.1.1 を参照してください。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | Capacity (5+) |
+---+---+---+-------------------+
図 5: 動的テーブル容量の設定
新しい容量は、セクション 3.2.3 で説明されている制限以下でなければなりません (MUST)。HTTP/3 では、この制限はデコーダから受信した SETTINGS_QPACK_MAX_TABLE_CAPACITY パラメータ (セクション 5) の値です。デコーダは、この制限を超える新しい動的テーブル容量値を QPACK_ENCODER_STREAM_ERROR タイプの接続エラーとして扱わなければなりません (MUST)。
動的テーブル容量を減らすと、エントリが削除される可能性があります。セクション 3.2.2 を参照してください。これにより、削除不可能なエントリが削除されてはなりません (MUST NOT)。セクション 2.1.1 を参照してください。動的テーブルの容量の変更は、エントリを挿入しないため、確認されません。
4.3.2 名前参照を使用した挿入 (Insert with Name Reference)
エンコーダは、'1' 1 ビットパターンで始まる命令を使用して、フィールド名が静的テーブルまたは動的テーブルに格納されているエントリのフィールド名と一致する動的テーブルにエントリを追加します。2 番目の ('T') ビットは、参照が静的テーブルへの参照か動的テーブルへの参照かを示します。続く 6 ビットプレフィックス整数 (セクション 4.1.1) は、フィールド名のテーブルエントリを特定するために使用されます。T=1 の場合、数値は静的テーブルインデックスを表します。T=0 の場合、数値は動的テーブル内のエントリの相対インデックスです。
フィールド名参照の後に、文字列リテラルとして表されるフィールド値が続きます。セクション 4.1.2 を参照してください。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 1 | T | Name Index (6+) |
+---+---+-----------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length bytes) |
+-------------------------------+
図 6: フィールド行の挿入 -- インデックス名
4.3.3 リテラル名を使用した挿入 (Insert with Literal Name)
エンコーダは、'01' 2 ビットパターンで始まる命令を使用して、フィールド名とフィールド値の両方が文字列リテラルとして表される動的テーブルにエントリを追加します。
これに続いて、6 ビットプレフィックス文字列リテラルとして表される名前と、8 ビットプレフィックス文字列リテラルとして表される値が続きます。セクション 4.1.2 を参照してください。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | H | Name Length (5+) |
+---+---+---+-------------------+
| Name String (Length bytes) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length bytes) |
+-------------------------------+
図 7: フィールド行の挿入 -- 新しい名前
4.3.4 複製 (Duplicate)
エンコーダは、'000' 3 ビットパターンで始まる命令を使用して、動的テーブル内の既存のエントリを複製します。これに続いて、5 ビットプレフィックスを持つ整数として表される既存エントリの相対インデックスが続きます。セクション 4.1.1 を参照してください。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | Index (5+) |
+---+---+---+-------------------+
図 8: 複製
既存のエントリは、名前も値も再送信せずに動的テーブルに再挿入されます。これは、古いエントリへの参照の追加を避けるのに役立ちます。これにより、新しいエントリの挿入がブロックされる可能性があります。
4.4 デコーダ命令 (Decoder Instructions)
デコーダは、フィールドセクションの処理とテーブル更新についてエンコーダに通知し、動的テーブルの一貫性を確保するために、デコーダストリーム上でデコーダ命令を送信します。
4.4.1 セクション確認応答 (Section Acknowledgment)
宣言された必要挿入カウントがゼロでないエンコードされたフィールドセクションを処理した後、デコーダはセクション確認応答命令を発行します。命令は '1' 1 ビットパターンで始まり、7 ビットプレフィックス整数としてエンコードされたフィールドセクションの関連ストリーム ID が続きます。セクション 4.1.1 を参照してください。
この命令は、セクション 2.1.4 およびセクション 2.2.2 で説明されているように使用されます。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 1 | Stream ID (7+) |
+---+---------------------------+
図 9: セクション確認応答
エンコーダが、ゼロ以外の必要挿入カウントを持つすべてのエンコードされたフィールドセクションがすでに確認されているストリームを参照するセクション確認応答命令を受信した場合、これは QPACK_DECODER_STREAM_ERROR タイプの接続エラーとして扱わなければなりません (MUST)。
セクション確認応答命令は、既知受信カウントを増加させる可能性があります。セクション 2.1.4 を参照してください。
4.4.2 ストリームキャンセル (Stream Cancellation)
ストリームがリセットされるか、読み取りが放棄されると、デコーダはストリームキャンセル命令を発行します。命令は '01' 2 ビットパターンで始まり、6 ビットプレフィックス整数としてエンコードされた影響を受けるストリームのストリーム ID が続きます。
この命令は、セクション 2.2.2 で説明されているように使用されます。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | Stream ID (6+) |
+---+---+-----------------------+
図 10: ストリームキャンセル
4.4.3 挿入カウント増分 (Insert Count Increment)
挿入カウント増分命令は '00' 2 ビットパターンで始まり、6 ビットプレフィックス整数としてエンコードされた増分が続きます。この命令は、既知受信カウント (セクション 2.1.4) を増分パラメータの値だけ増加させます。デコーダは、これまでに処理された動的テーブルの挿入と複製の合計数まで既知受信カウントを増加させる増分値を送信する必要があります。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | Increment (6+) |
+---+---+-----------------------+
図 11: 挿入カウント増分
ゼロに等しい増分フィールドを受信するか、既知受信カウントをエンコーダが送信した値を超えて増加させるエンコーダは、これを QPACK_DECODER_STREAM_ERROR タイプの接続エラーとして扱わなければなりません (MUST)。
4.5 フィールド行の表現 (Field Line Representations)
エンコードされたフィールドセクションは、プレフィックスとこのセクションで定義された空の可能性のある表現のシーケンスで構成されます。各表現は単一のフィールド行に対応します。これらの表現は、特定の状態の静的テーブルまたは動的テーブルを参照しますが、その状態を変更しません。
エンコードされたフィールドセクションは、囲んでいるプロトコルによって定義されたストリーム上のフレームで運ばれます。
4.5.1 エンコードされたフィールドセクションのプレフィックス (Encoded Field Section Prefix)
各エンコードされたフィールドセクションには、2 つの整数がプレフィックスとして付けられます。必要挿入カウントは、セクション 4.5.1.1 で説明されているエンコーディングを使用して 8 ビットプレフィックスを持つ整数としてエンコードされます。ベースは、符号ビット ('S') と 7 ビットプレフィックスを持つデルタベース値としてエンコードされます。セクション 4.5.1.2 を参照してください。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| Required Insert Count (8+) |
+---+---------------------------+
| S | Delta Base (7+) |
+---+---------------------------+
| Encoded Field Lines ...
+-------------------------------+
図 12: エンコードされたフィールドセクション
4.5.1.1 必要挿入カウント (Required Insert Count)
必要挿入カウントは、エンコードされたフィールドセクションを処理するために必要な動的テーブルの状態を識別します。ブロッキングデコーダは、必要挿入カウントを使用して、フィールドセクションの残りの部分を安全に処理できるタイミングを決定します。
エンコーダは、エンコードする前に必要挿入カウントを次のように変換します:
if ReqInsertCount == 0:
EncInsertCount = 0
else:
EncInsertCount = (ReqInsertCount mod (2 * MaxEntries)) + 1
ここで、MaxEntries は動的テーブルが持つことができる最大エントリ数です。最小のエントリは空の名前と値の文字列を持ち、サイズは 32 です。したがって、MaxEntries は次のように計算されます:
MaxEntries = floor( MaxTableCapacity / 32 )
MaxTableCapacity は、デコーダによって指定された動的テーブルの最大容量です。セクション 3.2.3 を参照してください。
このエンコーディングは、長期接続でのプレフィックスの長さを制限します。
デコーダは、次のようなアルゴリズムを使用して必要挿入カウントを再構築できます。デコーダが、準拠エンコーダによって生成できなかった EncodedInsertCount 値に遭遇した場合、これを QPACK_DECOMPRESSION_FAILED タイプの接続エラーとして扱わなければなりません (MUST)。
TotalNumberOfInserts は、デコーダの動的テーブルへの挿入の合計数です。
FullRange = 2 * MaxEntries
if EncodedInsertCount == 0:
ReqInsertCount = 0
else:
if EncodedInsertCount > FullRange:
Error
MaxValue = TotalNumberOfInserts + MaxEntries
# MaxWrapped は、2 * MaxEntries で割り切れる
# ReqInsertCount の最大可能値です
MaxWrapped = floor(MaxValue / FullRange) * FullRange
ReqInsertCount = MaxWrapped + EncodedInsertCount - 1
# ReqInsertCount が MaxValue を超える場合、エンコーダの値は
# 1 回少なくラップされている必要があります
if ReqInsertCount > MaxValue:
if ReqInsertCount <= FullRange:
Error
ReqInsertCount -= FullRange
# 0 の値は 0 としてエンコードする必要があります
if ReqInsertCount == 0:
Error
たとえば、動的テーブルが 100 バイトの場合、必要挿入カウントはモジュロ 6 でエンコードされます。デコーダが 10 回の挿入を受信した場合、エンコードされた値 4 は、フィールドセクションの必要挿入カウントが 9 であることを示します。
4.5.1.2 ベース (Base)
ベースは、セクション 3.2.5 で説明されているように、動的テーブル内の参照を解決するために使用されます。
スペースを節約するために、ベースは単一符号 ('S' 図 12 内) とデルタベース値を使用して必要挿入カウントに対して相対的にエンコードされます。符号ビット 0 は、ベースが必要挿入カウントの値以上であることを示します。デコーダは、デルタベースの値を必要挿入カウントに追加してベースの値を決定します。符号ビット 1 は、ベースが必要挿入カウント未満であることを示します。デコーダは、必要挿入カウントからデルタベースの値を減算し、さらに 1 を減算してベースの値を決定します。つまり:
if Sign == 0:
Base = ReqInsertCount + DeltaBase
else:
Base = ReqInsertCount - DeltaBase - 1
シングルパスエンコーダは、フィールドセクションをエンコードする前にベースを決定します。エンコーダがフィールドセクションをエンコードしながら動的テーブルにエントリを挿入してそれらを参照している場合、必要挿入カウントはベースよりも大きくなるため、エンコードされた差は負になり、符号ビットは 1 に設定されます。フィールドセクションがテーブル内の最新のエントリを参照する表現を使用してエンコードされておらず、新しいエントリが挿入されていない場合、ベースは必要挿入カウントよりも大きくなるため、エンコードされた差は正になり、符号ビットは 0 に設定されます。
ベースの値は負であってはなりません (MUST NOT)。プロトコルは負のベースとポストベースインデックスで正しく動作する可能性がありますが、これは不要で非効率的です。必要挿入カウントの値がデルタベースの値以下の場合、エンドポイントは符号ビット 1 のフィールドブロックを無効として扱わなければなりません (MUST)。
フィールドセクションをエンコードする前にテーブル更新を生成するエンコーダは、ベースを必要挿入カウントの値に設定する可能性があります。この場合、符号ビットとデルタベースの両方がゼロに設定されます。
動的テーブルへの参照なしでエンコードされたフィールドセクションは、ベースに任意の値を使用できます。デルタベースをゼロに設定することは、最も効率的なエンコーディングの 1 つです。
たとえば、必要挿入カウントが 9、符号ビットが 1、デルタベースが 2 の場合、ベースは 6 に設定され、3 つのエントリに対してポストベースインデックスが有効になります。この例では、相対インデックス 1 はテーブルに追加された 5 番目のエントリを参照します。ポストベースインデックス 1 は 8 番目のエントリを参照します。
4.5.2 インデックスされたフィールド行 (Indexed Field Line)
インデックスされたフィールド行表現は、静的テーブル内のエントリ、または絶対インデックスがベース値未満の動的テーブル内のエントリを識別します。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 1 | T | Index (6+) |
+---+---+-----------------------+
図 13: インデックスされたフィールド行
この表現は '1' 1 ビットパターンで始まり、参照が静的テーブルへの参照か動的テーブルへの参照かを示す 'T' ビットが続きます。続く 6 ビットプレフィックス整数 (セクション 4.1.1) は、フィールド行のテーブルエントリを特定するために使用されます。T=1 の場合、数値は静的テーブルインデックスを表します。T=0 の場合、数値は動的テーブル内のエントリの相対インデックスです。
4.5.3 ポストベースインデックス付きインデックスされたフィールド行 (Indexed Field Line with Post-Base Index)
ポストベースインデックス付きインデックスされたフィールド行表現は、絶対インデックスがベース値以上の動的テーブル内のエントリを識別します。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 1 | Index (4+) |
+---+---+---+---+---------------+
図 14: ポストベースインデックス付きインデックスされたフィールド行
この表現は '0001' 4 ビットパターンで始まります。これに続いて、4 ビットプレフィックスを持つ整数として表される一致するフィールド行のポストベースインデックス (セクション 3.2.6) が続きます。セクション 4.1.1 を参照してください。
4.5.4 名前参照付きリテラルフィールド行 (Literal Field Line with Name Reference)
名前参照付きリテラルフィールド行表現は、フィールド名が静的テーブル内のエントリのフィールド名、または絶対インデックスがベース値未満の動的テーブル内のエントリのフィールド名と一致するフィールド行をエンコードします。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 1 | N | T |Name Index (4+)|
+---+---+---+---+---------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length bytes) |
+-------------------------------+
図 15: 名前参照付きリテラルフィールド行
この表現は '01' 2 ビットパターンで始まります。続くビット 'N' は、仲介者が後続のホップでこのフィールド行を動的テーブルに追加することが許可されているかどうかを示します。'N' ビットが設定されている場合、エンコードされたフィールド行は常にリテラル表現を使用してエンコードされなければなりません (MUST)。特に、ピアが 'N' ビットが設定されたリテラルフィールド行として表されたフィールド行を受信した場合、このフィールド行を転送するためにリテラル表現を使用しなければなりません (MUST)。このビットは、圧縮することでリスクにさらされたくないフィールド値を保護することを目的としています。詳細については、セクション 7.1 を参照してください。
4 番目の ('T') ビットは、参照が静的テーブルへの参照か動的テーブルへの参照かを示します。続く 4 ビットプレフィックス整数 (セクション 4.1.1) は、フィールド名のテーブルエントリを特定するために使用されます。T=1 の場合、数値は静的テーブルインデックスを表します。T=0 の場合、数値は動的テーブル内のエントリの相対インデックスです。
動的テーブルエントリからはフィールド名のみが取得されます。フィールド値は 8 ビットプレフィックス文字列リテラルとしてエンコードされます。セクション 4.1.2 を参照してください。
4.5.5 ポストベース名前参照付きリテラルフィールド行 (Literal Field Line with Post-Base Name Reference)
ポストベース名前参照付きリテラルフィールド行表現は、フィールド名が絶対インデックスがベース値以上の動的テーブルエントリのフィールド名と一致するフィールド行をエンコードします。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 0 | 0 | N |NameIdx(3+)|
+---+---+---+---+---+-----------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length bytes) |
+-------------------------------+
図 16: ポストベース名前参照付きリテラルフィールド行
この表現は '0000' 4 ビットパターンで始まります。5 番目のビットは、セクション 4.5.4 で説明されている 'N' ビットです。これに続いて、3 ビットプレフィックスを持つ整数としてエンコードされた動的テーブルエントリのポストベースインデックス (セクション 3.2.6) が続きます。セクション 4.1.1 を参照してください。
動的テーブルエントリからはフィールド名のみが取得されます。フィールド値は 8 ビットプレフィックス文字列リテラルとしてエンコードされます。セクション 4.1.2 を参照してください。
4.5.6 リテラル名前付きリテラルフィールド行 (Literal Field Line with Literal Name)
リテラル名前付きリテラルフィールド行表現は、フィールド名とフィールド値を文字列リテラルとしてエンコードします。
0 1 2 3 4 5 6 7
+---+---+---+---+---+---+---+---+
| 0 | 0 | 1 | N | H |NameLen(3+)|
+---+---+---+---+---+-----------+
| Name String (Length bytes) |
+---+---------------------------+
| H | Value Length (7+) |
+---+---------------------------+
| Value String (Length bytes) |
+-------------------------------+
図 17: リテラル名前付きリテラルフィールド行
この表現は '001' 3 ビットパターンで始まります。4 番目のビットは、セクション 4.5.4 で説明されている 'N' ビットです。名前が続き、4 ビットプレフィックス文字列リテラルとして表され、次に値が続き、8 ビットプレフィックス文字列リテラルとして表されます。セクション 4.1.2 を参照してください。