Просмотр исходного кода

Add many test cases covering RFC7540 4.2

These tests cover frame sizes. It's mostly edge cases for sure
(ie misbehaving clients and us having to reject them properly).
I had these almost ready for a long time, so I'm glad I can
push them out.

This requires updating Cowlib too (we currently track master).
Loïc Hoguin 8 лет назад
Родитель
Сommit
a3636adcac
2 измененных файлов с 447 добавлено и 12 удалено
  1. 25 7
      src/cowboy_http2.erl
  2. 422 5
      test/rfc7540_SUITE.erl

+ 25 - 7
src/cowboy_http2.erl

@@ -52,7 +52,14 @@
 	%% @todo Since the ack is required, we must timeout if we don't receive it.
 	%% @todo I haven't put as much thought as I should have on this,
 	%% the final settings handling will be very different.
-	local_settings = #{} :: map(),
+	local_settings = #{
+%		header_table_size => 4096,
+%		enable_push => false, %% We are the server. Push is never enabled.
+%		max_concurrent_streams => infinity,
+%		initial_window_size => 65535,
+		max_frame_size => 16384
+%		max_header_list_size => infinity
+	} :: map(),
 	%% @todo We need a TimerRef to do SETTINGS_TIMEOUT errors.
 	%% We need to be careful there. It's well possible that we send
 	%% two SETTINGS frames before we receive a SETTINGS ack.
@@ -60,8 +67,8 @@
 	remote_settings = #{} :: map(),
 
 	%% Stream identifiers.
+	client_streamid = 0 :: non_neg_integer(),
 	server_streamid = 2 :: pos_integer(),
-	%% @todo last known good streamid
 
 	%% Currently active HTTP/2 streams. Streams may be initiated either
 	%% by the client or by the server through PUSH_PROMISE frames.
@@ -212,8 +219,9 @@ parse(State=#state{socket=Socket, transport=Transport, parse_state={preface, seq
 			end
 	end;
 %% @todo Perhaps instead of just more we can have {more, Len} to avoid all the checks.
-parse(State=#state{parse_state=ParseState}, Data) ->
-	case cow_http2:parse(Data) of
+parse(State=#state{local_settings=#{max_frame_size := MaxFrameSize},
+		parse_state=ParseState}, Data) ->
+	case cow_http2:parse(Data, MaxFrameSize) of
 		{ok, Frame, Rest} ->
 			case ParseState of
 				normal ->
@@ -529,13 +537,22 @@ sendfile(Socket, Transport, StreamID, IsFin, Offset, Bytes, Path, Length) ->
 	end.
 
 -spec terminate(#state{}, _) -> no_return().
-terminate(#state{socket=Socket, transport=Transport,
+terminate(undefined, Reason) ->
+	exit({shutdown, Reason});
+terminate(#state{socket=Socket, transport=Transport, client_streamid=LastStreamID,
 		streams=Streams, children=Children}, Reason) ->
-	%% @todo Send GOAWAY frame; need to keep track of last good stream id; how?
+	%% @todo We might want to optionally send the Reason value
+	%% as debug data in the GOAWAY frame here. Perhaps more.
+	Transport:send(Socket, cow_http2:goaway(LastStreamID, terminate_reason(Reason), <<>>)),
 	terminate_all_streams(Streams, Reason, Children),
 	Transport:close(Socket),
 	exit({shutdown, Reason}).
 
+terminate_reason({connection_error, Reason, _}) -> Reason;
+terminate_reason({stop, _, _}) -> no_error;
+terminate_reason({socket_error, _, _}) -> internal_error;
+terminate_reason({internal_error, _, _}) -> internal_error.
+
 terminate_all_streams([], _, []) ->
 	ok;
 terminate_all_streams([#stream{id=StreamID, state=StreamState}|Tail], Reason, Children0) ->
@@ -603,7 +620,8 @@ stream_init(State0=#state{ref=Ref, socket=Socket, transport=Transport, peer=Peer
 stream_handler_init(State=#state{opts=Opts}, StreamID, IsFin, Req) ->
 	try cowboy_stream:init(StreamID, Req, Opts) of
 		{Commands, StreamState} ->
-			commands(State, #stream{id=StreamID, state=StreamState, remote=IsFin}, Commands)
+			commands(State#state{client_streamid=StreamID},
+				#stream{id=StreamID, state=StreamState, remote=IsFin}, Commands)
 	catch Class:Reason ->
 		error_logger:error_msg("Exception occurred in "
 			"cowboy_stream:init(~p, ~p, ~p) with reason ~p:~p.",

+ 422 - 5
test/rfc7540_SUITE.erl

@@ -44,7 +44,8 @@ end_per_group(Name, _) ->
 
 init_routes(_) -> [
 	{"localhost", [
-		{"/", hello_h, []}
+		{"/", hello_h, []},
+		{"/echo/:key", echo_h, []}
 	]}
 ].
 
@@ -773,12 +774,10 @@ prior_knowledge_client_preface_settings_ack_timeout(Config) ->
 	{ok, << _:24, 7:8, _:72, 4:32 >>} = gen_tcp:recv(Socket, 17, 6000),
 	ok.
 
-prior_knowledge(Config) ->
-	doc("Streams can be initiated after a successful HTTP/2 connection "
-		"with prior knowledge of server capabilities. (RFC7540 3.4)"),
+%% Do a prior knowledge handshake.
+do_handshake(Config) ->
 	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
 	%% Send a valid preface.
-	%% @todo Use non-empty SETTINGS here. Just because.
 	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),
@@ -787,6 +786,13 @@ prior_knowledge(Config) ->
 	ok = gen_tcp:send(Socket, cow_http2:settings_ack()),
 	%% Receive the SETTINGS ack.
 	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
+	{ok, Socket}.
+
+prior_knowledge(Config) ->
+	doc("Streams can be initiated after a successful HTTP/2 connection "
+		"with prior knowledge of server capabilities. (RFC7540 3.4)"),
+	%% @todo Use non-empty SETTINGS here. Just because.
+	{ok, Socket} = do_handshake(Config),
 	%% Wait until after the SETTINGS ack timeout was supposed to trigger.
 	receive after 6000 -> ok end,
 	%% Send a PING.
@@ -802,3 +808,414 @@ prior_knowledge(Config) ->
 %%   without waiting for the 101 response (3.2, 3.5)
 %% * Prior knowledge handshake fails (3.4)
 %% * ALPN selects HTTP/1.1 (3.3)
+
+%% Frame size.
+
+max_frame_size_allow_exactly_default(Config) ->
+	doc("All implementations must allow frame sizes of at least 16384. (RFC7540 4.1, RFC7540 4.2)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a POST request with a DATA frame of exactly 16384 bytes.
+	{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, fin, << 0:16384/unit:8 >>)
+	]),
+	%% Receive a response with the same DATA frame.
+	{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
+	{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
+	{ok, << 16384:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
+	{ok, << 0:16384/unit:8 >>} = gen_tcp:recv(Socket, 16384, 1000),
+	ok.
+
+max_frame_size_reject_larger_than_default(Config) ->
+	doc("A FRAME_SIZE_ERROR connection error must be sent when receiving "
+		"frames larger than the default 16384 length. (RFC7540 4.1, RFC7540 4.2)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a POST request with a DATA frame larger than 16384 bytes.
+	{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, fin, << 0:16385/unit:8 >>)
+	]),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	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)"),
+	{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.
+
+%% 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)"),
+	{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.
+
+%% @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
+%% 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
+%% mandatory frame data (the pad length).
+
+data_reject_frame_size_0_padded_flag(Config) ->
+	doc("DATA frames of size 0 with the PADDED flag set must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.1)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a POST request with an incorrect padded DATA frame size.
+	{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),
+		<< 0:24, 0:8, 0:4, 1:1, 0:2, 1:1, 0:1, 1:31 >>
+	]),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+%% This case on the other hand is noted specifically in the RFC
+%% as being a PROTOCOL_ERROR. It can be thought of as the Pad Length
+%% being incorrect, rather than the frame size.
+
+data_reject_frame_size_too_small_padded_flag(Config) ->
+	doc("DATA frames with Pad Length >= Length must be rejected "
+		"with a PROTOCOL_ERROR connection error. (RFC7540 6.1)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a POST request with an incorrect padded DATA frame size.
+	{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),
+		<< 10:24, 0:8, 0:4, 1:1, 0:2, 1:1, 0:1, 1:31, 10:8, 0:80  >>
+	]),
+	%% Receive a PROTOCOL_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+headers_reject_frame_size_0_padded_flag(Config) ->
+	doc("HEADERS frames of size 0 with the PADDED flag set must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.2)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a padded HEADERS frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 0:24, 1:8, 0:4, 1:1, 0:2, 1:1, 0:1, 1:31 >>),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+headers_reject_frame_size_too_small_padded_flag(Config) ->
+	doc("HEADERS frames with no priority flag and Pad Length >= Length "
+		"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 6.2)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a padded HEADERS frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 10:24, 1:8, 0:4, 1:1, 0:2, 1:1, 0:1, 1:31, 10:8, 0:80 >>),
+	%% Receive a PROTOCOL_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+headers_reject_frame_size_too_small_priority_flag(Config) ->
+	doc("HEADERS frames of size smaller than 5 with the PRIORITY flag set must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.2)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a HEADERS frame with priority set and an incorrect size.
+	ok = gen_tcp:send(Socket, << 4:24, 1:8,
+		0:2, 1:1, 0:4, 1:1, 0:1, 1:31, 0:1, 3:31, 0:8 >>),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+headers_reject_frame_size_5_padded_and_priority_flags(Config) ->
+	doc("HEADERS frames of size smaller than 6 with the PADDED "
+		"and PRIORITY flags set must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.2)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a padded HEADERS frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 5:24, 1:8,
+		0:2, 1:1, 0:1, 1:1, 0:2, 1:1, 0:1, 1:31, 0:8, 0:1, 3:31, 0:8 >>),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+headers_reject_frame_size_too_small_padded_and_priority_flags(Config) ->
+	doc("HEADERS frames of size smaller than Length+6 with the PADDED and PRIORITY flags set "
+		"must be rejected with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.2)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a padded HEADERS frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 15:24, 1:8,
+		0:2, 1:1, 0:1, 1:1, 0:2, 1:1, 0:1, 1:31, 10:8, 0:1, 3:31, 0:8, 0:80 >>),
+	%% Receive a PROTOCOL_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+priority_reject_frame_size_too_small(Config) ->
+	doc("PRIORITY frames of size smaller than 5 must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.3)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a PRIORITY frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 4:24, 2:8, 0:9, 1:31, 0:1, 3:31, 0:8 >>),
+	%% Receive a FRAME_SIZE_ERROR stream error.
+	{ok, << _:24, 3:8, _:40, 6:32 >>} = gen_tcp:recv(Socket, 13, 6000),
+	ok.
+
+priority_reject_frame_size_too_large(Config) ->
+	doc("PRIORITY frames of size larger than 5 must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.3)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a PRIORITY frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 6:24, 2:8, 0:9, 1:31, 0:1, 3:31, 0:16 >>),
+	%% Receive a FRAME_SIZE_ERROR stream error.
+	{ok, << _:24, 3:8, _:40, 6:32 >>} = gen_tcp:recv(Socket, 13, 6000),
+	ok.
+
+rst_stream_reject_frame_size_too_small(Config) ->
+	doc("RST_STREAM frames of size smaller than 4 must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.4)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a request and reset it immediately.
+	{HeadersBlock, _} = cow_hpack:encode([
+		{<<":method">>, <<"GET">>},
+		{<<":scheme">>, <<"http">>},
+		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+		{<<":path">>, <<"/">>}
+	]),
+	ok = gen_tcp:send(Socket, [
+		cow_http2:headers(1, fin, HeadersBlock),
+		<< 3:24, 3:8, 0:9, 1:31, 8:32 >>
+	]),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+rst_stream_reject_frame_size_too_large(Config) ->
+	doc("RST_STREAM frames of size larger than 4 must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.4)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a request and reset it immediately.
+	{HeadersBlock, _} = cow_hpack:encode([
+		{<<":method">>, <<"GET">>},
+		{<<":scheme">>, <<"http">>},
+		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+		{<<":path">>, <<"/">>}
+	]),
+	ok = gen_tcp:send(Socket, [
+		cow_http2:headers(1, fin, HeadersBlock),
+		<< 5:24, 3:8, 0:9, 1:31, 8:32 >>
+	]),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+settings_reject_bad_frame_size(Config) ->
+	doc("SETTINGS frames must have a size multiple of 6 or be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.5)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a SETTINGS frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 5:24, 4:8, 0:40, 1:16, 4096:32 >>),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+settings_ack_reject_non_empty_frame_size(Config) ->
+	doc("SETTINGS frames with the ACK flag set and a non-empty payload "
+		"must be rejected with a FRAME_SIZE_ERROR connection error (RFC7540 4.2, RFC7540 6.5)"),
+	{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, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
+	{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
+	%% Send a SETTINGS ack with a payload.
+	ok = gen_tcp:send(Socket, << 6:24, 4:8, 0:7, 1:1, 0:32, 1:16, 4096:32 >>),
+	%% Receive the SETTINGS ack.
+	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+%% Note that clients are not supposed to send PUSH_PROMISE frames.
+%% However when they do, we need to be able to parse it in order
+%% to reject it, and so these errors may still occur.
+
+push_promise_reject_frame_size_too_small(Config) ->
+	doc("PUSH_PROMISE frames of size smaller than 4 must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.6)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a PUSH_PROMISE frame with an incorrect size.
+	{HeadersBlock, _} = cow_hpack:encode([
+		{<<":method">>, <<"GET">>},
+		{<<":scheme">>, <<"http">>},
+		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+		{<<":path">>, <<"/">>}
+	]),
+	ok = gen_tcp:send(Socket, [
+		<< 3:24, 5:8, 0:5, 1:1, 0:3, 1:31, 0:1, 3:31 >>,
+		HeadersBlock
+	]),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+push_promise_reject_frame_size_4_padded_flag(Config) ->
+	doc("PUSH_PROMISE frames of size smaller than 5 with the PADDED flag set must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.6)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a PUSH_PROMISE frame with an incorrect size.
+	{HeadersBlock, _} = cow_hpack:encode([
+		{<<":method">>, <<"GET">>},
+		{<<":scheme">>, <<"http">>},
+		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+		{<<":path">>, <<"/">>}
+	]),
+	ok = gen_tcp:send(Socket, [
+		<< 4:24, 5:8, 0:4, 1:1, 1:1, 0:3, 1:31, 0:1, 0:8, 3:31 >>,
+		HeadersBlock
+	]),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+push_promise_reject_frame_size_too_small_padded_flag(Config) ->
+	doc("PUSH_PROMISE frames of size smaller than Length+5 with the PADDED flag set "
+		"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 4.2, RFC7540 6.6)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a PUSH_PROMISE frame with an incorrect size.
+	{HeadersBlock, _} = cow_hpack:encode([
+		{<<":method">>, <<"GET">>},
+		{<<":scheme">>, <<"http">>},
+		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
+		{<<":path">>, <<"/">>}
+	]),
+	ok = gen_tcp:send(Socket, [
+		<< 14:24, 5:8, 0:4, 1:1, 1:1, 0:3, 1:31, 10:8, 0:1, 3:31 >>,
+		HeadersBlock,
+		<< 0:80 >>
+	]),
+	%% Receive a PROTOCOL_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+ping_reject_frame_size_too_small(Config) ->
+	doc("PING frames of size smaller than 8 must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.7)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a PING frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 7:24, 6:8, 0:40, 0:56 >>),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+ping_reject_frame_size_too_large(Config) ->
+	doc("PING frames of size larger than 8 must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.7)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a PING frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 9:24, 6:8, 0:40, 0:72 >>),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+goaway_reject_frame_size_too_small(Config) ->
+	doc("GOAWAY frames of size smaller than 8 must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.8)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a GOAWAY frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 7:24, 7:8, 0:40, 0:56 >>),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+goaway_allow_frame_size_too_large(Config) ->
+	doc("GOAWAY frames of size larger than 8 must be allowed. (RFC7540 6.8)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a GOAWAY frame with debug data.
+	ok = gen_tcp:send(Socket, << 12:24, 7:8, 0:40, 0:64, 99999:32 >>),
+	%% Receive a GOAWAY frame back.
+	{ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+window_update_reject_frame_size_too_small(Config) ->
+	doc("WINDOW_UPDATE frames of size smaller than 4 must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.9)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a WINDOW_UPDATE frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 3:24, 8:8, 0:40, 1000:24 >>),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+window_update_reject_frame_size_too_large(Config) ->
+	doc("WINDOW_UPDATE frames of size larger than 4 must be rejected "
+		"with a FRAME_SIZE_ERROR connection error. (RFC7540 4.2, RFC7540 6.9)"),
+	{ok, Socket} = do_handshake(Config),
+	%% Send a WINDOW_UPDATE frame with an incorrect size.
+	ok = gen_tcp:send(Socket, << 5:24, 8:8, 0:40, 1000:40 >>),
+	%% Receive a FRAME_SIZE_ERROR connection error.
+	{ok, << _:24, 7:8, _:72, 6:32 >>} = gen_tcp:recv(Socket, 17, 6000),
+	ok.
+
+%% Note: There is no particular limits on the size of CONTINUATION frames,
+%% they can go from 0 to SETTINGS_MAX_FRAME_SIZE.