Skip to content
This repository
Browse code

Merge pull request #74 from RJ/master

All my remaining hybi + proxy related changes
  • Loading branch information...
commit 23ff8a7108f4242ad8129ac0ba7d1c8c07f3aa37 2 parents d5233f0 + b2e7ba3
Roberto Ostinelli authored
8 src/misultin_acceptor.erl
@@ -231,7 +231,15 @@ get_peer_addr_port(Sock, SocketMode, CustomOpts) ->
231 231 % receive the first line, and extract peer address details as per http://haproxy.1wt.eu/download/1.5/doc/proxy-protocol.txt
232 232 -spec peername_from_proxy_line(Sock::socket(), SocketMode::socketmode()) -> {ok, {PeerAddr::inet:ip_address(), PeerPort::non_neg_integer()}} | {error, term()}.
233 233 peername_from_proxy_line(Sock, SocketMode) ->
  234 + %% Temporary socket options for reading PROXY line:
234 235 misultin_socket:setopts(Sock, [{active, once}, {packet, line}, list], SocketMode),
  236 + Val = parse_peername_from_proxy_line(Sock),
  237 + %% Set socket options back to previous values, set in misultin.erl
  238 + misultin_socket:setopts(Sock, [{active, false}, {packet, raw}, binary], SocketMode),
  239 + Val.
  240 +
  241 +-spec parse_peername_from_proxy_line(Sock::socket()) -> {ok, {PeerAddr::inet:ip_address(), PeerPort::non_neg_integer()}} | {error, term()}.
  242 +parse_peername_from_proxy_line(Sock) ->
235 243 receive
236 244 {TcpOrSsl, Sock, "PROXY " ++ ProxyLine} when TcpOrSsl =:= tcp; TcpOrSsl =:= ssl ->
237 245 case string:tokens(ProxyLine, "\r\n ") of
2  src/misultin_websocket.erl
@@ -221,8 +221,8 @@ ws_loop(WsHandleLoopPid, #ws{vsn = Vsn, socket = Socket, socket_mode = SocketMod
221 221 % close websocket and custom controlling loop
222 222 websocket_close(Socket, WsHandleLoopPid, SocketMode, WsAutoExit);
223 223 {send, Data} ->
224   - ?LOG_DEBUG("sending data to websocket: ~p", [Data]),
225 224 VsnMod = get_module_name_from_vsn(Vsn),
  225 + ?LOG_DEBUG("sending data: ~p to websocket module: ~p", [Data, VsnMod]),
226 226 misultin_socket:send(Socket, VsnMod:send_format(Data, State), SocketMode),
227 227 ws_loop(WsHandleLoopPid, Ws, State);
228 228 shutdown ->
10 src/misultin_websocket_draft-hixie-68.erl
@@ -81,14 +81,14 @@ handshake(#req{socket_mode = SocketMode, ws_force_ssl = WsForceSsl} = _Req, _Hea
81 81 ].
82 82
83 83 % ----------------------------------------------------------------------------------------------------------
84   -% Function: -> websocket_close | {websocket_close, DataToSendBeforeClose::binary() | iolist()} | NewStatus
  84 +% Function: -> websocket_close | {websocket_close, DataToSendBeforeClose::binary() | iolist()} | NewState
85 85 % Description: Callback to handle incomed data.
86 86 % ----------------------------------------------------------------------------------------------------------
87   --spec handle_data(Data::binary(), Status::undefined | term(), {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | term().
  87 +-spec handle_data(Data::binary(), State::undefined | term(), {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | term().
88 88 handle_data(Data, undefined, {Socket, SocketMode, WsHandleLoopPid}) ->
89 89 % init status
90 90 handle_data(Data, {buffer, none}, {Socket, SocketMode, WsHandleLoopPid});
91   -handle_data(Data, {buffer, B} = _Status, {Socket, SocketMode, WsHandleLoopPid}) ->
  91 +handle_data(Data, {buffer, B} = _State, {Socket, SocketMode, WsHandleLoopPid}) ->
92 92 % read status
93 93 i_handle_data(Data, B, {Socket, SocketMode, WsHandleLoopPid}).
94 94
@@ -96,8 +96,8 @@ handle_data(Data, {buffer, B} = _Status, {Socket, SocketMode, WsHandleLoopPid})
96 96 % Function: -> binary() | iolist()
97 97 % Description: Callback to format data before it is sent into the socket.
98 98 % ----------------------------------------------------------------------------------------------------------
99   --spec send_format(Data::iolist(), Status::term()) -> iolist().
100   -send_format(Data, _Status) ->
  99 +-spec send_format(Data::iolist(), State::term()) -> iolist().
  100 +send_format(Data, _State) ->
101 101 [0, Data, 255].
102 102
103 103 % ============================ /\ API ======================================================================
6 src/misultin_websocket_draft-hixie-76.erl
@@ -110,10 +110,10 @@ handshake(#req{socket = Sock, socket_mode = SocketMode, ws_force_ssl = WsForceSs
110 110 ].
111 111
112 112 % ----------------------------------------------------------------------------------------------------------
113   -% Function: -> websocket_close | {websocket_close, DataToSendBeforeClose::binary() | iolist()} | NewStatus
  113 +% Function: -> websocket_close | {websocket_close, DataToSendBeforeClose::binary() | iolist()} | NewState
114 114 % Description: Callback to handle incomed data.
115 115 % ----------------------------------------------------------------------------------------------------------
116   --spec handle_data(Data::binary(), Status::undefined | term(), {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | term().
  116 +-spec handle_data(Data::binary(), State::undefined | term(), {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | term().
117 117 handle_data(Data, undefined, {Socket, SocketMode, WsHandleLoopPid}) ->
118 118 % init status
119 119 handle_data(Data, {buffer, none}, {Socket, SocketMode, WsHandleLoopPid});
@@ -125,7 +125,7 @@ handle_data(Data, {buffer, B} = _State, {Socket, SocketMode, WsHandleLoopPid}) -
125 125 % Function: -> binary() | iolist()
126 126 % Description: Callback to format data before it is sent into the socket.
127 127 % ----------------------------------------------------------------------------------------------------------
128   --spec send_format(Data::iolist(), Status::term()) -> iolist().
  128 +-spec send_format(Data::iolist(), State::term()) -> iolist().
129 129 send_format(Data, _State) ->
130 130 [0, Data, 255].
131 131
200 src/misultin_websocket_draft-hybi-10.erl
@@ -3,8 +3,7 @@
3 3 %
4 4 % >-|-|-(°>
5 5 %
6   -% Copyright (C) 2011, Roberto Ostinelli <roberto@ostinelli.net>,
7   -% portions of code from Andy W. Song <https://github.com/awsong/erl_websocket>
  6 +% Copyright (C) 2011, Roberto Ostinelli <roberto@ostinelli.net>.
8 7 % All rights reserved.
9 8 %
10 9 % Code portions from Joe Armstrong have been originally taken under MIT license at the address:
@@ -38,19 +37,8 @@
38 37 % API
39 38 -export([check_websocket/1, handshake/3, handle_data/3, send_format/2]).
40 39
41   -% records
42   --record(state, {
43   - buffer,
44   - mask_key = <<0,0,0,0>>
45   -}).
46   -
47 40 % macros
48   --define(OP_CONT, 0).
49   --define(OP_TEXT, 1).
50   --define(OP_BIN, 2).
51   --define(OP_CLOSE, 8).
52   --define(OP_PING, 9).
53   --define(OP_PONG, 10).
  41 +-define(HYBI_COMMON, 'misultin_websocket_draft-hybi-10_17').
54 42
55 43 % includes
56 44 -include("../include/misultin.hrl").
@@ -69,197 +57,37 @@ check_websocket(Headers) ->
69 57 {'Upgrade', "websocket"}, {'Connection', "Upgrade"}, {'Host', ignore}, {'Sec-Websocket-Origin', ignore},
70 58 {'Sec-Websocket-Key', ignore}, {'Sec-WebSocket-Version', "8"}
71 59 ],
72   - % check for headers existance
73   - case misultin_websocket:check_headers(Headers, RequiredHeaders) of
74   - true -> true;
75   - _RemainingHeaders ->
76   - ?LOG_DEBUG("not this protocol, remaining headers: ~p", [_RemainingHeaders]),
77   - false
78   - end.
  60 + ?HYBI_COMMON:check_websocket(Headers, RequiredHeaders).
79 61
80 62 % ----------------------------------------------------------------------------------------------------------
81 63 % Function: -> iolist() | binary()
82 64 % Description: Callback to build handshake data.
83 65 % ----------------------------------------------------------------------------------------------------------
84 66 -spec handshake(Req::#req{}, Headers::http_headers(), {Path::string(), Origin::string(), Host::string()}) -> iolist().
85   -handshake(_Req, Headers, {_Path, _Origin, _Host}) ->
86   - % build data
87   - Key = list_to_binary(misultin_utility:header_get_value('Sec-WebSocket-Key', Headers)),
88   - Accept = base64:encode_to_string(crypto:sha(<<Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>)),
89   - ["HTTP/1.1 101 Switching Protocols\r\n",
90   - "Upgrade: websocket\r\n",
91   - "Connection: Upgrade\r\n",
92   - "Sec-WebSocket-Accept: ", Accept, "\r\n\r\n"
93   - ].
  67 +handshake(Req, Headers, {Path, Origin, Host}) ->
  68 + ?HYBI_COMMON:handshake(Req, Headers, {Path, Origin, Host}).
94 69
95 70 % ----------------------------------------------------------------------------------------------------------
96   -% Function: -> websocket_close | {websocket_close, DataToSendBeforeClose::binary() | iolist()} | NewStatus
  71 +% Function: -> websocket_close | {websocket_close, DataToSendBeforeClose::binary() | iolist()} | NewState
97 72 % Description: Callback to handle incomed data.
98 73 % ----------------------------------------------------------------------------------------------------------
99   --spec handle_data(Data::binary(), Status::undefined | term(), {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | term().
100   -handle_data(Data, undefined, {Socket, SocketMode, WsHandleLoopPid}) ->
101   - % init status
102   - handle_data(Data, #state{buffer = none}, {Socket, SocketMode, WsHandleLoopPid});
103   -handle_data(Data, State, {Socket, SocketMode, WsHandleLoopPid}) ->
104   - % read status
105   - i_handle_data(Data, State, {Socket, SocketMode, WsHandleLoopPid}).
  74 +-spec handle_data(Data::binary(),
  75 + State::undefined | term(),
  76 + {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | term().
  77 +handle_data(Data, St, Tuple) ->
  78 + ?HYBI_COMMON:handle_data(Data, St, Tuple).
106 79
107 80 % ----------------------------------------------------------------------------------------------------------
108 81 % Function: -> binary() | iolist()
109 82 % Description: Callback to format data before it is sent into the socket.
110 83 % ----------------------------------------------------------------------------------------------------------
111   --spec send_format(Data::iolist(), Status::term()) -> binary().
112   -send_format(Data, _State) ->
113   - send_format(Data, ?OP_TEXT, _State).
114   -send_format(Data, OpCode, _State) ->
115   - BData = erlang:iolist_to_binary(Data),
116   - Len = erlang:size(BData),
117   - if
118   - Len < 126 ->
119   - <<1:1, 0:3, OpCode:4, 0:1, Len:7, BData/binary>>;
120   - Len < 65536 ->
121   - <<1:1, 0:3, OpCode:4, 0:1, 126:7, Len:16, BData/binary>>;
122   - true ->
123   - <<1:1, 0:3, OpCode:4, 0:1, 127:7, 0:1, Len:63, BData/binary>>
124   - end.
  84 +-spec send_format(Data::iolist(), State::term()) -> binary().
  85 +send_format(Data, State) ->
  86 + ?HYBI_COMMON:send_format(Data, State).
125 87
126 88 % ============================ /\ API ======================================================================
127 89
128 90
129 91 % ============================ \/ INTERNAL FUNCTIONS =======================================================
130 92
131   -% 0 1 2 3
132   -% 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
133   -% +-+-+-+-+-------+-+-------------+-------------------------------+
134   -% |F|R|R|R| opcode|M| Payload len | Extended payload length |
135   -% |I|S|S|S| (4) |A| (7) | (16/63) |
136   -% |N|V|V|V| |S| | (if payload len==126/127) |
137   -% | |1|2|3| |K| | |
138   -% +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
139   -% | Extended payload length continued, if payload len == 127 |
140   -% + - - - - - - - - - - - - - - - +-------------------------------+
141   -% | |Masking-key, if MASK set to 1 |
142   -% +-------------------------------+-------------------------------+
143   -% | Masking-key (continued) | Payload Data |
144   -% +-------------------------------- - - - - - - - - - - - - - - - +
145   -% : Payload Data continued ... :
146   -% + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
147   -% | Payload Data continued ... |
148   -% +---------------------------------------------------------------+
149   -
150   -% handle incomed data
151   --spec i_handle_data(Data::binary(), State::undefined | term(), {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | term().
152   -i_handle_data(Data, #state{buffer = Buffer} = State, {Socket, SocketMode, WsHandleLoopPid}) when is_binary(Buffer) ->
153   - i_handle_data(<<Buffer/binary, Data/binary>>, State#state{buffer = none}, {Socket, SocketMode, WsHandleLoopPid});
154   -i_handle_data(<<Fin:1, 0:3, Opcode:4, 1:1, PayloadLen:7, MaskKey:4/binary, PayloadData/binary>>, State, {Socket, SocketMode, WsHandleLoopPid}) when PayloadLen < 126 andalso PayloadLen =< size(PayloadData) ->
155   - handle_frame(Fin, Opcode, PayloadLen, MaskKey, PayloadData, State#state{mask_key = MaskKey}, {Socket, SocketMode, WsHandleLoopPid});
156   -i_handle_data(<<Fin:1, 0:3, Opcode:4, 1:1, 126:7, PayloadLen:16, MaskKey:4/binary, PayloadData/binary>>, State, {Socket, SocketMode, WsHandleLoopPid}) when PayloadLen =< size(PayloadData) ->
157   - handle_frame(Fin, Opcode, PayloadLen, MaskKey, PayloadData, State#state{mask_key = MaskKey}, {Socket, SocketMode, WsHandleLoopPid});
158   -i_handle_data(<<Fin:1, 0:3, Opcode:4, 1:1, 127:7, 0:1, PayloadLen:63, MaskKey:4/binary, PayloadData/binary>>, State, {Socket, SocketMode, WsHandleLoopPid}) when PayloadLen =< size(PayloadData) ->
159   - handle_frame(Fin, Opcode, PayloadLen, MaskKey, PayloadData, State#state{mask_key = MaskKey}, {Socket, SocketMode, WsHandleLoopPid});
160   -i_handle_data(<<_Fin:1, 0:3, _Opcode:4, 0:1, _PayloadLen:7, _Data/binary>>, _State, {_Socket, _SocketMode, _WsHandleLoopPid}) ->
161   - ?LOG_DEBUG("client to server message was not sent masked, close websocket",[]),
162   - {websocket_close, websocket_close_data()};
163   -i_handle_data(Data, #state{buffer = none} = State, {_Socket, _SocketMode, _WsHandleLoopPid}) ->
164   - State#state{buffer = Data}.
165   -
166   -% handle frames
167   --spec handle_frame(
168   - Fin::integer(),
169   - Opcode::integer(),
170   - PayloadLen::integer(),
171   - MaskKey::binary(),
172   - PayloadData::binary(),
173   - State::term(),
174   - {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | term().
175   -handle_frame(1, ?OP_CONT, _Len, _MaskKey, _Data, _State, {_Socket, _SocketMode, _WsHandleLoopPid}) ->
176   - ?LOG_WARNING("received an unsupported segment ~p, closing websocket", [{1, ?OP_CONT}]),
177   - {websocket_close, websocket_close_data()};
178   -handle_frame(1, Opcode, Len, MaskKey, Data, State, {Socket, SocketMode, WsHandleLoopPid}) ->
179   - % frame without segment
180   - <<Data1:Len/binary, Rest/binary>> = Data,
181   - case Opcode of
182   - ?OP_BIN ->
183   - handle_frame_received_msg(MaskKey, Data1, Rest, State, {Socket, SocketMode, WsHandleLoopPid});
184   - ?OP_TEXT ->
185   - handle_frame_received_msg(MaskKey, Data1, Rest, State, {Socket, SocketMode, WsHandleLoopPid});
186   - ?OP_PING ->
187   - % ping
188   - misultin_socket:send(Socket, send_format(Data1, ?OP_PONG, State), SocketMode),
189   - handle_frame_continue(Rest, State, {Socket, SocketMode, WsHandleLoopPid});
190   - ?OP_CLOSE ->
191   - ?LOG_DEBUG("received a websocket close request",[]),
192   - websocket_close;
193   - _OpOther ->
194   - ?LOG_DEBUG("received segment with the unknown OpCode ~p, closing websocket", [_OpOther]),
195   - {websocket_close, websocket_close_data()}
196   - end;
197   -% first frame of a segment, TODO: comply to multiple segments
198   -handle_frame(0, _Opcode, _Len, _MaskKey, _Data, _State, {_Socket, _SocketMode, _WsHandleLoopPid}) ->
199   - ?LOG_WARNING("received an unsupported continuation segment with opcode ~p, closing websocket", [{0, _Opcode}]),
200   - {websocket_close, websocket_close_data()}.
201   -
202   -% received a message, send to websocket process
203   --spec handle_frame_received_msg(
204   - MaskKey::binary(),
205   - Data::binary(),
206   - Rest::binary(),
207   - State::term(),
208   - {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | #state{}.
209   -handle_frame_received_msg(MaskKey, Data, Rest, State, {Socket, SocketMode, WsHandleLoopPid}) ->
210   - Unmasked = binary_to_list(unmask(MaskKey, Data)),
211   - ?LOG_DEBUG("received message from client: ~p", [Unmasked]),
212   - misultin_websocket:send_to_browser(WsHandleLoopPid, Unmasked),
213   - handle_frame_continue(Rest, State, {Socket, SocketMode, WsHandleLoopPid}).
214   -
215   -% continue with rest of data
216   --spec handle_frame_continue(
217   - Rest::binary(),
218   - State::term(),
219   - {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | #state{}.
220   -handle_frame_continue(Rest, State, {Socket, SocketMode, WsHandleLoopPid}) ->
221   - case Rest of
222   - <<>> -> State#state{buffer = none};
223   - _ -> i_handle_data(Rest, State#state{buffer = none}, {Socket, SocketMode, WsHandleLoopPid})
224   - end.
225   -
226   -% unmask
227   --spec unmask(Key::binary(), Data::binary()) -> binary().
228   -unmask(Key, <<_:512,_Rest/binary>> = Data) ->
229   - K = binary:copy(Key, 512 div 32),
230   - <<LongKey:512>> = K,
231   - <<ShortKey:32>> = Key,
232   - unmask(ShortKey, LongKey, Data, <<>>);
233   -unmask(Key, Data) ->
234   - <<ShortKey:32>> = Key,
235   - unmask(ShortKey,none, Data, <<>>).
236   -unmask(Key, LongKey, Data, Accu) ->
237   - case Data of
238   - <<A:512, Rest/binary>> ->
239   - C = A bxor LongKey,
240   - unmask(Key, LongKey, Rest, <<Accu/binary, C:512>>);
241   - <<A:32,Rest/binary>> ->
242   - C = A bxor Key,
243   - unmask(Key, LongKey, Rest, <<Accu/binary, C:32>>);
244   - <<A:24>> ->
245   - <<B:24, _:8>> = <<Key:32>>,
246   - C = A bxor B,
247   - <<Accu/binary, C:24>>;
248   - <<A:16>> ->
249   - <<B:16, _:16>> = <<Key:32>>,
250   - C = A bxor B,
251   - <<Accu/binary, C:16>>;
252   - <<A:8>> ->
253   - <<B:8, _:24>> = <<Key:32>>,
254   - C = A bxor B,
255   - <<Accu/binary, C:8>>;
256   - <<>> ->
257   - Accu
258   - end.
259   -
260   -% websocket close data
261   --spec websocket_close_data() -> binary().
262   -websocket_close_data() ->
263   - <<1:1, 0:3, ?OP_CLOSE:4, 0:1, 0:7>>.
264   -
265 93 % ============================ /\ INTERNAL FUNCTIONS =======================================================
402 src/misultin_websocket_draft-hybi-10_17.erl
... ... @@ -0,0 +1,402 @@
  1 +% ==========================================================================================================
  2 +% MISULTIN - WebSocket - common implementation patterns of draft hybi 10 and 17
  3 +%
  4 +% >-|-|-(°>
  5 +%
  6 +% Copyright (C) 2011, Richard Jones <rj@metabrew.com>, Roberto Ostinelli <roberto@ostinelli.net>,
  7 +% portions of code from Andy W. Song <https://github.com/awsong/erl_websocket>
  8 +% All rights reserved.
  9 +%
  10 +% Code portions from Joe Armstrong have been originally taken under MIT license at the address:
  11 +% <http://armstrongonsoftware.blogspot.com/2009/12/comet-is-dead-long-live-websockets.html>
  12 +%
  13 +% BSD License
  14 +%
  15 +% Redistribution and use in source and binary forms, with or without modification, are permitted provided
  16 +% that the following conditions are met:
  17 +%
  18 +% * Redistributions of source code must retain the above copyright notice, this list of conditions and the
  19 +% following disclaimer.
  20 +% * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and
  21 +% the following disclaimer in the documentation and/or other materials provided with the distribution.
  22 +% * Neither the name of the authors nor the names of its contributors may be used to endorse or promote
  23 +% products derived from this software without specific prior written permission.
  24 +%
  25 +% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED
  26 +% WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
  27 +% PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
  28 +% ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
  29 +% TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  30 +% HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
  31 +% NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  32 +% POSSIBILITY OF SUCH DAMAGE.
  33 +% ==========================================================================================================
  34 +-module('misultin_websocket_draft-hybi-10_17').
  35 +-vsn("0.9-dev").
  36 +
  37 +% API
  38 +-export([check_websocket/2, handshake/3, handle_data/3, send_format/2]).
  39 +
  40 +% records
  41 +-record(state, {
  42 + buffer = <<>>,
  43 + mask_key = <<0,0,0,0>>,
  44 + fragments = [] %% if we are in the midst of receving a fragmented message, fragments are contained here in reverse order
  45 +}).
  46 +
  47 +-record(frame, {fin,
  48 + rsv1,
  49 + rsv2,
  50 + rsv3,
  51 + opcode,
  52 + maskbit,
  53 + length,
  54 + maskkey,
  55 + data}).
  56 +
  57 +% macros
  58 +-define(OP_CONT, 0).
  59 +-define(OP_TEXT, 1).
  60 +-define(OP_BIN, 2).
  61 +-define(OP_CLOSE, 8).
  62 +-define(OP_PING, 9).
  63 +-define(OP_PONG, 10).
  64 +
  65 +-define(IS_CONTROL_OPCODE(X), ((X band 8)=:=8) ).
  66 +
  67 +%% If we don't find a websocket protocol frame in this many bytes, connection aborts
  68 +-define(MAX_UNPARSED_BUFFER_SIZE, 1024 * 100).
  69 +
  70 +% includes
  71 +-include("../include/misultin.hrl").
  72 +
  73 +
  74 +% ============================ \/ API ======================================================================
  75 +
  76 +% ----------------------------------------------------------------------------------------------------------
  77 +% Function: -> true | false
  78 +% Description: Callback to check if the incoming request is a websocket request according to this protocol.
  79 +% ----------------------------------------------------------------------------------------------------------
  80 +-spec check_websocket(Headers::http_headers(), RequiredHeaders::http_headers()) -> boolean().
  81 +check_websocket(Headers, RequiredHeaders) ->
  82 + % check for headers existance
  83 + case misultin_websocket:check_headers(Headers, RequiredHeaders) of
  84 + true -> true;
  85 + _RemainingHeaders ->
  86 + ?LOG_DEBUG("not this protocol, remaining headers: ~p", [_RemainingHeaders]),
  87 + false
  88 + end.
  89 +
  90 +% ----------------------------------------------------------------------------------------------------------
  91 +% Function: -> iolist() | binary()
  92 +% Description: Callback to build handshake data.
  93 +% ----------------------------------------------------------------------------------------------------------
  94 +-spec handshake(Req::#req{}, Headers::http_headers(), {Path::string(), Origin::string(), Host::string()}) -> iolist().
  95 +handshake(_Req, Headers, {_Path, _Origin, _Host}) ->
  96 + % build data
  97 + Key = list_to_binary(misultin_utility:header_get_value('Sec-WebSocket-Key', Headers)),
  98 + Accept = base64:encode_to_string(crypto:sha(<<Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>)),
  99 + ["HTTP/1.1 101 Switching Protocols\r\n",
  100 + "Upgrade: websocket\r\n",
  101 + "Connection: Upgrade\r\n",
  102 + "Sec-WebSocket-Accept: ", Accept, "\r\n\r\n"
  103 + ].
  104 +
  105 +% ----------------------------------------------------------------------------------------------------------
  106 +% Function: -> websocket_close | {websocket_close, DataToSendBeforeClose::binary() | iolist()} | NewState
  107 +% Description: Callback to handle incomed data.
  108 +% ----------------------------------------------------------------------------------------------------------
  109 +-spec handle_data(Data::binary() | list(),
  110 + State::undefined | term(),
  111 + {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | term().
  112 +handle_data(Data, St, Tuple) when is_list(Data) ->
  113 + handle_data(list_to_binary(Data), St, Tuple);
  114 +handle_data(Data, undefined, {Socket, SocketMode, WsHandleLoopPid}) when is_binary(Data) ->
  115 + % init status
  116 + i_handle_data(#state{buffer = Data}, {Socket, SocketMode, WsHandleLoopPid});
  117 +handle_data(Data, State = #state{buffer = Buffer}, {Socket, SocketMode, WsHandleLoopPid}) when is_binary(Data) ->
  118 + % read status
  119 + i_handle_data(State#state{buffer = <<Buffer/binary, Data/binary>>}, {Socket, SocketMode, WsHandleLoopPid}).
  120 +
  121 +% ----------------------------------------------------------------------------------------------------------
  122 +% Function: -> binary() | iolist()
  123 +% Description: Callback to format data before it is sent into the socket.
  124 +% ----------------------------------------------------------------------------------------------------------
  125 +-spec send_format(Data::iolist(), State::term()) -> binary().
  126 +send_format(Data, _State) ->
  127 + send_format(Data, ?OP_TEXT, _State).
  128 +send_format(Data, OpCode, _State) ->
  129 + BData = erlang:iolist_to_binary(Data),
  130 + Len = erlang:size(BData),
  131 + if
  132 + Len < 126 ->
  133 + <<1:1, 0:3, OpCode:4, 0:1, Len:7, BData/binary>>;
  134 + Len < 65536 ->
  135 + <<1:1, 0:3, OpCode:4, 0:1, 126:7, Len:16, BData/binary>>;
  136 + true ->
  137 + <<1:1, 0:3, OpCode:4, 0:1, 127:7, 0:1, Len:63, BData/binary>>
  138 + end.
  139 +% ============================ /\ API ======================================================================
  140 +
  141 +
  142 +% ============================ \/ INTERNAL FUNCTIONS =======================================================
  143 +
  144 +% 0 1 2 3
  145 +% 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
  146 +% +-+-+-+-+-------+-+-------------+-------------------------------+
  147 +% |F|R|R|R| opcode|M| Payload len | Extended payload length |
  148 +% |I|S|S|S| (4) |A| (7) | (16/63) |
  149 +% |N|V|V|V| |S| | (if payload len==126/127) |
  150 +% | |1|2|3| |K| | |
  151 +% +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
  152 +% | Extended payload length continued, if payload len == 127 |
  153 +% + - - - - - - - - - - - - - - - +-------------------------------+
  154 +% | |Masking-key, if MASK set to 1 |
  155 +% +-------------------------------+-------------------------------+
  156 +% | Masking-key (continued) | Payload Data |
  157 +% +-------------------------------- - - - - - - - - - - - - - - - +
  158 +% : Payload Data continued ... :
  159 +% + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
  160 +% | Payload Data continued ... |
  161 +% +---------------------------------------------------------------+
  162 +
  163 +
  164 +% ---------------------------- \/ frame parsing ------------------------------------------------------------
  165 +
  166 +% parse received data and get the frames
  167 +-spec take_frame(Data::binary()) -> {#frame{} | undefined, Rest::binary()}.
  168 +% normal length
  169 +take_frame(<<Fin:1,
  170 + Rsv1:1, %% Rsv1 = 0
  171 + Rsv2:1, %% Rsv2 = 0
  172 + Rsv3:1, %% Rsv3 = 0
  173 + Opcode:4,
  174 + MaskBit:1, %% must be 1
  175 + PayloadLen:7,
  176 + MaskKey:4/binary,
  177 + PayloadData:PayloadLen/binary-unit:8,
  178 + Rest/binary>>) when PayloadLen < 126 ->
  179 + %% Don't auto-unmask control frames
  180 + Data = case ?IS_CONTROL_OPCODE(Opcode) of
  181 + true -> PayloadData;
  182 + false -> unmask(MaskKey,PayloadData)
  183 + end,
  184 + {#frame{fin=Fin,
  185 + rsv1=Rsv1,
  186 + rsv2=Rsv2,
  187 + rsv3=Rsv3,
  188 + opcode=Opcode,
  189 + maskbit=MaskBit,
  190 + length=PayloadLen,
  191 + maskkey=MaskKey,
  192 + data = Data}, Rest};
  193 +% extended payload (126)
  194 +take_frame(<<Fin:1,
  195 + Rsv1:1, %% Rsv1 = 0
  196 + Rsv2:1, %% Rsv2 = 0
  197 + Rsv3:1, %% Rsv3 = 0
  198 + Opcode:4,
  199 + MaskBit:1, %% must be 1
  200 + 126:7,
  201 + PayloadLen:16,
  202 + MaskKey:4/binary,
  203 + PayloadData:PayloadLen/binary-unit:8,
  204 + Rest/binary>>) ->
  205 + {#frame{fin=Fin,
  206 + rsv1=Rsv1,
  207 + rsv2=Rsv2,
  208 + rsv3=Rsv3,
  209 + opcode=Opcode,
  210 + maskbit=MaskBit,
  211 + length=PayloadLen,
  212 + maskkey=MaskKey,
  213 + data=unmask(MaskKey,PayloadData)}, Rest};
  214 +% extended payload (127)
  215 +take_frame(<<Fin:1,
  216 + Rsv1:1, %% Rsv1 = 0
  217 + Rsv2:1, %% Rsv2 = 0
  218 + Rsv3:1, %% Rsv3 = 0
  219 + Opcode:4,
  220 + MaskBit:1, %% must be 1
  221 + 127:7, %% "If 127, the following 8 bytes interpreted as a 64-bit unsigned integer (the most significant bit MUST be 0)"
  222 + 0:1, %% MSB of 0
  223 + PayloadLen:63,
  224 + MaskKey:4/binary,
  225 + PayloadData:PayloadLen/binary-unit:8,
  226 + Rest/binary>>) ->
  227 + {#frame{fin=Fin,
  228 + rsv1=Rsv1,
  229 + rsv2=Rsv2,
  230 + rsv3=Rsv3,
  231 + opcode=Opcode,
  232 + maskbit=MaskBit,
  233 + length=PayloadLen,
  234 + maskkey=MaskKey,
  235 + data=unmask(MaskKey, PayloadData)}, Rest};
  236 +% incomplete frame
  237 +take_frame(Data) when is_binary(Data), size(Data) < ?MAX_UNPARSED_BUFFER_SIZE ->
  238 + {undefined, Data};
  239 +% Try to prevent denial-of-service from clients that send an infinite stream of
  240 +% incompatible data
  241 +take_frame(Data) when is_binary(Data), size(Data) >= ?MAX_UNPARSED_BUFFER_SIZE ->
  242 + {error, max_size_reached}.
  243 +
  244 +% process incoming data
  245 +-spec i_handle_data(#state{}, {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> #state{} | {websocket_close, term()}.
  246 +i_handle_data(#state{buffer=ToParse} = State, {Socket, SocketMode, WsHandleLoopPid}) ->
  247 + case take_frame(ToParse) of
  248 + {error, max_size_reached} ->
  249 + ?LOG_DEBUG("reached max unparsed buffer size, aborting connection", []),
  250 + {websocket_close, websocket_close_data()};
  251 + {undefined, Rest} ->
  252 + ?LOG_DEBUG("no frame to take, add to buffer: ~p", [Rest]),
  253 + %% no full frame to be had yet
  254 + State#state{buffer = Rest};
  255 + {Frame=#frame{}, Rest} ->
  256 + ?LOG_DEBUG("parsed frame ~p, remaining buffer is: ~p", [Frame,Rest]),
  257 + %% sanity check, in case client is broken
  258 + case sanity_check(Frame) of
  259 + true ->
  260 + ?LOG_DEBUG("sanity checks successfully performed",[]),
  261 + case handle_frame(Frame,
  262 + State#state{buffer = Rest},
  263 + {Socket, SocketMode, WsHandleLoopPid}) of
  264 + %% tail-call if there is stuff in the buffer still to parse
  265 + NewState = #state{buffer = B} when is_binary(B), B =/= <<>> ->
  266 + i_handle_data(NewState, {Socket, SocketMode, WsHandleLoopPid});
  267 + Other ->
  268 + Other
  269 + end;
  270 + false -> % protocol error
  271 + ?LOG_DEBUG("sanity checks errors encountered, closing websocket",[]),
  272 + {websocket_close, websocket_close_data()}
  273 + end
  274 + end.
  275 +
  276 +% format sanity checks
  277 +-spec sanity_check(#frame{}) -> true | false.
  278 +sanity_check(Frame) ->
  279 + Checks = [
  280 + {1, Frame#frame.maskbit},
  281 + {0, Frame#frame.rsv1},
  282 + {0, Frame#frame.rsv2},
  283 + {0, Frame#frame.rsv3}
  284 + ],
  285 + lists:foldl(fun({A,B}, Acc) -> Acc andalso (A =:= B) end, true, Checks).
  286 +
  287 +% ---------------------------- /\ frame parsing ------------------------------------------------------------
  288 +
  289 +% ---------------------------- \/ fragment handling --------------------------------------------------------
  290 +
  291 +-spec handle_frame(#frame{}, #state{}, {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> #state{} | {websocket_close, term()}.
  292 +%% FRAGMENT - add to the list and carry on
  293 +%% "A fragmented message consists of a single frame with the FIN bit
  294 +%% clear and an opcode other than 0, followed by zero or more frames
  295 +%% with the FIN bit clear and the opcode set to 0, and terminated by
  296 +%% a single frame with the FIN bit set and an opcode of 0"
  297 +handle_frame(#frame{fin = 0, opcode = Opcode}, %% first fragment
  298 + State = #state{fragments = []} = Frame,
  299 + _) when Opcode =/= ?OP_CONT ->
  300 + ?LOG_DEBUG("first fragment: ~p", [Frame]),
  301 + State#state{fragments = [Frame]};
  302 +handle_frame(#frame{fin = 0, opcode = ?OP_CONT}, %% subsequent fragments
  303 + State = #state{fragments = Frags} = Frame,
  304 + _) when Frags =/= [] ->
  305 + ?LOG_DEBUG("next fragment: ~p", [Frame]),
  306 + State#state{fragments = [Frame | Frags]};
  307 +
  308 +%% Last frame in a fragmented message.
  309 +%% reassemble one large frame based on all the fragments, keeping opcode from first:
  310 +handle_frame(#frame{fin = 1, opcode = ?OP_CONT } = F,
  311 + State = #state{fragments = Frags},
  312 + {Socket, SocketMode, WsHandleLoopPid}) when Frags =/= [] ->
  313 + [Frame1|Frames] = lists:reverse([F|Frags]),
  314 + Frame = lists:foldl(
  315 + fun(#frame{length = L, data = D}, AccF) ->
  316 + %% NB: we unmask data as we parse frames, so concating here is ok:
  317 + AccF#frame{length = (AccF#frame.length + L), data = << (AccF#frame.data)/binary, D/binary >>}
  318 + end,
  319 + Frame1#frame{fin=1},
  320 + Frames
  321 + ),
  322 + ?LOG_DEBUG("final fragment, assembled: ~p",[Frame]),
  323 + %% now process this new combined message as if we got it all at once:
  324 + handle_frame(Frame, State#state{fragments = []}, {Socket, SocketMode, WsHandleLoopPid});
  325 +
  326 +%% end of fragments but no fragments stored - error
  327 +handle_frame(#frame{fin = 1, opcode = ?OP_CONT}, _, _) ->
  328 + %% closing, should only happen if client is broken
  329 + {websocket_close, websocket_close_data()};
  330 +
  331 +% ---------------------------- /\ fragment handling --------------------------------------------------------
  332 +
  333 +% ---------------------------- \/ frame handling -----------------------------------------------------------
  334 +
  335 +%% CONTROL FRAMES: 1) cannot be fragmented, thus have size <= 125bytes
  336 +%% 2) have an opcode where MSB is set
  337 +%% 3) can appear between larger fragmented message frames
  338 +handle_frame(#frame{fin=1, opcode=Opcode, data=Data},
  339 + State,
  340 + {Socket, SocketMode, _WsHandleLoopPid}) when ?IS_CONTROL_OPCODE(Opcode) ->
  341 + %% handle all known control opcodes:
  342 + case Opcode of
  343 + ?OP_PING ->
  344 + misultin_socket:send(Socket, send_format(Data, ?OP_PONG, State), SocketMode),
  345 + State;
  346 + ?OP_CLOSE ->
  347 + ?LOG_DEBUG("received a websocket close request",[]),
  348 + websocket_close;
  349 + _OpOther ->
  350 + ?LOG_DEBUG("received segment with the unknown control OpCode ~p, closing websocket", [_OpOther]),
  351 + {websocket_close, websocket_close_data()}
  352 + end;
  353 +
  354 +%% NORMAL FRAME (not a fragment, not a control frame)
  355 +handle_frame(#frame{fin=1, opcode=Opcode, data=Data},
  356 + State,
  357 + {_Socket, _SocketMode, WsHandleLoopPid}) when Opcode =:= ?OP_BIN; Opcode =:= ?OP_TEXT ->
  358 + misultin_websocket:send_to_browser(WsHandleLoopPid, binary_to_list(Data)),
  359 + State.
  360 +
  361 +% ---------------------------- /\ frame handling -----------------------------------------------------------
  362 +
  363 +% unmask
  364 +-spec unmask(Key::binary(), Data::binary()) -> binary().
  365 +unmask(Key, <<_:512,_Rest/binary>> = Data) ->
  366 + K = binary:copy(Key, 512 div 32),
  367 + <<LongKey:512>> = K,
  368 + <<ShortKey:32>> = Key,
  369 + unmask(ShortKey, LongKey, Data, <<>>);
  370 +unmask(Key, Data) ->
  371 + <<ShortKey:32>> = Key,
  372 + unmask(ShortKey,none, Data, <<>>).
  373 +unmask(Key, LongKey, Data, Accu) ->
  374 + case Data of
  375 + <<A:512, Rest/binary>> ->
  376 + C = A bxor LongKey,
  377 + unmask(Key, LongKey, Rest, <<Accu/binary, C:512>>);
  378 + <<A:32,Rest/binary>> ->
  379 + C = A bxor Key,
  380 + unmask(Key, LongKey, Rest, <<Accu/binary, C:32>>);
  381 + <<A:24>> ->
  382 + <<B:24, _:8>> = <<Key:32>>,
  383 + C = A bxor B,
  384 + <<Accu/binary, C:24>>;
  385 + <<A:16>> ->
  386 + <<B:16, _:16>> = <<Key:32>>,
  387 + C = A bxor B,
  388 + <<Accu/binary, C:16>>;
  389 + <<A:8>> ->
  390 + <<B:8, _:24>> = <<Key:32>>,
  391 + C = A bxor B,
  392 + <<Accu/binary, C:8>>;
  393 + <<>> ->
  394 + Accu
  395 + end.
  396 +
  397 +% websocket close data
  398 +-spec websocket_close_data() -> binary().
  399 +websocket_close_data() ->
  400 + <<1:1, 0:3, ?OP_CLOSE:4, 0:1, 0:7>>.
  401 +
  402 +% ============================ /\ INTERNAL FUNCTIONS =======================================================
202 src/misultin_websocket_draft-hybi-17.erl
... ... @@ -1,10 +1,9 @@
1 1 % ==========================================================================================================
2   -% MISULTIN - WebSocket - draft hybi 10
  2 +% MISULTIN - WebSocket - draft hybi 17
3 3 %
4 4 % >-|-|-(°>
5 5 %
6   -% Copyright (C) 2011, Roberto Ostinelli <roberto@ostinelli.net>,
7   -% portions of code from Andy W. Song <https://github.com/awsong/erl_websocket>
  6 +% Copyright (C) 2011, Roberto Ostinelli <roberto@ostinelli.net>.
8 7 % All rights reserved.
9 8 %
10 9 % Code portions from Joe Armstrong have been originally taken under MIT license at the address:
@@ -38,19 +37,8 @@
38 37 % API
39 38 -export([check_websocket/1, handshake/3, handle_data/3, send_format/2]).
40 39
41   -% records
42   --record(state, {
43   - buffer,
44   - mask_key = <<0,0,0,0>>
45   -}).
46   -
47 40 % macros
48   --define(OP_CONT, 0).
49   --define(OP_TEXT, 1).
50   --define(OP_BIN, 2).
51   --define(OP_CLOSE, 8).
52   --define(OP_PING, 9).
53   --define(OP_PONG, 10).
  41 +-define(HYBI_COMMON, 'misultin_websocket_draft-hybi-10_17').
54 42
55 43 % includes
56 44 -include("../include/misultin.hrl").
@@ -69,197 +57,37 @@ check_websocket(Headers) ->
69 57 {'Upgrade', "websocket"}, {'Connection', "Upgrade"}, {'Host', ignore},
70 58 {'Sec-Websocket-Key', ignore}, {'Sec-WebSocket-Version', "13"}
71 59 ],
72   - % check for headers existance
73   - case misultin_websocket:check_headers(Headers, RequiredHeaders) of
74   - true -> true;
75   - _RemainingHeaders ->
76   - ?LOG_DEBUG("not this protocol, remaining headers: ~p", [_RemainingHeaders]),
77   - false
78   - end.
  60 + ?HYBI_COMMON:check_websocket(Headers, RequiredHeaders).
79 61
80 62 % ----------------------------------------------------------------------------------------------------------
81 63 % Function: -> iolist() | binary()
82 64 % Description: Callback to build handshake data.
83 65 % ----------------------------------------------------------------------------------------------------------
84 66 -spec handshake(Req::#req{}, Headers::http_headers(), {Path::string(), Origin::string(), Host::string()}) -> iolist().
85   -handshake(_Req, Headers, {_Path, _Origin, _Host}) ->
86   - % build data
87   - Key = list_to_binary(misultin_utility:header_get_value('Sec-WebSocket-Key', Headers)),
88   - Accept = base64:encode_to_string(crypto:sha(<<Key/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>)),
89   - ["HTTP/1.1 101 Switching Protocols\r\n",
90   - "Upgrade: websocket\r\n",
91   - "Connection: Upgrade\r\n",
92   - "Sec-WebSocket-Accept: ", Accept, "\r\n\r\n"
93   - ].
  67 +handshake(Req, Headers, {Path, Origin, Host}) ->
  68 + ?HYBI_COMMON:handshake(Req, Headers, {Path, Origin, Host}).
94 69
95 70 % ----------------------------------------------------------------------------------------------------------
96   -% Function: -> websocket_close | {websocket_close, DataToSendBeforeClose::binary() | iolist()} | NewStatus
  71 +% Function: -> websocket_close | {websocket_close, DataToSendBeforeClose::binary() | iolist()} | NewState
97 72 % Description: Callback to handle incomed data.
98 73 % ----------------------------------------------------------------------------------------------------------
99   --spec handle_data(Data::binary(), Status::undefined | term(), {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | term().
100   -handle_data(Data, undefined, {Socket, SocketMode, WsHandleLoopPid}) ->
101   - % init status
102   - handle_data(Data, #state{buffer = none}, {Socket, SocketMode, WsHandleLoopPid});
103   -handle_data(Data, State, {Socket, SocketMode, WsHandleLoopPid}) ->
104   - % read status
105   - i_handle_data(Data, State, {Socket, SocketMode, WsHandleLoopPid}).
  74 +-spec handle_data(Data::binary(),
  75 + State::undefined | term(),
  76 + {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | term().
  77 +handle_data(Data, St, Tuple) ->
  78 + ?HYBI_COMMON:handle_data(Data, St, Tuple).
106 79
107 80 % ----------------------------------------------------------------------------------------------------------
108 81 % Function: -> binary() | iolist()
109 82 % Description: Callback to format data before it is sent into the socket.
110 83 % ----------------------------------------------------------------------------------------------------------
111   --spec send_format(Data::iolist(), Status::term()) -> binary().
112   -send_format(Data, _State) ->
113   - send_format(Data, ?OP_TEXT, _State).
114   -send_format(Data, OpCode, _State) ->
115   - BData = erlang:iolist_to_binary(Data),
116   - Len = erlang:size(BData),
117   - if
118   - Len < 126 ->
119   - <<1:1, 0:3, OpCode:4, 0:1, Len:7, BData/binary>>;
120   - Len < 65536 ->
121   - <<1:1, 0:3, OpCode:4, 0:1, 126:7, Len:16, BData/binary>>;
122   - true ->
123   - <<1:1, 0:3, OpCode:4, 0:1, 127:7, 0:1, Len:63, BData/binary>>
124   - end.
  84 +-spec send_format(Data::iolist(), State::term()) -> binary().
  85 +send_format(Data, State) ->
  86 + ?HYBI_COMMON:send_format(Data, State).
125 87
126 88 % ============================ /\ API ======================================================================
127 89
128 90
129 91 % ============================ \/ INTERNAL FUNCTIONS =======================================================
130 92
131   -% 0 1 2 3
132   -% 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
133   -% +-+-+-+-+-------+-+-------------+-------------------------------+
134   -% |F|R|R|R| opcode|M| Payload len | Extended payload length |
135   -% |I|S|S|S| (4) |A| (7) | (16/63) |
136   -% |N|V|V|V| |S| | (if payload len==126/127) |
137   -% | |1|2|3| |K| | |
138   -% +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
139   -% | Extended payload length continued, if payload len == 127 |
140   -% + - - - - - - - - - - - - - - - +-------------------------------+
141   -% | |Masking-key, if MASK set to 1 |
142   -% +-------------------------------+-------------------------------+
143   -% | Masking-key (continued) | Payload Data |
144   -% +-------------------------------- - - - - - - - - - - - - - - - +
145   -% : Payload Data continued ... :
146   -% + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
147   -% | Payload Data continued ... |
148   -% +---------------------------------------------------------------+
149   -
150   -% handle incomed data
151   --spec i_handle_data(Data::binary(), State::undefined | term(), {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | term().
152   -i_handle_data(Data, #state{buffer = Buffer} = State, {Socket, SocketMode, WsHandleLoopPid}) when is_binary(Buffer) ->
153   - i_handle_data(<<Buffer/binary, Data/binary>>, State#state{buffer = none}, {Socket, SocketMode, WsHandleLoopPid});
154   -i_handle_data(<<Fin:1, 0:3, Opcode:4, 1:1, PayloadLen:7, MaskKey:4/binary, PayloadData/binary>>, State, {Socket, SocketMode, WsHandleLoopPid}) when PayloadLen < 126 andalso PayloadLen =< size(PayloadData) ->
155   - handle_frame(Fin, Opcode, PayloadLen, MaskKey, PayloadData, State#state{mask_key = MaskKey}, {Socket, SocketMode, WsHandleLoopPid});
156   -i_handle_data(<<Fin:1, 0:3, Opcode:4, 1:1, 126:7, PayloadLen:16, MaskKey:4/binary, PayloadData/binary>>, State, {Socket, SocketMode, WsHandleLoopPid}) when PayloadLen =< size(PayloadData) ->
157   - handle_frame(Fin, Opcode, PayloadLen, MaskKey, PayloadData, State#state{mask_key = MaskKey}, {Socket, SocketMode, WsHandleLoopPid});
158   -i_handle_data(<<Fin:1, 0:3, Opcode:4, 1:1, 127:7, 0:1, PayloadLen:63, MaskKey:4/binary, PayloadData/binary>>, State, {Socket, SocketMode, WsHandleLoopPid}) when PayloadLen =< size(PayloadData) ->
159   - handle_frame(Fin, Opcode, PayloadLen, MaskKey, PayloadData, State#state{mask_key = MaskKey}, {Socket, SocketMode, WsHandleLoopPid});
160   -i_handle_data(<<_Fin:1, 0:3, _Opcode:4, 0:1, _PayloadLen:7, _Data/binary>>, _State, {_Socket, _SocketMode, _WsHandleLoopPid}) ->
161   - ?LOG_DEBUG("client to server message was not sent masked, close websocket",[]),
162   - {websocket_close, websocket_close_data()};
163   -i_handle_data(Data, #state{buffer = none} = State, {_Socket, _SocketMode, _WsHandleLoopPid}) ->
164   - State#state{buffer = Data}.
165   -
166   -% handle frames
167   --spec handle_frame(
168   - Fin::integer(),
169   - Opcode::integer(),
170   - PayloadLen::integer(),
171   - MaskKey::binary(),
172   - PayloadData::binary(),
173   - State::term(),
174   - {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | term().
175   -handle_frame(1, ?OP_CONT, _Len, _MaskKey, _Data, _State, {_Socket, _SocketMode, _WsHandleLoopPid}) ->
176   - ?LOG_WARNING("received an unsupported segment ~p, closing websocket", [{1, ?OP_CONT}]),
177   - {websocket_close, websocket_close_data()};
178   -handle_frame(1, Opcode, Len, MaskKey, Data, State, {Socket, SocketMode, WsHandleLoopPid}) ->
179   - % frame without segment
180   - <<Data1:Len/binary, Rest/binary>> = Data,
181   - case Opcode of
182   - ?OP_BIN ->
183   - handle_frame_received_msg(MaskKey, Data1, Rest, State, {Socket, SocketMode, WsHandleLoopPid});
184   - ?OP_TEXT ->
185   - handle_frame_received_msg(MaskKey, Data1, Rest, State, {Socket, SocketMode, WsHandleLoopPid});
186   - ?OP_PING ->
187   - % ping
188   - misultin_socket:send(Socket, send_format(Data1, ?OP_PONG, State), SocketMode),
189   - handle_frame_continue(Rest, State, {Socket, SocketMode, WsHandleLoopPid});
190   - ?OP_CLOSE ->
191   - ?LOG_DEBUG("received a websocket close request",[]),
192   - websocket_close;
193   - _OpOther ->
194   - ?LOG_DEBUG("received segment with the unknown OpCode ~p, closing websocket", [_OpOther]),
195   - {websocket_close, websocket_close_data()}
196   - end;
197   -% first frame of a segment, TODO: comply to multiple segments
198   -handle_frame(0, _Opcode, _Len, _MaskKey, _Data, _State, {_Socket, _SocketMode, _WsHandleLoopPid}) ->
199   - ?LOG_WARNING("received an unsupported continuation segment with opcode ~p, closing websocket", [{0, _Opcode}]),
200   - {websocket_close, websocket_close_data()}.
201   -
202   -% received a message, send to websocket process
203   --spec handle_frame_received_msg(
204   - MaskKey::binary(),
205   - Data::binary(),
206   - Rest::binary(),
207   - State::term(),
208   - {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | #state{}.
209   -handle_frame_received_msg(MaskKey, Data, Rest, State, {Socket, SocketMode, WsHandleLoopPid}) ->
210   - Unmasked = binary_to_list(unmask(MaskKey, Data)),
211   - ?LOG_DEBUG("received message from client: ~p", [Unmasked]),
212   - misultin_websocket:send_to_browser(WsHandleLoopPid, Unmasked),
213   - handle_frame_continue(Rest, State, {Socket, SocketMode, WsHandleLoopPid}).
214   -
215   -% continue with rest of data
216   --spec handle_frame_continue(
217   - Rest::binary(),
218   - State::term(),
219   - {Socket::socket(), SocketMode::socketmode(), WsHandleLoopPid::pid()}) -> websocket_close | {websocket_close, binary()} | #state{}.
220   -handle_frame_continue(Rest, State, {Socket, SocketMode, WsHandleLoopPid}) ->
221   - case Rest of
222   - <<>> -> State#state{buffer = none};
223   - _ -> i_handle_data(Rest, State#state{buffer = none}, {Socket, SocketMode, WsHandleLoopPid})
224   - end.
225   -
226   -% unmask
227   --spec unmask(Key::binary(), Data::binary()) -> binary().
228   -unmask(Key, <<_:512,_Rest/binary>> = Data) ->
229   - K = binary:copy(Key, 512 div 32),
230   - <<LongKey:512>> = K,
231   - <<ShortKey:32>> = Key,
232   - unmask(ShortKey, LongKey, Data, <<>>);
233   -unmask(Key, Data) ->
234   - <<ShortKey:32>> = Key,
235   - unmask(ShortKey,none, Data, <<>>).
236   -unmask(Key, LongKey, Data, Accu) ->
237   - case Data of
238   - <<A:512, Rest/binary>> ->
239   - C = A bxor LongKey,
240   - unmask(Key, LongKey, Rest, <<Accu/binary, C:512>>);
241   - <<A:32,Rest/binary>> ->
242   - C = A bxor Key,
243   - unmask(Key, LongKey, Rest, <<Accu/binary, C:32>>);
244   - <<A:24>> ->
245   - <<B:24, _:8>> = <<Key:32>>,
246   - C = A bxor B,
247   - <<Accu/binary, C:24>>;
248   - <<A:16>> ->
249   - <<B:16, _:16>> = <<Key:32>>,
250   - C = A bxor B,
251   - <<Accu/binary, C:16>>;
252   - <<A:8>> ->
253   - <<B:8, _:24>> = <<Key:32>>,
254   - C = A bxor B,
255   - <<Accu/binary, C:8>>;
256   - <<>> ->
257   - Accu
258   - end.
259   -
260   -% websocket close data
261   --spec websocket_close_data() -> binary().
262   -websocket_close_data() ->
263   - <<1:1, 0:3, ?OP_CLOSE:4, 0:1, 0:7>>.
264   -
265 93 % ============================ /\ INTERNAL FUNCTIONS =======================================================

0 comments on commit 23ff8a7

Please sign in to comment.
Something went wrong with that request. Please try again.