aboutsummaryrefslogtreecommitdiffstats
path: root/test
diff options
context:
space:
mode:
authorLoïc Hoguin <[email protected]>2018-04-26 22:08:05 +0200
committerLoïc Hoguin <[email protected]>2018-04-26 22:08:05 +0200
commitd38d86c4a93340b1dd2633e1649c257e3f160d63 (patch)
treebe371e3aab65b4375ab03e71426bb72652e12872 /test
parentb2f16d462a9e08b50430814a3028bb5b123a02d2 (diff)
downloadcowboy-d38d86c4a93340b1dd2633e1649c257e3f160d63.tar.gz
cowboy-d38d86c4a93340b1dd2633e1649c257e3f160d63.tar.bz2
cowboy-d38d86c4a93340b1dd2633e1649c257e3f160d63.zip
Add options controlling initial control flow windows
Diffstat (limited to 'test')
-rw-r--r--test/http2_SUITE.erl38
-rw-r--r--test/rfc7540_SUITE.erl222
2 files changed, 242 insertions, 18 deletions
diff --git a/test/http2_SUITE.erl b/test/http2_SUITE.erl
index 933a2b2..5b9ca95 100644
--- a/test/http2_SUITE.erl
+++ b/test/http2_SUITE.erl
@@ -18,6 +18,7 @@
-import(ct_helper, [config/2]).
-import(ct_helper, [doc/1]).
+-import(ct_helper, [name/0]).
-import(cowboy_test, [gun_open/1]).
all() -> [{group, clear}].
@@ -46,28 +47,49 @@ do_handshake(Config) ->
{ok, Socket}.
inactivity_timeout(Config) ->
- doc("Terminate when the inactivity timeout is reached"),
+ doc("Terminate when the inactivity timeout is reached."),
ProtoOpts = #{
env => #{dispatch => cowboy_router:compile(init_routes(Config))},
inactivity_timeout => 1000
},
- {ok, _} = cowboy:start_clear(inactivity_timeout, [{port, 0}], ProtoOpts),
- Port = ranch:get_port(inactivity_timeout),
+ {ok, _} = cowboy:start_clear(name(), [{port, 0}], ProtoOpts),
+ Port = ranch:get_port(name()),
{ok, Socket} = do_handshake([{port, Port}|Config]),
receive after 1000 -> ok end,
%% Receive a GOAWAY frame back with an INTERNAL_ERROR.
{ok, << _:24, 7:8, _:72, 2:32 >>} = gen_tcp:recv(Socket, 17, 1000),
ok.
+initial_connection_window_size(Config) ->
+ doc("Confirm a WINDOW_UPDATE frame is sent when the configured "
+ "connection window is larger than the default."),
+ ConfiguredSize = 100000,
+ ProtoOpts = #{
+ env => #{dispatch => cowboy_router:compile(init_routes(Config))},
+ initial_connection_window_size => ConfiguredSize
+ },
+ {ok, _} = cowboy:start_clear(name(), [{port, 0}], ProtoOpts),
+ Port = ranch:get_port(name()),
+ {ok, Socket} = gen_tcp:connect("localhost", Port, [binary, {active, false}]),
+ %% Send a valid preface.
+ ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
+ %% Receive the server preface.
+ {ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
+ {ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
+ %% Receive a WINDOW_UPDATE frame incrementing the connection window to 100000.
+ {ok, <<4:24, 8:8, 0:41, Size:31>>} = gen_tcp:recv(Socket, 13, 1000),
+ ConfiguredSize = Size + 65535,
+ ok.
+
preface_timeout_infinity(Config) ->
- doc("Ensure infinity for preface_timeout is accepted"),
+ doc("Ensure infinity for preface_timeout is accepted."),
ProtoOpts = #{
env => #{dispatch => cowboy_router:compile(init_routes(Config))},
preface_timeout => infinity
},
- {ok, Pid} = cowboy:start_clear(preface_timeout_infinity, [{port, 0}], ProtoOpts),
+ {ok, Pid} = cowboy:start_clear(name(), [{port, 0}], ProtoOpts),
Ref = erlang:monitor(process, Pid),
- Port = ranch:get_port(preface_timeout_infinity),
+ Port = ranch:get_port(name()),
{ok, _} = do_handshake([{port, Port}|Config]),
receive
{'DOWN', Ref, process, Pid, Reason} ->
@@ -83,8 +105,8 @@ resp_iolist_body(Config) ->
ProtoOpts = #{
env => #{dispatch => cowboy_router:compile(init_routes(Config))}
},
- {ok, _} = cowboy:start_clear(resp_iolist_body, [{port, 0}], ProtoOpts),
- Port = ranch:get_port(resp_iolist_body),
+ {ok, _} = cowboy:start_clear(name(), [{port, 0}], ProtoOpts),
+ Port = ranch:get_port(name()),
ConnPid = gun_open([{type, tcp}, {protocol, http2}, {port, Port}|Config]),
Ref = gun:get(ConnPid, "/resp_iolist_body"),
{response, nofin, 200, RespHeaders} = gun:await(ConnPid, Ref),
diff --git a/test/rfc7540_SUITE.erl b/test/rfc7540_SUITE.erl
index 14e38fd..d4d5f30 100644
--- a/test/rfc7540_SUITE.erl
+++ b/test/rfc7540_SUITE.erl
@@ -2578,11 +2578,139 @@ settings_max_concurrent_streams_0(Config0) ->
% of error code determines whether the endpoint wishes to enable
% automatic retry (see Section 8.1.4) for details).
-% SETTINGS_INITIAL_WINDOW_SIZE (0x4):
-% Values above the maximum flow-control window size of 2^31-1 MUST
-% be treated as a connection error (Section 5.4.1) of type
-% FLOW_CONTROL_ERROR.
-%
+settings_initial_window_size(Config0) ->
+ doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to "
+ "change the initial window size of streams. (RFC7540 6.5.2)"),
+ %% Create a new listener that allows only a single concurrent stream.
+ Config = cowboy_test:init_http(name(), #{
+ env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
+ initial_connection_window_size => 100000,
+ initial_stream_window_size => 100000
+ }, Config0),
+ %% We need to do the handshake manually because a WINDOW_UPDATE
+ %% frame will be sent to update the connection window.
+ {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
+ %% Send a valid preface.
+ ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
+ %% Receive the server preface.
+ {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000),
+ {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000),
+ %% Send the SETTINGS ack.
+ ok = gen_tcp:send(Socket, cow_http2:settings_ack()),
+ %% Receive the WINDOW_UPDATE for the connection.
+ {ok, << 4:24, 8:8, 0:40, _:32 >>} = gen_tcp:recv(Socket, 13, 1000),
+ %% Receive the SETTINGS ack.
+ {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
+ %% Send a HEADERS frame initiating a stream followed by
+ %% DATA frames totaling 90000 bytes of body.
+ Headers = [
+ {<<":method">>, <<"POST">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+ {<<":path">>, <<"/long_polling">>}
+ ],
+ {HeadersBlock, _} = cow_hpack:encode(Headers),
+ ok = gen_tcp:send(Socket, [
+ cow_http2:headers(1, nofin, HeadersBlock),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, fin, <<0:15000/unit:8>>)
+ ]),
+ %% Receive a proper response.
+ {ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
+ {ok, _} = gen_tcp:recv(Socket, Len2, 6000),
+ %% No errors follow due to our sending of more than 65535 bytes of data.
+ {error, timeout} = gen_tcp:recv(Socket, 0, 1000),
+ ok.
+
+settings_initial_window_size_after_ack(Config0) ->
+ doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to "
+ "change the initial window size of streams. It is applied "
+ "to all existing streams upon receipt of the SETTINGS ack. (RFC7540 6.5.2)"),
+ %% Create a new listener that allows only a single concurrent stream.
+ Config = cowboy_test:init_http(name(), #{
+ env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
+ initial_stream_window_size => 0
+ }, Config0),
+ %% We need to do the handshake manually because we don't
+ %% want to send the SETTINGS ack immediately.
+ {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
+ %% Send a valid preface.
+ ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
+ %% Receive the server preface.
+ {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000),
+ {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000),
+ %%
+ %% Don't send the SETTINGS ack yet! We want to create a stream first.
+ %%
+ %% Receive the SETTINGS ack.
+ {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
+ %% Send a HEADERS frame initiating a stream, a SETTINGS ack
+ %% and a small DATA frame despite no window available in the stream.
+ Headers = [
+ {<<":method">>, <<"POST">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+ {<<":path">>, <<"/long_polling">>}
+ ],
+ {HeadersBlock, _} = cow_hpack:encode(Headers),
+ ok = gen_tcp:send(Socket, [
+ cow_http2:headers(1, nofin, HeadersBlock),
+ cow_http2:settings_ack(),
+ cow_http2:data(1, fin, <<0:32/unit:8>>)
+ ]),
+ %% Receive a FLOW_CONTROL_ERROR stream error.
+ {ok, << _:24, 3:8, _:8, 1:32, 3:32 >>} = gen_tcp:recv(Socket, 13, 6000),
+ ok.
+
+settings_initial_window_size_before_ack(Config0) ->
+ doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to "
+ "change the initial window size of streams. It is only "
+ "applied upon receipt of the SETTINGS ack. (RFC7540 6.5.2)"),
+ %% Create a new listener that allows only a single concurrent stream.
+ Config = cowboy_test:init_http(name(), #{
+ env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
+ initial_stream_window_size => 0
+ }, Config0),
+ %% We need to do the handshake manually because we don't
+ %% want to send the SETTINGS ack.
+ {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
+ %% Send a valid preface.
+ ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
+ %% Receive the server preface.
+ {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000),
+ {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000),
+ %%
+ %% Don't send the SETTINGS ack! We want the server to keep the original settings.
+ %%
+ %% Receive the SETTINGS ack.
+ {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
+ %% Send a HEADERS frame initiating a stream followed by
+ %% DATA frames totaling 60000 bytes of body.
+ Headers = [
+ {<<":method">>, <<"POST">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+ {<<":path">>, <<"/long_polling">>}
+ ],
+ {HeadersBlock, _} = cow_hpack:encode(Headers),
+ ok = gen_tcp:send(Socket, [
+ cow_http2:headers(1, nofin, HeadersBlock),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, fin, <<0:15000/unit:8>>)
+ ]),
+ %% Receive a proper response.
+ {ok, << Len2:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
+ {ok, _} = gen_tcp:recv(Socket, Len2, 6000),
+ %% No errors follow due to our sending of more than 0 bytes of data.
+ {error, timeout} = gen_tcp:recv(Socket, 0, 1000),
+ ok.
+
% SETTINGS_MAX_FRAME_SIZE (0x5):
% The initial value is 2^14 (16,384) octets. The value advertised
% by an endpoint MUST be between this initial value and the maximum
@@ -2737,11 +2865,85 @@ window_update_reject_0_stream(Config) ->
% the receiver does not, the flow-control window at the sender and
% receiver can become different.
+data_reject_overflow(Config0) ->
+ doc("DATA frames that cause the connection flow control window "
+ "to overflow must be rejected with a FLOW_CONTROL_ERROR "
+ "connection error. (RFC7540 6.9.1)"),
+ %% Create a new listener that allows only a single concurrent stream.
+ Config = cowboy_test:init_http(name(), #{
+ env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
+ initial_stream_window_size => 100000
+ }, Config0),
+ {ok, Socket} = do_handshake(Config),
+ %% Send a HEADERS frame initiating a stream followed by
+ %% DATA frames totaling 90000 bytes of body.
+ Headers = [
+ {<<":method">>, <<"POST">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+ {<<":path">>, <<"/long_polling">>}
+ ],
+ {HeadersBlock, _} = cow_hpack:encode(Headers),
+ ok = gen_tcp:send(Socket, [
+ cow_http2:headers(1, nofin, HeadersBlock),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, fin, <<0:15000/unit:8>>)
+ ]),
+ %% Receive a FLOW_CONTROL_ERROR connection error.
+ {ok, << _:24, 7:8, _:72, 3:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+ ok.
+
+data_reject_overflow_stream(Config0) ->
+ doc("DATA frames that cause the stream flow control window "
+ "to overflow must be rejected with a FLOW_CONTROL_ERROR "
+ "stream error. (RFC7540 6.9.1)"),
+ %% Create a new listener that allows only a single concurrent stream.
+ Config = cowboy_test:init_http(name(), #{
+ env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
+ initial_connection_window_size => 100000
+ }, Config0),
+ %% We need to do the handshake manually because a WINDOW_UPDATE
+ %% frame will be sent to update the connection window.
+ {ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
+ %% Send a valid preface.
+ ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
+ %% Receive the server preface.
+ {ok, << Len1:24 >>} = gen_tcp:recv(Socket, 3, 1000),
+ {ok, << 4:8, 0:40, _:Len1/binary >>} = gen_tcp:recv(Socket, 6 + Len1, 1000),
+ %% Send the SETTINGS ack.
+ ok = gen_tcp:send(Socket, cow_http2:settings_ack()),
+ %% Receive the WINDOW_UPDATE for the connection.
+ {ok, << 4:24, 8:8, 0:40, _:32 >>} = gen_tcp:recv(Socket, 13, 1000),
+ %% Receive the SETTINGS ack.
+ {ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
+ %% Send a HEADERS frame initiating a stream followed by
+ %% DATA frames totaling 90000 bytes of body.
+ Headers = [
+ {<<":method">>, <<"POST">>},
+ {<<":scheme">>, <<"http">>},
+ {<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+ {<<":path">>, <<"/long_polling">>}
+ ],
+ {HeadersBlock, _} = cow_hpack:encode(Headers),
+ ok = gen_tcp:send(Socket, [
+ cow_http2:headers(1, nofin, HeadersBlock),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, nofin, <<0:15000/unit:8>>),
+ cow_http2:data(1, fin, <<0:15000/unit:8>>)
+ ]),
+ %% Receive a FLOW_CONTROL_ERROR stream error.
+ {ok, << _:24, 3:8, _:8, 1:32, 3:32 >>} = gen_tcp:recv(Socket, 13, 6000),
+ ok.
+
%% (RFC7540 6.9.1)
-% The sender MUST NOT
-% send a flow-controlled frame with a length that exceeds the space
-% available in either of the flow-control windows advertised by the
-% receiver. Frames with zero length with the END_STREAM flag set (that
+% Frames with zero length with the END_STREAM flag set (that
% is, an empty DATA frame) MAY be sent if there is no available space
% in either flow-control window.
@@ -2852,7 +3054,7 @@ settings_initial_window_size_changes_negative(Config) ->
settings_initial_window_size_reject_overflow(Config) ->
doc("A SETTINGS_INITIAL_WINDOW_SIZE that causes a flow control window "
"to exceed 2^31-1 must be rejected with a FLOW_CONTROL_ERROR "
- "connection error. (RFC7540 6.9.2)"),
+ "connection error. (RFC7540 6.5.2, RFC7540 6.9.2)"),
{ok, Socket} = do_handshake(Config),
%% Set SETTINGS_INITIAL_WINDOW_SIZE to 2^31.
ok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 16#80000000})),