|
|
adeb5bb |
From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= <essen@ninenines.eu>
|
|
|
adeb5bb |
Date: Thu, 11 Jun 2015 14:04:21 +0200
|
|
|
adeb5bb |
Subject: [PATCH] Add cowboy_http2 with initial HTTP/2 suppport
|
|
|
adeb5bb |
|
|
|
adeb5bb |
|
|
|
adeb5bb |
diff --git a/src/cow_http2.erl b/src/cow_http2.erl
|
|
|
adeb5bb |
new file mode 100644
|
|
|
adeb5bb |
index 0000000..a4d2e23
|
|
|
adeb5bb |
--- /dev/null
|
|
|
adeb5bb |
+++ b/src/cow_http2.erl
|
|
|
adeb5bb |
@@ -0,0 +1,337 @@
|
|
|
adeb5bb |
+%% Copyright (c) 2015, Loïc Hoguin <essen@ninenines.eu>
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% Permission to use, copy, modify, and/or distribute this software for any
|
|
|
adeb5bb |
+%% purpose with or without fee is hereby granted, provided that the above
|
|
|
adeb5bb |
+%% copyright notice and this permission notice appear in all copies.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
|
adeb5bb |
+%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
|
adeb5bb |
+%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
|
adeb5bb |
+%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
|
adeb5bb |
+%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
|
adeb5bb |
+%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
|
adeb5bb |
+%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+-module(cow_http2).
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+%% Parsing.
|
|
|
adeb5bb |
+-export([parse/1]).
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+%% Building.
|
|
|
adeb5bb |
+-export([data/3]).
|
|
|
adeb5bb |
+-export([headers/3]).
|
|
|
adeb5bb |
+-export([rst_stream/2]).
|
|
|
adeb5bb |
+-export([settings/1]).
|
|
|
adeb5bb |
+-export([push_promise/3]).
|
|
|
adeb5bb |
+-export([ping_ack/1]).
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+-type streamid() :: pos_integer().
|
|
|
adeb5bb |
+-type fin() :: fin | nofin.
|
|
|
adeb5bb |
+-type head_fin() :: head_fin | head_nofin.
|
|
|
adeb5bb |
+-type exclusive() :: exclusive | shared.
|
|
|
adeb5bb |
+-type weight() :: 1..256.
|
|
|
adeb5bb |
+-type settings() :: map().
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+-type error() :: no_error
|
|
|
adeb5bb |
+ | protocol_error
|
|
|
adeb5bb |
+ | internal_error
|
|
|
adeb5bb |
+ | flow_control_error
|
|
|
adeb5bb |
+ | settings_timeout
|
|
|
adeb5bb |
+ | stream_closed
|
|
|
adeb5bb |
+ | frame_size_error
|
|
|
adeb5bb |
+ | refused_stream
|
|
|
adeb5bb |
+ | cancel
|
|
|
adeb5bb |
+ | compression_error
|
|
|
adeb5bb |
+ | connect_error
|
|
|
adeb5bb |
+ | enhance_your_calm
|
|
|
adeb5bb |
+ | inadequate_security
|
|
|
adeb5bb |
+ | http_1_1_required
|
|
|
adeb5bb |
+ | unknown_error.
|
|
|
adeb5bb |
+-export_type([error/0]).
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+-type frame() :: {data, streamid(), fin(), binary()}
|
|
|
adeb5bb |
+ | {headers, streamid(), fin(), head_fin(), binary()}
|
|
|
adeb5bb |
+ | {headers, streamid(), fin(), head_fin(), exclusive(), streamid(), weight(), binary()}
|
|
|
adeb5bb |
+ | {priority, streamid(), exclusive(), streamid(), weight()}
|
|
|
adeb5bb |
+ | {rst_stream, streamid(), error()}
|
|
|
adeb5bb |
+ | {settings, settings()}
|
|
|
adeb5bb |
+ | settings_ack
|
|
|
adeb5bb |
+ | {push_promise, streamid(), head_fin(), streamid(), binary()}
|
|
|
adeb5bb |
+ | {ping, integer()}
|
|
|
adeb5bb |
+ | {ping_ack, integer()}
|
|
|
adeb5bb |
+ | {goaway, streamid(), error(), binary()}
|
|
|
adeb5bb |
+ | {window_update, non_neg_integer()}
|
|
|
adeb5bb |
+ | {window_update, streamid(), non_neg_integer()}
|
|
|
adeb5bb |
+ | {continuation, streamid(), head_fin(), binary()}.
|
|
|
adeb5bb |
+-export_type([frame/0]).
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+%% Parsing.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% DATA frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(<< _:24, 0:8, _:9, 0:31, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'DATA frames MUST be associated with a stream. (RFC7540 6.1)'};
|
|
|
adeb5bb |
+parse(<< Len0:24, 0:8, _:4, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.1)'};
|
|
|
adeb5bb |
+%% No padding.
|
|
|
adeb5bb |
+parse(<< Len:24, 0:8, _:4, 0:1, _:2, FlagEndStream:1, _:1, StreamID:31, Data:Len/binary, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {ok, {data, StreamID, parse_fin(FlagEndStream), Data}, Rest};
|
|
|
adeb5bb |
+%% Padding.
|
|
|
adeb5bb |
+parse(<< Len0:24, 0:8, _:4, 1:1, _:2, FlagEndStream:1, _:1, StreamID:31, PadLen:8, Rest0/bits >>)
|
|
|
adeb5bb |
+ when byte_size(Rest0) >= Len0 - 1 ->
|
|
|
adeb5bb |
+ Len = Len0 - PadLen,
|
|
|
adeb5bb |
+ case Rest0 of
|
|
|
adeb5bb |
+ << Data:Len/binary, 0:PadLen/binary, Rest/bits >> ->
|
|
|
adeb5bb |
+ {ok, {data, StreamID, parse_fin(FlagEndStream), Data}, Rest};
|
|
|
adeb5bb |
+ _ ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.1)'}
|
|
|
adeb5bb |
+ end;
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% HEADERS frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(<< _:24, 1:8, _:9, 0:31, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'HEADERS frames MUST be associated with a stream. (RFC7540 6.2)'};
|
|
|
adeb5bb |
+parse(<< Len0:24, 1:8, _:4, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.2)'};
|
|
|
adeb5bb |
+parse(<< Len0:24, 1:8, _:2, 1:1, _:1, 1:1, _:35, PadLen:8, _/bits >>) when PadLen >= Len0 - 5 ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'Length of padding MUST be less than length of payload. (RFC7540 6.2)'};
|
|
|
adeb5bb |
+%% No padding, no priority.
|
|
|
adeb5bb |
+parse(<< Len:24, 1:8, _:2, 0:1, _:1, 0:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
|
|
|
adeb5bb |
+ HeaderBlockFragment:Len/binary, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders), HeaderBlockFragment}, Rest};
|
|
|
adeb5bb |
+%% Padding, no priority.
|
|
|
adeb5bb |
+parse(<< Len0:24, 1:8, _:2, 0:1, _:1, 1:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
|
|
|
adeb5bb |
+ PadLen:8, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 1 ->
|
|
|
adeb5bb |
+ Len = Len0 - PadLen - 1,
|
|
|
adeb5bb |
+ case Rest0 of
|
|
|
adeb5bb |
+ << HeaderBlockFragment:Len/binary, 0:PadLen/binary, Rest/bits >> ->
|
|
|
adeb5bb |
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders), HeaderBlockFragment}, Rest};
|
|
|
adeb5bb |
+ _ ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.2)'}
|
|
|
adeb5bb |
+ end;
|
|
|
adeb5bb |
+%% No padding, priority.
|
|
|
adeb5bb |
+parse(<< Len0:24, 1:8, _:2, 1:1, _:1, 0:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
|
|
|
adeb5bb |
+ E:1, DepStreamID:31, Weight:8, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 5 ->
|
|
|
adeb5bb |
+ Len = Len0 - 5,
|
|
|
adeb5bb |
+ << HeaderBlockFragment:Len/binary, Rest/bits >> = Rest0,
|
|
|
adeb5bb |
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders),
|
|
|
adeb5bb |
+ parse_exclusive(E), DepStreamID, Weight + 1, HeaderBlockFragment}, Rest};
|
|
|
adeb5bb |
+%% Padding, priority.
|
|
|
adeb5bb |
+parse(<< Len0:24, 1:8, _:2, 1:1, _:1, 1:1, FlagEndHeaders:1, _:1, FlagEndStream:1, _:1, StreamID:31,
|
|
|
adeb5bb |
+ PadLen:8, E:1, DepStreamID:31, Weight:8, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 6 ->
|
|
|
adeb5bb |
+ Len = Len0 - PadLen - 6,
|
|
|
adeb5bb |
+ case Rest0 of
|
|
|
adeb5bb |
+ << HeaderBlockFragment:Len/binary, 0:PadLen/binary, Rest/bits >> ->
|
|
|
adeb5bb |
+ {ok, {headers, StreamID, parse_fin(FlagEndStream), parse_head_fin(FlagEndHeaders),
|
|
|
adeb5bb |
+ parse_exclusive(E), DepStreamID, Weight + 1, HeaderBlockFragment}, Rest};
|
|
|
adeb5bb |
+ _ ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.2)'}
|
|
|
adeb5bb |
+ end;
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% PRIORITY frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(<< 5:24, 2:8, _:9, 0:31, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'PRIORITY frames MUST be associated with a stream. (RFC7540 6.3)'};
|
|
|
adeb5bb |
+parse(<< 5:24, 2:8, _:9, StreamID:31, E:1, DepStreamID:31, Weight:8, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {ok, {priority, StreamID, parse_exclusive(E), DepStreamID, Weight + 1}, Rest};
|
|
|
adeb5bb |
+%% @todo figure out how to best deal with frame size errors; if we have everything fine
|
|
|
adeb5bb |
+%% if not we might want to inform the caller how much he should expect so that it can
|
|
|
adeb5bb |
+%% decide if it should just close the connection
|
|
|
adeb5bb |
+parse(<< BadLen:24, 2:8, _:9, StreamID:31, _:BadLen/binary, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {stream_error, StreamID, frame_size_error, 'PRIORITY frames MUST be 5 bytes wide. (RFC7540 6.3)', Rest};
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% RST_STREAM frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(<< 4:24, 3:8, _:9, 0:31, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'RST_STREAM frames MUST be associated with a stream. (RFC7540 6.4)'};
|
|
|
adeb5bb |
+parse(<< 4:24, 3:8, _:9, StreamID:31, ErrorCode:32, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {ok, {rst_stream, StreamID, parse_error_code(ErrorCode)}, Rest};
|
|
|
adeb5bb |
+%% @todo same as priority
|
|
|
adeb5bb |
+parse(<< BadLen:24, 3:8, _:9, StreamID:31, _:BadLen/binary, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {stream_error, StreamID, frame_size_error, 'RST_STREAM frames MUST be 4 bytes wide. (RFC7540 6.4)', Rest};
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% SETTINGS frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(<< 0:24, 4:8, _:7, 1:1, _:1, 0:31, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {ok, settings_ack, Rest};
|
|
|
adeb5bb |
+parse(<< _:24, 4:8, _:7, 1:1, _:1, 0:31, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, frame_size_error, 'SETTINGS frames with the ACK flag set MUST have a length of 0. (RFC7540 6.5)'};
|
|
|
adeb5bb |
+parse(<< Len:24, 4:8, _:7, 0:1, _:1, 0:31, _/bits >>) when Len rem 6 =/= 0 ->
|
|
|
adeb5bb |
+ {connection_error, frame_size_error, 'SETTINGS frames MUST have a length multiple of 6. (RFC7540 6.5)'};
|
|
|
adeb5bb |
+parse(<< Len:24, 4:8, _:7, 0:1, _:1, 0:31, Rest/bits >>) when byte_size(Rest) >= Len ->
|
|
|
adeb5bb |
+ parse_settings(Rest, Len, #{});
|
|
|
adeb5bb |
+parse(<< _:24, 4:8, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'SETTINGS frames MUST NOT be associated with a stream. (RFC7540 6.5)'};
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% PUSH_PROMISE frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(<< _:24, 5:8, _:9, 0:31, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'PUSH_PROMISE frames MUST be associated with a stream. (RFC7540 6.6)'};
|
|
|
adeb5bb |
+parse(<< Len0:24, 5:8, _:4, 0:1, FlagEndHeaders:1, _:3, StreamID:31, _:1, PromisedStreamID:31, Rest0/bits >>)
|
|
|
adeb5bb |
+ when byte_size(Rest0) >= Len0 - 4 ->
|
|
|
adeb5bb |
+ Len = Len0 - 4,
|
|
|
adeb5bb |
+ << HeaderBlockFragment:Len/binary, Rest/bits >> = Rest0,
|
|
|
adeb5bb |
+ {ok, {push_promise, StreamID, parse_head_fin(FlagEndHeaders), PromisedStreamID, HeaderBlockFragment}, Rest};
|
|
|
adeb5bb |
+parse(<< Len0:24, 5:8, _:4, 1:1, FlagEndHeaders:1, _:2, StreamID:31, PadLen:8, _:1, PromisedStreamID:31, Rest0/bits >>)
|
|
|
adeb5bb |
+ when byte_size(Rest0) >= Len0 - 5 ->
|
|
|
adeb5bb |
+ Len = Len0 - 5,
|
|
|
adeb5bb |
+ case Rest0 of
|
|
|
adeb5bb |
+ << HeaderBlockFragment:Len/binary, 0:PadLen/binary, Rest/bits >> ->
|
|
|
adeb5bb |
+ {ok, {push_promise, StreamID, parse_head_fin(FlagEndHeaders), PromisedStreamID, HeaderBlockFragment}, Rest};
|
|
|
adeb5bb |
+ _ ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'Padding octets MUST be set to zero. (RFC7540 6.6)'}
|
|
|
adeb5bb |
+ end;
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% PING frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(<< 8:24, 6:8, _:7, 1:1, _:1, 0:31, Opaque:64, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {ok, {ping_ack, Opaque}, Rest};
|
|
|
adeb5bb |
+parse(<< 8:24, 6:8, _:7, 0:1, _:1, 0:31, Opaque:64, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {ok, {ping, Opaque}, Rest};
|
|
|
adeb5bb |
+parse(<< 8:24, 6:8, _:104, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'PING frames MUST NOT be associated with a stream. (RFC7540 6.7)'};
|
|
|
adeb5bb |
+parse(<< _:24, 6:8, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, frame_size_error, 'PING frames MUST be 8 bytes wide. (RFC7540 6.7)'};
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% GOAWAY frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(<< Len0:24, 7:8, _:9, 0:31, _:1, LastStreamID:31, ErrorCode:32, Rest0/bits >>) when byte_size(Rest0) >= Len0 - 8 ->
|
|
|
adeb5bb |
+ Len = Len0 - 8,
|
|
|
adeb5bb |
+ << DebugData:Len/binary, Rest/bits >> = Rest0,
|
|
|
adeb5bb |
+ {ok, {goaway, LastStreamID, parse_error_code(ErrorCode), DebugData}, Rest};
|
|
|
adeb5bb |
+parse(<< _:24, 7:8, _:40, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'GOAWAY frames MUST NOT be associated with a stream. (RFC7540 6.8)'};
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% WINDOW_UPDATE frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(<< 4:24, 8:8, _:9, 0:31, _:1, 0:31, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'WINDOW_UPDATE frames MUST have a non-zero increment. (RFC7540 6.9)'};
|
|
|
adeb5bb |
+parse(<< 4:24, 8:8, _:9, 0:31, _:1, Increment:31, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {ok, {window_update, Increment}, Rest};
|
|
|
adeb5bb |
+parse(<< 4:24, 8:8, _:9, StreamID:31, _:1, 0:31, _/bits >>) ->
|
|
|
adeb5bb |
+ {stream_error, StreamID, protocol_error, 'WINDOW_UPDATE frames MUST have a non-zero increment. (RFC7540 6.9)'};
|
|
|
adeb5bb |
+parse(<< 4:24, 8:8, _:9, StreamID:31, _:1, Increment:31, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {ok, {window_update, StreamID, Increment}, Rest};
|
|
|
adeb5bb |
+parse(<< _:24, 8:8, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, frame_size_error, 'WINDOW_UPDATE frames MUST be 4 bytes wide. (RFC7540 6.9)'};
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% CONTINUATION frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(<< _:24, 9:8, _:9, 0:31, _/bits >>) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'CONTINUATION frames MUST be associated with a stream. (RFC7540 6.10)'};
|
|
|
adeb5bb |
+parse(<< Len:24, 9:8, _:5, FlagEndHeaders:1, _:3, StreamID:31, HeaderBlockFragment:Len/binary, Rest/bits >>) ->
|
|
|
adeb5bb |
+ {ok, {continuation, StreamID, parse_head_fin(FlagEndHeaders), HeaderBlockFragment}, Rest};
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+%% Incomplete frames.
|
|
|
adeb5bb |
+%%
|
|
|
adeb5bb |
+parse(_) ->
|
|
|
adeb5bb |
+ more.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+parse_fin(0) -> nofin;
|
|
|
adeb5bb |
+parse_fin(1) -> fin.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+parse_head_fin(0) -> head_nofin;
|
|
|
adeb5bb |
+parse_head_fin(1) -> head_fin.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+parse_exclusive(0) -> shared;
|
|
|
adeb5bb |
+parse_exclusive(1) -> exclusive.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+parse_error_code( 0) -> no_error;
|
|
|
adeb5bb |
+parse_error_code( 1) -> protocol_error;
|
|
|
adeb5bb |
+parse_error_code( 2) -> internal_error;
|
|
|
adeb5bb |
+parse_error_code( 3) -> flow_control_error;
|
|
|
adeb5bb |
+parse_error_code( 4) -> settings_timeout;
|
|
|
adeb5bb |
+parse_error_code( 5) -> stream_closed;
|
|
|
adeb5bb |
+parse_error_code( 6) -> frame_size_error;
|
|
|
adeb5bb |
+parse_error_code( 7) -> refused_stream;
|
|
|
adeb5bb |
+parse_error_code( 8) -> cancel;
|
|
|
adeb5bb |
+parse_error_code( 9) -> compression_error;
|
|
|
adeb5bb |
+parse_error_code(10) -> connect_error;
|
|
|
adeb5bb |
+parse_error_code(11) -> enhance_your_calm;
|
|
|
adeb5bb |
+parse_error_code(12) -> inadequate_security;
|
|
|
adeb5bb |
+parse_error_code(13) -> http_1_1_required;
|
|
|
adeb5bb |
+parse_error_code(_) -> unknown_error.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+parse_settings(Rest, 0, Settings) ->
|
|
|
adeb5bb |
+ {ok, {settings, Settings}, Rest};
|
|
|
adeb5bb |
+%% SETTINGS_HEADER_TABLE_SIZE.
|
|
|
adeb5bb |
+parse_settings(<< 1:16, Value:32, Rest/bits >>, Len, Settings) ->
|
|
|
adeb5bb |
+ parse_settings(Rest, Len - 6, Settings#{header_table_size => Value});
|
|
|
adeb5bb |
+%% SETTINGS_ENABLE_PUSH.
|
|
|
adeb5bb |
+parse_settings(<< 2:16, 0:32, Rest/bits >>, Len, Settings) ->
|
|
|
adeb5bb |
+ parse_settings(Rest, Len - 6, Settings#{enable_push => false});
|
|
|
adeb5bb |
+parse_settings(<< 2:16, 1:32, Rest/bits >>, Len, Settings) ->
|
|
|
adeb5bb |
+ parse_settings(Rest, Len - 6, Settings#{enable_push => true});
|
|
|
adeb5bb |
+parse_settings(<< 2:16, _:32, _/bits >>, _, _) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'The SETTINGS_ENABLE_PUSH value MUST be 0 or 1. (RFC7540 6.5.2)'};
|
|
|
adeb5bb |
+%% SETTINGS_MAX_CONCURRENT_STREAMS.
|
|
|
adeb5bb |
+parse_settings(<< 3:16, Value:32, Rest/bits >>, Len, Settings) ->
|
|
|
adeb5bb |
+ parse_settings(Rest, Len - 6, Settings#{max_concurrent_streams => Value});
|
|
|
adeb5bb |
+%% SETTINGS_INITIAL_WINDOW_SIZE.
|
|
|
adeb5bb |
+parse_settings(<< 4:16, Value:32, _/bits >>, _, _) when Value > 16#7fffffff ->
|
|
|
adeb5bb |
+ {connection_error, flow_control_error, 'The maximum SETTINGS_INITIAL_WINDOW_SIZE value is 0x7fffffff. (RFC7540 6.5.2)'};
|
|
|
adeb5bb |
+parse_settings(<< 4:16, Value:32, Rest/bits >>, Len, Settings) ->
|
|
|
adeb5bb |
+ parse_settings(Rest, Len - 6, Settings#{initial_window_size => Value});
|
|
|
adeb5bb |
+%% SETTINGS_MAX_FRAME_SIZE.
|
|
|
adeb5bb |
+parse_settings(<< 5:16, Value:32, _/bits >>, _, _) when Value =< 16#3fff ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'The SETTINGS_MAX_FRAME_SIZE value must be > 0x3fff. (RFC7540 6.5.2)'};
|
|
|
adeb5bb |
+parse_settings(<< 5:16, Value:32, Rest/bits >>, Len, Settings) when Value =< 16#ffffff ->
|
|
|
adeb5bb |
+ parse_settings(Rest, Len - 6, Settings#{max_frame_size => Value});
|
|
|
adeb5bb |
+parse_settings(<< 5:16, _:32, _/bits >>, _, _) ->
|
|
|
adeb5bb |
+ {connection_error, protocol_error, 'The SETTINGS_MAX_FRAME_SIZE value must be =< 0xffffff. (RFC7540 6.5.2)'};
|
|
|
adeb5bb |
+%% SETTINGS_MAX_HEADER_LIST_SIZE.
|
|
|
adeb5bb |
+parse_settings(<< 6:16, Value:32, Rest/bits >>, Len, Settings) ->
|
|
|
adeb5bb |
+ parse_settings(Rest, Len - 6, Settings#{max_header_list_size => Value});
|
|
|
adeb5bb |
+parse_settings(<< _:48, Rest/bits >>, Len, Settings) ->
|
|
|
adeb5bb |
+ parse_settings(Rest, Len - 6, Settings).
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+%% Building.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+%% @todo Check size and create multiple frames if needed.
|
|
|
adeb5bb |
+data(StreamID, IsFin, Data) ->
|
|
|
adeb5bb |
+ Len = iolist_size(Data),
|
|
|
adeb5bb |
+ FlagEndStream = flag_fin(IsFin),
|
|
|
adeb5bb |
+ [<< Len:24, 0:15, FlagEndStream:1, 0:1, StreamID:31 >>, Data].
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+%% @todo Check size of HeaderBlock and use CONTINUATION frames if needed.
|
|
|
adeb5bb |
+headers(StreamID, IsFin, HeaderBlock) ->
|
|
|
adeb5bb |
+ Len = iolist_size(HeaderBlock),
|
|
|
adeb5bb |
+ FlagEndStream = flag_fin(IsFin),
|
|
|
adeb5bb |
+ FlagEndHeaders = 1,
|
|
|
adeb5bb |
+ [<< Len:24, 1:8, 0:5, FlagEndHeaders:1, 0:1, FlagEndStream:1, 0:1, StreamID:31 >>, HeaderBlock].
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+rst_stream(StreamID, Reason) ->
|
|
|
adeb5bb |
+ ErrorCode = error_code(Reason),
|
|
|
adeb5bb |
+ << 4:24, 3:8, 0:9, StreamID:31, ErrorCode:32 >>.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+%% @todo Actually implement it. :-)
|
|
|
adeb5bb |
+settings(#{}) ->
|
|
|
adeb5bb |
+ << 0:24, 4:8, 0:40 >>.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+%% @todo Check size of HeaderBlock and use CONTINUATION frames if needed.
|
|
|
adeb5bb |
+push_promise(StreamID, PromisedStreamID, HeaderBlock) ->
|
|
|
adeb5bb |
+ Len = iolist_size(HeaderBlock) + 4,
|
|
|
adeb5bb |
+ FlagEndHeaders = 1,
|
|
|
adeb5bb |
+ [<< Len:24, 5:8, 0:5, FlagEndHeaders:1, 0:3, StreamID:31, 0:1, PromisedStreamID:31 >>, HeaderBlock].
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+ping_ack(Opaque) ->
|
|
|
adeb5bb |
+ << 8:24, 6:8, 0:7, 1:1, 0:32, Opaque:64 >>.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+flag_fin(nofin) -> 0;
|
|
|
adeb5bb |
+flag_fin(fin) -> 1.
|
|
|
adeb5bb |
+
|
|
|
adeb5bb |
+error_code(no_error) -> 0;
|
|
|
adeb5bb |
+error_code(protocol_error) -> 1;
|
|
|
adeb5bb |
+error_code(internal_error) -> 2;
|
|
|
adeb5bb |
+error_code(flow_control_error) -> 3;
|
|
|
adeb5bb |
+error_code(settings_timeout) -> 4;
|
|
|
adeb5bb |
+error_code(stream_closed) -> 5;
|
|
|
adeb5bb |
+error_code(frame_size_error) -> 6;
|
|
|
adeb5bb |
+error_code(refused_stream) -> 7;
|
|
|
adeb5bb |
+error_code(cancel) -> 8;
|
|
|
adeb5bb |
+error_code(compression_error) -> 9;
|
|
|
adeb5bb |
+error_code(connect_error) -> 10;
|
|
|
adeb5bb |
+error_code(enhance_your_calm) -> 11;
|
|
|
adeb5bb |
+error_code(inadequate_security) -> 12;
|
|
|
adeb5bb |
+error_code(http_1_1_required) -> 13.
|