cow_spdy.erl (9353B)
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 40 deflate_init() -> 41 Zdef = zlib:open(), 42 ok = zlib:deflateInit(Zdef), 43 _ = zlib:deflateSetDictionary(Zdef, ?ZDICT), 44 Zdef. 45 46 inflate_init() -> 47 Zinf = zlib:open(), 48 ok = zlib:inflateInit(Zinf), 49 Zinf. 50 51 %% Parse. 52 53 split(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}; 58 split(_) -> 59 false. 60 61 parse(<< 0:1, StreamID:31, 0:7, IsFinFlag:1, _:24, Data/bits >>, _) -> 62 {data, StreamID, from_flag(IsFinFlag), Data}; 63 parse(<< 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; 76 parse(<< 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; 85 parse(<< 1:1, 3:15, 3:16, 0:8, _:56, StatusCode:32 >>, _) 86 when StatusCode =:= 0; StatusCode > 11 -> 87 {error, badprotocol}; 88 parse(<< 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}; 103 parse(<< 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; 126 parse(<< 1:1, 3:15, 6:16, 0:8, _:24, PingID:32 >>, _) -> 127 {ping, PingID}; 128 parse(<< 1:1, 3:15, 7:16, 0:8, _:56, StatusCode:32 >>, _) 129 when StatusCode > 2 -> 130 {error, badprotocol}; 131 parse(<< 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}; 139 parse(<< 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; 147 parse(<< 1:1, 3:15, 9:16, 0:8, _:57, 0:31 >>, _) -> 148 {error, badprotocol}; 149 parse(<< 1:1, 3:15, 9:16, 0:8, _:25, StreamID:31, 150 _:1, DeltaWindowSize:31 >>, _) -> 151 {window_update, StreamID, DeltaWindowSize}; 152 parse(_, _) -> 153 {error, badprotocol}. 154 155 parse_headers(Data, Zinf) -> 156 [<< NbHeaders:32, Rest/bits >>] = inflate(Zinf, Data), 157 parse_headers(Rest, NbHeaders, [], []). 158 159 parse_headers(<<>>, 0, Headers, SpHeaders) -> 160 {ok, lists:reverse(Headers), lists:sort(SpHeaders)}; 161 parse_headers(<<>>, _, _, _) -> 162 error; 163 parse_headers(_, 0, _, _) -> 164 error; 165 parse_headers(<< 0:32, _/bits >>, _, _, _) -> 166 error; 167 parse_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 177 inflate(Zinf, Data) -> 178 try 179 zlib:inflate(Zinf, Data) 180 catch _:_ -> 181 ok = zlib:inflateSetDictionary(Zinf, ?ZDICT), 182 zlib:inflate(Zinf, <<>>) 183 end. 184 185 from_flag(0) -> false; 186 from_flag(1) -> true. 187 188 %% Build. 189 190 data(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 195 syn_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 211 syn_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 221 rst_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 238 settings(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). 261 settings_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 271 ping(PingID) -> 272 << 1:1, 3:15, 6:16, 0:8, 4:24, PingID:32 >>. 273 274 goaway(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 286 build_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 296 merge_headers([], Acc) -> 297 lists:reverse(Acc); 298 merge_headers([{Name, Value1}, {Name, Value2}|Tail], Acc) -> 299 merge_headers([{Name, [Value1, 0, Value2]}|Tail], Acc); 300 merge_headers([Head|Tail], Acc) -> 301 merge_headers(Tail, [Head|Acc]). 302 303 -ifdef(TEST). 304 merge_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 312 to_flag(false) -> 0; 313 to_flag(true) -> 1.