PageRenderTime 80ms CodeModel.GetById 3ms app.highlight 70ms RepoModel.GetById 2ms app.codeStats 0ms

/test/ws_SUITE.erl

https://github.com/bsmr-erlang/cowboy
Erlang | 544 lines | 449 code | 49 blank | 46 comment | 2 complexity | ecac07f7aef6d84e4370fadc53a3e018 MD5 | raw file
  1%% Copyright (c) 2011-2017, Loïc Hoguin <essen@ninenines.eu>
  2%%
  3%% Permission to use, copy, modify, and/or distribute this software for any
  4%% purpose with or without fee is hereby granted, provided that the above
  5%% copyright notice and this permission notice appear in all copies.
  6%%
  7%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
  8%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
  9%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 10%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 11%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 12%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 13%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 14
 15-module(ws_SUITE).
 16-compile(export_all).
 17
 18-import(ct_helper, [config/2]).
 19-import(ct_helper, [doc/1]).
 20
 21%% ct.
 22
 23all() ->
 24	[{group, autobahn}, {group, ws}].
 25
 26groups() ->
 27	BaseTests = ct_helper:all(?MODULE) -- [autobahn_fuzzingclient],
 28	[{autobahn, [], [autobahn_fuzzingclient]}, {ws, [parallel], BaseTests}].
 29
 30init_per_group(Name = autobahn, Config) ->
 31	%% Some systems have it named pip2.
 32	Out = os:cmd("pip show autobahntestsuite ; pip2 show autobahntestsuite"),
 33	case string:str(Out, "autobahntestsuite") of
 34		0 ->
 35			ct:print("Skipping the autobahn group because the "
 36				"Autobahn Test Suite is not installed.~nTo install it, "
 37				"please follow the instructions on this page:~n~n    "
 38				"http://autobahn.ws/testsuite/installation.html"),
 39			{skip, "Autobahn Test Suite not installed."};
 40		_ ->
 41			{ok, _} = cowboy:start_clear(Name, [{port, 33080}], #{
 42				env => #{dispatch => init_dispatch()}
 43			}),
 44			Config
 45	end;
 46init_per_group(Name = ws, Config) ->
 47	cowboy_test:init_http(Name, #{
 48		env => #{dispatch => init_dispatch()}
 49	}, Config).
 50
 51end_per_group(Listener, _Config) ->
 52	cowboy:stop_listener(Listener).
 53
 54%% Dispatch configuration.
 55
 56init_dispatch() ->
 57	cowboy_router:compile([
 58		{"localhost", [
 59			{"/ws_echo", ws_echo, []},
 60			{"/ws_echo_timer", ws_echo_timer, []},
 61			{"/ws_init", ws_init_h, []},
 62			{"/ws_init_shutdown", ws_init_shutdown, []},
 63			{"/ws_send_many", ws_send_many, [
 64				{sequence, [
 65					{text, <<"one">>},
 66					{text, <<"two">>},
 67					{text, <<"seven!">>}]}
 68			]},
 69			{"/ws_send_close", ws_send_many, [
 70				{sequence, [
 71					{text, <<"send">>},
 72					close,
 73					{text, <<"won't be received">>}]}
 74			]},
 75			{"/ws_send_close_payload", ws_send_many, [
 76				{sequence, [
 77					{text, <<"send">>},
 78					{close, 1001, <<"some text!">>},
 79					{text, <<"won't be received">>}]}
 80			]},
 81			{"/ws_subprotocol", ws_subprotocol, []},
 82			{"/terminate", ws_terminate_h, []},
 83			{"/ws_timeout_hibernate", ws_timeout_hibernate, []},
 84			{"/ws_timeout_cancel", ws_timeout_cancel, []}
 85		]}
 86	]).
 87
 88%% Tests.
 89
 90autobahn_fuzzingclient(Config) ->
 91	doc("Autobahn test suite for the Websocket protocol."),
 92	Self = self(),
 93	spawn_link(fun() -> start_port(Config, Self) end),
 94	receive autobahn_exit -> ok end,
 95	ct:log("<h2><a href=\"log_private/reports/servers/index.html\">Full report</a></h2>~n"),
 96	Report = config(priv_dir, Config) ++ "reports/servers/index.html",
 97	ct:print("Autobahn Test Suite report: file://~s~n", [Report]),
 98	{ok, HTML} = file:read_file(Report),
 99	case length(binary:matches(HTML, <<"case_failed">>)) > 2 of
100		true -> error(failed);
101		false -> ok
102	end.
103
104start_port(Config, Pid) ->
105	Port = open_port({spawn, "wstest -m fuzzingclient -s " ++ config(data_dir, Config) ++ "client.json"},
106		[{line, 10000}, {cd, config(priv_dir, Config)}, binary, eof]),
107	receive_infinity(Port, Pid).
108
109receive_infinity(Port, Pid) ->
110	receive
111		{Port, {data, {eol, Line}}} ->
112			io:format(user, "~s~n", [Line]),
113			receive_infinity(Port, Pid);
114		{Port, eof} ->
115			Pid ! autobahn_exit
116	end.
117
118ws0(Config) ->
119	doc("Websocket version 0 (hixie-76 draft) is no longer supported."),
120	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
121	ok = gen_tcp:send(Socket,
122		"GET /ws_echo_timer HTTP/1.1\r\n"
123		"Host: localhost\r\n"
124		"Connection: Upgrade\r\n"
125		"Upgrade: WebSocket\r\n"
126		"Origin: http://localhost\r\n"
127		"Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
128		"Sec-Websocket-Key2: 1711 M;4\\74  80<6\r\n"
129		"\r\n"),
130	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
131	{ok, {http_response, {1, 1}, 400, _}, _} = erlang:decode_packet(http, Handshake, []),
132	ok.
133
134ws7(Config) ->
135	doc("Websocket version 7 (draft) is supported."),
136	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
137	ok = gen_tcp:send(Socket, [
138		"GET /ws_echo_timer HTTP/1.1\r\n"
139		"Host: localhost\r\n"
140		"Connection: Upgrade\r\n"
141		"Upgrade: websocket\r\n"
142		"Sec-WebSocket-Origin: http://localhost\r\n"
143		"Sec-WebSocket-Version: 7\r\n"
144		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
145		"\r\n"]),
146	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
147	{ok, {http_response, {1, 1}, 101, _}, Rest} = erlang:decode_packet(http, Handshake, []),
148	[Headers, <<>>] = do_decode_headers(erlang:decode_packet(httph, Rest, []), []),
149	{_, "Upgrade"} = lists:keyfind('Connection', 1, Headers),
150	{_, "websocket"} = lists:keyfind('Upgrade', 1, Headers),
151	{_, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers),
152	do_ws_version(Socket).
153
154ws8(Config) ->
155	doc("Websocket version 8 (draft) is supported."),
156	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
157	ok = gen_tcp:send(Socket, [
158		"GET /ws_echo_timer HTTP/1.1\r\n"
159		"Host: localhost\r\n"
160		"Connection: Upgrade\r\n"
161		"Upgrade: websocket\r\n"
162		"Sec-WebSocket-Origin: http://localhost\r\n"
163		"Sec-WebSocket-Version: 8\r\n"
164		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
165		"\r\n"]),
166	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
167	{ok, {http_response, {1, 1}, 101, _}, Rest} = erlang:decode_packet(http, Handshake, []),
168	[Headers, <<>>] = do_decode_headers(erlang:decode_packet(httph, Rest, []), []),
169	{_, "Upgrade"} = lists:keyfind('Connection', 1, Headers),
170	{_, "websocket"} = lists:keyfind('Upgrade', 1, Headers),
171	{_, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers),
172	do_ws_version(Socket).
173
174ws13(Config) ->
175	doc("Websocket version 13 (RFC) is supported."),
176	{ok, Socket, _} = do_handshake("/ws_echo_timer", Config),
177	do_ws_version(Socket).
178
179do_ws_version(Socket) ->
180	%% Masked text hello echoed back clear by the server.
181	Mask = 16#37fa213d,
182	MaskedHello = do_mask(<<"Hello">>, Mask, <<>>),
183	ok = gen_tcp:send(Socket, << 1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),
184	{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000),
185	%% Empty binary frame echoed back.
186	ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 0:7, 0:32 >>),
187	{ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
188	%% Masked binary hello echoed back clear by the server.
189	ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),
190	{ok, << 1:1, 0:3, 2:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000),
191	%% Frames sent on timer by the handler.
192	{ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>} = gen_tcp:recv(Socket, 0, 6000),
193	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000),
194	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000),
195	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>} = gen_tcp:recv(Socket, 0, 6000),
196	%% Client-initiated ping/pong.
197	ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>),
198	{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
199	%% Client-initiated close.
200	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>),
201	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
202	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
203	ok.
204
205ws_init_return_ok(Config) ->
206	doc("Handler does nothing."),
207	{ok, Socket, _} = do_handshake("/ws_init?ok", Config),
208	%% The handler does nothing; nothing should happen here.
209	{error, timeout} = gen_tcp:recv(Socket, 0, 1000),
210	ok.
211
212ws_init_return_ok_hibernate(Config) ->
213	doc("Handler does nothing; hibernates."),
214	{ok, Socket, _} = do_handshake("/ws_init?ok_hibernate", Config),
215	%% The handler does nothing; nothing should happen here.
216	{error, timeout} = gen_tcp:recv(Socket, 0, 1000),
217	ok.
218
219ws_init_return_reply(Config) ->
220	doc("Handler sends a text frame just after the handshake."),
221	{ok, Socket, _} = do_handshake("/ws_init?reply", Config),
222	{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000),
223	ok.
224
225ws_init_return_reply_hibernate(Config) ->
226	doc("Handler sends a text frame just after the handshake and then hibernates."),
227	{ok, Socket, _} = do_handshake("/ws_init?reply_hibernate", Config),
228	{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000),
229	ok.
230
231ws_init_return_reply_close(Config) ->
232	doc("Handler closes immediately after the handshake."),
233	{ok, Socket, _} = do_handshake("/ws_init?reply_close", Config),
234	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
235	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
236	ok.
237
238ws_init_return_reply_close_hibernate(Config) ->
239	doc("Handler closes immediately after the handshake, then attempts to hibernate."),
240	{ok, Socket, _} = do_handshake("/ws_init?reply_close_hibernate", Config),
241	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
242	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
243	ok.
244
245ws_init_return_reply_many(Config) ->
246	doc("Handler sends many frames just after the handshake."),
247	{ok, Socket, _} = do_handshake("/ws_init?reply_many", Config),
248	%% We catch all frames at once and check them directly.
249	{ok, <<
250		1:1, 0:3, 1:4, 0:1, 5:7, "Hello",
251		1:1, 0:3, 2:4, 0:1, 5:7, "World" >>} = gen_tcp:recv(Socket, 14, 6000),
252	ok.
253
254ws_init_return_reply_many_hibernate(Config) ->
255	doc("Handler sends many frames just after the handshake and then hibernates."),
256	{ok, Socket, _} = do_handshake("/ws_init?reply_many_hibernate", Config),
257	%% We catch all frames at once and check them directly.
258	{ok, <<
259		1:1, 0:3, 1:4, 0:1, 5:7, "Hello",
260		1:1, 0:3, 2:4, 0:1, 5:7, "World" >>} = gen_tcp:recv(Socket, 14, 6000),
261	ok.
262
263ws_init_return_reply_many_close(Config) ->
264	doc("Handler sends many frames including a close frame just after the handshake."),
265	{ok, Socket, _} = do_handshake("/ws_init?reply_many_close", Config),
266	%% We catch all frames at once and check them directly.
267	{ok, <<
268		1:1, 0:3, 1:4, 0:1, 5:7, "Hello",
269		1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 9, 6000),
270	ok.
271
272ws_init_return_reply_many_close_hibernate(Config) ->
273	doc("Handler sends many frames including a close frame just after the handshake and then hibernates."),
274	{ok, Socket, _} = do_handshake("/ws_init?reply_many_close_hibernate", Config),
275	%% We catch all frames at once and check them directly.
276	{ok, <<
277		1:1, 0:3, 1:4, 0:1, 5:7, "Hello",
278		1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 9, 6000),
279	ok.
280
281ws_init_return_stop(Config) ->
282	doc("Handler closes immediately after the handshake."),
283	{ok, Socket, _} = do_handshake("/ws_init?stop", Config),
284	{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
285	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
286	ok.
287
288ws_init_shutdown_before_handshake(Config) ->
289	doc("Handler stops before Websocket handshake."),
290	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
291	ok = gen_tcp:send(Socket, [
292		"GET /ws_init_shutdown HTTP/1.1\r\n"
293		"Host: localhost\r\n"
294		"Connection: Upgrade\r\n"
295		"Origin: http://localhost\r\n"
296		"Sec-WebSocket-Version: 13\r\n"
297		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
298		"Upgrade: websocket\r\n"
299		"\r\n"]),
300	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
301	{ok, {http_response, {1, 1}, 403, _}, _Rest} = erlang:decode_packet(http, Handshake, []),
302	ok.
303
304ws_send_close(Config) ->
305	doc("Server-initiated close frame ends the connection."),
306	{ok, Socket, _} = do_handshake("/ws_send_close", Config),
307	%% We catch all frames at once and check them directly.
308	{ok, <<
309		1:1, 0:3, 1:4, 0:1, 4:7, "send",
310		1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 8, 6000),
311	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
312	ok.
313
314ws_send_close_payload(Config) ->
315	doc("Server-initiated close frame with payload ends the connection."),
316	{ok, Socket, _} = do_handshake("/ws_send_close_payload", Config),
317	%% We catch all frames at once and check them directly.
318	{ok, <<
319		1:1, 0:3, 1:4, 0:1, 4:7, "send",
320		1:1, 0:3, 8:4, 0:1, 12:7, 1001:16, "some text!" >>} = gen_tcp:recv(Socket, 20, 6000),
321	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
322	ok.
323
324ws_send_many(Config) ->
325	doc("Server sends many frames in a single reply."),
326	{ok, Socket, _} = do_handshake("/ws_send_many", Config),
327	%% We catch all frames at once and check them directly.
328	{ok, <<
329		1:1, 0:3, 1:4, 0:1, 3:7, "one",
330		1:1, 0:3, 1:4, 0:1, 3:7, "two",
331		1:1, 0:3, 1:4, 0:1, 6:7, "seven!" >>} = gen_tcp:recv(Socket, 18, 6000),
332	ok.
333
334ws_single_bytes(Config) ->
335	doc("Client sends a text frame one byte at a time."),
336	{ok, Socket, _} = do_handshake("/ws_echo", Config),
337	%% We sleep between sends to make sure only one byte is sent.
338	ok = gen_tcp:send(Socket, << 16#81 >>), timer:sleep(100),
339	ok = gen_tcp:send(Socket, << 16#85 >>), timer:sleep(100),
340	ok = gen_tcp:send(Socket, << 16#37 >>), timer:sleep(100),
341	ok = gen_tcp:send(Socket, << 16#fa >>), timer:sleep(100),
342	ok = gen_tcp:send(Socket, << 16#21 >>), timer:sleep(100),
343	ok = gen_tcp:send(Socket, << 16#3d >>), timer:sleep(100),
344	ok = gen_tcp:send(Socket, << 16#7f >>), timer:sleep(100),
345	ok = gen_tcp:send(Socket, << 16#9f >>), timer:sleep(100),
346	ok = gen_tcp:send(Socket, << 16#4d >>), timer:sleep(100),
347	ok = gen_tcp:send(Socket, << 16#51 >>), timer:sleep(100),
348	ok = gen_tcp:send(Socket, << 16#58 >>),
349	{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000),
350	ok.
351
352ws_subprotocol(Config) ->
353	doc("Websocket sub-protocol negotiation."),
354	{ok, _, Headers} = do_handshake("/ws_subprotocol",
355		"Sec-WebSocket-Protocol: foo, bar\r\n", Config),
356	{_, "foo"} = lists:keyfind("sec-websocket-protocol", 1, Headers),
357	ok.
358
359ws_terminate(Config) ->
360	doc("The Req object is kept in a more compact form by default."),
361	{ok, Socket, _} = do_handshake("/terminate",
362		"x-test-pid: " ++ pid_to_list(self()) ++ "\r\n", Config),
363	%% Send a close frame.
364	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>),
365	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
366	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
367	%% Confirm terminate/3 was called with a compacted Req.
368	receive {terminate, _, Req} ->
369		true = maps:is_key(path, Req),
370		false = maps:is_key(headers, Req),
371		ok
372	after 1000 ->
373		error(timeout)
374	end.
375
376ws_terminate_fun(Config) ->
377	doc("A function can be given to filter the Req object."),
378	{ok, Socket, _} = do_handshake("/terminate?req_filter",
379		"x-test-pid: " ++ pid_to_list(self()) ++ "\r\n", Config),
380	%% Send a close frame.
381	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>),
382	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
383	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
384	%% Confirm terminate/3 was called with a compacted Req.
385	receive {terminate, _, Req} ->
386		filtered = Req,
387		ok
388	after 1000 ->
389		error(timeout)
390	end.
391
392ws_text_fragments(Config) ->
393	doc("Client sends fragmented text frames."),
394	{ok, Socket, _} = do_handshake("/ws_echo", Config),
395	%% Send two "Hello" over two fragments and two sends.
396	Mask = 16#37fa213d,
397	MaskedHello = do_mask(<<"Hello">>, Mask, <<>>),
398	ok = gen_tcp:send(Socket, << 0:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),
399	ok = gen_tcp:send(Socket, << 1:1, 0:3, 0:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),
400	{ok, << 1:1, 0:3, 1:4, 0:1, 10:7, "HelloHello" >>} = gen_tcp:recv(Socket, 0, 6000),
401	%% Send three "Hello" over three fragments and one send.
402	ok = gen_tcp:send(Socket, [
403		<< 0:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>,
404		<< 0:1, 0:3, 0:4, 1:1, 5:7, Mask:32, MaskedHello/binary  >>,
405		<< 1:1, 0:3, 0:4, 1:1, 5:7, Mask:32, MaskedHello/binary  >>]),
406	{ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>} = gen_tcp:recv(Socket, 0, 6000),
407	ok.
408
409ws_timeout_hibernate(Config) ->
410	doc("Server-initiated close on timeout with hibernating process."),
411	{ok, Socket, _} = do_handshake("/ws_timeout_hibernate", Config),
412	{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
413	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
414	ok.
415
416ws_timeout_no_cancel(Config) ->
417	doc("Server-initiated timeout is not influenced by reception of Erlang messages."),
418	{ok, Socket, _} = do_handshake("/ws_timeout_cancel", Config),
419	{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
420	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
421	ok.
422
423ws_timeout_reset(Config) ->
424	doc("Server-initiated timeout is reset when client sends more data."),
425	{ok, Socket, _} = do_handshake("/ws_timeout_cancel", Config),
426	%% Send and receive back a frame a few times.
427	Mask = 16#37fa213d,
428	MaskedHello = do_mask(<<"Hello">>, Mask, <<>>),
429	[begin
430		ok = gen_tcp:send(Socket, << 1:1, 0:3, 1:4, 1:1, 5:7, Mask:32, MaskedHello/binary >>),
431		{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>} = gen_tcp:recv(Socket, 0, 6000),
432		timer:sleep(500)
433	end || _ <- [1, 2, 3, 4]],
434	%% Timeout will occur after we stop sending data.
435	{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
436	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
437	ok.
438
439ws_webkit_deflate(Config) ->
440	doc("x-webkit-deflate-frame compression."),
441	{ok, Socket, Headers} = do_handshake("/ws_echo",
442		"Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n", Config),
443	{_, "x-webkit-deflate-frame"} = lists:keyfind("sec-websocket-extensions", 1, Headers),
444	%% Send and receive a compressed "Hello" frame.
445	Mask = 16#11223344,
446	CompressedHello = << 242, 72, 205, 201, 201, 7, 0 >>,
447	MaskedHello = do_mask(CompressedHello, Mask, <<>>),
448	ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32, MaskedHello/binary >>),
449	{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, CompressedHello/binary >>} = gen_tcp:recv(Socket, 0, 6000),
450	%% Client-initiated close.
451	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>),
452	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
453	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
454	ok.
455
456ws_webkit_deflate_fragments(Config) ->
457	doc("Client sends an x-webkit-deflate-frame compressed and fragmented text frame."),
458	{ok, Socket, Headers} = do_handshake("/ws_echo",
459		"Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n", Config),
460	{_, "x-webkit-deflate-frame"} = lists:keyfind("sec-websocket-extensions", 1, Headers),
461	%% Send a compressed "Hello" over two fragments and two sends.
462	Mask = 16#11223344,
463	CompressedHello = << 242, 72, 205, 201, 201, 7, 0 >>,
464	MaskedHello1 = do_mask(binary:part(CompressedHello, 0, 4), Mask, <<>>),
465	MaskedHello2 = do_mask(binary:part(CompressedHello, 4, 3), Mask, <<>>),
466	ok = gen_tcp:send(Socket, << 0:1, 1:1, 0:2, 1:4, 1:1, 4:7, Mask:32, MaskedHello1/binary >>),
467	ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 0:4, 1:1, 3:7, Mask:32, MaskedHello2/binary >>),
468	{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, CompressedHello/binary >>} = gen_tcp:recv(Socket, 0, 6000),
469	ok.
470
471ws_webkit_deflate_single_bytes(Config) ->
472	doc("Client sends an x-webkit-deflate-frame compressed text frame one byte at a time."),
473	{ok, Socket, Headers} = do_handshake("/ws_echo",
474		"Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n", Config),
475	{_, "x-webkit-deflate-frame"} = lists:keyfind("sec-websocket-extensions", 1, Headers),
476	%% We sleep between sends to make sure only one byte is sent.
477	Mask = 16#11223344,
478	CompressedHello = << 242, 72, 205, 201, 201, 7, 0 >>,
479	MaskedHello = do_mask(CompressedHello, Mask, <<>>),
480	ok = gen_tcp:send(Socket, << 16#c1 >>), timer:sleep(100),
481	ok = gen_tcp:send(Socket, << 16#87 >>), timer:sleep(100),
482	ok = gen_tcp:send(Socket, << 16#11 >>), timer:sleep(100),
483	ok = gen_tcp:send(Socket, << 16#22 >>), timer:sleep(100),
484	ok = gen_tcp:send(Socket, << 16#33 >>), timer:sleep(100),
485	ok = gen_tcp:send(Socket, << 16#44 >>), timer:sleep(100),
486	ok = gen_tcp:send(Socket, [binary:at(MaskedHello, 0)]), timer:sleep(100),
487	ok = gen_tcp:send(Socket, [binary:at(MaskedHello, 1)]), timer:sleep(100),
488	ok = gen_tcp:send(Socket, [binary:at(MaskedHello, 2)]), timer:sleep(100),
489	ok = gen_tcp:send(Socket, [binary:at(MaskedHello, 3)]), timer:sleep(100),
490	ok = gen_tcp:send(Socket, [binary:at(MaskedHello, 4)]), timer:sleep(100),
491	ok = gen_tcp:send(Socket, [binary:at(MaskedHello, 5)]), timer:sleep(100),
492	ok = gen_tcp:send(Socket, [binary:at(MaskedHello, 6)]),
493	{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, CompressedHello/binary >>} = gen_tcp:recv(Socket, 0, 6000),
494	ok.
495
496%% Internal.
497
498do_handshake(Path, Config) ->
499	do_handshake(Path, "", Config).
500
501do_handshake(Path, ExtraHeaders, Config) ->
502	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config),
503		[binary, {active, false}]),
504	ok = gen_tcp:send(Socket, [
505		"GET ", Path, " HTTP/1.1\r\n"
506		"Host: localhost\r\n"
507		"Connection: Upgrade\r\n"
508		"Origin: http://localhost\r\n"
509		"Sec-WebSocket-Version: 13\r\n"
510		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
511		"Upgrade: websocket\r\n",
512		ExtraHeaders,
513		"\r\n"]),
514	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
515	{ok, {http_response, {1, 1}, 101, _}, Rest} = erlang:decode_packet(http, Handshake, []),
516	[Headers, <<>>] = do_decode_headers(erlang:decode_packet(httph, Rest, []), []),
517	{_, "Upgrade"} = lists:keyfind('Connection', 1, Headers),
518	{_, "websocket"} = lists:keyfind('Upgrade', 1, Headers),
519	{_, "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="} = lists:keyfind("sec-websocket-accept", 1, Headers),
520	{ok, Socket, Headers}.
521
522do_decode_headers({ok, http_eoh, Rest}, Acc) ->
523	[Acc, Rest];
524do_decode_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->
525	F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,
526	do_decode_headers(erlang:decode_packet(httph, Rest, []), [{F(Key), Value}|Acc]).
527
528do_mask(<<>>, _, Acc) ->
529	Acc;
530do_mask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
531	T = O bxor MaskKey,
532	do_mask(Rest, MaskKey, << Acc/binary, T:32 >>);
533do_mask(<< O:24 >>, MaskKey, Acc) ->
534	<< MaskKey2:24, _:8 >> = << MaskKey:32 >>,
535	T = O bxor MaskKey2,
536	<< Acc/binary, T:24 >>;
537do_mask(<< O:16 >>, MaskKey, Acc) ->
538	<< MaskKey2:16, _:16 >> = << MaskKey:32 >>,
539	T = O bxor MaskKey2,
540	<< Acc/binary, T:16 >>;
541do_mask(<< O:8 >>, MaskKey, Acc) ->
542	<< MaskKey2:8, _:24 >> = << MaskKey:32 >>,
543	T = O bxor MaskKey2,
544	<< Acc/binary, T:8 >>.