Browse Source

Add options controlling maximum h2 frame sizes

Loïc Hoguin 7 years ago
parent
commit
9a29aea148
4 changed files with 174 additions and 73 deletions
  1. 17 1
      doc/src/manual/cowboy_http2.asciidoc
  2. 14 6
      src/cowboy_http2.erl
  3. 38 2
      test/http2_SUITE.erl
  4. 105 64
      test/rfc7540_SUITE.erl

+ 17 - 1
doc/src/manual/cowboy_http2.asciidoc

@@ -26,6 +26,8 @@ opts() :: #{
     max_concurrent_streams         => non_neg_integer() | infinity,
     max_concurrent_streams         => non_neg_integer() | infinity,
     max_decode_table_size          => non_neg_integer(),
     max_decode_table_size          => non_neg_integer(),
     max_encode_table_size          => non_neg_integer(),
     max_encode_table_size          => non_neg_integer(),
+    max_frame_size_received        => 16384..16777215,
+    max_frame_size_sent            => 16384..16777215 | infinity,
     middlewares                    => [module()],
     middlewares                    => [module()],
     preface_timeout                => timeout(),
     preface_timeout                => timeout(),
     shutdown_timeout               => timeout(),
     shutdown_timeout               => timeout(),
@@ -84,6 +86,19 @@ max_encode_table_size (4096)::
     value to what the client advertises and choose the smallest one as the
     value to what the client advertises and choose the smallest one as the
     encoder's header table size.
     encoder's header table size.
 
 
+max_frame_size_received (16384)::
+    Maximum size of the frames received by the server. This value is
+    advertised to the remote endpoint which can then decide to use
+    any value lower or equal for its frame sizes.
+
+max_frame_size_sent (infinity)::
+    Maximum size of the frames sent by the server. This option allows
+    setting an upper limit to the frame sizes instead of blindly
+    following the client's advertised maximum.
++
+    Note that actual frame sizes may be lower than the limit when
+    there is not enough space left in the flow control window.
+
 middlewares ([cowboy_router, cowboy_handler])::
 middlewares ([cowboy_router, cowboy_handler])::
     Middlewares to run for every request.
     Middlewares to run for every request.
 
 
@@ -100,7 +115,8 @@ stream_handlers ([cowboy_stream_h])::
 
 
 * *2.4*: Add the options `initial_connection_window_size`,
 * *2.4*: Add the options `initial_connection_window_size`,
          `initial_stream_window_size`, `max_concurrent_streams`,
          `initial_stream_window_size`, `max_concurrent_streams`,
-         `max_decode_table_size` and `max_encode_table_size`
+         `max_decode_table_size`, `max_encode_table_size`,
+         `max_frame_size_received` and `max_frame_size_sent`
          to configure HTTP/2 SETTINGS.
          to configure HTTP/2 SETTINGS.
 * *2.4*: Add the experimental option `enable_connect_protocol`.
 * *2.4*: Add the experimental option `enable_connect_protocol`.
 * *2.0*: Protocol introduced.
 * *2.0*: Protocol introduced.

+ 14 - 6
src/cowboy_http2.erl

@@ -32,6 +32,8 @@
 	max_concurrent_streams => non_neg_integer() | infinity,
 	max_concurrent_streams => non_neg_integer() | infinity,
 	max_decode_table_size => non_neg_integer(),
 	max_decode_table_size => non_neg_integer(),
 	max_encode_table_size => non_neg_integer(),
 	max_encode_table_size => non_neg_integer(),
+	max_frame_size_received => 16384..16777215,
+	max_frame_size_sent => 16384..16777215 | infinity,
 	middlewares => [module()],
 	middlewares => [module()],
 	preface_timeout => timeout(),
 	preface_timeout => timeout(),
 	shutdown_timeout => timeout(),
 	shutdown_timeout => timeout(),
@@ -89,7 +91,7 @@
 	%% the final settings handling will be very different.
 	%% the final settings handling will be very different.
 	local_settings = #{
 	local_settings = #{
 %		header_table_size => 4096,
 %		header_table_size => 4096,
-%		enable_push => false, %% We are the server. Push is never enabled.
+%		enable_push => false, %% We are the server. Push is never enabled for clients.
 %		max_concurrent_streams => infinity,
 %		max_concurrent_streams => infinity,
 		initial_window_size => 65535,
 		initial_window_size => 65535,
 		max_frame_size => 16384
 		max_frame_size => 16384
@@ -215,9 +217,10 @@ settings_init(State, Opts) ->
 		max_concurrent_streams, infinity),
 		max_concurrent_streams, infinity),
 	S2 = setting_from_opt(S1, Opts, initial_stream_window_size,
 	S2 = setting_from_opt(S1, Opts, initial_stream_window_size,
 		initial_window_size, 65535),
 		initial_window_size, 65535),
-	%% @todo max_frame_size
+	S3 = setting_from_opt(S2, Opts, max_frame_size_received,
+		max_frame_size, 16384),
 	%% @todo max_header_list_size
 	%% @todo max_header_list_size
-	Settings = setting_from_opt(S2, Opts, enable_connect_protocol,
+	Settings = setting_from_opt(S3, Opts, enable_connect_protocol,
 		enable_connect_protocol, false),
 		enable_connect_protocol, false),
 	State#state{next_settings=Settings}.
 	State#state{next_settings=Settings}.
 
 
@@ -810,10 +813,15 @@ send_data(State=#state{local_window=ConnWindow},
 		Stream=#stream{local_window=StreamWindow}, IsFin, Data, In)
 		Stream=#stream{local_window=StreamWindow}, IsFin, Data, In)
 		when ConnWindow =< 0; StreamWindow =< 0 ->
 		when ConnWindow =< 0; StreamWindow =< 0 ->
 	{State, queue_data(Stream, IsFin, Data, In)};
 	{State, queue_data(Stream, IsFin, Data, In)};
-send_data(State=#state{socket=Socket, transport=Transport, local_window=ConnWindow},
+send_data(State=#state{socket=Socket, transport=Transport, opts=Opts,
+		remote_settings=RemoteSettings, local_window=ConnWindow},
 		Stream=#stream{id=StreamID, local_window=StreamWindow}, IsFin, Data, In) ->
 		Stream=#stream{id=StreamID, local_window=StreamWindow}, IsFin, Data, In) ->
-	MaxFrameSize = 16384, %% @todo Use the real SETTINGS_MAX_FRAME_SIZE set by the client.
-	MaxSendSize = min(min(ConnWindow, StreamWindow), MaxFrameSize),
+	RemoteMaxFrameSize = maps:get(max_frame_size, RemoteSettings, 16384),
+	ConfiguredMaxFrameSize = maps:get(max_frame_size_sent, Opts, infinity),
+	MaxSendSize = min(
+		min(ConnWindow, StreamWindow),
+		min(RemoteMaxFrameSize, ConfiguredMaxFrameSize)
+	),
 	case Data of
 	case Data of
 		{sendfile, Offset, Bytes, Path} when Bytes =< MaxSendSize ->
 		{sendfile, Offset, Bytes, Path} when Bytes =< MaxSendSize ->
 			Transport:send(Socket, cow_http2:data_header(StreamID, IsFin, Bytes)),
 			Transport:send(Socket, cow_http2:data_header(StreamID, IsFin, Bytes)),

+ 38 - 2
test/http2_SUITE.erl

@@ -28,15 +28,19 @@ groups() -> [{clear, [parallel], ct_helper:all(?MODULE)}].
 init_routes(_) -> [
 init_routes(_) -> [
 	{"localhost", [
 	{"localhost", [
 		{"/", hello_h, []},
 		{"/", hello_h, []},
+		{"/echo/:key", echo_h, []},
 		{"/resp_iolist_body", resp_iolist_body_h, []}
 		{"/resp_iolist_body", resp_iolist_body_h, []}
 	]}
 	]}
 ].
 ].
 
 
-%% Do a prior knowledge handshake (function copied from rfc7540_SUITE).
+%% Do a prior knowledge handshake (function originally copied from rfc7540_SUITE).
 do_handshake(Config) ->
 do_handshake(Config) ->
+	do_handshake(#{}, Config).
+
+do_handshake(Settings, Config) ->
 	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
 	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
 	%% Send a valid preface.
 	%% Send a valid preface.
-	ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
+	ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(Settings)]),
 	%% Receive the server preface.
 	%% Receive the server preface.
 	{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
 	{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
 	{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
 	{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
@@ -81,6 +85,38 @@ initial_connection_window_size(Config) ->
 	ConfiguredSize = Size + 65535,
 	ConfiguredSize = Size + 65535,
 	ok.
 	ok.
 
 
+max_frame_size_sent(Config) ->
+	doc("Confirm that frames sent by Cowboy are limited in size "
+		"by the max_frame_size_sent configuration value."),
+	MaxFrameSize = 20000,
+	ProtoOpts = #{
+		env => #{dispatch => cowboy_router:compile(init_routes(Config))},
+		max_frame_size_sent => MaxFrameSize
+	},
+	{ok, _} = cowboy:start_clear(name(), [{port, 0}], ProtoOpts),
+	Port = ranch:get_port(name()),
+	{ok, Socket} = do_handshake(#{max_frame_size => MaxFrameSize + 10000}, [{port, Port}|Config]),
+	%% Send a request with a 30000 bytes body.
+	{HeadersBlock, _} = cow_hpack:encode([
+		{<<":method">>, <<"POST">>},
+		{<<":scheme">>, <<"http">>},
+		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+		{<<":path">>, <<"/echo/read_body">>}
+	]),
+	ok = gen_tcp:send(Socket, [
+		cow_http2:headers(1, nofin, HeadersBlock),
+		cow_http2:data(1, nofin, <<0:16384/unit:8>>),
+		cow_http2:data(1, fin, <<0:13616/unit:8>>)
+	]),
+	%% Receive a HEADERS frame as a response.
+	{ok, <<Len:24, 1:8, _:40>>} = gen_tcp:recv(Socket, 9, 6000),
+	{ok, _} = gen_tcp:recv(Socket, Len, 6000),
+	%% The DATA frames following must have lengths of 20000
+	%% and then 10000 due to the limit.
+	{ok, <<20000:24, 0:8, _:40, _:20000/unit:8>>} = gen_tcp:recv(Socket, 20009, 6000),
+	{ok, <<10000:24, 0:8, _:40, _:10000/unit:8>>} = gen_tcp:recv(Socket, 10009, 6000),
+	ok.
+
 preface_timeout_infinity(Config) ->
 preface_timeout_infinity(Config) ->
 	doc("Ensure infinity for preface_timeout is accepted."),
 	doc("Ensure infinity for preface_timeout is accepted."),
 	ProtoOpts = #{
 	ProtoOpts = #{

+ 105 - 64
test/rfc7540_SUITE.erl

@@ -1277,65 +1277,63 @@ max_frame_size_reject_larger_than_default(Config) ->
 	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
 	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
 	ok.
 	ok.
 
 
-%% @todo We need configurable SETTINGS in Cowboy for these tests.
-%%	max_frame_size_config_reject_too_small(Config) ->
-%%		doc("SETTINGS_MAX_FRAME_SIZE configuration values smaller than "
-%%			"16384 must be rejected. (RFC7540 6.5.2)"),
-%%		%% @todo This requires us to have a configurable SETTINGS in Cowboy.
-%%		todo.
-%%
-%%	max_frame_size_config_reject_too_large(Config) ->
-%%		doc("SETTINGS_MAX_FRAME_SIZE configuration values larger than "
-%%			"16777215 must be rejected. (RFC7540 6.5.2)"),
-%%		%% @todo This requires us to have a configurable SETTINGS in Cowboy.
-%%		todo.
-%%
-%%	max_frame_size_allow_exactly_custom(Config) ->
-%%		doc("An endpoint that sets SETTINGS_MAX_FRAME_SIZE must allow frames "
-%%			"of up to that size. (RFC7540 4.2, RFC7540 6.5.2)"),
-%%		%% @todo This requires us to have a configurable SETTINGS in Cowboy.
-%%		todo.
-%%
-%%	max_frame_size_reject_larger_than_custom(Config) ->
-%%		doc("An endpoint that sets SETTINGS_MAX_FRAME_SIZE must reject frames "
-%%			"of up to that size with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.5.2)"),
-%%		%% @todo This requires us to have a configurable SETTINGS in Cowboy.
-%%		todo.
-
-%% @todo How do I test this?
-%%
-%%	max_frame_size_client_default_respect_limits(Config) ->
-%%		doc("The server must not send frame sizes of more "
-%%			"than 16384 by default. (RFC7540 4.1, RFC7540 4.2)"),
-
-%% This is about the client sending a SETTINGS frame.
-max_frame_size_client_override_reject_too_small(Config) ->
-	doc("A SETTINGS_MAX_FRAME_SIZE smaller than 16384 must be rejected "
-		"with a PROTOCOL_ERROR connection error. (RFC7540 6.5.2)"),
+max_frame_size_allow_exactly_custom(Config0) ->
+	doc("An endpoint that sets SETTINGS_MAX_FRAME_SIZE must allow frames "
+		"of up to that size. (RFC7540 4.2, RFC7540 6.5.2)"),
+	%% Create a new listener that sets the maximum frame size to 30000.
+	Config = cowboy_test:init_http(name(), #{
+		env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
+		max_frame_size_received => 30000
+	}, Config0),
+	%% Do the handshake.
 	{ok, Socket} = do_handshake(Config),
 	{ok, Socket} = do_handshake(Config),
-	%% Send a SETTINGS frame with a SETTINGS_MAX_FRAME_SIZE lower than 16384.
-	ok = gen_tcp:send(Socket, << 6:24, 4:8, 0:40, 5:16, 16383:32 >>),
-	%% Receive a PROTOCOL_ERROR connection error.
-	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	%% Send a HEADERS frame initiating a stream followed by
+	%% a single 30000 bytes DATA frame.
+	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, fin, <<0:30000/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 a 25000 bytes frame.
+	{error, timeout} = gen_tcp:recv(Socket, 0, 1000),
 	ok.
 	ok.
 
 
-%% This is about the client sending a SETTINGS frame.
-max_frame_size_client_override_reject_too_large(Config) ->
-	doc("A SETTINGS_MAX_FRAME_SIZE larger than 16777215 must be rejected "
-		"with a PROTOCOL_ERROR connection error. (RFC7540 6.5.2)"),
+max_frame_size_reject_larger_than_custom(Config0) ->
+	doc("An endpoint that sets SETTINGS_MAX_FRAME_SIZE must reject frames "
+		"of up to that size with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.5.2)"),
+	%% Create a new listener that sets the maximum frame size to 30000.
+	Config = cowboy_test:init_http(name(), #{
+		env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
+		max_frame_size_received => 30000
+	}, Config0),
+	%% Do the handshake.
 	{ok, Socket} = do_handshake(Config),
 	{ok, Socket} = do_handshake(Config),
-	%% Send a SETTINGS frame with a SETTINGS_MAX_FRAME_SIZE larger than 16777215.
-	ok = gen_tcp:send(Socket, << 6:24, 4:8, 0:40, 5:16, 16777216:32 >>),
-	%% Receive a PROTOCOL_ERROR connection error.
-	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	%% Send a HEADERS frame initiating a stream followed by
+	%% a single DATA frame larger than 30000 bytes.
+	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, fin, <<0:30001/unit:8>>)
+	]),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
 	ok.
 	ok.
 
 
-%% @todo How do I test this?
-%%
-%%	max_frame_size_client_custom_respect_limits(Config) ->
-%%		doc("The server must not send frame sizes of more than "
-%%			"client's advertised limits. (RFC7540 4.1, RFC7540 4.2)"),
-
 %% I am using FRAME_SIZE_ERROR here because the information in the
 %% I am using FRAME_SIZE_ERROR here because the information in the
 %% frame header tells us this frame is at least 1 byte long, while
 %% frame header tells us this frame is at least 1 byte long, while
 %% the given length is smaller; i.e. it is too small to contain
 %% the given length is smaller; i.e. it is too small to contain
@@ -2422,7 +2420,7 @@ continuation_with_extension_frame_interleaved_error(Config) ->
 %   incomplete SETTINGS frame MUST be treated as a connection error
 %   incomplete SETTINGS frame MUST be treated as a connection error
 %   (Section 5.4.1) of type PROTOCOL_ERROR.
 %   (Section 5.4.1) of type PROTOCOL_ERROR.
 
 
-%% (RFC7540 6.5.2)
+%% Settings.
 
 
 settings_header_table_size_client(Config) ->
 settings_header_table_size_client(Config) ->
 	doc("The SETTINGS_HEADER_TABLE_SIZE setting can be used to "
 	doc("The SETTINGS_HEADER_TABLE_SIZE setting can be used to "
@@ -2581,7 +2579,7 @@ settings_max_concurrent_streams_0(Config0) ->
 settings_initial_window_size(Config0) ->
 settings_initial_window_size(Config0) ->
 	doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to "
 	doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to "
 		"change the initial window size of streams. (RFC7540 6.5.2)"),
 		"change the initial window size of streams. (RFC7540 6.5.2)"),
-	%% Create a new listener that allows only a single concurrent stream.
+	%% Create a new listener that sets initial window sizes to 100000.
 	Config = cowboy_test:init_http(name(), #{
 	Config = cowboy_test:init_http(name(), #{
 		env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
 		env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
 		initial_connection_window_size => 100000,
 		initial_connection_window_size => 100000,
@@ -2630,7 +2628,7 @@ settings_initial_window_size_after_ack(Config0) ->
 	doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to "
 	doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to "
 		"change the initial window size of streams. It is applied "
 		"change the initial window size of streams. It is applied "
 		"to all existing streams upon receipt of the SETTINGS ack. (RFC7540 6.5.2)"),
 		"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.
+	%% Create a new listener that sets the initial stream window sizes to 0.
 	Config = cowboy_test:init_http(name(), #{
 	Config = cowboy_test:init_http(name(), #{
 		env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
 		env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
 		initial_stream_window_size => 0
 		initial_stream_window_size => 0
@@ -2670,7 +2668,7 @@ settings_initial_window_size_before_ack(Config0) ->
 	doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to "
 	doc("The SETTINGS_INITIAL_WINDOW_SIZE setting can be used to "
 		"change the initial window size of streams. It is only "
 		"change the initial window size of streams. It is only "
 		"applied upon receipt of the SETTINGS ack. (RFC7540 6.5.2)"),
 		"applied upon receipt of the SETTINGS ack. (RFC7540 6.5.2)"),
-	%% Create a new listener that allows only a single concurrent stream.
+	%% Create a new listener that sets the initial stream window sizes to 0.
 	Config = cowboy_test:init_http(name(), #{
 	Config = cowboy_test:init_http(name(), #{
 		env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
 		env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
 		initial_stream_window_size => 0
 		initial_stream_window_size => 0
@@ -2711,13 +2709,56 @@ settings_initial_window_size_before_ack(Config0) ->
 	{error, timeout} = gen_tcp:recv(Socket, 0, 1000),
 	{error, timeout} = gen_tcp:recv(Socket, 0, 1000),
 	ok.
 	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
-%      allowed frame size (2^24-1 or 16,777,215 octets), inclusive.
-%      Values outside this range MUST be treated as a connection error
-%      (Section 5.4.1) of type PROTOCOL_ERROR.
-%
+settings_max_frame_size(Config0) ->
+	doc("The SETTINGS_MAX_FRAME_SIZE setting can be used to "
+		"change the maximum frame size allowed. (RFC7540 6.5.2)"),
+	%% Create a new listener that sets the maximum frame size to 30000.
+	Config = cowboy_test:init_http(name(), #{
+		env => #{dispatch => cowboy_router:compile(init_routes(Config0))},
+		max_frame_size_received => 30000
+	}, Config0),
+	%% Do the handshake.
+	{ok, Socket} = do_handshake(Config),
+	%% Send a HEADERS frame initiating a stream followed by
+	%% a single 25000 bytes DATA frame.
+	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, fin, <<0:25000/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 a 25000 bytes frame.
+	{error, timeout} = gen_tcp:recv(Socket, 0, 1000),
+	ok.
+
+settings_max_frame_size_reject_too_small(Config) ->
+	doc("A SETTINGS_MAX_FRAME_SIZE smaller than 16384 must be rejected "
+		"with a PROTOCOL_ERROR connection error. (RFC7540 6.5.2)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a SETTINGS frame with a SETTINGS_MAX_FRAME_SIZE lower than 16384.
+	ok = gen_tcp:send(Socket, << 6:24, 4:8, 0:40, 5:16, 16383:32 >>),
+	%% Receive a PROTOCOL_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+settings_max_frame_size_reject_too_large(Config) ->
+	doc("A SETTINGS_MAX_FRAME_SIZE larger than 16777215 must be rejected "
+		"with a PROTOCOL_ERROR connection error. (RFC7540 6.5.2)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a SETTINGS frame with a SETTINGS_MAX_FRAME_SIZE larger than 16777215.
+	ok = gen_tcp:send(Socket, << 6:24, 4:8, 0:40, 5:16, 16777216:32 >>),
+	%% Receive a PROTOCOL_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
 %   SETTINGS_MAX_HEADER_LIST_SIZE (0x6):  This advisory setting informs a
 %   SETTINGS_MAX_HEADER_LIST_SIZE (0x6):  This advisory setting informs a
 %      peer of the maximum size of header list that the sender is
 %      peer of the maximum size of header list that the sender is
 %      prepared to accept, in octets.  The value is based on the
 %      prepared to accept, in octets.  The value is based on the