PageRenderTime 94ms CodeModel.GetById 2ms app.highlight 85ms RepoModel.GetById 1ms app.codeStats 0ms

/test/ws_SUITE.erl

https://github.com/fgallaire/cowboy
Erlang | 710 lines | 629 code | 43 blank | 38 comment | 2 complexity | 70f502aa88ca66a2ae25d51e3a56f487 MD5 | raw file
  1%% Copyright (c) 2011-2014, 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(cowboy_test, [config/2]).
 19
 20%% ct.
 21
 22all() ->
 23	[{group, autobahn}, {group, ws}].
 24
 25groups() ->
 26	BaseTests = cowboy_test:all(?MODULE) -- [autobahn_fuzzingclient],
 27	[{autobahn, [], [autobahn_fuzzingclient]}, {ws, [parallel], BaseTests}].
 28
 29init_per_suite(Config) ->
 30	Config.
 31
 32init_per_group(Name = autobahn, Config) ->
 33	%% Some systems have it named pip2.
 34	Out = os:cmd("pip show autobahntestsuite ; pip2 show autobahntestsuite"),
 35	case string:str(Out, "autobahntestsuite") of
 36		0 ->
 37			ct:print("Skipping the autobahn group because the "
 38				"Autobahn Test Suite is not installed.~nTo install it, "
 39				"please follow the instructions on this page:~n~n    "
 40				"http://autobahn.ws/testsuite/installation.html"),
 41			{skip, "Autobahn Test Suite not installed."};
 42		_ ->
 43			{ok, _} = cowboy:start_http(Name, 100, [{port, 33080}], [
 44				{env, [{dispatch, init_dispatch()}]}]),
 45			Config
 46	end;
 47init_per_group(Name = ws, Config) ->
 48	cowboy_test:init_http(Name, [
 49		{env, [{dispatch, init_dispatch()}]},
 50		{compress, true}
 51	], Config).
 52
 53end_per_group(Listener, _Config) ->
 54	cowboy:stop_listener(Listener).
 55
 56%% Dispatch configuration.
 57
 58init_dispatch() ->
 59	cowboy_router:compile([
 60		{"localhost", [
 61			{"/ws_echo", ws_echo, []},
 62			{"/ws_echo_timer", ws_echo_timer, []},
 63			{"/ws_init_shutdown", ws_init_shutdown, []},
 64			{"/ws_send_many", ws_send_many, [
 65				{sequence, [
 66					{text, <<"one">>},
 67					{text, <<"two">>},
 68					{text, <<"seven!">>}]}
 69			]},
 70			{"/ws_send_close", ws_send_many, [
 71				{sequence, [
 72					{text, <<"send">>},
 73					close,
 74					{text, <<"won't be received">>}]}
 75			]},
 76			{"/ws_send_close_payload", ws_send_many, [
 77				{sequence, [
 78					{text, <<"send">>},
 79					{close, 1001, <<"some text!">>},
 80					{text, <<"won't be received">>}]}
 81			]},
 82			{"/ws_timeout_hibernate", ws_timeout_hibernate, []},
 83			{"/ws_timeout_cancel", ws_timeout_cancel, []},
 84			{"/ws_upgrade_with_opts", ws_upgrade_with_opts,
 85				<<"failure">>}
 86		]}
 87	]).
 88
 89%% Tests.
 90
 91autobahn_fuzzingclient(Config) ->
 92	Out = os:cmd("cd " ++ config(priv_dir, Config)
 93		++ " && wstest -m fuzzingclient -s "
 94		++ config(data_dir, Config) ++ "client.json"),
 95	Report = config(priv_dir, Config) ++ "reports/servers/index.html",
 96	ct:log("<h2><a href=\"~s\">Full report</a></h2>~n", [Report]),
 97	ct:print("Autobahn Test Suite report: file://~s~n", [Report]),
 98	ct:log("~s~n", [Out]),
 99	{ok, HTML} = file:read_file(Report),
100	case length(binary:matches(HTML, <<"case_failed">>)) > 2 of
101		true -> error(failed);
102		false -> ok
103	end.
104
105%% We do not support hixie76 anymore.
106ws0(Config) ->
107	{port, Port} = lists:keyfind(port, 1, Config),
108	{ok, Socket} = gen_tcp:connect("localhost", Port,
109		[binary, {active, false}, {packet, raw}]),
110	ok = gen_tcp:send(Socket,
111		"GET /ws_echo_timer HTTP/1.1\r\n"
112		"Host: localhost\r\n"
113		"Connection: Upgrade\r\n"
114		"Upgrade: WebSocket\r\n"
115		"Origin: http://localhost\r\n"
116		"Sec-Websocket-Key1: Y\" 4 1Lj!957b8@0H756!i\r\n"
117		"Sec-Websocket-Key2: 1711 M;4\\74  80<6\r\n"
118		"\r\n"),
119	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
120	{ok, {http_response, {1, 1}, 400, _}, _}
121		= erlang:decode_packet(http, Handshake, []).
122
123ws8(Config) ->
124	{port, Port} = lists:keyfind(port, 1, Config),
125	{ok, Socket} = gen_tcp:connect("localhost", Port,
126		[binary, {active, false}, {packet, raw}]),
127	ok = gen_tcp:send(Socket, [
128		"GET /ws_echo_timer HTTP/1.1\r\n"
129		"Host: localhost\r\n"
130		"Connection: Upgrade\r\n"
131		"Upgrade: websocket\r\n"
132		"Sec-WebSocket-Origin: http://localhost\r\n"
133		"Sec-WebSocket-Version: 8\r\n"
134		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
135		"\r\n"]),
136	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
137	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
138		= erlang:decode_packet(http, Handshake, []),
139	[Headers, <<>>] = do_decode_headers(
140		erlang:decode_packet(httph, Rest, []), []),
141	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
142	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
143	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
144		= lists:keyfind("sec-websocket-accept", 1, Headers),
145	ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
146		16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
147	{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
148		= gen_tcp:recv(Socket, 0, 6000),
149	{ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
150		= gen_tcp:recv(Socket, 0, 6000),
151	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
152		= gen_tcp:recv(Socket, 0, 6000),
153	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
154		= gen_tcp:recv(Socket, 0, 6000),
155	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
156		= gen_tcp:recv(Socket, 0, 6000),
157	ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
158	{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
159	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
160	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
161	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
162	ok.
163
164ws8_init_shutdown(Config) ->
165	{port, Port} = lists:keyfind(port, 1, Config),
166	{ok, Socket} = gen_tcp:connect("localhost", Port,
167		[binary, {active, false}, {packet, raw}]),
168	ok = gen_tcp:send(Socket, [
169		"GET /ws_init_shutdown HTTP/1.1\r\n"
170		"Host: localhost\r\n"
171		"Connection: Upgrade\r\n"
172		"Upgrade: websocket\r\n"
173		"Sec-WebSocket-Origin: http://localhost\r\n"
174		"Sec-WebSocket-Version: 8\r\n"
175		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
176		"\r\n"]),
177	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
178	{ok, {http_response, {1, 1}, 403, "Forbidden"}, _Rest}
179		= erlang:decode_packet(http, Handshake, []),
180	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
181	ok.
182
183ws8_single_bytes(Config) ->
184	{port, Port} = lists:keyfind(port, 1, Config),
185	{ok, Socket} = gen_tcp:connect("localhost", Port,
186		[binary, {active, false}, {packet, raw}]),
187	ok = gen_tcp:send(Socket, [
188		"GET /ws_echo_timer HTTP/1.1\r\n"
189		"Host: localhost\r\n"
190		"Connection: Upgrade\r\n"
191		"Upgrade: websocket\r\n"
192		"Sec-WebSocket-Origin: http://localhost\r\n"
193		"Sec-WebSocket-Version: 8\r\n"
194		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
195		"\r\n"]),
196	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
197	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
198		= erlang:decode_packet(http, Handshake, []),
199	[Headers, <<>>] = do_decode_headers(
200		erlang:decode_packet(httph, Rest, []), []),
201	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
202	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
203	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
204		= lists:keyfind("sec-websocket-accept", 1, Headers),
205	ok = gen_tcp:send(Socket, << 16#81 >>), %% send one byte
206	ok = timer:sleep(100), %% sleep for a period
207	ok = gen_tcp:send(Socket, << 16#85 >>), %% send another and so on
208	ok = timer:sleep(100),
209	ok = gen_tcp:send(Socket, << 16#37 >>),
210	ok = timer:sleep(100),
211	ok = gen_tcp:send(Socket, << 16#fa >>),
212	ok = timer:sleep(100),
213	ok = gen_tcp:send(Socket, << 16#21 >>),
214	ok = timer:sleep(100),
215	ok = gen_tcp:send(Socket, << 16#3d >>),
216	ok = timer:sleep(100),
217	ok = gen_tcp:send(Socket, << 16#7f >>),
218	ok = timer:sleep(100),
219	ok = gen_tcp:send(Socket, << 16#9f >>),
220	ok = timer:sleep(100),
221	ok = gen_tcp:send(Socket, << 16#4d >>),
222	ok = timer:sleep(100),
223	ok = gen_tcp:send(Socket, << 16#51 >>),
224	ok = timer:sleep(100),
225	ok = gen_tcp:send(Socket, << 16#58 >>),
226	{ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
227		= gen_tcp:recv(Socket, 0, 6000),
228	{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
229		= gen_tcp:recv(Socket, 0, 6000),
230	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
231		= gen_tcp:recv(Socket, 0, 6000),
232	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
233		= gen_tcp:recv(Socket, 0, 6000),
234	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
235		= gen_tcp:recv(Socket, 0, 6000),
236	ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
237	{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
238	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
239	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
240	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
241	ok.
242
243ws13(Config) ->
244	{port, Port} = lists:keyfind(port, 1, Config),
245	{ok, Socket} = gen_tcp:connect("localhost", Port,
246		[binary, {active, false}, {packet, raw}]),
247	ok = gen_tcp:send(Socket, [
248		"GET /ws_echo_timer HTTP/1.1\r\n"
249		"Host: localhost\r\n"
250		"Connection: Upgrade\r\n"
251		"Origin: http://localhost\r\n"
252		"Sec-WebSocket-Version: 13\r\n"
253		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
254		"Upgrade: websocket\r\n"
255		"\r\n"]),
256	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
257	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
258		= erlang:decode_packet(http, Handshake, []),
259	[Headers, <<>>] = do_decode_headers(
260		erlang:decode_packet(httph, Rest, []), []),
261	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
262	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
263	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
264		= lists:keyfind("sec-websocket-accept", 1, Headers),
265	%% text
266	ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
267		16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
268	{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
269		= gen_tcp:recv(Socket, 0, 6000),
270	%% binary (empty)
271	ok = gen_tcp:send(Socket, << 1:1, 0:3, 2:4, 1:1, 0:7, 0:32 >>),
272	{ok, << 1:1, 0:3, 2:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
273	%% binary
274	ok = gen_tcp:send(Socket, << 16#82, 16#85, 16#37, 16#fa, 16#21, 16#3d,
275		16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
276	{ok, << 1:1, 0:3, 2:4, 0:1, 5:7, "Hello" >>}
277		= gen_tcp:recv(Socket, 0, 6000),
278	%% Receives.
279	{ok, << 1:1, 0:3, 1:4, 0:1, 14:7, "websocket_init" >>}
280		= gen_tcp:recv(Socket, 0, 6000),
281	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
282		= gen_tcp:recv(Socket, 0, 6000),
283	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
284		= gen_tcp:recv(Socket, 0, 6000),
285	{ok, << 1:1, 0:3, 1:4, 0:1, 16:7, "websocket_handle" >>}
286		= gen_tcp:recv(Socket, 0, 6000),
287	ok = gen_tcp:send(Socket, << 1:1, 0:3, 9:4, 1:1, 0:7, 0:32 >>), %% ping
288	{ok, << 1:1, 0:3, 10:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000), %% pong
289	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
290	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
291	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
292	ok.
293
294ws_deflate(Config) ->
295	{port, Port} = lists:keyfind(port, 1, Config),
296	{ok, Socket} = gen_tcp:connect("localhost", Port,
297		[binary, {active, false}, {packet, raw}, {nodelay, true}]),
298	ok = gen_tcp:send(Socket, [
299		"GET /ws_echo HTTP/1.1\r\n"
300		"Host: localhost\r\n"
301		"Connection: Upgrade\r\n"
302		"Upgrade: websocket\r\n"
303		"Sec-WebSocket-Origin: http://localhost\r\n"
304		"Sec-WebSocket-Version: 8\r\n"
305		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
306		"Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"
307		"\r\n"]),
308	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
309	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
310		= erlang:decode_packet(http, Handshake, []),
311	[Headers, <<>>] = do_decode_headers(
312		erlang:decode_packet(httph, Rest, []), []),
313	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
314	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
315	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
316		= lists:keyfind("sec-websocket-accept", 1, Headers),
317	{"sec-websocket-extensions", "x-webkit-deflate-frame"}
318		= lists:keyfind("sec-websocket-extensions", 1, Headers),
319
320	Mask = 16#11223344,
321	Hello = << 242, 72, 205, 201, 201, 7, 0 >>,
322	MaskedHello = do_mask(Hello, Mask, <<>>),
323
324	% send compressed text frame containing the Hello string
325	ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32,
326		MaskedHello/binary >>),
327	% receive compressed text frame containing the Hello string
328	{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>}
329		= gen_tcp:recv(Socket, 0, 6000),
330
331	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
332	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
333	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
334	ok.
335
336ws_deflate_chunks(Config) ->
337	{port, Port} = lists:keyfind(port, 1, Config),
338	{ok, Socket} = gen_tcp:connect("localhost", Port,
339		[binary, {active, false}, {packet, raw}, {nodelay, true}]),
340	ok = gen_tcp:send(Socket, [
341		"GET /ws_echo HTTP/1.1\r\n"
342		"Host: localhost\r\n"
343		"Connection: Upgrade\r\n"
344		"Upgrade: websocket\r\n"
345		"Sec-WebSocket-Origin: http://localhost\r\n"
346		"Sec-WebSocket-Version: 8\r\n"
347		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
348		"Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"
349		"\r\n"]),
350	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
351	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
352		= erlang:decode_packet(http, Handshake, []),
353	[Headers, <<>>] = do_decode_headers(
354		erlang:decode_packet(httph, Rest, []), []),
355	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
356	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
357	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
358		= lists:keyfind("sec-websocket-accept", 1, Headers),
359	{"sec-websocket-extensions", "x-webkit-deflate-frame"}
360		= lists:keyfind("sec-websocket-extensions", 1, Headers),
361
362	Mask = 16#11223344,
363	Hello = << 242, 72, 205, 201, 201, 7, 0 >>,
364	MaskedHello = do_mask(Hello, Mask, <<>>),
365
366	% send compressed text frame containing the Hello string
367	ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 1:4, 1:1, 7:7, Mask:32,
368		(binary:part(MaskedHello, 0, 4))/binary >>),
369	ok = timer:sleep(100),
370	ok = gen_tcp:send(Socket, binary:part(MaskedHello, 4, 3)),
371
372	% receive compressed text frame containing the Hello string
373	{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>}
374		= gen_tcp:recv(Socket, 0, 6000),
375
376	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
377	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
378	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
379	ok.
380
381ws_deflate_fragments(Config) ->
382	{port, Port} = lists:keyfind(port, 1, Config),
383	{ok, Socket} = gen_tcp:connect("localhost", Port,
384		[binary, {active, false}, {packet, raw}, {nodelay, true}]),
385	ok = gen_tcp:send(Socket, [
386		"GET /ws_echo HTTP/1.1\r\n"
387		"Host: localhost\r\n"
388		"Connection: Upgrade\r\n"
389		"Upgrade: websocket\r\n"
390		"Sec-WebSocket-Origin: http://localhost\r\n"
391		"Sec-WebSocket-Version: 8\r\n"
392		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
393		"Sec-WebSocket-Extensions: x-webkit-deflate-frame\r\n"
394		"\r\n"]),
395	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
396	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
397		= erlang:decode_packet(http, Handshake, []),
398	[Headers, <<>>] = do_decode_headers(
399		erlang:decode_packet(httph, Rest, []), []),
400	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
401	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
402	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
403		= lists:keyfind("sec-websocket-accept", 1, Headers),
404	{"sec-websocket-extensions", "x-webkit-deflate-frame"}
405		= lists:keyfind("sec-websocket-extensions", 1, Headers),
406
407	Mask = 16#11223344,
408	Hello = << 242, 72, 205, 201, 201, 7, 0 >>,
409
410	% send compressed text frame containing the Hello string
411	% as 2 separate fragments
412	ok = gen_tcp:send(Socket, << 0:1, 1:1, 0:2, 1:4, 1:1, 4:7, Mask:32,
413		(do_mask(binary:part(Hello, 0, 4), Mask, <<>>))/binary >>),
414	ok = gen_tcp:send(Socket, << 1:1, 1:1, 0:2, 0:4, 1:1, 3:7, Mask:32,
415		(do_mask(binary:part(Hello, 4, 3), Mask, <<>>))/binary >>),
416	% receive compressed text frame containing the Hello string
417	{ok, << 1:1, 1:1, 0:2, 1:4, 0:1, 7:7, Hello/binary >>}
418		= gen_tcp:recv(Socket, 0, 6000),
419
420	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
421	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
422	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
423	ok.
424
425ws_send_close(Config) ->
426	{port, Port} = lists:keyfind(port, 1, Config),
427	{ok, Socket} = gen_tcp:connect("localhost", Port,
428		[binary, {active, false}, {packet, raw}]),
429	ok = gen_tcp:send(Socket, [
430		"GET /ws_send_close HTTP/1.1\r\n"
431		"Host: localhost\r\n"
432		"Connection: Upgrade\r\n"
433		"Upgrade: websocket\r\n"
434		"Sec-WebSocket-Origin: http://localhost\r\n"
435		"Sec-WebSocket-Version: 8\r\n"
436		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
437		"\r\n"]),
438	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
439	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
440		= erlang:decode_packet(http, Handshake, []),
441	[Headers, <<>>] = do_decode_headers(
442		erlang:decode_packet(httph, Rest, []), []),
443	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
444	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
445	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
446		= lists:keyfind("sec-websocket-accept", 1, Headers),
447	%% We catch all frames at once and check them directly.
448	{ok, Many} = gen_tcp:recv(Socket, 8, 6000),
449	<< 1:1, 0:3, 1:4, 0:1, 4:7, "send",
450		1:1, 0:3, 8:4, 0:8 >> = Many,
451	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
452	ok.
453
454ws_send_close_payload(Config) ->
455	{port, Port} = lists:keyfind(port, 1, Config),
456	{ok, Socket} = gen_tcp:connect("localhost", Port,
457		[binary, {active, false}, {packet, raw}]),
458	ok = gen_tcp:send(Socket, [
459		"GET /ws_send_close_payload HTTP/1.1\r\n"
460		"Host: localhost\r\n"
461		"Connection: Upgrade\r\n"
462		"Upgrade: websocket\r\n"
463		"Sec-WebSocket-Origin: http://localhost\r\n"
464		"Sec-WebSocket-Version: 8\r\n"
465		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
466		"\r\n"]),
467	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
468	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
469		= erlang:decode_packet(http, Handshake, []),
470	[Headers, <<>>] = do_decode_headers(
471		erlang:decode_packet(httph, Rest, []), []),
472	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
473	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
474	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
475		= lists:keyfind("sec-websocket-accept", 1, Headers),
476	%% We catch all frames at once and check them directly.
477	{ok, Many} = gen_tcp:recv(Socket, 20, 6000),
478	<< 1:1, 0:3, 1:4, 0:1, 4:7, "send",
479		1:1, 0:3, 8:4, 0:1, 12:7, 1001:16, "some text!" >> = Many,
480	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
481	ok.
482
483ws_send_many(Config) ->
484	{port, Port} = lists:keyfind(port, 1, Config),
485	{ok, Socket} = gen_tcp:connect("localhost", Port,
486		[binary, {active, false}, {packet, raw}]),
487	ok = gen_tcp:send(Socket, [
488		"GET /ws_send_many HTTP/1.1\r\n"
489		"Host: localhost\r\n"
490		"Connection: Upgrade\r\n"
491		"Upgrade: websocket\r\n"
492		"Sec-WebSocket-Origin: http://localhost\r\n"
493		"Sec-WebSocket-Version: 8\r\n"
494		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
495		"\r\n"]),
496	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
497	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
498		= erlang:decode_packet(http, Handshake, []),
499	[Headers, <<>>] = do_decode_headers(
500		erlang:decode_packet(httph, Rest, []), []),
501	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
502	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
503	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
504		= lists:keyfind("sec-websocket-accept", 1, Headers),
505	%% We catch all frames at once and check them directly.
506	{ok, Many} = gen_tcp:recv(Socket, 18, 6000),
507	<< 1:1, 0:3, 1:4, 0:1, 3:7, "one",
508		1:1, 0:3, 1:4, 0:1, 3:7, "two",
509		1:1, 0:3, 1:4, 0:1, 6:7, "seven!" >> = Many,
510	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
511	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
512	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
513	ok.
514
515ws_text_fragments(Config) ->
516	{port, Port} = lists:keyfind(port, 1, Config),
517	{ok, Socket} = gen_tcp:connect("localhost", Port,
518		[binary, {active, false}, {packet, raw}]),
519	ok = gen_tcp:send(Socket, [
520		"GET /ws_echo HTTP/1.1\r\n"
521		"Host: localhost\r\n"
522		"Connection: Upgrade\r\n"
523		"Upgrade: websocket\r\n"
524		"Sec-WebSocket-Origin: http://localhost\r\n"
525		"Sec-WebSocket-Version: 8\r\n"
526		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
527		"\r\n"]),
528	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
529	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
530		= erlang:decode_packet(http, Handshake, []),
531	[Headers, <<>>] = do_decode_headers(
532		erlang:decode_packet(httph, Rest, []), []),
533	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
534	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
535	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
536		= lists:keyfind("sec-websocket-accept", 1, Headers),
537
538	ok = gen_tcp:send(Socket, [
539		<< 0:1, 0:3, 1:4, 1:1, 5:7 >>,
540		<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
541		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
542	ok = gen_tcp:send(Socket, [
543		<< 1:1, 0:3, 0:4, 1:1, 5:7 >>,
544		<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
545		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
546	{ok, << 1:1, 0:3, 1:4, 0:1, 10:7, "HelloHello" >>}
547		= gen_tcp:recv(Socket, 0, 6000),
548
549	ok = gen_tcp:send(Socket, [
550		%% #1
551		<< 0:1, 0:3, 1:4, 1:1, 5:7 >>,
552		<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
553		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
554		%% #2
555		<< 0:1, 0:3, 0:4, 1:1, 5:7 >>,
556		<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
557		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>,
558		%% #3
559		<< 1:1, 0:3, 0:4, 1:1, 5:7 >>,
560		<< 16#37 >>, << 16#fa >>, << 16#21 >>, << 16#3d >>, << 16#7f >>,
561		<< 16#9f >>, << 16#4d >>, << 16#51 >>, << 16#58 >>]),
562	{ok, << 1:1, 0:3, 1:4, 0:1, 15:7, "HelloHelloHello" >>}
563		= gen_tcp:recv(Socket, 0, 6000),
564	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
565	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
566	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
567	ok.
568
569ws_timeout_hibernate(Config) ->
570	{port, Port} = lists:keyfind(port, 1, Config),
571	{ok, Socket} = gen_tcp:connect("localhost", Port,
572		[binary, {active, false}, {packet, raw}]),
573	ok = gen_tcp:send(Socket, [
574		"GET /ws_timeout_hibernate HTTP/1.1\r\n"
575		"Host: localhost\r\n"
576		"Connection: Upgrade\r\n"
577		"Upgrade: websocket\r\n"
578		"Sec-WebSocket-Origin: http://localhost\r\n"
579		"Sec-WebSocket-Version: 8\r\n"
580		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
581		"\r\n"]),
582	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
583	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
584		= erlang:decode_packet(http, Handshake, []),
585	[Headers, <<>>] = do_decode_headers(
586		erlang:decode_packet(httph, Rest, []), []),
587	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
588	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
589	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
590		= lists:keyfind("sec-websocket-accept", 1, Headers),
591	{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
592	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
593	ok.
594
595ws_timeout_cancel(Config) ->
596	%% Erlang messages to a socket should not cancel the timeout	
597	{port, Port} = lists:keyfind(port, 1, Config),
598	{ok, Socket} = gen_tcp:connect("localhost", Port,
599		[binary, {active, false}, {packet, raw}]),
600	ok = gen_tcp:send(Socket, [
601		"GET /ws_timeout_cancel HTTP/1.1\r\n"
602		"Host: localhost\r\n"
603		"Connection: Upgrade\r\n"
604		"Upgrade: websocket\r\n"
605		"Sec-WebSocket-Origin: http://localhost\r\n"
606		"Sec-WebSocket-Version: 8\r\n"
607		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
608		"\r\n"]),
609	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
610	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
611		= erlang:decode_packet(http, Handshake, []),
612	[Headers, <<>>] = do_decode_headers(
613		erlang:decode_packet(httph, Rest, []), []),
614	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
615	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
616	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
617		= lists:keyfind("sec-websocket-accept", 1, Headers),
618	{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
619	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
620	ok.
621
622ws_timeout_reset(Config) ->
623	%% Erlang messages across a socket should reset the timeout	
624	{port, Port} = lists:keyfind(port, 1, Config),
625	{ok, Socket} = gen_tcp:connect("localhost", Port,
626		[binary, {active, false}, {packet, raw}]),
627	ok = gen_tcp:send(Socket, [
628		"GET /ws_timeout_cancel HTTP/1.1\r\n"
629		"Host: localhost\r\n"
630		"Connection: Upgrade\r\n"
631		"Upgrade: websocket\r\n"
632		"Sec-WebSocket-Origin: http://localhost\r\n"
633		"Sec-Websocket-Version: 13\r\n"
634		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
635		"\r\n"]),
636	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
637	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
638		= erlang:decode_packet(http, Handshake, []),
639	[Headers, <<>>] = do_decode_headers(
640		erlang:decode_packet(httph, Rest, []), []),
641	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
642	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
643	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
644		= lists:keyfind("sec-websocket-accept", 1, Headers),
645	[begin
646		ok = gen_tcp:send(Socket, << 16#81, 16#85, 16#37, 16#fa, 16#21, 16#3d,
647			16#7f, 16#9f, 16#4d, 16#51, 16#58 >>),
648		{ok, << 1:1, 0:3, 1:4, 0:1, 5:7, "Hello" >>}
649			= gen_tcp:recv(Socket, 0, 6000),
650		ok = timer:sleep(500)
651	end || _ <- [1, 2, 3, 4]],
652	{ok, << 1:1, 0:3, 8:4, 0:1, 2:7, 1000:16 >>} = gen_tcp:recv(Socket, 0, 6000),
653	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
654	ok.
655
656ws_upgrade_with_opts(Config) ->
657	{port, Port} = lists:keyfind(port, 1, Config),
658	{ok, Socket} = gen_tcp:connect("localhost", Port,
659		[binary, {active, false}, {packet, raw}]),
660	ok = gen_tcp:send(Socket, [
661		"GET /ws_upgrade_with_opts HTTP/1.1\r\n"
662		"Host: localhost\r\n"
663		"Connection: Upgrade\r\n"
664		"Upgrade: websocket\r\n"
665		"Sec-WebSocket-Origin: http://localhost\r\n"
666		"Sec-WebSocket-Version: 8\r\n"
667		"Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n"
668		"\r\n"]),
669	{ok, Handshake} = gen_tcp:recv(Socket, 0, 6000),
670	{ok, {http_response, {1, 1}, 101, "Switching Protocols"}, Rest}
671		= erlang:decode_packet(http, Handshake, []),
672	[Headers, <<>>] = do_decode_headers(
673		erlang:decode_packet(httph, Rest, []), []),
674	{'Connection', "Upgrade"} = lists:keyfind('Connection', 1, Headers),
675	{'Upgrade', "websocket"} = lists:keyfind('Upgrade', 1, Headers),
676	{"sec-websocket-accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}
677		= lists:keyfind("sec-websocket-accept", 1, Headers),
678	{ok, Response} = gen_tcp:recv(Socket, 9, 6000),
679	<< 1:1, 0:3, 1:4, 0:1, 7:7, "success" >> = Response,
680	ok = gen_tcp:send(Socket, << 1:1, 0:3, 8:4, 1:1, 0:7, 0:32 >>), %% close
681	{ok, << 1:1, 0:3, 8:4, 0:8 >>} = gen_tcp:recv(Socket, 0, 6000),
682	{error, closed} = gen_tcp:recv(Socket, 0, 6000),
683	ok.
684
685%% Internal.
686
687do_decode_headers({ok, http_eoh, Rest}, Acc) ->
688	[Acc, Rest];
689do_decode_headers({ok, {http_header, _I, Key, _R, Value}, Rest}, Acc) ->
690	F = fun(S) when is_atom(S) -> S; (S) -> string:to_lower(S) end,
691	do_decode_headers(erlang:decode_packet(httph, Rest, []),
692		[{F(Key), Value}|Acc]).
693
694do_mask(<<>>, _, Acc) ->
695	Acc;
696do_mask(<< O:32, Rest/bits >>, MaskKey, Acc) ->
697	T = O bxor MaskKey,
698	do_mask(Rest, MaskKey, << Acc/binary, T:32 >>);
699do_mask(<< O:24 >>, MaskKey, Acc) ->
700	<< MaskKey2:24, _:8 >> = << MaskKey:32 >>,
701	T = O bxor MaskKey2,
702	<< Acc/binary, T:24 >>;
703do_mask(<< O:16 >>, MaskKey, Acc) ->
704	<< MaskKey2:16, _:16 >> = << MaskKey:32 >>,
705	T = O bxor MaskKey2,
706	<< Acc/binary, T:16 >>;
707do_mask(<< O:8 >>, MaskKey, Acc) ->
708	<< MaskKey2:8, _:24 >> = << MaskKey:32 >>,
709	T = O bxor MaskKey2,
710	<< Acc/binary, T:8 >>.