|
@@ -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,
|