aboutsummaryrefslogblamecommitdiffstats
path: root/test/rfc7540_SUITE.erl
blob: a024d64ce43b2c425f74f19dc623f2f89c6ad754 (plain) (tree)
1
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
                                                             












































                                                                                                                  

                                          
























                                                                                                     
                                  












                                                                                                     
                                  




                                                                                           
                                                                   






                                                                                                     
                                  

                                                                                           
                                                                  

           
                                                         






                                                                                                     
                                  
                         
                                                                  









                                                                                                     
                                  













                                                                                                     
                                  





                                                                                   









                                                      







                                                                                                     
                                  

                                                                                           
                                 









                                                                                                             
                                  

                                                                                           
                                 











                                                                                                     
                                  

                                                                                           
                                 


                                                                                  




                                                                                      
                                                                         
                                                        









                                                                                                     
                                  

                                                                                           
                                 





                                                                                       


















                                                                                  








                                                                                                     
                                  

                                                                                           
                                 





                                                                                       


















                                                                                  








                                                                                                     
                                  

                                                                                           
                                 





                                                                                               


















                                                                                  








                                                                                                     
                                  

                                                                                           
                                 





                                                                                                    


















                                                                                  







                                                                                                     
                                  

                                                                                           
                                 















                                                                                                         
                                  

                                                                                           
                                 











                                                                                                 



























                                                                        
 






                                                                      
 








                                                                                                     
                                  

                                                                                           
                                 







                                                                                                 
                                                                                     
















                                                                              








                                                                                                     
                                  

                                                                                           
                                 





                                                                     


                                                                                  
























                                                                                                 




























































































































































































                                                                                                     
                                                        











                                                                                                     
                                                        











                                                                                                     
                                                        



























                                                                                                         

                                  

                                                                                                     







                                                                                                 






                                                                              







                                                                               






                                                                     
 
























                                                                                   




































































































































































































































































































































































































                                                                                                       







































































































































































































































































































                                                                                                                     
                                                                                   








                                                                                                   
                                                                                             














                                                                                                       
                                             
                                   
                                                                             



                                                     


                                                                           































































                                                                                        


















































                                                                                                    




                                                                      
                                                                                      






































































































                                                                                          
                                                                                 














                                                                                        

                                                                

                                                                                        
                                                                                     










                                                                                  

                                                                           


























































                                                                                                     



                                                                       

                                                                                      
                                                                                     












                                                                                        

                                                                           

           

                                                                

                                                                                         
                                                                                     












                                                                                  

                                                                           





































































                                                                                     
                                                                                     




















































                                                                                                    
                                                                           





















                                                                                  
                                                                           



























                                                                                  
                                                                           






















































































































                                                                                                     






                                                                 

                                                                                   
                                                                                           















                                                                                  

                                                                           


                                                                      
                                                              






                                                                         


















































































































































































































































































































































































                                                                                     
                                                                                         
                                   
                                                    



















                                                                                  
                                                       



                                                                                
















































































                                                                                             
%% Copyright (c) 2016-2017, Loïc Hoguin <[email protected]>
%%
%% Permission to use, copy, modify, and/or distribute this software for any
%% purpose with or without fee is hereby granted, provided that the above
%% copyright notice and this permission notice appear in all copies.
%%
%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

-module(rfc7540_SUITE).
-compile(export_all).

-import(ct_helper, [config/2]).
-import(ct_helper, [doc/1]).
-import(cowboy_test, [raw_open/1]).
-import(cowboy_test, [raw_send/2]).
-import(cowboy_test, [raw_recv_head/1]).
-import(cowboy_test, [raw_recv/3]).

all() -> [{group, clear}, {group, tls}].

groups() ->
	Modules = ct_helper:all(?MODULE),
	Clear = [M || M <- Modules, lists:sublist(atom_to_list(M), 4) =/= "alpn"] -- [prior_knowledge_reject_tls],
	TLS = [M || M <- Modules, lists:sublist(atom_to_list(M), 4) =:= "alpn"] ++ [prior_knowledge_reject_tls],
	[{clear, [parallel], Clear}, {tls, [parallel], TLS}].

init_per_group(Name = clear, Config) ->
	cowboy_test:init_http(Name = clear, #{
		env => #{dispatch => cowboy_router:compile(init_routes(Config))}
	}, Config);
init_per_group(Name = tls, Config) ->
	cowboy_test:init_http2(Name = tls, #{
		env => #{dispatch => cowboy_router:compile(init_routes(Config))}
	}, Config).

end_per_group(Name, _) ->
	ok = cowboy:stop_listener(Name).

init_routes(_) -> [
	{"localhost", [
		{"/", hello_h, []},
		{"/echo/:key", echo_h, []}
	]}
].

%% Starting HTTP/2 for "http" URIs.

http_upgrade_ignore_h2(Config) ->
	doc("An h2 token in an Upgrade field must be ignored. (RFC7540 3.2)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
	ok.

http_upgrade_ignore_if_http_10(Config) ->
	doc("The Upgrade header must be ignored if part of an HTTP/1.0 request. (RFC7230 6.7)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.0\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
	ok.

http_upgrade_ignore_missing_upgrade_in_connection(Config) ->
	doc("The Upgrade header must be listed in the "
		"Connection header field. (RFC7230 6.7)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
	ok.

http_upgrade_ignore_missing_http2_settings_in_connection(Config) ->
	doc("The HTTP2-Settings header must be listed in the "
		"Connection header field. (RFC7540 3.2.1, RFC7230 6.7)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
	ok.

http_upgrade_ignore_zero_http2_settings_header(Config) ->
	doc("The HTTP Upgrade request must include "
		"exactly one HTTP2-Settings header field (RFC7540 3.2, RFC7540 3.2.1)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"\r\n"]),
	{ok, <<"HTTP/1.1 200">>} = gen_tcp:recv(Socket, 12, 1000),
	ok.

http_upgrade_reject_two_http2_settings_header(Config) ->
	doc("The HTTP Upgrade request must include "
		"exactly one HTTP2-Settings header field (RFC7540 3.2, RFC7540 3.2.1)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	{ok, <<"HTTP/1.1 400">>} = gen_tcp:recv(Socket, 12, 1000),
	ok.

http_upgrade_reject_bad_http2_settings_header(Config) ->
	doc("The HTTP Upgrade request must include "
		"a valid HTTP2-Settings header field (RFC7540 3.2, RFC7540 3.2.1)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		%% We send a full SETTINGS frame on purpose.
		"HTTP2-Settings: ", base64:encode(cow_http2:settings(#{})), "\r\n",
		"\r\n"]),
	{ok, <<"HTTP/1.1 400">>} = gen_tcp:recv(Socket, 12, 1000),
	ok.

%% Match directly for now.
do_recv_101(Socket) ->
	{ok, <<
		"HTTP/1.1 101 Switching Protocols\r\n"
		"connection: Upgrade\r\n"
		"upgrade: h2c\r\n"
		"\r\n"
	>>} = gen_tcp:recv(Socket, 71, 1000),
	ok.

http_upgrade_101(Config) ->
	doc("A 101 response must be sent on successful upgrade "
		"to HTTP/2 when using the HTTP Upgrade mechanism. (RFC7540 3.2, RFC7230 6.7)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	ok.

http_upgrade_server_preface(Config) ->
	doc("The first frame after the upgrade must be a "
		"SETTINGS frame for the server connection preface. (RFC7540 3.2, RFC7540 3.5, RFC7540 6.5)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% Receive the server preface.
	{ok, << _:24, 4:8, 0:40 >>} = gen_tcp:recv(Socket, 9, 1000),
	ok.

http_upgrade_client_preface_timeout(Config) ->
	doc("Clients negotiating HTTP/2 and not sending a preface in "
		"a timely manner must be disconnected."),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% 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 the response to the initial HTTP/1.1 request.
	{ok, << HeadersSkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, _} = gen_tcp:recv(Socket, HeadersSkipLen, 1000),
	{ok, << DataSkipLen:24, 0:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, _} = gen_tcp:recv(Socket, DataSkipLen, 1000),
	%% Do not send the preface. Wait for the server to disconnect us.
	{error, closed} = gen_tcp:recv(Socket, 9, 6000),
	ok.

http_upgrade_reject_missing_client_preface(Config) ->
	doc("Servers must treat an invalid connection preface as a "
		"connection error of type PROTOCOL_ERROR. (RFC7540 3.2, RFC7540 3.5)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% Send a SETTINGS frame directly instead of the proper preface.
	ok = gen_tcp:send(Socket, 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),
	%% We expect the server to close the connection when it receives a bad preface.
	%% The server may however have already started sending the response to the
	%% initial HTTP/1.1 request.
	Received = lists:reverse(lists:foldl(fun(_, Acc) ->
		case gen_tcp:recv(Socket, 9, 1000) of
			{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[headers|Acc];
			{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[data|Acc];
			{error, _} ->
				[closed|Acc]
		end
	end, [], [1, 2, 3])),
	case Received of
		[closed|_] -> ok;
		[headers, closed|_] -> ok;
		[headers, data, closed] -> ok
	end.

http_upgrade_reject_invalid_client_preface(Config) ->
	doc("Servers must treat an invalid connection preface as a "
		"connection error of type PROTOCOL_ERROR. (RFC7540 3.2, RFC7540 3.5)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% Send a slightly incorrect preface.
	ok = gen_tcp:send(Socket, "PRI * HTTP/2.0\r\n\r\nSM: Value\r\n\r\n"),
	%% 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),
	%% We expect the server to close the connection when it receives a bad preface.
	%% The server may however have already started sending the response to the
	%% initial HTTP/1.1 request.
	Received = lists:reverse(lists:foldl(fun(_, Acc) ->
		case gen_tcp:recv(Socket, 9, 1000) of
			{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[headers|Acc];
			{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[data|Acc];
			{error, _} ->
				[closed|Acc]
		end
	end, [], [1, 2, 3])),
	case Received of
		[closed|_] -> ok;
		[headers, closed|_] -> ok;
		[headers, data, closed] -> ok
	end.

http_upgrade_reject_missing_client_preface_settings(Config) ->
	doc("Servers must treat an invalid connection preface as a "
		"connection error of type PROTOCOL_ERROR. (RFC7540 3.2, RFC7540 3.5)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% Send a valid preface sequence except followed by a PING instead of a SETTINGS frame.
	ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:ping(0)]),
	%% 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),
	%% We expect the server to close the connection when it receives a bad preface.
	%% The server may however have already started sending the response to the
	%% initial HTTP/1.1 request.
	Received = lists:reverse(lists:foldl(fun(_, Acc) ->
		case gen_tcp:recv(Socket, 9, 1000) of
			{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[headers|Acc];
			{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[data|Acc];
			{error, _} ->
				[closed|Acc]
		end
	end, [], [1, 2, 3])),
	case Received of
		[closed|_] -> ok;
		[headers, closed|_] -> ok;
		[headers, data, closed] -> ok
	end.

http_upgrade_reject_invalid_client_preface_settings(Config) ->
	doc("Servers must treat an invalid connection preface as a "
		"connection error of type PROTOCOL_ERROR. (RFC7540 3.2, RFC7540 3.5)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% Send a valid preface sequence except followed by a badly formed SETTINGS frame.
	ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", << 0:24, 4:8, 0:9, 1:31 >>]),
	%% 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),
	%% We expect the server to close the connection when it receives a bad preface.
	%% The server may however have already started sending the response to the
	%% initial HTTP/1.1 request.
	Received = lists:reverse(lists:foldl(fun(_, Acc) ->
		case gen_tcp:recv(Socket, 9, 1000) of
			{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[headers|Acc];
			{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[data|Acc];
			{error, _} ->
				[closed|Acc]
		end
	end, [], [1, 2, 3])),
	case Received of
		[closed|_] -> ok;
		[headers, closed|_] -> ok;
		[headers, data, closed] -> ok
	end.

http_upgrade_accept_client_preface_empty_settings(Config) ->
	doc("The SETTINGS frame in the client preface may be empty. (RFC7540 3.2, RFC7540 3.5)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% Send a valid preface sequence except followed by an empty SETTINGS frame.
	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 the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	ok.

http_upgrade_client_preface_settings_ack_timeout(Config) ->
	doc("The SETTINGS frames sent by the client must be acknowledged. (RFC7540 3.5, RFC7540 6.5.3)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% 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 the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	%% Do not ack the server preface. Expect a GOAWAY with reason SETTINGS_TIMEOUT.
	{ok, << _:24, 7:8, _:72, 4:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

%% @todo We need a successful test with actual options in HTTP2-Settings.
%% SETTINGS_MAX_FRAME_SIZE is probably the easiest to test. The relevant
%% RFC quote is:
%%
%% 3.2.1
%%   A server decodes and interprets these values as it would any other
%%   SETTINGS frame. Explicit acknowledgement of these settings
%%   (Section 6.5.3) is not necessary, since a 101 response serves as
%%   implicit acknowledgement.

%% @todo We need to test an upgrade with a request body. It is probably
%% worth having a configuration value for how much we accept while still
%% upgrading (if too big, we would just stay on HTTP/1.1). We therefore
%% needs a test for when the body is small enough, and one for when the
%% body is larger than we accept. The relevant RFC quote is:
%%
%% 3.2
%%   Requests that contain a payload body MUST be sent in their entirety
%%   before the client can send HTTP/2 frames.  This means that a large
%%   request can block the use of the connection until it is completely
%%   sent.

%% @todo We should definitely have a test with OPTIONS. The relevant
%% RFC quote is:
%%
%% 3.2
%%   If concurrency of an initial request with subsequent requests is
%%   important, an OPTIONS request can be used to perform the upgrade to
%%   HTTP/2, at the cost of an additional round trip.

%% @todo If we ever handle priority, we need to check that the initial
%% HTTP/1.1 request has default priority. The relevant RFC quote is:
%%
%% 3.2
%%   The HTTP/1.1 request that is sent prior to upgrade is assigned a
%%   stream identifier of 1 (see Section 5.1.1) with default priority
%%   values (Section 5.3.5).

http_upgrade_response(Config) ->
	doc("A response must be sent to the initial HTTP/1.1 request "
		"after switching to HTTP/2. The response must use "
		"the stream identifier 1. (RFC7540 3.2)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% 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),
	{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
	%% Send the SETTINGS ack.
	ok = gen_tcp:send(Socket, cow_http2:settings_ack()),
	%% Receive the SETTINGS ack, and the response HEADERS and DATA (Stream ID 1).
	Received = lists:reverse(lists:foldl(fun(_, Acc) ->
		case gen_tcp:recv(Socket, 9, 1000) of
			{ok, << 0:24, 4:8, 1:8, 0:32 >>} ->
				[settings_ack|Acc];
			{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[headers|Acc];
			{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[data|Acc]
		end
	end, [], [1, 2, 3])),
	case Received of
		[settings_ack, headers, data] -> ok;
		[headers, settings_ack, data] -> ok;
		[headers, data, settings_ack] -> ok
	end.

http_upgrade_response_half_closed(Config) ->
	doc("The stream for the initial HTTP/1.1 request is half-closed. (RFC7540 3.2)"),
	%% Try sending more data after the upgrade and get an error.
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% Send a valid preface followed by an unexpected DATA frame.
	ok = gen_tcp:send(Socket, [
		"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",
		cow_http2:settings(#{}),
		cow_http2:data(1, fin, <<"Unexpected DATA frame.">>)
	]),
	%% 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),
	%% Skip the SETTINGS ack, receive the response HEADERS, DATA and RST_STREAM (streamid 1).
	Received = lists:reverse(lists:foldl(fun(_, Acc) ->
		case gen_tcp:recv(Socket, 9, 1000) of
			{ok, << 0:24, 4:8, 1:8, 0:32 >>} ->
				Acc;
			{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[headers|Acc];
			{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[data|Acc];
			{ok, << 4:24, 3:8, 0:8, 1:32 >>} ->
				%% We expect a STREAM_CLOSED reason.
				{ok, << 5:32 >>} = gen_tcp:recv(Socket, 4, 1000),
				[rst_stream|Acc];
			{error, _} ->
				%% Can be timeouts, ignore them.
				Acc
		end
	end, [], [1, 2, 3, 4])),
	case Received of
		[rst_stream] -> ok;
		[headers, rst_stream] -> ok;
		[headers, data, rst_stream] -> ok
	end.

%% Starting HTTP/2 for "https" URIs.

alpn_ignore_h2c(Config) ->
	doc("An h2c ALPN protocol identifier must be ignored. (RFC7540 3.3)"),
	{ok, Socket} = ssl:connect("localhost", config(port, Config),
		[{alpn_advertised_protocols, [<<"h2c">>, <<"http/1.1">>]}, binary, {active, false}]),
	{ok, <<"http/1.1">>} = ssl:negotiated_protocol(Socket),
	ok.

alpn_server_preface(Config) ->
	doc("The first frame must be a SETTINGS frame "
		"for the server connection preface. (RFC7540 3.3, RFC7540 3.5, RFC7540 6.5)"),
	{ok, Socket} = ssl:connect("localhost", config(port, Config),
		[{alpn_advertised_protocols, [<<"h2">>]}, binary, {active, false}]),
	{ok, <<"h2">>} = ssl:negotiated_protocol(Socket),
	%% Receive the server preface.
	{ok, << _:24, 4:8, 0:40 >>} = ssl:recv(Socket, 9, 1000),
	ok.

alpn_client_preface_timeout(Config) ->
	doc("Clients negotiating HTTP/2 and not sending a preface in "
		"a timely manner must be disconnected."),
	{ok, Socket} = ssl:connect("localhost", config(port, Config),
		[{alpn_advertised_protocols, [<<"h2">>]}, binary, {active, false}]),
	{ok, <<"h2">>} = ssl:negotiated_protocol(Socket),
	%% Receive the server preface.
	{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),
	{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),
	%% Do not send the preface. Wait for the server to disconnect us.
	{error, closed} = ssl:recv(Socket, 3, 6000),
	ok.

alpn_reject_missing_client_preface(Config) ->
	doc("Servers must treat an invalid connection preface as a "
		"connection error of type PROTOCOL_ERROR. (RFC7540 3.3, RFC7540 3.5)"),
	{ok, Socket} = ssl:connect("localhost", config(port, Config),
		[{alpn_advertised_protocols, [<<"h2">>]}, binary, {active, false}]),
	{ok, <<"h2">>} = ssl:negotiated_protocol(Socket),
	%% Send a SETTINGS frame directly instead of the proper preface.
	ok = ssl:send(Socket, cow_http2:settings(#{})),
	%% Receive the server preface.
	{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),
	{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),
	%% We expect the server to close the connection when it receives a bad preface.
	{error, closed} = ssl:recv(Socket, 3, 1000),
	ok.

alpn_reject_invalid_client_preface(Config) ->
	doc("Servers must treat an invalid connection preface as a "
		"connection error of type PROTOCOL_ERROR. (RFC7540 3.3, RFC7540 3.5)"),
	{ok, Socket} = ssl:connect("localhost", config(port, Config),
		[{alpn_advertised_protocols, [<<"h2">>]}, binary, {active, false}]),
	{ok, <<"h2">>} = ssl:negotiated_protocol(Socket),
	%% Send a slightly incorrect preface.
	ok = ssl:send(Socket, "PRI * HTTP/2.0\r\n\r\nSM: Value\r\n\r\n"),
	%% Receive the server preface.
	{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),
	{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),
	%% We expect the server to close the connection when it receives a bad preface.
	{error, closed} = ssl:recv(Socket, 3, 1000),
	ok.

alpn_reject_missing_client_preface_settings(Config) ->
	doc("Servers must treat an invalid connection preface as a "
		"connection error of type PROTOCOL_ERROR. (RFC7540 3.3, RFC7540 3.5)"),
	{ok, Socket} = ssl:connect("localhost", config(port, Config),
		[{alpn_advertised_protocols, [<<"h2">>]}, binary, {active, false}]),
	{ok, <<"h2">>} = ssl:negotiated_protocol(Socket),
	%% Send a valid preface sequence except followed by a PING instead of a SETTINGS frame.
	ok = ssl:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:ping(0)]),
	%% Receive the server preface.
	{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),
	{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),
	%% We expect the server to close the connection when it receives a bad preface.
	{error, closed} = ssl:recv(Socket, 3, 1000),
	ok.

alpn_reject_invalid_client_preface_settings(Config) ->
	doc("Servers must treat an invalid connection preface as a "
		"connection error of type PROTOCOL_ERROR. (RFC7540 3.3, RFC7540 3.5)"),
	{ok, Socket} = ssl:connect("localhost", config(port, Config),
		[{alpn_advertised_protocols, [<<"h2">>]}, binary, {active, false}]),
	{ok, <<"h2">>} = ssl:negotiated_protocol(Socket),
	%% Send a valid preface sequence except followed by a badly formed SETTINGS frame.
	ok = ssl:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", << 0:24, 4:8, 0:9, 1:31 >>]),
	%% Receive the server preface.
	{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),
	{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),
	%% We expect the server to close the connection when it receives a bad preface.
	{error, closed} = ssl:recv(Socket, 3, 1000),
	ok.

alpn_accept_client_preface_empty_settings(Config) ->
	doc("The SETTINGS frame in the client preface may be empty. (RFC7540 3.3, RFC7540 3.5)"),
	{ok, Socket} = ssl:connect("localhost", config(port, Config),
		[{alpn_advertised_protocols, [<<"h2">>]}, binary, {active, false}]),
	{ok, <<"h2">>} = ssl:negotiated_protocol(Socket),
	%% Send a valid preface sequence except followed by an empty SETTINGS frame.
	ok = ssl:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
	%% Receive the server preface.
	{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),
	{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),
	%% Receive the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = ssl:recv(Socket, 9, 1000),
	ok.

alpn_client_preface_settings_ack_timeout(Config) ->
	doc("Failure to acknowledge the server's SETTINGS frame "
		"results in a SETTINGS_TIMEOUT connection error. (RFC7540 3.5, RFC7540 6.5.3)"),
	{ok, Socket} = ssl:connect("localhost", config(port, Config),
		[{alpn_advertised_protocols, [<<"h2">>]}, binary, {active, false}]),
	{ok, <<"h2">>} = ssl:negotiated_protocol(Socket),
	%% Send a valid preface.
	ok = ssl:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
	%% Receive the server preface.
	{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),
	{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),
	%% Receive the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = ssl:recv(Socket, 9, 1000),
	%% Do not ack the server preface. Expect a GOAWAY with reason SETTINGS_TIMEOUT.
	{ok, << _:24, 7:8, _:72, 4:32 >>} = ssl:recv(Socket, 17, 6000),
	ok.

alpn(Config) ->
	doc("Successful ALPN negotiation. (RFC7540 3.3)"),
	{ok, Socket} = ssl:connect("localhost", config(port, Config),
		[{alpn_advertised_protocols, [<<"h2">>]}, binary, {active, false}]),
	{ok, <<"h2">>} = ssl:negotiated_protocol(Socket),
	%% Send a valid preface.
	%% @todo Use non-empty SETTINGS here. Just because.
	ok = ssl:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
	%% Receive the server preface.
	{ok, << Len:24 >>} = ssl:recv(Socket, 3, 1000),
	{ok, << 4:8, 0:40, _:Len/binary >>} = ssl:recv(Socket, 6 + Len, 1000),
	%% Send the SETTINGS ack.
	ok = ssl:send(Socket, cow_http2:settings_ack()),
	%% Receive the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = ssl:recv(Socket, 9, 1000),
	%% Wait until after the SETTINGS ack timeout was supposed to trigger.
	receive after 6000 -> ok end,
	%% Send a PING.
	ok = ssl:send(Socket, cow_http2:ping(0)),
	%% Receive a PING ack back, indicating the connection is still up.
	{ok, << 8:24, 6:8, 0:7, 1:1, 0:96 >>} = ssl:recv(Socket, 17, 1000),
	ok.

%% Starting HTTP/2 with prior knowledge.

prior_knowledge_reject_tls(Config) ->
	doc("Implementations that support HTTP/2 over TLS must use ALPN. (RFC7540 3.4)"),
	{ok, Socket} = ssl:connect("localhost", config(port, Config), [binary, {active, false}]),
	%% Send a valid preface.
	ok = ssl:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:settings(#{})]),
	%% We expect the server to send an HTTP 400 error
	%% when trying to use HTTP/2 without going through ALPN negotiation.
	{ok, <<"HTTP/1.1 400">>} = ssl:recv(Socket, 12, 1000),
	ok.

prior_knowledge_server_preface(Config) ->
	doc("The first frame must be a SETTINGS frame "
		"for the server connection preface. (RFC7540 3.4, RFC7540 3.5, 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, << _:24, 4:8, 0:40 >>} = gen_tcp:recv(Socket, 9, 1000),
	ok.

%% Note: the client preface timeout doesn't apply in this case,
%% so we don't test it. An HTTP/1.1 client that does not send
%% a request in a timely manner will get disconnected by the
%% HTTP protocol code, not by HTTP/2's.

%% Note: the test that starts by sending a SETTINGS frame is
%% redundant with tests sending garbage on the connection.
%% From the point of view of an HTTP/1.1 connection, a
%% SETTINGS frame is indistinguishable from garbage.

prior_knowledge_reject_invalid_client_preface(Config) ->
	doc("An incorrect preface is an invalid HTTP/1.1 request. (RFC7540 3.4)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	%% Send a slightly incorrect preface.
	ok = gen_tcp:send(Socket, "PRI * HTTP/2.0\r\n\r\nSM: Value\r\n\r\n"),
	%% We propagate to HTTP/2 after checking only the request-line.
	%% The server then sends its preface before checking the full client preface.
	{ok, << Len:24 >>} = gen_tcp:recv(Socket, 3, 1000),
	{ok, << 4:8, 0:40, _:Len/binary >>} = gen_tcp:recv(Socket, 6 + Len, 1000),
	%% We expect the server to close the connection when it receives a bad preface.
	{error, closed} = gen_tcp:recv(Socket, 9, 1000),
	ok.

prior_knowledge_reject_missing_client_preface_settings(Config) ->
	doc("Servers must treat an invalid connection preface as a "
		"connection error of type PROTOCOL_ERROR. (RFC7540 3.4, RFC7540 3.5)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	%% Send a valid preface sequence except followed by a PING instead of a SETTINGS frame.
	ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", cow_http2:ping(0)]),
	%% 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),
	%% We expect the server to close the connection when it receives a bad preface.
	{error, closed} = gen_tcp:recv(Socket, 9, 1000),
	ok.

prior_knowledge_reject_invalid_client_preface_settings(Config) ->
	doc("Servers must treat an invalid connection preface as a "
		"connection error of type PROTOCOL_ERROR. (RFC7540 3.4, RFC7540 3.5)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	%% Send a valid preface sequence except followed by a badly formed SETTINGS frame.
	ok = gen_tcp:send(Socket, ["PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", << 0:24, 4:8, 0:9, 1:31 >>]),
	%% 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),
	%% We expect the server to close the connection when it receives a bad preface.
	{error, closed} = gen_tcp:recv(Socket, 9, 1000),
	ok.

prior_knowledge_accept_client_preface_empty_settings(Config) ->
	doc("The SETTINGS frame in the client preface may be empty. (RFC7540 3.4, RFC7540 3.5)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	%% Send a valid preface sequence except followed by an empty SETTINGS frame.
	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 the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	ok.

prior_knowledge_client_preface_settings_ack_timeout(Config) ->
	doc("The SETTINGS frames sent by the client must be acknowledged. (RFC7540 3.5, RFC7540 6.5.3)"),
	{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),
	%% Receive the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	%% Do not ack the server preface. Expect a GOAWAY with reason SETTINGS_TIMEOUT.
	{ok, << _:24, 7:8, _:72, 4:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

%% Do a prior knowledge handshake.
do_handshake(Config) ->
	{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 the SETTINGS ack.
	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.
	ok = gen_tcp:send(Socket, cow_http2:ping(0)),
	%% Receive a PING ack back, indicating the connection is still up.
	{ok, << 8:24, 6:8, 0:7, 1:1, 0:96 >>} = gen_tcp:recv(Socket, 17, 1000),
	ok.

%% @todo If we ever add an option to disable HTTP/2, we need to check
%% the following things:
%% * HTTP/1.1 Upgrade returns an HTTP/1.1 response (3.2)
%% * HTTP/1.1 Upgrade errors out if the client sends HTTP/2 frames
%%   without waiting for the 101 response (3.2, 3.5)
%% * Prior knowledge handshake fails (3.4)
%% * ALPN selects HTTP/1.1 (3.3)

%% Frame format.

ignore_unknown_frames(Config) ->
	doc("Frames of unknown type must be ignored and discarded. (RFC7540 4.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request with a single DATA frame,
	%% and an unknown frame type interleaved.
	{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, 99:8, 0:40, 0:80 >>,
		cow_http2:data(1, fin, << 0:100/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, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

ignore_data_unknown_flags(Config) ->
	doc("Undefined DATA frame flags must be ignored. (RFC7540 4.1, RFC7540 6.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request with a DATA frame with unknown flags.
	{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),
		<< 100:24, 0:8,
			1:1, 1:1, 1:1, 1:1, %% Undefined.
			0:1, %% PADDED.
			1:1, 1:1, %% Undefined.
			1:1, %% END_STREAM.
			0:1, 1:31, 0:100/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, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

ignore_headers_unknown_flags(Config) ->
	doc("Undefined HEADERS frame flags must be ignored. (RFC7540 4.1, RFC7540 6.2)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request with a HEADERS frame with unknown flags.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"POST">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/echo/read_body">>}
	]),
	Len = iolist_size(HeadersBlock),
	ok = gen_tcp:send(Socket, [
		<< Len:24, 1:8,
			1:1, 1:1, %% Undefined.
			0:1, %% PRIORITY.
			1:1, %% Undefined.
			0:1, %% PADDED.
			1:1, %% END_HEADERS.
			1:1, %% Undefined.
			0:1, %% END_STREAM.
			0:1, 1:31 >>,
		HeadersBlock,
		cow_http2:data(1, fin, << 0:100/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, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

ignore_priority_unknown_flags(Config) ->
	doc("Undefined PRIORITY frame flags must be ignored. (RFC7540 4.1, RFC7540 6.3)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request with an interleaved PRIORITY frame with unknown flags.
	{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),
		<< 5:24, 2:8,
			1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.
			0:1, 1:31, 0:1, 3:31, 0:8 >>,
		cow_http2:data(1, fin, << 0:100/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, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

ignore_rst_stream_unknown_flags(Config) ->
	doc("Undefined RST_STREAM frame flags must be ignored. (RFC7540 4.1, RFC7540 6.4)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request then cancel it with an RST_STREAM frame with unknown flags.
	{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),
		<< 4:24, 3:8,
			1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.
			0:1, 1:31, 8:32 >>,
		cow_http2:headers(3, nofin, HeadersBlock),
		cow_http2:data(3, fin, << 0:100/unit:8 >>)
	]),
	%% Receive a response with the same DATA frame.
	{ok, << SkipLen:24, 1:8, _:8, 3:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
	{ok, << 100:24, 0:8, 1:8, 3:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

ignore_settings_unknown_flags(Config) ->
	doc("Undefined SETTINGS frame flags must be ignored. (RFC7540 4.1, RFC7540 6.5)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a SETTINGS frame with unknown flags.
	ok = gen_tcp:send(Socket, << 6:24, 4:8,
		1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.
		0:1, %% ACK.
		0:32, 2:16, 0:32 >>),
	%% Receive a SETTINGS ack.
	{ok, << 0:24, 4:8, 0:7, 1:1, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	ok.

ignore_push_promise_unknown_flags(Config) ->
	doc("Undefined PUSH_PROMISE frame flags must be ignored. (RFC7540 4.1, RFC7540 6.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a PUSH_PROMISE frame with unknown flags.
	ok = gen_tcp:send(Socket, << 4:24, 5:8,
		1:1, 1:1, 1:1, 1:1, %% Undefined.
		0:1, %% PADDED.
		1:1, %% END_HEADERS.
		1:1, 1:1, %% Undefined.
		0:1, 1:31, 0:1, 3:31 >>
	),
	%% Receive a PROTOCOL_ERROR connection error.
	%%
	%% Note that it is not possible to distinguish between the expected
	%% result and the server rejecting PUSH_PROMISE frames.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

ignore_ping_unknown_flags(Config) ->
	doc("Undefined PING frame flags must be ignored. (RFC7540 4.1, RFC7540 6.7)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a PING frame with unknown flags.
	ok = gen_tcp:send(Socket, << 8:24, 6:8,
		1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.
		0:1, %% ACK.
		0:32, 0:64 >>),
	%% Receive a PING ACK in return.
	{ok, << 8:24, 6:8, _:7, 1:1, _:32, 0:64 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

ignore_goaway_unknown_flags(Config) ->
	doc("Undefined GOAWAY frame flags must be ignored. (RFC7540 4.1, RFC7540 6.8)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a GOAWAY frame with unknown flags.
	ok = gen_tcp:send(Socket, << 8:24, 7:8,
		1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.
		0:32, 0:64 >>),
	%% Receive a GOAWAY frame back.
	{ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

ignore_window_update_unknown_flags(Config) ->
	doc("Undefined WINDOW_UPDATE frame flags must be ignored. (RFC7540 4.1, RFC7540 6.9)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a WINDOW_UPDATE frame with unknown flags.
	ok = gen_tcp:send(Socket, << 4:24, 8:8,
		1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.
		0:32, 1000:32 >>),
	%% We expect no errors or replies, therefore we send a PING frame.
	ok = gen_tcp:send(Socket, cow_http2:ping(0)),
	%% And receive a PING ACK in return.
	{ok, << 8:24, 6:8, _:7, 1:1, _:32, 0:64 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

ignore_continuation_unknown_flags(Config) ->
	doc("Undefined CONTINUATION frame flags must be ignored. (RFC7540 4.1, RFC7540 6.10)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request with a CONTINUATION frame with unknown flags.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"POST">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/echo/read_body">>}
	]),
	Len = iolist_size(HeadersBlock),
	ok = gen_tcp:send(Socket, [
		<< 0:24, 1:8, 0:8, 0:1, 1:31 >>,
		<< Len:24, 9:8,
			1:1, 1:1, 1:1, 1:1, 1:1, %% Undefined.
			1:1, %% END_HEADERS.
			1:1, 1:1, %% Undefined.
			0:1, 1:31 >>,
		HeadersBlock,
		cow_http2:data(1, fin, << 0:100/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, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

%% @todo Flags that have no defined semantics for
%% a particular frame type MUST be left unset (0x0) when sending. (RFC7540 4.1)

ignore_data_reserved_bit(Config) ->
	doc("Reserved 1-bit field of DATA frame must be ignored. (RFC7540 4.1, RFC7540 6.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request with a DATA frame with the reserved bit set.
	{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),
		<< 100:24, 0:8, 0:7, 1:1,
			1:1, %% Reserved bit.
			1:31, 0:100/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, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

ignore_headers_reserved_bit(Config) ->
	doc("Reserved 1-bit field of HEADERS frame must be ignored. (RFC7540 4.1, RFC7540 6.2)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request with a HEADERS frame with the reserved bit set.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"POST">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/echo/read_body">>}
	]),
	Len = iolist_size(HeadersBlock),
	ok = gen_tcp:send(Socket, [
		<< Len:24, 1:8, 0:5, 1:1, 0:2,
			1:1, %% Reserved bit.
			1:31 >>,
		HeadersBlock,
		cow_http2:data(1, fin, << 0:100/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, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

ignore_priority_reserved_bit(Config) ->
	doc("Reserved 1-bit field of PRIORITY frame must be ignored. (RFC7540 4.1, RFC7540 6.3)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request with an interleaved PRIORITY frame with the reserved bit set.
	{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),
		<< 5:24, 2:8, 0:8,
			1:1, %% Reserved bit.
			1:31, 0:1, 3:31, 0:8 >>,
		cow_http2:data(1, fin, << 0:100/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, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

ignore_rst_stream_reserved_bit(Config) ->
	doc("Reserved 1-bit field of RST_STREAM frame must be ignored. (RFC7540 4.1, RFC7540 6.4)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request then cancel it with an RST_STREAM frame with the reserved bit set.
	{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),
		<< 4:24, 3:8, 0:8,
			1:1, %% Reserved bit.
			1:31, 8:32 >>,
		cow_http2:headers(3, nofin, HeadersBlock),
		cow_http2:data(3, fin, << 0:100/unit:8 >>)
	]),
	%% Receive a response with the same DATA frame.
	{ok, << SkipLen:24, 1:8, _:8, 3:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
	{ok, << 100:24, 0:8, 1:8, 3:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

ignore_settings_reserved_bit(Config) ->
	doc("Reserved 1-bit field of SETTINGS frame must be ignored. (RFC7540 4.1, RFC7540 6.5)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a SETTINGS frame with the reserved bit set.
	ok = gen_tcp:send(Socket, << 6:24, 4:8, 0:8,
		1:1, %% Reserved bit.
		0:31, 2:16, 0:32 >>),
	%% Receive a SETTINGS ack.
	{ok, << 0:24, 4:8, 0:7, 1:1, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	ok.

ignore_push_promise_reserved_bit(Config) ->
	doc("Reserved 1-bit field of PUSH_PROMISE frame must be ignored. (RFC7540 4.1, RFC7540 6.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a PUSH_PROMISE frame with the reserved bit set.
	ok = gen_tcp:send(Socket, << 4:24, 5:8, 0:5, 1:1, 0:2,
		1:1, %% Reserved bit.
		1:31, 0:1, 3:31 >>
	),
	%% Receive a PROTOCOL_ERROR connection error.
	%%
	%% Note that it is not possible to distinguish between the expected
	%% result and the server rejecting PUSH_PROMISE frames.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

ignore_ping_reserved_bit(Config) ->
	doc("Reserved 1-bit field of PING frame must be ignored. (RFC7540 4.1, RFC7540 6.7)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a PING frame with the reserved bit set.
	ok = gen_tcp:send(Socket, << 8:24, 6:8, 0:8,
		1:1, %% Reserved bit.
		0:31, 0:64 >>),
	%% Receive a PING ACK in return.
	{ok, << 8:24, 6:8, _:7, 1:1, _:32, 0:64 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

ignore_goaway_reserved_bit(Config) ->
	doc("Reserved 1-bit field of GOAWAY frame must be ignored. (RFC7540 4.1, RFC7540 6.8)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a GOAWAY frame with the reserved bit set.
	ok = gen_tcp:send(Socket, << 8:24, 7:8, 0:8,
		1:1, %% Reserved bit.
		0:31, 0:64 >>),
	%% Receive a GOAWAY frame back.
	{ok, << _:24, 7:8, _:72, 0:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

ignore_window_update_reserved_bit(Config) ->
	doc("Reserved 1-bit field of WINDOW_UPDATE frame must be ignored. (RFC7540 4.1, RFC7540 6.9)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a WINDOW_UPDATE frame with the reserved bit set.
	ok = gen_tcp:send(Socket, << 4:24, 8:8, 0:8,
		1:1, %% Reserved bit.
		0:31, 1000:32 >>),
	%% We expect no errors or replies, therefore we send a PING frame.
	ok = gen_tcp:send(Socket, cow_http2:ping(0)),
	%% And receive a PING ACK in return.
	{ok, << 8:24, 6:8, _:7, 1:1, _:32, 0:64 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

ignore_continuation_reserved_bit(Config) ->
	doc("Reserved 1-bit field of CONTINUATION frame must be ignored. (RFC7540 4.1, RFC7540 6.10)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a POST request with a CONTINUATION frame with the reserved bit set.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"POST">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/echo/read_body">>}
	]),
	Len = iolist_size(HeadersBlock),
	ok = gen_tcp:send(Socket, [
		<< 0:24, 1:8, 0:8, 0:1, 1:31 >>,
		<< Len:24, 9:8, 0:5, 1:1, 0:2,
			1:1, %% Reserved bit.
			1:31 >>,
		HeadersBlock,
		cow_http2:data(1, fin, << 0:100/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, << 100:24, 0:8, 1:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, << 0:100/unit:8 >>} = gen_tcp:recv(Socket, 100, 1000),
	ok.

%% @todo The reserved 1-bit field MUST remain unset (0x0) when sending. (RFC7540 4.1)

%% 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.
	ok = gen_tcp:send(Socket, << 3:24, 5:8, 0:5, 1:1, 0:3, 1:31, 0:1, 3:31 >>),
	%% 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.
	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 >>),
	%% 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">>, <<"/">>}
	]),
	Len = 14 + iolist_size(HeadersBlock),
	ok = gen_tcp:send(Socket, [
		<< Len: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.
	%%
	%% Note that it is not possible to distinguish between a Pad Length
	%% error and the server rejecting PUSH_PROMISE frames.
	{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.

%% Header compression and decompression.

headers_compression_error(Config) ->
	doc("A decoding error in a HEADERS frame's header block must be rejected "
		"with a COMPRESSION_ERROR connection error. (RFC7540 4.3, RFC7540 6.2)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with an invalid header block.
	ok = gen_tcp:send(Socket, << 10:24, 1:8, 0:5, 1:1, 0:1, 1:1, 0:1, 1:31, 0:10/unit:8 >>),
	%% Receive a COMPRESSION_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 9:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

continuation_compression_error(Config) ->
	doc("A decoding error in a CONTINUATION frame's header block must be rejected "
		"with a COMPRESSION_ERROR connection error. (RFC7540 4.3, RFC7540 6.10)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a CONTINUATION frame with an invalid header block.
	ok = gen_tcp:send(Socket, [
		<< 0:24, 1:8, 0:7, 1:1, 0:1, 1:31 >>,
		<< 10:24, 9:8, 0:5, 1:1, 0:3, 1:31, 0:10/unit:8 >>
	]),
	%% Receive a COMPRESSION_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 9:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

continuation_with_frame_interleaved_error(Config) ->
	doc("Frames interleaved in a header block must be rejected "
		"with a PROTOCOL_ERROR connection error. (RFC7540 4.3, RFC7540 6.2, RFC7540 6.10)"),
	{ok, Socket} = do_handshake(Config),
	%% Send an unterminated HEADERS frame followed by a PING frame.
	ok = gen_tcp:send(Socket, [
		<< 0:24, 1:8, 0:7, 1:1, 0:1, 1:31 >>,
		cow_http2:ping(0)
	]),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

continuation_wrong_stream_error(Config) ->
	doc("CONTINUATION frames with an incorrect stream identifier must be rejected "
		"with a PROTOCOL_ERROR connection error. (RFC7540 4.3, RFC7540 6.2)"),
	{ok, Socket} = do_handshake(Config),
	%% Send an unterminated HEADERS frame followed by a CONTINUATION frame for another stream.
	ok = gen_tcp:send(Socket, [
		<< 0:24, 1:8, 0:7, 1:1, 0:1, 1:31 >>,
		<< 0:24, 9:8, 0:9, 3:31 >>
	]),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

%% Stream states.

idle_stream_reject_data(Config) ->
	doc("DATA frames received on an idle stream must be rejected "
		"with a PROTOCOL_ERROR connection error. (RFC7540 5.1, RFC7540 6.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a DATA frame on an idle stream.
	ok = gen_tcp:send(Socket, cow_http2:data(1, fin, <<"Unexpected DATA frame.">>)),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

idle_stream_accept_headers(Config) ->
	doc("HEADERS frames received on an idle stream must be accepted. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame on an idle stream.
	{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)),
	%% Receive a HEADERS frame as a response.
	{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	ok.

idle_stream_accept_priority(Config) ->
	doc("PRIORITY frames received on an idle stream must be accepted. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a PRIORITY frame on an idle stream.
	ok = gen_tcp:send(Socket, cow_http2:priority(1, shared, 3, 123)),
	%% Receive no error.
	{error, timeout} = gen_tcp:recv(Socket, 7, 1000),
	%% Send a HEADERS frame on the same stream.
	{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)),
	%% Receive a HEADERS frame as a response.
	{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	ok.

idle_stream_reject_rst_stream(Config) ->
	doc("RST_STREAM frames received on an idle stream must be rejected "
		"with a PROTOCOL_ERROR connection error. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send an RST_STREAM frame on an idle stream.
	ok = gen_tcp:send(Socket, cow_http2:rst_stream(1, no_error)),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

idle_stream_reject_push_promise(Config) ->
	doc("PUSH_PROMISE frames received on an idle stream must be rejected "
		"with a PROTOCOL_ERROR connection error. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a PUSH_PROMISE frame on an idle stream.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:push_promise(1, 3, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

idle_stream_reject_window_update(Config) ->
	doc("WINDOW_UPDATE frames received on an idle stream must be rejected "
		"with a PROTOCOL_ERROR connection error. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a WINDOW_UPDATE frame on an idle stream.
	ok = gen_tcp:send(Socket, cow_http2:window_update(1, 12345)),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

%reserved (local) - after sending PUSH_PROMISE:
%      An endpoint MUST NOT send any type of frame other than HEADERS,
%      RST_STREAM, or PRIORITY in this state.
%%% how to test this?
%
%      A PRIORITY or WINDOW_UPDATE frame MAY be received in this state.
%      Receiving any type of frame other than RST_STREAM, PRIORITY, or
%      WINDOW_UPDATE on a stream in this state MUST be treated as a
%      connection error (Section 5.4.1) of type PROTOCOL_ERROR.
%%% we need to use a large enough file for this
%
%reserved_local_reject_data
%reserved_local_reject_headers
%reserved_local_accept_priority
%reserved_local_accept_rst_stream
%reserved_local_reject_push_promise %% do we even care? we reject it always
%reserved_local_accept_window_update
%
%half-closed (remote):
%      If an endpoint receives additional frames, other than
%      WINDOW_UPDATE, PRIORITY, or RST_STREAM, for a stream that is in
%      this state, it MUST respond with a stream error (Section 5.4.2) of
%      type STREAM_CLOSED.

half_closed_remote_reject_data(Config) ->
	doc("DATA frames received on a half-closed (remote) stream must be rejected "
		"with a STREAM_CLOSED stream error. (RFC7540 5.1, RFC7540 6.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with the FIN flag set.
	{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)),
	%% Send a DATA frame on that now half-closed (remote) stream.
	ok = gen_tcp:send(Socket, cow_http2:data(1, fin, <<"Unexpected DATA frame.">>)),
	%% Receive a STREAM_CLOSED stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 5:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

%% We reject all invalid HEADERS with a connection error because
%% we do not want to waste resources decoding them.
half_closed_remote_reject_headers(Config) ->
	doc("HEADERS frames received on a half-closed (remote) stream must be rejected "
		"with a STREAM_CLOSED connection error. (RFC7540 4.3, RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with the FIN flag set.
	{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)),
	%% Send a HEADERS frame on that now half-closed (remote) stream.
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a STREAM_CLOSED connection error.
	{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

half_closed_remote_accept_priority(Config) ->
	doc("PRIORITY frames received on a half-closed stream must be accepted. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with the FIN flag set.
	{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)),
	%% Send a PRIORITY frame on that now half-closed (remote) stream.
	ok = gen_tcp:send(Socket, cow_http2:priority(1, shared, 3, 123)),
	%% Receive a HEADERS frame as a response.
	{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	ok.

half_closed_remote_accept_rst_stream(Config) ->
	doc("RST_STREAM frames received on a half-closed stream must be accepted. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with the FIN flag set.
	{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)),
	%% Send an RST_STREAM frame on that now half-closed (remote) stream.
	ok = gen_tcp:send(Socket, cow_http2:rst_stream(1, no_error)),
	%% Receive nothing back.
	{error, timeout} = gen_tcp:recv(Socket, 9, 6000),
	ok.

%% half_closed_remote_reject_push_promise
%%
%% We respond to all PUSH_PROMISE frames with a PROTOCOL_ERROR connection error
%% because PUSH is disabled in that direction. We therefore cannot test other
%% error conditions.

half_closed_remote_accept_window_update(Config) ->
	doc("WINDOW_UPDATE frames received on a half-closed stream must be accepted. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with the FIN flag set.
	{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)),
	%% Send a WINDOW_UPDATE frame on that now half-closed (remote) stream.
	ok = gen_tcp:send(Socket, cow_http2:window_update(1, 12345)),
	%% Receive a HEADERS frame as a response.
	{ok, << _:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	ok.

%% We reject DATA frames sent on closed streams with a STREAM_CLOSED
%% connection error regardless of how the stream was closed to simplify
%% the implementation. This excludes the few frames we ignore from
%% lingering streams that we canceled.
rst_stream_closed_reject_data(Config) ->
	doc("DATA frames received on a stream closed via RST_STREAM must be rejected "
		"with a STREAM_CLOSED connection error. (RFC7540 5.1, RFC7540 6.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),
	%% Send an RST_STREAM frame to close the stream.
	ok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),
	%% Send a DATA frame on the now RST_STREAM closed stream.
	ok = gen_tcp:send(Socket, cow_http2:data(1, fin, <<"Unexpected DATA frame.">>)),
	%% Receive a STREAM_CLOSED connection error.
	{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

%% We reject all invalid HEADERS with a connection error because
%% we do not want to waste resources decoding them.
rst_stream_closed_reject_headers(Config) ->
	doc("HEADERS frames received on a stream closed via RST_STREAM must be rejected "
		"with a STREAM_CLOSED connection error. (RFC7540 4.3, RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),
	%% Send an RST_STREAM frame to close the stream.
	ok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),
	%% Send a HEADERS frame on the now RST_STREAM closed stream.
	ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),
	%% Receive a STREAM_CLOSED connection error.
	{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

rst_stream_closed_accept_priority(Config) ->
	doc("PRIORITY frames received on a stream closed via RST_STREAM "
		"must be accepted. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),
	%% Send an RST_STREAM frame to close the stream.
	ok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),
	%% Send a PRIORITY frame on that now RST_STREAM closed stream.
	ok = gen_tcp:send(Socket, cow_http2:priority(1, shared, 3, 123)),
	%% Receive nothing back.
	{error, timeout} = gen_tcp:recv(Socket, 9, 6000),
	ok.

rst_stream_closed_ignore_rst_stream(Config) ->
	doc("RST_STREAM frames received on a stream closed via RST_STREAM "
		"must be ignored to avoid looping. (RFC7540 5.1, RFC7540 5.4.2)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),
	%% Send an RST_STREAM frame to close the stream.
	ok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),
	%% Send an extra RST_STREAM.
	ok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),
	%% Receive nothing back.
	{error, timeout} = gen_tcp:recv(Socket, 9, 6000),
	ok.

%% rst_stream_closed_reject_push_promise
%%
%% We respond to all PUSH_PROMISE frames with a PROTOCOL_ERROR connection error
%% because PUSH is disabled in that direction. We therefore cannot test other
%% error conditions.

rst_stream_closed_reject_window_update(Config) ->
	doc("WINDOW_UPDATE frames received on a stream closed via RST_STREAM "
		"must be rejected with a STREAM_CLOSED stream error. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),
	%% Send an RST_STREAM frame to close the stream.
	ok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),
	%% Send a WINDOW_UPDATE frame on the now RST_STREAM closed stream.
	ok = gen_tcp:send(Socket, cow_http2:window_update(1, 12345)),
	%% Receive a STREAM_CLOSED stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 5:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

stream_closed_reject_data(Config) ->
	doc("DATA frames received on a stream closed normally must be rejected "
		"with a STREAM_CLOSED connection error. (RFC7540 5.1, RFC7540 6.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame.
	{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)),
	%% Receive the response.
	{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length1, 6000),
	{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length2, 6000),
	%% Send a DATA frame on the now closed stream.
	ok = gen_tcp:send(Socket, cow_http2:data(1, fin, <<"Unexpected DATA frame.">>)),
	%% Receive a STREAM_CLOSED connection error.
	{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

stream_closed_reject_headers(Config) ->
	doc("HEADERS frames received on a stream closed normally must be rejected "
		"with a STREAM_CLOSED connection error. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame.
	{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)),
	%% Receive the response.
	{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length1, 6000),
	{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length2, 6000),
	%% Send a HEADERS frame on the now closed stream.
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a STREAM_CLOSED connection error.
	{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

stream_closed_accept_priority(Config) ->
	doc("PRIORITY frames received on a stream closed normally must be accepted. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame.
	{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)),
	%% Receive the response.
	{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length1, 6000),
	{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length2, 6000),
	%% Send a PRIORITY frame on the now closed stream.
	ok = gen_tcp:send(Socket, cow_http2:priority(1, shared, 3, 123)),
	%% Receive nothing back.
	{error, timeout} = gen_tcp:recv(Socket, 9, 6000),
	ok.

stream_closed_accept_rst_stream(Config) ->
	doc("RST_STREAM frames received on a stream closed normally "
		"must be accepted for a short period. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame.
	{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)),
	%% Receive the response.
	{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length1, 6000),
	{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length2, 6000),
	%% Send an RST_STREAM frame on the now closed stream.
	ok = gen_tcp:send(Socket, cow_http2:rst_stream(1, cancel)),
	%% Receive nothing back.
	{error, timeout} = gen_tcp:recv(Socket, 9, 6000),
	ok.

%% stream_closed_reject_push_promise
%%
%% We respond to all PUSH_PROMISE frames with a PROTOCOL_ERROR connection error
%% because PUSH is disabled in that direction. We therefore cannot test other
%% error conditions.

stream_closed_accept_window_update(Config) ->
	doc("WINDOW_UPDATE frames received on a stream closed normally "
		"must be accepted for a short period. (RFC7540 5.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame.
	{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)),
	%% Receive the response.
	{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length1, 6000),
	{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length2, 6000),
	%% Send a WINDOW_UPDATE frame on the now closed stream.
	ok = gen_tcp:send(Socket, cow_http2:window_update(1, 12345)),
	%% Receive nothing back.
	{error, timeout} = gen_tcp:recv(Socket, 9, 6000),
	ok.

%% @todo While we accept RST_STREAM and WINDOW_UPDATE for a short period
%% after the stream closed normally, we may want to reject the ones coming
%% a significant amount of time after that.

%% @todo Frames may arrive on a stream after we send an RST_STREAM for it.
%% They must be ignored for a short period of time:
%
%      If this state is reached as a result of sending a RST_STREAM
%      frame, the peer that receives the RST_STREAM might have already
%      sent -- or enqueued for sending -- frames on the stream that
%      cannot be withdrawn.  An endpoint MUST ignore frames that it
%      receives on closed streams after it has sent a RST_STREAM frame.
%      An endpoint MAY choose to limit the period over which it ignores
%      frames and treat frames that arrive after this time as being in
%      error.

%% @todo Ensure that rejected DATA frames result in the connection
%% flow-control window being updated. How to test this?
%
%      Flow-controlled frames (i.e., DATA) received after sending
%      RST_STREAM are counted toward the connection flow-control window.
%      Even though these frames might be ignored, because they are sent
%      before the sender receives the RST_STREAM, the sender will
%      consider the frames to count against the flow-control window.

%% Stream identifiers.

reject_streamid_even(Config) ->
	doc("HEADERS frames received with an even-numbered streamid "
		"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 5.1.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with an even-numbered streamid.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(2, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

reject_streamid_0(Config) ->
	doc("HEADERS frames received with streamid 0 (zero) "
		"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 5.1.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with an streamid 0.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(0, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

http_upgrade_reject_reuse_streamid_1(Config) ->
	doc("Attempts to reuse streamid 1 after upgrading to HTTP/2 "
		"must be rejected with a PROTOCOL_ERROR connection error. (RFC7540 5.1.1)"),
	{ok, Socket} = gen_tcp:connect("localhost", config(port, Config), [binary, {active, false}]),
	ok = gen_tcp:send(Socket, [
		"GET / HTTP/1.1\r\n"
		"Host: localhost\r\n"
		"Connection: Upgrade, HTTP2-Settings\r\n"
		"Upgrade: h2c\r\n"
		"HTTP2-Settings: ", base64:encode(cow_http2:settings_payload(#{})), "\r\n",
		"\r\n"]),
	ok = do_recv_101(Socket),
	%% 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 the SETTINGS ack.
	ok = gen_tcp:send(Socket, cow_http2:settings_ack()),
	%% Receive the SETTINGS ack, and the response HEADERS and DATA (Stream ID 1).
	Received = lists:reverse(lists:foldl(fun(_, Acc) ->
		case gen_tcp:recv(Socket, 9, 1000) of
			{ok, << 0:24, 4:8, 1:8, 0:32 >>} ->
				[settings_ack|Acc];
			{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[headers|Acc];
			{ok, << SkipLen:24, 0:8, _:8, 1:32 >>} ->
				{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
				[data|Acc]
		end
	end, [], [1, 2, 3])),
	case Received of
		[settings_ack, headers, data] -> ok;
		[headers, settings_ack, data] -> ok;
		[headers, data, settings_ack] -> ok
	end,
	%% Send a HEADERS frame with streamid 1.
	{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)),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

%% The RFC gives us various error codes to return for this case,
%% depending on whether the stream existed previously and how it
%% ended up being (half-)closed. Cowboy rejects all these HEADERS
%% frames the same way: with a STREAM_CLOSED connection error.
%% Making it a connection error is particularly important in the
%% cases where a stream error would be allowed because we avoid
%% having to decode the headers and save up resources.
reject_streamid_lower(Config) ->
	doc("HEADERS frames received with streamid lower than the previous stream "
		"must be rejected with a STREAM_CLOSED connection error. (RFC7540 5.1.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with streamid 5.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(5, fin, HeadersBlock)),
	%% Receive the response.
	{ok, << Length1:24, 1:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length1, 6000),
	{ok, << Length2:24, 0:8, _:40 >>} = gen_tcp:recv(Socket, 9, 6000),
	{ok, _} = gen_tcp:recv(Socket, Length2, 6000),
	%% Send a HEADERS frame with streamid 3.
	ok = gen_tcp:send(Socket, cow_http2:headers(3, fin, HeadersBlock)),
	%% Receive a STREAM_CLOSED connection error.
	{ok, << _:24, 7:8, _:72, 5:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

%% @todo We need an option to limit the number of streams one can open
%% on a connection. And we need to enforce it. (RFC7540 5.1.1)
%
%   Stream identifiers cannot be reused.  Long-lived connections can
%   result in an endpoint exhausting the available range of stream
%   identifiers.  A server
%   that is unable to establish a new stream identifier can send a GOAWAY
%   frame so that the client is forced to open a new connection for new
%   streams.

%% @todo We need this option too. (RFC7540 5.1.2)
%   A peer can limit the number of concurrently active streams using the
%   SETTINGS_MAX_CONCURRENT_STREAMS parameter (see Section 6.5.2) within
%   a SETTINGS frame.  The maximum concurrent streams setting is specific
%   to each endpoint and applies only to the peer that receives the
%   setting.  That is, clients specify the maximum number of concurrent
%   streams the server can initiate, and servers specify the maximum
%   number of concurrent streams the client can initiate.
%
%   Streams that are in the "open" state or in either of the "half-
%   closed" states count toward the maximum number of streams that an
%   endpoint is permitted to open.  Streams in any of these three states
%   count toward the limit advertised in the
%   SETTINGS_MAX_CONCURRENT_STREAMS setting.  Streams in either of the
%   "reserved" states do not count toward the stream limit.
%
%   Endpoints MUST NOT exceed the limit set by their peer.  An endpoint
%   that receives a HEADERS frame that causes its advertised concurrent
%   stream limit to be exceeded MUST treat this as a stream error
%   (Section 5.4.2) of type PROTOCOL_ERROR or REFUSED_STREAM.  The choice
%   of error code determines whether the endpoint wishes to enable
%   automatic retry (see Section 8.1.4) for details).
%
%   An endpoint that wishes to reduce the value of
%   SETTINGS_MAX_CONCURRENT_STREAMS to a value that is below the current
%   number of open streams can either close streams that exceed the new
%   value or allow streams to complete.

%% (RFC7540 5.2.1)
%   3.  Flow control is directional with overall control provided by the
%       receiver.  A receiver MAY choose to set any window size that it
%       desires for each stream and for the entire connection.  A sender
%       MUST respect flow-control limits imposed by a receiver.  Clients,
%       servers, and intermediaries all independently advertise their
%       flow-control window as a receiver and abide by the flow-control
%       limits set by their peer when sending.
%
%   4.  The initial value for the flow-control window is 65,535 octets
%       for both new streams and the overall connection.
%
%   5.  The frame type determines whether flow control applies to a
%       frame.  Of the frames specified in this document, only DATA
%       frames are subject to flow control; all other frame types do not
%       consume space in the advertised flow-control window.  This
%       ensures that important control frames are not blocked by flow
%       control.
%
%   6.  Flow control cannot be disabled.

%% (RFC7540 5.2.2)
%   Even with full awareness of the current bandwidth-delay product,
%   implementation of flow control can be difficult.  When using flow
%   control, the receiver MUST read from the TCP receive buffer in a
%   timely fashion.  Failure to do so could lead to a deadlock when
%   critical frames, such as WINDOW_UPDATE, are not read and acted upon.

%% @todo Stream priorities. (RFC7540 5.3 5.3.x)

%% (RFC7540 5.4.1)
%   An endpoint that encounters a connection error SHOULD first send a
%   GOAWAY frame (Section 6.8) with the stream identifier of the last
%   stream that it successfully received from its peer.  The GOAWAY frame
%   includes an error code that indicates why the connection is
%   terminating.  After sending the GOAWAY frame for an error condition,
%   the endpoint MUST close the TCP connection.
%
%   An endpoint can end a connection at any time.  In particular, an
%   endpoint MAY choose to treat a stream error as a connection error.
%   Endpoints SHOULD send a GOAWAY frame when ending a connection,
%   providing that circumstances permit it.

%% (RFC7540 5.4.2)
%   A RST_STREAM is the last frame that an endpoint can send on a stream.
%   The peer that sends the RST_STREAM frame MUST be prepared to receive
%   any frames that were sent or enqueued for sending by the remote peer.
%   These frames can be ignored, except where they modify connection
%   state (such as the state maintained for header compression
%   (Section 4.3) or flow control).
%
%   Normally, an endpoint SHOULD NOT send more than one RST_STREAM frame
%   for any stream.  However, an endpoint MAY send additional RST_STREAM
%   frames if it receives frames on a closed stream after more than a
%   round-trip time.  This behavior is permitted to deal with misbehaving
%   implementations.
%
%   To avoid looping, an endpoint MUST NOT send a RST_STREAM in response
%   to a RST_STREAM frame.

%% (RFC7540 5.5)
%   Extensions are permitted to use new frame types (Section 4.1), new
%   settings (Section 6.5.2), or new error codes (Section 7).  Registries
%   are established for managing these extension points: frame types
%   (Section 11.2), settings (Section 11.3), and error codes
%   (Section 11.4).
%
%   Implementations MUST ignore unknown or unsupported values in all
%   extensible protocol elements.  Implementations MUST discard frames
%   that have unknown or unsupported types.  This means that any of these
%   extension points can be safely used by extensions without prior
%   arrangement or negotiation.  However, extension frames that appear in
%   the middle of a header block (Section 4.3) are not permitted; these
%   MUST be treated as a connection error (Section 5.4.1) of type
%   PROTOCOL_ERROR.

continuation_with_extension_frame_interleaved_error(Config) ->
	doc("Extension frames interleaved in a header block must be rejected "
		"with a PROTOCOL_ERROR connection error. "
		"(RFC7540 4.3, RFC7540 5.5, RFC7540 6.2, RFC7540 6.10)"),
	{ok, Socket} = do_handshake(Config),
	%% Send an unterminated HEADERS frame followed by an extension frame.
	ok = gen_tcp:send(Socket, [
		<< 0:24, 1:8, 0:7, 1:1, 0:1, 1:31 >>,
		<< 0:24, 128:8, 0:8, 0:32 >>
	]),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

%% (RFC7540 6.1) DATA
%   Padding:  Padding octets that contain no application semantic value.
%      Padding octets MUST be set to zero when sending.  A receiver is
%      not obligated to verify padding but MAY treat non-zero padding as
%      a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
%
%   DATA frames MUST be associated with a stream.  If a DATA frame is
%   received whose stream identifier field is 0x0, the recipient MUST
%   respond with a connection error (Section 5.4.1) of type
%   PROTOCOL_ERROR.

%% (RFC7540 6.2) HEADERS
%   Padding:  Padding octets that contain no application semantic value.
%      Padding octets MUST be set to zero when sending.  A receiver is
%      not obligated to verify padding but MAY treat non-zero padding as
%      a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
%
%      A HEADERS frame carries the END_STREAM flag that signals the end
%      of a stream.  However, a HEADERS frame with the END_STREAM flag
%      set can be followed by CONTINUATION frames on the same stream.
%      Logically, the CONTINUATION frames are part of the HEADERS frame.
%
%% @todo We probably need a test for the server sending HEADERS too large.
%   The payload of a HEADERS frame contains a header block fragment
%   (Section 4.3).  A header block that does not fit within a HEADERS
%   frame is continued in a CONTINUATION frame (Section 6.10).
%
%   HEADERS frames MUST be associated with a stream.  If a HEADERS frame
%   is received whose stream identifier field is 0x0, the recipient MUST
%   respond with a connection error (Section 5.4.1) of type
%   PROTOCOL_ERROR.

%% (RFC7540 6.3) PRIORITY
%   The PRIORITY frame always identifies a stream.  If a PRIORITY frame
%   is received with a stream identifier of 0x0, the recipient MUST
%   respond with a connection error (Section 5.4.1) of type
%   PROTOCOL_ERROR.

%% (RFC7540 6.4) RST_STREAM
%   The RST_STREAM frame fully terminates the referenced stream and
%   causes it to enter the "closed" state.  After receiving a RST_STREAM
%   on a stream, the receiver MUST NOT send additional frames for that
%   stream, with the exception of PRIORITY.  However, after sending the
%   RST_STREAM, the sending endpoint MUST be prepared to receive and
%   process additional frames sent on the stream that might have been
%   sent by the peer prior to the arrival of the RST_STREAM.
%
%   RST_STREAM frames MUST be associated with a stream.  If a RST_STREAM
%   frame is received with a stream identifier of 0x0, the recipient MUST
%   treat this as a connection error (Section 5.4.1) of type
%   PROTOCOL_ERROR.

%% (RFC7540 6.5) SETTINGS
%   A SETTINGS frame MUST be sent by both endpoints at the start of a
%   connection and MAY be sent at any other time by either endpoint over
%   the lifetime of the connection.  Implementations MUST support all of
%   the parameters defined by this specification.
%
%   SETTINGS frames always apply to a connection, never a single stream.
%   The stream identifier for a SETTINGS frame MUST be zero (0x0).  If an
%   endpoint receives a SETTINGS frame whose stream identifier field is
%   anything other than 0x0, the endpoint MUST respond with a connection
%   error (Section 5.4.1) of type PROTOCOL_ERROR.
%
%   The SETTINGS frame affects connection state.  A badly formed or
%   incomplete SETTINGS frame MUST be treated as a connection error
%   (Section 5.4.1) of type PROTOCOL_ERROR.

%% (RFC7540 6.5.2)
%   SETTINGS_ENABLE_PUSH (0x2):  This setting can be used to disable
%      server push (Section 8.2).  An endpoint MUST NOT send a
%      PUSH_PROMISE frame if it receives this parameter set to a value of
%      0.  An endpoint that has both set this parameter to 0 and had it
%      acknowledged MUST treat the receipt of a PUSH_PROMISE frame as a
%      connection error (Section 5.4.1) of type PROTOCOL_ERROR.
%
%   SETTINGS_MAX_CONCURRENT_STREAMS (0x3):  Indicates the maximum number
%      of concurrent streams that the sender will allow.  This limit is
%      directional: it applies to the number of streams that the sender
%      permits the receiver to create.  Initially, there is no limit to
%      this value.  It is recommended that this value be no smaller than
%      100, so as to not unnecessarily limit parallelism.
%
%      A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be
%      treated as special by endpoints.  A zero value does prevent the
%      creation of new streams; however, this can also happen for any
%      limit that is exhausted with active streams.  Servers SHOULD only
%      set a zero value for short durations; if a server does not wish to
%      accept requests, closing the connection is more appropriate.
%
%   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_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.
%
%   An endpoint that receives a SETTINGS frame with any unknown or
%   unsupported identifier MUST ignore that setting. (6.5.2 and 6.5.3)

%% (RFC7540 6.5.3)
%   Upon receiving a SETTINGS frame with the ACK flag set, the
%   sender of the altered parameters can rely on the setting having been
%   applied.
%
%   If the sender of a SETTINGS frame does not receive an acknowledgement
%   within a reasonable amount of time, it MAY issue a connection error
%   (Section 5.4.1) of type SETTINGS_TIMEOUT.

%% (RFC7540 6.6) PUSH_PROMISE
% @todo PUSH_PROMISE frames have a reserved bit in the payload that must be ignored.
%
%   Padding:  Padding octets that contain no application semantic value.
%      Padding octets MUST be set to zero when sending.  A receiver is
%      not obligated to verify padding but MAY treat non-zero padding as
%      a connection error (Section 5.4.1) of type PROTOCOL_ERROR.
%
%   PUSH_PROMISE frames MUST only be sent on a peer-initiated stream that
%   is in either the "open" or "half-closed (remote)" state.  The stream
%   identifier of a PUSH_PROMISE frame indicates the stream it is
%   associated with.  If the stream identifier field specifies the value
%   0x0, a recipient MUST respond with a connection error (Section 5.4.1)
%   of type PROTOCOL_ERROR.
%
%   PUSH_PROMISE MUST NOT be sent if the SETTINGS_ENABLE_PUSH setting of
%   the peer endpoint is set to 0.  An endpoint that has set this setting
%   and has received acknowledgement MUST treat the receipt of a
%   PUSH_PROMISE frame as a connection error (Section 5.4.1) of type
%   PROTOCOL_ERROR.
%
%   Since PUSH_PROMISE reserves a stream, ignoring a PUSH_PROMISE frame
%   causes the stream state to become indeterminate.  A receiver MUST
%   treat the receipt of a PUSH_PROMISE on a stream that is neither
%   "open" nor "half-closed (local)" as a connection error
%   (Section 5.4.1) of type PROTOCOL_ERROR.
%
%   A receiver MUST treat the receipt of a PUSH_PROMISE that promises an
%   illegal stream identifier (Section 5.1.1) as a connection error
%   (Section 5.4.1) of type PROTOCOL_ERROR.  Note that an illegal stream
%   identifier is an identifier for a stream that is not currently in the
%   "idle" state.

%% (RFC7540 6.7) PING
%   PING frames are not associated with any individual stream.  If a PING
%   frame is received with a stream identifier field value other than
%   0x0, the recipient MUST respond with a connection error
%   (Section 5.4.1) of type PROTOCOL_ERROR.

%% (RFC7540 6.8) GOAWAY
% @todo GOAWAY frames have a reserved bit in the payload that must be ignored.
%
%% @todo We should eventually implement the mechanism for gracefully
%% shutting down of the connection. (Send the GOAWAY, finish processing
%% the current set of streams, give up after a certain timeout.)
%
%% @todo If we graceful shutdown and receive a GOAWAY, we give up too.
%   A GOAWAY frame might not immediately precede closing of the
%   connection; a receiver of a GOAWAY that has no more use for the
%   connection SHOULD still send a GOAWAY frame before terminating the
%   connection.
%
%% @todo And it gets more complex when you think about h1 to h2 proxies.
%   A server that is attempting to gracefully shut down a
%   connection SHOULD send an initial GOAWAY frame with the last stream
%   identifier set to 2^31-1 and a NO_ERROR code.  This signals to the
%   client that a shutdown is imminent and that initiating further
%   requests is prohibited.  After allowing time for any in-flight stream
%   creation (at least one round-trip time), the server can send another
%   GOAWAY frame with an updated last stream identifier.  This ensures
%   that a connection can be cleanly shut down without losing requests.
%
%% @todo And of course even if we shutdown we need to be careful about
%% the connection state.
%   After sending a GOAWAY frame, the sender can discard frames for
%   streams initiated by the receiver with identifiers higher than the
%   identified last stream.  However, any frames that alter connection
%   state cannot be completely ignored.  For instance, HEADERS,
%   PUSH_PROMISE, and CONTINUATION frames MUST be minimally processed to
%   ensure the state maintained for header compression is consistent (see
%   Section 4.3); similarly, DATA frames MUST be counted toward the
%   connection flow-control window.  Failure to process these frames can
%   cause flow control or header compression state to become
%   unsynchronized.
%
%   The GOAWAY frame applies to the connection, not a specific stream.
%   An endpoint MUST treat a GOAWAY frame with a stream identifier other
%   than 0x0 as a connection error (Section 5.4.1) of type
%   PROTOCOL_ERROR.

%% (RFC7540 6.9) WINDOW_UPDATE
% @todo WINDOW_UPDATE frames have a reserved bit in the payload that must be ignored.

window_update_reject_0(Config) ->
	doc("WINDOW_UPDATE frames with an increment of 0 for the connection "
		"flow control window must be rejected with a "
		"PROTOCOL_ERROR connection error. (RFC7540 6.9.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send connection-wide WINDOW_UPDATE frame with a value of 0.
	ok = gen_tcp:send(Socket, [
		cow_http2:window_update(0)
	]),
	%% Receive a PROTOCOL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 1:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

window_update_reject_0_stream(Config) ->
	doc("WINDOW_UPDATE frames with an increment of 0 for a stream "
		"flow control window must be rejected with a "
		"PROTOCOL_ERROR stream error. (RFC7540 6.9.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame immediately followed by
	%% a WINDOW_UPDATE frame with a value of 0.
	{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),
		cow_http2:window_update(1, 0)
	]),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

%   A receiver that receives a flow-controlled frame MUST always account
%   for its contribution against the connection flow-control window,
%   unless the receiver treats this as a connection error
%   (Section 5.4.1).  This is necessary even if the frame is in error.
%   The sender counts the frame toward the flow-control window, but if
%   the receiver does not, the flow-control window at the sender and
%   receiver can become different.

%% (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
%   is, an empty DATA frame) MAY be sent if there is no available space
%   in either flow-control window.

window_update_reject_overflow(Config) ->
	doc("WINDOW_UPDATE frames that cause the connection flow control "
		"window to exceed 2^31-1 must be rejected with a "
		"FLOW_CONTROL_ERROR connection error. (RFC7540 6.9.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a connection-wide WINDOW_UPDATE frame that causes the window to overflow.
	ok = gen_tcp:send(Socket, [
		cow_http2:window_update(16#7fffffff)
	]),
	%% Receive a FLOW_CONTROL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 3:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.

window_update_reject_overflow_stream(Config) ->
	doc("WINDOW_UPDATE frames that cause a stream flow control "
		"window to exceed 2^31-1 must be rejected with a "
		"FLOW_CONTROL_ERROR stream error. (RFC7540 6.9.1)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame immediately followed by a WINDOW_UPDATE
	%% frame that causes the stream window to overflow.
	{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),
		cow_http2:window_update(1, 16#7fffffff)
	]),
	%% 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_changes(Config) ->
	doc("When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, the server "
		"must adjust the size of the flow control windows of the active "
		"streams. (RFC7540 6.9.2)"),
	{ok, Socket} = do_handshake(Config),
	%% Set SETTINGS_INITIAL_WINDOW_SIZE to 0 to prevent sending of DATA.
	ok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 0})),
	%% Receive the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	%% Send a HEADERS frame.
	{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)),
	%% Receive a response but no DATA frames are coming.
	{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
	{error, timeout} = gen_tcp:recv(Socket, 9, 1000),
	%% Set SETTINGS_INITIAL_WINDOW_SIZE to a larger value.
	ok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 5})),
	%% Receive the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	%% Receive a DATA frame of that size and no other.
	{ok, << 5:24, 0:8, 0:8, 1:32, "Hello" >>} = gen_tcp:recv(Socket, 14, 1000),
	{error, timeout} = gen_tcp:recv(Socket, 9, 1000),
	%% Set SETTINGS_INITIAL_WINDOW_SIZE to exactly the size in the body.
	ok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 12})),
	%% Receive the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	%% Receive the rest of the response.
	{ok, << 7:24, 0:8, 1:8, 1:32, " world!" >>} = gen_tcp:recv(Socket, 16, 1000),
	ok.

settings_initial_window_size_changes_negative(Config) ->
	doc("When the value of SETTINGS_INITIAL_WINDOW_SIZE changes, the server "
		"must adjust the size of the flow control windows of the active "
		"streams even if their window end up negative. (RFC7540 6.9.2)"),
	{ok, Socket} = do_handshake(Config),
	%% Set SETTINGS_INITIAL_WINDOW_SIZE to 5.
	ok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 5})),
	%% Receive the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	%% Send a HEADERS frame.
	{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)),
	%% Receive a response with a single DATA frame of the initial size we set.
	{ok, << SkipLen:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	{ok, _} = gen_tcp:recv(Socket, SkipLen, 1000),
	{ok, << 5:24, 0:8, 0:8, 1:32, "Hello" >>} = gen_tcp:recv(Socket, 14, 1000),
	{error, timeout} = gen_tcp:recv(Socket, 9, 1000),
	%% Set SETTINGS_INITIAL_WINDOW_SIZE to 0 to make the stream's window negative.
	ok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 0})),
	%% Receive the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	%% Set SETTINGS_INITIAL_WINDOW_SIZE to exactly the size in the body.
	ok = gen_tcp:send(Socket, cow_http2:settings(#{initial_window_size => 12})),
	%% Receive the SETTINGS ack.
	{ok, << 0:24, 4:8, 1:8, 0:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	%% Receive the rest of the response.
	{ok, << 7:24, 0:8, 1:8, 1:32, " world!" >>} = gen_tcp:recv(Socket, 16, 1000),
	ok.

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)"),
	{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})),
	%% Receive a FLOW_CONTROL_ERROR connection error.
	{ok, << _:24, 7:8, _:72, 3:32 >>} = gen_tcp:recv(Socket, 17, 6000),
	ok.