5. Data Framing (データフレーミング)
WebSocketプロトコルは、データを送信するためにフレーム (Frame) を使用します。本章では、WebSocketフレームの形式と処理ルールを定義します。
5.1 Overview (概要)
WebSocket接続が確立されると、クライアントとサーバーは双方向にデータを送信できます。データは一連のフレームの形式で送信されます。
主要概念:
- フレーム (Frame): 送信の基本単位で、ヘッダーとペイロードを含む
- メッセージ (Message): アプリケーションレベルのデータで、1つ以上のフレームで構成される可能性がある
- フラグメンテーション (Fragmentation): 大きなメッセージを複数のフレームに分けて送信できる
5.2 Base Framing Protocol (基本フレーミングプロトコル)
フレーム構造
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len | Extended payload length |
|I|S|S|S| (4) |A| (7) | (16/64) |
|N|V|V|V| |S| | (if payload len==126/127) |
| |1|2|3| |K| | |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
| Extended payload length continued, if payload len == 127 |
+ - - - - - - - - - - - - - - - +-------------------------------+
| |Masking-key, if MASK set to 1 |
+-------------------------------+-------------------------------+
| Masking-key (continued) | Payload Data |
+-------------------------------- - - - - - - - - - - - - - - - +
: Payload Data continued ... :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
| Payload Data continued ... |
+---------------------------------------------------------------+
フィールドの説明
FIN (1ビット)
0: これはメッセージの最後のフレームではない(後続フレームあり)1: これはメッセージの最後のフレーム(または唯一のフレーム)
メッセージフラグメンテーションの例:
フレーム1: FIN=0, Opcode=0x1 (Text), Data="Hello "
フレーム2: FIN=0, Opcode=0x0 (Continuation), Data="World"
フレーム3: FIN=1, Opcode=0x0 (Continuation), Data="!"
完全なメッセージ: "Hello World!"
RSV1, RSV2, RSV3 (各1ビット)
- 拡張用に予約されている
- 拡張がネゴシエートされていない場合、0でなければならない (MUST)
- 定義された拡張なしで非ゼロ値を受信した場合、接続をクローズしなければならない (MUST)
Opcode (4ビット)
フレームのタイプを定義:
| Opcode | タイプ | 説明 |
|---|---|---|
0x0 | Continuation | 継続フレーム(フラグメント化されたメッセージの後続フレーム) |
0x1 | Text | テキストフレーム(UTF-8エンコード) |
0x2 | Binary | バイナリフレーム |
0x3-0x7 | - | 予約済み(データフレーム) |
0x8 | Close | クローズフレーム |
0x9 | Ping | Pingフレーム |
0xA | Pong | Pongフレーム |
0xB-0xF | - | 予約済み(制御フレーム) |
MASK (1ビット)
- クライアントからサーバー: 1でなければならない (MUST)
- サーバーからクライアント: 0でなければならない (MUST)
MASK=1の場合、Payload DataはMasking-keyを使用してマスクされなければなりません。
Payload Length (7ビット、7+16ビット、または7+64ビット)
ペイロード長のエンコーディング:
- 0-125: これが実際の長さ
- 126: 後続の16ビット(2バイト)が実際の長さ(ネットワークバイトオーダー)
- 127: 後続の64ビット(8バイト)が実際の長さ(ネットワークバイトオーダー)
例:
ペイロード長 = 100バイト
→ Payload len = 100 (直接エンコード)
ペイロード長 = 1000バイト
→ Payload len = 126
→ Extended payload length = 1000 (16ビット)
ペイロード長 = 100000バイト
→ Payload len = 127
→ Extended payload length = 100000 (64ビット)
Masking-key (0または4バイト)
MASK=1の場合、32ビット(4バイト)のマスキングキーを含みます。
Payload Data (x+yバイト)
ペイロードデータ = Extension Data + Application Data
- Extension Data: 長さx、拡張ネゴシエーションによって決定、デフォルトは0
- Application Data: 長さy、実際のアプリケーションデータ
5.3 Client-to-Server Masking (クライアントからサーバーへのマスキング)
なぜマスキングが必要か?
セキュリティ上の理由: キャッシュポイズニング攻撃 (Cache Poisoning Attack) を防ぐため。一部の中間プロキシがWebSocketフレームを誤ってキャッシュする可能性があり、マスキングによりデータの予測不可能性を確保します。
マスキングアルゴリズム
クライアントは、サーバーに送信するすべてのフレームを以下のアルゴリズムでマスクしなければなりません (MUST):
1. 32ビットのランダムなマスキングキーを生成
2. マスキングキーをフレームヘッダーのMasking-keyフィールドに配置
3. Payload Dataの各バイトにマスクを適用:
transformed-octet-i = original-octet-i XOR masking-key[i MOD 4]
アルゴリズム実装 (JavaScript):
function maskData(data, maskingKey) {
const masked = new Uint8Array(data.length);
for (let i = 0; i < data.length; i++) {
masked[i] = data[i] ^ maskingKey[i % 4];
}
return masked;
}
// 例
const data = Buffer.from('Hello');
const maskingKey = Buffer.from([0x37, 0xfa, 0x21, 0x3d]);
const masked = maskData(data, maskingKey);
// デコード(同じアルゴリズムを使用)
const unmasked = maskData(masked, maskingKey); // 'Hello'
重要なポイント:
- XORは自己逆演算:
(A XOR B) XOR B = A - サーバーは同じアルゴリズムを使用してデコード
- 送信するフレームごとに新しいランダムキーを使用しなければならない
5.4 Fragmentation (フラグメンテーション)
大きなメッセージは複数のフレームに分けて送信できます。
フラグメンテーションルール
- 最初のフレーム: FIN=0, Opcode=データタイプ (0x1または0x2)
- 中間フレーム: FIN=0, Opcode=0x0 (Continuation)
- 最後のフレーム: FIN=1, Opcode=0x0 (Continuation)
フラグメンテーションの例
メッセージ "Hello World!" を3つのフレームで送信:
フレーム1:
FIN = 0
Opcode = 0x1 (Text)
Payload = "Hello "
フレーム2:
FIN = 0
Opcode = 0x0 (Continuation)
Payload = "World"
フレーム3:
FIN = 1
Opcode = 0x0 (Continuation)
Payload = "!"
フラグメンテーションの制約
- 制御フレーム (Close、Ping、Pong) はフラグメント化してはならない (MUST NOT)
- 制御フレームはフラグメント化されたデータフレームの間に挿入できる
- フラグメントは順序通りに送受信しなければならない
5.5 Control Frames (制御フレーム)
制御フレームは接続状態を通信するために使用されます。Opcodeの範囲: 0x8-0xF。
5.5.1 Close (クローズフレーム)
- Opcode:
0x8 - クローズコードと理由を含むことができる
- 詳細は第7章を参照
5.5.2 Ping (Pingフレーム)
- Opcode:
0x9 - 用途: ハートビート検出、接続が生きているかチェック
- アプリケーションデータを運ぶことができる(最大125バイト)
- 受信者はPongフレームで応答しなければならない (MUST)
クライアント → サーバー: Ping (Opcode=0x9)
サーバー → クライアント: Pong (Opcode=0xA, 同じPayload)
5.5.3 Pong (Pongフレーム)
- Opcode:
0xA - 用途: Pingフレームに応答
- Pingフレームと同じPayloadを含まなければならない (MUST)
- 自発的に送信することもできる(一方向ハートビート)
制御フレームのルール
- 最大Payload長: 125バイト
- フラグメント化してはならない (MUST NOT)
- フラグメント化されたデータフレームの間に挿入できる
5.6 Data Frames (データフレーム)
データフレームはアプリケーションまたは拡張データを送信します。Opcodeの範囲: 0x0-0x2、0x3-0x7は予約済み。
Text Frame (テキストフレーム)
- Opcode:
0x1 - Payloadは有効なUTF-8エンコードテキストでなければならない (MUST)
- 無効なUTF-8を受信した場合、接続をクローズしなければならない (MUST)
Binary Frame (バイナリフレーム)
- Opcode:
0x2 - Payloadは任意のバイナリデータ
- アプリケーション層が解釈を担当
5.7 Examples (例)
単一のマスクなしテキストフレーム
0x81 0x05 0x48 0x65 0x6c 0x6c 0x6f
│ │ └─────────┬────────────┘
│ │ └─ "Hello" (5バイト)
│ └─ Payload len = 5
└─ FIN=1, Opcode=0x1 (Text)
単一のマスク付きテキストフレーム
0x81 0x85 0x37 0xfa 0x21 0x3d 0x7f 0x9f 0x4d 0x51 0x58
│ │ └─────┬────────┘ └──────┬───────────┘
│ │ │ └─ マスクされた "Hello"
│ │ └─ マスキングキー
│ └─ MASK=1, Payload len = 5
└─ FIN=1, Opcode=0x1 (Text)
フラグメント化されたメッセージ
フレーム1: 0x01 0x03 0x48 0x65 0x6c // FIN=0, Text, "Hel"
フレーム2: 0x80 0x02 0x6c 0x6f // FIN=1, Continuation, "lo"
完全なメッセージ: "Hello"
Pingフレーム
0x89 0x05 0x48 0x65 0x6c 0x6c 0x6f
│ │ └─────────┬────────────┘
│ │ └─ "Hello" (オプションデータ)
│ └─ Payload len = 5
└─ FIN=1, Opcode=0x9 (Ping)
5.8 Extensibility (拡張性)
プロトコルは以下の方法で拡張できます:
- Opcode: 0x3-0x7および0xB-0xFは将来の使用のために予約されている
- RSVビット: 拡張の使用のために予約されている
- Extension Data: 拡張はPayloadの前にデータを追加できる
拡張はハンドシェイク(Sec-WebSocket-Extensions)を通じてネゴシエートされなければなりません。
参考リンク
- 前の章: 4. Opening Handshake (オープニングハンドシェイク)
- 次の章: 6. Sending and Receiving Data (データの送受信)
- 詳細説明: WebSocketフレーム構造の詳細