1%% Copyright (c) 2013-2018, 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(cow_spdy).
16
17%% Zstream.
18-export([deflate_init/0]).
19-export([inflate_init/0]).
20
21%% Parse.
22-export([split/1]).
23-export([parse/2]).
24
25%% Build.
26-export([data/3]).
27-export([syn_stream/12]).
28-export([syn_reply/6]).
29-export([rst_stream/2]).
30-export([settings/2]).
31-export([ping/1]).
32-export([goaway/2]).
33%% @todo headers
34%% @todo window_update
35
36-include("cow_spdy.hrl").
37
38%% Zstream.
39
40deflate_init() ->
41	Zdef = zlib:open(),
42	ok = zlib:deflateInit(Zdef),
43	_ = zlib:deflateSetDictionary(Zdef, ?ZDICT),
44	Zdef.
45
46inflate_init() ->
47	Zinf = zlib:open(),
48	ok = zlib:inflateInit(Zinf),
49	Zinf.
50
51%% Parse.
52
53split(Data = << _:40, Length:24, _/bits >>)
54		when byte_size(Data) >= Length + 8 ->
55	Length2 = Length + 8,
56	<< Frame:Length2/binary, Rest/bits >> = Data,
57	{true, Frame, Rest};
58split(_) ->
59	false.
60
61parse(<< 0:1, StreamID:31, 0:7, IsFinFlag:1, _:24, Data/bits >>, _) ->
62	{data, StreamID, from_flag(IsFinFlag), Data};
63parse(<< 1:1, 3:15, 1:16, 0:6, IsUnidirectionalFlag:1, IsFinFlag:1,
64		_:25, StreamID:31, _:1, AssocToStreamID:31, Priority:3, _:5,
65		0:8, Rest/bits >>, Zinf) ->
66	case parse_headers(Rest, Zinf) of
67		{ok, Headers, [{<<":host">>, Host}, {<<":method">>, Method},
68				{<<":path">>, Path}, {<<":scheme">>, Scheme},
69				{<<":version">>, Version}]} ->
70			{syn_stream, StreamID, AssocToStreamID, from_flag(IsFinFlag),
71				from_flag(IsUnidirectionalFlag), Priority, Method,
72				Scheme, Host, Path, Version, Headers};
73		_ ->
74			{error, badprotocol}
75	end;
76parse(<< 1:1, 3:15, 2:16, 0:7, IsFinFlag:1, _:25,
77		StreamID:31, Rest/bits >>, Zinf) ->
78	case parse_headers(Rest, Zinf) of
79		{ok, Headers, [{<<":status">>, Status}, {<<":version">>, Version}]} ->
80			{syn_reply, StreamID, from_flag(IsFinFlag),
81				Status, Version, Headers};
82		_ ->
83			{error, badprotocol}
84	end;
85parse(<< 1:1, 3:15, 3:16, 0:8, _:56, StatusCode:32 >>, _)
86		when StatusCode =:= 0; StatusCode > 11 ->
87	{error, badprotocol};
88parse(<< 1:1, 3:15, 3:16, 0:8, _:25, StreamID:31, StatusCode:32 >>, _) ->
89	Status = case StatusCode of
90		1 -> protocol_error;
91		2 -> invalid_stream;
92		3 -> refused_stream;
93		4 -> unsupported_version;
94		5 -> cancel;
95		6 -> internal_error;
96		7 -> flow_control_error;
97		8 -> stream_in_use;
98		9 -> stream_already_closed;
99		10 -> invalid_credentials;
100		11 -> frame_too_large
101	end,
102	{rst_stream, StreamID, Status};
103parse(<< 1:1, 3:15, 4:16, 0:7, ClearSettingsFlag:1, _:24,
104		NbEntries:32, Rest/bits >>, _) ->
105	try
106		Settings = [begin
107			Is0 = 0,
108			Key = case ID of
109				1 -> upload_bandwidth;
110				2 -> download_bandwidth;
111				3 -> round_trip_time;
112				4 -> max_concurrent_streams;
113				5 -> current_cwnd;
114				6 -> download_retrans_rate;
115				7 -> initial_window_size;
116				8 -> client_certificate_vector_size
117			end,
118			{Key, Value, from_flag(PersistFlag), from_flag(WasPersistedFlag)}
119		end || << Is0:6, WasPersistedFlag:1, PersistFlag:1,
120			ID:24, Value:32 >> <= Rest],
121		NbEntries = length(Settings),
122		{settings, from_flag(ClearSettingsFlag), Settings}
123	catch _:_ ->
124		{error, badprotocol}
125	end;
126parse(<< 1:1, 3:15, 6:16, 0:8, _:24, PingID:32 >>, _) ->
127	{ping, PingID};
128parse(<< 1:1, 3:15, 7:16, 0:8, _:56, StatusCode:32 >>, _)
129		when StatusCode > 2 ->
130	{error, badprotocol};
131parse(<< 1:1, 3:15, 7:16, 0:8, _:25, LastGoodStreamID:31,
132		StatusCode:32 >>, _) ->
133	Status = case StatusCode of
134		0 -> ok;
135		1 -> protocol_error;
136		2 -> internal_error
137	end,
138	{goaway, LastGoodStreamID, Status};
139parse(<< 1:1, 3:15, 8:16, 0:7, IsFinFlag:1, _:25, StreamID:31,
140		Rest/bits >>, Zinf) ->
141	case parse_headers(Rest, Zinf) of
142		{ok, Headers, []} ->
143			{headers, StreamID, from_flag(IsFinFlag), Headers};
144		_ ->
145			{error, badprotocol}
146	end;
147parse(<< 1:1, 3:15, 9:16, 0:8, _:57, 0:31 >>, _) ->
148	{error, badprotocol};
149parse(<< 1:1, 3:15, 9:16, 0:8, _:25, StreamID:31,
150		_:1, DeltaWindowSize:31 >>, _) ->
151	{window_update, StreamID, DeltaWindowSize};
152parse(_, _) ->
153	{error, badprotocol}.
154
155parse_headers(Data, Zinf) ->
156	[<< NbHeaders:32, Rest/bits >>] = inflate(Zinf, Data),
157	parse_headers(Rest, NbHeaders, [], []).
158
159parse_headers(<<>>, 0, Headers, SpHeaders) ->
160	{ok, lists:reverse(Headers), lists:sort(SpHeaders)};
161parse_headers(<<>>, _, _, _) ->
162	error;
163parse_headers(_, 0, _, _) ->
164	error;
165parse_headers(<< 0:32, _/bits >>, _, _, _) ->
166	error;
167parse_headers(<< L1:32, Key:L1/binary, L2:32, Value:L2/binary, Rest/bits >>,
168		NbHeaders, Acc, SpAcc) ->
169	case Key of
170		<< $:, _/bits >> ->
171			parse_headers(Rest, NbHeaders - 1, Acc,
172				lists:keystore(Key, 1, SpAcc, {Key, Value}));
173		_ ->
174			parse_headers(Rest, NbHeaders - 1, [{Key, Value}|Acc], SpAcc)
175	end.
176
177inflate(Zinf, Data) ->
178	try
179		zlib:inflate(Zinf, Data)
180	catch _:_ ->
181		ok = zlib:inflateSetDictionary(Zinf, ?ZDICT),
182		zlib:inflate(Zinf, <<>>)
183	end.
184
185from_flag(0) -> false;
186from_flag(1) -> true.
187
188%% Build.
189
190data(StreamID, IsFin, Data) ->
191	IsFinFlag = to_flag(IsFin),
192	Length = iolist_size(Data),
193	[<< 0:1, StreamID:31, 0:7, IsFinFlag:1, Length:24 >>, Data].
194
195syn_stream(Zdef, StreamID, AssocToStreamID, IsFin, IsUnidirectional,
196		Priority, Method, Scheme, Host, Path, Version, Headers) ->
197	IsFinFlag = to_flag(IsFin),
198	IsUnidirectionalFlag = to_flag(IsUnidirectional),
199	HeaderBlock = build_headers(Zdef, [
200		{<<":method">>, Method},
201		{<<":scheme">>, Scheme},
202		{<<":host">>, Host},
203		{<<":path">>, Path},
204		{<<":version">>, Version}
205		|Headers]),
206	Length = 10 + iolist_size(HeaderBlock),
207	[<< 1:1, 3:15, 1:16, 0:6, IsUnidirectionalFlag:1, IsFinFlag:1,
208		Length:24, 0:1, StreamID:31, 0:1, AssocToStreamID:31,
209		Priority:3, 0:5, 0:8 >>, HeaderBlock].
210
211syn_reply(Zdef, StreamID, IsFin, Status, Version, Headers) ->
212	IsFinFlag = to_flag(IsFin),
213	HeaderBlock = build_headers(Zdef, [
214		{<<":status">>, Status},
215		{<<":version">>, Version}
216		|Headers]),
217	Length = 4 + iolist_size(HeaderBlock),
218	[<< 1:1, 3:15, 2:16, 0:7, IsFinFlag:1, Length:24,
219		0:1, StreamID:31 >>, HeaderBlock].
220
221rst_stream(StreamID, Status) ->
222	StatusCode = case Status of
223		protocol_error -> 1;
224		invalid_stream -> 2;
225		refused_stream -> 3;
226		unsupported_version -> 4;
227		cancel -> 5;
228		internal_error -> 6;
229		flow_control_error -> 7;
230		stream_in_use -> 8;
231		stream_already_closed -> 9;
232		invalid_credentials -> 10;
233		frame_too_large -> 11
234	end,
235	<< 1:1, 3:15, 3:16, 0:8, 8:24,
236		0:1, StreamID:31, StatusCode:32 >>.
237
238settings(ClearSettingsFlag, Settings) ->
239	IsClearSettingsFlag = to_flag(ClearSettingsFlag),
240	NbEntries = length(Settings),
241	Entries = [begin
242		IsWasPersistedFlag = to_flag(WasPersistedFlag),
243		IsPersistFlag = to_flag(PersistFlag),
244		ID = case Key of
245			upload_bandwidth -> 1;
246			download_bandwidth -> 2;
247			round_trip_time -> 3;
248			max_concurrent_streams -> 4;
249			current_cwnd -> 5;
250			download_retrans_rate -> 6;
251			initial_window_size -> 7;
252			client_certificate_vector_size -> 8
253		end,
254		<< 0:6, IsWasPersistedFlag:1, IsPersistFlag:1, ID:24, Value:32 >>
255	end || {Key, Value, WasPersistedFlag, PersistFlag} <- Settings],
256	Length = 4 + iolist_size(Entries),
257	[<< 1:1, 3:15, 4:16, 0:7, IsClearSettingsFlag:1, Length:24,
258		NbEntries:32 >>, Entries].
259
260-ifdef(TEST).
261settings_frame_test() ->
262	ClearSettingsFlag = false,
263	Settings = [{max_concurrent_streams,1000,false,false},
264				{initial_window_size,10485760,false,false}],
265	Bin = list_to_binary(cow_spdy:settings(ClearSettingsFlag, Settings)),
266	P = cow_spdy:parse(Bin, undefined),
267	P = {settings, ClearSettingsFlag, Settings},
268	ok.
269-endif.
270
271ping(PingID) ->
272	<< 1:1, 3:15, 6:16, 0:8, 4:24, PingID:32 >>.
273
274goaway(LastGoodStreamID, Status) ->
275	StatusCode = case Status of
276		ok -> 0;
277		protocol_error -> 1;
278		internal_error -> 2
279	end,
280	<< 1:1, 3:15, 7:16, 0:8, 8:24,
281		0:1, LastGoodStreamID:31, StatusCode:32 >>.
282
283%% @todo headers
284%% @todo window_update
285
286build_headers(Zdef, Headers) ->
287	Headers1 = merge_headers(lists:sort(Headers), []),
288	NbHeaders = length(Headers1),
289	Headers2 = [begin
290		L1 = iolist_size(Key),
291		L2 = iolist_size(Value),
292		[<< L1:32 >>, Key, << L2:32 >>, Value]
293	end || {Key, Value} <- Headers1],
294	zlib:deflate(Zdef, [<< NbHeaders:32 >>, Headers2], full).
295
296merge_headers([], Acc) ->
297	lists:reverse(Acc);
298merge_headers([{Name, Value1}, {Name, Value2}|Tail], Acc) ->
299	merge_headers([{Name, [Value1, 0, Value2]}|Tail], Acc);
300merge_headers([Head|Tail], Acc) ->
301	merge_headers(Tail, [Head|Acc]).
302
303-ifdef(TEST).
304merge_headers_test_() ->
305	Tests = [
306		{[{<<"set-cookie">>, <<"session=123">>}, {<<"set-cookie">>, <<"other=456">>}, {<<"content-type">>, <<"text/html">>}],
307		 [{<<"set-cookie">>, [<<"session=123">>, 0, <<"other=456">>]}, {<<"content-type">>, <<"text/html">>}]}
308	],
309	[fun() -> D = merge_headers(R, []) end || {R, D} <- Tests].
310-endif.
311
312to_flag(false) -> 0;
313to_flag(true) -> 1.
314