aboutsummaryrefslogtreecommitdiffstats
path: root/src/cowboy_websocket.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/cowboy_websocket.erl')
-rw-r--r--src/cowboy_websocket.erl306
1 files changed, 147 insertions, 159 deletions
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index ece953c..50d1c88 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -31,14 +31,8 @@
-type opcode() :: 0 | 1 | 2 | 8 | 9 | 10.
-type mask_key() :: 0..16#ffffffff.
-
-%% The websocket_data/4 function may be called multiple times for a message.
-%% The websocket_dispatch/4 function is only called once for each message.
--type frag_state() ::
- undefined | %% no fragmentation has been seen.
- {nofin, opcode()} | %% first fragment has been seen.
- {nofin, opcode(), binary()} | %% first fragment has been unmasked.
- {fin, opcode(), binary()}. %% last fragment has been seen.
+-type frag_state() :: undefined
+ | {nofin, opcode(), binary()} | {fin, opcode(), binary()}.
-record(state, {
env :: cowboy_middleware:env(),
@@ -181,9 +175,8 @@ handler_loop_timeout(State=#state{timeout=Timeout, timeout_ref=PrevRef}) ->
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
-handler_loop(State=#state{
- socket=Socket, messages={OK, Closed, Error}, timeout_ref=TRef},
- Req, HandlerState, SoFar) ->
+handler_loop(State=#state{socket=Socket, messages={OK, Closed, Error},
+ timeout_ref=TRef}, Req, HandlerState, SoFar) ->
receive
{OK, Socket, Data} ->
State2 = handler_loop_timeout(State),
@@ -202,187 +195,182 @@ handler_loop(State=#state{
SoFar, websocket_info, Message, fun handler_before_loop/4)
end.
+%% All frames passing through this function are considered valid.
-spec websocket_data(#state{}, Req, any(), binary())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
-%% No more data.
-websocket_data(State, Req, HandlerState, <<>>) ->
- handler_before_loop(State, Req, HandlerState, <<>>);
-%% Incomplete.
-websocket_data(State, Req, HandlerState, Data) when byte_size(Data) =:= 1 ->
- handler_before_loop(State, Req, HandlerState, Data);
-%% 7 bit payload length prefix exists
-websocket_data(State, Req, HandlerState,
- << Fin:1, Rsv:3, Opcode:4, 1:1, PayloadLen:7, Rest/bits >>
- = Data) when PayloadLen < 126 ->
+%% RSV bits MUST be 0 unless an extension is negotiated
+%% that defines meanings for non-zero values.
+websocket_data(State, Req, HandlerState, << _:1, Rsv:3, _/bits >>)
+ when Rsv =/= 0 ->
+ websocket_close(State, Req, HandlerState, {error, badframe});
+%% Control frames MUST NOT be fragmented.
+websocket_data(State, Req, HandlerState, << 0:1, _:3, Opcode:4, _/bits >>)
+ when Opcode >= 8 ->
+ websocket_close(State, Req, HandlerState, {error, badframe});
+%% A fragmented message MUST start a non-zero opcode.
+websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
+ << 0:1, _:3, 0:4, _/bits >>) ->
+ websocket_close(State, Req, HandlerState, {error, badframe});
+%% Non-control opcode when expecting control message or next fragment.
+websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState,
+ << _:4, Opcode:4, _/bits >>)
+ when Opcode =/= 0, Opcode < 8 ->
+ websocket_close(State, Req, HandlerState, {error, badframe});
+%% 7 bits payload length.
+websocket_data(State, Req, HandlerState, << Fin:1, _Rsv:3, Opcode:4, 1:1,
+ Len:7, MaskKey:32, Rest/bits >>)
+ when Len < 126 ->
websocket_data(State, Req, HandlerState,
- Fin, Rsv, Opcode, PayloadLen, Rest, Data);
-%% 7+16 bits payload length prefix exists
-websocket_data(State, Req, HandlerState,
- << Fin:1, Rsv:3, Opcode:4, 1:1, 126:7, PayloadLen:16, Rest/bits >>
- = Data) when PayloadLen > 125 ->
+ Opcode, Len, MaskKey, Rest, Fin);
+%% 16 bits payload length.
+websocket_data(State, Req, HandlerState, << Fin:1, _Rsv:3, Opcode:4, 1:1,
+ 126:7, Len:16, MaskKey:32, Rest/bits >>)
+ when Len > 125, Opcode < 8 ->
websocket_data(State, Req, HandlerState,
- Fin, Rsv, Opcode, PayloadLen, Rest, Data);
-%% 7+16 bits payload length prefix missing
-websocket_data(State, Req, HandlerState,
- << _Fin:1, _Rsv:3, _Opcode:4, 1:1, 126:7, Rest/bits >>
- = Data) when byte_size(Rest) < 2 ->
- handler_before_loop(State, Req, HandlerState, Data);
-%% 7+64 bits payload length prefix exists
-websocket_data(State, Req, HandlerState,
- << Fin:1, Rsv:3, Opcode:4, 1:1, 127:7, 0:1, PayloadLen:63,
- Rest/bits >> = Data) when PayloadLen > 16#FFFF ->
+ Opcode, Len, MaskKey, Rest, Fin);
+%% 63 bits payload length.
+websocket_data(State, Req, HandlerState, << Fin:1, _Rsv:3, Opcode:4, 1:1,
+ 127:7, 0:1, Len:63, MaskKey:32, Rest/bits >>)
+ when Len > 16#ffff, Opcode < 8 ->
websocket_data(State, Req, HandlerState,
- Fin, Rsv, Opcode, PayloadLen, Rest, Data);
-%% 7+64 bits payload length prefix missing
-websocket_data(State, Req, HandlerState,
- << _Fin:1, _Rsv:3, _Opcode:4, 1:1, 127:7, Rest/bits >>
- = Data) when byte_size(Rest) < 8 ->
- handler_before_loop(State, Req, HandlerState, Data);
-%% Invalid payload length or mask bit was not set.
-websocket_data(State, Req, HandlerState, _Data) ->
- websocket_close(State, Req, HandlerState, {error, badframe}).
-
--spec websocket_data(#state{}, Req, any(), non_neg_integer(),
- non_neg_integer(), non_neg_integer(),
- non_neg_integer(), binary(), binary())
- -> {ok, Req, cowboy_middleware:env()}
- when Req::cowboy_req:req().
-%% A fragmented message MUST start a non-zero opcode.
-websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
- _Fin=0, _Rsv=0, _Opcode=0, _PayloadLen, _Rest, _Buffer) ->
+ Opcode, Len, MaskKey, Rest, Fin);
+%% When payload length is over 63 bits, the most significant bit MUST be 0.
+websocket_data(State, Req, HandlerState, << _:8, 1:1, 127:7, 1:1, _/bits >>) ->
websocket_close(State, Req, HandlerState, {error, badframe});
-%% A control message MUST NOT be fragmented.
-websocket_data(State, Req, HandlerState, _Fin=0, _Rsv=0, Opcode,
- _PayloadLen, _Rest, _Buffer) when Opcode >= 8 ->
+%% All frames sent from the client to the server are masked.
+websocket_data(State, Req, HandlerState, << _:8, 0:1, _/bits >>) ->
websocket_close(State, Req, HandlerState, {error, badframe});
-%% The opcode is only included in the first message fragment.
-websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
- _Fin=0, _Rsv=0, Opcode, PayloadLen, Rest, Data) ->
- websocket_before_unmask(
- State#state{frag_state={nofin, Opcode}}, Req, HandlerState,
- Data, Rest, 0, PayloadLen);
-%% non-control opcode when expecting control message or next fragment.
-websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState, _Fin,
- _Rsv=0, Opcode, _Ln, _Rest, _Data) when Opcode > 0, Opcode < 8 ->
+%% For the next two clauses, it can be one of the following:
+%%
+%% * The minimal number of bytes MUST be used to encode the length
+%% * All control frames MUST have a payload length of 125 bytes or less
+websocket_data(State, Req, HandlerState, << _:9, 126:7, _:48, _/bits >>) ->
websocket_close(State, Req, HandlerState, {error, badframe});
-%% If the first message fragment was incomplete, retry unmasking.
-websocket_data(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState,
- _Fin=0, _Rsv=0, Opcode, PayloadLen, Rest, Data) ->
- websocket_before_unmask(
- State#state{frag_state={nofin, Opcode}}, Req, HandlerState,
- Data, Rest, 0, PayloadLen);
-%% if the opcode is zero and the fin flag is zero, unmask and await next.
-websocket_data(State=#state{frag_state={nofin, _Opcode, _Payloads}}, Req,
- HandlerState, _Fin=0, _Rsv=0, _Opcode2=0, PayloadLen, Rest,
- Data) ->
- websocket_before_unmask(
- State, Req, HandlerState, Data, Rest, 0, PayloadLen);
-%% when the last fragment is seen. Update the fragmentation status.
-websocket_data(State=#state{frag_state={nofin, Opcode, Payloads}}, Req,
- HandlerState, _Fin=1, _Rsv=0, _Opcode=0, PayloadLen, Rest,
- Data) ->
- websocket_before_unmask(
- State#state{frag_state={fin, Opcode, Payloads}},
- Req, HandlerState, Data, Rest, 0, PayloadLen);
-%% control messages MUST NOT use 7+16 bits or 7+64 bits payload length prefixes
-websocket_data(State, Req, HandlerState, _Fin, _Rsv, Opcode, PayloadLen,
- _Rest, _Data) when Opcode >= 8, PayloadLen > 125 ->
- websocket_close(State, Req, HandlerState, {error, badframe});
-%% unfragmented message. unmask and dispatch the message.
-websocket_data(State, Req, HandlerState, _Fin=1, _Rsv=0,
- Opcode, PayloadLen, Rest, Data) ->
- websocket_before_unmask(
- State, Req, HandlerState, Data, Rest, Opcode, PayloadLen);
-%% Something was wrong with the frame. Close the connection.
-websocket_data(State, Req, HandlerState, _Fin, _Rsv, _Opcode,
- _PayloadLen, _Rest, _Data) ->
- websocket_close(State, Req, HandlerState, {error, badframe}).
+websocket_data(State, Req, HandlerState, << _:9, 127:7, _:96, _/bits >>) ->
+ websocket_close(State, Req, HandlerState, {error, badframe});
+%% Need more data.
+websocket_data(State, Req, HandlerState, Data) ->
+ handler_before_loop(State, Req, HandlerState, Data).
-%% Routing depending on whether unmasking is needed.
--spec websocket_before_unmask(#state{}, Req, any(), binary(),
- binary(), opcode(), non_neg_integer() | undefined)
+%% Initialize or update fragmentation state.
+-spec websocket_data(#state{}, Req, any(),
+ opcode(), non_neg_integer(), mask_key(), binary(), 0 | 1)
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
-websocket_before_unmask(State, Req, HandlerState, Data,
- Rest, Opcode, PayloadLen) ->
- case PayloadLen of
- N when N + 4 > byte_size(Rest); N =:= undefined ->
- %% @todo We probably should allow limiting frame length.
- handler_before_loop(State, Req, HandlerState, Data);
- _N ->
- << MaskKey:32, Payload:PayloadLen/binary, Rest2/bits >> = Rest,
- websocket_unmask(State, Req, HandlerState, Rest2,
- Opcode, Payload, MaskKey)
- end.
+%% The opcode is only included in the first frame fragment.
+websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
+ Opcode, Len, MaskKey, Data, 0) ->
+ websocket_payload(State#state{frag_state={nofin, Opcode, <<>>}},
+ Req, HandlerState, 0, Len, MaskKey, <<>>, Data);
+%% Subsequent frame fragments.
+websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState,
+ 0, Len, MaskKey, Data, 0) ->
+ websocket_payload(State, Req, HandlerState,
+ 0, Len, MaskKey, <<>>, Data);
+%% Final frame fragment.
+websocket_data(State=#state{frag_state={nofin, Opcode, SoFar}},
+ Req, HandlerState, 0, Len, MaskKey, Data, 1) ->
+ websocket_payload(State#state{frag_state={fin, Opcode, SoFar}},
+ Req, HandlerState, 0, Len, MaskKey, <<>>, Data);
+%% Unfragmented frame.
+websocket_data(State, Req, HandlerState, Opcode, Len, MaskKey, Data, 1) ->
+ websocket_payload(State, Req, HandlerState,
+ Opcode, Len, MaskKey, <<>>, Data).
-%% Unmasking.
--spec websocket_unmask(#state{}, Req, any(), binary(),
- opcode(), binary(), mask_key())
+-spec websocket_payload(#state{}, Req, any(),
+ opcode(), non_neg_integer(), mask_key(), binary(), binary())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
-websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, Payload, MaskKey) ->
- websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, Payload, MaskKey, <<>>).
+websocket_payload(State, Req, HandlerState,
+ Opcode, Len, MaskKey, Unmasked, Data)
+ when byte_size(Data) < Len ->
+ Unmasked2 = websocket_unmask(Data,
+ rotate_mask_key(MaskKey, byte_size(Unmasked)), Unmasked),
+ websocket_payload_loop(State, Req, HandlerState,
+ Opcode, Len - byte_size(Data), MaskKey, Unmasked2);
+websocket_payload(State, Req, HandlerState,
+ Opcode, Len, MaskKey, Unmasked, Data) ->
+ << End:Len/binary, Rest/bits >> = Data,
+ Unmasked2 = websocket_unmask(End,
+ rotate_mask_key(MaskKey, byte_size(Unmasked)), Unmasked),
+ websocket_dispatch(State, Req, HandlerState, Rest, Opcode, Unmasked2).
--spec websocket_unmask(#state{}, Req, any(), binary(),
- opcode(), binary(), mask_key(), binary())
- -> {ok, Req, cowboy_middleware:env()}
- | {suspend, module(), atom(), [any()]}
- when Req::cowboy_req:req().
-websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, << O:32, Rest/bits >>, MaskKey, Acc) ->
+-spec websocket_unmask(B, mask_key(), B) -> B when B::binary().
+websocket_unmask(<<>>, _, Unmasked) ->
+ Unmasked;
+websocket_unmask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
T = O bxor MaskKey,
- websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, Rest, MaskKey, << Acc/binary, T:32 >>);
-websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, << O:24 >>, MaskKey, Acc) ->
+ websocket_unmask(Rest, MaskKey, << Acc/binary, T:32 >>);
+websocket_unmask(<< O:24 >>, MaskKey, Acc) ->
<< MaskKey2:24, _:8 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
- websocket_dispatch(State, Req, HandlerState, RemainingData,
- Opcode, << Acc/binary, T:24 >>);
-websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, << O:16 >>, MaskKey, Acc) ->
+ << Acc/binary, T:24 >>;
+websocket_unmask(<< O:16 >>, MaskKey, Acc) ->
<< MaskKey2:16, _:16 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
- websocket_dispatch(State, Req, HandlerState, RemainingData,
- Opcode, << Acc/binary, T:16 >>);
-websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, << O:8 >>, MaskKey, Acc) ->
+ << Acc/binary, T:16 >>;
+websocket_unmask(<< O:8 >>, MaskKey, Acc) ->
<< MaskKey2:8, _:24 >> = << MaskKey:32 >>,
T = O bxor MaskKey2,
- websocket_dispatch(State, Req, HandlerState, RemainingData,
- Opcode, << Acc/binary, T:8 >>);
-websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, <<>>, _MaskKey, Acc) ->
- websocket_dispatch(State, Req, HandlerState, RemainingData,
- Opcode, Acc).
+ << Acc/binary, T:8 >>.
+
+%% Because we unmask on the fly we need to continue from the right mask byte.
+-spec rotate_mask_key(mask_key(), non_neg_integer()) -> mask_key().
+rotate_mask_key(MaskKey, UnmaskedLen) ->
+ Left = UnmaskedLen rem 4,
+ Right = 4 - Left,
+ (MaskKey bsl (Left * 8)) + (MaskKey bsr (Right * 8)).
+
+-spec websocket_payload_loop(#state{}, Req, any(),
+ opcode(), non_neg_integer(), mask_key(), binary())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {suspend, module(), atom(), [any()]}
+ when Req::cowboy_req:req().
+websocket_payload_loop(State=#state{socket=Socket, transport=Transport,
+ messages={OK, Closed, Error}, timeout_ref=TRef},
+ Req, HandlerState, Opcode, Len, MaskKey, Unmasked) ->
+ Transport:setopts(Socket, [{active, once}]),
+ receive
+ {OK, Socket, Data} ->
+ State2 = handler_loop_timeout(State),
+ websocket_payload(State2, Req, HandlerState,
+ Opcode, Len, MaskKey, Unmasked, Data);
+ {Closed, Socket} ->
+ handler_terminate(State, Req, HandlerState, {error, closed});
+ {Error, Socket, Reason} ->
+ handler_terminate(State, Req, HandlerState, {error, Reason});
+ {timeout, TRef, ?MODULE} ->
+ websocket_close(State, Req, HandlerState, {normal, timeout});
+ {timeout, OlderTRef, ?MODULE} when is_reference(OlderTRef) ->
+ websocket_payload_loop(State, Req, HandlerState,
+ Opcode, Len, MaskKey, Unmasked);
+ Message ->
+ handler_call(State, Req, HandlerState,
+ <<>>, websocket_info, Message,
+ fun (State2, Req2, HandlerState2, _) ->
+ websocket_payload_loop(State2, Req2, HandlerState2,
+ Opcode, Len, MaskKey, Unmasked)
+ end)
+ end.
-%% Dispatching.
-spec websocket_dispatch(#state{}, Req, any(), binary(), opcode(), binary())
-> {ok, Req, cowboy_middleware:env()}
| {suspend, module(), atom(), [any()]}
when Req::cowboy_req:req().
-%% First frame of a fragmented message unmasked. Expect intermediate or last.
-websocket_dispatch(State=#state{frag_state={nofin, Opcode}}, Req, HandlerState,
- RemainingData, 0, Payload) ->
- websocket_data(State#state{frag_state={nofin, Opcode, Payload}},
- Req, HandlerState, RemainingData);
-%% Intermediate frame of a fragmented message unmasked. Add payload to buffer.
-websocket_dispatch(State=#state{frag_state={nofin, Opcode, Payloads}}, Req,
- HandlerState, RemainingData, 0, Payload) ->
+%% Continuation frame.
+websocket_dispatch(State=#state{frag_state={nofin, Opcode, SoFar}},
+ Req, HandlerState, RemainingData, 0, Payload) ->
websocket_data(State#state{frag_state={nofin, Opcode,
- <<Payloads/binary, Payload/binary>>}}, Req, HandlerState,
- RemainingData);
-%% Last frame of a fragmented message unmasked. Dispatch to handler.
-websocket_dispatch(State=#state{frag_state={fin, Opcode, Payloads}}, Req,
- HandlerState, RemainingData, 0, Payload) ->
+ << SoFar/binary, Payload/binary >>}}, Req, HandlerState, RemainingData);
+%% Last continuation frame.
+websocket_dispatch(State=#state{frag_state={fin, Opcode, SoFar}},
+ Req, HandlerState, RemainingData, 0, Payload) ->
websocket_dispatch(State#state{frag_state=undefined}, Req, HandlerState,
- RemainingData, Opcode, <<Payloads/binary, Payload/binary>>);
+ RemainingData, Opcode, << SoFar/binary, Payload/binary >>);
%% Text frame.
websocket_dispatch(State, Req, HandlerState, RemainingData, 1, Payload) ->
handler_call(State, Req, HandlerState, RemainingData,