aboutsummaryrefslogblamecommitdiffstats
path: root/test/rfc7540_SUITE.erl
blob: a97da71dd21c99a6f30d62400b17a58f1a080a61 (plain) (tree)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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
1226
1227
1228
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
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
                                                             














                                                                           
                            


                               
                                   


























                                                                                                                  
                                   

                                                 
























                                                                                                     
                                  












                                                                                                     
                                  




                                                                                           
                                                                   






                                                                                                     
                                  

                                                                                           
                                                                  

           
                                                         






                                                                                                     
                                  
                         
                                                                  









                                                                                                     
                                  













                                                                                                     
                                  
                                                            
                                                                                                     



                                                                  









                                                      







                                                                                                     
                                  

                                                                                           
                                 









                                                                                                             
                                  

                                                                                           
                                 











                                                                                                     
                                  

                                                                                           
                                 


                                                                                  




                                                                                      
                                                                         
                                                        









                                                                                                     
                                  

                                                                                           
                                 





                                                                                       


















                                                                                  








                                                                                                     
                                  

                                                                                           
                                 





                                                                                       


















                                                                                  








                                                                                                     
                                  

                                                                                           
                                 





                                                                                               


















                                                                                  








                                                                                                     
                                  

                                                                                           
                                 





                                                                                                    


















                                                                                  







                                                                                                     
                                  

                                                                                           
                                 















                                                                                                         
                                  

                                                                                           
                                 











                                                                                                 



























                                                                        
 






                                                                      
 








                                                                                                     
                                  

                                                                                           
                                 







                                                                                                 
                                                                                     
















                                                                              








                                                                                                     
                                  

                                                                                           
                                 





                                                                     


                                                                                  
























                                                                                                 




























































































































































































                                                                                                     
                                                        











                                                                                                     
                                                        











                                                                                                     
                                                        



























                                                                                                         

                                  

                                                                                                     







                                                                                                 






                                                                              







                                                                               






                                                                     
 
























                                                                                   




































































































































































































































































































































































































                                                                                                       
















                                                                                                        







                                                                                      





















































































































































































































































































                                                                                                                     
                                                                                   








                                                                                                   
                                                                                             














                                                                                                       
                                             
                                   
                                                                             



                                                     


                                                                           































































                                                                                        


















































                                                                                                    




                                                                      
                                                                                      






































































































                                                                                          
                                                                                 














                                                                                        

                                                                

                                                                                        
                                                                                     










                                                                                  

                                                                           


























































                                                                                                     



                                                                       

                                                                                      
                                                                                     












                                                                                        

                                                                           

           

                                                                

                                                                                         
                                                                                     












                                                                                  

                                                                           





































































                                                                                     
                                                                                     




















































                                                                                                    
                                                                           





















                                                                                  
                                                                           



























                                                                                  
                                                                           






















































































































                                                                                                     






                                                                 

                                                                                   
                                                                                           















                                                                                  

                                                                           


                                                                      
                                                              






                                                                         


















































































































































































































































































































































































                                                                                     
                                                                                         
                                   
                                                    



















                                                                                  
                                                       



                                                                                
















































































                                                                                             






































































































































































































































































































































































































































































                                                                                                    


















                                                                                        





































































































































                                                                                    
%% 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).
-compile(nowarn_export_all).

-import(ct_helper, [config/2]).
-import(ct_helper, [doc/1]).
-import(cowboy_test, [gun_open/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, []},
		{"/resp/:key[/:arg]", resp_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(iolist_to_binary(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 >>} = case gen_tcp:recv(Socket, 9, 1000) of
		%% We received a WINDOW_UPDATE first. Skip it and the next.
		{ok, <<4:24, 8:8, 0:40>>} ->
			{ok, _} = gen_tcp:recv(Socket, 4 + 13, 1000),
			gen_tcp:recv(Socket, 9, 1000);
		Res ->
			Res
	end,
	{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.

%% (RFC7540 6.9.3)
%% @todo The right way to do this seems to be to wait for the SETTINGS ack
%% before we KNOW the flow control window was updated on the other side.
%   A receiver that wishes to use a smaller flow-control window than the
%   current size can send a new SETTINGS frame.  However, the receiver
%   MUST be prepared to receive data that exceeds this window size, since
%   the sender might send data that exceeds the lower limit prior to
%   processing the SETTINGS frame.

%% (RFC7540 6.10) CONTINUATION
%   CONTINUATION frames MUST be associated with a stream.  If a
%   CONTINUATION 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.
%
%   A CONTINUATION frame MUST be preceded by a HEADERS, PUSH_PROMISE or
%   CONTINUATION frame without the END_HEADERS flag set.  A recipient
%   that observes violation of this rule MUST respond with a connection
%   error (Section 5.4.1) of type PROTOCOL_ERROR.

%% (RFC7540 7) Error Codes
%   Unknown or unsupported error codes MUST NOT trigger any special
%   behavior.  These MAY be treated by an implementation as being
%   equivalent to INTERNAL_ERROR.

%% (RFC7540 8.1)
%   A HEADERS frame (and associated CONTINUATION frames) can only appear
%   at the start or end of a stream.  An endpoint that receives a HEADERS
%   frame without the END_STREAM flag set after receiving a final (non-
%   informational) status code MUST treat the corresponding request or
%   response as malformed (Section 8.1.2.6).
%
%% @todo This one is interesting to implement because Cowboy DOES this.
%   A server can
%   send a complete response prior to the client sending an entire
%   request if the response does not depend on any portion of the request
%   that has not been sent and received.  When this is true, a server MAY
%   request that the client abort transmission of a request without error
%   by sending a RST_STREAM with an error code of NO_ERROR after sending
%   a complete response (i.e., a frame with the END_STREAM flag).

headers_reject_uppercase_header_name(Config) ->
	doc("Requests containing uppercase header names must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a uppercase header name.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<"HELLO">>, <<"world">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_response_pseudo_headers(Config) ->
	doc("Requests containing response pseudo-headers must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.1, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a response pseudo-header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<":status">>, <<"200">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_unknown_pseudo_headers(Config) ->
	doc("Requests containing unknown pseudo-headers must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.1, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with an unknown pseudo-header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<":upgrade">>, <<"websocket">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

%% @todo Implement request trailers. reject_pseudo_headers_in_trailers(Config) ->
%   Pseudo-header fields MUST NOT appear in trailers.
%   Endpoints MUST treat a request or response that contains
%   undefined or invalid pseudo-header fields as malformed
%   (Section 8.1.2.6).

reject_pseudo_headers_after_regular_headers(Config) ->
	doc("Requests containing pseudo-headers after regular headers must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.1, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a pseudo-header after regular headers.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<"content-length">>, <<"0">>},
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_connection_header(Config) ->
	doc("Requests containing a connection header must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a connection header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<"connection">>, <<"close">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_keep_alive_header(Config) ->
	doc("Requests containing a keep-alive header must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a keep-alive header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<"keep-alive">>, <<"timeout=5, max=1000">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_proxy_authenticate_header(Config) ->
	doc("Requests containing a connection header must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a proxy-authenticate header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<"proxy-authenticate">>, <<"Basic">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_proxy_authorization_header(Config) ->
	doc("Requests containing a connection header must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a proxy-authorization header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<"proxy-authorization">>, <<"Basic YWxhZGRpbjpvcGVuc2VzYW1l">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_transfer_encoding_header(Config) ->
	doc("Requests containing a connection header must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a transfer-encoding header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<"transfer-encoding">>, <<"chunked">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_upgrade_header(Config) ->
	doc("Requests containing a connection header must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a upgrade header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<"upgrade">>, <<"websocket">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

accept_te_header_value_trailers(Config) ->
	doc("Requests containing a TE header with a value of \"trailers\" "
		"must be accepted. (RFC7540 8.1.2.2)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a TE header with value "trailers".
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<"te">>, <<"trailers">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a response.
	{ok, << _:24, 1:8, _:8, 1:32 >>} = gen_tcp:recv(Socket, 9, 1000),
	ok.

reject_te_header_other_values(Config) ->
	doc("Requests containing a TE header with a value other than \"trailers\" must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.2, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a TE header with a different value.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>},
		{<<"te">>, <<"trailers, deflate;q=0.5">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

%% (RFC7540 8.1.2.2)
%   This means that an intermediary transforming an HTTP/1.x message to
%   HTTP/2 will need to remove any header fields nominated by the
%   Connection header field, along with the Connection header field
%   itself.  Such intermediaries SHOULD also remove other connection-
%   specific header fields, such as Keep-Alive, Proxy-Connection,
%   Transfer-Encoding, and Upgrade, even if they are not nominated by the
%   Connection header field.

reject_userinfo(Config) ->
	doc("An authority containing a userinfo component must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with a userinfo authority component.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"user@localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<"/">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

%% (RFC7540 8.1.2.3)
%      To ensure that the HTTP/1.1 request line can be reproduced
%      accurately, this pseudo-header field MUST be omitted when
%      translating from an HTTP/1.1 request that has a request target in
%      origin or asterisk form (see [RFC7230], Section 5.3).  Clients
%      that generate HTTP/2 requests directly SHOULD use the ":authority"
%      pseudo-header field instead of the Host header field.  An
%      intermediary that converts an HTTP/2 request to HTTP/1.1 MUST
%      create a Host header field if one is not present in a request by
%      copying the value of the ":authority" pseudo-header field.

reject_empty_path(Config) ->
	doc("A request containing an empty path component must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with an empty path component.
	{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 stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_missing_pseudo_header_method(Config) ->
	doc("A request without a method component must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame without a :method pseudo-header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<>>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_many_pseudo_header_method(Config) ->
	doc("A request containing more than one method component must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with more than one :method pseudo-header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":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 stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_missing_pseudo_header_scheme(Config) ->
	doc("A request without a scheme component must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame without a :scheme pseudo-header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<>>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_many_pseudo_header_scheme(Config) ->
	doc("A request containing more than one scheme component must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with more than one :scheme pseudo-header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<>>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_missing_pseudo_header_authority(Config) ->
	doc("A request without an authority component must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame without an :authority pseudo-header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":path">>, <<>>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_many_pseudo_header_authority(Config) ->
	doc("A request containing more than one authority component must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with more than one :authority pseudo-header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<>>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_missing_pseudo_header_path(Config) ->
	doc("A request without a path component must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame without a :path pseudo-header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>} %% @todo Correct port number.
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

reject_many_pseudo_header_path(Config) ->
	doc("A request containing more than one path component must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7540 8.1.2.3, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with more than one :path pseudo-header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<>>},
		{<<":path">>, <<>>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, fin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

%% (RFC7540 8.1.2.4)
%   For HTTP/2 responses, a single ":status" pseudo-header field is
%   defined that carries the HTTP status code field (see [RFC7231],
%   Section 6).  This pseudo-header field MUST be included in all
%   responses; otherwise, the response is malformed (Section 8.1.2.6).

%% (RFC7540 8.1.2.5)
%   To allow for better compression efficiency, the Cookie header field
%   MAY be split into separate header fields, each with one or more
%   cookie-pairs.  If there are multiple Cookie header fields after
%   decompression, these MUST be concatenated into a single octet string
%   using the two-octet delimiter of 0x3B, 0x20 (the ASCII string "; ")
%   before being passed into a non-HTTP/2 context, such as an HTTP/1.1
%   connection, or a generic HTTP server application.

%% (RFC7540 8.1.2.6)
%   A request or response that includes a payload body can include a
%   content-length header field.  A request or response is also malformed
%   if the value of a content-length header field does not equal the sum
%   of the DATA frame payload lengths that form the body.  A response
%   that is defined to have no payload, as described in [RFC7230],
%   Section 3.3.2, can have a non-zero content-length header field, even
%   though no content is included in DATA frames.

reject_duplicate_content_length_header(Config) ->
	doc("A request with duplicate content-length headers must be rejected "
		"with a PROTOCOL_ERROR stream error. (RFC7230 3.3.2, RFC7540 8.1.2.6)"),
	{ok, Socket} = do_handshake(Config),
	%% Send a HEADERS frame with more than one content-length header.
	{HeadersBlock, _} = cow_hpack:encode([
		{<<":method">>, <<"GET">>},
		{<<":scheme">>, <<"http">>},
		{<<":authority">>, <<"localhost">>}, %% @todo Correct port number.
		{<<":path">>, <<>>},
		{<<"content-length">>, <<"12">>},
		{<<"content-length">>, <<"12">>}
	]),
	ok = gen_tcp:send(Socket, cow_http2:headers(1, nofin, HeadersBlock)),
	%% Receive a PROTOCOL_ERROR stream error.
	{ok, << _:24, 3:8, _:8, 1:32, 1:32 >>} = gen_tcp:recv(Socket, 13, 6000),
	ok.

%   Intermediaries that process HTTP requests or responses (i.e., any
%   intermediary not acting as a tunnel) MUST NOT forward a malformed
%   request or response.  Malformed requests or responses that are
%   detected MUST be treated as a stream error (Section 5.4.2) of type
%   PROTOCOL_ERROR.
%
%   For malformed requests, a server MAY send an HTTP response prior to
%   closing or resetting the stream.  Clients MUST NOT accept a malformed
%   response.  Note that these requirements are intended to protect
%   against several types of common attacks against HTTP; they are
%   deliberately strict because being permissive can expose
%   implementations to these vulnerabilities.

%% @todo It migh be worth reproducing the good examples. (RFC7540 8.1.3)

%% (RFC7540 8.1.4)
%   A server MUST NOT indicate that a stream has not been processed
%   unless it can guarantee that fact.  If frames that are on a stream
%   are passed to the application layer for any stream, then
%   REFUSED_STREAM MUST NOT be used for that stream, and a GOAWAY frame
%   MUST include a stream identifier that is greater than or equal to the
%   given stream identifier.

%% (RFC7540 8.2)
%   Promised requests MUST be cacheable (see [RFC7231], Section 4.2.3),
%   MUST be safe (see [RFC7231], Section 4.2.1), and MUST NOT include a
%   request body.
%
%   The server MUST include a value in the ":authority" pseudo-header
%   field for which the server is authoritative (see Section 10.1).
%
%   A client cannot push.  Thus, servers MUST treat the receipt of a
%   PUSH_PROMISE frame as a connection error (Section 5.4.1) of type
%   PROTOCOL_ERROR.

%% (RFC7540 8.2.1)
%   The header fields in PUSH_PROMISE and any subsequent CONTINUATION
%   frames MUST be a valid and complete set of request header fields
%   (Section 8.1.2.3).  The server MUST include a method in the ":method"
%   pseudo-header field that is safe and cacheable.  If a client receives
%   a PUSH_PROMISE that does not include a complete and valid set of
%   header fields or the ":method" pseudo-header field identifies a
%   method that is not safe, it MUST respond with a stream error
%   (Section 5.4.2) of type PROTOCOL_ERROR.
%
%% @todo This probably should be documented.
%   The server SHOULD send PUSH_PROMISE (Section 6.6) frames prior to
%   sending any frames that reference the promised responses.  This
%   avoids a race where clients issue requests prior to receiving any
%   PUSH_PROMISE frames.
%
%   PUSH_PROMISE frames MUST NOT be sent by the client.
%
%   PUSH_PROMISE frames can be sent by the server in response to any
%   client-initiated stream, but the stream MUST be in either the "open"
%   or "half-closed (remote)" state with respect to the server.
%   PUSH_PROMISE frames are interspersed with the frames that comprise a
%   response, though they cannot be interspersed with HEADERS and
%   CONTINUATION frames that comprise a single header block.

%% (RFC7540 8.2.2)
%   If the client determines, for any reason, that it does not wish to
%   receive the pushed response from the server or if the server takes
%   too long to begin sending the promised response, the client can send
%   a RST_STREAM frame, using either the CANCEL or REFUSED_STREAM code
%   and referencing the pushed stream's identifier.
%
%   A client can use the SETTINGS_MAX_CONCURRENT_STREAMS setting to limit
%   the number of responses that can be concurrently pushed by a server.
%   Advertising a SETTINGS_MAX_CONCURRENT_STREAMS value of zero disables
%   server push by preventing the server from creating the necessary
%   streams.  This does not prohibit a server from sending PUSH_PROMISE
%   frames; clients need to reset any promised streams that are not
%   wanted.

%% @todo Implement CONNECT. (RFC7540 8.3)

status_code_421(Config) ->
	doc("The 421 Misdirected Request status code can be sent. (RFC7540 9.1.2)"),
	ConnPid = gun_open(Config),
	Ref = gun:get(ConnPid, "/resp/reply2/421"),
	{response, fin, 421, _} = gun:await(ConnPid, Ref),
	ok.

%% @todo Review (RFC7540 9.2, 9.2.1, 9.2.2) TLS 1.2 usage.
%% We probably want different ways to enforce these to simplify the life
%% of users. A function cowboy:start_h2_tls could do the same as start_tls
%% but with the security requirements of HTTP/2 enforced. Another way is to
%% have an option at the establishment of the connection that checks that
%% the security of the connection is adequate.

%% (RFC7540 10.3)
%   The HTTP/2 header field encoding allows the expression of names that
%   are not valid field names in the Internet Message Syntax used by
%   HTTP/1.1.  Requests or responses containing invalid header field
%   names MUST be treated as malformed (Section 8.1.2.6).
%
%   Similarly, HTTP/2 allows header field values that are not valid.
%   While most of the values that can be encoded will not alter header
%   field parsing, carriage return (CR, ASCII 0xd), line feed (LF, ASCII
%   0xa), and the zero character (NUL, ASCII 0x0) might be exploited by
%   an attacker if they are translated verbatim.  Any request or response
%   that contains a character not permitted in a header field value MUST
%   be treated as malformed (Section 8.1.2.6).  Valid characters are
%   defined by the "field-content" ABNF rule in Section 3.2 of [RFC7230].

%% (RFC7540 10.5) Denial-of-Service Considerations
%   An endpoint that doesn't monitor this behavior exposes itself to a
%   risk of denial-of-service attack.  Implementations SHOULD track the
%   use of these features and set limits on their use.  An endpoint MAY
%   treat activity that is suspicious as a connection error
%   (Section 5.4.1) of type ENHANCE_YOUR_CALM.

%% (RFC7540 10.5.1)
%   A server that receives a larger header block than it is willing to
%   handle can send an HTTP 431 (Request Header Fields Too Large) status
%   code [RFC6585].  A client can discard responses that it cannot
%   process.  The header block MUST be processed to ensure a consistent
%   connection state, unless the connection is closed.

%% @todo Implement CONNECT and limit the number of CONNECT streams (RFC7540 10.5.2).

%% @todo This probably should be documented. (RFC7540 10.6)
%   Implementations communicating on a secure channel MUST NOT compress
%   content that includes both confidential and attacker-controlled data
%   unless separate compression dictionaries are used for each source of
%   data.  Compression MUST NOT be used if the source of data cannot be
%   reliably determined.  Generic stream compression, such as that
%   provided by TLS, MUST NOT be used with HTTP/2 (see Section 9.2).

%% (RFC7540 A)
%   An HTTP/2 implementation MAY treat the negotiation of any of the
%   following cipher suites with TLS 1.2 as a connection error
%   (Section 5.4.1) of type INADEQUATE_SECURITY.