[53] [DFN[[RUBYB[フレーム]@en[frame]]]]は、[[WebSocket接続の確立]]の後に
[[WebSocketサーバー]]と[[WebSocketクライアント]]との間でやり取りされる情報の単位です。

* 仕様書

[REFS[
- [1] '''[CITE@en[RFC 6455 - The WebSocket Protocol]] ([TIME[2015-03-11 20:42:50 +09:00]] 版) <https://tools.ietf.org/html/rfc6455#section-5>'''
-- [49] [CITE@en[RFC 6455 - The WebSocket Protocol]] ([TIME[2015-03-11 20:42:50 +09:00]] 版) <https://tools.ietf.org/html/rfc6455#section-5.8>
- [43] [CITE@en[RFC 6455 - The WebSocket Protocol]] ([TIME[2015-03-11 20:42:50 +09:00]] 版) <https://tools.ietf.org/html/rfc6455#section-10.3>
- [61] [CITE@en[RFC 6455 - The WebSocket Protocol]] ([TIME[2015-03-11 20:42:50 +09:00]] 版) <https://tools.ietf.org/html/rfc6455#section-11.8>
- [65] [CITE[WebSocket Protocol Registries]] ([TIME[2015-03-06 13:09:47 +09:00]] 版) <https://www.iana.org/assignments/websocket/websocket.xml#opcode>
]REFS]

* 構文

[8] [[フレーム]]は、次のような構造です [SRC[>>1]]。
可変長の[[ヘッダー]]と、可変長のデータで構成されます。
[[ヘッダー]]の長さとデータの長さは、どちらも[[ヘッダー]]の値により決まります。

[FIG(packet)[
:width:32

=1 [[FIN]]
=1 [[RSV1]]
=1 [[RSV2]]
=1 [[RSV3]]
=4 [[opcode]]
=1 [[マスク]]
=23 [[payload長]]
=32 [[マスクキー]]
=32... [[Payload Data]]
]FIG]

[FIG(list members)[
[FIGCAPTION[
[[フレーム]]
]FIGCAPTION]

:[DFN[[CODE[[[FIN]]]]]]:
[[メッセージ]]の最後の[RUBYB[断片]@en[fragment]]であるかどうかを示します。
[CODE[[[0]]]] は最後でないこと、 [CODE[[[1]]]] は最後であることを表します。
: [F[RSV1]] :
[[拡張][WebSocket拡張]]により規定される場合を除き、 [N[0]] でなければ[MUST[なりません]]。
: [F[RSV2]] :
[[拡張][WebSocket拡張]]により規定される場合を除き、 [N[0]] でなければ[MUST[なりません]]。
: [F[RSV3]] :
[[拡張][WebSocket拡張]]により規定される場合を除き、 [N[0]] でなければ[MUST[なりません]]。
:[DFN[[CODE[[[opcode]]]]]]:
[[payload data]] の解釈を規定するものです。
:[DFN[[RUBYB[マスク]@en[mask]]]] ([DFN[[CODE[[[frame-masking-key]]]]]]):
[[payload data]] が[[マスク]]されるかどうかを示します。
[CODE[[[1]]]] は[[マスク]]されていること、[CODE[[[0]]]] は[[マスク]]されていないことを示します。
:[DFN[[RUBYB[payload長]@en[payload length]]]]:
[[payload data]] の長さを[[バイト]]単位で示します。
:[DFN[[RUBYB[[[マスクキー]]]@en[masking key]]]] ([DFN[[CODE[[[frame-masking-key]]]]]]):
[[マスク]]する際に使う32ビットの値です。[[マスク]]ビットが [CODE[[[1]]]]
であれば、本欄が存在します。 [CODE[[[0]]]] なら、存在しません。
: [F[拡張データ]] :
[[拡張]]により規定された長さと用途のデータです。[[拡張]]がなければ長さ0です。
:[DFN[[RUBYB[応用データ]@en[application data]]]]:
任意の[[応用]]のデータです。
]FIG]

* opcode

[14] [[データフレーム]] ([[非制御フレーム]]) の [[opcode]] には、
次のものがあります [SRC[>>1]]。
[FIG(list short)[
- [CODE[[[0]]]] - [[継続フレーム]]
- [CODE[[[1]]]] - [[テキストフレーム]]
- [CODE[[[2]]]] - [[バイナリーフレーム]]
- [CODE[[[3]]]] - 非制御フレームに予約
- [CODE[[[4]]]] - 非制御フレームに予約
- [CODE[[[5]]]] - 非制御フレームに予約
- [CODE[[[6]]]] - 非制御フレームに予約
- [CODE[[[7]]]] - 非制御フレームに予約
]FIG]

[44] [DFN[[RUBYB[[[制御フレーム]]]@en[control frame]]]]の [[opcode]] には、次のものがあります [SRC[>>1]]。
[FIG(list short)[
- [CODE[[[8]]]] - [[Closeフレーム]]
- [CODE[[[9]]]] - [[Pingフレーム]]
- [CODE[[[A]]]] - [[Pongフレーム]]
- [CODE[[[B]]]] - 制御フレームに予約
- [CODE[[[C]]]] - 制御フレームに予約
- [CODE[[[D]]]] - 制御フレームに予約
- [CODE[[[E]]]] - 制御フレームに予約
- [CODE[[[F]]]] - 制御フレームに予約
]FIG]

[45] [[制御フレーム]]は、状態を通信するために使うものです [SRC[>>1]]。

[51] 予約された [CODE[[[opcode]]]] は、[[拡張][WebSocket拡張]]が使うことができます 
[SRC[>>49]]。

[62] [[opcode]] には[[IANA登録簿]]があります [SRC[>>61, >>65]]。

[10] 未知の [F[opcode]] の[[フレーム][WebSocketフレーム]]を受信した場合
[[WebSocket接続失敗]]としなければ[MUST[なりません]] [SRC[>>1]]。


* payload

[18] [DFN[[RUBYB[payloadデータ]@en[payload data]]]]は、
[[拡張データ]]と[[応用データ]]を指します [SRC[>>1]]。

[47] [[payload長]]は、[[バイト]]単位の[[payloadデータ]]の長さを表すものです。
次のうち、最短の形を使わなければ[['''なりません''']]。 [SRC[>>1]]
[FIG(list)[
- [15] [CODE[0]]-[CODE[125]] なら、これが [[payload長]]です。
- [16] [CODE[126]] なら、次の2バイトが[[payload長]]の[[16ビット符号無し整数]]
([[ネットワークバイト順]]) です。
- [17] [CODE[127]] なら、次の8バイトが[[payload長]]の[[64ビット符号無し整数]]
([[ネットワークバイト順]]) です。
ただし[[最上位ビット]]は [CODE[0]] でなければ[['''なりません''']]。
]FIG]

[54] 従って最大長は2[SUP[63]]-1バイトです。 (つまり 8388608 [[TB]] [[未満]]まで表現できます。)

[46] [[制御フレーム]]の [[payload長]]は、125バイト[[以下]]でなければ[['''なりません''']] [SRC[>>1]]。

[50] これらの規定に違反した[[フレーム]]を受信した時にどう処理するべきかは不明です。

[56] 最短形でない場合、
[[Chrome]] は[[状態符号]]が [CODE[[[1002]]]] で理由が [CODE[WebSocket Protocol Error]]
の [[Closeフレーム]]を送信します。 [[Firefox]] は16ビット整数なら最短形でなくてもよく、
64ビット整数なら送信しないで接続を閉じます。
[TIME[2015-08-16T13:59:43.200Z]]

[57] 値が 2[SUP[32]]-1 [[以下]]なら、 [[Chrome]] も [[Firefox]] も受信しようと試みるようです。
しかし [[Chrome]] は受信し続けた末にクラッシュ、 [[Firefox]] は途中で断念して [CODE[[[RST]]]]
するようです。 2[SUP[32]] [[以上]]なら (64ビット整数で最上位ビットが立っている場合も)、
[[Chrome]] は[[コンソール]]に [CODE[Invalid frame header]] と表示して
([CODE(DOMe)@en[[[error]]]] イベントは無し) すぐに切断します。
[[Firefox]] は [CODE(DOMe)@en[[[error]]]] イベントを[[発火]]してすぐに[[切断]]します。 [TIME[2015-08-16T14:29:01.600Z]]

[58] [[制御フレーム]]なのに長さが125を超えていると、
[[Chrome]] も [[Firefox]] も [CODE(DOMe)@en[[[error]]]] [[イベント]]を[[発火]]し、
[[Chrome]] は [[Closeフレーム]]を送信 ([CODE[[[1002]]]] [CODE[[[WebSocket Protocol Error]]]])、
[[Firefox]] は[[接続]]を閉じます。 [TIME[2015-08-16T14:40:37.500Z]]

[55] まとめると、[[フレーム]]の長さの決定は次のように行えます。
[FIG(steps)[
= 次の2バイトを取得します。
= 長さを、取得した2バイト目の下位7ビットとします。
= 長さが 126 なら、
== 次の2バイトを取得します。
== 長さを、ネットワークバイト順の16ビット符号無し整数として解釈した値に設定します。
== 長さが 126 [[未満]]なら、[[WebSocket接続失敗]]とします。
= 長さが 127 なら、
== 次の8バイトを取得します。
== 長さを、ネットワークバイト順の64ビット符号無し整数として解釈した値に設定します。
== 長さが 2[SUP[16]] [[未満]]なら、 [[WebSocket接続失敗]]とします。
= 長さが2[SUP[63]]以上なら、 [[WebSocket接続失敗]]とします。
= 長さが実装の扱える範囲を超えていれば、 [[WebSocket接続失敗]]とします。
= 制御フレームで、長さが125バイトを超えているなら、
== [[WebSocket接続失敗]]とします。
= 2バイト目の最上位ビットが立っていれば、
== 次の32ビットを取得します。
]FIG]

* マスク

[19] [[フレーム]]は、[DFN[[RUBYB[[[マスク]]]@en[mask]]]]する場合と[[マスク]]しない場合があります。

[20] [[マスク]]した[[フレーム]]は、[[マスク]]欄の値が [CODE[[[1]]]] でなければ[['''なりません''']]
[SRC[>>1]]。

[21] [RUBYB[マスクキー]@en[masking key]]は、[[クライアント]]が[[無作為]]に選択した32ビット値です。
[[マスクキー]]は、[[マスク]]した[[フレーム]]内の[[マスクキー]]欄に示されます。 [SRC[>>1]]

[2] [[クライアント]]は、[[サーバー]]に送信するすべての[[フレーム]]を[[マスク]]しなければ[['''なりません''']] [SRC[>>1]]。

[5] [[サーバー]]は、[[クライアント]]に[[マスク]]した[[フレーム]]を送信しては[['''なりません''']]
[SRC[>>1]]。

[23] [[マスク]]する際に新鮮な値を[[マスクキー]]として選ばなければ[['''なりません''']] [SRC[>>1]]。
[[マスクキー]]は予測不能である必要がありますから、
強い[[エントロピー]]源から得なければ[['''なりません''']] [SRC[>>1]]。
前の[[フレーム]]から以後の[[フレーム]]の[[マスクキー]]を容易に予測できては[['''なりません''']]
[SRC[>>1]]。

;; [3] これは、[[プロキシ]]等の[[中間器]]の混乱を防ぐため、また[[セキュリティー]]のために必要な措置です
[SRC[>>1, >>43]]。

;; [22] [[RFC 4086]] に強い[[エントロピー]]源についての議論があります [SRC[>>1]]。

[24] [[マスク]]の適用と復元は、どちらも次の手順により行います [SRC[>>1]]。

[FIG(steps)[
= [25] 入力データの各[[オクテット]]について、
== [26] 出力の[[オクテット]] [VAR[i]] は、入力のオクテット [VAR[i]] と[[マスクデータ]]のオクテット [VAR[i]] [[mod]] 4 の [[XOR]] とします。
]FIG]

[4] [[サーバー]]は、[[マスク]]されていない[[フレーム]]を受信したら、
[[接続]]を閉じなければ[['''なりません''']] [SRC[>>1]]。
この場合[[サーバー]]は [CODE(HTTP)@en[[[Close]]]] [[フレーム]]を[[状態符号]]
[CODE(HTTP)[[[1002]]]] で送信して構いません [SRC[>>1]]。

[6] [[クライアント]]は、[[マスク]]された[[フレーム]]を受信したら、
[[接続]]を閉じなければ[['''なりません''']] [SRC[>>1]]。
この場合[[クライアント]]は [CODE(HTTP)@en[[[Close]]]] [[フレーム]]を[[状態符号]]
[CODE(HTTP)[[[1002]]]] で送信して構いません [SRC[>>1]]。

[68] [[Microsoft]] は、 [[Web]] ではなく公衆インターネットでもない限られた環境では[[マスク]]しないことでパフォーマンスが向上するとして、
[[マスクキー]]を 0 とすることによりデータを[[マスク]]しないで送信するオプションを実装しています
[SRC[>>67]]。

[REFS[
- [67] [CITE@en['''['''MS-WSPE''']''': WebSocket Protocol Extensions]] ([TIME[2015-05-17 00:10:16 +09:00]] 版) <https://msdn.microsoft.com/en-us/library/hh553781.aspx>
]REFS]

* 断片化

[27] [DFN[[RUBYB[断片化]@en[fragmentation]]]]により、
送信開始時点で送信データの大きさがわからない場合でも送信開始できます [SRC[>>1]]。

;; [28] [[断片化]]を使って、送信データの末端を待たずに適度にバッファーにデータが溜まった時点で送信できます [SRC[>>1]]。

[29] [[断片化]]は[RUBYB[[[多重化]]]@en[multiplexing]]により単一の[[WebSocket接続]]を複数の大きなメッセージで共有するために使うことができます。
ただし [[WebSocket]] 本体では[[多重化]]のための拡張は規定していません。 [SRC[>>1]]

[48] [[断片化]]された(かもしれない)状態のものが[[フレーム]]と呼ばれるのに対し、
そのデータを結合したものが[DFN[[RUBYB[[[メッセージ]]]@en[message]]]]
[SRC[>>1]] と呼ばれています。

[39] 受信者は[[断片化]]に対応しなければ[['''なりません''']] [SRC[>>1]]。

[31] 1つのメッセージが単一[[フレーム]]で表現される時は、
[CODE[[[FIN]]]] が [CODE[1]] に設定され [CODE[[[opcode]]]] は [CODE[0]] 以外となります
[SRC[>>1]]。

[FIG(railroad)[
= ([CODE[[[FIN]]]] = [CODE[[[1]]]], [CODE[[[opcode]]]] ≠ [CODE[[[0]]]])
]FIG]

[32] 1つのメッセージが複数[[フレーム]]で表現される時は、
[CODE[[[FIN]]]] が [CODE[0]] に設定され [CODE[[[opcode]]]] は [CODE[0]] 以外となる[[フレーム]]、
0個以上の [CODE[[[FIN]]]] が [CODE[[[0]]]] に設定され [CODE[[[opcode]]]] が [CODE[[[0]]]]
の[[フレーム]]、
[CODE[[[FIN]]]] が [CODE[[[0]]]] に設定され [CODE[[[opcode]]]] が [CODE[[[0]]]]
のフレームの列となります [SRC[>>1]]。

[FIG(railroad)[
= ([CODE[[[FIN]]]] = [CODE[[[0]]]], [CODE[[[opcode]]]] ≠ [CODE[[[0]]]])
= *
== ([CODE[[[FIN]]]] = [CODE[[[0]]]], [CODE[[[opcode]]]] = [CODE[[[0]]]])
= ([CODE[[[FIN]]]] = [CODE[[[1]]]], [CODE[[[opcode]]]] = [CODE[[[0]]]])
]FIG]

[33] [[制御フレーム]]は、断片化しては[['''なりません''']] [SRC[>>1]]。
[[中間器]]は[[制御フレーム]]の断片化状態を変更しては[['''なりません''']] [SRC[>>1]]。

[59] [[制御フレーム]]の [CODE[[[FIN]]]] が 0 の場合には、
[[Chrome]] でも [[Firefox]] でも[[接続]]が閉じられます。
[[Chrome]] は[[状態符号]]が [CODE[[[1002]]]] で理由が
[CODE[WebSocket Protocol Error]] の[[Closeフレーム]]を送信します。 [TIME[2015-08-17T07:12:56.800Z]]

[35] 断片は、送信者が送信した順序で受信者に配送しなければ[['''なりません''']] [SRC[>>1]]。

[34] [[制御フレーム]]を他の断片化された[[メッセージ]]の[[フレーム]]間に注入しても構いません
[SRC[>>1]]。受信者はこれを扱えなければ[['''なりません''']] [SRC[>>1]]。

;; [42] 大きなメッセージの転送中に [[ping]] の遅延が大きくなることを防ぐためです [SRC[>>1]]。

[36] [[拡張]]により特に規定される場合を除き、
断片を他の断片化された[[メッセージ]]の[[フレーム]]間に注入しては[['''なりません''']]
[SRC[>>1]]。

;; [52] これら禁止規定に反した場合にどう処理するべきかは不明です。

[30] [[拡張]]で特に規定される場合を除き、[[フレーム]] (の境界) は意味を持ちません。
1つの大きなメッセージも、複数に分割された[[フレーム]]を順に連結したものも、等価です。
送信者は任意の大きさに分割できます。 [SRC[>>1]]

[37] [[中間器]]は、[[拡張]]が適用されない場合や、
すべての適用される[[拡張]]を理解し問題ないと判断できる場合には、
[[フレーム]]を分割したり結合したりするかもしれません。 [SRC[>>1]]
[CODE[[[RSV1]]]], [CODE[[[RSV2]]]], [CODE[[[RSV3]]]]
のいずれかが設定されているか[[拡張]]が用いられていて、その意味を理解できなければ、
[[断片化]]の状態を変更しては[['''なりません''']] [SRC[>>1]]。

[40] [[WebSocket handshake]] を見ていない[[中間器]]は、
[[断片化]]の状態を変更しては[['''なりません''']] [SRC[>>1]]。

;; [41] どういう状況でしょうか? [[WebSocket接続の確立]]とそれ以後の処理を別の[[中間器]]で行う場合??

[38] [[拡張]]によっては、各[[フレーム]]にのみ[[拡張データ]]を含められる、
最初の[[フレーム]]にのみ含められるといったような規定があるかもしれません。 [SRC[>>1]]

* 受信

[69] [[WebSocketメッセージ受信]]を参照。