changeset 876:a6abbfed42c0

Tests: split HTTP/2 tests, HTTP2 package introduced.
author Andrey Zelenkov <zelenkov@nginx.com>
date Wed, 23 Mar 2016 17:23:08 +0300
parents c380b4b7e2e4
children 8c7414010ae8
files h2.t h2_cache.t h2_headers.t h2_limit_conn.t h2_limit_req.t h2_priority.t h2_proxy_protocol.t h2_request_body.t h2_ssl.t h2_variables.t lib/Test/Nginx/HTTP2.pm
diffstat 11 files changed, 3848 insertions(+), 3261 deletions(-) [+]
line wrap: on
line diff
--- a/h2.t
+++ b/h2.t
@@ -12,27 +12,20 @@ use strict;
 
 use Test::More;
 
-use IO::Select;
 use Socket qw/ CRLF /;
 
 BEGIN { use FindBin; chdir($FindBin::Bin); }
 
 use lib 'lib';
 use Test::Nginx;
+use Test::Nginx::HTTP2 qw/ :DEFAULT :frame :io /;
 
 ###############################################################################
 
 select STDERR; $| = 1;
 select STDOUT; $| = 1;
 
-eval { require IO::Socket::SSL; };
-plan(skip_all => 'IO::Socket::SSL not installed') if $@;
-eval { IO::Socket::SSL::SSL_VERIFY_NONE(); };
-plan(skip_all => 'IO::Socket::SSL too old') if $@;
-
-my $t = Test::Nginx->new()->has(qw/http http_ssl http_v2 proxy cache/)
-	->has(qw/limit_conn rewrite realip shmem/)
-	->has_daemon('openssl')->plan(319);
+my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/)->plan(137);
 
 # Some systems may have also a bug in not treating zero writev iovcnt as EINVAL
 
@@ -50,24 +43,11 @@ events {
 http {
     %%TEST_GLOBALS_HTTP%%
 
-    proxy_cache_path %%TESTDIR%%/cache    keys_zone=NAME:1m;
-    limit_conn_zone  $binary_remote_addr  zone=conn:1m;
-    limit_req_zone   $binary_remote_addr  zone=req:1m rate=1r/s;
-
     server {
         listen       127.0.0.1:8080 http2;
         listen       127.0.0.1:8081;
-        listen       127.0.0.1:8082 proxy_protocol http2;
-        listen       127.0.0.1:8084 http2 ssl;
-        listen       127.0.0.1:8092 http2 sndbuf=128;
-        listen       127.0.0.1:8094 ssl;
         server_name  localhost;
 
-        ssl_certificate_key localhost.key;
-        ssl_certificate localhost.crt;
-        http2_max_field_size 128k;
-        http2_max_header_size 128k;
-
         location / {
             add_header X-Header X-Foo;
             add_header X-Sent-Foo $http_x_foo;
@@ -76,9 +56,6 @@ http {
         }
         location /t {
         }
-        location /t3.html {
-            limit_conn conn 1;
-        }
         location /gzip.html {
             gzip on;
             gzip_min_length 0;
@@ -86,41 +63,10 @@ http {
             alias %%TESTDIR%%/t2.html;
         }
         location /frame_size {
-            add_header X-LongHeader $arg_h;
-            add_header X-LongHeader $arg_h;
-            add_header X-LongHeader $arg_h;
             http2_chunk_size 64k;
             alias %%TESTDIR%%/t1.html;
             output_buffers 2 1m;
         }
-        location /continuation {
-            add_header X-LongHeader $arg_h;
-            add_header X-LongHeader $arg_h;
-            add_header X-LongHeader $arg_h;
-            return 200 body;
-
-            location /continuation/204 {
-                return 204;
-            }
-        }
-        location /pp {
-            set_real_ip_from 127.0.0.1/32;
-            real_ip_header proxy_protocol;
-            alias %%TESTDIR%%/t2.html;
-            add_header X-PP $remote_addr;
-        }
-        location /h2 {
-            return 200 $http2;
-        }
-        location /sp {
-            return 200 $server_protocol;
-        }
-        location /scheme {
-            return 200 $scheme;
-        }
-        location /https {
-            return 200 $https;
-        }
         location /chunk_size {
             http2_chunk_size 1;
             return 200 'body';
@@ -138,63 +84,6 @@ http {
         location /return301_relative {
             return 301 /;
         }
-        location /proxy/ {
-            add_header X-UC-a $upstream_cookie_a;
-            add_header X-UC-c $upstream_cookie_c;
-            proxy_pass http://127.0.0.1:8083/;
-            proxy_set_header X-Cookie-a $cookie_a;
-            proxy_set_header X-Cookie-c $cookie_c;
-        }
-        location /proxy2/ {
-            add_header X-Body "$request_body";
-            add_header X-Body-File $request_body_file;
-            client_body_in_file_only on;
-            proxy_pass http://127.0.0.1:8081/;
-        }
-        location /proxy_ssl/ {
-            proxy_pass https://127.0.0.1:8094/;
-        }
-        location /limit_req {
-            limit_req  zone=req burst=2;
-            alias %%TESTDIR%%/t2.html;
-        }
-        location /proxy_limit_req/ {
-            add_header X-Body $request_body;
-            add_header X-Body-File $request_body_file;
-            client_body_in_file_only on;
-            proxy_pass http://127.0.0.1:8081/;
-            limit_req  zone=req burst=2;
-        }
-        location /cache/ {
-            proxy_pass http://127.0.0.1:8081/;
-            proxy_cache NAME;
-            proxy_cache_valid 1m;
-        }
-        location /proxy_buffering_off {
-            proxy_pass http://127.0.0.1:8081/;
-            proxy_cache NAME;
-            proxy_cache_valid 1m;
-            proxy_buffering off;
-        }
-        location /client_max_body_size {
-            add_header X-Body $request_body;
-            add_header X-Body-File $request_body_file;
-            client_body_in_single_buffer on;
-            client_body_in_file_only on;
-            proxy_pass http://127.0.0.1:8081/;
-            client_max_body_size 10;
-        }
-        location /set-cookie {
-            add_header Set-Cookie a=b;
-            add_header Set-Cookie c=d;
-            return 200;
-        }
-        location /cookie {
-            add_header X-Cookie $http_cookie;
-            add_header X-Cookie-a $cookie_a;
-            add_header X-Cookie-c $cookie_c;
-            return 200;
-        }
         location /charset {
             charset utf-8;
             return 200;
@@ -221,20 +110,6 @@ http {
     }
 
     server {
-        listen       127.0.0.1:8087 http2;
-        server_name  localhost;
-
-        http2_max_field_size 22;
-    }
-
-    server {
-        listen       127.0.0.1:8088 http2;
-        server_name  localhost;
-
-        http2_max_header_size 64;
-    }
-
-    server {
         listen       127.0.0.1:8089 http2;
         server_name  localhost;
 
@@ -251,7 +126,7 @@ http {
         client_body_timeout 1s;
 
         location /proxy2/ {
-            add_header X-Body "$request_body";
+            add_header X-Body $request_body;
             proxy_pass http://127.0.0.1:8081/;
         }
     }
@@ -278,31 +153,7 @@ http {
 
 EOF
 
-$t->write_file('openssl.conf', <<EOF);
-[ req ]
-default_bits = 2048
-encrypt_key = no
-distinguished_name = req_distinguished_name
-[ req_distinguished_name ]
-EOF
-
-my $d = $t->testdir();
-
-foreach my $name ('localhost') {
-	system('openssl req -x509 -new '
-		. "-config '$d/openssl.conf' -subj '/CN=$name/' "
-		. "-out '$d/$name.crt' -keyout '$d/$name.key' "
-		. ">>$d/openssl.out 2>&1") == 0
-		or die "Can't create certificate for $name: $!\n";
-}
-
-$t->run_daemon(\&http_daemon);
-
-open OLDERR, ">&", \*STDERR; close STDERR;
 $t->run();
-open STDERR, ">&", \*OLDERR;
-
-$t->waitforsocket('127.0.0.1:8083');
 
 # file size is slightly beyond initial window size: 2**16 + 80 bytes
 
@@ -312,21 +163,6 @@ open STDERR, ">&", \*OLDERR;
 	join('', map { sprintf "XX%06dXX", $_ } (1 .. 500000)));
 
 $t->write_file('t2.html', 'SEE-THIS');
-$t->write_file('t3.html', 'SEE-THIS');
-$t->write_file('t4.html', 'SEE-THIS');
-
-my %cframe = (
-	0 => { name => 'DATA', value => \&data },
-	1 => { name => 'HEADERS', value => \&headers },
-#	2 => { name => 'PRIORITY', value => \&priority },
-	3 => { name => 'RST_STREAM', value => \&rst_stream },
-	4 => { name => 'SETTINGS', value => \&settings },
-#	5 => { name => 'PUSH_PROMISE', value => \&push_promise },
-	6 => { name => 'PING', value => \&ping },
-	7 => { name => 'GOAWAY', value => \&goaway },
-	8 => { name => 'WINDOW_UPDATE', value => \&window_update },
-	9 => { name => 'CONTINUATION', value => \&headers },
-);
 
 ###############################################################################
 
@@ -496,411 +332,6 @@ is($frame->{sid}, $sid, 'HEADERS stream 
 is($frame->{length}, length 'body', 'DATA length 2');
 is($frame->{data}, 'body', 'DATA payload 2');
 
-# various HEADERS compression/encoding, see hpack() for mode details
-
-# 6.1. Indexed Header Field Representation
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'indexed header field');
-
-# 6.2.1. Literal Header Field with Incremental Indexing
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 1, huff => 0 },
-	{ name => ':scheme', value => 'http', mode => 1, huff => 0 },
-	{ name => ':path', value => '/', mode => 1, huff => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal with indexing');
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 1, huff => 1 },
-	{ name => ':scheme', value => 'http', mode => 1, huff => 1 },
-	{ name => ':path', value => '/', mode => 1, huff => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal with indexing - huffman');
-
-# 6.2.1. Literal Header Field with Incremental Indexing -- New Name
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 2, huff => 0 },
-	{ name => ':scheme', value => 'http', mode => 2, huff => 0 },
-	{ name => ':path', value => '/', mode => 2, huff => 0 },
-	{ name => ':authority', value => 'localhost', mode => 2, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal with indexing - new');
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 2, huff => 1 },
-	{ name => ':scheme', value => 'http', mode => 2, huff => 1 },
-	{ name => ':path', value => '/', mode => 2, huff => 1 },
-	{ name => ':authority', value => 'localhost', mode => 2, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal with indexing - new huffman');
-
-# 6.2.2. Literal Header Field without Indexing
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 3, huff => 0 },
-	{ name => ':scheme', value => 'http', mode => 3, huff => 0 },
-	{ name => ':path', value => '/', mode => 3, huff => 0 },
-	{ name => ':authority', value => 'localhost', mode => 3, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal without indexing');
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 3, huff => 1 },
-	{ name => ':scheme', value => 'http', mode => 3, huff => 1 },
-	{ name => ':path', value => '/', mode => 3, huff => 1 },
-	{ name => ':authority', value => 'localhost', mode => 3, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal without indexing - huffman');
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 3, huff => 0 },
-	{ name => ':scheme', value => 'http', mode => 3, huff => 0 },
-	{ name => ':path', value => '/', mode => 3, huff => 0 },
-	{ name => ':authority', value => 'localhost', mode => 3, huff => 0 },
-	{ name => 'referer', value => 'foo', mode => 3, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200,
-	'literal without indexing - multibyte index');
-is($frame->{headers}->{'x-referer'}, 'foo',
-	'literal without indexing - multibyte index value');
-
-# 6.2.2. Literal Header Field without Indexing -- New Name
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 4, huff => 0 },
-	{ name => ':scheme', value => 'http', mode => 4, huff => 0 },
-	{ name => ':path', value => '/', mode => 4, huff => 0 },
-	{ name => ':authority', value => 'localhost', mode => 4, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal without indexing - new');
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 4, huff => 1 },
-	{ name => ':scheme', value => 'http', mode => 4, huff => 1 },
-	{ name => ':path', value => '/', mode => 4, huff => 1 },
-	{ name => ':authority', value => 'localhost', mode => 4, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200,
-	'literal without indexing - new huffman');
-
-# 6.2.3. Literal Header Field Never Indexed
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 5, huff => 0 },
-	{ name => ':scheme', value => 'http', mode => 5, huff => 0 },
-	{ name => ':path', value => '/', mode => 5, huff => 0 },
-	{ name => ':authority', value => 'localhost', mode => 5, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal never indexed');
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 5, huff => 1 },
-	{ name => ':scheme', value => 'http', mode => 5, huff => 1 },
-	{ name => ':path', value => '/', mode => 5, huff => 1 },
-	{ name => ':authority', value => 'localhost', mode => 5, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal never indexed - huffman');
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 5, huff => 0 },
-	{ name => ':scheme', value => 'http', mode => 5, huff => 0 },
-	{ name => ':path', value => '/', mode => 5, huff => 0 },
-	{ name => ':authority', value => 'localhost', mode => 5, huff => 0 },
-	{ name => 'referer', value => 'foo', mode => 5, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200,
-	'literal never indexed - multibyte index');
-is($frame->{headers}->{'x-referer'}, 'foo',
-	'literal never indexed - multibyte index value');
-
-# 6.2.3. Literal Header Field Never Indexed -- New Name
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 6, huff => 0 },
-	{ name => ':scheme', value => 'http', mode => 6, huff => 0 },
-	{ name => ':path', value => '/', mode => 6, huff => 0 },
-	{ name => ':authority', value => 'localhost', mode => 6, huff => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal never indexed - new');
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 6, huff => 1 },
-	{ name => ':scheme', value => 'http', mode => 6, huff => 1 },
-	{ name => ':path', value => '/', mode => 6, huff => 1 },
-	{ name => ':authority', value => 'localhost', mode => 6, huff => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'literal never indexed - new huffman');
-
-# reuse literal with multibyte indexing
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'referer', value => 'foo', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - new');
-
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 0 },
-	{ name => 'referer', value => 'foo', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - indexed');
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'x-foo', value => 'X-Bar', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - new');
-
-# reuse literal with multibyte indexing - reused name
-
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 0 },
-	{ name => 'x-foo', value => 'X-Bar', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - indexed');
-
-# reuse literal with multibyte indexing - reused name only
-
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 0 },
-	{ name => 'x-foo', value => 'X-Baz', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-sent-foo'}, 'X-Baz',
-	'name with indexing - indexed name');
-
-# response header field with characters not suitable for huffman encoding
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'x-foo', value => '{{{{{', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-sent-foo'}, '{{{{{', 'rare chars');
-like($sess->{headers}, qr/\Q{{{{{/, 'rare chars - no huffman encoding');
-
-# response header field with huffman encoding
-# NB: implementation detail, not obligated
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'x-foo', value => 'aaaaa', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-sent-foo'}, 'aaaaa', 'well known chars');
-
-TODO: {
-local $TODO = 'not yet' unless $t->has_version('1.9.12');
-
-unlike($sess->{headers}, qr/aaaaa/, 'well known chars - huffman encoding');
-
-}
-
-# response header field with huffman encoding - complete table mod \0, CR, LF
-# first saturate with short-encoded characters (NB: implementation detail)
-
-my $field = pack "C*", ((map { 97 } (1 .. 862)), 1 .. 9, 11, 12, 14 .. 255);
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'x-foo', value => $field, mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-sent-foo'}, $field, 'all chars');
-
-TODO: {
-local $TODO = 'not yet' unless $t->has_version('1.9.12');
-
-unlike($sess->{headers}, qr/abcde/, 'all chars - huffman encoding');
-
-}
-
-# 6.3.  Dynamic Table Size Update
-
-# remove some indexed headers from the dynamic table
-# by maintaining dynamic table space only for index 0
-# 'x-foo' has index 0, and 'referer' has index 1
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'referer', value => 'foo', mode => 1 },
-	{ name => 'x-foo', value => 'X-Bar', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-$sid = new_stream($sess, { table_size => 61, headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => 'x-foo', value => 'X-Bar', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-isnt($frame, undef, 'updated table size - remaining index');
-
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'referer', value => 'foo', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame, undef, 'invalid index');
-
-# 5.4.1.  Connection Error Handling
-#   An endpoint that encounters a connection error SHOULD first send a
-#   GOAWAY frame <..>
-
-($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
-ok($frame, 'invalid index - GOAWAY');
-
-# RFC 7541, 2.3.3.  Index Address Space
-#   Indices strictly greater than the sum of the lengths of both tables
-#   MUST be treated as a decoding error.
-
-# 4.3.  Header Compression and Decompression
-#   A decoding error in a header block MUST be treated
-#   as a connection error of type COMPRESSION_ERROR.
-
-is($frame->{last_sid}, $sid, 'invalid index - GOAWAY last stream');
-is($frame->{code}, 9, 'invalid index - GOAWAY COMPRESSION_ERROR');
-
-# HPACK zero index
-
-# RFC 7541, 6.1  Indexed Header Field Representation
-#   The index value of 0 is not used.  It MUST be treated as a decoding
-#   error if found in an indexed header field representation.
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => '', value => '', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-ok($frame, 'zero index - GOAWAY');
-is($frame->{code}, 9, 'zero index - GOAWAY COMPRESSION_ERROR');
-
-# invalid table size update
-
-$sess = new_session();
-$sid = new_stream($sess, { table_size => 4097, headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => 'x-foo', value => 'X-Bar', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
-ok($frame, 'invalid table size - GOAWAY');
-is($frame->{last_sid}, $sid, 'invalid table size - GOAWAY last stream');
-is($frame->{code}, 9, 'invalid table size - GOAWAY COMPRESSION_ERROR');
-
 # HEAD
 
 $sess = new_session();
@@ -915,17 +346,6 @@ is($frame->{headers}->{'x-header'}, 'X-F
 ($frame) = grep { $_->{type} eq "DATA" } @$frames;
 is($frame, undef, 'HEAD - no body');
 
-# GET with PROXY protocol
-
-my $proxy = 'PROXY TCP4 192.0.2.1 192.0.2.2 1234 5678' . CRLF;
-$sess = new_session(8082, proxy => $proxy);
-$sid = new_stream($sess, { path => '/pp' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-ok($frame, 'PROXY HEADERS frame');
-is($frame->{headers}->{'x-pp'}, '192.0.2.1', 'PROXY remote addr');
-
 # range filter
 
 $sess = new_session();
@@ -944,162 +364,6 @@ is($frame->{headers}->{':status'}, 206, 
 is($frame->{length}, 10, 'range - DATA length');
 is($frame->{data}, '002XXXX000', 'range - DATA payload');
 
-# $http2
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/h2' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'h2c', 'http variable - h2c');
-
-# SSL/TLS connection, NPN
-
-SKIP: {
-eval { IO::Socket::SSL->can_npn() or die; };
-skip 'OpenSSL NPN support required', 1 if $@;
-
-$sess = new_session(8084, SSL => 1, npn => 'h2');
-$sid = new_stream($sess, { path => '/h2' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'h2', 'http variable - npn');
-
-}
-
-# SSL/TLS connection, ALPN
-
-SKIP: {
-eval { IO::Socket::SSL->can_alpn() or die; };
-skip 'OpenSSL ALPN support required', 1 if $@;
-
-$sess = new_session(8084, SSL => 1, alpn => 'h2');
-$sid = new_stream($sess, { path => '/h2' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'h2', 'http variable - alpn');
-
-}
-
-# $server_protocol
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/sp' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'HTTP/2.0', 'server_protocol variable');
-
-# $server_protocol - SSL/TLS connection, NPN
-
-SKIP: {
-eval { IO::Socket::SSL->can_npn() or die; };
-skip 'OpenSSL NPN support required', 1 if $@;
-
-$sess = new_session(8084, SSL => 1, npn => 'h2');
-$sid = new_stream($sess, { path => '/sp' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'HTTP/2.0', 'server_protocol variable - npn');
-
-}
-
-# $server_protocol - SSL/TLS connection, ALPN
-
-SKIP: {
-eval { IO::Socket::SSL->can_alpn() or die; };
-skip 'OpenSSL ALPN support required', 1 if $@;
-
-$sess = new_session(8084, SSL => 1, alpn => 'h2');
-$sid = new_stream($sess, { path => '/sp' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'HTTP/2.0', 'server_protocol variable - alpn');
-
-}
-
-# $scheme
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/scheme' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'http', 'scheme variable');
-
-# $scheme - SSL/TLS connection, NPN
-
-SKIP: {
-eval { IO::Socket::SSL->can_npn() or die; };
-skip 'OpenSSL NPN support required', 1 if $@;
-
-$sess = new_session(8084, SSL => 1, npn => 'h2');
-$sid = new_stream($sess, { path => '/scheme' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'https', 'scheme variable - npn');
-
-}
-
-# $scheme - SSL/TLS connection, ALPN
-
-SKIP: {
-eval { IO::Socket::SSL->can_alpn() or die; };
-skip 'OpenSSL ALPN support required', 1 if $@;
-
-$sess = new_session(8084, SSL => 1, alpn => 'h2');
-$sid = new_stream($sess, { path => '/scheme' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'https', 'scheme variable - alpn');
-
-}
-
-# $https
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/https' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, '', 'https variable');
-
-# $https - SSL/TLS connection, NPN
-
-SKIP: {
-eval { IO::Socket::SSL->can_npn() or die; };
-skip 'OpenSSL NPN support required', 1 if $@;
-
-$sess = new_session(8084, SSL => 1, npn => 'h2');
-$sid = new_stream($sess, { path => '/https' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'on', 'https variable - npn');
-
-}
-
-# $https - SSL/TLS connection, ALPN
-
-SKIP: {
-eval { IO::Socket::SSL->can_alpn() or die; };
-skip 'OpenSSL ALPN support required', 1 if $@;
-
-$sess = new_session(8084, SSL => 1, alpn => 'h2');
-$sid = new_stream($sess, { path => '/https' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{data}, 'on', 'https variable - alpn');
-
-}
-
 # http2_chunk_size=1
 
 $sess = new_session();
@@ -1197,86 +461,6 @@ is($frame->{headers}->{':status'}, 200, 
 
 }
 
-# request header field with multiple values
-
-# 8.1.2.5.  Compressing the Cookie Header Field
-#   To allow for better compression efficiency, the Cookie header field
-#   MAY be split into separate header fields <..>.
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/cookie', mode => 2 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'cookie', value => 'a=b', mode => 2},
-	{ name => 'cookie', value => 'c=d', mode => 2}]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-cookie-a'}, 'b',
-	'multiple request header fields - cookie');
-is($frame->{headers}->{'x-cookie-c'}, 'd',
-	'multiple request header fields - cookie 2');
-is($frame->{headers}->{'x-cookie'}, 'a=b; c=d',
-	'multiple request header fields - semi-colon');
-
-# request header field with multiple values to HTTP backend
-
-# 8.1.2.5.  Compressing the Cookie Header Field
-#   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 <..>
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/proxy/cookie', mode => 2 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'cookie', value => 'a=b', mode => 2 },
-	{ name => 'cookie', value => 'c=d', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-sent-cookie'}, 'a=b; c=d',
-	'multiple request header fields proxied - semi-colon');
-is($frame->{headers}->{'x-sent-cookie2'}, '',
-	'multiple request header fields proxied - dublicate cookie');
-is($frame->{headers}->{'x-sent-cookie-a'}, 'b',
-	'multiple request header fields proxied - cookie 1');
-is($frame->{headers}->{'x-sent-cookie-c'}, 'd',
-	'multiple request header fields proxied - cookie 2');
-
-# response header field with multiple values
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/set-cookie' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'set-cookie'}[0], 'a=b',
-	'multiple response header fields - cookie');
-is($frame->{headers}->{'set-cookie'}[1], 'c=d',
-	'multiple response header fields - cookie 2');
-
-# response header field with multiple values from HTTP backend
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy/set-cookie' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'set-cookie'}[0], 'a=b',
-	'multiple response header proxied - cookie');
-is($frame->{headers}->{'set-cookie'}[1], 'c=d',
-	'multiple response header proxied - cookie 2');
-is($frame->{headers}->{'x-uc-a'}, 'b',
-	'multiple response header proxied - upstream cookie');
-is($frame->{headers}->{'x-uc-c'}, 'd',
-	'multiple response header proxied - upstream cookie 2');
-
 # internal redirect
 
 $sess = new_session();
@@ -1431,257 +615,6 @@ gunzip_like($frame->{data}, qr/^SEE-THIS
 ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
 is($frame->{headers}->{'content-type'}, 'text/plain; charset=utf-8', 'charset');
 
-# simple proxy cache test
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/cache/t4.html' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, '200', 'proxy cache');
-
-my $etag = $frame->{headers}->{'etag'};
-
-($frame) = grep { $_->{type} eq "DATA" } @$frames;
-is($frame->{length}, length 'SEE-THIS', 'proxy cache - DATA');
-is($frame->{data}, 'SEE-THIS', 'proxy cache - DATA payload');
-
-$t->write_file('t4.html', 'NOOP');
-
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/cache/t4.html' },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'if-none-match', value => $etag }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 304, 'proxy cache conditional');
-
-# HEADERS could be received with fin, followed by DATA
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/cache/t2.html?1', method => 'HEAD' });
-
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-push @$frames, $_ for @{h2_read($sess, all => [{ sid => $sid }])};
-ok(!grep ({ $_->{type} eq "DATA" } @$frames), 'proxy cache HEAD - no body');
-
-# proxy cache - expect no stray empty DATA frame
-
-TODO: {
-local $TODO = 'not yet';
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/cache/t2.html?2' });
-
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-@data = grep ({ $_->{type} eq "DATA" } @$frames);
-is(@data, 1, 'proxy cache write - data frames');
-is(join(' ', map { $_->{data} } @data), 'SEE-THIS', 'proxy cache write - data');
-is(join(' ', map { $_->{flags} } @data), '1', 'proxy cache write - flags');
-
-}
-
-# HEAD on empty cache with proxy_buffering off
-
-$sess = new_session();
-$sid = new_stream($sess,
-	{ path => '/proxy_buffering_off/t2.html?1', method => 'HEAD' });
-
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-push @$frames, $_ for @{h2_read($sess, all => [{ sid => $sid }])};
-ok(!grep ({ $_->{type} eq "DATA" } @$frames),
-	'proxy cache HEAD buffering off - no body');
-
-# request body (uses proxied response)
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy2/t2.html', body_more => 1 });
-h2_body($sess, 'TEST');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST', 'request body');
-
-# request body with padding (uses proxied response)
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy2/t2.html', body_more => 1 });
-h2_body($sess, 'TEST', { body_padding => 42 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST',
-	'request body with padding');
-
-$sid = new_stream($sess);
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, '200', 'request body with padding - next');
-
-# request body sent in multiple DATA frames in a single packet
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy2/t2.html', body_more => 1 });
-h2_body($sess, 'TEST', { body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST',
-	'request body in multiple frames');
-
-# request body sent in multiple DATA frames, each in its own packet
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy2/t2.html', body_more => 1 });
-h2_body($sess, 'TEST', { body_more => 1 });
-select undef, undef, undef, 0.1;
-h2_body($sess, 'MOREDATA');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTMOREDATA',
-	'request body in multiple frames separately');
-
-# request body with an empty DATA frame
-# "zero size buf in output" alerts seen
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy2/', body_more => 1 });
-h2_body($sess, '');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'request body - empty');
-
-TODO: {
-local $TODO = 'not yet';
-
-ok($frame->{headers}{'x-body-file'}, 'request body - empty body file');
-
-}
-
-TODO: {
-todo_skip 'empty body file', 1 unless $frame->{headers}{'x-body-file'};
-
-is(read_body_file($frame->{headers}{'x-body-file'}), '',
-	'request body - empty content');
-
-}
-
-# same as above but proxied to ssl backend
-
-TODO: {
-local $TODO = 'not yet';
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy_ssl/', body_more => 1 });
-h2_body($sess, '');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'request body - empty - proxy ssl');
-
-}
-
-# request body delayed in limit_req
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy_limit_req/', body_more => 1 });
-h2_body($sess, 'TEST');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST',
-	'request body - limit req');
-
-# request body delayed in limit_req - with an empty DATA frame
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy_limit_req/', body_more => 1 });
-h2_body($sess, '');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'request body - limit req - empty');
-
-# predict send windows
-
-$sid = new_stream($sess);
-my ($maxwin) = sort {$a <=> $b} $sess->{streams}{$sid}, $sess->{conn_window};
-
-SKIP: {
-skip 'leaves coredump', 1 unless $t->has_version('1.9.7');
-skip 'not enough window', 1 if $maxwin < 5;
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/proxy_limit_req/', body => 'TEST2' });
-select undef, undef, undef, 1.1;
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST2',
-	'request body - limit req 2');
-
-}
-
-# partial request body data frame received (to be discarded) within request
-# delayed in limit_req, the rest of data frame is received after response
-
-$sess = new_session();
-
-SKIP: {
-skip 'not enough window', 1 if $maxwin < 4;
-
-TODO: {
-todo_skip 'use-after-free', 1 unless $ENV{TEST_NGINX_UNSAFE}
-	or $t->has_version('1.9.12');
-
-$sid = new_stream($sess, { path => '/limit_req', body => 'TEST', split => [61],
-	split_delay => 1.1 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, '200', 'discard body - limit req - limited');
-
-}
-
-}
-
-$sid = new_stream($sess, { path => '/' });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, '200', 'discard body - limit req - next');
-
-# ditto, but instead of receiving the rest of data frame, connection is closed
-# 'http request already closed while closing request' alert can be produced
-
-SKIP: {
-skip 'not enough window', 1 if $maxwin < 4;
-
-TODO: {
-todo_skip 'use-after-free', 1 unless $ENV{TEST_NGINX_UNSAFE}
-	or $t->has_version('1.9.12');
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/limit_req', body => 'TEST', split => [61],
-	abort => 1 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, '200', 'discard body - limit req - eof');
-
-select undef, undef, undef, 1.1;
-undef $sess;
-
-}
-
-}
-
 # partial request header frame received (field split),
 # the rest of frame is received after client header timeout
 
@@ -1727,260 +660,6 @@ h2_ping($sess, 'SEE-THIS');
 ($frame) = grep { $_->{type} eq "PING" && $_->{flags} & 0x1 } @$frames;
 ok($frame, 'client body timeout - PING');
 
-# malformed request body length not equal to content-length
-
-$sess = new_session();
-$sid = new_stream($sess,
-	{ body_more => 1, headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/client_max_body_size', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'content-length', value => '5', mode => 1 }]});
-h2_body($sess, 'TEST');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 400, 'request body less than content-length');
-
-$sid = new_stream($sess,
-	{ body_more => 1, headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/client_max_body_size', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'content-length', value => '3', mode => 1 }]});
-h2_body($sess, 'TEST');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 400, 'request body more than content-length');
-
-# client_max_body_size
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t2.html',
-	body_more => 1 });
-h2_body($sess, 'TESTTEST12');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'client_max_body_size - status');
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
-	'client_max_body_size - body');
-
-# client_max_body_size - limited
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t2.html',
-	body_more => 1 });
-h2_body($sess, 'TESTTEST123');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 413, 'client_max_body_size - limited');
-
-# client_max_body_size - many DATA frames
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t2.html',
-	body_more => 1 });
-h2_body($sess, 'TESTTEST12', { body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'client_max_body_size many - status');
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
-	'client_max_body_size many - body');
-
-# client_max_body_size - many DATA frames - limited
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t2.html',
-	body_more => 1 });
-h2_body($sess, 'TESTTEST123', { body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 413, 'client_max_body_size many - limited');
-
-# client_max_body_size - padded DATA
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t2.html',
-	body_more => 1 });
-h2_body($sess, 'TESTTEST12', { body_padding => 42 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200, 'client_max_body_size pad - status');
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
-	'client_max_body_size pad - body');
-
-# client_max_body_size - padded DATA - limited
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t2.html',
-	body_more => 1 });
-h2_body($sess, 'TESTTEST123', { body_padding => 42 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 413, 'client_max_body_size pad - limited');
-
-# client_max_body_size - many padded DATA frames
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t2.html',
-	body_more => 1 });
-h2_body($sess, 'TESTTEST12', { body_padding => 42, body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200,
-	'client_max_body_size many pad - status');
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
-	'client_max_body_size many pad - body');
-
-# client_max_body_size - many padded DATA frames - limited
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/client_max_body_size/t2.html',
-	body_more => 1 });
-h2_body($sess, 'TESTTEST123', { body_padding => 42, body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 413,
-	'client_max_body_size many pad - limited');
-
-# request body without content-length
-
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
-	{ name => ':method', value => 'GET', mode => 2 },
-	{ name => ':scheme', value => 'http', mode => 2 },
-	{ name => ':path', value => '/client_max_body_size', mode => 2 },
-	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST12');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200,
-	'request body without content-length - status');
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
-	'request body without content-length - body');
-
-# request body without content-length - limited
-
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
-	{ name => ':method', value => 'GET', mode => 2 },
-	{ name => ':scheme', value => 'http', mode => 2 },
-	{ name => ':path', value => '/client_max_body_size', mode => 2 },
-	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST123');
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 413,
-	'request body without content-length - limited');
-
-# request body without content-length - many DATA frames
-
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
-	{ name => ':method', value => 'GET', mode => 2 },
-	{ name => ':scheme', value => 'http', mode => 2 },
-	{ name => ':path', value => '/client_max_body_size', mode => 2 },
-	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST12', { body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200,
-	'request body without content-length many - status');
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
-	'request body without content-length many - body');
-
-# request body without content-length - many DATA frames - limited
-
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
-	{ name => ':method', value => 'GET', mode => 2 },
-	{ name => ':scheme', value => 'http', mode => 2 },
-	{ name => ':path', value => '/client_max_body_size', mode => 2 },
-	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST123', { body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 413,
-	'request body without content-length many - limited');
-
-# request body without content-length - padding
-
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
-	{ name => ':method', value => 'GET', mode => 2 },
-	{ name => ':scheme', value => 'http', mode => 2 },
-	{ name => ':path', value => '/client_max_body_size', mode => 2 },
-	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST12', { body_padding => 42 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200,
-	'request body without content-length pad - status');
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
-	'request body without content-length pad - body');
-
-# request body without content-length - padding - limited
-
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
-	{ name => ':method', value => 'GET', mode => 2 },
-	{ name => ':scheme', value => 'http', mode => 2 },
-	{ name => ':path', value => '/client_max_body_size', mode => 2 },
-	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST123', { body_padding => 42 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 413,
-	'request body without content-length pad - limited');
-
-# request body without content-length - padding with many DATA frames
-
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
-	{ name => ':method', value => 'GET', mode => 2 },
-	{ name => ':scheme', value => 'http', mode => 2 },
-	{ name => ':path', value => '/client_max_body_size', mode => 2 },
-	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST', { body_padding => 42, body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 200,
-	'request body without content-length many pad - status');
-is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST',
-	'request body without content-length many pad - body');
-
-# request body without content-length - padding with many DATA frames - limited
-
-$sess = new_session();
-$sid = new_stream($sess, { body_more => 1, headers => [
-	{ name => ':method', value => 'GET', mode => 2 },
-	{ name => ':scheme', value => 'http', mode => 2 },
-	{ name => ':path', value => '/client_max_body_size', mode => 2 },
-	{ name => ':authority', value => 'localhost', mode => 2 }]});
-h2_body($sess, 'TESTTEST123', { body_padding => 42, body_split => [2] });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 413,
-	'request body without content-length many pad - limited');
 
 # proxied request with logging pristine request header field (e.g., referer)
 
@@ -2197,316 +876,6 @@ h2_window($sess, 2**18);
 @data = grep { $_->{type} eq "DATA" } @$frames;
 is($data[0]->{length}, 2**15, 'max frame size - custom');
 
-# CONTINUATION in response
-# put three long header fields (not less than SETTINGS_MAX_FRAME_SIZE/2)
-# to break header block into separate frames, one such field per frame
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**13 });
-
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
-@data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
-is(@{$data[-1]->{headers}{'x-longheader'}}, 3,
-	'response CONTINUATION - headers');
-is($data[-1]->{headers}{'x-longheader'}[0], 'x' x 2**13,
-	'response CONTINUATION - header 1');
-is($data[-1]->{headers}{'x-longheader'}[1], 'x' x 2**13,
-	'response CONTINUATION - header 2');
-is($data[-1]->{headers}{'x-longheader'}[2], 'x' x 2**13,
-	'response CONTINUATION - header 3');
-@data = sort { $a <=> $b } map { $_->{length} } @data;
-cmp_ok($data[-1], '<=', 2**14, 'response CONTINUATION - max frame size');
-
-# same but without response DATA frames
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/continuation/204?h=' . 'x' x 2**13 });
-
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
-@data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
-is(@{$data[-1]->{headers}{'x-longheader'}}, 3,
-	'no body CONTINUATION - headers');
-is($data[-1]->{headers}{'x-longheader'}[0], 'x' x 2**13,
-	'no body CONTINUATION - header 1');
-is($data[-1]->{headers}{'x-longheader'}[1], 'x' x 2**13,
-	'no body CONTINUATION - header 2');
-is($data[-1]->{headers}{'x-longheader'}[2], 'x' x 2**13,
-	'no body CONTINUATION - header 3');
-@data = sort { $a <=> $b } map { $_->{length} } @data;
-cmp_ok($data[-1], '<=', 2**14, 'no body CONTINUATION - max frame size');
-
-# response header block is always split by SETTINGS_MAX_FRAME_SIZE
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**15 });
-
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
-@data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
-@data = sort { $a <=> $b } map { $_->{length} } @data;
-cmp_ok($data[-1], '<=', 2**14, 'response header frames limited');
-
-# response header frame sent in parts
-
-TODO: {
-local $TODO = 'not yet' unless $t->has_version('1.9.7');
-
-$sess = new_session(8092);
-h2_settings($sess, 0, 0x5 => 2**17);
-
-$sid = new_stream($sess, { path => '/frame_size?h=' . 'x' x 2**15 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-ok($frame, 'response header - parts');
-
-SKIP: {
-skip 'response header failed', 1 unless $frame;
-
-is(length join('', @{$frame->{headers}->{'x-longheader'}}), 98304,
-	'response header - headers');
-
-}
-
-# response header block split and sent in parts
-
-$sess = new_session(8092);
-$sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**15 });
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
-
-@data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
-($lengths) = sort { $b <=> $a } map { $_->{length} } @data;
-cmp_ok($lengths, '<=', 16384, 'response header split - max size');
-
-is(length join('', @{@$frames[-1]->{headers}->{'x-longheader'}}), 98304,
-	'response header split - headers');
-
-}
-
-# max_field_size - header field name
-
-$sess = new_session(8087);
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-ok($frame, 'field name size less');
-
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-ok($frame, 'field name size second');
-
-$sess = new_session(8087);
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname10' x 2 . 'xx', value => 'value', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-ok($frame, 'field name size equal');
-
-$sess = new_session(8087);
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname10' x 2 . 'xxx', value => 'value', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-is($frame, undef, 'field name size greater');
-
-# max_field_size - header field value
-
-$sess = new_session(8087);
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'name', value => 'valu5' x 4 . 'x', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-ok($frame, 'field value size less');
-
-$sess = new_session(8087);
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'name', value => 'valu5' x 4 . 'xx', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-ok($frame, 'field value size equal');
-
-$sess = new_session(8087);
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'name', value => 'valu5' x 4 . 'xxx', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-is($frame, undef, 'field value size greater');
-
-# max_header_size
-
-$sess = new_session(8088);
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname9', value => 'x', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-ok($frame, 'header size less');
-
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname9', value => 'x', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-ok($frame, 'header size second');
-
-$sess = new_session(8088);
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname9', value => 'xx', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-ok($frame, 'header size equal');
-
-$sess = new_session(8088);
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname9', value => 'xxx', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-is($frame, undef, 'header size greater');
-
-# header size is based on (decompressed) header list
-# two extra 1-byte indices would otherwise fit in max_header_size
-
-$sess = new_session(8088);
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname9', value => 'x', mode => 2 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-ok($frame, 'header size new index');
-
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname9', value => 'x', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'DATA' } @$frames;
-ok($frame, 'header size indexed');
-
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/t2.html', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'longname9', value => 'x', mode => 0 },
-	{ name => 'longname9', value => 'x', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames;
-is($frame->{code}, 0xb, 'header size indexed greater');
-
-# HPACK table boundary
-
-$sess = new_session();
-h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => '', mode => 0 },
-	{ name => 'x' x 2016, value => 'x' x 2048, mode => 2 }]}), fin => 1 }]);
-$frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => '', mode => 0 },
-	{ name => 'x' x 2016, value => 'x' x 2048, mode => 0 }]}), fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-ok($frame, 'HPACK table boundary');
-
-h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => '', mode => 0 },
-	{ name => 'x' x 33, value => 'x' x 4031, mode => 2 }]}), fin => 1 }]);
-$frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => '', mode => 0 },
-	{ name => 'x' x 33, value => 'x' x 4031, mode => 0 }]}), fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-ok($frame, 'HPACK table boundary - header field name');
-
-h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => '', mode => 0 },
-	{ name => 'x', value => 'x' x 64, mode => 2 }]}), fin => 1 }]);
-$frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => '', mode => 0 },
-	{ name => 'x', value => 'x' x 64, mode => 0 }]}), fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-ok($frame, 'HPACK table boundary - header field value');
-
 # stream multiplexing + WINDOW_UPDATE
 
 $sess = new_session();
@@ -2540,436 +909,6 @@ is($sum, 81, 'multiple - stream1 remain 
 $sum = eval join '+', map { $_->{length} } @data;
 is($sum, 2**16 + 80, 'multiple - stream2 full data');
 
-# stream muliplexing + PRIORITY frames
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
-
-h2_priority($sess, 0, $sid);
-h2_priority($sess, 255, $sid2);
-
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 }
-]);
-
-@data = grep { $_->{type} eq "DATA" } @$frames;
-is(join(' ', map { $_->{sid} } @data), "$sid2 $sid", 'weight - PRIORITY 1');
-
-# and vice versa
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
-
-h2_priority($sess, 255, $sid);
-h2_priority($sess, 0, $sid2);
-
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 }
-]);
-
-@data = grep { $_->{type} eq "DATA" } @$frames;
-is(join(' ', map { $_->{sid} } @data), "$sid $sid2", 'weight - PRIORITY 2');
-
-# stream muliplexing + HEADERS PRIORITY flag
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/t1.html', prio => 0 });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t2.html', prio => 255 });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
-
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 }
-]);
-
-@data = grep { $_->{type} eq "DATA" } @$frames;
-my $sids = join ' ', map { $_->{sid} } @data;
-is($sids, "$sid2 $sid", 'weight - HEADERS PRIORITY 1');
-
-# and vice versa
-
-$sess = new_session();
-$sid = new_stream($sess, { path => '/t1.html', prio => 255 });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t2.html', prio => 0 });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
-
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 }
-]);
-
-@data = grep { $_->{type} eq "DATA" } @$frames;
-$sids = join ' ', map { $_->{sid} } @data;
-is($sids, "$sid $sid2", 'weight - HEADERS PRIORITY 2');
-
-# 5.3.1.  Stream Dependencies
-
-# PRIORITY frame
-
-$sess = new_session();
-
-h2_priority($sess, 16, 3, 0);
-h2_priority($sess, 16, 1, 3);
-
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
-
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 },
-]);
-
-@data = grep { $_->{type} eq "DATA" } @$frames;
-$sids = join ' ', map { $_->{sid} } @data;
-is($sids, "$sid2 $sid", 'dependency - PRIORITY 1');
-
-# and vice versa
-
-$sess = new_session();
-
-h2_priority($sess, 16, 1, 0);
-h2_priority($sess, 16, 3, 1);
-
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
-
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 },
-]);
-
-@data = grep { $_->{type} eq "DATA" } @$frames;
-$sids = join ' ', map { $_->{sid} } @data;
-is($sids, "$sid $sid2", 'dependency - PRIORITY 2');
-
-# PRIORITY - self dependency
-
-# 5.3.1.  Stream Dependencies
-#   A stream cannot depend on itself.  An endpoint MUST treat this as a
-#   stream error of type PROTOCOL_ERROR.
-
-$sess = new_session();
-$sid = new_stream($sess);
-h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-h2_priority($sess, 0, $sid, $sid);
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
-
-($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
-is($frame->{sid}, $sid, 'dependency - PRIORITY self - RST_STREAM');
-is($frame->{code}, 1, 'dependency - PRIORITY self - PROTOCOL_ERROR');
-
-# HEADERS PRIORITY flag, reprioritize prior PRIORITY frame records
-
-$sess = new_session();
-
-h2_priority($sess, 16, 1, 0);
-h2_priority($sess, 16, 3, 0);
-
-$sid = new_stream($sess, { path => '/t1.html', dep => 3 });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
-
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 },
-]);
-
-@data = grep { $_->{type} eq "DATA" } @$frames;
-$sids = join ' ', map { $_->{sid} } @data;
-is($sids, "$sid2 $sid", 'dependency - HEADERS PRIORITY 1');
-
-# and vice versa
-
-$sess = new_session();
-
-h2_priority($sess, 16, 1, 0);
-h2_priority($sess, 16, 3, 0);
-
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t2.html', dep => 1 });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
-
-h2_window($sess, 2**17, $sid);
-h2_window($sess, 2**17, $sid2);
-h2_window($sess, 2**17);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 },
-]);
-
-@data = grep { $_->{type} eq "DATA" } @$frames;
-$sids = join ' ', map { $_->{sid} } @data;
-is($sids, "$sid $sid2", 'dependency - HEADERS PRIORITY 2');
-
-# HEADERS - self dependency
-
-$sess = new_session();
-$sid = new_stream($sess, { dep => 1 });
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
-
-($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
-is($frame->{sid}, $sid, 'dependency - HEADERS self - RST_STREAM');
-is($frame->{code}, 1, 'dependency - HEADERS self - PROTOCOL_ERROR');
-
-# PRIORITY frame, weighted dependencies
-
-$sess = new_session();
-
-h2_priority($sess, 16, 5, 0);
-h2_priority($sess, 255, 1, 5);
-h2_priority($sess, 0, 3, 5);
-
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
-
-my $sid3 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid3, fin => 0x4 }]);
-
-h2_window($sess, 2**16, 1);
-h2_window($sess, 2**16, 3);
-h2_window($sess, 2**16, 5);
-h2_window($sess, 2**16);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 },
-	{ sid => $sid3, fin => 1 },
-]);
-
-@data = grep { $_->{type} eq "DATA" } @$frames;
-$sids = join ' ', map { $_->{sid} } @data;
-is($sids, "$sid3 $sid $sid2", 'weighted dependency - PRIORITY 1');
-
-# and vice versa
-
-$sess = new_session();
-
-h2_priority($sess, 16, 5, 0);
-h2_priority($sess, 0, 1, 5);
-h2_priority($sess, 255, 3, 5);
-
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
-
-$sid3 = new_stream($sess, { path => '/t2.html' });
-h2_read($sess, all => [{ sid => $sid3, fin => 0x4 }]);
-
-h2_window($sess, 2**16, 1);
-h2_window($sess, 2**16, 3);
-h2_window($sess, 2**16, 5);
-h2_window($sess, 2**16);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 },
-	{ sid => $sid3, fin => 1 },
-]);
-
-@data = grep { $_->{type} eq "DATA" } @$frames;
-$sids = join ' ', map { $_->{sid} } @data;
-is($sids, "$sid3 $sid2 $sid", 'weighted dependency - PRIORITY 2');
-
-# PRIORITY - reprioritization with circular dependency - after [3] removed
-# initial dependency tree:
-# 1 <- [3] <- 5
-
-$sess = new_session();
-
-h2_window($sess, 2**18);
-
-h2_priority($sess, 16, 1, 0);
-h2_priority($sess, 16, 3, 1);
-h2_priority($sess, 16, 5, 3);
-
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid2, length => 2**16 - 1 }]);
-
-$sid3 = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid3, length => 2**16 - 1 }]);
-
-h2_window($sess, 2**16, $sid2);
-
-$frames = h2_read($sess, all => [{ sid => $sid2, fin => 1 }]);
-$sids = join ' ', map { $_->{sid} } grep { $_->{type} eq "DATA" } @$frames;
-is($sids, $sid2, 'removed dependency');
-
-for (1 .. 40) {
-	h2_read($sess, all => [{ sid => new_stream($sess), fin => 1 }]);
-}
-
-# make circular dependency
-# 1 <- 5 -- current dependency tree before reprioritization
-# 5 <- 1
-# 1 <- 5
-
-h2_priority($sess, 16, 1, 5);
-h2_priority($sess, 16, 5, 1);
-
-h2_window($sess, 2**16, $sid);
-h2_window($sess, 2**16, $sid3);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid3, fin => 1 },
-]);
-
-($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid } @$frames;
-is($frame->{length}, 81, 'removed dependency - first stream');
-
-($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid3 } @$frames;
-is($frame->{length}, 81, 'removed dependency - last stream');
-
-# PRIORITY - reprioritization with circular dependency - exclusive [5]
-# 1 <- [5] <- 3
-
-$sess = new_session();
-
-h2_window($sess, 2**18);
-
-h2_priority($sess, 16, 1, 0);
-h2_priority($sess, 16, 3, 1);
-h2_priority($sess, 16, 5, 1, excl => 1);
-
-$sid = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
-
-$sid2 = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid2, length => 2**16 - 1 }]);
-
-$sid3 = new_stream($sess, { path => '/t1.html' });
-h2_read($sess, all => [{ sid => $sid3, length => 2**16 - 1 }]);
-
-h2_window($sess, 2**16, $sid);
-
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-$sids = join ' ', map { $_->{sid} } grep { $_->{type} eq "DATA" } @$frames;
-is($sids, $sid, 'exclusive dependency - parent removed');
-
-# make circular dependency
-# 5 <- 3 -- current dependency tree before reprioritization
-# 3 <- 5
-
-h2_priority($sess, 16, 5, 3);
-
-h2_window($sess, 2**16, $sid2);
-h2_window($sess, 2**16, $sid3);
-
-$frames = h2_read($sess, all => [
-	{ sid => $sid2, fin => 1 },
-	{ sid => $sid3, fin => 1 },
-]);
-
-($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid2 } @$frames;
-is($frame->{length}, 81, 'exclusive dependency - first stream');
-
-($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid3 } @$frames;
-is($frame->{length}, 81, 'exclusive dependency - last stream');
-
-# limit_conn
-
-$sess = new_session();
-h2_settings($sess, 0, 0x4 => 1);
-
-$sid = new_stream($sess, { path => '/t3.html' });
-$frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames;
-is($frame->{headers}->{':status'}, 200, 'limit_conn first stream');
-
-$sid2 = new_stream($sess, { path => '/t3.html' });
-$frames = h2_read($sess, all => [{ sid => $sid2, fin => 0 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames;
-is($frame->{headers}->{':status'}, 503, 'limit_conn rejected');
-
-h2_settings($sess, 0, 0x4 => 2**16);
-
-h2_read($sess, all => [
-	{ sid => $sid, fin => 1 },
-	{ sid => $sid2, fin => 1 }
-]);
-
-# limit_conn + client's RST_STREAM
-
-$sess = new_session();
-h2_settings($sess, 0, 0x4 => 1);
-
-$sid = new_stream($sess, { path => '/t3.html' });
-$frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]);
-h2_rst($sess, $sid, 5);
-
-($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames;
-is($frame->{headers}->{':status'}, 200, 'RST_STREAM 1');
-
-$sid2 = new_stream($sess, { path => '/t3.html' });
-$frames = h2_read($sess, all => [{ sid => $sid2, fin => 0 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames;
-is($frame->{headers}->{':status'}, 200, 'RST_STREAM 2');
-
 # http2_max_concurrent_streams
 
 $sess = new_session(8086, pure => 1);
@@ -3065,68 +1004,6 @@ is($frame->{code}, 1, 'invalid preface -
 ok($frame, 'invalid preface 2 - GOAWAY frame');
 is($frame->{code}, 1, 'invalid preface 2 - error code');
 
-# invalid PROXY protocol string
-
-$sess = new_session(8082, proxy => 'BOGUS TCP4 192.0.2.1 192.0.2.2 1234 5678',
-	pure => 1);
-$frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
-
-($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
-ok($frame, 'invalid PROXY - GOAWAY frame');
-is($frame->{code}, 1, 'invalid PROXY - error code');
-
-# ensure that request header field value with newline doesn't get split
-#
-# 10.3.  Intermediary Encapsulation Attacks
-#   Any request or response that contains a character not permitted
-#   in a header field value MUST be treated as malformed.
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/proxy2/', mode => 1 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'x-foo', value => "x-bar\r\nreferer:see-this", mode => 2 }]});
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
-
-# 10.3.  Intermediary Encapsulation Attacks
-#   An intermediary therefore cannot translate an HTTP/2 request or response
-#   containing an invalid field name into an HTTP/1.1 message.
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-isnt($frame->{headers}->{'x-referer'}, 'see-this', 'newline in request header');
-
-# 8.1.2.6.  Malformed Requests and Responses
-#   Malformed requests or responses that are detected MUST be treated
-#   as a stream error (Section 5.4.2) of type PROTOCOL_ERROR.
-
-($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
-is($frame->{sid}, $sid, 'newline in request header - RST_STREAM sid');
-is($frame->{length}, 4, 'newline in request header - RST_STREAM length');
-is($frame->{flags}, 0, 'newline in request header - RST_STREAM flags');
-is($frame->{code}, 1, 'newline in request header - RST_STREAM code');
-
-# invalid header name as seen with underscore should not lead to ignoring rest
-
-TODO: {
-local $TODO = 'not yet' unless $t->has_version('1.9.7');
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 },
-	{ name => 'x_foo', value => "x-bar", mode => 2 },
-	{ name => 'referer', value => "see-this", mode => 1 }]});
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{'x-referer'}, 'see-this', 'after invalid header name');
-
-}
-
 # GOAWAY on SYN_STREAM with even StreamID
 
 $sess = new_session();
@@ -3170,36 +1047,6 @@ ok($frame, 'dup stream - GOAWAY frame');
 is($frame->{code}, 1, 'dup stream - error code');
 is($frame->{last_sid}, $sid, 'dup stream - last stream');
 
-# missing mandatory request header ':scheme'
-
-TODO: {
-local $TODO = 'not yet';
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => 'localhost', mode => 1 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 400, 'incomplete headers');
-
-}
-
-# empty request header ':authority'
-
-$sess = new_session();
-$sid = new_stream($sess, { headers => [
-	{ name => ':method', value => 'GET', mode => 0 },
-	{ name => ':scheme', value => 'http', mode => 0 },
-	{ name => ':path', value => '/', mode => 0 },
-	{ name => ':authority', value => '', mode => 0 }]});
-$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
-
-($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
-is($frame->{headers}->{':status'}, 400, 'empty authority');
-
 # aborted stream with zero HEADERS payload followed by client connection close
 
 new_stream(new_session(), { split => [ 9 ], abort => 1 });
@@ -3214,14 +1061,6 @@ h2_ping($sess, 'SEE-THIS');
 ($frame) = grep { $_->{type} eq "PING" } @$frames;
 is($frame->{value}, 'SEE-THIS', 'unknown frame type');
 
-# client sent invalid :path header
-
-$sid = new_stream($sess, { path => 't1.html' });
-$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
-
-($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
-is($frame->{code}, 1, 'invalid path');
-
 # GOAWAY - force closing a connection by server
 
 $sid = new_stream($sess);
@@ -3262,1037 +1101,6 @@ ok($frame, 'GOAWAY on connection close')
 
 ###############################################################################
 
-sub h2_ping {
-	my ($sess, $payload) = @_;
-
-	raw_write($sess->{socket}, pack("x2C2x5a8", 8, 0x6, $payload));
-}
-
-sub h2_rst {
-	my ($sess, $stream, $error) = @_;
-
-	raw_write($sess->{socket}, pack("x2C2xNN", 4, 0x3, $stream, $error));
-}
-
-sub h2_goaway {
-	my ($sess, $stream, $lstream, $err, $debug, %extra) = @_;
-	$debug = '' unless defined $debug;
-	my $len = defined $extra{len} ? $extra{len} : 8 + length($debug);
-	my $buf = pack("x2C2xN3A*", $len, 0x7, $stream, $lstream, $err, $debug);
-
-	my @bufs = map {
-		raw_write($sess->{socket}, substr $buf, 0, $_, "");
-		select undef, undef, undef, 0.4;
-	} @{$extra{split}};
-
-	raw_write($sess->{socket}, $buf);
-}
-
-sub h2_priority {
-	my ($sess, $w, $stream, $dep, %extra) = @_;
-
-	$stream = 0 unless defined $stream;
-	$dep = 0 unless defined $dep;
-	$dep |= $extra{excl} << 31 if exists $extra{excl};
-	raw_write($sess->{socket}, pack("x2C2xNNC", 5, 0x2, $stream, $dep, $w));
-}
-
-sub h2_window {
-	my ($sess, $win, $stream) = @_;
-
-	$stream = 0 unless defined $stream;
-	raw_write($sess->{socket}, pack("x2C2xNN", 4, 0x8, $stream, $win));
-}
-
-sub h2_settings {
-	my ($sess, $ack, %extra) = @_;
-
-	my $len = 6 * keys %extra;
-	my $buf = pack_length($len) . pack "CCx4", 0x4, $ack ? 0x1 : 0x0;
-	$buf .= join '', map { pack "nN", $_, $extra{$_} } keys %extra;
-	raw_write($sess->{socket}, $buf);
-}
-
-sub h2_unknown {
-	my ($sess, $payload) = @_;
-
-	my $buf = pack_length(length($payload)) . pack("Cx5a*", 0xa, $payload);
-	raw_write($sess->{socket}, $buf);
-}
-
-sub h2_continue {
-	my ($ctx, $stream, $uri) = @_;
-
-	$uri->{h2_continue} = 1;
-	return new_stream($ctx, $uri, $stream);
-}
-
-sub h2_body {
-	my ($sess, $body, $extra) = @_;
-	$extra = {} unless defined $extra;
-
-	my $len = length $body;
-	my $sid = $sess->{last_stream};
-
-	if ($len > $sess->{conn_window} || $len > $sess->{streams}{$sid}) {
-		h2_read($sess, all => [{ type => 'WINDOW_UPDATE' }]);
-	}
-
-	if ($len > $sess->{conn_window} || $len > $sess->{streams}{$sid}) {
-		return;
-	}
-
-	$sess->{conn_window} -= $len;
-	$sess->{streams}{$sid} -= $len;
-
-	my $buf;
-
-	my $split = ref $extra->{body_split} && $extra->{body_split} || [];
-	for (@$split) {
-		$buf .= pack_body($sess, substr($body, 0, $_, ""), 0x0, $extra);
-	}
-
-	$buf .= pack_body($sess, $body, 0x1, $extra) if defined $body;
-
-	$split = ref $extra->{split} && $extra->{split} || [];
-	for (@$split) {
-		raw_write($sess->{socket}, substr($buf, 0, $_, ""));
-		return if $extra->{abort};
-		select undef, undef, undef, ($extra->{split_delay} || 0.2);
-	}
-
-	raw_write($sess->{socket}, $buf);
-}
-
-sub pack_body {
-	my ($ctx, $body, $flags, $extra) = @_;
-
-	my $pad = defined $extra->{body_padding} ? $extra->{body_padding} : 0;
-	my $padlen = defined $extra->{body_padding} ? 1 : 0;
-
-	my $buf = pack_length(length($body) + $pad + $padlen);
-	$flags |= 0x8 if $padlen;
-	vec($flags, 0, 1) = 0 if $extra->{body_more};
-	$buf .= pack 'CC', 0x0, $flags;		# DATA, END_STREAM
-	$buf .= pack 'N', $ctx->{last_stream};
-	$buf .= pack 'C', $pad if $padlen;	# DATA Pad Length?
-	$buf .= $body;
-	$buf .= pack "x$pad" if $padlen;	# DATA Padding
-	return $buf;
-}
-
-sub new_stream {
-	my ($ctx, $uri, $stream) = @_;
-	my ($input, $buf);
-	my ($d, $status);
-
-	$ctx->{headers} = '';
-
-	my $host = $uri->{host} || '127.0.0.1:8080';
-	my $method = $uri->{method} || 'GET';
-	my $scheme = $uri->{scheme} || 'http';
-	my $path = $uri->{path} || '/';
-	my $headers = $uri->{headers};
-	my $body = $uri->{body};
-	my $prio = $uri->{prio};
-	my $dep = $uri->{dep};
-
-	my $pad = defined $uri->{padding} ? $uri->{padding} : 0;
-	my $padlen = defined $uri->{padding} ? 1 : 0;
-
-	my $type = defined $uri->{h2_continue} ? 0x9 : 0x1;
-	my $flags = defined $uri->{continuation} ? 0x0 : 0x4;
-	$flags |= 0x1 unless defined $body || defined $uri->{body_more};
-	$flags |= 0x8 if $padlen;
-	$flags |= 0x20 if defined $dep || defined $prio;
-
-	if ($stream) {
-		$ctx->{last_stream} = $stream;
-	} else {
-		$ctx->{last_stream} += 2;
-		$ctx->{streams}{$ctx->{last_stream}} = $ctx->{iws};
-	}
-
-	$buf = pack("xxx");			# Length stub
-	$buf .= pack("CC", $type, $flags);	# END_HEADERS
-	$buf .= pack("N", $ctx->{last_stream});	# Stream-ID
-
-	$dep = 0 if defined $prio and not defined $dep;
-	$prio = 16 if defined $dep and not defined $prio;
-
-	unless ($headers) {
-		$input = hpack($ctx, ":method", $method);
-		$input .= hpack($ctx, ":scheme", $scheme);
-		$input .= hpack($ctx, ":path", $path);
-		$input .= hpack($ctx, ":authority", $host);
-		$input .= hpack($ctx, "content-length", length($body)) if $body;
-
-	} else {
-		$input = join '', map {
-			hpack($ctx, $_->{name}, $_->{value},
-			mode => $_->{mode}, huff => $_->{huff})
-		} @$headers if $headers;
-	}
-
-	$input = pack("B*", '001' . ipack(5, $uri->{table_size})) . $input
-		if defined $uri->{table_size};
-
-	my $split = ref $uri->{continuation} && $uri->{continuation} || [];
-	my @input = map { substr $input, 0, $_, "" } @$split;
-	push @input, $input;
-
-	# set length, attach headers, padding, priority
-
-	my $hlen = length($input[0]) + $pad + $padlen;
-	$hlen += 5 if $flags & 0x20;
-	$buf |= pack_length($hlen);
-
-	$buf .= pack 'C', $pad if $padlen;		# Pad Length?
-	$buf .= pack 'NC', $dep, $prio if $flags & 0x20;
-	$buf .= $input[0];
-	$buf .= (pack 'C', 0) x $pad if $padlen;	# Padding
-
-	shift @input;
-
-	while (@input) {
-		$input = shift @input;
-		$flags = @input ? 0x0 : 0x4;
-		$buf .= pack_length(length($input));
-		$buf .= pack("CC", 0x9, $flags);
-		$buf .= pack("N", $ctx->{last_stream});
-		$buf .= $input;
-	}
-
-	$split = ref $uri->{body_split} && $uri->{body_split} || [];
-	for (@$split) {
-		$buf .= pack_body($ctx, substr($body, 0, $_, ""), 0x0, $uri);
-	}
-
-	$buf .= pack_body($ctx, $body, 0x1, $uri) if defined $body;
-
-	$split = ref $uri->{split} && $uri->{split} || [];
-	for (@$split) {
-		raw_write($ctx->{socket}, substr($buf, 0, $_, ""));
-		goto done if $uri->{abort};
-		select undef, undef, undef, ($uri->{split_delay} || 0.2);
-	}
-
-	raw_write($ctx->{socket}, $buf);
-done:
-	return $ctx->{last_stream};
-}
-
-sub h2_read {
-	my ($sess, %extra) = @_;
-	my (@got);
-	my $s = $sess->{socket};
-	my $buf = '';
-
-	while (1) {
-		$buf = raw_read($s, $buf, 9);
-		last if length $buf < 9;
-
-		my $length = unpack_length($buf);
-		my $type = unpack('x3C', $buf);
-		my $flags = unpack('x4C', $buf);
-
-		my $stream = unpack "x5 B32", $buf;
-		substr($stream, 0, 1) = 0;
-		$stream = unpack("N", pack("B32", $stream));
-
-		$buf = raw_read($s, $buf, $length + 9);
-		last if length($buf) < $length + 9;
-
-		$buf = substr($buf, 9);
-
-		my $frame = $cframe{$type}{value}($sess, $buf, $length, $flags,
-			$stream);
-		$frame->{length} = $length;
-		$frame->{type} = $cframe{$type}{name};
-		$frame->{flags} = $flags;
-		$frame->{sid} = $stream;
-		push @got, $frame;
-
-		$buf = substr($buf, $length);
-
-		last unless $extra{all} && test_fin($got[-1], $extra{all});
-	};
-	return \@got;
-}
-
-sub test_fin {
-	my ($frame, $all) = @_;
-	my @test = @{$all};
-
-	# wait for the specified DATA length
-
-	for (@test) {
-		if ($_->{length} && $frame->{type} eq 'DATA') {
-			# check also for StreamID if needed
-
-			if (!$_->{sid} || $_->{sid} == $frame->{sid}) {
-				$_->{length} -= $frame->{length};
-			}
-		}
-	}
-	@test = grep { !(defined $_->{length} && $_->{length} == 0) } @test;
-
-	# wait for the fin flag
-
-	@test = grep { !(defined $_->{fin}
-		&& $_->{sid} == $frame->{sid} && $_->{fin} & $frame->{flags})
-	} @test if defined $frame->{flags};
-
-	# wait for the specified frame
-
-	@test = grep { !($_->{type} && $_->{type} eq $frame->{type}) } @test;
-
-	@{$all} = @test;
-}
-
-sub headers {
-	my ($ctx, $buf, $len, $flags) = @_;
-	$ctx->{headers} .= substr($buf, 0, $len);
-	return unless $flags & 0x4;
-	{ headers => hunpack($ctx, $ctx->{headers}, length($ctx->{headers})) };
-}
-
-sub data {
-	my ($ctx, $buf, $len) = @_;
-	return { data => substr($buf, 0, $len) };
-}
-
-sub settings {
-	my ($ctx, $buf, $len) = @_;
-	my %payload;
-	my $skip = 0;
-
-	for (1 .. $len / 6) {
-		my $id = hex unpack "\@$skip n", $buf; $skip += 2;
-		$payload{$id} = unpack "\@$skip N", $buf; $skip += 4;
-
-		$ctx->{iws} = $payload{$id} if $id == 4;
-	}
-	return \%payload;
-}
-
-sub ping {
-	my ($ctx, $buf, $len) = @_;
-	return { value => unpack "A$len", $buf };
-}
-
-sub rst_stream {
-	my ($ctx, $buf, $len) = @_;
-	return { code => unpack "N", $buf };
-}
-
-sub goaway {
-	my ($ctx, $buf, $len) = @_;
-	my %payload;
-
-	my $stream = unpack "B32", $buf;
-	substr($stream, 0, 1) = 0;
-	$stream = unpack("N", pack("B32", $stream));
-	$payload{last_sid} = $stream;
-
-	$len -= 4;
-	$payload{code} = unpack "x4 N", $buf;
-	$payload{debug} = unpack "x8 A$len", $buf;
-	return \%payload;
-}
-
-sub window_update {
-	my ($ctx, $buf, $len, $flags, $sid) = @_;
-	my $value = unpack "B32", $buf;
-	substr($value, 0, 1) = 0;
-	$value = unpack("N", pack("B32", $value));
-
-	unless ($sid) {
-		$ctx->{conn_window} += $value;
-
-	} else {
-		$ctx->{streams}{$sid} = $ctx->{iws}
-			unless defined $ctx->{streams}{$sid};
-		$ctx->{streams}{$sid} += $value;
-	}
-
-	return { wdelta => $value };
-}
-
-sub pack_length {
-	pack 'c3', unpack 'xc3', pack 'N', $_[0];
-}
-
-sub unpack_length {
-	unpack 'N', pack 'xc3', unpack 'c3', $_[0];
-}
-
-sub raw_read {
-	my ($s, $buf, $len) = @_;
-	my $got = '';
-
-	while (length($buf) < $len && IO::Select->new($s)->can_read(1))  {
-		$s->sysread($got, 16384) or last;
-		log_in($got);
-		$buf .= $got;
-	}
-	return $buf;
-}
-
-sub raw_write {
-	my ($s, $message) = @_;
-
-	local $SIG{PIPE} = 'IGNORE';
-
-	while (IO::Select->new($s)->can_write(0.4)) {
-		log_out($message);
-		my $n = $s->syswrite($message);
-		last unless $n;
-		$message = substr($message, $n);
-		last unless length $message;
-	}
-}
-
-sub new_session {
-	my ($port, %extra) = @_;
-
-	my $s = new_socket($port, %extra);
-	my $preface = $extra{preface}
-		|| 'PRI * HTTP/2.0' . CRLF . CRLF . 'SM' . CRLF . CRLF;
-
-	if ($extra{proxy}) {
-		raw_write($s, $extra{proxy});
-	}
-
-	# preface
-
-	raw_write($s, $preface);
-
-	my $ctx = { socket => $s, last_stream => -1,
-		dynamic_encode => [ static_table() ],
-		dynamic_decode => [ static_table() ],
-		static_table_size => scalar @{[static_table()]},
-		iws => 65535, conn_window => 65535, streams => {}};
-
-	return $ctx if $extra{pure};
-
-	# update windows, if any
-
-	h2_read($ctx, all => [
-		{ type => 'WINDOW_UPDATE' },
-		{ type => 'SETTINGS'}
-	]);
-
-	return $ctx;
-}
-
-sub new_socket {
-	my ($port, %extra) = @_;
-	my $npn = $extra{'npn'};
-	my $alpn = $extra{'alpn'};
-	my $s;
-
-	$port = 8080 unless defined $port;
-
-	eval {
-		local $SIG{ALRM} = sub { die "timeout\n" };
-		local $SIG{PIPE} = sub { die "sigpipe\n" };
-		alarm(2);
-		$s = IO::Socket::INET->new(
-			Proto => 'tcp',
-			PeerAddr => "127.0.0.1:$port",
-		);
-		IO::Socket::SSL->start_SSL($s,
-			SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
-			SSL_npn_protocols => $npn ? [ $npn ] : undef,
-			SSL_alpn_protocols => $alpn ? [ $alpn ] : undef,
-			SSL_error_trap => sub { die $_[1] }
-		) if $extra{'SSL'};
-		alarm(0);
-	};
-	alarm(0);
-
-	if ($@) {
-		log_in("died: $@");
-		return undef;
-	}
-
-	return $s;
-}
-
-sub static_table {
-	[ '',			''		], # unused
-	[ ':authority',		''		],
-	[ ':method',		'GET'		],
-	[ ':method',		'POST'		],
-	[ ':path',		'/'		],
-	[ ':path',		'/index.html'	],
-	[ ':scheme',		'http'		],
-	[ ':scheme',		'https'		],
-	[ ':status',		'200'		],
-	[ ':status',		'204'		],
-	[ ':status',		'206'		],
-	[ ':status',		'304'		],
-	[ ':status',		'400'		],
-	[ ':status',		'404'		],
-	[ ':status',		'500'		],
-	[ 'accept-charset',	''		],
-	[ 'accept-encoding',	'gzip, deflate'	],
-	[ 'accept-language',	''		],
-	[ 'accept-ranges',	''		],
-	[ 'accept',		''		],
-	[ 'access-control-allow-origin',
-				''		],
-	[ 'age',		''		],
-	[ 'allow',		''		],
-	[ 'authorization', 	''		],
-	[ 'cache-control', 	''		],
-	[ 'content-disposition',
-				''		],
-	[ 'content-encoding',	''		],
-	[ 'content-language',	''		],
-	[ 'content-length',	''		],
-	[ 'content-location',	''		],
-	[ 'content-range',	''		],
-	[ 'content-type',	''		],
-	[ 'cookie',		''		],
-	[ 'date',		''		],
-	[ 'etag',		''		],
-	[ 'expect',		''		],
-	[ 'expires',		''		],
-	[ 'from',		''		],
-	[ 'host',		''		],
-	[ 'if-match',		''		],
-	[ 'if-modified-since',	''		],
-	[ 'if-none-match',	''		],
-	[ 'if-range',		''		],
-	[ 'if-unmodified-since',
-				''		],
-	[ 'last-modified',	''		],
-	[ 'link',		''		],
-	[ 'location',		''		],
-	[ 'max-forwards',	''		],
-	[ 'proxy-authenticate',	''		],
-	[ 'proxy-authorization',
-				''		],
-	[ 'range',		''		],
-	[ 'referer',		''		],
-	[ 'refresh',		''		],
-	[ 'retry-after',	''		],
-	[ 'server',		''		],
-	[ 'set-cookie',		''		],
-	[ 'strict-transport-security',
-				''		],
-	[ 'transfer-encoding',	''		],
-	[ 'user-agent',		''		],
-	[ 'vary',		''		],
-	[ 'via',		''		],
-	[ 'www-authenticate',	''		],
-}
-
-# RFC 7541, 5.1.  Integer Representation
-
-sub ipack {
-	my ($base, $d) = @_;
-	return sprintf("%.*b", $base, $d) if $d < 2**$base - 1;
-
-	my $o = sprintf("%${base}b", 2**$base - 1);
-	$d -= 2**$base - 1;
-	while ($d >= 128) {
-		$o .= sprintf("%8b", $d % 128 + 128);
-		$d /= 128;
-	}
-	$o .= sprintf("%08b", $d);
-	return $o;
-}
-
-sub iunpack {
-	my ($base, $b, $s) = @_;
-
-	my $len = unpack("\@$s B8", $b); $s++;
-	my $prefix = substr($len, 0, 8 - $base);
-	$len = '0' x (8 - $base) . substr($len, 8 - $base);
-	$len = unpack("C", pack("B8", $len));
-
-	return ($len, $s, $prefix) if $len < 2**$base - 1;
-
-	my $m = 0;
-	my $d;
-
-	do {
-		$d = unpack("\@$s C", $b); $s++;
-		$len += ($d & 127) * 2**$m;
-		$m += $base;
-	} while (($d & 128) == 128);
-
-	return ($len, $s, $prefix);
-}
-
-sub hpack {
-	my ($ctx, $name, $value, %extra) = @_;
-	my $table = $ctx->{dynamic_encode};
-	my $mode = defined $extra{mode} ? $extra{mode} : 1;
-	my $huff = $extra{huff};
-
-	my ($index, $buf) = 0;
-
-	# 6.1.  Indexed Header Field Representation
-
-	if ($mode == 0) {
-		++$index until $index > $#$table
-			or $table->[$index][0] eq $name
-			and $table->[$index][1] eq $value;
-		$buf = pack('B*', '1' . ipack(7, $index));
-	}
-
-	# 6.2.1.  Literal Header Field with Incremental Indexing
-
-	if ($mode == 1) {
-		splice @$table, $ctx->{static_table_size}, 0, [ $name, $value ];
-
-		++$index until $index > $#$table
-			or $table->[$index][0] eq $name;
-		my $value = $huff ? huff($value) : $value;
-
-		$buf = pack('B*', '01' . ipack(6, $index)
-			. ($huff ? '1' : '0') . ipack(7, length($value)));
-		$buf .= $value;
-	}
-
-	# 6.2.1.  Literal Header Field with Incremental Indexing -- New Name
-
-	if ($mode == 2) {
-		splice @$table, $ctx->{static_table_size}, 0, [ $name, $value ];
-
-		my $name = $huff ? huff($name) : $name;
-		my $value = $huff ? huff($value) : $value;
-		my $hbit = ($huff ? '1' : '0');
-
-		$buf = pack('B*', '01000000');
-		$buf .= pack('B*', $hbit . ipack(7, length($name)));
-		$buf .= $name;
-		$buf .= pack('B*', $hbit . ipack(7, length($value)));
-		$buf .= $value;
-	}
-
-	# 6.2.2.  Literal Header Field without Indexing
-
-	if ($mode == 3) {
-		++$index until $index > $#$table
-			or $table->[$index][0] eq $name;
-		my $value = $huff ? huff($value) : $value;
-
-		$buf = pack('B*', '0000' . ipack(4, $index)
-			. ($huff ? '1' : '0') . ipack(7, length($value)));
-		$buf .= $value;
-	}
-
-	# 6.2.2.  Literal Header Field without Indexing -- New Name
-
-	if ($mode == 4) {
-		my $name = $huff ? huff($name) : $name;
-		my $value = $huff ? huff($value) : $value;
-		my $hbit = ($huff ? '1' : '0');
-
-		$buf = pack('B*', '00000000');
-		$buf .= pack('B*', $hbit . ipack(7, length($name)));
-		$buf .= $name;
-		$buf .= pack('B*', $hbit . ipack(7, length($value)));
-		$buf .= $value;
-	}
-
-	# 6.2.3.  Literal Header Field Never Indexed
-
-	if ($mode == 5) {
-		++$index until $index > $#$table
-			or $table->[$index][0] eq $name;
-		my $value = $huff ? huff($value) : $value;
-
-		$buf = pack('B*', '0001' . ipack(4, $index)
-			. ($huff ? '1' : '0') . ipack(7, length($value)));
-		$buf .= $value;
-	}
-
-	# 6.2.3.  Literal Header Field Never Indexed -- New Name
-
-	if ($mode == 6) {
-		my $name = $huff ? huff($name) : $name;
-		my $value = $huff ? huff($value) : $value;
-		my $hbit = ($huff ? '1' : '0');
-
-		$buf = pack('B*', '00010000');
-		$buf .= pack('B*', $hbit . ipack(7, length($name)));
-		$buf .= $name;
-		$buf .= pack('B*', $hbit . ipack(7, length($value)));
-		$buf .= $value;
-	}
-
-	return $buf;
-}
-
-sub hunpack {
-	my ($ctx, $data, $length) = @_;
-	my $table = $ctx->{dynamic_decode};
-	my %headers;
-	my $skip = 0;
-	my ($index, $name, $value);
-
-	my $field = sub {
-		my ($b) = @_;
-		my ($len, $s, $huff) = iunpack(7, @_);
-
-		my $field = substr($b, $s, $len);
-		$field = $huff ? dehuff($field) : $field;
-		$s += $len;
-		return ($field, $s);
-	};
-
-	my $add = sub {
-		my ($h, $n, $v) = @_;
-		return $h->{$n} = $v unless exists $h->{$n};
-		$h->{$n} = [ $h->{$n} ] unless ref $h->{$n};
-		push @{$h->{$n}}, $v;
-	};
-
-	while ($skip < $length) {
-		my $ib = unpack("\@$skip B8", $data);
-
-		if (substr($ib, 0, 1) eq '1') {
-			($index, $skip) = iunpack(7, $data, $skip);
-			$add->(\%headers,
-				$table->[$index][0], $table->[$index][1]);
-			next;
-		}
-
-		if (substr($ib, 0, 2) eq '01') {
-			($index, $skip) = iunpack(6, $data, $skip);
-			$name = $table->[$index][0];
-
-			($name, $skip) = $field->($data, $skip) unless $name;
-			($value, $skip) = $field->($data, $skip);
-
-			splice @$table,
-				$ctx->{static_table_size}, 0, [ $name, $value ];
-			$add->(\%headers, $name, $value);
-			next;
-		}
-
-		if (substr($ib, 0, 4) eq '0000') {
-			($index, $skip) = iunpack(4, $data, $skip);
-			$name = $table->[$index][0];
-
-			($name, $skip) = $field->($data, $skip) unless $name;
-			($value, $skip) = $field->($data, $skip);
-
-			$add->(\%headers, $name, $value);
-			next;
-		}
-		last;
-	}
-
-	return \%headers;
-}
-
-sub huff_code { scalar {
-	pack('C', 0)	=> '1111111111000',
-	pack('C', 1)	=> '11111111111111111011000',
-	pack('C', 2)	=> '1111111111111111111111100010',
-	pack('C', 3)	=> '1111111111111111111111100011',
-	pack('C', 4)	=> '1111111111111111111111100100',
-	pack('C', 5)	=> '1111111111111111111111100101',
-	pack('C', 6)	=> '1111111111111111111111100110',
-	pack('C', 7)	=> '1111111111111111111111100111',
-	pack('C', 8)	=> '1111111111111111111111101000',
-	pack('C', 9)	=> '111111111111111111101010',
-	pack('C', 10)	=> '111111111111111111111111111100',
-	pack('C', 11)	=> '1111111111111111111111101001',
-	pack('C', 12)	=> '1111111111111111111111101010',
-	pack('C', 13)	=> '111111111111111111111111111101',
-	pack('C', 14)	=> '1111111111111111111111101011',
-	pack('C', 15)	=> '1111111111111111111111101100',
-	pack('C', 16)	=> '1111111111111111111111101101',
-	pack('C', 17)	=> '1111111111111111111111101110',
-	pack('C', 18)	=> '1111111111111111111111101111',
-	pack('C', 19)	=> '1111111111111111111111110000',
-	pack('C', 20)	=> '1111111111111111111111110001',
-	pack('C', 21)	=> '1111111111111111111111110010',
-	pack('C', 22)	=> '111111111111111111111111111110',
-	pack('C', 23)	=> '1111111111111111111111110011',
-	pack('C', 24)	=> '1111111111111111111111110100',
-	pack('C', 25)	=> '1111111111111111111111110101',
-	pack('C', 26)	=> '1111111111111111111111110110',
-	pack('C', 27)	=> '1111111111111111111111110111',
-	pack('C', 28)	=> '1111111111111111111111111000',
-	pack('C', 29)	=> '1111111111111111111111111001',
-	pack('C', 30)	=> '1111111111111111111111111010',
-	pack('C', 31)	=> '1111111111111111111111111011',
-	pack('C', 32)	=> '010100',
-	pack('C', 33)	=> '1111111000',
-	pack('C', 34)	=> '1111111001',
-	pack('C', 35)	=> '111111111010',
-	pack('C', 36)	=> '1111111111001',
-	pack('C', 37)	=> '010101',
-	pack('C', 38)	=> '11111000',
-	pack('C', 39)	=> '11111111010',
-	pack('C', 40)	=> '1111111010',
-	pack('C', 41)	=> '1111111011',
-	pack('C', 42)	=> '11111001',
-	pack('C', 43)	=> '11111111011',
-	pack('C', 44)	=> '11111010',
-	pack('C', 45)	=> '010110',
-	pack('C', 46)	=> '010111',
-	pack('C', 47)	=> '011000',
-	pack('C', 48)	=> '00000',
-	pack('C', 49)	=> '00001',
-	pack('C', 50)	=> '00010',
-	pack('C', 51)	=> '011001',
-	pack('C', 52)	=> '011010',
-	pack('C', 53)	=> '011011',
-	pack('C', 54)	=> '011100',
-	pack('C', 55)	=> '011101',
-	pack('C', 56)	=> '011110',
-	pack('C', 57)	=> '011111',
-	pack('C', 58)	=> '1011100',
-	pack('C', 59)	=> '11111011',
-	pack('C', 60)	=> '111111111111100',
-	pack('C', 61)	=> '100000',
-	pack('C', 62)	=> '111111111011',
-	pack('C', 63)	=> '1111111100',
-	pack('C', 64)	=> '1111111111010',
-	pack('C', 65)	=> '100001',
-	pack('C', 66)	=> '1011101',
-	pack('C', 67)	=> '1011110',
-	pack('C', 68)	=> '1011111',
-	pack('C', 69)	=> '1100000',
-	pack('C', 70)	=> '1100001',
-	pack('C', 71)	=> '1100010',
-	pack('C', 72)	=> '1100011',
-	pack('C', 73)	=> '1100100',
-	pack('C', 74)	=> '1100101',
-	pack('C', 75)	=> '1100110',
-	pack('C', 76)	=> '1100111',
-	pack('C', 77)	=> '1101000',
-	pack('C', 78)	=> '1101001',
-	pack('C', 79)	=> '1101010',
-	pack('C', 80)	=> '1101011',
-	pack('C', 81)	=> '1101100',
-	pack('C', 82)	=> '1101101',
-	pack('C', 83)	=> '1101110',
-	pack('C', 84)	=> '1101111',
-	pack('C', 85)	=> '1110000',
-	pack('C', 86)	=> '1110001',
-	pack('C', 87)	=> '1110010',
-	pack('C', 88)	=> '11111100',
-	pack('C', 89)	=> '1110011',
-	pack('C', 90)	=> '11111101',
-	pack('C', 91)	=> '1111111111011',
-	pack('C', 92)	=> '1111111111111110000',
-	pack('C', 93)	=> '1111111111100',
-	pack('C', 94)	=> '11111111111100',
-	pack('C', 95)	=> '100010',
-	pack('C', 96)	=> '111111111111101',
-	pack('C', 97)	=> '00011',
-	pack('C', 98)	=> '100011',
-	pack('C', 99)	=> '00100',
-	pack('C', 100)	=> '100100',
-	pack('C', 101)	=> '00101',
-	pack('C', 102)	=> '100101',
-	pack('C', 103)	=> '100110',
-	pack('C', 104)	=> '100111',
-	pack('C', 105)	=> '00110',
-	pack('C', 106)	=> '1110100',
-	pack('C', 107)	=> '1110101',
-	pack('C', 108)	=> '101000',
-	pack('C', 109)	=> '101001',
-	pack('C', 110)	=> '101010',
-	pack('C', 111)	=> '00111',
-	pack('C', 112)	=> '101011',
-	pack('C', 113)	=> '1110110',
-	pack('C', 114)	=> '101100',
-	pack('C', 115)	=> '01000',
-	pack('C', 116)	=> '01001',
-	pack('C', 117)	=> '101101',
-	pack('C', 118)	=> '1110111',
-	pack('C', 119)	=> '1111000',
-	pack('C', 120)	=> '1111001',
-	pack('C', 121)	=> '1111010',
-	pack('C', 122)	=> '1111011',
-	pack('C', 123)	=> '111111111111110',
-	pack('C', 124)	=> '11111111100',
-	pack('C', 125)	=> '11111111111101',
-	pack('C', 126)	=> '1111111111101',
-	pack('C', 127)	=> '1111111111111111111111111100',
-	pack('C', 128)	=> '11111111111111100110',
-	pack('C', 129)	=> '1111111111111111010010',
-	pack('C', 130)	=> '11111111111111100111',
-	pack('C', 131)	=> '11111111111111101000',
-	pack('C', 132)	=> '1111111111111111010011',
-	pack('C', 133)	=> '1111111111111111010100',
-	pack('C', 134)	=> '1111111111111111010101',
-	pack('C', 135)	=> '11111111111111111011001',
-	pack('C', 136)	=> '1111111111111111010110',
-	pack('C', 137)	=> '11111111111111111011010',
-	pack('C', 138)	=> '11111111111111111011011',
-	pack('C', 139)	=> '11111111111111111011100',
-	pack('C', 140)	=> '11111111111111111011101',
-	pack('C', 141)	=> '11111111111111111011110',
-	pack('C', 142)	=> '111111111111111111101011',
-	pack('C', 143)	=> '11111111111111111011111',
-	pack('C', 144)	=> '111111111111111111101100',
-	pack('C', 145)	=> '111111111111111111101101',
-	pack('C', 146)	=> '1111111111111111010111',
-	pack('C', 147)	=> '11111111111111111100000',
-	pack('C', 148)	=> '111111111111111111101110',
-	pack('C', 149)	=> '11111111111111111100001',
-	pack('C', 150)	=> '11111111111111111100010',
-	pack('C', 151)	=> '11111111111111111100011',
-	pack('C', 152)	=> '11111111111111111100100',
-	pack('C', 153)	=> '111111111111111011100',
-	pack('C', 154)	=> '1111111111111111011000',
-	pack('C', 155)	=> '11111111111111111100101',
-	pack('C', 156)	=> '1111111111111111011001',
-	pack('C', 157)	=> '11111111111111111100110',
-	pack('C', 158)	=> '11111111111111111100111',
-	pack('C', 159)	=> '111111111111111111101111',
-	pack('C', 160)	=> '1111111111111111011010',
-	pack('C', 161)	=> '111111111111111011101',
-	pack('C', 162)	=> '11111111111111101001',
-	pack('C', 163)	=> '1111111111111111011011',
-	pack('C', 164)	=> '1111111111111111011100',
-	pack('C', 165)	=> '11111111111111111101000',
-	pack('C', 166)	=> '11111111111111111101001',
-	pack('C', 167)	=> '111111111111111011110',
-	pack('C', 168)	=> '11111111111111111101010',
-	pack('C', 169)	=> '1111111111111111011101',
-	pack('C', 170)	=> '1111111111111111011110',
-	pack('C', 171)	=> '111111111111111111110000',
-	pack('C', 172)	=> '111111111111111011111',
-	pack('C', 173)	=> '1111111111111111011111',
-	pack('C', 174)	=> '11111111111111111101011',
-	pack('C', 175)	=> '11111111111111111101100',
-	pack('C', 176)	=> '111111111111111100000',
-	pack('C', 177)	=> '111111111111111100001',
-	pack('C', 178)	=> '1111111111111111100000',
-	pack('C', 179)	=> '111111111111111100010',
-	pack('C', 180)	=> '11111111111111111101101',
-	pack('C', 181)	=> '1111111111111111100001',
-	pack('C', 182)	=> '11111111111111111101110',
-	pack('C', 183)	=> '11111111111111111101111',
-	pack('C', 184)	=> '11111111111111101010',
-	pack('C', 185)	=> '1111111111111111100010',
-	pack('C', 186)	=> '1111111111111111100011',
-	pack('C', 187)	=> '1111111111111111100100',
-	pack('C', 188)	=> '11111111111111111110000',
-	pack('C', 189)	=> '1111111111111111100101',
-	pack('C', 190)	=> '1111111111111111100110',
-	pack('C', 191)	=> '11111111111111111110001',
-	pack('C', 192)	=> '11111111111111111111100000',
-	pack('C', 193)	=> '11111111111111111111100001',
-	pack('C', 194)	=> '11111111111111101011',
-	pack('C', 195)	=> '1111111111111110001',
-	pack('C', 196)	=> '1111111111111111100111',
-	pack('C', 197)	=> '11111111111111111110010',
-	pack('C', 198)	=> '1111111111111111101000',
-	pack('C', 199)	=> '1111111111111111111101100',
-	pack('C', 200)	=> '11111111111111111111100010',
-	pack('C', 201)	=> '11111111111111111111100011',
-	pack('C', 202)	=> '11111111111111111111100100',
-	pack('C', 203)	=> '111111111111111111111011110',
-	pack('C', 204)	=> '111111111111111111111011111',
-	pack('C', 205)	=> '11111111111111111111100101',
-	pack('C', 206)	=> '111111111111111111110001',
-	pack('C', 207)	=> '1111111111111111111101101',
-	pack('C', 208)	=> '1111111111111110010',
-	pack('C', 209)	=> '111111111111111100011',
-	pack('C', 210)	=> '11111111111111111111100110',
-	pack('C', 211)	=> '111111111111111111111100000',
-	pack('C', 212)	=> '111111111111111111111100001',
-	pack('C', 213)	=> '11111111111111111111100111',
-	pack('C', 214)	=> '111111111111111111111100010',
-	pack('C', 215)	=> '111111111111111111110010',
-	pack('C', 216)	=> '111111111111111100100',
-	pack('C', 217)	=> '111111111111111100101',
-	pack('C', 218)	=> '11111111111111111111101000',
-	pack('C', 219)	=> '11111111111111111111101001',
-	pack('C', 220)	=> '1111111111111111111111111101',
-	pack('C', 221)	=> '111111111111111111111100011',
-	pack('C', 222)	=> '111111111111111111111100100',
-	pack('C', 223)	=> '111111111111111111111100101',
-	pack('C', 224)	=> '11111111111111101100',
-	pack('C', 225)	=> '111111111111111111110011',
-	pack('C', 226)	=> '11111111111111101101',
-	pack('C', 227)	=> '111111111111111100110',
-	pack('C', 228)	=> '1111111111111111101001',
-	pack('C', 229)	=> '111111111111111100111',
-	pack('C', 230)	=> '111111111111111101000',
-	pack('C', 231)	=> '11111111111111111110011',
-	pack('C', 232)	=> '1111111111111111101010',
-	pack('C', 233)	=> '1111111111111111101011',
-	pack('C', 234)	=> '1111111111111111111101110',
-	pack('C', 235)	=> '1111111111111111111101111',
-	pack('C', 236)	=> '111111111111111111110100',
-	pack('C', 237)	=> '111111111111111111110101',
-	pack('C', 238)	=> '11111111111111111111101010',
-	pack('C', 239)	=> '11111111111111111110100',
-	pack('C', 240)	=> '11111111111111111111101011',
-	pack('C', 241)	=> '111111111111111111111100110',
-	pack('C', 242)	=> '11111111111111111111101100',
-	pack('C', 243)	=> '11111111111111111111101101',
-	pack('C', 244)	=> '111111111111111111111100111',
-	pack('C', 245)	=> '111111111111111111111101000',
-	pack('C', 246)	=> '111111111111111111111101001',
-	pack('C', 247)	=> '111111111111111111111101010',
-	pack('C', 248)	=> '111111111111111111111101011',
-	pack('C', 249)	=> '1111111111111111111111111110',
-	pack('C', 250)	=> '111111111111111111111101100',
-	pack('C', 251)	=> '111111111111111111111101101',
-	pack('C', 252)	=> '111111111111111111111101110',
-	pack('C', 253)	=> '111111111111111111111101111',
-	pack('C', 254)	=> '111111111111111111111110000',
-	pack('C', 255)	=> '11111111111111111111101110',
-	'_eos'		=> '111111111111111111111111111111',
-}};
-
-sub huff {
-	my ($string) = @_;
-	my $code = &huff_code;
-
-	my $ret = join '', map { $code->{$_} } (split //, $string);
-	my $len = length($ret) + (8 - length($ret) % 8);
-	$ret .= $code->{_eos};
-
-	return pack("B$len", $ret);
-}
-
-sub dehuff {
-	my ($string) = @_;
-	my $code = &huff_code;
-	my %decode = reverse %$code;
-
-	my $ret = ''; my $c = '';
-	for (split //, unpack('B*', $string)) {
-		$c .= $_;
-		next unless exists $decode{$c};
-		last if $decode{$c} eq '_eos';
-
-		$ret .= $decode{$c};
-		$c = '';
-	}
-
-	return $ret;
-}
-
-###############################################################################
-
-sub read_body_file {
-	my ($path) = @_;
-	open FILE, $path or return "$!";
-	local $/;
-	my $content = <FILE>;
-	close FILE;
-	return $content;
-}
-
 sub gunzip_like {
 	my ($in, $re, $name) = @_;
 
@@ -4310,69 +1118,3 @@ sub gunzip_like {
 }
 
 ###############################################################################
-
-# for tests with multiple header fields
-
-sub http_daemon {
-	my $server = IO::Socket::INET->new(
-		Proto => 'tcp',
-		LocalHost => '127.0.0.1',
-		LocalPort => 8083,
-		Listen => 5,
-		Reuse => 1
-	)
-		or die "Can't create listening socket: $!\n";
-
-	local $SIG{PIPE} = 'IGNORE';
-
-	while (my $client = $server->accept()) {
-		$client->autoflush(1);
-
-		my $headers = '';
-		my $uri = '';
-
-		while (<$client>) {
-			$headers .= $_;
-			last if (/^\x0d?\x0a?$/);
-		}
-
-		next if $headers eq '';
-		$uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
-
-		if ($uri eq '/cookie') {
-
-			my ($cookie, $cookie2) = $headers =~ /Cookie: (.+)/ig;
-			$cookie2 = '' unless defined $cookie2;
-
-			my ($cookie_a, $cookie_c) = ('', '');
-			$cookie_a = $1 if $headers =~ /X-Cookie-a: (.+)/i;
-			$cookie_c = $1 if $headers =~ /X-Cookie-c: (.+)/i;
-
-			print $client <<EOF;
-HTTP/1.1 200 OK
-Connection: close
-X-Sent-Cookie: $cookie
-X-Sent-Cookie2: $cookie2
-X-Sent-Cookie-a: $cookie_a
-X-Sent-Cookie-c: $cookie_c
-
-EOF
-
-		} elsif ($uri eq '/set-cookie') {
-
-			print $client <<EOF;
-HTTP/1.1 200 OK
-Connection: close
-Set-Cookie: a=b
-Set-Cookie: c=d
-
-EOF
-
-		}
-
-	} continue {
-		close $client;
-	}
-}
-
-###############################################################################
new file mode 100644
--- /dev/null
+++ b/h2_cache.t
@@ -0,0 +1,131 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for HTTP/2 protocol with cache.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP2;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http http_v2 cache/)->plan(9)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    proxy_cache_path %%TESTDIR%%/cache    keys_zone=NAME:1m;
+
+    server {
+        listen       127.0.0.1:8080 http2;
+        listen       127.0.0.1:8081;
+        server_name  localhost;
+
+        location /cache {
+            proxy_pass http://127.0.0.1:8081/;
+            proxy_cache NAME;
+            proxy_cache_valid 1m;
+        }
+        location /proxy_buffering_off {
+            proxy_pass http://127.0.0.1:8081/;
+            proxy_cache NAME;
+            proxy_cache_valid 1m;
+            proxy_buffering off;
+        }
+    }
+}
+
+EOF
+
+$t->write_file('t.html', 'SEE-THIS');
+$t->run();
+
+###############################################################################
+
+# simple proxy cache test
+
+my $sess = new_session();
+my $sid = new_stream($sess, { path => '/cache/t.html' });
+my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, '200', 'proxy cache');
+
+my $etag = $frame->{headers}->{'etag'};
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{length}, length 'SEE-THIS', 'proxy cache - DATA');
+is($frame->{data}, 'SEE-THIS', 'proxy cache - DATA payload');
+
+$t->write_file('t.html', 'NOOP');
+
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/cache/t.html' },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'if-none-match', value => $etag }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 304, 'proxy cache conditional');
+
+# HEADERS could be received with fin, followed by DATA
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/cache/t.html?1', method => 'HEAD' });
+
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+push @$frames, $_ for @{h2_read($sess, all => [{ sid => $sid }])};
+ok(!grep ({ $_->{type} eq "DATA" } @$frames), 'proxy cache HEAD - no body');
+
+# proxy cache - expect no stray empty DATA frame
+
+TODO: {
+local $TODO = 'not yet';
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/cache/t.html?2' });
+
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+my @data = grep ({ $_->{type} eq "DATA" } @$frames);
+is(@data, 1, 'proxy cache write - data frames');
+is(join(' ', map { $_->{data} } @data), 'SEE-THIS', 'proxy cache write - data');
+is(join(' ', map { $_->{flags} } @data), '1', 'proxy cache write - flags');
+
+}
+
+# HEAD on empty cache with proxy_buffering off
+
+$sess = new_session();
+$sid = new_stream($sess,
+	{ path => '/proxy_buffering_off/t.html?1', method => 'HEAD' });
+
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+push @$frames, $_ for @{h2_read($sess, all => [{ sid => $sid }])};
+ok(!grep ({ $_->{type} eq "DATA" } @$frames),
+	'proxy cache HEAD buffering off - no body');
+
+###############################################################################
new file mode 100644
--- /dev/null
+++ b/h2_headers.t
@@ -0,0 +1,1085 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for HTTP/2 protocol with headers.
+# various HEADERS compression/encoding, see hpack() for mode details.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite/)->plan(92)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    server {
+        listen       127.0.0.1:8080 http2;
+        listen       127.0.0.1:8081;
+        listen       127.0.0.1:8082 http2 sndbuf=128;
+        server_name  localhost;
+
+        http2_max_field_size 128k;
+        http2_max_header_size 128k;
+
+        location / {
+            add_header X-Sent-Foo $http_x_foo;
+            add_header X-Referer $http_referer;
+            return 200;
+        }
+        location /frame_size {
+            add_header X-LongHeader $arg_h;
+            add_header X-LongHeader $arg_h;
+            add_header X-LongHeader $arg_h;
+            alias %%TESTDIR%%/t2.html;
+        }
+        location /continuation {
+            add_header X-LongHeader $arg_h;
+            add_header X-LongHeader $arg_h;
+            add_header X-LongHeader $arg_h;
+            return 200 body;
+
+            location /continuation/204 {
+                return 204;
+            }
+        }
+        location /proxy/ {
+            add_header X-UC-a $upstream_cookie_a;
+            add_header X-UC-c $upstream_cookie_c;
+            proxy_pass http://127.0.0.1:8083/;
+            proxy_set_header X-Cookie-a $cookie_a;
+            proxy_set_header X-Cookie-c $cookie_c;
+        }
+        location /proxy2/ {
+            proxy_pass http://127.0.0.1:8081/;
+        }
+        location /set-cookie {
+            add_header Set-Cookie a=b;
+            add_header Set-Cookie c=d;
+            return 200;
+        }
+        location /cookie {
+            add_header X-Cookie $http_cookie;
+            add_header X-Cookie-a $cookie_a;
+            add_header X-Cookie-c $cookie_c;
+            return 200;
+        }
+    }
+
+    server {
+        listen       127.0.0.1:8084 http2;
+        server_name  localhost;
+
+        http2_max_field_size 22;
+    }
+
+    server {
+        listen       127.0.0.1:8085 http2;
+        server_name  localhost;
+
+        http2_max_header_size 64;
+    }
+}
+
+EOF
+
+$t->run_daemon(\&http_daemon);
+
+open OLDERR, ">&", \*STDERR; close STDERR;
+$t->run();
+open STDERR, ">&", \*OLDERR;
+
+$t->waitforsocket('127.0.0.1:8083');
+
+# file size is slightly beyond initial window size: 2**16 + 80 bytes
+
+$t->write_file('t1.html',
+	join('', map { sprintf "X%04dXXX", $_ } (1 .. 8202)));
+
+$t->write_file('t2.html', 'SEE-THIS');
+
+###############################################################################
+
+# 6.1. Indexed Header Field Representation
+
+my $sess = new_session();
+my $sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 }]});
+my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'indexed header field');
+
+# 6.2.1. Literal Header Field with Incremental Indexing
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 1, huff => 0 },
+	{ name => ':scheme', value => 'http', mode => 1, huff => 0 },
+	{ name => ':path', value => '/', mode => 1, huff => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1, huff => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal with indexing');
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 1, huff => 1 },
+	{ name => ':scheme', value => 'http', mode => 1, huff => 1 },
+	{ name => ':path', value => '/', mode => 1, huff => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1, huff => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal with indexing - huffman');
+
+# 6.2.1. Literal Header Field with Incremental Indexing -- New Name
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 2, huff => 0 },
+	{ name => ':scheme', value => 'http', mode => 2, huff => 0 },
+	{ name => ':path', value => '/', mode => 2, huff => 0 },
+	{ name => ':authority', value => 'localhost', mode => 2, huff => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal with indexing - new');
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 2, huff => 1 },
+	{ name => ':scheme', value => 'http', mode => 2, huff => 1 },
+	{ name => ':path', value => '/', mode => 2, huff => 1 },
+	{ name => ':authority', value => 'localhost', mode => 2, huff => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal with indexing - new huffman');
+
+# 6.2.2. Literal Header Field without Indexing
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 3, huff => 0 },
+	{ name => ':scheme', value => 'http', mode => 3, huff => 0 },
+	{ name => ':path', value => '/', mode => 3, huff => 0 },
+	{ name => ':authority', value => 'localhost', mode => 3, huff => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal without indexing');
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 3, huff => 1 },
+	{ name => ':scheme', value => 'http', mode => 3, huff => 1 },
+	{ name => ':path', value => '/', mode => 3, huff => 1 },
+	{ name => ':authority', value => 'localhost', mode => 3, huff => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal without indexing - huffman');
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 3, huff => 0 },
+	{ name => ':scheme', value => 'http', mode => 3, huff => 0 },
+	{ name => ':path', value => '/', mode => 3, huff => 0 },
+	{ name => ':authority', value => 'localhost', mode => 3, huff => 0 },
+	{ name => 'referer', value => 'foo', mode => 3, huff => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200,
+	'literal without indexing - multibyte index');
+is($frame->{headers}->{'x-referer'}, 'foo',
+	'literal without indexing - multibyte index value');
+
+# 6.2.2. Literal Header Field without Indexing -- New Name
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 4, huff => 0 },
+	{ name => ':scheme', value => 'http', mode => 4, huff => 0 },
+	{ name => ':path', value => '/', mode => 4, huff => 0 },
+	{ name => ':authority', value => 'localhost', mode => 4, huff => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal without indexing - new');
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 4, huff => 1 },
+	{ name => ':scheme', value => 'http', mode => 4, huff => 1 },
+	{ name => ':path', value => '/', mode => 4, huff => 1 },
+	{ name => ':authority', value => 'localhost', mode => 4, huff => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200,
+	'literal without indexing - new huffman');
+
+# 6.2.3. Literal Header Field Never Indexed
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 5, huff => 0 },
+	{ name => ':scheme', value => 'http', mode => 5, huff => 0 },
+	{ name => ':path', value => '/', mode => 5, huff => 0 },
+	{ name => ':authority', value => 'localhost', mode => 5, huff => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal never indexed');
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 5, huff => 1 },
+	{ name => ':scheme', value => 'http', mode => 5, huff => 1 },
+	{ name => ':path', value => '/', mode => 5, huff => 1 },
+	{ name => ':authority', value => 'localhost', mode => 5, huff => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal never indexed - huffman');
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 5, huff => 0 },
+	{ name => ':scheme', value => 'http', mode => 5, huff => 0 },
+	{ name => ':path', value => '/', mode => 5, huff => 0 },
+	{ name => ':authority', value => 'localhost', mode => 5, huff => 0 },
+	{ name => 'referer', value => 'foo', mode => 5, huff => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200,
+	'literal never indexed - multibyte index');
+is($frame->{headers}->{'x-referer'}, 'foo',
+	'literal never indexed - multibyte index value');
+
+# 6.2.3. Literal Header Field Never Indexed -- New Name
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 6, huff => 0 },
+	{ name => ':scheme', value => 'http', mode => 6, huff => 0 },
+	{ name => ':path', value => '/', mode => 6, huff => 0 },
+	{ name => ':authority', value => 'localhost', mode => 6, huff => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal never indexed - new');
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 6, huff => 1 },
+	{ name => ':scheme', value => 'http', mode => 6, huff => 1 },
+	{ name => ':path', value => '/', mode => 6, huff => 1 },
+	{ name => ':authority', value => 'localhost', mode => 6, huff => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'literal never indexed - new huffman');
+
+# reuse literal with multibyte indexing
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'referer', value => 'foo', mode => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - new');
+
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 0 },
+	{ name => 'referer', value => 'foo', mode => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-referer'}, 'foo', 'value with indexing - indexed');
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'x-foo', value => 'X-Bar', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - new');
+
+# reuse literal with multibyte indexing - reused name
+
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 0 },
+	{ name => 'x-foo', value => 'X-Bar', mode => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-sent-foo'}, 'X-Bar', 'name with indexing - indexed');
+
+# reuse literal with multibyte indexing - reused name only
+
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 0 },
+	{ name => 'x-foo', value => 'X-Baz', mode => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-sent-foo'}, 'X-Baz',
+	'name with indexing - indexed name');
+
+# response header field with characters not suitable for huffman encoding
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'x-foo', value => '{{{{{', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-sent-foo'}, '{{{{{', 'rare chars');
+like($sess->{headers}, qr/\Q{{{{{/, 'rare chars - no huffman encoding');
+
+# response header field with huffman encoding
+# NB: implementation detail, not obligated
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'x-foo', value => 'aaaaa', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-sent-foo'}, 'aaaaa', 'well known chars');
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.9.12');
+
+unlike($sess->{headers}, qr/aaaaa/, 'well known chars - huffman encoding');
+
+}
+
+# response header field with huffman encoding - complete table mod \0, CR, LF
+# first saturate with short-encoded characters (NB: implementation detail)
+
+my $field = pack "C*", ((map { 97 } (1 .. 862)), 1 .. 9, 11, 12, 14 .. 255);
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'x-foo', value => $field, mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-sent-foo'}, $field, 'all chars');
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.9.12');
+
+unlike($sess->{headers}, qr/abcde/, 'all chars - huffman encoding');
+
+}
+
+# 6.3.  Dynamic Table Size Update
+
+# remove some indexed headers from the dynamic table
+# by maintaining dynamic table space only for index 0
+# 'x-foo' has index 0, and 'referer' has index 1
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'referer', value => 'foo', mode => 1 },
+	{ name => 'x-foo', value => 'X-Bar', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+$sid = new_stream($sess, { table_size => 61, headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => 'x-foo', value => 'X-Bar', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+isnt($frame, undef, 'updated table size - remaining index');
+
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'referer', value => 'foo', mode => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame, undef, 'invalid index');
+
+# 5.4.1.  Connection Error Handling
+#   An endpoint that encounters a connection error SHOULD first send a
+#   GOAWAY frame <..>
+
+($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
+ok($frame, 'invalid index - GOAWAY');
+
+# RFC 7541, 2.3.3.  Index Address Space
+#   Indices strictly greater than the sum of the lengths of both tables
+#   MUST be treated as a decoding error.
+
+# 4.3.  Header Compression and Decompression
+#   A decoding error in a header block MUST be treated
+#   as a connection error of type COMPRESSION_ERROR.
+
+is($frame->{last_sid}, $sid, 'invalid index - GOAWAY last stream');
+is($frame->{code}, 9, 'invalid index - GOAWAY COMPRESSION_ERROR');
+
+# HPACK zero index
+
+# RFC 7541, 6.1  Indexed Header Field Representation
+#   The index value of 0 is not used.  It MUST be treated as a decoding
+#   error if found in an indexed header field representation.
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => '', value => '', mode => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+ok($frame, 'zero index - GOAWAY');
+is($frame->{code}, 9, 'zero index - GOAWAY COMPRESSION_ERROR');
+
+# invalid table size update
+
+$sess = new_session();
+$sid = new_stream($sess, { table_size => 4097, headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => 'x-foo', value => 'X-Bar', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
+ok($frame, 'invalid table size - GOAWAY');
+is($frame->{last_sid}, $sid, 'invalid table size - GOAWAY last stream');
+is($frame->{code}, 9, 'invalid table size - GOAWAY COMPRESSION_ERROR');
+
+# request header field with multiple values
+
+# 8.1.2.5.  Compressing the Cookie Header Field
+#   To allow for better compression efficiency, the Cookie header field
+#   MAY be split into separate header fields <..>.
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/cookie', mode => 2 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'cookie', value => 'a=b', mode => 2},
+	{ name => 'cookie', value => 'c=d', mode => 2}]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-cookie-a'}, 'b',
+	'multiple request header fields - cookie');
+is($frame->{headers}->{'x-cookie-c'}, 'd',
+	'multiple request header fields - cookie 2');
+is($frame->{headers}->{'x-cookie'}, 'a=b; c=d',
+	'multiple request header fields - semi-colon');
+
+# request header field with multiple values to HTTP backend
+
+# 8.1.2.5.  Compressing the Cookie Header Field
+#   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 <..>
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/proxy/cookie', mode => 2 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'cookie', value => 'a=b', mode => 2 },
+	{ name => 'cookie', value => 'c=d', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-sent-cookie'}, 'a=b; c=d',
+	'multiple request header fields proxied - semi-colon');
+is($frame->{headers}->{'x-sent-cookie2'}, '',
+	'multiple request header fields proxied - dublicate cookie');
+is($frame->{headers}->{'x-sent-cookie-a'}, 'b',
+	'multiple request header fields proxied - cookie 1');
+is($frame->{headers}->{'x-sent-cookie-c'}, 'd',
+	'multiple request header fields proxied - cookie 2');
+
+# response header field with multiple values
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/set-cookie' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'set-cookie'}[0], 'a=b',
+	'multiple response header fields - cookie');
+is($frame->{headers}->{'set-cookie'}[1], 'c=d',
+	'multiple response header fields - cookie 2');
+
+# response header field with multiple values from HTTP backend
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/proxy/set-cookie' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'set-cookie'}[0], 'a=b',
+	'multiple response header proxied - cookie');
+is($frame->{headers}->{'set-cookie'}[1], 'c=d',
+	'multiple response header proxied - cookie 2');
+is($frame->{headers}->{'x-uc-a'}, 'b',
+	'multiple response header proxied - upstream cookie');
+is($frame->{headers}->{'x-uc-c'}, 'd',
+	'multiple response header proxied - upstream cookie 2');
+
+# CONTINUATION in response
+# put three long header fields (not less than SETTINGS_MAX_FRAME_SIZE/2)
+# to break header block into separate frames, one such field per frame
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**13 });
+
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+my @data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
+is(@{$data[-1]->{headers}{'x-longheader'}}, 3,
+	'response CONTINUATION - headers');
+is($data[-1]->{headers}{'x-longheader'}[0], 'x' x 2**13,
+	'response CONTINUATION - header 1');
+is($data[-1]->{headers}{'x-longheader'}[1], 'x' x 2**13,
+	'response CONTINUATION - header 2');
+is($data[-1]->{headers}{'x-longheader'}[2], 'x' x 2**13,
+	'response CONTINUATION - header 3');
+@data = sort { $a <=> $b } map { $_->{length} } @data;
+cmp_ok($data[-1], '<=', 2**14, 'response CONTINUATION - max frame size');
+
+# same but without response DATA frames
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/continuation/204?h=' . 'x' x 2**13 });
+
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+@data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
+is(@{$data[-1]->{headers}{'x-longheader'}}, 3,
+	'no body CONTINUATION - headers');
+is($data[-1]->{headers}{'x-longheader'}[0], 'x' x 2**13,
+	'no body CONTINUATION - header 1');
+is($data[-1]->{headers}{'x-longheader'}[1], 'x' x 2**13,
+	'no body CONTINUATION - header 2');
+is($data[-1]->{headers}{'x-longheader'}[2], 'x' x 2**13,
+	'no body CONTINUATION - header 3');
+@data = sort { $a <=> $b } map { $_->{length} } @data;
+cmp_ok($data[-1], '<=', 2**14, 'no body CONTINUATION - max frame size');
+
+# response header block is always split by SETTINGS_MAX_FRAME_SIZE
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**15 });
+
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+@data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
+@data = sort { $a <=> $b } map { $_->{length} } @data;
+cmp_ok($data[-1], '<=', 2**14, 'response header frames limited');
+
+# response header frame sent in parts
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.9.7');
+
+$sess = new_session(8082);
+h2_settings($sess, 0, 0x5 => 2**17);
+
+$sid = new_stream($sess, { path => '/frame_size?h=' . 'x' x 2**15 });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+ok($frame, 'response header - parts');
+
+SKIP: {
+skip 'response header failed', 1 unless $frame;
+
+is(length join('', @{$frame->{headers}->{'x-longheader'}}), 98304,
+	'response header - headers');
+
+}
+
+# response header block split and sent in parts
+
+$sess = new_session(8082);
+$sid = new_stream($sess, { path => '/continuation?h=' . 'x' x 2**15 });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 0x4 }]);
+
+@data = grep { $_->{type} =~ "HEADERS|CONTINUATION" } @$frames;
+my ($lengths) = sort { $b <=> $a } map { $_->{length} } @data;
+cmp_ok($lengths, '<=', 16384, 'response header split - max size');
+
+is(length join('', @{@$frames[-1]->{headers}->{'x-longheader'}}), 98304,
+	'response header split - headers');
+
+}
+
+# max_field_size - header field name
+
+$sess = new_session(8084);
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+ok($frame, 'field name size less');
+
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname10' x 2 . 'x', value => 'value', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+ok($frame, 'field name size second');
+
+$sess = new_session(8084);
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname10' x 2 . 'xx', value => 'value', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+ok($frame, 'field name size equal');
+
+$sess = new_session(8084);
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname10' x 2 . 'xxx', value => 'value', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+is($frame, undef, 'field name size greater');
+
+# max_field_size - header field value
+
+$sess = new_session(8084);
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'name', value => 'valu5' x 4 . 'x', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+ok($frame, 'field value size less');
+
+$sess = new_session(8084);
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'name', value => 'valu5' x 4 . 'xx', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+ok($frame, 'field value size equal');
+
+$sess = new_session(8084);
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'name', value => 'valu5' x 4 . 'xxx', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+is($frame, undef, 'field value size greater');
+
+# max_header_size
+
+$sess = new_session(8085);
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname9', value => 'x', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+ok($frame, 'header size less');
+
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname9', value => 'x', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+ok($frame, 'header size second');
+
+$sess = new_session(8085);
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname9', value => 'xx', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+ok($frame, 'header size equal');
+
+$sess = new_session(8085);
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname9', value => 'xxx', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+is($frame, undef, 'header size greater');
+
+# header size is based on (decompressed) header list
+# two extra 1-byte indices would otherwise fit in max_header_size
+
+$sess = new_session(8085);
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname9', value => 'x', mode => 2 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+ok($frame, 'header size new index');
+
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname9', value => 'x', mode => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'DATA' } @$frames;
+ok($frame, 'header size indexed');
+
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/t2.html', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'longname9', value => 'x', mode => 0 },
+	{ name => 'longname9', value => 'x', mode => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq 'GOAWAY' } @$frames;
+is($frame->{code}, 0xb, 'header size indexed greater');
+
+# HPACK table boundary
+
+$sess = new_session();
+h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => '', mode => 0 },
+	{ name => 'x' x 2016, value => 'x' x 2048, mode => 2 }]}), fin => 1 }]);
+$frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => '', mode => 0 },
+	{ name => 'x' x 2016, value => 'x' x 2048, mode => 0 }]}), fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+ok($frame, 'HPACK table boundary');
+
+h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => '', mode => 0 },
+	{ name => 'x' x 33, value => 'x' x 4031, mode => 2 }]}), fin => 1 }]);
+$frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => '', mode => 0 },
+	{ name => 'x' x 33, value => 'x' x 4031, mode => 0 }]}), fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+ok($frame, 'HPACK table boundary - header field name');
+
+h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => '', mode => 0 },
+	{ name => 'x', value => 'x' x 64, mode => 2 }]}), fin => 1 }]);
+$frames = h2_read($sess, all => [{ sid => new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => '', mode => 0 },
+	{ name => 'x', value => 'x' x 64, mode => 0 }]}), fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+ok($frame, 'HPACK table boundary - header field value');
+
+# ensure that request header field value with newline doesn't get split
+#
+# 10.3.  Intermediary Encapsulation Attacks
+#   Any request or response that contains a character not permitted
+#   in a header field value MUST be treated as malformed.
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/proxy2/', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'x-foo', value => "x-bar\r\nreferer:see-this", mode => 2 }]});
+$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+
+# 10.3.  Intermediary Encapsulation Attacks
+#   An intermediary therefore cannot translate an HTTP/2 request or response
+#   containing an invalid field name into an HTTP/1.1 message.
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+isnt($frame->{headers}->{'x-referer'}, 'see-this', 'newline in request header');
+
+# 8.1.2.6.  Malformed Requests and Responses
+#   Malformed requests or responses that are detected MUST be treated
+#   as a stream error (Section 5.4.2) of type PROTOCOL_ERROR.
+
+($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
+is($frame->{sid}, $sid, 'newline in request header - RST_STREAM sid');
+is($frame->{length}, 4, 'newline in request header - RST_STREAM length');
+is($frame->{flags}, 0, 'newline in request header - RST_STREAM flags');
+is($frame->{code}, 1, 'newline in request header - RST_STREAM code');
+
+# invalid header name as seen with underscore should not lead to ignoring rest
+
+TODO: {
+local $TODO = 'not yet' unless $t->has_version('1.9.7');
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'x_foo', value => "x-bar", mode => 2 },
+	{ name => 'referer', value => "see-this", mode => 1 }]});
+$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{'x-referer'}, 'see-this', 'after invalid header name');
+
+}
+
+# missing mandatory request header ':scheme'
+
+TODO: {
+local $TODO = 'not yet';
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => 'localhost', mode => 1 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 400, 'incomplete headers');
+
+}
+
+# empty request header ':authority'
+
+$sess = new_session();
+$sid = new_stream($sess, { headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/', mode => 0 },
+	{ name => ':authority', value => '', mode => 0 }]});
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 400, 'empty authority');
+
+# client sent invalid :path header
+
+$sid = new_stream($sess, { path => 't1.html' });
+$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+
+($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
+is($frame->{code}, 1, 'invalid path');
+
+###############################################################################
+
+sub read_body_file {
+	my ($path) = @_;
+	open FILE, $path or return "$!";
+	local $/;
+	my $content = <FILE>;
+	close FILE;
+	return $content;
+}
+
+###############################################################################
+
+sub http_daemon {
+	my $server = IO::Socket::INET->new(
+		Proto => 'tcp',
+		LocalHost => '127.0.0.1',
+		LocalPort => 8083,
+		Listen => 5,
+		Reuse => 1
+	)
+		or die "Can't create listening socket: $!\n";
+
+	local $SIG{PIPE} = 'IGNORE';
+
+	while (my $client = $server->accept()) {
+		$client->autoflush(1);
+
+		my $headers = '';
+		my $uri = '';
+
+		while (<$client>) {
+			$headers .= $_;
+			last if (/^\x0d?\x0a?$/);
+		}
+
+		next if $headers eq '';
+		$uri = $1 if $headers =~ /^\S+\s+([^ ]+)\s+HTTP/i;
+
+		if ($uri eq '/cookie') {
+
+			my ($cookie, $cookie2) = $headers =~ /Cookie: (.+)/ig;
+			$cookie2 = '' unless defined $cookie2;
+
+			my ($cookie_a, $cookie_c) = ('', '');
+			$cookie_a = $1 if $headers =~ /X-Cookie-a: (.+)/i;
+			$cookie_c = $1 if $headers =~ /X-Cookie-c: (.+)/i;
+
+			print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+X-Sent-Cookie: $cookie
+X-Sent-Cookie2: $cookie2
+X-Sent-Cookie-a: $cookie_a
+X-Sent-Cookie-c: $cookie_c
+
+EOF
+
+		} elsif ($uri eq '/set-cookie') {
+
+			print $client <<EOF;
+HTTP/1.1 200 OK
+Connection: close
+Set-Cookie: a=b
+Set-Cookie: c=d
+
+EOF
+
+		}
+
+	} continue {
+		close $client;
+	}
+}
+
+###############################################################################
new file mode 100644
--- /dev/null
+++ b/h2_limit_conn.t
@@ -0,0 +1,99 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for HTTP/2 protocol with limit_conn.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http http_v2 limit_conn/)->plan(4)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    limit_conn_zone  $binary_remote_addr  zone=conn:1m;
+
+    server {
+        listen       127.0.0.1:8080 http2;
+        listen       127.0.0.1:8081;
+        server_name  localhost;
+
+        location /t.html {
+            limit_conn conn 1;
+        }
+    }
+}
+
+EOF
+
+$t->write_file('t.html', 'SEE-THIS');
+$t->run();
+
+###############################################################################
+
+my $sess = new_session();
+h2_settings($sess, 0, 0x4 => 1);
+
+my $sid = new_stream($sess, { path => '/t.html' });
+my $frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]);
+
+my ($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames;
+is($frame->{headers}->{':status'}, 200, 'limit_conn first stream');
+
+my $sid2 = new_stream($sess, { path => '/t.html' });
+$frames = h2_read($sess, all => [{ sid => $sid2, fin => 0 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames;
+is($frame->{headers}->{':status'}, 503, 'limit_conn rejected');
+
+h2_settings($sess, 0, 0x4 => 2**16);
+
+h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 }
+]);
+
+# limit_conn + client's RST_STREAM
+
+$sess = new_session();
+h2_settings($sess, 0, 0x4 => 1);
+
+$sid = new_stream($sess, { path => '/t.html' });
+$frames = h2_read($sess, all => [{ sid => $sid, length => 1 }]);
+h2_rst($sess, $sid, 5);
+
+($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid } @$frames;
+is($frame->{headers}->{':status'}, 200, 'RST_STREAM 1');
+
+$sid2 = new_stream($sess, { path => '/t.html' });
+$frames = h2_read($sess, all => [{ sid => $sid2, fin => 0 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" && $_->{sid} == $sid2 } @$frames;
+is($frame->{headers}->{':status'}, 200, 'RST_STREAM 2');
+
+###############################################################################
new file mode 100644
--- /dev/null
+++ b/h2_limit_req.t
@@ -0,0 +1,181 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for HTTP/2 protocol with limit_req.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http http_v2 proxy rewrite limit_req/)
+	->plan(6);
+
+# Some systems may have also a bug in not treating zero writev iovcnt as EINVAL
+
+$t->todo_alerts();
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    limit_req_zone   $binary_remote_addr  zone=req:1m rate=1r/s;
+
+    server {
+        listen       127.0.0.1:8080 http2;
+        listen       127.0.0.1:8081;
+        server_name  localhost;
+
+        location / { }
+        location /limit_req {
+            limit_req  zone=req burst=2;
+            alias %%TESTDIR%%/t.html;
+        }
+        location /proxy_limit_req/ {
+            add_header X-Body $request_body;
+            add_header X-Body-File $request_body_file;
+            client_body_in_file_only on;
+            proxy_pass http://127.0.0.1:8081/;
+            limit_req  zone=req burst=2;
+        }
+    }
+}
+
+EOF
+
+$t->write_file('index.html', '');
+$t->write_file('t.html', 'SEE-THIS');
+$t->run();
+
+###############################################################################
+
+# request body delayed in limit_req
+
+my $sess = new_session();
+my $sid = new_stream($sess, { path => '/proxy_limit_req/', body_more => 1 });
+h2_body($sess, 'TEST');
+my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST',
+	'request body - limit req');
+
+# request body delayed in limit_req - with an empty DATA frame
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/proxy_limit_req/', body_more => 1 });
+h2_body($sess, '');
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'request body - limit req - empty');
+
+# predict send windows
+
+$sid = new_stream($sess);
+my ($maxwin) = sort {$a <=> $b} $sess->{streams}{$sid}, $sess->{conn_window};
+
+SKIP: {
+skip 'leaves coredump', 1 unless $t->has_version('1.9.7');
+skip 'not enough window', 1 if $maxwin < 5;
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/proxy_limit_req/', body => 'TEST2' });
+select undef, undef, undef, 1.1;
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST2',
+	'request body - limit req 2');
+
+}
+
+# partial request body data frame received (to be discarded) within request
+# delayed in limit_req, the rest of data frame is received after response
+
+$sess = new_session();
+
+SKIP: {
+skip 'not enough window', 1 if $maxwin < 4;
+
+TODO: {
+todo_skip 'use-after-free', 1 unless $ENV{TEST_NGINX_UNSAFE}
+	or $t->has_version('1.9.12');
+
+$sid = new_stream($sess, { path => '/limit_req', body => 'TEST', split => [61],
+	split_delay => 1.1 });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, '200', 'discard body - limit req - limited');
+
+}
+
+}
+
+$sid = new_stream($sess, { path => '/' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, '200', 'discard body - limit req - next');
+
+# ditto, but instead of receiving the rest of data frame, connection is closed
+# 'http request already closed while closing request' alert can be produced
+
+SKIP: {
+skip 'not enough window', 1 if $maxwin < 4;
+
+TODO: {
+todo_skip 'use-after-free', 1 unless $ENV{TEST_NGINX_UNSAFE}
+	or $t->has_version('1.9.12');
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/limit_req', body => 'TEST', split => [61],
+	abort => 1 });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, '200', 'discard body - limit req - eof');
+
+select undef, undef, undef, 1.1;
+undef $sess;
+
+}
+
+}
+
+###############################################################################
+
+sub read_body_file {
+	my ($path) = @_;
+	open FILE, $path or return "$!";
+	local $/;
+	my $content = <FILE>;
+	close FILE;
+	return $content;
+}
+
+###############################################################################
new file mode 100644
--- /dev/null
+++ b/h2_priority.t
@@ -0,0 +1,446 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for HTTP/2 protocol with priority.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http http_v2/)->plan(20)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    server {
+        listen       127.0.0.1:8080 http2;
+        server_name  localhost;
+    }
+}
+
+EOF
+
+$t->run();
+
+# file size is slightly beyond initial window size: 2**16 + 80 bytes
+
+$t->write_file('t1.html',
+	join('', map { sprintf "X%04dXXX", $_ } (1 .. 8202)));
+
+$t->write_file('t2.html', 'SEE-THIS');
+
+###############################################################################
+
+# stream muliplexing + PRIORITY frames
+
+my $sess = new_session();
+my $sid = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+my $sid2 = new_stream($sess, { path => '/t2.html' });
+h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+
+h2_priority($sess, 0, $sid);
+h2_priority($sess, 255, $sid2);
+
+h2_window($sess, 2**17, $sid);
+h2_window($sess, 2**17, $sid2);
+h2_window($sess, 2**17);
+
+my $frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 }
+]);
+
+my @data = grep { $_->{type} eq "DATA" } @$frames;
+is(join(' ', map { $_->{sid} } @data), "$sid2 $sid", 'weight - PRIORITY 1');
+
+# and vice versa
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t2.html' });
+h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+
+h2_priority($sess, 255, $sid);
+h2_priority($sess, 0, $sid2);
+
+h2_window($sess, 2**17, $sid);
+h2_window($sess, 2**17, $sid2);
+h2_window($sess, 2**17);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 }
+]);
+
+@data = grep { $_->{type} eq "DATA" } @$frames;
+is(join(' ', map { $_->{sid} } @data), "$sid $sid2", 'weight - PRIORITY 2');
+
+# stream muliplexing + HEADERS PRIORITY flag
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/t1.html', prio => 0 });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t2.html', prio => 255 });
+h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+
+h2_window($sess, 2**17, $sid);
+h2_window($sess, 2**17, $sid2);
+h2_window($sess, 2**17);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 }
+]);
+
+@data = grep { $_->{type} eq "DATA" } @$frames;
+my $sids = join ' ', map { $_->{sid} } @data;
+is($sids, "$sid2 $sid", 'weight - HEADERS PRIORITY 1');
+
+# and vice versa
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/t1.html', prio => 255 });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t2.html', prio => 0 });
+h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+
+h2_window($sess, 2**17, $sid);
+h2_window($sess, 2**17, $sid2);
+h2_window($sess, 2**17);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 }
+]);
+
+@data = grep { $_->{type} eq "DATA" } @$frames;
+$sids = join ' ', map { $_->{sid} } @data;
+is($sids, "$sid $sid2", 'weight - HEADERS PRIORITY 2');
+
+# 5.3.1.  Stream Dependencies
+
+# PRIORITY frame
+
+$sess = new_session();
+
+h2_priority($sess, 16, 3, 0);
+h2_priority($sess, 16, 1, 3);
+
+$sid = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t2.html' });
+h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+
+h2_window($sess, 2**17, $sid);
+h2_window($sess, 2**17, $sid2);
+h2_window($sess, 2**17);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 },
+]);
+
+@data = grep { $_->{type} eq "DATA" } @$frames;
+$sids = join ' ', map { $_->{sid} } @data;
+is($sids, "$sid2 $sid", 'dependency - PRIORITY 1');
+
+# and vice versa
+
+$sess = new_session();
+
+h2_priority($sess, 16, 1, 0);
+h2_priority($sess, 16, 3, 1);
+
+$sid = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t2.html' });
+h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+
+h2_window($sess, 2**17, $sid);
+h2_window($sess, 2**17, $sid2);
+h2_window($sess, 2**17);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 },
+]);
+
+@data = grep { $_->{type} eq "DATA" } @$frames;
+$sids = join ' ', map { $_->{sid} } @data;
+is($sids, "$sid $sid2", 'dependency - PRIORITY 2');
+
+# PRIORITY - self dependency
+
+# 5.3.1.  Stream Dependencies
+#   A stream cannot depend on itself.  An endpoint MUST treat this as a
+#   stream error of type PROTOCOL_ERROR.
+
+$sess = new_session();
+$sid = new_stream($sess);
+h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+h2_priority($sess, 0, $sid, $sid);
+$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+
+my ($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
+is($frame->{sid}, $sid, 'dependency - PRIORITY self - RST_STREAM');
+is($frame->{code}, 1, 'dependency - PRIORITY self - PROTOCOL_ERROR');
+
+# HEADERS PRIORITY flag, reprioritize prior PRIORITY frame records
+
+$sess = new_session();
+
+h2_priority($sess, 16, 1, 0);
+h2_priority($sess, 16, 3, 0);
+
+$sid = new_stream($sess, { path => '/t1.html', dep => 3 });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t2.html' });
+h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+
+h2_window($sess, 2**17, $sid);
+h2_window($sess, 2**17, $sid2);
+h2_window($sess, 2**17);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 },
+]);
+
+@data = grep { $_->{type} eq "DATA" } @$frames;
+$sids = join ' ', map { $_->{sid} } @data;
+is($sids, "$sid2 $sid", 'dependency - HEADERS PRIORITY 1');
+
+# and vice versa
+
+$sess = new_session();
+
+h2_priority($sess, 16, 1, 0);
+h2_priority($sess, 16, 3, 0);
+
+$sid = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t2.html', dep => 1 });
+h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+
+h2_window($sess, 2**17, $sid);
+h2_window($sess, 2**17, $sid2);
+h2_window($sess, 2**17);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 },
+]);
+
+@data = grep { $_->{type} eq "DATA" } @$frames;
+$sids = join ' ', map { $_->{sid} } @data;
+is($sids, "$sid $sid2", 'dependency - HEADERS PRIORITY 2');
+
+# HEADERS - self dependency
+
+$sess = new_session();
+$sid = new_stream($sess, { dep => 1 });
+$frames = h2_read($sess, all => [{ type => 'RST_STREAM' }]);
+
+($frame) = grep { $_->{type} eq "RST_STREAM" } @$frames;
+is($frame->{sid}, $sid, 'dependency - HEADERS self - RST_STREAM');
+is($frame->{code}, 1, 'dependency - HEADERS self - PROTOCOL_ERROR');
+
+# PRIORITY frame, weighted dependencies
+
+$sess = new_session();
+
+h2_priority($sess, 16, 5, 0);
+h2_priority($sess, 255, 1, 5);
+h2_priority($sess, 0, 3, 5);
+
+$sid = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t2.html' });
+h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+
+my $sid3 = new_stream($sess, { path => '/t2.html' });
+h2_read($sess, all => [{ sid => $sid3, fin => 0x4 }]);
+
+h2_window($sess, 2**16, 1);
+h2_window($sess, 2**16, 3);
+h2_window($sess, 2**16, 5);
+h2_window($sess, 2**16);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 },
+	{ sid => $sid3, fin => 1 },
+]);
+
+@data = grep { $_->{type} eq "DATA" } @$frames;
+$sids = join ' ', map { $_->{sid} } @data;
+is($sids, "$sid3 $sid $sid2", 'weighted dependency - PRIORITY 1');
+
+# and vice versa
+
+$sess = new_session();
+
+h2_priority($sess, 16, 5, 0);
+h2_priority($sess, 0, 1, 5);
+h2_priority($sess, 255, 3, 5);
+
+$sid = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t2.html' });
+h2_read($sess, all => [{ sid => $sid2, fin => 0x4 }]);
+
+$sid3 = new_stream($sess, { path => '/t2.html' });
+h2_read($sess, all => [{ sid => $sid3, fin => 0x4 }]);
+
+h2_window($sess, 2**16, 1);
+h2_window($sess, 2**16, 3);
+h2_window($sess, 2**16, 5);
+h2_window($sess, 2**16);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid2, fin => 1 },
+	{ sid => $sid3, fin => 1 },
+]);
+
+@data = grep { $_->{type} eq "DATA" } @$frames;
+$sids = join ' ', map { $_->{sid} } @data;
+is($sids, "$sid3 $sid2 $sid", 'weighted dependency - PRIORITY 2');
+
+# PRIORITY - reprioritization with circular dependency - after [3] removed
+# initial dependency tree:
+# 1 <- [3] <- 5
+
+$sess = new_session();
+
+h2_window($sess, 2**18);
+
+h2_priority($sess, 16, 1, 0);
+h2_priority($sess, 16, 3, 1);
+h2_priority($sess, 16, 5, 3);
+
+$sid = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid2, length => 2**16 - 1 }]);
+
+$sid3 = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid3, length => 2**16 - 1 }]);
+
+h2_window($sess, 2**16, $sid2);
+
+$frames = h2_read($sess, all => [{ sid => $sid2, fin => 1 }]);
+$sids = join ' ', map { $_->{sid} } grep { $_->{type} eq "DATA" } @$frames;
+is($sids, $sid2, 'removed dependency');
+
+for (1 .. 40) {
+	h2_read($sess, all => [{ sid => new_stream($sess), fin => 1 }]);
+}
+
+# make circular dependency
+# 1 <- 5 -- current dependency tree before reprioritization
+# 5 <- 1
+# 1 <- 5
+
+h2_priority($sess, 16, 1, 5);
+h2_priority($sess, 16, 5, 1);
+
+h2_window($sess, 2**16, $sid);
+h2_window($sess, 2**16, $sid3);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid, fin => 1 },
+	{ sid => $sid3, fin => 1 },
+]);
+
+($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid } @$frames;
+is($frame->{length}, 81, 'removed dependency - first stream');
+
+($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid3 } @$frames;
+is($frame->{length}, 81, 'removed dependency - last stream');
+
+# PRIORITY - reprioritization with circular dependency - exclusive [5]
+# 1 <- [5] <- 3
+
+$sess = new_session();
+
+h2_window($sess, 2**18);
+
+h2_priority($sess, 16, 1, 0);
+h2_priority($sess, 16, 3, 1);
+h2_priority($sess, 16, 5, 1, excl => 1);
+
+$sid = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid, length => 2**16 - 1 }]);
+
+$sid2 = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid2, length => 2**16 - 1 }]);
+
+$sid3 = new_stream($sess, { path => '/t1.html' });
+h2_read($sess, all => [{ sid => $sid3, length => 2**16 - 1 }]);
+
+h2_window($sess, 2**16, $sid);
+
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+$sids = join ' ', map { $_->{sid} } grep { $_->{type} eq "DATA" } @$frames;
+is($sids, $sid, 'exclusive dependency - parent removed');
+
+# make circular dependency
+# 5 <- 3 -- current dependency tree before reprioritization
+# 3 <- 5
+
+h2_priority($sess, 16, 5, 3);
+
+h2_window($sess, 2**16, $sid2);
+h2_window($sess, 2**16, $sid3);
+
+$frames = h2_read($sess, all => [
+	{ sid => $sid2, fin => 1 },
+	{ sid => $sid3, fin => 1 },
+]);
+
+($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid2 } @$frames;
+is($frame->{length}, 81, 'exclusive dependency - first stream');
+
+($frame) = grep { $_->{type} eq "DATA" && $_->{sid} == $sid3 } @$frames;
+is($frame->{length}, 81, 'exclusive dependency - last stream');
+
+###############################################################################
new file mode 100644
--- /dev/null
+++ b/h2_proxy_protocol.t
@@ -0,0 +1,81 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for HTTP/2 protocol with proxy_protocol.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+use Socket qw/ CRLF /;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP2;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http http_v2 realip/)->plan(4)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    server {
+        listen       127.0.0.1:8080 http2;
+        listen       127.0.0.1:8081 proxy_protocol http2;
+        server_name  localhost;
+
+        location /pp {
+            set_real_ip_from 127.0.0.1/32;
+            real_ip_header proxy_protocol;
+            alias %%TESTDIR%%/t.html;
+            add_header X-PP $remote_addr;
+        }
+    }
+}
+
+EOF
+
+$t->write_file('t.html', 'SEE-THIS');
+$t->run();
+
+###############################################################################
+
+my $proxy = 'PROXY TCP4 192.0.2.1 192.0.2.2 1234 5678' . CRLF;
+my $sess = new_session(8081, proxy => $proxy);
+my $sid = new_stream($sess, { path => '/pp' });
+my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+ok($frame, 'PROXY HEADERS frame');
+is($frame->{headers}->{'x-pp'}, '192.0.2.1', 'PROXY remote addr');
+
+# invalid PROXY protocol string
+
+$sess = new_session(8081, proxy => 'BOGUS TCP4 192.0.2.1 192.0.2.2 1234 5678',
+	pure => 1);
+$frames = h2_read($sess, all => [{ type => 'GOAWAY' }]);
+
+($frame) = grep { $_->{type} eq "GOAWAY" } @$frames;
+ok($frame, 'invalid PROXY - GOAWAY frame');
+is($frame->{code}, 1, 'invalid PROXY - error code');
+
+###############################################################################
new file mode 100644
--- /dev/null
+++ b/h2_request_body.t
@@ -0,0 +1,437 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for HTTP/2 protocol with request body.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP2 qw/ :DEFAULT :frame /;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http http_ssl http_v2 proxy/)->plan(35);
+
+# Some systems may have also a bug in not treating zero writev iovcnt as EINVAL
+
+$t->todo_alerts();
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    server {
+        listen       127.0.0.1:8080 http2;
+        listen       127.0.0.1:8081;
+        listen       127.0.0.1:8082 ssl;
+        server_name  localhost;
+
+        location / { }
+        location /proxy2/ {
+            add_header X-Body $request_body;
+            add_header X-Body-File $request_body_file;
+            client_body_in_file_only on;
+            proxy_pass http://127.0.0.1:8081/;
+        }
+        location /proxy_ssl/ {
+            proxy_pass https://127.0.0.1:8082/;
+        }
+        location /client_max_body_size {
+            add_header X-Body $request_body;
+            add_header X-Body-File $request_body_file;
+            client_body_in_single_buffer on;
+            client_body_in_file_only on;
+            proxy_pass http://127.0.0.1:8081/;
+            client_max_body_size 10;
+        }
+    }
+}
+
+EOF
+
+$t->write_file('index.html', '');
+$t->write_file('t.html', 'SEE-THIS');
+$t->run();
+
+###############################################################################
+
+# request body (uses proxied response)
+
+my $sess = new_session();
+my $sid = new_stream($sess, { path => '/proxy2/t.html', body_more => 1 });
+h2_body($sess, 'TEST');
+my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+my ($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST', 'request body');
+
+# request body with padding (uses proxied response)
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/proxy2/t.html', body_more => 1 });
+h2_body($sess, 'TEST', { body_padding => 42 });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST',
+	'request body with padding');
+
+$sid = new_stream($sess);
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, '200', 'request body with padding - next');
+
+# request body sent in multiple DATA frames in a single packet
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/proxy2/t.html', body_more => 1 });
+h2_body($sess, 'TEST', { body_split => [2] });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TEST',
+	'request body in multiple frames');
+
+# request body sent in multiple DATA frames, each in its own packet
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/proxy2/t.html', body_more => 1 });
+h2_body($sess, 'TEST', { body_more => 1 });
+select undef, undef, undef, 0.1;
+h2_body($sess, 'MOREDATA');
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTMOREDATA',
+	'request body in multiple frames separately');
+
+# request body with an empty DATA frame
+# "zero size buf in output" alerts seen
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/proxy2/', body_more => 1 });
+h2_body($sess, '');
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'request body - empty');
+
+TODO: {
+local $TODO = 'not yet';
+
+ok($frame->{headers}{'x-body-file'}, 'request body - empty body file');
+
+}
+
+TODO: {
+todo_skip 'empty body file', 1 unless $frame->{headers}{'x-body-file'};
+
+is(read_body_file($frame->{headers}{'x-body-file'}), '',
+	'request body - empty content');
+
+}
+
+# same as above but proxied to ssl backend
+
+TODO: {
+local $TODO = 'not yet';
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/proxy_ssl/', body_more => 1 });
+h2_body($sess, '');
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'request body - empty - proxy ssl');
+
+}
+
+# malformed request body length not equal to content-length
+
+$sess = new_session();
+$sid = new_stream($sess,
+	{ body_more => 1, headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/client_max_body_size', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'content-length', value => '5', mode => 1 }]});
+h2_body($sess, 'TEST');
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 400, 'request body less than content-length');
+
+$sid = new_stream($sess,
+	{ body_more => 1, headers => [
+	{ name => ':method', value => 'GET', mode => 0 },
+	{ name => ':scheme', value => 'http', mode => 0 },
+	{ name => ':path', value => '/client_max_body_size', mode => 1 },
+	{ name => ':authority', value => 'localhost', mode => 1 },
+	{ name => 'content-length', value => '3', mode => 1 }]});
+h2_body($sess, 'TEST');
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 400, 'request body more than content-length');
+
+# client_max_body_size
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+	body_more => 1 });
+h2_body($sess, 'TESTTEST12');
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'client_max_body_size - status');
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
+	'client_max_body_size - body');
+
+# client_max_body_size - limited
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+	body_more => 1 });
+h2_body($sess, 'TESTTEST123');
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 413, 'client_max_body_size - limited');
+
+# client_max_body_size - many DATA frames
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+	body_more => 1 });
+h2_body($sess, 'TESTTEST12', { body_split => [2] });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'client_max_body_size many - status');
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
+	'client_max_body_size many - body');
+
+# client_max_body_size - many DATA frames - limited
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+	body_more => 1 });
+h2_body($sess, 'TESTTEST123', { body_split => [2] });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 413, 'client_max_body_size many - limited');
+
+# client_max_body_size - padded DATA
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+	body_more => 1 });
+h2_body($sess, 'TESTTEST12', { body_padding => 42 });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200, 'client_max_body_size pad - status');
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
+	'client_max_body_size pad - body');
+
+# client_max_body_size - padded DATA - limited
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+	body_more => 1 });
+h2_body($sess, 'TESTTEST123', { body_padding => 42 });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 413, 'client_max_body_size pad - limited');
+
+# client_max_body_size - many padded DATA frames
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+	body_more => 1 });
+h2_body($sess, 'TESTTEST12', { body_padding => 42, body_split => [2] });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200,
+	'client_max_body_size many pad - status');
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
+	'client_max_body_size many pad - body');
+
+# client_max_body_size - many padded DATA frames - limited
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/client_max_body_size/t.html',
+	body_more => 1 });
+h2_body($sess, 'TESTTEST123', { body_padding => 42, body_split => [2] });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 413,
+	'client_max_body_size many pad - limited');
+
+# request body without content-length
+
+$sess = new_session();
+$sid = new_stream($sess, { body_more => 1, headers => [
+	{ name => ':method', value => 'GET', mode => 2 },
+	{ name => ':scheme', value => 'http', mode => 2 },
+	{ name => ':path', value => '/client_max_body_size', mode => 2 },
+	{ name => ':authority', value => 'localhost', mode => 2 }]});
+h2_body($sess, 'TESTTEST12');
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200,
+	'request body without content-length - status');
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
+	'request body without content-length - body');
+
+# request body without content-length - limited
+
+$sess = new_session();
+$sid = new_stream($sess, { body_more => 1, headers => [
+	{ name => ':method', value => 'GET', mode => 2 },
+	{ name => ':scheme', value => 'http', mode => 2 },
+	{ name => ':path', value => '/client_max_body_size', mode => 2 },
+	{ name => ':authority', value => 'localhost', mode => 2 }]});
+h2_body($sess, 'TESTTEST123');
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 413,
+	'request body without content-length - limited');
+
+# request body without content-length - many DATA frames
+
+$sess = new_session();
+$sid = new_stream($sess, { body_more => 1, headers => [
+	{ name => ':method', value => 'GET', mode => 2 },
+	{ name => ':scheme', value => 'http', mode => 2 },
+	{ name => ':path', value => '/client_max_body_size', mode => 2 },
+	{ name => ':authority', value => 'localhost', mode => 2 }]});
+h2_body($sess, 'TESTTEST12', { body_split => [2] });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200,
+	'request body without content-length many - status');
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
+	'request body without content-length many - body');
+
+# request body without content-length - many DATA frames - limited
+
+$sess = new_session();
+$sid = new_stream($sess, { body_more => 1, headers => [
+	{ name => ':method', value => 'GET', mode => 2 },
+	{ name => ':scheme', value => 'http', mode => 2 },
+	{ name => ':path', value => '/client_max_body_size', mode => 2 },
+	{ name => ':authority', value => 'localhost', mode => 2 }]});
+h2_body($sess, 'TESTTEST123', { body_split => [2] });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 413,
+	'request body without content-length many - limited');
+
+# request body without content-length - padding
+
+$sess = new_session();
+$sid = new_stream($sess, { body_more => 1, headers => [
+	{ name => ':method', value => 'GET', mode => 2 },
+	{ name => ':scheme', value => 'http', mode => 2 },
+	{ name => ':path', value => '/client_max_body_size', mode => 2 },
+	{ name => ':authority', value => 'localhost', mode => 2 }]});
+h2_body($sess, 'TESTTEST12', { body_padding => 42 });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200,
+	'request body without content-length pad - status');
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST12',
+	'request body without content-length pad - body');
+
+# request body without content-length - padding - limited
+
+$sess = new_session();
+$sid = new_stream($sess, { body_more => 1, headers => [
+	{ name => ':method', value => 'GET', mode => 2 },
+	{ name => ':scheme', value => 'http', mode => 2 },
+	{ name => ':path', value => '/client_max_body_size', mode => 2 },
+	{ name => ':authority', value => 'localhost', mode => 2 }]});
+h2_body($sess, 'TESTTEST123', { body_padding => 42 });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 413,
+	'request body without content-length pad - limited');
+
+# request body without content-length - padding with many DATA frames
+
+$sess = new_session();
+$sid = new_stream($sess, { body_more => 1, headers => [
+	{ name => ':method', value => 'GET', mode => 2 },
+	{ name => ':scheme', value => 'http', mode => 2 },
+	{ name => ':path', value => '/client_max_body_size', mode => 2 },
+	{ name => ':authority', value => 'localhost', mode => 2 }]});
+h2_body($sess, 'TESTTEST', { body_padding => 42, body_split => [2] });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 200,
+	'request body without content-length many pad - status');
+is(read_body_file($frame->{headers}->{'x-body-file'}), 'TESTTEST',
+	'request body without content-length many pad - body');
+
+# request body without content-length - padding with many DATA frames - limited
+
+$sess = new_session();
+$sid = new_stream($sess, { body_more => 1, headers => [
+	{ name => ':method', value => 'GET', mode => 2 },
+	{ name => ':scheme', value => 'http', mode => 2 },
+	{ name => ':path', value => '/client_max_body_size', mode => 2 },
+	{ name => ':authority', value => 'localhost', mode => 2 }]});
+h2_body($sess, 'TESTTEST123', { body_padding => 42, body_split => [2] });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "HEADERS" } @$frames;
+is($frame->{headers}->{':status'}, 413,
+	'request body without content-length many pad - limited');
+
+###############################################################################
+
+sub read_body_file {
+	my ($path) = @_;
+	open FILE, $path or return "$!";
+	local $/;
+	my $content = <FILE>;
+	close FILE;
+	return $content;
+}
+
+###############################################################################
new file mode 100644
--- /dev/null
+++ b/h2_ssl.t
@@ -0,0 +1,217 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for HTTP/2 protocol with ssl.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP2;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+eval { require IO::Socket::SSL; };
+plan(skip_all => 'IO::Socket::SSL not installed') if $@;
+eval { IO::Socket::SSL::SSL_VERIFY_NONE(); };
+plan(skip_all => 'IO::Socket::SSL too old') if $@;
+
+my $t = Test::Nginx->new()->has(qw/http http_ssl http_v2 rewrite/)
+	->has_daemon('openssl')->plan(8);
+
+$t->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    server {
+        listen       127.0.0.1:8080 http2;
+        listen       127.0.0.1:8081 http2 ssl;
+        server_name  localhost;
+
+        ssl_certificate_key localhost.key;
+        ssl_certificate localhost.crt;
+
+        location /h2 {
+            return 200 $http2;
+        }
+        location /sp {
+            return 200 $server_protocol;
+        }
+        location /scheme {
+            return 200 $scheme;
+        }
+        location /https {
+            return 200 $https;
+        }
+    }
+}
+
+EOF
+
+$t->write_file('openssl.conf', <<EOF);
+[ req ]
+default_bits = 2048
+encrypt_key = no
+distinguished_name = req_distinguished_name
+[ req_distinguished_name ]
+EOF
+
+my $d = $t->testdir();
+
+foreach my $name ('localhost') {
+	system('openssl req -x509 -new '
+		. "-config '$d/openssl.conf' -subj '/CN=$name/' "
+		. "-out '$d/$name.crt' -keyout '$d/$name.key' "
+		. ">>$d/openssl.out 2>&1") == 0
+		or die "Can't create certificate for $name: $!\n";
+}
+
+open OLDERR, ">&", \*STDERR; close STDERR;
+$t->run();
+open STDERR, ">&", \*OLDERR;
+
+###############################################################################
+
+my ($sess, $sid, $frames, $frame);
+
+# SSL/TLS connection, NPN
+
+SKIP: {
+eval { IO::Socket::SSL->can_npn() or die; };
+skip 'OpenSSL NPN support required', 1 if $@;
+
+$sess = new_session(8081, SSL => 1, npn => 'h2');
+$sid = new_stream($sess, { path => '/h2' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'h2', 'http variable - npn');
+
+}
+
+# SSL/TLS connection, ALPN
+
+SKIP: {
+eval { IO::Socket::SSL->can_alpn() or die; };
+skip 'OpenSSL ALPN support required', 1 if $@;
+
+$sess = new_session(8081, SSL => 1, alpn => 'h2');
+$sid = new_stream($sess, { path => '/h2' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'h2', 'http variable - alpn');
+
+}
+
+# $server_protocol - SSL/TLS connection, NPN
+
+SKIP: {
+eval { IO::Socket::SSL->can_npn() or die; };
+skip 'OpenSSL NPN support required', 1 if $@;
+
+$sess = new_session(8081, SSL => 1, npn => 'h2');
+$sid = new_stream($sess, { path => '/sp' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'HTTP/2.0', 'server_protocol variable - npn');
+
+}
+
+# $server_protocol - SSL/TLS connection, ALPN
+
+SKIP: {
+eval { IO::Socket::SSL->can_alpn() or die; };
+skip 'OpenSSL ALPN support required', 1 if $@;
+
+$sess = new_session(8081, SSL => 1, alpn => 'h2');
+$sid = new_stream($sess, { path => '/sp' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'HTTP/2.0', 'server_protocol variable - alpn');
+
+}
+
+# $scheme - SSL/TLS connection, NPN
+
+SKIP: {
+eval { IO::Socket::SSL->can_npn() or die; };
+skip 'OpenSSL NPN support required', 1 if $@;
+
+$sess = new_session(8081, SSL => 1, npn => 'h2');
+$sid = new_stream($sess, { path => '/scheme' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'https', 'scheme variable - npn');
+
+}
+
+# $scheme - SSL/TLS connection, ALPN
+
+SKIP: {
+eval { IO::Socket::SSL->can_alpn() or die; };
+skip 'OpenSSL ALPN support required', 1 if $@;
+
+$sess = new_session(8081, SSL => 1, alpn => 'h2');
+$sid = new_stream($sess, { path => '/scheme' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'https', 'scheme variable - alpn');
+
+}
+
+# $https - SSL/TLS connection, NPN
+
+SKIP: {
+eval { IO::Socket::SSL->can_npn() or die; };
+skip 'OpenSSL NPN support required', 1 if $@;
+
+$sess = new_session(8081, SSL => 1, npn => 'h2');
+$sid = new_stream($sess, { path => '/https' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'on', 'https variable - npn');
+
+}
+
+# $https - SSL/TLS connection, ALPN
+
+SKIP: {
+eval { IO::Socket::SSL->can_alpn() or die; };
+skip 'OpenSSL ALPN support required', 1 if $@;
+
+$sess = new_session(8081, SSL => 1, alpn => 'h2');
+$sid = new_stream($sess, { path => '/https' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'on', 'https variable - alpn');
+
+}
+
+###############################################################################
new file mode 100644
--- /dev/null
+++ b/h2_variables.t
@@ -0,0 +1,100 @@
+#!/usr/bin/perl
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Tests for HTTP/2 protocol with variables.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use Test::More;
+
+BEGIN { use FindBin; chdir($FindBin::Bin); }
+
+use lib 'lib';
+use Test::Nginx;
+use Test::Nginx::HTTP2;
+
+###############################################################################
+
+select STDERR; $| = 1;
+select STDOUT; $| = 1;
+
+my $t = Test::Nginx->new()->has(qw/http http_v2 rewrite/)->plan(4)
+	->write_file_expand('nginx.conf', <<'EOF');
+
+%%TEST_GLOBALS%%
+
+daemon off;
+
+events {
+}
+
+http {
+    %%TEST_GLOBALS_HTTP%%
+
+    server {
+        listen       127.0.0.1:8080 http2;
+        server_name  localhost;
+
+        location /h2 {
+            return 200 $http2;
+        }
+        location /sp {
+            return 200 $server_protocol;
+        }
+        location /scheme {
+            return 200 $scheme;
+        }
+        location /https {
+            return 200 $https;
+        }
+    }
+}
+
+EOF
+
+$t->run();
+
+###############################################################################
+
+# $http2
+
+my $sess = new_session();
+my $sid = new_stream($sess, { path => '/h2' });
+my $frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+my ($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'h2c', 'http variable - h2c');
+
+# $server_protocol
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/sp' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'HTTP/2.0', 'server_protocol variable');
+
+# $scheme
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/scheme' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, 'http', 'scheme variable');
+
+# $https
+
+$sess = new_session();
+$sid = new_stream($sess, { path => '/https' });
+$frames = h2_read($sess, all => [{ sid => $sid, fin => 1 }]);
+
+($frame) = grep { $_->{type} eq "DATA" } @$frames;
+is($frame->{data}, '', 'https variable');
+
+###############################################################################
new file mode 100644
--- /dev/null
+++ b/lib/Test/Nginx/HTTP2.pm
@@ -0,0 +1,1068 @@
+package Test::Nginx::HTTP2;
+
+# (C) Sergey Kandaurov
+# (C) Nginx, Inc.
+
+# Module for nginx HTTP/2 tests.
+
+###############################################################################
+
+use warnings;
+use strict;
+
+use base qw/ Exporter /;
+our @EXPORT = qw/ new_session new_stream h2_read /;
+our %EXPORT_TAGS = (
+	io => [ qw/ raw_write raw_read / ],
+	frame => [ qw/ h2_ping h2_rst h2_goaway h2_priority h2_window
+		h2_settings h2_unknown h2_continue h2_body/ ]
+);
+our @EXPORT_OK = ( @{ $EXPORT_TAGS{'io'} }, @{ $EXPORT_TAGS{'frame'} } );
+
+use Test::More qw//;
+use IO::Select;
+use IO::Socket;
+use Socket qw/ CRLF /;
+
+use Test::Nginx;
+
+my %cframe = (
+	0 => { name => 'DATA', value => \&data },
+	1 => { name => 'HEADERS', value => \&headers },
+#	2 => { name => 'PRIORITY', value => \&priority },
+	3 => { name => 'RST_STREAM', value => \&rst_stream },
+	4 => { name => 'SETTINGS', value => \&settings },
+#	5 => { name => 'PUSH_PROMISE', value => \&push_promise },
+	6 => { name => 'PING', value => \&ping },
+	7 => { name => 'GOAWAY', value => \&goaway },
+	8 => { name => 'WINDOW_UPDATE', value => \&window_update },
+	9 => { name => 'CONTINUATION', value => \&headers },
+);
+
+sub h2_ping {
+	my ($sess, $payload) = @_;
+
+	raw_write($sess->{socket}, pack("x2C2x5a8", 8, 0x6, $payload));
+}
+
+sub h2_rst {
+	my ($sess, $stream, $error) = @_;
+
+	raw_write($sess->{socket}, pack("x2C2xNN", 4, 0x3, $stream, $error));
+}
+
+sub h2_goaway {
+	my ($sess, $stream, $lstream, $err, $debug, %extra) = @_;
+	$debug = '' unless defined $debug;
+	my $len = defined $extra{len} ? $extra{len} : 8 + length($debug);
+	my $buf = pack("x2C2xN3A*", $len, 0x7, $stream, $lstream, $err, $debug);
+
+	my @bufs = map {
+		raw_write($sess->{socket}, substr $buf, 0, $_, "");
+		select undef, undef, undef, 0.4;
+	} @{$extra{split}};
+
+	raw_write($sess->{socket}, $buf);
+}
+
+sub h2_priority {
+	my ($sess, $w, $stream, $dep, %extra) = @_;
+
+	$stream = 0 unless defined $stream;
+	$dep = 0 unless defined $dep;
+	$dep |= $extra{excl} << 31 if exists $extra{excl};
+	raw_write($sess->{socket}, pack("x2C2xNNC", 5, 0x2, $stream, $dep, $w));
+}
+
+sub h2_window {
+	my ($sess, $win, $stream) = @_;
+
+	$stream = 0 unless defined $stream;
+	raw_write($sess->{socket}, pack("x2C2xNN", 4, 0x8, $stream, $win));
+}
+
+sub h2_settings {
+	my ($sess, $ack, %extra) = @_;
+
+	my $len = 6 * keys %extra;
+	my $buf = pack_length($len) . pack "CCx4", 0x4, $ack ? 0x1 : 0x0;
+	$buf .= join '', map { pack "nN", $_, $extra{$_} } keys %extra;
+	raw_write($sess->{socket}, $buf);
+}
+
+sub h2_unknown {
+	my ($sess, $payload) = @_;
+
+	my $buf = pack_length(length($payload)) . pack("Cx5a*", 0xa, $payload);
+	raw_write($sess->{socket}, $buf);
+}
+
+sub h2_continue {
+	my ($ctx, $stream, $uri) = @_;
+
+	$uri->{h2_continue} = 1;
+	return new_stream($ctx, $uri, $stream);
+}
+
+sub h2_body {
+	my ($sess, $body, $extra) = @_;
+	$extra = {} unless defined $extra;
+
+	my $len = length $body;
+	my $sid = $sess->{last_stream};
+
+	if ($len > $sess->{conn_window} || $len > $sess->{streams}{$sid}) {
+		h2_read($sess, all => [{ type => 'WINDOW_UPDATE' }]);
+	}
+
+	if ($len > $sess->{conn_window} || $len > $sess->{streams}{$sid}) {
+		return;
+	}
+
+	$sess->{conn_window} -= $len;
+	$sess->{streams}{$sid} -= $len;
+
+	my $buf;
+
+	my $split = ref $extra->{body_split} && $extra->{body_split} || [];
+	for (@$split) {
+		$buf .= pack_body($sess, substr($body, 0, $_, ""), 0x0, $extra);
+	}
+
+	$buf .= pack_body($sess, $body, 0x1, $extra) if defined $body;
+
+	$split = ref $extra->{split} && $extra->{split} || [];
+	for (@$split) {
+		raw_write($sess->{socket}, substr($buf, 0, $_, ""));
+		return if $extra->{abort};
+		select undef, undef, undef, ($extra->{split_delay} || 0.2);
+	}
+
+	raw_write($sess->{socket}, $buf);
+}
+
+sub pack_body {
+	my ($ctx, $body, $flags, $extra) = @_;
+
+	my $pad = defined $extra->{body_padding} ? $extra->{body_padding} : 0;
+	my $padlen = defined $extra->{body_padding} ? 1 : 0;
+
+	my $buf = pack_length(length($body) + $pad + $padlen);
+	$flags |= 0x8 if $padlen;
+	vec($flags, 0, 1) = 0 if $extra->{body_more};
+	$buf .= pack 'CC', 0x0, $flags;		# DATA, END_STREAM
+	$buf .= pack 'N', $ctx->{last_stream};
+	$buf .= pack 'C', $pad if $padlen;	# DATA Pad Length?
+	$buf .= $body;
+	$buf .= pack "x$pad" if $padlen;	# DATA Padding
+	return $buf;
+}
+
+sub new_stream {
+	my ($ctx, $uri, $stream) = @_;
+	my ($input, $buf);
+	my ($d, $status);
+
+	$ctx->{headers} = '';
+
+	my $host = $uri->{host} || '127.0.0.1:8080';
+	my $method = $uri->{method} || 'GET';
+	my $scheme = $uri->{scheme} || 'http';
+	my $path = $uri->{path} || '/';
+	my $headers = $uri->{headers};
+	my $body = $uri->{body};
+	my $prio = $uri->{prio};
+	my $dep = $uri->{dep};
+
+	my $pad = defined $uri->{padding} ? $uri->{padding} : 0;
+	my $padlen = defined $uri->{padding} ? 1 : 0;
+
+	my $type = defined $uri->{h2_continue} ? 0x9 : 0x1;
+	my $flags = defined $uri->{continuation} ? 0x0 : 0x4;
+	$flags |= 0x1 unless defined $body || defined $uri->{body_more};
+	$flags |= 0x8 if $padlen;
+	$flags |= 0x20 if defined $dep || defined $prio;
+
+	if ($stream) {
+		$ctx->{last_stream} = $stream;
+	} else {
+		$ctx->{last_stream} += 2;
+		$ctx->{streams}{$ctx->{last_stream}} = $ctx->{iws};
+	}
+
+	$buf = pack("xxx");			# Length stub
+	$buf .= pack("CC", $type, $flags);	# END_HEADERS
+	$buf .= pack("N", $ctx->{last_stream});	# Stream-ID
+
+	$dep = 0 if defined $prio and not defined $dep;
+	$prio = 16 if defined $dep and not defined $prio;
+
+	unless ($headers) {
+		$input = hpack($ctx, ":method", $method);
+		$input .= hpack($ctx, ":scheme", $scheme);
+		$input .= hpack($ctx, ":path", $path);
+		$input .= hpack($ctx, ":authority", $host);
+		$input .= hpack($ctx, "content-length", length($body)) if $body;
+
+	} else {
+		$input = join '', map {
+			hpack($ctx, $_->{name}, $_->{value},
+			mode => $_->{mode}, huff => $_->{huff})
+		} @$headers if $headers;
+	}
+
+	$input = pack("B*", '001' . ipack(5, $uri->{table_size})) . $input
+		if defined $uri->{table_size};
+
+	my $split = ref $uri->{continuation} && $uri->{continuation} || [];
+	my @input = map { substr $input, 0, $_, "" } @$split;
+	push @input, $input;
+
+	# set length, attach headers, padding, priority
+
+	my $hlen = length($input[0]) + $pad + $padlen;
+	$hlen += 5 if $flags & 0x20;
+	$buf |= pack_length($hlen);
+
+	$buf .= pack 'C', $pad if $padlen;		# Pad Length?
+	$buf .= pack 'NC', $dep, $prio if $flags & 0x20;
+	$buf .= $input[0];
+	$buf .= (pack 'C', 0) x $pad if $padlen;	# Padding
+
+	shift @input;
+
+	while (@input) {
+		$input = shift @input;
+		$flags = @input ? 0x0 : 0x4;
+		$buf .= pack_length(length($input));
+		$buf .= pack("CC", 0x9, $flags);
+		$buf .= pack("N", $ctx->{last_stream});
+		$buf .= $input;
+	}
+
+	$split = ref $uri->{body_split} && $uri->{body_split} || [];
+	for (@$split) {
+		$buf .= pack_body($ctx, substr($body, 0, $_, ""), 0x0, $uri);
+	}
+
+	$buf .= pack_body($ctx, $body, 0x1, $uri) if defined $body;
+
+	$split = ref $uri->{split} && $uri->{split} || [];
+	for (@$split) {
+		raw_write($ctx->{socket}, substr($buf, 0, $_, ""));
+		goto done if $uri->{abort};
+		select undef, undef, undef, ($uri->{split_delay} || 0.2);
+	}
+
+	raw_write($ctx->{socket}, $buf);
+done:
+	return $ctx->{last_stream};
+}
+
+sub h2_read {
+	my ($sess, %extra) = @_;
+	my (@got);
+	my $s = $sess->{socket};
+	my $buf = '';
+
+	while (1) {
+		$buf = raw_read($s, $buf, 9);
+		last if length $buf < 9;
+
+		my $length = unpack_length($buf);
+		my $type = unpack('x3C', $buf);
+		my $flags = unpack('x4C', $buf);
+
+		my $stream = unpack "x5 B32", $buf;
+		substr($stream, 0, 1) = 0;
+		$stream = unpack("N", pack("B32", $stream));
+
+		$buf = raw_read($s, $buf, $length + 9);
+		last if length($buf) < $length + 9;
+
+		$buf = substr($buf, 9);
+
+		my $frame = $cframe{$type}{value}($sess, $buf, $length, $flags,
+			$stream);
+		$frame->{length} = $length;
+		$frame->{type} = $cframe{$type}{name};
+		$frame->{flags} = $flags;
+		$frame->{sid} = $stream;
+		push @got, $frame;
+
+		$buf = substr($buf, $length);
+
+		last unless $extra{all} && test_fin($got[-1], $extra{all});
+	};
+	return \@got;
+}
+
+sub test_fin {
+	my ($frame, $all) = @_;
+	my @test = @{$all};
+
+	# wait for the specified DATA length
+
+	for (@test) {
+		if ($_->{length} && $frame->{type} eq 'DATA') {
+			# check also for StreamID if needed
+
+			if (!$_->{sid} || $_->{sid} == $frame->{sid}) {
+				$_->{length} -= $frame->{length};
+			}
+		}
+	}
+	@test = grep { !(defined $_->{length} && $_->{length} == 0) } @test;
+
+	# wait for the fin flag
+
+	@test = grep { !(defined $_->{fin}
+		&& $_->{sid} == $frame->{sid} && $_->{fin} & $frame->{flags})
+	} @test if defined $frame->{flags};
+
+	# wait for the specified frame
+
+	@test = grep { !($_->{type} && $_->{type} eq $frame->{type}) } @test;
+
+	@{$all} = @test;
+}
+
+sub headers {
+	my ($ctx, $buf, $len, $flags) = @_;
+	$ctx->{headers} .= substr($buf, 0, $len);
+	return unless $flags & 0x4;
+	{ headers => hunpack($ctx, $ctx->{headers}, length($ctx->{headers})) };
+}
+
+sub data {
+	my ($ctx, $buf, $len) = @_;
+	return { data => substr($buf, 0, $len) };
+}
+
+sub settings {
+	my ($ctx, $buf, $len) = @_;
+	my %payload;
+	my $skip = 0;
+
+	for (1 .. $len / 6) {
+		my $id = hex unpack "\@$skip n", $buf; $skip += 2;
+		$payload{$id} = unpack "\@$skip N", $buf; $skip += 4;
+
+		$ctx->{iws} = $payload{$id} if $id == 4;
+	}
+	return \%payload;
+}
+
+sub ping {
+	my ($ctx, $buf, $len) = @_;
+	return { value => unpack "A$len", $buf };
+}
+
+sub rst_stream {
+	my ($ctx, $buf, $len) = @_;
+	return { code => unpack "N", $buf };
+}
+
+sub goaway {
+	my ($ctx, $buf, $len) = @_;
+	my %payload;
+
+	my $stream = unpack "B32", $buf;
+	substr($stream, 0, 1) = 0;
+	$stream = unpack("N", pack("B32", $stream));
+	$payload{last_sid} = $stream;
+
+	$len -= 4;
+	$payload{code} = unpack "x4 N", $buf;
+	$payload{debug} = unpack "x8 A$len", $buf;
+	return \%payload;
+}
+
+sub window_update {
+	my ($ctx, $buf, $len, $flags, $sid) = @_;
+	my $value = unpack "B32", $buf;
+	substr($value, 0, 1) = 0;
+	$value = unpack("N", pack("B32", $value));
+
+	unless ($sid) {
+		$ctx->{conn_window} += $value;
+
+	} else {
+		$ctx->{streams}{$sid} = $ctx->{iws}
+			unless defined $ctx->{streams}{$sid};
+		$ctx->{streams}{$sid} += $value;
+	}
+
+	return { wdelta => $value };
+}
+
+sub pack_length {
+	pack 'c3', unpack 'xc3', pack 'N', $_[0];
+}
+
+sub unpack_length {
+	unpack 'N', pack 'xc3', unpack 'c3', $_[0];
+}
+
+sub raw_read {
+	my ($s, $buf, $len, $log) = @_;
+	$log = \&log_in unless defined $log;
+	my $got = '';
+
+	while (length($buf) < $len && IO::Select->new($s)->can_read(1))  {
+		$s->sysread($got, 16384) or last;
+		$log->($got);
+		$buf .= $got;
+	}
+	return $buf;
+}
+
+sub raw_write {
+	my ($s, $message) = @_;
+
+	local $SIG{PIPE} = 'IGNORE';
+
+	while (IO::Select->new($s)->can_write(0.4)) {
+		log_out($message);
+		my $n = $s->syswrite($message);
+		last unless $n;
+		$message = substr($message, $n);
+		last unless length $message;
+	}
+}
+
+sub new_session {
+	my ($port, %extra) = @_;
+
+	my $s = new_socket($port, %extra);
+	my $preface = $extra{preface}
+		|| 'PRI * HTTP/2.0' . CRLF . CRLF . 'SM' . CRLF . CRLF;
+
+	if ($extra{proxy}) {
+		raw_write($s, $extra{proxy});
+	}
+
+	# preface
+
+	raw_write($s, $preface);
+
+	my $ctx = { socket => $s, last_stream => -1,
+		dynamic_encode => [ static_table() ],
+		dynamic_decode => [ static_table() ],
+		static_table_size => scalar @{[static_table()]},
+		iws => 65535, conn_window => 65535, streams => {}};
+
+	return $ctx if $extra{pure};
+
+	# update windows, if any
+
+	h2_read($ctx, all => [
+		{ type => 'WINDOW_UPDATE' },
+		{ type => 'SETTINGS'}
+	]);
+
+	return $ctx;
+}
+
+sub new_socket {
+	my ($port, %extra) = @_;
+	my $npn = $extra{'npn'};
+	my $alpn = $extra{'alpn'};
+	my $s;
+
+	$port = 8080 unless defined $port;
+
+	eval {
+		local $SIG{ALRM} = sub { die "timeout\n" };
+		local $SIG{PIPE} = sub { die "sigpipe\n" };
+		alarm(2);
+		$s = IO::Socket::INET->new(
+			Proto => 'tcp',
+			PeerAddr => "127.0.0.1:$port",
+		);
+		require IO::Socket::SSL if $extra{'SSL'};
+		IO::Socket::SSL->start_SSL($s,
+			SSL_verify_mode => IO::Socket::SSL::SSL_VERIFY_NONE(),
+			SSL_npn_protocols => $npn ? [ $npn ] : undef,
+			SSL_alpn_protocols => $alpn ? [ $alpn ] : undef,
+			SSL_error_trap => sub { die $_[1] }
+		) if $extra{'SSL'};
+		alarm(0);
+	};
+	alarm(0);
+
+	if ($@) {
+		log_in("died: $@");
+		return undef;
+	}
+
+	return $s;
+}
+
+sub static_table {
+	[ '',			''		], # unused
+	[ ':authority',		''		],
+	[ ':method',		'GET'		],
+	[ ':method',		'POST'		],
+	[ ':path',		'/'		],
+	[ ':path',		'/index.html'	],
+	[ ':scheme',		'http'		],
+	[ ':scheme',		'https'		],
+	[ ':status',		'200'		],
+	[ ':status',		'204'		],
+	[ ':status',		'206'		],
+	[ ':status',		'304'		],
+	[ ':status',		'400'		],
+	[ ':status',		'404'		],
+	[ ':status',		'500'		],
+	[ 'accept-charset',	''		],
+	[ 'accept-encoding',	'gzip, deflate'	],
+	[ 'accept-language',	''		],
+	[ 'accept-ranges',	''		],
+	[ 'accept',		''		],
+	[ 'access-control-allow-origin',
+				''		],
+	[ 'age',		''		],
+	[ 'allow',		''		],
+	[ 'authorization', 	''		],
+	[ 'cache-control', 	''		],
+	[ 'content-disposition',
+				''		],
+	[ 'content-encoding',	''		],
+	[ 'content-language',	''		],
+	[ 'content-length',	''		],
+	[ 'content-location',	''		],
+	[ 'content-range',	''		],
+	[ 'content-type',	''		],
+	[ 'cookie',		''		],
+	[ 'date',		''		],
+	[ 'etag',		''		],
+	[ 'expect',		''		],
+	[ 'expires',		''		],
+	[ 'from',		''		],
+	[ 'host',		''		],
+	[ 'if-match',		''		],
+	[ 'if-modified-since',	''		],
+	[ 'if-none-match',	''		],
+	[ 'if-range',		''		],
+	[ 'if-unmodified-since',
+				''		],
+	[ 'last-modified',	''		],
+	[ 'link',		''		],
+	[ 'location',		''		],
+	[ 'max-forwards',	''		],
+	[ 'proxy-authenticate',	''		],
+	[ 'proxy-authorization',
+				''		],
+	[ 'range',		''		],
+	[ 'referer',		''		],
+	[ 'refresh',		''		],
+	[ 'retry-after',	''		],
+	[ 'server',		''		],
+	[ 'set-cookie',		''		],
+	[ 'strict-transport-security',
+				''		],
+	[ 'transfer-encoding',	''		],
+	[ 'user-agent',		''		],
+	[ 'vary',		''		],
+	[ 'via',		''		],
+	[ 'www-authenticate',	''		],
+}
+
+# RFC 7541, 5.1.  Integer Representation
+
+sub ipack {
+	my ($base, $d) = @_;
+	return sprintf("%.*b", $base, $d) if $d < 2**$base - 1;
+
+	my $o = sprintf("%${base}b", 2**$base - 1);
+	$d -= 2**$base - 1;
+	while ($d >= 128) {
+		$o .= sprintf("%8b", $d % 128 + 128);
+		$d /= 128;
+	}
+	$o .= sprintf("%08b", $d);
+	return $o;
+}
+
+sub iunpack {
+	my ($base, $b, $s) = @_;
+
+	my $len = unpack("\@$s B8", $b); $s++;
+	my $prefix = substr($len, 0, 8 - $base);
+	$len = '0' x (8 - $base) . substr($len, 8 - $base);
+	$len = unpack("C", pack("B8", $len));
+
+	return ($len, $s, $prefix) if $len < 2**$base - 1;
+
+	my $m = 0;
+	my $d;
+
+	do {
+		$d = unpack("\@$s C", $b); $s++;
+		$len += ($d & 127) * 2**$m;
+		$m += $base;
+	} while (($d & 128) == 128);
+
+	return ($len, $s, $prefix);
+}
+
+sub hpack {
+	my ($ctx, $name, $value, %extra) = @_;
+	my $table = $ctx->{dynamic_encode};
+	my $mode = defined $extra{mode} ? $extra{mode} : 1;
+	my $huff = $extra{huff};
+
+	my ($index, $buf) = 0;
+
+	# 6.1.  Indexed Header Field Representation
+
+	if ($mode == 0) {
+		++$index until $index > $#$table
+			or $table->[$index][0] eq $name
+			and $table->[$index][1] eq $value;
+		$buf = pack('B*', '1' . ipack(7, $index));
+	}
+
+	# 6.2.1.  Literal Header Field with Incremental Indexing
+
+	if ($mode == 1) {
+		splice @$table, $ctx->{static_table_size}, 0, [ $name, $value ];
+
+		++$index until $index > $#$table
+			or $table->[$index][0] eq $name;
+		my $value = $huff ? huff($value) : $value;
+
+		$buf = pack('B*', '01' . ipack(6, $index)
+			. ($huff ? '1' : '0') . ipack(7, length($value)));
+		$buf .= $value;
+	}
+
+	# 6.2.1.  Literal Header Field with Incremental Indexing -- New Name
+
+	if ($mode == 2) {
+		splice @$table, $ctx->{static_table_size}, 0, [ $name, $value ];
+
+		my $name = $huff ? huff($name) : $name;
+		my $value = $huff ? huff($value) : $value;
+		my $hbit = ($huff ? '1' : '0');
+
+		$buf = pack('B*', '01000000');
+		$buf .= pack('B*', $hbit . ipack(7, length($name)));
+		$buf .= $name;
+		$buf .= pack('B*', $hbit . ipack(7, length($value)));
+		$buf .= $value;
+	}
+
+	# 6.2.2.  Literal Header Field without Indexing
+
+	if ($mode == 3) {
+		++$index until $index > $#$table
+			or $table->[$index][0] eq $name;
+		my $value = $huff ? huff($value) : $value;
+
+		$buf = pack('B*', '0000' . ipack(4, $index)
+			. ($huff ? '1' : '0') . ipack(7, length($value)));
+		$buf .= $value;
+	}
+
+	# 6.2.2.  Literal Header Field without Indexing -- New Name
+
+	if ($mode == 4) {
+		my $name = $huff ? huff($name) : $name;
+		my $value = $huff ? huff($value) : $value;
+		my $hbit = ($huff ? '1' : '0');
+
+		$buf = pack('B*', '00000000');
+		$buf .= pack('B*', $hbit . ipack(7, length($name)));
+		$buf .= $name;
+		$buf .= pack('B*', $hbit . ipack(7, length($value)));
+		$buf .= $value;
+	}
+
+	# 6.2.3.  Literal Header Field Never Indexed
+
+	if ($mode == 5) {
+		++$index until $index > $#$table
+			or $table->[$index][0] eq $name;
+		my $value = $huff ? huff($value) : $value;
+
+		$buf = pack('B*', '0001' . ipack(4, $index)
+			. ($huff ? '1' : '0') . ipack(7, length($value)));
+		$buf .= $value;
+	}
+
+	# 6.2.3.  Literal Header Field Never Indexed -- New Name
+
+	if ($mode == 6) {
+		my $name = $huff ? huff($name) : $name;
+		my $value = $huff ? huff($value) : $value;
+		my $hbit = ($huff ? '1' : '0');
+
+		$buf = pack('B*', '00010000');
+		$buf .= pack('B*', $hbit . ipack(7, length($name)));
+		$buf .= $name;
+		$buf .= pack('B*', $hbit . ipack(7, length($value)));
+		$buf .= $value;
+	}
+
+	return $buf;
+}
+
+sub hunpack {
+	my ($ctx, $data, $length) = @_;
+	my $table = $ctx->{dynamic_decode};
+	my %headers;
+	my $skip = 0;
+	my ($index, $name, $value);
+
+	my $field = sub {
+		my ($b) = @_;
+		my ($len, $s, $huff) = iunpack(7, @_);
+
+		my $field = substr($b, $s, $len);
+		$field = $huff ? dehuff($field) : $field;
+		$s += $len;
+		return ($field, $s);
+	};
+
+	my $add = sub {
+		my ($h, $n, $v) = @_;
+		return $h->{$n} = $v unless exists $h->{$n};
+		$h->{$n} = [ $h->{$n} ] unless ref $h->{$n};
+		push @{$h->{$n}}, $v;
+	};
+
+	while ($skip < $length) {
+		my $ib = unpack("\@$skip B8", $data);
+
+		if (substr($ib, 0, 1) eq '1') {
+			($index, $skip) = iunpack(7, $data, $skip);
+			$add->(\%headers,
+				$table->[$index][0], $table->[$index][1]);
+			next;
+		}
+
+		if (substr($ib, 0, 2) eq '01') {
+			($index, $skip) = iunpack(6, $data, $skip);
+			$name = $table->[$index][0];
+
+			($name, $skip) = $field->($data, $skip) unless $name;
+			($value, $skip) = $field->($data, $skip);
+
+			splice @$table,
+				$ctx->{static_table_size}, 0, [ $name, $value ];
+			$add->(\%headers, $name, $value);
+			next;
+		}
+
+		if (substr($ib, 0, 4) eq '0000') {
+			($index, $skip) = iunpack(4, $data, $skip);
+			$name = $table->[$index][0];
+
+			($name, $skip) = $field->($data, $skip) unless $name;
+			($value, $skip) = $field->($data, $skip);
+
+			$add->(\%headers, $name, $value);
+			next;
+		}
+		last;
+	}
+
+	return \%headers;
+}
+
+sub huff_code { scalar {
+	pack('C', 0)	=> '1111111111000',
+	pack('C', 1)	=> '11111111111111111011000',
+	pack('C', 2)	=> '1111111111111111111111100010',
+	pack('C', 3)	=> '1111111111111111111111100011',
+	pack('C', 4)	=> '1111111111111111111111100100',
+	pack('C', 5)	=> '1111111111111111111111100101',
+	pack('C', 6)	=> '1111111111111111111111100110',
+	pack('C', 7)	=> '1111111111111111111111100111',
+	pack('C', 8)	=> '1111111111111111111111101000',
+	pack('C', 9)	=> '111111111111111111101010',
+	pack('C', 10)	=> '111111111111111111111111111100',
+	pack('C', 11)	=> '1111111111111111111111101001',
+	pack('C', 12)	=> '1111111111111111111111101010',
+	pack('C', 13)	=> '111111111111111111111111111101',
+	pack('C', 14)	=> '1111111111111111111111101011',
+	pack('C', 15)	=> '1111111111111111111111101100',
+	pack('C', 16)	=> '1111111111111111111111101101',
+	pack('C', 17)	=> '1111111111111111111111101110',
+	pack('C', 18)	=> '1111111111111111111111101111',
+	pack('C', 19)	=> '1111111111111111111111110000',
+	pack('C', 20)	=> '1111111111111111111111110001',
+	pack('C', 21)	=> '1111111111111111111111110010',
+	pack('C', 22)	=> '111111111111111111111111111110',
+	pack('C', 23)	=> '1111111111111111111111110011',
+	pack('C', 24)	=> '1111111111111111111111110100',
+	pack('C', 25)	=> '1111111111111111111111110101',
+	pack('C', 26)	=> '1111111111111111111111110110',
+	pack('C', 27)	=> '1111111111111111111111110111',
+	pack('C', 28)	=> '1111111111111111111111111000',
+	pack('C', 29)	=> '1111111111111111111111111001',
+	pack('C', 30)	=> '1111111111111111111111111010',
+	pack('C', 31)	=> '1111111111111111111111111011',
+	pack('C', 32)	=> '010100',
+	pack('C', 33)	=> '1111111000',
+	pack('C', 34)	=> '1111111001',
+	pack('C', 35)	=> '111111111010',
+	pack('C', 36)	=> '1111111111001',
+	pack('C', 37)	=> '010101',
+	pack('C', 38)	=> '11111000',
+	pack('C', 39)	=> '11111111010',
+	pack('C', 40)	=> '1111111010',
+	pack('C', 41)	=> '1111111011',
+	pack('C', 42)	=> '11111001',
+	pack('C', 43)	=> '11111111011',
+	pack('C', 44)	=> '11111010',
+	pack('C', 45)	=> '010110',
+	pack('C', 46)	=> '010111',
+	pack('C', 47)	=> '011000',
+	pack('C', 48)	=> '00000',
+	pack('C', 49)	=> '00001',
+	pack('C', 50)	=> '00010',
+	pack('C', 51)	=> '011001',
+	pack('C', 52)	=> '011010',
+	pack('C', 53)	=> '011011',
+	pack('C', 54)	=> '011100',
+	pack('C', 55)	=> '011101',
+	pack('C', 56)	=> '011110',
+	pack('C', 57)	=> '011111',
+	pack('C', 58)	=> '1011100',
+	pack('C', 59)	=> '11111011',
+	pack('C', 60)	=> '111111111111100',
+	pack('C', 61)	=> '100000',
+	pack('C', 62)	=> '111111111011',
+	pack('C', 63)	=> '1111111100',
+	pack('C', 64)	=> '1111111111010',
+	pack('C', 65)	=> '100001',
+	pack('C', 66)	=> '1011101',
+	pack('C', 67)	=> '1011110',
+	pack('C', 68)	=> '1011111',
+	pack('C', 69)	=> '1100000',
+	pack('C', 70)	=> '1100001',
+	pack('C', 71)	=> '1100010',
+	pack('C', 72)	=> '1100011',
+	pack('C', 73)	=> '1100100',
+	pack('C', 74)	=> '1100101',
+	pack('C', 75)	=> '1100110',
+	pack('C', 76)	=> '1100111',
+	pack('C', 77)	=> '1101000',
+	pack('C', 78)	=> '1101001',
+	pack('C', 79)	=> '1101010',
+	pack('C', 80)	=> '1101011',
+	pack('C', 81)	=> '1101100',
+	pack('C', 82)	=> '1101101',
+	pack('C', 83)	=> '1101110',
+	pack('C', 84)	=> '1101111',
+	pack('C', 85)	=> '1110000',
+	pack('C', 86)	=> '1110001',
+	pack('C', 87)	=> '1110010',
+	pack('C', 88)	=> '11111100',
+	pack('C', 89)	=> '1110011',
+	pack('C', 90)	=> '11111101',
+	pack('C', 91)	=> '1111111111011',
+	pack('C', 92)	=> '1111111111111110000',
+	pack('C', 93)	=> '1111111111100',
+	pack('C', 94)	=> '11111111111100',
+	pack('C', 95)	=> '100010',
+	pack('C', 96)	=> '111111111111101',
+	pack('C', 97)	=> '00011',
+	pack('C', 98)	=> '100011',
+	pack('C', 99)	=> '00100',
+	pack('C', 100)	=> '100100',
+	pack('C', 101)	=> '00101',
+	pack('C', 102)	=> '100101',
+	pack('C', 103)	=> '100110',
+	pack('C', 104)	=> '100111',
+	pack('C', 105)	=> '00110',
+	pack('C', 106)	=> '1110100',
+	pack('C', 107)	=> '1110101',
+	pack('C', 108)	=> '101000',
+	pack('C', 109)	=> '101001',
+	pack('C', 110)	=> '101010',
+	pack('C', 111)	=> '00111',
+	pack('C', 112)	=> '101011',
+	pack('C', 113)	=> '1110110',
+	pack('C', 114)	=> '101100',
+	pack('C', 115)	=> '01000',
+	pack('C', 116)	=> '01001',
+	pack('C', 117)	=> '101101',
+	pack('C', 118)	=> '1110111',
+	pack('C', 119)	=> '1111000',
+	pack('C', 120)	=> '1111001',
+	pack('C', 121)	=> '1111010',
+	pack('C', 122)	=> '1111011',
+	pack('C', 123)	=> '111111111111110',
+	pack('C', 124)	=> '11111111100',
+	pack('C', 125)	=> '11111111111101',
+	pack('C', 126)	=> '1111111111101',
+	pack('C', 127)	=> '1111111111111111111111111100',
+	pack('C', 128)	=> '11111111111111100110',
+	pack('C', 129)	=> '1111111111111111010010',
+	pack('C', 130)	=> '11111111111111100111',
+	pack('C', 131)	=> '11111111111111101000',
+	pack('C', 132)	=> '1111111111111111010011',
+	pack('C', 133)	=> '1111111111111111010100',
+	pack('C', 134)	=> '1111111111111111010101',
+	pack('C', 135)	=> '11111111111111111011001',
+	pack('C', 136)	=> '1111111111111111010110',
+	pack('C', 137)	=> '11111111111111111011010',
+	pack('C', 138)	=> '11111111111111111011011',
+	pack('C', 139)	=> '11111111111111111011100',
+	pack('C', 140)	=> '11111111111111111011101',
+	pack('C', 141)	=> '11111111111111111011110',
+	pack('C', 142)	=> '111111111111111111101011',
+	pack('C', 143)	=> '11111111111111111011111',
+	pack('C', 144)	=> '111111111111111111101100',
+	pack('C', 145)	=> '111111111111111111101101',
+	pack('C', 146)	=> '1111111111111111010111',
+	pack('C', 147)	=> '11111111111111111100000',
+	pack('C', 148)	=> '111111111111111111101110',
+	pack('C', 149)	=> '11111111111111111100001',
+	pack('C', 150)	=> '11111111111111111100010',
+	pack('C', 151)	=> '11111111111111111100011',
+	pack('C', 152)	=> '11111111111111111100100',
+	pack('C', 153)	=> '111111111111111011100',
+	pack('C', 154)	=> '1111111111111111011000',
+	pack('C', 155)	=> '11111111111111111100101',
+	pack('C', 156)	=> '1111111111111111011001',
+	pack('C', 157)	=> '11111111111111111100110',
+	pack('C', 158)	=> '11111111111111111100111',
+	pack('C', 159)	=> '111111111111111111101111',
+	pack('C', 160)	=> '1111111111111111011010',
+	pack('C', 161)	=> '111111111111111011101',
+	pack('C', 162)	=> '11111111111111101001',
+	pack('C', 163)	=> '1111111111111111011011',
+	pack('C', 164)	=> '1111111111111111011100',
+	pack('C', 165)	=> '11111111111111111101000',
+	pack('C', 166)	=> '11111111111111111101001',
+	pack('C', 167)	=> '111111111111111011110',
+	pack('C', 168)	=> '11111111111111111101010',
+	pack('C', 169)	=> '1111111111111111011101',
+	pack('C', 170)	=> '1111111111111111011110',
+	pack('C', 171)	=> '111111111111111111110000',
+	pack('C', 172)	=> '111111111111111011111',
+	pack('C', 173)	=> '1111111111111111011111',
+	pack('C', 174)	=> '11111111111111111101011',
+	pack('C', 175)	=> '11111111111111111101100',
+	pack('C', 176)	=> '111111111111111100000',
+	pack('C', 177)	=> '111111111111111100001',
+	pack('C', 178)	=> '1111111111111111100000',
+	pack('C', 179)	=> '111111111111111100010',
+	pack('C', 180)	=> '11111111111111111101101',
+	pack('C', 181)	=> '1111111111111111100001',
+	pack('C', 182)	=> '11111111111111111101110',
+	pack('C', 183)	=> '11111111111111111101111',
+	pack('C', 184)	=> '11111111111111101010',
+	pack('C', 185)	=> '1111111111111111100010',
+	pack('C', 186)	=> '1111111111111111100011',
+	pack('C', 187)	=> '1111111111111111100100',
+	pack('C', 188)	=> '11111111111111111110000',
+	pack('C', 189)	=> '1111111111111111100101',
+	pack('C', 190)	=> '1111111111111111100110',
+	pack('C', 191)	=> '11111111111111111110001',
+	pack('C', 192)	=> '11111111111111111111100000',
+	pack('C', 193)	=> '11111111111111111111100001',
+	pack('C', 194)	=> '11111111111111101011',
+	pack('C', 195)	=> '1111111111111110001',
+	pack('C', 196)	=> '1111111111111111100111',
+	pack('C', 197)	=> '11111111111111111110010',
+	pack('C', 198)	=> '1111111111111111101000',
+	pack('C', 199)	=> '1111111111111111111101100',
+	pack('C', 200)	=> '11111111111111111111100010',
+	pack('C', 201)	=> '11111111111111111111100011',
+	pack('C', 202)	=> '11111111111111111111100100',
+	pack('C', 203)	=> '111111111111111111111011110',
+	pack('C', 204)	=> '111111111111111111111011111',
+	pack('C', 205)	=> '11111111111111111111100101',
+	pack('C', 206)	=> '111111111111111111110001',
+	pack('C', 207)	=> '1111111111111111111101101',
+	pack('C', 208)	=> '1111111111111110010',
+	pack('C', 209)	=> '111111111111111100011',
+	pack('C', 210)	=> '11111111111111111111100110',
+	pack('C', 211)	=> '111111111111111111111100000',
+	pack('C', 212)	=> '111111111111111111111100001',
+	pack('C', 213)	=> '11111111111111111111100111',
+	pack('C', 214)	=> '111111111111111111111100010',
+	pack('C', 215)	=> '111111111111111111110010',
+	pack('C', 216)	=> '111111111111111100100',
+	pack('C', 217)	=> '111111111111111100101',
+	pack('C', 218)	=> '11111111111111111111101000',
+	pack('C', 219)	=> '11111111111111111111101001',
+	pack('C', 220)	=> '1111111111111111111111111101',
+	pack('C', 221)	=> '111111111111111111111100011',
+	pack('C', 222)	=> '111111111111111111111100100',
+	pack('C', 223)	=> '111111111111111111111100101',
+	pack('C', 224)	=> '11111111111111101100',
+	pack('C', 225)	=> '111111111111111111110011',
+	pack('C', 226)	=> '11111111111111101101',
+	pack('C', 227)	=> '111111111111111100110',
+	pack('C', 228)	=> '1111111111111111101001',
+	pack('C', 229)	=> '111111111111111100111',
+	pack('C', 230)	=> '111111111111111101000',
+	pack('C', 231)	=> '11111111111111111110011',
+	pack('C', 232)	=> '1111111111111111101010',
+	pack('C', 233)	=> '1111111111111111101011',
+	pack('C', 234)	=> '1111111111111111111101110',
+	pack('C', 235)	=> '1111111111111111111101111',
+	pack('C', 236)	=> '111111111111111111110100',
+	pack('C', 237)	=> '111111111111111111110101',
+	pack('C', 238)	=> '11111111111111111111101010',
+	pack('C', 239)	=> '11111111111111111110100',
+	pack('C', 240)	=> '11111111111111111111101011',
+	pack('C', 241)	=> '111111111111111111111100110',
+	pack('C', 242)	=> '11111111111111111111101100',
+	pack('C', 243)	=> '11111111111111111111101101',
+	pack('C', 244)	=> '111111111111111111111100111',
+	pack('C', 245)	=> '111111111111111111111101000',
+	pack('C', 246)	=> '111111111111111111111101001',
+	pack('C', 247)	=> '111111111111111111111101010',
+	pack('C', 248)	=> '111111111111111111111101011',
+	pack('C', 249)	=> '1111111111111111111111111110',
+	pack('C', 250)	=> '111111111111111111111101100',
+	pack('C', 251)	=> '111111111111111111111101101',
+	pack('C', 252)	=> '111111111111111111111101110',
+	pack('C', 253)	=> '111111111111111111111101111',
+	pack('C', 254)	=> '111111111111111111111110000',
+	pack('C', 255)	=> '11111111111111111111101110',
+	'_eos'		=> '111111111111111111111111111111',
+}};
+
+sub huff {
+	my ($string) = @_;
+	my $code = &huff_code;
+
+	my $ret = join '', map { $code->{$_} } (split //, $string);
+	my $len = length($ret) + (8 - length($ret) % 8);
+	$ret .= $code->{_eos};
+
+	return pack("B$len", $ret);
+}
+
+sub dehuff {
+	my ($string) = @_;
+	my $code = &huff_code;
+	my %decode = reverse %$code;
+
+	my $ret = ''; my $c = '';
+	for (split //, unpack('B*', $string)) {
+		$c .= $_;
+		next unless exists $decode{$c};
+		last if $decode{$c} eq '_eos';
+
+		$ret .= $decode{$c};
+		$c = '';
+	}
+
+	return $ret;
+}
+
+###############################################################################
+
+1;
+
+###############################################################################