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.erl724
1 files changed, 391 insertions, 333 deletions
diff --git a/src/cowboy_websocket.erl b/src/cowboy_websocket.erl
index 8c02ac7..debb69f 100644
--- a/src/cowboy_websocket.erl
+++ b/src/cowboy_websocket.erl
@@ -1,4 +1,4 @@
-%% Copyright (c) 2011-2012, Loïc Hoguin <[email protected]>
+%% Copyright (c) 2011-2013, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
@@ -12,7 +12,10 @@
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
-%% @doc WebSocket protocol implementation.
+%% @doc Websocket protocol implementation.
+%%
+%% Cowboy supports versions 7 through 17 of the Websocket drafts.
+%% It also supports RFC6455, the proposed standard for Websocket.
-module(cowboy_websocket).
%% API.
@@ -21,52 +24,52 @@
%% Internal.
-export([handler_loop/4]).
+-type close_code() :: 1000..4999.
+-export_type([close_code/0]).
+
-type frame() :: close | ping | pong
| {text | binary | close | ping | pong, binary()}
- | {close, 1000..4999, binary()}.
+ | {close, close_code(), binary()}.
-export_type([frame/0]).
-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(),
socket = undefined :: inet:socket(),
transport = undefined :: module(),
- version :: 0 | 7 | 8 | 13,
handler :: module(),
- opts :: any(),
- challenge = undefined :: undefined | binary() | {binary(), binary()},
+ handler_opts :: any(),
+ key = undefined :: undefined | binary(),
timeout = infinity :: timeout(),
timeout_ref = undefined :: undefined | reference(),
messages = undefined :: undefined | {atom(), atom(), atom()},
hibernate = false :: boolean(),
- eop :: undefined | tuple(), %% hixie-76 specific.
- origin = undefined :: undefined | binary(), %% hixie-76 specific.
- frag_state = undefined :: frag_state()
+ frag_state = undefined :: frag_state(),
+ utf8_state = <<>> :: binary()
}).
-%% @doc Upgrade a HTTP request to the WebSocket protocol.
+%% @doc Upgrade an HTTP request to the Websocket protocol.
%%
-%% You do not need to call this function manually. To upgrade to the WebSocket
+%% You do not need to call this function manually. To upgrade to the Websocket
%% protocol, you simply need to return <em>{upgrade, protocol, {@module}}</em>
%% in your <em>cowboy_http_handler:init/3</em> handler function.
--spec upgrade(pid(), module(), any(), cowboy_req:req()) -> closed.
-upgrade(ListenerPid, Handler, Opts, Req) ->
+-spec upgrade(Req, Env, module(), any())
+ -> {ok, Req, Env} | {error, 400, Req}
+ | {suspend, module(), atom(), [any()]}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade(Req, Env, Handler, HandlerOpts) ->
+ {_, ListenerPid} = lists:keyfind(listener, 1, Env),
ranch_listener:remove_connection(ListenerPid),
- {ok, Transport, Socket} = cowboy_req:transport(Req),
- State = #state{socket=Socket, transport=Transport,
- handler=Handler, opts=Opts},
+ [Socket, Transport] = cowboy_req:get([socket, transport], Req),
+ State = #state{env=Env, socket=Socket, transport=Transport,
+ handler=Handler, handler_opts=HandlerOpts},
case catch websocket_upgrade(State, Req) of
{ok, State2, Req2} -> handler_init(State2, Req2);
- {'EXIT', _Reason} -> upgrade_error(Req)
+ {'EXIT', _Reason} -> upgrade_error(Req, Env)
end.
-spec websocket_upgrade(#state{}, Req)
@@ -79,41 +82,21 @@ websocket_upgrade(State, Req) ->
{ok, [<<"websocket">>], Req3}
= cowboy_req:parse_header(<<"upgrade">>, Req2),
{Version, Req4} = cowboy_req:header(<<"sec-websocket-version">>, Req3),
- websocket_upgrade(Version, State, Req4).
-
-%% @todo Handle the Sec-Websocket-Protocol header.
-%% @todo Reply a proper error, don't die, if a required header is undefined.
--spec websocket_upgrade(undefined | <<_:8>>, #state{}, Req)
- -> {ok, #state{}, Req} when Req::cowboy_req:req().
-%% No version given. Assuming hixie-76 draft.
-%%
-%% We need to wait to send a reply back before trying to read the
-%% third part of the challenge key, because proxies will wait for
-%% a reply before sending it. Therefore we calculate the challenge
-%% key only in websocket_handshake/3.
-websocket_upgrade(undefined, State, Req) ->
- {Origin, Req2} = cowboy_req:header(<<"origin">>, Req),
- {Key1, Req3} = cowboy_req:header(<<"sec-websocket-key1">>, Req2),
- {Key2, Req4} = cowboy_req:header(<<"sec-websocket-key2">>, Req3),
- false = lists:member(undefined, [Origin, Key1, Key2]),
- EOP = binary:compile_pattern(<< 255 >>),
- {ok, State#state{version=0, origin=Origin, challenge={Key1, Key2},
- eop=EOP}, cowboy_req:set_meta(websocket_version, 0, Req4)};
-%% Versions 7 and 8. Implementation follows the hybi 7 through 17 drafts.
-websocket_upgrade(Version, State, Req)
- when Version =:= <<"7">>; Version =:= <<"8">>;
- Version =:= <<"13">> ->
- {Key, Req2} = cowboy_req:header(<<"sec-websocket-key">>, Req),
- false = Key =:= undefined,
- Challenge = hybi_challenge(Key),
IntVersion = list_to_integer(binary_to_list(Version)),
- {ok, State#state{version=IntVersion, challenge=Challenge},
- cowboy_req:set_meta(websocket_version, IntVersion, Req2)}.
-
--spec handler_init(#state{}, cowboy_req:req()) -> closed.
-handler_init(State=#state{transport=Transport, handler=Handler, opts=Opts},
- Req) ->
- try Handler:websocket_init(Transport:name(), Req, Opts) of
+ true = (IntVersion =:= 7) orelse (IntVersion =:= 8)
+ orelse (IntVersion =:= 13),
+ {Key, Req5} = cowboy_req:header(<<"sec-websocket-key">>, Req4),
+ false = Key =:= undefined,
+ {ok, State#state{key=Key},
+ cowboy_req:set_meta(websocket_version, IntVersion, Req5)}.
+
+-spec handler_init(#state{}, Req)
+ -> {ok, Req, cowboy_middleware:env()} | {error, 400, Req}
+ | {suspend, module(), atom(), [any()]}
+ when Req::cowboy_req:req().
+handler_init(State=#state{env=Env, transport=Transport,
+ handler=Handler, handler_opts=HandlerOpts}, Req) ->
+ try Handler:websocket_init(Transport:name(), Req, HandlerOpts) of
{ok, Req2, HandlerState} ->
websocket_handshake(State, Req2, HandlerState);
{ok, Req2, HandlerState, hibernate} ->
@@ -127,60 +110,36 @@ handler_init(State=#state{transport=Transport, handler=Handler, opts=Opts},
hibernate=true}, Req2, HandlerState);
{shutdown, Req2} ->
cowboy_req:ensure_response(Req2, 400),
- closed
+ {ok, Req2, [{result, closed}|Env]}
catch Class:Reason ->
- upgrade_error(Req),
error_logger:error_msg(
"** Cowboy handler ~p terminating in ~p/~p~n"
" for the reason ~p:~p~n** Options were ~p~n"
"** Request was ~p~n** Stacktrace: ~p~n~n",
- [Handler, websocket_init, 3, Class, Reason, Opts,
- cowboy_req:to_list(Req),erlang:get_stacktrace()])
+ [Handler, websocket_init, 3, Class, Reason, HandlerOpts,
+ cowboy_req:to_list(Req),erlang:get_stacktrace()]),
+ upgrade_error(Req, Env)
end.
--spec upgrade_error(cowboy_req:req()) -> closed.
-upgrade_error(Req) ->
+%% Only send an error reply if there is no resp_sent message.
+-spec upgrade_error(Req, Env) -> {ok, Req, Env} | {error, 400, Req}
+ when Req::cowboy_req:req(), Env::cowboy_middleware:env().
+upgrade_error(Req, Env) ->
receive
- {cowboy_req, resp_sent} -> closed
+ {cowboy_req, resp_sent} ->
+ {ok, Req, [{result, closed}|Env]}
after 0 ->
- _ = cowboy_req:reply(400, [], [], Req),
- closed
+ {error, 400, Req}
end.
--spec websocket_handshake(#state{}, cowboy_req:req(), any()) -> closed.
-websocket_handshake(State=#state{socket=Socket, transport=Transport,
- version=0, origin=Origin, challenge={Key1, Key2}},
- Req, HandlerState) ->
- {<< "http", Location/binary >>, Req1} = cowboy_req:url(Req),
- {ok, Req2} = cowboy_req:upgrade_reply(
- <<"101 WebSocket Protocol Handshake">>,
- [{<<"upgrade">>, <<"WebSocket">>},
- {<<"sec-websocket-location">>, << "ws", Location/binary >>},
- {<<"sec-websocket-origin">>, Origin}],
- Req1),
- %% Flush the resp_sent message before moving on.
- receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
- %% We replied with a proper response. Proxies should be happy enough,
- %% we can now read the 8 last bytes of the challenge keys and send
- %% the challenge response directly to the socket.
- %%
- %% We use a trick here to read exactly 8 bytes of the body regardless
- %% of what's in the buffer.
- {ok, Req3} = cowboy_req:init_stream(
- fun cowboy_http:te_identity/2, {0, 8},
- fun cowboy_http:ce_identity/1, Req2),
- case cowboy_req:body(Req3) of
- {ok, Key3, Req4} ->
- Challenge = hixie76_challenge(Key1, Key2, Key3),
- Transport:send(Socket, Challenge),
- handler_before_loop(State#state{messages=Transport:messages()},
- Req4, HandlerState, <<>>);
- _Any ->
- %% If an error happened reading the body, stop there.
- handler_terminate(State, Req3, HandlerState, {error, closed})
- end;
-websocket_handshake(State=#state{transport=Transport, challenge=Challenge},
+-spec websocket_handshake(#state{}, Req, any())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {suspend, module(), atom(), [any()]}
+ when Req::cowboy_req:req().
+websocket_handshake(State=#state{transport=Transport, key=Key},
Req, HandlerState) ->
+ Challenge = base64:encode(crypto:sha(
+ << Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>)),
{ok, Req2} = cowboy_req:upgrade_reply(
101,
[{<<"upgrade">>, <<"websocket">>},
@@ -189,17 +148,19 @@ websocket_handshake(State=#state{transport=Transport, challenge=Challenge},
%% Flush the resp_sent message before moving on.
receive {cowboy_req, resp_sent} -> ok after 0 -> ok end,
State2 = handler_loop_timeout(State),
- handler_before_loop(State2#state{messages=Transport:messages()},
- Req2, HandlerState, <<>>).
+ handler_before_loop(State2#state{key=undefined,
+ messages=Transport:messages()}, Req2, HandlerState, <<>>).
--spec handler_before_loop(#state{}, cowboy_req:req(), any(), binary()) -> closed.
+-spec handler_before_loop(#state{}, Req, any(), binary())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {suspend, module(), atom(), [any()]}
+ when Req::cowboy_req:req().
handler_before_loop(State=#state{
socket=Socket, transport=Transport, hibernate=true},
Req, HandlerState, SoFar) ->
Transport:setopts(Socket, [{active, once}]),
- catch erlang:hibernate(?MODULE, handler_loop,
- [State#state{hibernate=false}, Req, HandlerState, SoFar]),
- closed;
+ {suspend, ?MODULE, handler_loop,
+ [State#state{hibernate=false}, Req, HandlerState, SoFar]};
handler_before_loop(State=#state{socket=Socket, transport=Transport},
Req, HandlerState, SoFar) ->
Transport:setopts(Socket, [{active, once}]),
@@ -215,10 +176,12 @@ handler_loop_timeout(State=#state{timeout=Timeout, timeout_ref=PrevRef}) ->
State#state{timeout_ref=TRef}.
%% @private
--spec handler_loop(#state{}, cowboy_req:req(), any(), binary()) -> closed.
-handler_loop(State=#state{
- socket=Socket, messages={OK, Closed, Error}, timeout_ref=TRef},
- Req, HandlerState, SoFar) ->
+-spec handler_loop(#state{}, Req, any(), binary())
+ -> {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) ->
receive
{OK, Socket, Data} ->
State2 = handler_loop_timeout(State),
@@ -237,191 +200,298 @@ handler_loop(State=#state{
SoFar, websocket_info, Message, fun handler_before_loop/4)
end.
--spec websocket_data(#state{}, cowboy_req:req(), any(), binary()) -> closed.
-%% No more data.
-websocket_data(State, Req, HandlerState, <<>>) ->
- handler_before_loop(State, Req, HandlerState, <<>>);
-%% hixie-76 close frame.
-websocket_data(State=#state{version=0}, Req, HandlerState,
- << 255, 0, _Rest/binary >>) ->
- websocket_close(State, Req, HandlerState, {normal, closed});
-%% hixie-76 data frame. We only support the frame type 0, same as the specs.
-websocket_data(State=#state{version=0, eop=EOP}, Req, HandlerState,
- Data = << 0, _/binary >>) ->
- case binary:match(Data, EOP) of
- {Pos, 1} ->
- Pos2 = Pos - 1,
- << 0, Payload:Pos2/binary, 255, Rest/bits >> = Data,
- handler_call(State, Req, HandlerState,
- Rest, websocket_handle, {text, Payload}, fun websocket_data/4);
- nomatch ->
- %% @todo We probably should allow limiting frame length.
- handler_before_loop(State, Req, HandlerState, Data)
- end;
-%% incomplete hybi data frame.
-websocket_data(State=#state{version=Version}, Req, HandlerState, Data)
- when Version =/= 0, byte_size(Data) =:= 1 ->
- handler_before_loop(State, Req, HandlerState, Data);
-%% 7 bit payload length prefix exists
+%% All frames passing through this function are considered valid,
+%% with the only exception of text and close frames with a payload
+%% which may still contain errors.
+-spec websocket_data(#state{}, Req, any(), binary())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {suspend, module(), atom(), [any()]}
+ when Req::cowboy_req:req().
+%% 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});
+%% Invalid opcode. Note that these opcodes may be used by extensions.
+websocket_data(State, Req, HandlerState, << _:4, Opcode:4, _/bits >>)
+ when Opcode > 2, Opcode =/= 8, Opcode =/= 9, Opcode =/= 10 ->
+ 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 frame MUST NOT use the zero opcode unless fragmentation was initiated.
+websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
+ << _:4, 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});
+%% Close control frame length MUST be 0 or >= 2.
+websocket_data(State, Req, HandlerState, << _:4, 8:4, _:1, 1:7, _/bits >>) ->
+ websocket_close(State, Req, HandlerState, {error, badframe});
+%% Close control frame with incomplete close code. Need more data.
websocket_data(State, Req, HandlerState,
- << Fin:1, Rsv:3, Opcode:4, Mask:1, PayloadLen:7, Rest/bits >>
- = Data) when PayloadLen < 126 ->
+ Data = << _:4, 8:4, 1:1, Len:7, _/bits >>)
+ when Len > 1, byte_size(Data) < 8 ->
+ handler_before_loop(State, Req, HandlerState, Data);
+%% 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, Mask, PayloadLen, Rest, Data);
-%% 7+16 bits payload length prefix exists
-websocket_data(State, Req, HandlerState,
- << Fin:1, Rsv:3, Opcode:4, Mask: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, Mask, PayloadLen, Rest, Data);
-%% 7+16 bits payload length prefix missing
-websocket_data(State, Req, HandlerState,
- << _Fin:1, _Rsv:3, _Opcode:4, _Mask: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, Mask: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, Mask, PayloadLen, Rest, Data);
-%% 7+64 bits payload length prefix missing
-websocket_data(State, Req, HandlerState,
- << _Fin:1, _Rsv:3, _Opcode:4, _Mask:1, 127:7, Rest/bits >>
- = Data) when byte_size(Rest) < 8 ->
- handler_before_loop(State, Req, HandlerState, Data);
-%% invalid payload length prefix.
-websocket_data(State, Req, HandlerState, _Data) ->
- websocket_close(State, Req, HandlerState, {error, badframe}).
-
--spec websocket_data(#state{}, cowboy_req:req(), any(), non_neg_integer(),
- non_neg_integer(), non_neg_integer(), non_neg_integer(),
- non_neg_integer(), binary(), binary()) -> closed.
-%% A fragmented message MUST start a non-zero opcode.
-websocket_data(State=#state{frag_state=undefined}, Req, HandlerState,
- _Fin=0, _Rsv=0, _Opcode=0, _Mask, _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, _Mask,
- _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, Mask, PayloadLen, Rest, Data) ->
- websocket_before_unmask(
- State#state{frag_state={nofin, Opcode}}, Req, HandlerState,
- Data, Rest, 0, Mask, PayloadLen);
-%% non-control opcode when expecting control message or next fragment.
-websocket_data(State=#state{frag_state={nofin, _, _}}, Req, HandlerState, _Fin,
- _Rsv=0, Opcode, _Mask, _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, Mask, PayloadLen, Rest, Data) ->
- websocket_before_unmask(
- State#state{frag_state={nofin, Opcode}}, Req, HandlerState,
- Data, Rest, 0, Mask, 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, Mask, PayloadLen, Rest,
- Data) ->
- websocket_before_unmask(
- State, Req, HandlerState, Data, Rest, 0, Mask, 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, Mask, PayloadLen, Rest,
- Data) ->
- websocket_before_unmask(
- State#state{frag_state={fin, Opcode, Payloads}},
- Req, HandlerState, Data, Rest, 0, Mask, PayloadLen);
-%% control messages MUST NOT use 7+16 bits or 7+64 bits payload length prefixes
-websocket_data(State, Req, HandlerState, _Fin, _Rsv, Opcode, _Mask, 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=#state{version=Version}, Req, HandlerState, _Fin=1, _Rsv=0,
- Opcode, Mask, PayloadLen, Rest, Data) when Version =/= 0 ->
- websocket_before_unmask(
- State, Req, HandlerState, Data, Rest, Opcode, Mask, PayloadLen);
-%% Something was wrong with the frame. Close the connection.
-websocket_data(State, Req, HandlerState, _Fin, _Rsv, _Opcode, _Mask,
- _PayloadLen, _Rest, _Data) ->
- websocket_close(State, Req, HandlerState, {error, badframe}).
-
-%% hybi routing depending on whether unmasking is needed.
--spec websocket_before_unmask(#state{}, cowboy_req:req(), any(), binary(),
- binary(), opcode(), 0 | 1, non_neg_integer() | undefined) -> closed.
-websocket_before_unmask(State, Req, HandlerState, Data,
- Rest, Opcode, Mask, PayloadLen) ->
- case {Mask, PayloadLen} of
- {0, 0} ->
- websocket_dispatch(State, Req, HandlerState, Rest, Opcode, <<>>);
- {1, N} when N + 4 > byte_size(Rest); N =:= undefined ->
- %% @todo We probably should allow limiting frame length.
- handler_before_loop(State, Req, HandlerState, Data);
- {1, _N} ->
- << MaskKey:32, Payload:PayloadLen/binary, Rest2/bits >> = Rest,
- websocket_unmask(State, Req, HandlerState, Rest2,
- Opcode, Payload, MaskKey)
- end.
-
-%% hybi unmasking.
--spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(),
- opcode(), binary(), mask_key()) -> closed.
-websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, Payload, MaskKey) ->
- websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, Payload, MaskKey, <<>>).
-
--spec websocket_unmask(#state{}, cowboy_req:req(), any(), binary(),
- opcode(), binary(), mask_key(), binary()) -> closed.
-websocket_unmask(State, Req, HandlerState, RemainingData,
- Opcode, << O:32, Rest/bits >>, MaskKey, Acc) ->
+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).
+
+%% 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().
+%% 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).
+
+-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().
+%% Close control frames with a payload MUST contain a valid close code.
+websocket_payload(State, Req, HandlerState,
+ Opcode=8, Len, MaskKey, <<>>, << MaskedCode:2/binary, Rest/bits >>) ->
+ Unmasked = << Code:16 >> = websocket_unmask(MaskedCode, MaskKey, <<>>),
+ if Code < 1000; Code =:= 1004; Code =:= 1005; Code =:= 1006;
+ (Code > 1011) and (Code < 3000); Code > 4999 ->
+ websocket_close(State, Req, HandlerState, {error, badframe});
+ true ->
+ websocket_payload(State, Req, HandlerState,
+ Opcode, Len - 2, MaskKey, Unmasked, Rest)
+ end;
+%% Text frames and close control frames MUST have a payload that is valid UTF-8.
+websocket_payload(State=#state{utf8_state=Incomplete},
+ Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data)
+ when (byte_size(Data) < Len) andalso ((Opcode =:= 1) orelse
+ ((Opcode =:= 8) andalso (Unmasked =/= <<>>))) ->
+ Unmasked2 = websocket_unmask(Data,
+ rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>),
+ case is_utf8(<< Incomplete/binary, Unmasked2/binary >>) of
+ false ->
+ websocket_close(State, Req, HandlerState, {error, badencoding});
+ Utf8State ->
+ websocket_payload_loop(State#state{utf8_state=Utf8State},
+ Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey,
+ << Unmasked/binary, Unmasked2/binary >>)
+ end;
+websocket_payload(State=#state{utf8_state=Incomplete},
+ Req, HandlerState, Opcode, Len, MaskKey, Unmasked, Data)
+ when Opcode =:= 1; (Opcode =:= 8) and (Unmasked =/= <<>>) ->
+ << End:Len/binary, Rest/bits >> = Data,
+ Unmasked2 = websocket_unmask(End,
+ rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>),
+ case is_utf8(<< Incomplete/binary, Unmasked2/binary >>) of
+ <<>> ->
+ websocket_dispatch(State#state{utf8_state= <<>>},
+ Req, HandlerState, Rest, Opcode,
+ << Unmasked/binary, Unmasked2/binary >>);
+ _ ->
+ websocket_close(State, Req, HandlerState, {error, badencoding})
+ end;
+%% Fragmented text frames may cut payload in the middle of UTF-8 codepoints.
+websocket_payload(State=#state{frag_state={_, 1, _}, utf8_state=Incomplete},
+ Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, Data)
+ when byte_size(Data) < Len ->
+ Unmasked2 = websocket_unmask(Data,
+ rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>),
+ case is_utf8(<< Incomplete/binary, Unmasked2/binary >>) of
+ false ->
+ websocket_close(State, Req, HandlerState, {error, badencoding});
+ Utf8State ->
+ websocket_payload_loop(State#state{utf8_state=Utf8State},
+ Req, HandlerState, Opcode, Len - byte_size(Data), MaskKey,
+ << Unmasked/binary, Unmasked2/binary >>)
+ end;
+websocket_payload(State=#state{frag_state={Fin, 1, _}, utf8_state=Incomplete},
+ Req, HandlerState, Opcode=0, Len, MaskKey, Unmasked, Data) ->
+ << End:Len/binary, Rest/bits >> = Data,
+ Unmasked2 = websocket_unmask(End,
+ rotate_mask_key(MaskKey, byte_size(Unmasked)), <<>>),
+ case is_utf8(<< Incomplete/binary, Unmasked2/binary >>) of
+ <<>> ->
+ websocket_dispatch(State#state{utf8_state= <<>>},
+ Req, HandlerState, Rest, Opcode,
+ << Unmasked/binary, Unmasked2/binary >>);
+ Utf8State when is_binary(Utf8State), Fin =:= nofin ->
+ websocket_dispatch(State#state{utf8_state=Utf8State},
+ Req, HandlerState, Rest, Opcode,
+ << Unmasked/binary, Unmasked2/binary >>);
+ _ ->
+ websocket_close(State, Req, HandlerState, {error, badencoding})
+ end;
+%% Other frames have a binary payload.
+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(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)).
+
+%% Returns <<>> if the argument is valid UTF-8, false if not,
+%% or the incomplete part of the argument if we need more data.
+-spec is_utf8(binary()) -> false | binary().
+is_utf8(Valid = <<>>) ->
+ Valid;
+is_utf8(<< _/utf8, Rest/binary >>) ->
+ is_utf8(Rest);
+%% 2 bytes. Codepages C0 and C1 are invalid; fail early.
+is_utf8(<< 2#1100000:7, _/bits >>) ->
+ false;
+is_utf8(Incomplete = << 2#110:3, _:5 >>) ->
+ Incomplete;
+%% 3 bytes.
+is_utf8(Incomplete = << 2#1110:4, _:4 >>) ->
+ Incomplete;
+is_utf8(Incomplete = << 2#1110:4, _:4, 2#10:2, _:6 >>) ->
+ Incomplete;
+%% 4 bytes. Codepage F4 may have invalid values greater than 0x10FFFF.
+is_utf8(<< 2#11110100:8, 2#10:2, High:6, _/bits >>) when High >= 2#10000 ->
+ false;
+is_utf8(Incomplete = << 2#11110:5, _:3 >>) ->
+ Incomplete;
+is_utf8(Incomplete = << 2#11110:5, _:3, 2#10:2, _:6 >>) ->
+ Incomplete;
+is_utf8(Incomplete = << 2#11110:5, _:3, 2#10:2, _:6, 2#10:2, _:6 >>) ->
+ Incomplete;
+%% Invalid.
+is_utf8(_) ->
+ false.
+
+-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.
-%% hybi dispatching.
--spec websocket_dispatch(#state{}, cowboy_req:req(), any(), binary(),
- opcode(), binary()) -> closed.
-%% 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) ->
+-spec websocket_dispatch(#state{}, Req, any(), binary(), opcode(), binary())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {suspend, module(), atom(), [any()]}
+ when Req::cowboy_req:req().
+%% 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,
@@ -431,13 +501,15 @@ websocket_dispatch(State, Req, HandlerState, RemainingData, 2, Payload) ->
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {binary, Payload}, fun websocket_data/4);
%% Close control frame.
-%% @todo Handle the optional Payload.
-websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, _Payload) ->
- websocket_close(State, Req, HandlerState, {normal, closed});
+websocket_dispatch(State, Req, HandlerState, _RemainingData, 8, <<>>) ->
+ websocket_close(State, Req, HandlerState, {remote, closed});
+websocket_dispatch(State, Req, HandlerState, _RemainingData, 8,
+ << Code:16, Payload/bits >>) ->
+ websocket_close(State, Req, HandlerState, {remote, Code, Payload});
%% Ping control frame. Send a pong back and forward the ping to the handler.
websocket_dispatch(State=#state{socket=Socket, transport=Transport},
Req, HandlerState, RemainingData, 9, Payload) ->
- Len = hybi_payload_length(byte_size(Payload)),
+ Len = payload_length_to_binary(byte_size(Payload)),
Transport:send(Socket, << 1:1, 0:3, 10:4, 0:1, Len/bits, Payload/binary >>),
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {ping, Payload}, fun websocket_data/4);
@@ -446,10 +518,12 @@ websocket_dispatch(State, Req, HandlerState, RemainingData, 10, Payload) ->
handler_call(State, Req, HandlerState, RemainingData,
websocket_handle, {pong, Payload}, fun websocket_data/4).
--spec handler_call(#state{}, cowboy_req:req(), any(), binary(),
- atom(), any(), fun()) -> closed.
-handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
- RemainingData, Callback, Message, NextState) ->
+-spec handler_call(#state{}, Req, any(), binary(), atom(), any(), fun())
+ -> {ok, Req, cowboy_middleware:env()}
+ | {suspend, module(), atom(), [any()]}
+ when Req::cowboy_req:req().
+handler_call(State=#state{handler=Handler, handler_opts=HandlerOpts}, Req,
+ HandlerState, RemainingData, Callback, Message, NextState) ->
try Handler:Callback(Message, Req, HandlerState) of
{ok, Req2, HandlerState2} ->
NextState(State, Req2, HandlerState2, RemainingData);
@@ -515,7 +589,7 @@ handler_call(State=#state{handler=Handler, opts=Opts}, Req, HandlerState,
" for the reason ~p:~p~n** Message was ~p~n"
"** Options were ~p~n** Handler state was ~p~n"
"** Request was ~p~n** Stacktrace: ~p~n~n",
- [Handler, Callback, 3, Class, Reason, Message, Opts,
+ [Handler, Callback, 3, Class, Reason, Message, HandlerOpts,
HandlerState, PLReq, erlang:get_stacktrace()]),
websocket_close(State, Req, HandlerState, {error, handler})
end.
@@ -528,13 +602,6 @@ websocket_opcode(pong) -> 10.
-spec websocket_send(frame(), #state{})
-> ok | shutdown | {error, atom()}.
-%% hixie-76 text frame.
-websocket_send({text, Payload}, #state{
- socket=Socket, transport=Transport, version=0}) ->
- Transport:send(Socket, [0, Payload, 255]);
-%% Ignore all unknown frame types for compatibility with hixie 76.
-websocket_send(_Any, #state{version=0}) ->
- ok;
websocket_send(Type, #state{socket=Socket, transport=Transport})
when Type =:= close ->
Opcode = websocket_opcode(Type),
@@ -554,7 +621,7 @@ websocket_send({Type = close, StatusCode, Payload}, #state{
Len = 2 + iolist_size(Payload),
%% Control packets must not be > 125 in length.
true = Len =< 125,
- BinLen = hybi_payload_length(Len),
+ BinLen = payload_length_to_binary(Len),
Transport:send(Socket,
[<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits, StatusCode:16 >>, Payload]),
shutdown;
@@ -567,7 +634,7 @@ websocket_send({Type, Payload}, #state{socket=Socket, transport=Transport}) ->
true ->
true
end,
- BinLen = hybi_payload_length(Len),
+ BinLen = payload_length_to_binary(Len),
Transport:send(Socket,
[<< 1:1, 0:3, Opcode:4, 0:1, BinLen/bits >>, Payload]).
@@ -582,20 +649,32 @@ websocket_send_many([Frame|Tail], State) ->
Error -> Error
end.
--spec websocket_close(#state{}, cowboy_req:req(), any(), {atom(), atom()})
- -> closed.
-websocket_close(State=#state{socket=Socket, transport=Transport, version=0},
- Req, HandlerState, Reason) ->
- Transport:send(Socket, << 255, 0 >>),
- handler_terminate(State, Req, HandlerState, Reason);
+-spec websocket_close(#state{}, Req, any(),
+ {atom(), atom()} | {remote, close_code(), binary()})
+ -> {ok, Req, cowboy_middleware:env()}
+ when Req::cowboy_req:req().
websocket_close(State=#state{socket=Socket, transport=Transport},
Req, HandlerState, Reason) ->
- Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>),
+ case Reason of
+ {normal, _} ->
+ Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>);
+ {error, badframe} ->
+ Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1002:16 >>);
+ {error, badencoding} ->
+ Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1007:16 >>);
+ {error, handler} ->
+ Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, 1011:16 >>);
+ {remote, closed} ->
+ Transport:send(Socket, << 1:1, 0:3, 8:4, 0:8 >>);
+ {remote, Code, _} ->
+ Transport:send(Socket, << 1:1, 0:3, 8:4, 0:1, 2:7, Code:16 >>)
+ end,
handler_terminate(State, Req, HandlerState, Reason).
--spec handler_terminate(#state{}, cowboy_req:req(),
- any(), atom() | {atom(), atom()}) -> closed.
-handler_terminate(#state{handler=Handler, opts=Opts},
+-spec handler_terminate(#state{}, Req, any(), atom() | {atom(), atom()})
+ -> {ok, Req, cowboy_middleware:env()}
+ when Req::cowboy_req:req().
+handler_terminate(#state{env=Env, handler=Handler, handler_opts=HandlerOpts},
Req, HandlerState, TerminateReason) ->
try
Handler:websocket_terminate(TerminateReason, Req, HandlerState)
@@ -606,35 +685,14 @@ handler_terminate(#state{handler=Handler, opts=Opts},
" for the reason ~p:~p~n** Initial reason was ~p~n"
"** Options were ~p~n** Handler state was ~p~n"
"** Request was ~p~n** Stacktrace: ~p~n~n",
- [Handler, websocket_terminate, 3, Class, Reason, TerminateReason, Opts,
- HandlerState, PLReq, erlang:get_stacktrace()])
+ [Handler, websocket_terminate, 3, Class, Reason, TerminateReason,
+ HandlerOpts, HandlerState, PLReq, erlang:get_stacktrace()])
end,
- closed.
-
-%% hixie-76 specific.
-
--spec hixie76_challenge(binary(), binary(), binary()) -> binary().
-hixie76_challenge(Key1, Key2, Key3) ->
- IntKey1 = hixie76_key_to_integer(Key1),
- IntKey2 = hixie76_key_to_integer(Key2),
- erlang:md5(<< IntKey1:32, IntKey2:32, Key3/binary >>).
-
--spec hixie76_key_to_integer(binary()) -> integer().
-hixie76_key_to_integer(Key) ->
- Number = list_to_integer([C || << C >> <= Key, C >= $0, C =< $9]),
- Spaces = length([C || << C >> <= Key, C =:= 32]),
- Number div Spaces.
-
-%% hybi specific.
-
--spec hybi_challenge(binary()) -> binary().
-hybi_challenge(Key) ->
- Bin = << Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" >>,
- base64:encode(crypto:sha(Bin)).
+ {ok, Req, [{result, closed}|Env]}.
--spec hybi_payload_length(0..16#7fffffffffffffff)
+-spec payload_length_to_binary(0..16#7fffffffffffffff)
-> << _:7 >> | << _:23 >> | << _:71 >>.
-hybi_payload_length(N) ->
+payload_length_to_binary(N) ->
case N of
N when N =< 125 -> << N:7 >>;
N when N =< 16#ffff -> << 126:7, N:16 >>;