[master] 66d2682 Add our skeleton HTTP2 implementation, which seems to survive at least trival traffic in real life.

Poul-Henning Kamp phk at FreeBSD.org
Tue Aug 30 13:45:12 CEST 2016


commit 66d2682647b8fc00c32f69bb4b1856d7d4dcc30c
Author: Poul-Henning Kamp <phk at FreeBSD.org>
Date:   Tue Aug 30 11:43:29 2016 +0000

    Add our skeleton HTTP2 implementation, which seems to survive
    at least trival traffic in real life.

diff --git a/bin/varnishd/Makefile.am b/bin/varnishd/Makefile.am
index 1a328fb..d26d982 100644
--- a/bin/varnishd/Makefile.am
+++ b/bin/varnishd/Makefile.am
@@ -58,6 +58,8 @@ varnishd_SOURCES = \
 	hash/hash_critbit.c \
 	hash/mgt_hash.c \
 	hash/hash_simple_list.c \
+	hpack/vhp_table.c \
+	hpack/vhp_decode.c \
 	http1/cache_http1_deliver.c \
 	http1/cache_http1_fetch.c \
 	http1/cache_http1_fsm.c \
@@ -65,6 +67,11 @@ varnishd_SOURCES = \
 	http1/cache_http1_pipe.c \
 	http1/cache_http1_proto.c \
 	http1/cache_http1_vfp.c \
+	http2/cache_http2_deliver.c \
+	http2/cache_http2_hpack.c \
+	http2/cache_http2_panic.c \
+	http2/cache_http2_proto.c \
+	http2/cache_http2_send.c \
 	mgt/mgt_acceptor.c \
 	mgt/mgt_child.c \
 	mgt/mgt_cli.c \
@@ -111,7 +118,9 @@ noinst_HEADERS = \
 	cache/cache_transport.h \
 	common/heritage.h \
 	hash/hash_slinger.h \
+	hpack/vhp.h \
 	http1/cache_http1.h \
+	http2/cache_http2.h \
 	mgt/mgt.h \
 	mgt/mgt_param.h \
 	storage/storage.h \
@@ -153,6 +162,26 @@ varnishd_LDADD = \
 EXTRA_DIST = builtin.vcl
 DISTCLEANFILES = builtin_vcl.h
 
+noinst_PROGRAMS = vhp_gen_hufdec
+vhp_gen_hufdec_SOURCES = hpack/vhp_gen_hufdec.c
+vhp_gen_hufdec_CFLAGS = -include config.h
+vhp_gen_hufdec_LDADD = \
+	$(top_builddir)/lib/libvarnish/libvarnish.la
+
+noinst_PROGRAMS += vhp_table_test
+vhp_table_test_SOURCES = hpack/vhp_table.c
+vhp_table_test_CFLAGS = -DTABLE_TEST_DRIVER -include config.h
+vhp_table_test_LDADD = \
+	$(top_builddir)/lib/libvarnish/libvarnish.la
+
+noinst_PROGRAMS += vhp_decode_test
+vhp_decode_test_SOURCES = hpack/vhp_decode.c hpack/vhp_table.c 
+vhp_decode_test_CFLAGS = -DDECODE_TEST_DRIVER -include config.h
+vhp_decode_test_LDADD = \
+	$(top_builddir)/lib/libvarnish/libvarnish.la
+
+TESTS = vhp_table_test vhp_decode_test
+
 #
 # Turn the builtin.vcl file into a C-string we can include in the program.
 #
@@ -168,5 +197,12 @@ builtin_vcl.h:	builtin.vcl
 	    -e 's/$$/\\n"/' \
 	    -e 's/^/ "/' $(srcdir)/builtin.vcl >> $@
 
+vhp_hufdec.h: vhp_gen_hufdec
+	$(AM_V_GEN) ./vhp_gen_hufdec > vhp_hufdec.h_
+	mv vhp_hufdec.h_ vhp_hufdec.h
+
+BUILT_SOURCES	=	vhp_hufdec.h
+DISTCLEANFILES	=	vhp_hufdec.h
+
 # Explicitly record dependency
 mgt/mgt_vcc.c:	builtin_vcl.h
diff --git a/bin/varnishd/cache/cache.h b/bin/varnishd/cache/cache.h
index 923a8d2..57f260c 100644
--- a/bin/varnishd/cache/cache.h
+++ b/bin/varnishd/cache/cache.h
@@ -816,6 +816,9 @@ extern const char H__Status[];
 extern const char H__Proto[];
 extern const char H__Reason[];
 
+/* cache_http2_deliver.c */
+void V2D_Init(void);
+
 /* cache_main.c */
 #define VXID(u) ((u) & VSL_IDENTMASK)
 uint32_t VXID_Get(struct worker *, uint32_t marker);
diff --git a/bin/varnishd/cache/cache_acceptor.c b/bin/varnishd/cache/cache_acceptor.c
index 58481d5..d1f001a 100644
--- a/bin/varnishd/cache/cache_acceptor.c
+++ b/bin/varnishd/cache/cache_acceptor.c
@@ -632,6 +632,7 @@ XPORT_Init(void)
 
 	VTAILQ_INSERT_TAIL(&transports, &PROXY_transport, list);
 	VTAILQ_INSERT_TAIL(&transports, &HTTP1_transport, list);
+	VTAILQ_INSERT_TAIL(&transports, &H2_transport, list);
 
 	n = 0;
 	VTAILQ_FOREACH(xp, &transports, list)
diff --git a/bin/varnishd/cache/cache_main.c b/bin/varnishd/cache/cache_main.c
index 6e67968..7c076e2 100644
--- a/bin/varnishd/cache/cache_main.c
+++ b/bin/varnishd/cache/cache_main.c
@@ -241,6 +241,7 @@ child_main(void)
 	VBE_InitCfg();
 	Pool_Init();
 	V1P_Init();
+	V2D_Init();
 
 	EXP_Init();
 	HSH_Init(heritage.hash);
diff --git a/bin/varnishd/cache/cache_transport.h b/bin/varnishd/cache/cache_transport.h
index ef57325..2633cfe 100644
--- a/bin/varnishd/cache/cache_transport.h
+++ b/bin/varnishd/cache/cache_transport.h
@@ -66,6 +66,8 @@ struct transport {
 
 extern struct transport PROXY_transport;
 extern struct transport HTTP1_transport;
+extern struct transport H2_transport;
+htc_complete_f H2_prism_complete;
 
 const struct transport *XPORT_ByNumber(uint16_t no);
 void VPX_Send_Proxy(int fd, int version, const struct sess *);
diff --git a/bin/varnishd/flint.sh b/bin/varnishd/flint.sh
index f96d0fc..ba15504 100755
--- a/bin/varnishd/flint.sh
+++ b/bin/varnishd/flint.sh
@@ -23,6 +23,7 @@ flexelint \
 	common/*.c \
 	hash/*.c \
 	http1/*.c \
+	http2/*.c \
 	mgt/*.c \
 	proxy/*.c \
 	storage/*.c \
diff --git a/bin/varnishd/hpack/vhp.h b/bin/varnishd/hpack/vhp.h
new file mode 100644
index 0000000..0f9852d
--- /dev/null
+++ b/bin/varnishd/hpack/vhp.h
@@ -0,0 +1,129 @@
+/*-
+ * Copyright (c) 2016 Varnish Software
+ * All rights reserved.
+ *
+ * Author: Martin Blix Grydeland <martin at varnish-software.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <stdint.h>
+
+/* VHT - Varnish HPACK Table */
+
+#define VHT_ENTRY_SIZE 32U
+
+struct vht_entry {
+	unsigned		magic;
+#define	VHT_ENTRY_MAGIC		0xc06dd892
+	unsigned		offset;
+	unsigned		namelen;
+	unsigned		valuelen;
+};
+
+struct vht_table {
+	unsigned		magic;
+#define VHT_TABLE_MAGIC		0x6bbdc683
+	unsigned		n;
+	unsigned		size;
+	unsigned		maxsize; /* n * 32 + size <= maxsize */
+	unsigned		protomax;
+	unsigned		bufsize;
+	char			*buf;
+};
+
+void VHT_NewEntry(struct vht_table *);
+int VHT_NewEntry_Indexed(struct vht_table *, unsigned);
+void VHT_AppendName(struct vht_table *, const char *, ssize_t);
+void VHT_AppendValue(struct vht_table *, const char *, ssize_t);
+int VHT_SetMaxTableSize(struct vht_table *, size_t);
+int VHT_SetProtoMax(struct vht_table *, size_t);
+const char *VHT_LookupName(const struct vht_table *, unsigned, size_t *);
+const char *VHT_LookupValue(const struct vht_table *, unsigned, size_t *);
+int VHT_Init(struct vht_table *, size_t);
+void VHT_Fini(struct vht_table *);
+
+/* VHD - Varnish HPACK Decoder */
+
+enum vhd_ret_e {
+#define VHD_RET(NAME, VAL, DESC)		\
+	VHD_##NAME = VAL,
+#include "tbl/vhd_return.h"
+#undef VHD_RET
+};
+
+struct vhd_int {
+	uint8_t			magic;
+#define VHD_INT_MAGIC		0x05
+
+	uint8_t			pfx;
+	uint8_t			m;
+	unsigned		v;
+};
+
+struct vhd_raw {
+	uint8_t			magic;
+#define VHD_RAW_MAGIC		0xa0
+
+	unsigned		l;
+};
+
+struct vhd_huffman {
+	uint8_t			magic;
+#define VHD_HUFFMAN_MAGIC	0x56
+
+	uint8_t			blen;
+	uint16_t		bits;
+	uint16_t		pos;
+	unsigned		len;
+};
+
+struct vhd_lookup {
+	uint8_t			magic;
+#define VHD_LOOKUP_MAGIC	0x65
+
+	unsigned		l;
+};
+
+struct vhd_decode {
+	unsigned		magic;
+#define VHD_DECODE_MAGIC	0x9cbc72b2
+
+	unsigned		index;
+	uint16_t		state;
+	int8_t			error;
+	uint8_t			first;
+
+	union {
+		struct vhd_int		integer[1];
+		struct vhd_lookup	lookup[1];
+		struct vhd_raw		raw[1];
+		struct vhd_huffman	huffman[1];
+	};
+};
+
+void VHD_Init(struct vhd_decode *);
+enum vhd_ret_e  VHD_Decode(struct vhd_decode *, struct vht_table *,
+    const uint8_t *in, size_t inlen, size_t *p_inused,
+    char *out, size_t outlen, size_t *p_outused);
+const char *VHD_Error(enum vhd_ret_e);
diff --git a/bin/varnishd/hpack/vhp_decode.c b/bin/varnishd/hpack/vhp_decode.c
new file mode 100644
index 0000000..334db2d
--- /dev/null
+++ b/bin/varnishd/hpack/vhp_decode.c
@@ -0,0 +1,1218 @@
+/*-
+ * Copyright (c) 2016 Varnish Software
+ * All rights reserved.
+ *
+ * Author: Martin Blix Grydeland <martin at varnish-software.com>
+ * Author: Dridi Boukelmoune <dridi.boukelmoune at gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+#include <stdlib.h>
+
+#include "vdef.h"
+#include "vas.h"
+#include "miniobj.h"
+
+#include "hpack/vhp.h"
+
+#include "vhp_hufdec.h"
+
+struct vhd_ctx {
+	struct vhd_decode *d;
+	struct vht_table *tbl;
+	const uint8_t *in;
+	const uint8_t *in_e;
+	char *out;
+	char *out_e;
+};
+
+typedef enum vhd_ret_e vhd_state_f(struct vhd_ctx *ctx, unsigned first);
+
+/* Function flags */
+#define VHD_INCREMENTAL	(1U << 0)
+
+/* Functions */
+enum vhd_func_e {
+#define VHD_FSM_FUNC(NAME, func)		\
+	VHD_F_##NAME,
+#include "tbl/vhd_fsm_funcs.h"
+#undef VHD_FSM_FUNC
+	VHD_F__MAX,
+};
+#define VHD_FSM_FUNC(NAME, func)		\
+	static vhd_state_f func;
+#include "tbl/vhd_fsm_funcs.h"
+#undef VHD_FSM_FUNC
+
+/* States */
+enum vhd_state_e {
+#define VHD_FSM(STATE, FUNC, arg1, arg2)	\
+	VHD_S_##STATE,
+#include "tbl/vhd_fsm.h"
+#undef VHD_FSM
+	VHD_S__MAX,
+};
+static const struct vhd_state {
+	const char		*name;
+	enum vhd_func_e		func;
+	unsigned		arg1;
+	unsigned		arg2;
+} vhd_states[VHD_S__MAX] = {
+#define VHD_FSM(STATE, FUNC, arg1, arg2)	\
+	[VHD_S_##STATE] = { #STATE, VHD_F_##FUNC, arg1, arg2 },
+#include "tbl/vhd_fsm.h"
+#undef VHD_FSM
+};
+
+/* Utility functions */
+static void
+vhd_set_state(struct vhd_decode *d, enum vhd_state_e state)
+{
+	AN(d);
+	assert(state >= 0 && state < VHD_S__MAX);
+	d->state = state;
+	d->first = 1;
+}
+
+static void
+vhd_next_state(struct vhd_decode *d)
+{
+	AN(d);
+	assert(d->state + 1 < VHD_S__MAX);
+	vhd_set_state(d, d->state + 1);
+}
+
+/* State functions */
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_skip(struct vhd_ctx *ctx, unsigned first)
+{
+	AN(ctx);
+	AN(first);
+	vhd_next_state(ctx->d);
+	return (VHD_AGAIN);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_goto(struct vhd_ctx *ctx, unsigned first)
+{
+	const struct vhd_state *s;
+
+	AN(ctx);
+	AN(first);
+	assert(ctx->d->state < VHD_S__MAX);
+	s = &vhd_states[ctx->d->state];
+	assert(s->arg1 < VHD_S__MAX);
+	vhd_set_state(ctx->d, s->arg1);
+	return (VHD_AGAIN);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_idle(struct vhd_ctx *ctx, unsigned first)
+{
+	uint8_t c;
+
+	AN(ctx);
+	(void)first;
+
+	while (ctx->in < ctx->in_e) {
+		c = *ctx->in;
+		if ((c & 0x80) == 0x80)
+			vhd_set_state(ctx->d, VHD_S_HP61_START);
+		else if ((c & 0xc0) == 0x40)
+			vhd_set_state(ctx->d, VHD_S_HP621_START);
+		else if ((c & 0xf0) == 0x00)
+			vhd_set_state(ctx->d, VHD_S_HP622_START);
+		else if ((c & 0xf0) == 0x10)
+			vhd_set_state(ctx->d, VHD_S_HP623_START);
+		else if ((c & 0xe0) == 0x20)
+			vhd_set_state(ctx->d, VHD_S_HP63_START);
+		else
+			return (VHD_ERR_ARG);
+		return (VHD_AGAIN);
+	}
+
+	return (VHD_OK);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_integer(struct vhd_ctx *ctx, unsigned first)
+{
+	const struct vhd_state *s;
+	struct vhd_int *i;
+	uint8_t c;
+	unsigned mask;
+
+	assert(UINT_MAX >= UINT32_MAX);
+
+	AN(ctx);
+	assert(ctx->d->state < VHD_S__MAX);
+	s = &vhd_states[ctx->d->state];
+	i = ctx->d->integer;
+
+	if (first) {
+		INIT_OBJ(i, VHD_INT_MAGIC);
+		i->pfx = s->arg1;
+		assert(i->pfx >= 4 && i->pfx <= 7);
+	}
+	CHECK_OBJ_NOTNULL(i, VHD_INT_MAGIC);
+
+	while (ctx->in < ctx->in_e) {
+		c = *ctx->in;
+		ctx->in++;
+		if (i->pfx) {
+			mask = (1U << i->pfx) - 1;
+			i->pfx = 0;
+			i->v = c & mask;
+			if (i->v < mask) {
+				vhd_next_state(ctx->d);
+				return (VHD_AGAIN);
+			}
+		} else {
+			if ((i->m == 28 && (c & 0x78)) || i->m > 28)
+				return (VHD_ERR_INT);
+			i->v += (c & 0x7f) * ((uint32_t)1 << i->m);
+			i->m += 7;
+			if (!(c & 0x80)) {
+				vhd_next_state(ctx->d);
+				return (VHD_AGAIN);
+			}
+		}
+	}
+	return (VHD_MORE);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_set_max(struct vhd_ctx *ctx, unsigned first)
+{
+	AN(ctx);
+	AN(first);
+	CHECK_OBJ_NOTNULL(ctx->d->integer, VHD_INT_MAGIC);
+	if (ctx->tbl == NULL)
+		return (VHD_ERR_UPD);
+	if (VHT_SetMaxTableSize(ctx->tbl, ctx->d->integer->v))
+		return (VHD_ERR_UPD);
+	vhd_next_state(ctx->d);
+	return (VHD_AGAIN);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_set_idx(struct vhd_ctx *ctx, unsigned first)
+{
+	AN(ctx);
+	AN(first);
+	CHECK_OBJ_NOTNULL(ctx->d->integer, VHD_INT_MAGIC);
+	ctx->d->index = ctx->d->integer->v;
+	vhd_next_state(ctx->d);
+	return (VHD_AGAIN);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_lookup(struct vhd_ctx *ctx, unsigned first)
+{
+	const struct vhd_state *s;
+	struct vhd_lookup *lu;
+	const char *p;
+	size_t l;
+
+	AN(ctx);
+	assert(ctx->d->state < VHD_S__MAX);
+	s = &vhd_states[ctx->d->state];
+	lu = ctx->d->lookup;
+
+	if (first)
+		INIT_OBJ(lu, VHD_LOOKUP_MAGIC);
+	CHECK_OBJ_NOTNULL(lu, VHD_LOOKUP_MAGIC);
+
+	switch (s->arg1) {
+	case VHD_NAME:
+	case VHD_NAME_SEC:
+		p = VHT_LookupName(ctx->tbl, ctx->d->index, &l);
+		break;
+	case VHD_VALUE:
+	case VHD_VALUE_SEC:
+		p = VHT_LookupValue(ctx->tbl, ctx->d->index, &l);
+		break;
+	default:
+		WRONG("vhd_lookup wrong arg1");
+		break;
+	}
+	if (first && p == NULL)
+		return (VHD_ERR_IDX);
+	AN(p);
+	assert(l <= UINT_MAX);
+	if (first)
+		lu->l = l;
+
+	assert(lu->l <= l);
+	p += l - lu->l;
+	l = lu->l;
+	if (l > ctx->out_e - ctx->out)
+		l = ctx->out_e - ctx->out;
+	memcpy(ctx->out, p, l);
+	ctx->out += l;
+	lu->l -= l;
+
+	if (lu->l == 0) {
+		vhd_next_state(ctx->d);
+		return (s->arg1);
+	}
+	assert(ctx->out == ctx->out_e);
+	return (VHD_BUF);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_new(struct vhd_ctx *ctx, unsigned first)
+{
+	AN(ctx);
+	AN(first);
+	if (ctx->tbl != NULL)
+		VHT_NewEntry(ctx->tbl);
+	vhd_next_state(ctx->d);
+	return (VHD_AGAIN);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_new_idx(struct vhd_ctx *ctx, unsigned first)
+{
+	AN(ctx);
+	AN(first);
+	if (ctx->tbl != NULL) {
+		if (VHT_NewEntry_Indexed(ctx->tbl, ctx->d->index))
+			return (VHD_ERR_IDX);
+	}
+	vhd_next_state(ctx->d);
+	return (VHD_AGAIN);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_branch_zidx(struct vhd_ctx *ctx, unsigned first)
+{
+	const struct vhd_state *s;
+
+	AN(ctx);
+	(void)first;
+	assert(ctx->d->state < VHD_S__MAX);
+	s = &vhd_states[ctx->d->state];
+	assert(s->arg1 < VHD_S__MAX);
+
+	if (ctx->d->index == 0)
+		vhd_set_state(ctx->d, s->arg1);
+	else
+		vhd_next_state(ctx->d);
+	return (VHD_AGAIN);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_branch_bit0(struct vhd_ctx *ctx, unsigned first)
+{
+	const struct vhd_state *s;
+
+	AN(ctx);
+	(void)first;
+	assert(ctx->d->state < VHD_S__MAX);
+	s = &vhd_states[ctx->d->state];
+	assert(s->arg1 < VHD_S__MAX);
+
+	if (ctx->in == ctx->in_e)
+		return (VHD_MORE);
+
+	if (*ctx->in & 0x80)
+		vhd_set_state(ctx->d, s->arg1);
+	else
+		vhd_next_state(ctx->d);
+	return (VHD_AGAIN);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_raw(struct vhd_ctx *ctx, unsigned first)
+{
+	const struct vhd_state *s;
+	struct vhd_raw *raw;
+	size_t l2;
+
+	AN(ctx);
+	assert(ctx->d->state < VHD_S__MAX);
+	s = &vhd_states[ctx->d->state];
+
+	raw = ctx->d->raw;
+	if (first) {
+		CHECK_OBJ_NOTNULL(ctx->d->integer, VHD_INT_MAGIC);
+		l2 = ctx->d->integer->v;
+		INIT_OBJ(raw, VHD_RAW_MAGIC);
+		raw->l = l2;
+	}
+	CHECK_OBJ_NOTNULL(raw, VHD_RAW_MAGIC);
+
+	while (raw->l > 0) {
+		l2 = raw->l;
+		if (l2 > (ctx->in_e - ctx->in))
+			l2 = ctx->in_e - ctx->in;
+		if (l2 == 0)
+			return (VHD_MORE);
+		if (l2 > (ctx->out_e - ctx->out))
+			l2 = ctx->out_e - ctx->out;
+		if (l2 == 0)
+			return (VHD_BUF);
+		memcpy(ctx->out, ctx->in, l2);
+		ctx->in += l2;
+		if (ctx->tbl != NULL && (s->arg2 & VHD_INCREMENTAL)) {
+			switch (s->arg1) {
+			case VHD_NAME:
+				VHT_AppendName(ctx->tbl, ctx->out, l2);
+				break;
+			case VHD_VALUE:
+				VHT_AppendValue(ctx->tbl, ctx->out, l2);
+				break;
+			default:
+				WRONG("vhd_raw wrong arg1");
+				break;
+			}
+		}
+		ctx->out += l2;
+		raw->l -= l2;
+	}
+	vhd_next_state(ctx->d);
+	return (s->arg1);
+}
+
+static enum vhd_ret_e __match_proto__(vhd_state_f)
+vhd_huffman(struct vhd_ctx *ctx, unsigned first)
+{
+	const struct vhd_state *s;
+	struct vhd_huffman *huf;
+	enum vhd_ret_e r;
+	unsigned u, l;
+
+	AN(ctx);
+	assert(ctx->d->state < VHD_S__MAX);
+	s = &vhd_states[ctx->d->state];
+
+	huf = ctx->d->huffman;
+	if (first) {
+		CHECK_OBJ_NOTNULL(ctx->d->integer, VHD_INT_MAGIC);
+		l = ctx->d->integer->v;
+		INIT_OBJ(huf, VHD_HUFFMAN_MAGIC);
+		huf->len = l;
+	}
+	CHECK_OBJ_NOTNULL(huf, VHD_HUFFMAN_MAGIC);
+
+	r = VHD_OK;
+	l = 0;
+	while (1) {
+		assert(huf->pos < HUFDEC_LEN);
+		assert(hufdec[huf->pos].mask > 0);
+		assert(hufdec[huf->pos].mask <= 8);
+
+		if (huf->len > 0 && huf->blen < hufdec[huf->pos].mask) {
+			/* Refill from input */
+			if (ctx->in == ctx->in_e) {
+				r = VHD_MORE;
+				break;
+			}
+			huf->bits = (huf->bits << 8) | *ctx->in;
+			huf->blen += 8;
+			huf->len--;
+			ctx->in++;
+		}
+
+		if (huf->len == 0 && huf->pos == 0 && huf->blen <= 7 &&
+		    huf->bits == (1U << huf->blen) - 1U) {
+			/* End of stream */
+			r = s->arg1;
+			vhd_next_state(ctx->d);
+			break;
+		}
+
+		if (ctx->out + l == ctx->out_e) {
+			r = VHD_BUF;
+			break;
+		}
+
+		if (huf->blen >= hufdec[huf->pos].mask)
+			u = huf->bits >> (huf->blen - hufdec[huf->pos].mask);
+		else
+			u = huf->bits << (hufdec[huf->pos].mask - huf->blen);
+		huf->pos += u;
+		assert(huf->pos < HUFDEC_LEN);
+
+		if (hufdec[huf->pos].len == 0 ||
+		    hufdec[huf->pos].len > huf->blen) {
+			/* Invalid or incomplete code */
+			r = VHD_ERR_HUF;
+			break;
+		}
+
+		huf->blen -= hufdec[huf->pos].len;
+		huf->bits &= (1U << huf->blen) - 1U;
+
+		if (hufdec[huf->pos].jump) {
+			huf->pos += hufdec[huf->pos].jump;
+			assert(huf->pos < HUFDEC_LEN);
+		} else {
+			ctx->out[l++] = hufdec[huf->pos].chr;
+			huf->pos = 0;
+		}
+	}
+
+	if (l > 0 && ctx->tbl != NULL && (s->arg2 & VHD_INCREMENTAL)) {
+		switch (s->arg1) {
+		case VHD_NAME:
+			VHT_AppendName(ctx->tbl, ctx->out, l);
+			break;
+		case VHD_VALUE:
+			VHT_AppendValue(ctx->tbl, ctx->out, l);
+			break;
+		default:
+			WRONG("vhd_raw wrong arg1");
+			break;
+		}
+	}
+	ctx->out += l;
+
+	assert(r != VHD_OK);
+	return (r);
+}
+
+/* Public interface */
+
+const char *
+VHD_Error(enum vhd_ret_e r)
+{
+	switch (r) {
+#define VHD_RET(NAME, VAL, DESC)			\
+	case VHD_##NAME:				\
+		return ("VHD_" #NAME " (" DESC ")");
+#include "tbl/vhd_return.h"
+#undef VHD_RET
+	default:
+		return ("VHD_UNKNOWN");
+	}
+}
+
+enum vhd_ret_e
+VHD_Decode(struct vhd_decode *d, struct vht_table *tbl,
+    const uint8_t *in, size_t inlen, size_t *p_inused,
+    char *out, size_t outlen, size_t *p_outused)
+{
+	const struct vhd_state *s;
+	struct vhd_ctx ctx[1];
+	enum vhd_ret_e ret;
+	unsigned first;
+
+	CHECK_OBJ_NOTNULL(d, VHD_DECODE_MAGIC);
+	CHECK_OBJ_ORNULL(tbl, VHT_TABLE_MAGIC);
+	AN(in);
+	AN(p_inused);
+	AN(out);
+	AN(p_outused);
+
+	if (d->error < 0)
+		return (d->error);
+
+	assert(*p_inused <= inlen);
+	assert(*p_outused <= outlen);
+
+	ctx->d = d;
+	ctx->tbl = tbl;
+	ctx->in = in + *p_inused;
+	ctx->in_e = in + inlen;
+	ctx->out = out + *p_outused;
+	ctx->out_e = out + outlen;
+
+	do {
+		first = d->first;
+		d->first = 0;
+		assert(d->state < VHD_S__MAX);
+		s = &vhd_states[d->state];
+		switch (s->func) {
+#define VHD_FSM_FUNC(NAME, func)		\
+		case VHD_F_##NAME:		\
+			ret = func(ctx, first);	\
+			break;
+#include "tbl/vhd_fsm_funcs.h"
+#undef VHD_FSM_FUNC
+		default:
+			WRONG("Undefined vhd function");
+			break;
+		}
+	} while (ret == VHD_AGAIN);
+
+	if (ret < 0)
+		d->error = ret;
+
+	assert(in + *p_inused <= ctx->in);
+	*p_inused += ctx->in - (in + *p_inused);
+	assert(out + *p_outused <= ctx->out);
+	*p_outused += ctx->out - (out + *p_outused);
+
+	return (ret);
+}
+
+void
+VHD_Init(struct vhd_decode *d)
+{
+
+	AN(d);
+	assert(VHD_S__MAX <= UINT16_MAX);
+	assert(HUFDEC_LEN <= UINT16_MAX);
+	INIT_OBJ(d, VHD_DECODE_MAGIC);
+	d->state = VHD_S_IDLE;
+	d->first = 1;
+}
+
+/* Test driver */
+
+#ifdef DECODE_TEST_DRIVER
+
+#include <ctype.h>
+#include <stdarg.h>
+
+static int verbose = 0;
+
+static size_t
+hexbuf(uint8_t *buf, size_t buflen, const char *h)
+{
+	size_t l;
+	uint8_t u;
+
+	AN(h);
+	AN(buf);
+
+	l = 0;
+	for (; *h != '\0'; h++) {
+		if (l == buflen * 2)
+			WRONG("Too small buffer");
+		if (isspace(*h))
+			continue;
+		if (*h >= '0' && *h <= '9')
+			u = *h - '0';
+		else if (*h >= 'a' && *h <= 'f')
+			u = 0xa + *h - 'a';
+		else if (*h >= 'A' && *h <= 'F')
+			u = 0xa + *h - 'A';
+		else
+			WRONG("Bad input character");
+		assert(u <= 0xf);
+		if (l % 2 == 0) {
+			u <<= 4;
+			buf[l / 2] = u;
+		} else {
+			buf[l / 2] |= u;
+		}
+		l++;
+	}
+	AZ(l % 2);
+	return (l / 2);
+}
+
+static int
+match(const char *b, size_t l, ...)
+{
+	va_list ap;
+	const char *e;
+	const char *m;
+	int r = 0;
+
+	va_start(ap, l);
+	e = b + l;
+	while (1) {
+		m = va_arg(ap, const char *);
+		if (m == NULL)
+			break;
+		l = strlen(m);
+		if (e - b <= l || b[l] != '\0' || strncmp(b, m, l)) {
+			printf("%.*s != %s\n", (int)(e - b), b, m);
+			r = -1;
+			break;
+		} else if (verbose) {
+			printf("%s == %s\n", b, m);
+		}
+		b += l + 1;
+	}
+	va_end(ap);
+	return (r);
+}
+
+#define M_1IN (1U << 0)
+#define M_1OUT (1U << 1)
+
+static enum vhd_ret_e
+decode(struct vhd_decode *d, struct vht_table *tbl, uint8_t *in, size_t in_l,
+    char *out, size_t out_l, unsigned m)
+{
+	size_t in_u, out_u;
+	enum vhd_ret_e r;
+
+	CHECK_OBJ_NOTNULL(d, VHD_DECODE_MAGIC);
+	AN(in);
+	AN(out);
+
+	in_u = 0;
+	out_u = 0;
+
+	while (1) {
+		r = VHD_Decode(d, tbl, in,
+		    (m & M_1IN ? (in_l > in_u ? in_u + 1 : in_u) : in_l),
+		    &in_u,
+		    out,
+		    (m & M_1OUT ? (out_l > out_u ? out_u + 1 : out_u) : out_l),
+		    &out_u);
+		assert(in_u <= in_l);
+		assert(out_u <= out_l);
+		if (r < VHD_OK)
+			return (r);
+
+		switch (r) {
+		case VHD_OK:
+			return (r);
+
+		case VHD_MORE:
+			if (in_u == in_l)
+				return (r);
+			break;
+
+		case VHD_BUF:
+			if (out_u == out_l)
+				return (r);
+			break;
+
+		case VHD_NAME:
+		case VHD_NAME_SEC:
+			assert(out_l - out_u > 0);
+			out[out_u++] = '\0';
+			if (verbose)
+				printf("Name%s: '%s'\n",
+				    (r == VHD_NAME_SEC ? " (sec)" : ""),
+				    out);
+			out += out_u;
+			out_l -= out_u;
+			out_u = 0;
+			break;
+
+		case VHD_VALUE:
+		case VHD_VALUE_SEC:
+			assert(out_l - out_u > 0);
+			out[out_u++] = '\0';
+			if (verbose)
+				printf("Value%s: '%s'\n",
+				    (r == VHD_VALUE_SEC ? " (sec)" : ""),
+				    out);
+			out += out_u;
+			out_l -= out_u;
+			out_u = 0;
+			break;
+
+		default:
+			WRONG("Wrong return code");
+			break;
+		}
+	}
+
+	NEEDLESS_RETURN(0);
+}
+
+#define CHECK_RET(r, e)					\
+	do {						\
+		if (verbose || r != e) {		\
+			printf("%s %s %s\n",		\
+			    VHD_Error(r),		\
+			    (r == e ? "==" : "!="),	\
+			    VHD_Error(e));		\
+		}					\
+		assert(r == e);				\
+	} while (0)
+
+#define CHECK_INT(d, u)							\
+	do {								\
+		CHECK_OBJ_NOTNULL(d->integer, VHD_INT_MAGIC);		\
+		if (verbose || d->integer->v != u) {			\
+			printf("%u %s %u\n", d->integer->v,		\
+			    (d->integer->v == u ? "==" : "!="),		\
+			    u);						\
+		}							\
+		assert(d->integer->v == u);				\
+	} while (0)
+
+static void
+test_integer(unsigned mode)
+{
+	struct vhd_decode d[1];
+	uint8_t in[128];
+	size_t in_l;
+	char out[128];
+	enum vhd_ret_e r;
+
+	/* Test single byte decoding */
+	VHD_Init(d);
+	vhd_set_state(d, VHD_S_TEST_INT5);
+	in_l = hexbuf(in, sizeof in, "1e");
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	CHECK_INT(d, 30);
+
+	/* Test multibyte decoding */
+	VHD_Init(d);
+	vhd_set_state(d, VHD_S_TEST_INT5);
+	in_l = hexbuf(in, sizeof in, "ff 9a 0a");
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	CHECK_INT(d, 1337);
+
+	/* Test max size we allow */
+	VHD_Init(d);
+	vhd_set_state(d, VHD_S_TEST_INT5);
+	in_l = hexbuf(in, sizeof in, "1f ff ff ff ff 07");
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	CHECK_INT(d, 0x8000001E);
+
+	/* Test overflow */
+	VHD_Init(d);
+	vhd_set_state(d, VHD_S_TEST_INT5);
+	in_l = hexbuf(in, sizeof in, "1f ff ff ff ff 08");
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_ERR_INT);
+}
+
+static void
+test_raw(unsigned mode)
+{
+	struct vhd_decode d[1];
+	uint8_t in[128];
+	size_t in_l;
+	char out[128];
+	enum vhd_ret_e r;
+
+	/* Test raw encoding */
+	VHD_Init(d);
+	vhd_set_state(d, VHD_S_TEST_LITERAL);
+	in_l = hexbuf(in, sizeof in,
+	    "0a63 7573 746f 6d2d 6b65 790d 6375 7374 6f6d 2d68 6561 6465 72");
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out, "custom-key", "custom-header", NULL));
+
+	/* Test too short input */
+	VHD_Init(d);
+	vhd_set_state(d, VHD_S_TEST_LITERAL);
+	in_l = hexbuf(in, sizeof in,
+	    "02");
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_MORE);
+}
+
+static void
+test_huffman(unsigned mode)
+{
+	struct vhd_decode d[1];
+	uint8_t in[256];
+	size_t in_l;
+	char out[256];
+	enum vhd_ret_e r;
+
+	/* Decode a huffman encoded value */
+	VHD_Init(d);
+	in_l = hexbuf(in, sizeof in,
+	    "0141 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff");
+	vhd_set_state(d, VHD_S_TEST_LITERAL);
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out, "A", "www.example.com", NULL));
+
+	/* Decode an incomplete input buffer */
+	VHD_Init(d);
+	in_l = hexbuf(in, sizeof in,
+	    "0141 81");
+	vhd_set_state(d, VHD_S_TEST_LITERAL);
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_MORE);
+
+	/* Decode an incomplete huffman code */
+	VHD_Init(d);
+	in_l = hexbuf(in, sizeof in,
+	    "0141 81 fe");
+	vhd_set_state(d, VHD_S_TEST_LITERAL);
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_ERR_HUF);
+
+	/* Decode an invalid huffman code */
+	VHD_Init(d);
+	in_l = hexbuf(in, sizeof in,
+	    "0141 84 ff ff ff ff");
+	vhd_set_state(d, VHD_S_TEST_LITERAL);
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_ERR_HUF);
+}
+
+static void
+test_c2(unsigned mode)
+{
+	struct vhd_decode d[1];
+	uint8_t in[256];
+	size_t in_l;
+	char out[256];
+	enum vhd_ret_e r;
+
+	/* See RFC 7541 Appendix C.2 */
+
+	VHD_Init(d);
+
+	/* C.2.1 */
+	in_l = hexbuf(in, sizeof in,
+	    "400a 6375 7374 6f6d 2d6b 6579 0d63 7573"
+	    "746f 6d2d 6865 6164 6572");
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		"custom-key", "custom-header",
+		NULL));
+
+	/* C.2.2 */
+	in_l = hexbuf(in, sizeof in,
+	    "040c 2f73 616d 706c 652f 7061 7468");
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":path", "/sample/path",
+		NULL));
+
+	/* C.2.3 */
+	in_l = hexbuf(in, sizeof in,
+	    "1008 7061 7373 776f 7264 0673 6563 7265"
+	    "74");
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		"password", "secret",
+		NULL));
+
+	/* C.2.4 */
+	in_l = hexbuf(in, sizeof in,
+	    "82");
+	r = decode(d, NULL, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":method", "GET",
+		NULL));
+}
+
+static void
+test_c3(unsigned mode)
+{
+	struct vht_table t[1];
+	struct vhd_decode d[1];
+	uint8_t in[256];
+	size_t in_l;
+	char out[256];
+	enum vhd_ret_e r;
+
+	/* See RFC 7541 Appendix C.3 */
+
+	AZ(VHT_Init(t, 4096));
+	VHD_Init(d);
+
+	/* C.3.1 */
+	in_l = hexbuf(in, sizeof in,
+	    "8286 8441 0f77 7777 2e65 7861 6d70 6c65"
+	    "2e63 6f6d");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":method", "GET",
+		":scheme", "http",
+		":path", "/",
+		":authority", "www.example.com",
+		NULL));
+
+	/* C.3.2 */
+	in_l = hexbuf(in, sizeof in,
+	    "8286 84be 5808 6e6f 2d63 6163 6865");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":method", "GET",
+		":scheme", "http",
+		":path", "/",
+		":authority", "www.example.com",
+		"cache-control", "no-cache",
+		NULL));
+
+	/* C.3.3 */
+	in_l = hexbuf(in, sizeof in,
+	    "8287 85bf 400a 6375 7374 6f6d 2d6b 6579"
+	    "0c63 7573 746f 6d2d 7661 6c75 65");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":method", "GET",
+		":scheme", "https",
+		":path", "/index.html",
+		":authority", "www.example.com",
+		"custom-key", "custom-value",
+		NULL));
+
+	VHT_Fini(t);
+}
+
+static void
+test_c4(unsigned mode)
+{
+	struct vht_table t[1];
+	struct vhd_decode d[1];
+	uint8_t in[256];
+	size_t in_l;
+	char out[256];
+	enum vhd_ret_e r;
+
+	/* See RFC 7541 Appendix C.4 */
+
+	AZ(VHT_Init(t, 4096));
+	VHD_Init(d);
+
+	/* C.4.1 */
+	in_l = hexbuf(in, sizeof in,
+	    "8286 8441 8cf1 e3c2 e5f2 3a6b a0ab 90f4 ff");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":method", "GET",
+		":scheme", "http",
+		":path", "/",
+		":authority", "www.example.com",
+		NULL));
+
+	/* C.4.2 */
+	in_l = hexbuf(in, sizeof in,
+	    "8286 84be 5886 a8eb 1064 9cbf");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":method", "GET",
+		":scheme", "http",
+		":path", "/",
+		":authority", "www.example.com",
+		"cache-control", "no-cache",
+		NULL));
+
+	/* C.4.3 */
+	in_l = hexbuf(in, sizeof in,
+	    "8287 85bf 4088 25a8 49e9 5ba9 7d7f 8925"
+	    "a849 e95b b8e8 b4bf");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":method", "GET",
+		":scheme", "https",
+		":path", "/index.html",
+		":authority", "www.example.com",
+		"custom-key", "custom-value",
+		NULL));
+
+	VHT_Fini(t);
+}
+
+static void
+test_c5(unsigned mode)
+{
+	struct vht_table t[1];
+	struct vhd_decode d[1];
+	uint8_t in[256];
+	size_t in_l;
+	char out[256];
+	enum vhd_ret_e r;
+
+	/* See RFC 7541 Appendix C.5 */
+
+	AZ(VHT_Init(t, 256));
+	VHD_Init(d);
+
+	/* C.5.1 */
+	in_l = hexbuf(in, sizeof in,
+	    "4803 3330 3258 0770 7269 7661 7465 611d"
+	    "4d6f 6e2c 2032 3120 4f63 7420 3230 3133"
+	    "2032 303a 3133 3a32 3120 474d 546e 1768"
+	    "7474 7073 3a2f 2f77 7777 2e65 7861 6d70"
+	    "6c65 2e63 6f6d");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":status", "302",
+		"cache-control", "private",
+		"date", "Mon, 21 Oct 2013 20:13:21 GMT",
+		"location", "https://www.example.com",
+		NULL));
+
+	/* C.5.2 */
+	in_l = hexbuf(in, sizeof in,
+	    "4803 3330 37c1 c0bf");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":status", "307",
+		"cache-control", "private",
+		"date", "Mon, 21 Oct 2013 20:13:21 GMT",
+		"location", "https://www.example.com",
+		NULL));
+
+	/* C.5.3 */
+	in_l = hexbuf(in, sizeof in,
+	    "88c1 611d 4d6f 6e2c 2032 3120 4f63 7420"
+	    "3230 3133 2032 303a 3133 3a32 3220 474d"
+	    "54c0 5a04 677a 6970 7738 666f 6f3d 4153"
+	    "444a 4b48 514b 425a 584f 5157 454f 5049"
+	    "5541 5851 5745 4f49 553b 206d 6178 2d61"
+	    "6765 3d33 3630 303b 2076 6572 7369 6f6e"
+	    "3d31");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":status", "200",
+		"cache-control", "private",
+		"date", "Mon, 21 Oct 2013 20:13:22 GMT",
+		"location", "https://www.example.com",
+		"content-encoding", "gzip",
+		"set-cookie",
+		"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+		NULL));
+
+	VHT_Fini(t);
+}
+
+static void
+test_c6(unsigned mode)
+{
+	struct vht_table t[1];
+	struct vhd_decode d[1];
+	uint8_t in[256];
+	size_t in_l;
+	char out[256];
+	enum vhd_ret_e r;
+
+	/* See RFC 7541 Appendix C.6 */
+
+	AZ(VHT_Init(t, 256));
+	VHD_Init(d);
+
+	/* C.6.1 */
+	in_l = hexbuf(in, sizeof in,
+	    "4882 6402 5885 aec3 771a 4b61 96d0 7abe"
+	    "9410 54d4 44a8 2005 9504 0b81 66e0 82a6"
+	    "2d1b ff6e 919d 29ad 1718 63c7 8f0b 97c8"
+	    "e9ae 82ae 43d3");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":status", "302",
+		"cache-control", "private",
+		"date", "Mon, 21 Oct 2013 20:13:21 GMT",
+		"location", "https://www.example.com",
+		NULL));
+
+	/* C.6.2 */
+	in_l = hexbuf(in, sizeof in,
+	    "4883 640e ffc1 c0bf");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":status", "307",
+		"cache-control", "private",
+		"date", "Mon, 21 Oct 2013 20:13:21 GMT",
+		"location", "https://www.example.com",
+		NULL));
+
+	/* C.6.3 */
+	in_l = hexbuf(in, sizeof in,
+	    "88c1 6196 d07a be94 1054 d444 a820 0595"
+	    "040b 8166 e084 a62d 1bff c05a 839b d9ab"
+	    "77ad 94e7 821d d7f2 e6c7 b335 dfdf cd5b"
+	    "3960 d5af 2708 7f36 72c1 ab27 0fb5 291f"
+	    "9587 3160 65c0 03ed 4ee5 b106 3d50 07");
+	r = decode(d, t, in, in_l, out, sizeof out, mode);
+	CHECK_RET(r, VHD_OK);
+	AZ(match(out, sizeof out,
+		":status", "200",
+		"cache-control", "private",
+		"date", "Mon, 21 Oct 2013 20:13:22 GMT",
+		"location", "https://www.example.com",
+		"content-encoding", "gzip",
+		"set-cookie",
+		"foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU; max-age=3600; version=1",
+		NULL));
+
+	VHT_Fini(t);
+}
+
+#define do_test(name)						\
+	do {							\
+		printf("Doing test: %s\n", #name);		\
+		name(0);					\
+		printf("Doing test: %s 1IN\n", #name);		\
+		name(M_1IN);					\
+		printf("Doing test: %s 1OUT\n", #name);		\
+		name(M_1OUT);					\
+		printf("Doing test: %s 1IN|1OUT\n", #name);	\
+		name(M_1IN|M_1OUT);				\
+		printf("Test finished: %s\n\n", #name);		\
+	} while (0)
+
+int
+main(int argc, char **argv)
+{
+	if (argc == 2 && !strcmp(argv[1], "-v"))
+		verbose = 1;
+	else if (argc != 1) {
+		fprintf(stderr, "Usage: %s [-v]\n", argv[0]);
+		return (1);
+	}
+
+	if (verbose) {
+		printf("sizeof (struct vhd_int)=%ju\n",
+		    sizeof (struct vhd_int));
+		printf("sizeof (struct vhd_lookup)=%ju\n",
+		    sizeof (struct vhd_lookup));
+		printf("sizeof (struct vhd_raw)=%ju\n",
+		    sizeof (struct vhd_raw));
+		printf("sizeof (struct vhd_huffman)=%ju\n",
+		    sizeof (struct vhd_huffman));
+		printf("sizeof (struct vhd_decode)=%ju\n",
+		    sizeof (struct vhd_decode));
+	}
+
+	do_test(test_integer);
+	do_test(test_raw);
+	do_test(test_huffman);
+
+	do_test(test_c2);
+	do_test(test_c3);
+	do_test(test_c4);
+	do_test(test_c5);
+	do_test(test_c6);
+
+	return (0);
+}
+
+#endif	/* DECODE_TEST_DRIVER */
diff --git a/bin/varnishd/hpack/vhp_gen_hufdec.c b/bin/varnishd/hpack/vhp_gen_hufdec.c
new file mode 100644
index 0000000..7372815
--- /dev/null
+++ b/bin/varnishd/hpack/vhp_gen_hufdec.c
@@ -0,0 +1,255 @@
+/*-
+ * Copyright (c) 2016 Dridi Boukelmoune
+ * All rights reserved.
+ *
+ * Author: Dridi Boukelmoune <dridi.boukelmoune at gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+#include <stdlib.h>
+#include <ctype.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <string.h>
+#include <limits.h>
+
+#include "vdef.h"
+#include "vas.h"
+
+static unsigned minlen = UINT_MAX;
+static unsigned maxlen = 0;
+static unsigned idx = 0;
+
+static const struct {
+	uint32_t	code;
+	unsigned	blen;
+	char		chr;
+} huf[] = {
+#define HPH(c, h, l) { h, l, (char)c },
+#include "tbl/vhp_huffman.h"
+#undef HPH
+};
+
+#define HUF_LEN (sizeof huf / sizeof huf[0])
+
+struct tbl;
+
+struct cod {
+	uint32_t		bits;
+	unsigned		len;
+	uint8_t			chr;
+	struct tbl		*next;
+};
+
+struct tbl {
+	unsigned		mask;
+	uint32_t		code;
+	unsigned		masked;
+	unsigned		n;
+	unsigned		idx;
+	unsigned		lvl;
+	unsigned		p_idx;
+	struct cod		e[];
+};
+
+static struct tbl *
+tbl_new(unsigned mask)
+{
+	unsigned n;
+	size_t size;
+	struct tbl *tbl;
+
+	assert(mask > 0);
+	assert(mask <= 8);
+	n = 1U << mask;
+	size = sizeof (struct tbl) + n * sizeof (struct cod);
+	tbl = calloc(1, size);
+	AN(tbl);
+	memset(tbl, 0, size);
+	tbl->mask = mask;
+	tbl->n = n;
+	tbl->idx = idx;
+	idx += n;
+	return (tbl);
+}
+
+static void
+tbl_add(struct tbl *tbl, uint32_t code, unsigned codelen,
+    uint32_t bits, unsigned len, char chr)
+{
+	uint32_t b;
+	unsigned u;
+
+	AN(tbl);
+	assert(codelen > 0);
+	assert(codelen <= maxlen);
+	assert(len > 0);
+	assert(tbl->mask > 0);
+
+	if (len > tbl->mask) {
+		/* Does not belong in this table */
+		b = bits >> (len - tbl->mask);
+		bits &= (1U << (len - tbl->mask)) - 1;
+		if (tbl->e[b].next == NULL) {
+			tbl->e[b].len = tbl->mask;
+			tbl->e[b].next = tbl_new(len - tbl->mask);
+			AN(tbl->e[b].next);
+
+			tbl->e[b].next->masked = tbl->masked + tbl->mask;
+			tbl->e[b].next->code = code;
+			tbl->e[b].next->lvl = tbl->lvl + 1;
+			tbl->e[b].next->p_idx = tbl->idx + b;
+		}
+		AN(tbl->e[b].next);
+		tbl_add(tbl->e[b].next, code, codelen,
+		    bits, len - tbl->mask, chr);
+		return;
+	}
+
+	bits = bits << (tbl->mask - len);
+	for (u = 0; u < (1U << (tbl->mask - len)); u++) {
+		b = bits | u;
+		assert(b < tbl->n);
+		AZ(tbl->e[b].len);
+		AZ(tbl->e[b].next);
+		tbl->e[b].len = len;
+		tbl->e[b].chr = chr;
+	}
+}
+
+static void
+print_lsb(uint32_t c, int l)
+{
+	assert(l <= 32);
+
+	while (l > 0) {
+		if (c & (1U << (l - 1)))
+			printf("1");
+		else
+			printf("0");
+		l--;
+	}
+}
+
+static void
+tbl_print(const struct tbl *tbl)
+{
+	unsigned u;
+
+	printf("/* Table: lvl=%u p_idx=%u n=%u mask=%u masked=%u */\n",
+	    tbl->lvl, tbl->p_idx, tbl->n, tbl->mask, tbl->masked);
+	for (u = 0; u < tbl->n; u++) {
+		printf("/* %3u: ", tbl->idx + u);
+		printf("%*s", maxlen - tbl->mask - tbl->masked, "");
+		printf("%*s", tbl->mask - tbl->e[u].len, "");
+
+		if (tbl->masked > 0) {
+			printf("(");
+			print_lsb(tbl->code >> tbl->mask, tbl->masked);
+			printf(") ");
+		} else
+			printf("   ");
+		if (tbl->e[u].len < tbl->mask) {
+			print_lsb(u >> (tbl->mask - tbl->e[u].len),
+			    tbl->e[u].len);
+			printf(" (");
+			print_lsb(u, tbl->mask - tbl->e[u].len);
+			printf(")");
+		} else {
+			assert(tbl->e[u].len == tbl->mask);
+			print_lsb(u, tbl->e[u].len);
+			printf("   ");
+		}
+		printf("%*s", 3 - (tbl->mask - tbl->e[u].len), "");
+		printf(" */ ");
+
+		if (tbl->e[u].next) {
+			/* Jump to next table */
+			assert(tbl->e[u].next->idx - (tbl->idx + u)
+			    <= UINT8_MAX);
+			printf("{ .len = %u, .jump = %u },",
+			    tbl->e[u].len,
+			    tbl->e[u].next->idx - (tbl->idx + u));
+			printf(" /* Next: %u */", tbl->e[u].next->idx);
+		} else if (tbl->e[u].len) {
+			printf("{ ");
+			printf(".len = %u", tbl->e[u].len);
+			printf(", .chr = (char)0x%02x", tbl->e[u].chr);
+			if (isgraph(tbl->e[u].chr))
+				printf(" /* '%c' */", tbl->e[u].chr);
+			if (u == 0)
+				/* First in table, set mask */
+				printf(", .mask = %u", tbl->mask);
+			printf(" },");
+		} else
+			printf("{ .len = 0 }, /* invalid */");
+		printf("\n");
+	}
+
+	for (u = 0; u < tbl->n; u++)
+		if (tbl->e[u].next)
+			tbl_print(tbl->e[u].next);
+}
+
+int
+main(int argc, const char **argv)
+{
+	struct tbl *top;
+	unsigned u;
+
+	(void)argc;
+	(void)argv;
+
+	for (u = 0; u < HUF_LEN; u++) {
+		if (maxlen < huf[u].blen)
+			maxlen = huf[u].blen;
+		if (minlen > huf[u].blen)
+			minlen = huf[u].blen;
+	}
+
+	top = tbl_new(8);
+	AN(top);
+
+	for (u = 0; u < HUF_LEN; u++)
+		tbl_add(top, huf[u].code, huf[u].blen,
+		    huf[u].code, huf[u].blen, huf[u].chr);
+
+	printf("/*\n");
+	printf(" * NB: This file is machine generated, DO NOT EDIT!\n");
+	printf(" */\n\n");
+
+	printf("#define HUFDEC_LEN %u\n", idx);
+	printf("#define HUFDEC_MIN %u\n", minlen);
+	printf("#define HUFDEC_MAX %u\n\n", maxlen);
+
+	printf("static const struct {\n");
+	printf("\tuint8_t\tmask;\n");
+	printf("\tuint8_t\tlen;\n");
+	printf("\tuint8_t\tjump;\n");
+	printf("\tchar\tchr;\n");
+	printf("} hufdec[HUFDEC_LEN] = {\n");
+	tbl_print(top);
+	printf("};\n");
+
+	return (0);
+}
diff --git a/bin/varnishd/hpack/vhp_table.c b/bin/varnishd/hpack/vhp_table.c
new file mode 100644
index 0000000..26a046b
--- /dev/null
+++ b/bin/varnishd/hpack/vhp_table.c
@@ -0,0 +1,785 @@
+/*-
+ * Copyright (c) 2016 Varnish Software
+ * All rights reserved.
+ *
+ * Author: Martin Blix Grydeland <martin at varnish-software.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+/*
+ * Layout:
+ *
+ * buf [
+ *    <x bytes name index n - 1> <x bytes value index n - 1>
+ *    <x bytes name index n - 2> <x bytes value index n - 2>
+ *    ...
+ *    <x bytes name index 0> <x bytes value index 0>
+ *
+ *    (padding bytes for pointer alignment)
+ *
+ *    <struct vht_entry index 0>
+ *    <struct vht_entry index 1>
+ *    ...
+ *    <struct vht_entry index n - 1>
+ * ]
+ *
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <stdint.h>
+#include <limits.h>
+
+#include "vdef.h"
+#include "miniobj.h"
+#include "vas.h"
+
+#include "hpack/vhp.h"
+
+#define VHT_STATIC_MAX 61
+
+struct vht_static {
+	const char *name;
+	unsigned namelen;
+	const char *value;
+	unsigned valuelen;
+};
+
+static const struct vht_static static_table[] = {
+#define HPS(NUM, NAME, VAL)			\
+	{ NAME, sizeof NAME - 1, VAL, sizeof VAL - 1 },
+#include "tbl/vhp_static.h"
+#undef HPS
+};
+
+#define TBLSIZE(tbl) ((tbl)->size + (tbl)->n * VHT_ENTRY_SIZE)
+#define ENTRIES(buf, bufsize, n)					\
+	(((struct vht_entry *)((uintptr_t)(buf) + bufsize)) - (n))
+#define TBLENTRIES(tbl) ENTRIES((tbl)->buf, (tbl)->bufsize, (tbl)->n)
+#define TBLENTRY(tbl, i) (&TBLENTRIES(tbl)[(i)])
+#define ENTRYLEN(e) ((e)->namelen + (e)->valuelen)
+#define ENTRYSIZE(e) (ENTRYLEN(e) + VHT_ENTRY_SIZE)
+
+/****************************************************************************/
+/* Internal interface */
+
+static void
+vht_newentry(struct vht_table *tbl)
+{
+	struct vht_entry *e;
+
+	assert(tbl->maxsize - TBLSIZE(tbl) >= VHT_ENTRY_SIZE);
+	tbl->n++;
+	e = TBLENTRY(tbl, 0);
+	INIT_OBJ(e, VHT_ENTRY_MAGIC);
+	e->offset = tbl->size;
+}
+
+/* Trim elements from the end until the table size is less than max. */
+static void
+vht_trim(struct vht_table *tbl, ssize_t max)
+{
+	unsigned u, v;
+	int i;
+	struct vht_entry *e;
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+
+	if (max < 0)
+		max = 0;
+	if (TBLSIZE(tbl) <= max)
+		return;
+
+	u = v = 0;
+	for (i = tbl->n - 1; i >= 0; i--) {
+		e = TBLENTRY(tbl, i);
+		CHECK_OBJ_NOTNULL(e, VHT_ENTRY_MAGIC);
+		if (TBLSIZE(tbl) - (u + v * VHT_ENTRY_SIZE) > max) {
+			/* Trim entry */
+			assert(e->offset == u);
+			u += ENTRYLEN(e);
+			v++;
+			e->magic = 0;
+		} else {
+			/* Fixup offset */
+			assert(e->offset >= u);
+			e->offset -= u;
+		}
+	}
+	assert(v <= tbl->n);
+
+	memmove(tbl->buf, tbl->buf + u, tbl->size - u);
+	memmove(TBLENTRY(tbl, v), TBLENTRY(tbl, 0), (tbl->n - v) * sizeof *e);
+	tbl->n -= v;
+	tbl->size -= u;
+}
+
+/* Append len bytes from buf to entry 0 name. Asserts if no space. */
+static void
+vht_appendname(struct vht_table *tbl, const char *buf, size_t len)
+{
+	struct vht_entry *e;
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+	e = TBLENTRY(tbl, 0);
+	CHECK_OBJ_NOTNULL(e, VHT_ENTRY_MAGIC);
+	AZ(e->valuelen);	/* Name needs to be set before value */
+	assert(TBLSIZE(tbl) + len <= tbl->maxsize);
+	assert(e->offset + e->namelen == tbl->size);
+	memcpy(tbl->buf + tbl->size, buf, len);
+	e->namelen += len;
+	tbl->size += len;
+}
+
+/* Append len bytes from buf to entry 0 value. Asserts if no space. */
+static void
+vht_appendvalue(struct vht_table *tbl, const char *buf, size_t len)
+{
+	struct vht_entry *e;
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+	e = TBLENTRY(tbl, 0);
+	CHECK_OBJ_NOTNULL(e, VHT_ENTRY_MAGIC);
+	assert(TBLSIZE(tbl) + len <= tbl->maxsize);
+	assert(e->offset + e->namelen + e->valuelen == tbl->size);
+	memcpy(tbl->buf + tbl->size, buf, len);
+	e->valuelen += len;
+	tbl->size += len;
+}
+
+/****************************************************************************/
+/* Public interface */
+
+void
+VHT_NewEntry(struct vht_table *tbl)
+{
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+	assert(tbl->maxsize <= tbl->protomax);
+	vht_trim(tbl, tbl->maxsize - VHT_ENTRY_SIZE);
+	if (tbl->maxsize - TBLSIZE(tbl) < VHT_ENTRY_SIZE) {
+		/* Maxsize less than one entry */
+		assert(tbl->maxsize < VHT_ENTRY_SIZE);
+		return;
+	}
+	vht_newentry(tbl);
+}
+
+int
+VHT_NewEntry_Indexed(struct vht_table *tbl, unsigned idx)
+{
+	struct vht_entry *e, *e2;
+	unsigned l, l2, lbuf, lentry, lname, u;
+	uint8_t buf[48];
+
+	/* Referenced name insertion. This has to be done carefully
+	   because the referenced name may be evicted as the result of the
+	   insertion (RFC 7541 section 4.4). */
+
+	assert(sizeof buf >= VHT_ENTRY_SIZE);
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+	assert(tbl->maxsize <= tbl->protomax);
+
+	if (idx == 0)
+		return (-1);
+
+	if (idx <= VHT_STATIC_MAX) {
+		/* Static table reference */
+		VHT_NewEntry(tbl);
+		VHT_AppendName(tbl, static_table[idx - 1].name,
+		    static_table[idx - 1].namelen);
+		return (0);
+	}
+	idx -= VHT_STATIC_MAX + 1;
+
+	if (idx >= tbl->n)
+		return (-1);	/* No such index */
+	assert(tbl->maxsize >= VHT_ENTRY_SIZE);
+
+	e = TBLENTRY(tbl, idx);
+	CHECK_OBJ_NOTNULL(e, VHT_ENTRY_MAGIC);
+
+	/* Count how many elements we can safely evict to make space
+	   without evicting the referenced entry. */
+	l = 0;
+	u = 0;
+	while (tbl->n - 1 - u > idx &&
+	    tbl->maxsize - TBLSIZE(tbl) + l < VHT_ENTRY_SIZE + e->namelen) {
+		e2 = TBLENTRY(tbl, tbl->n - 1 - u);
+		CHECK_OBJ_NOTNULL(e2, VHT_ENTRY_MAGIC);
+		l += ENTRYSIZE(e2);
+		u++;
+	}
+	vht_trim(tbl, TBLSIZE(tbl) - l);
+	e += u;
+	assert(e == TBLENTRY(tbl, idx));
+
+	if (tbl->maxsize - TBLSIZE(tbl) >= VHT_ENTRY_SIZE + e->namelen) {
+		/* New entry with name fits */
+		vht_newentry(tbl);
+		idx++;
+		assert(e == TBLENTRY(tbl, idx));
+		CHECK_OBJ_NOTNULL(e, VHT_ENTRY_MAGIC);
+		vht_appendname(tbl, tbl->buf + e->offset, e->namelen);
+		return (0);
+	}
+
+	/* The tricky case: The referenced name will be evicted as a
+	   result of the insertion. Move the element data to the end of
+	   the buffer through a local buffer. */
+
+	/* Remove the referenced element from the entry list */
+	assert(idx == tbl->n - 1);
+	assert(e->offset == 0);
+	lname = e->namelen;
+	lentry = ENTRYLEN(e);
+	e->magic = 0;
+	memmove(TBLENTRY(tbl, 1), TBLENTRY(tbl, 0), (tbl->n - 1) * sizeof *e);
+	tbl->n--;
+
+	/* Shift the referenced element last in the buffer. Use what space
+	   is available in the table buffer and the local buffer to keep
+	   memmove operations to a minimum. */
+	l = 0;
+	while (l < lentry) {
+		l2 = lentry - l;
+		if (l2 > tbl->maxsize - TBLSIZE(tbl))
+			l2 = tbl->maxsize - TBLSIZE(tbl);
+		lbuf = lentry - l - l2;
+		if (lbuf > sizeof buf)
+			lbuf = sizeof buf;
+		memcpy(tbl->buf + tbl->size, tbl->buf, l2);
+		memcpy(buf, tbl->buf + l2, lbuf);
+		memmove(tbl->buf, tbl->buf + l2 + lbuf, tbl->size + l2);
+		memcpy(tbl->buf + tbl->size - lbuf, buf, lbuf);
+		l += l2 + lbuf;
+	}
+	assert(l == lentry);
+	tbl->size -= lentry;
+
+	/* Fix up the existing element offsets */
+	for (u = 0; u < tbl->n; u++) {
+		e = TBLENTRY(tbl, u);
+		CHECK_OBJ_NOTNULL(e, VHT_ENTRY_MAGIC);
+		assert(e->offset >= lentry);
+		e->offset -= lentry;
+		assert(e->offset + ENTRYLEN(e) <= tbl->size);
+	}
+
+	/* Insert the new entry with the name now present at the end of
+	   the buffer. */
+	assert(tbl->maxsize - TBLSIZE(tbl) >= VHT_ENTRY_SIZE + lname);
+	tbl->n++;
+	e = TBLENTRY(tbl, 0);
+	INIT_OBJ(e, VHT_ENTRY_MAGIC);
+	e->offset = tbl->size;
+	e->namelen = lname;
+	tbl->size += lname;
+
+	return (0);
+}
+
+void
+VHT_AppendName(struct vht_table *tbl, const char *buf, ssize_t len)
+{
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+	assert(tbl->maxsize <= tbl->protomax);
+	if (len == 0)
+		return;
+	AN(buf);
+	if (len < 0)
+		len = strlen(buf);
+	vht_trim(tbl, tbl->maxsize - len);
+	if (tbl->n == 0)
+		/* Max size exceeded */
+		return;
+	vht_appendname(tbl, buf, len);
+}
+
+void
+VHT_AppendValue(struct vht_table *tbl, const char *buf, ssize_t len)
+{
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+	assert(tbl->maxsize <= tbl->protomax);
+	if (len == 0)
+		return;
+	AN(buf);
+	if (len < 0)
+		len = strlen(buf);
+	vht_trim(tbl, tbl->maxsize - len);
+	if (tbl->n == 0)
+		/* Max size exceeded */
+		return;
+	vht_appendvalue(tbl, buf, len);
+}
+
+int
+VHT_SetMaxTableSize(struct vht_table *tbl, size_t maxsize)
+{
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+	assert(tbl->maxsize <= tbl->protomax);
+	if (maxsize > tbl->protomax)
+		return (-1);
+	vht_trim(tbl, maxsize);
+	assert(TBLSIZE(tbl) <= maxsize);
+	tbl->maxsize = maxsize;
+	return (0);
+}
+
+int
+VHT_SetProtoMax(struct vht_table *tbl, size_t protomax)
+{
+	size_t bufsize;
+	char *buf;
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+	assert(protomax <= UINT_MAX);
+	assert(tbl->maxsize <= tbl->protomax);
+
+	if (protomax == tbl->protomax)
+		return (0);
+
+	if (tbl->maxsize > protomax)
+		tbl->maxsize = protomax;
+	vht_trim(tbl, tbl->maxsize);
+	assert(TBLSIZE(tbl) <= tbl->maxsize);
+
+	bufsize = PRNDUP(protomax);
+	if (bufsize == tbl->bufsize) {
+		tbl->protomax = protomax;
+		return (0);
+	}
+
+	buf = malloc(bufsize);
+	if (buf == NULL)
+		return (-1);
+
+	memcpy(buf, tbl->buf, tbl->size);
+	memcpy(ENTRIES(buf, bufsize, tbl->n), TBLENTRIES(tbl),
+	    sizeof (struct vht_entry) * tbl->n);
+	free(tbl->buf);
+	tbl->buf = buf;
+	tbl->bufsize = bufsize;
+	tbl->protomax = protomax;
+	return (0);
+}
+
+const char *
+VHT_LookupName(const struct vht_table *tbl, unsigned idx, size_t *plen)
+{
+	struct vht_entry *e;
+
+	AN(plen);
+	*plen = 0;
+
+	if (idx == 0) {
+		return (NULL);
+	}
+	if (idx <= VHT_STATIC_MAX) {
+		*plen = static_table[idx - 1].namelen;
+		return (static_table[idx - 1].name);
+	}
+
+	if (tbl == NULL)
+		return (NULL);
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+
+	idx -= VHT_STATIC_MAX + 1;
+	if (idx >= tbl->n)
+		return (NULL);
+
+	e = TBLENTRY(tbl, idx);
+	CHECK_OBJ_NOTNULL(e, VHT_ENTRY_MAGIC);
+	assert(e->offset + e->namelen <= tbl->size);
+	*plen = e->namelen;
+	return (tbl->buf + e->offset);
+}
+
+const char *
+VHT_LookupValue(const struct vht_table *tbl, unsigned idx, size_t *plen)
+{
+	struct vht_entry *e;
+
+	AN(plen);
+	*plen = 0;
+
+	if (idx == 0) {
+		return (NULL);
+	}
+	if (idx <= VHT_STATIC_MAX) {
+		*plen = static_table[idx - 1].valuelen;
+		return (static_table[idx - 1].value);
+	}
+
+	if (tbl == NULL)
+		return (NULL);
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+
+	idx -= VHT_STATIC_MAX + 1;
+	if (idx >= tbl->n)
+		return (NULL);
+
+	e = TBLENTRY(tbl, idx);
+	CHECK_OBJ_NOTNULL(e, VHT_ENTRY_MAGIC);
+	assert(e->offset + e->namelen + e->valuelen <= tbl->size);
+	*plen = e->valuelen;
+	return (tbl->buf + e->offset + e->namelen);
+}
+
+int
+VHT_Init(struct vht_table *tbl, size_t protomax)
+{
+	int r;
+
+	assert(sizeof (struct vht_entry) <= VHT_ENTRY_SIZE);
+
+	AN(tbl);
+	if (protomax > UINT_MAX)
+		return (-1);
+	INIT_OBJ(tbl, VHT_TABLE_MAGIC);
+	r = VHT_SetProtoMax(tbl, protomax);
+	if (r) {
+		tbl->magic = 0;
+		return (r);
+	}
+	tbl->maxsize = tbl->protomax;
+	return (0);
+}
+
+void
+VHT_Fini(struct vht_table *tbl)
+{
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+	free(tbl->buf);
+	memset(tbl, 0, sizeof *tbl);
+}
+
+/****************************************************************************/
+/* Internal interface */
+
+#ifdef TABLE_TEST_DRIVER
+
+#define VHT_DYNAMIC (VHT_STATIC_MAX + 1)
+
+static int verbose = 0;
+
+static int
+vht_matchtable(struct vht_table *tbl, ...)
+{
+	va_list ap;
+	unsigned u;
+	int r;
+	const char *a, *b;
+	const struct vht_entry *e;
+
+	CHECK_OBJ_NOTNULL(tbl, VHT_TABLE_MAGIC);
+
+	va_start(ap, tbl);
+	r = 0;
+	for (u = 0; u < tbl->n; u++) {
+		a = NULL;
+		b = NULL;
+		if (!r) {
+			a = va_arg(ap, const char *);
+			if (a == NULL) {
+				printf("Too many elements in table\n");
+				r = -1;
+			} else {
+				b = va_arg(ap, const char *);
+				AN(b);
+			}
+		}
+
+		e = TBLENTRY(tbl, u);
+		CHECK_OBJ_NOTNULL(e, VHT_ENTRY_MAGIC);
+
+		if (a) {
+			AN(b);
+			if (e->namelen != strlen(a) ||
+			    strncmp(a, tbl->buf + e->offset, e->namelen))
+				r = -1;
+			if (e->valuelen != strlen(b) ||
+			    strncmp(b, tbl->buf + e->offset + e->namelen,
+			     e->valuelen))
+				r = -1;
+		}
+
+		if (verbose || r)
+			printf("%2u: @%03u (\"%.*s\", \"%.*s\")",
+			    u, e->offset, (int)e->namelen, tbl->buf + e->offset,
+			    (int)e->valuelen, tbl->buf + e->offset +e->namelen);
+
+		if (a && (verbose || r)) {
+			AN(b);
+			printf(" %s (\"%s\", \"%s\")", (r ? "!=" : "=="), a, b);
+		}
+
+		if (verbose || r)
+			printf("\n");
+	}
+	if (!r) {
+		a = va_arg(ap, const char *);
+		if (a != NULL) {
+			printf("Missing elements in table\n");
+			r = -1;
+		}
+	}
+	va_end(ap);
+
+	if (verbose || r)
+		printf("n=%d, size=%u, tblsz=%u, max=%u, pmax=%u, bufsz=%u\n",
+		    tbl->n, tbl->size, TBLSIZE(tbl), tbl->maxsize,
+		    tbl->protomax, tbl->bufsize);
+
+	return (r);
+}
+
+static void
+test_1(void)
+{
+	/* Static table */
+
+	const char *p;
+	size_t l;
+
+	if (verbose)
+		printf("Test 1:\n");
+
+	/* 1: ':authority' -> '' */
+	p = VHT_LookupName(NULL, 1, &l);
+	assert(l == strlen(":authority"));
+	AZ(strncmp(p, ":authority", strlen(":authority")));
+	p = VHT_LookupValue(NULL, 1, &l);
+	AN(p);
+	AZ(l);
+
+	/* 5: ':path' -> '/index.html' */
+	p = VHT_LookupValue(NULL, 5, &l);
+	assert(l == strlen("/index.html"));
+	AZ(strncmp(p, "/index.html", strlen("/index.html")));
+
+	/* 61: 'www-authenticate' -> '' */
+	p = VHT_LookupName(NULL, 61, &l);
+	assert(l == strlen("www-authenticate"));
+	AZ(strncmp(p, "www-authenticate", strlen("www-authenticate")));
+	p = VHT_LookupValue(NULL, 61, &l);
+	AN(p);
+	AZ(l);
+
+	/* Test zero index */
+	AZ(VHT_LookupName(NULL, 0, &l));
+	AZ(l);
+	AZ(VHT_LookupValue(NULL, 0, &l));
+	AZ(l);
+
+	printf("Test 1 finished successfully\n");
+	if (verbose)
+		printf("\n");
+}
+
+static void
+test_2(void)
+{
+	/* Test filling and overflow */
+
+	struct vht_table tbl[1];
+
+	if (verbose)
+		printf("Test 2:\n");
+
+	AZ(VHT_Init(tbl, VHT_ENTRY_SIZE + 10));
+
+	VHT_NewEntry(tbl);
+	VHT_AppendName(tbl, "12345", -1);
+	VHT_AppendValue(tbl, "abcde", -1);
+	assert(TBLSIZE(tbl) == VHT_ENTRY_SIZE + 10);
+	/* 0: '12345' -> 'abcde' */
+	AZ(vht_matchtable(tbl, "12345", "abcde", NULL));
+
+	VHT_AppendValue(tbl, "f", -1);
+	AZ(vht_matchtable(tbl, NULL));
+
+	VHT_NewEntry(tbl);
+	AZ(vht_matchtable(tbl, "", "", NULL));
+
+	VHT_Fini(tbl);
+	AZ(tbl->buf);
+
+	printf("Test 2 finished successfully\n");
+	if (verbose)
+		printf("\n");
+}
+
+static void
+test_3(void)
+{
+	/* Test change in proto max size and dynamic max size */
+
+	struct vht_table tbl[1];
+
+	if (verbose)
+		printf("Test 3:\n");
+
+	AZ(VHT_Init(tbl, 4096));
+
+	VHT_NewEntry(tbl);
+	VHT_AppendName(tbl, "a", -1);
+	VHT_AppendValue(tbl, "12345", -1);
+	VHT_NewEntry(tbl);
+	VHT_AppendName(tbl, "b", -1);
+	VHT_AppendValue(tbl, "67890", -1);
+	VHT_NewEntry(tbl);
+	VHT_AppendName(tbl, "c", -1);
+	VHT_AppendValue(tbl, "abcde", -1);
+	AZ(vht_matchtable(tbl, "c", "abcde", "b", "67890", "a", "12345", NULL));
+
+	/* Buffer reallocation */
+	AZ(VHT_SetProtoMax(tbl, VHT_ENTRY_SIZE * 2 + 6 * 2));
+	AZ(vht_matchtable(tbl, "c", "abcde", "b", "67890", NULL));
+
+	/* Increase table size beyond protomax */
+	assert(VHT_SetMaxTableSize(tbl, VHT_ENTRY_SIZE * 2 + 6 * 2 + 1) == -1);
+
+	/* Decrease by one */
+	AZ(VHT_SetMaxTableSize(tbl, VHT_ENTRY_SIZE * 2 + 6 * 2 - 1));
+	AZ(vht_matchtable(tbl, "c", "abcde", NULL));
+
+	/* Increase by one back to protomax */
+	AZ(VHT_SetMaxTableSize(tbl, VHT_ENTRY_SIZE * 2 + 6 * 2));
+	AZ(vht_matchtable(tbl, "c", "abcde", NULL));
+
+	/* Add entry */
+	VHT_NewEntry(tbl);
+	VHT_AppendName(tbl, "d", -1);
+	VHT_AppendValue(tbl, "ABCDE", -1);
+	AZ(vht_matchtable(tbl, "d", "ABCDE", "c", "abcde", NULL));
+
+	/* Set to zero */
+	AZ(VHT_SetMaxTableSize(tbl, 0));
+	AZ(vht_matchtable(tbl, NULL));
+	VHT_NewEntry(tbl);
+	AZ(vht_matchtable(tbl, NULL));
+
+	/* Set protomax to zero */
+	AZ(VHT_SetProtoMax(tbl, 0));
+	AZ(vht_matchtable(tbl, NULL));
+	VHT_NewEntry(tbl);
+	AZ(vht_matchtable(tbl, NULL));
+
+	VHT_Fini(tbl);
+
+	printf("Test 3 finished successfully\n");
+	if (verbose)
+		printf("\n");
+}
+
+static void
+test_4(void)
+{
+	/* Referenced name new entry */
+
+	struct vht_table tbl[1];
+	static const char longname[] =
+	    "1234567890"
+	    "1234567890"
+	    "1234567890"
+	    "1234567890"
+	    "1234567890"
+	    "1";	/* 51 bytes + VHT_ENTRY_SIZE == 83 */
+
+	if (verbose)
+		printf("Test 4:\n");
+
+	AZ(VHT_Init(tbl, VHT_ENTRY_SIZE * 2 + 10 * 2)); /* 84 bytes */
+
+	/* New entry indexed from static table */
+	AZ(VHT_NewEntry_Indexed(tbl, 4));
+	VHT_AppendValue(tbl, "12345", -1);
+	AZ(vht_matchtable(tbl, ":path", "12345", NULL));
+
+	/* New entry indexed from dynamic table */
+	AZ(VHT_NewEntry_Indexed(tbl, VHT_DYNAMIC + 0));
+	VHT_AppendValue(tbl, "abcde", -1);
+	AZ(vht_matchtable(tbl, ":path", "abcde", ":path", "12345", NULL));
+	AZ(tbl->maxsize - TBLSIZE(tbl)); /* No space left */
+
+	/* New entry indexed from dynamic table, no overlap eviction */
+	AZ(VHT_NewEntry_Indexed(tbl, VHT_DYNAMIC + 0));
+	VHT_AppendValue(tbl, "ABCDE", -1);
+	AZ(vht_matchtable(tbl, ":path", "ABCDE", ":path", "abcde", NULL));
+
+	/* New entry indexed from dynamic table, overlap eviction */
+	AZ(VHT_NewEntry_Indexed(tbl, VHT_DYNAMIC + 1));
+	AZ(vht_matchtable(tbl, ":path", "", ":path", "ABCDE", NULL));
+
+	/* New entry indexed from dynamic table, overlap eviction with
+	   overlap larger than the copy buffer size */
+	VHT_NewEntry(tbl);
+	VHT_AppendName(tbl, longname, strlen(longname));
+	AZ(vht_matchtable(tbl, longname, "", NULL));
+	AZ(VHT_NewEntry_Indexed(tbl, VHT_DYNAMIC + 0));
+	VHT_AppendValue(tbl, "2", -1);
+	AZ(vht_matchtable(tbl, longname, "2", NULL));
+
+	VHT_Fini(tbl);
+	printf("Test 4 finished successfully\n");
+	if (verbose)
+		printf("\n");
+}
+
+int
+main(int argc, char **argv)
+{
+
+	if (argc == 2 && !strcmp(argv[1], "-v"))
+		verbose = 1;
+	else if (argc != 1) {
+		fprintf(stderr, "Usage: %s [-v]\n", argv[0]);
+		return (1);
+	}
+
+	if (verbose) {
+		printf("sizeof (struct vht_table) == %ju\n",
+		    sizeof (struct vht_table));
+		printf("sizeof (struct vht_entry) == %ju\n",
+		    sizeof (struct vht_entry));
+		printf("\n");
+	}
+
+	test_1();
+	test_2();
+	test_3();
+	test_4();
+
+	return (0);
+}
+
+#endif	/* TABLE_TEST_DRIVER */
diff --git a/bin/varnishd/http1/cache_http1_fsm.c b/bin/varnishd/http1/cache_http1_fsm.c
index 27a5597..2a4a11a 100644
--- a/bin/varnishd/http1/cache_http1_fsm.c
+++ b/bin/varnishd/http1/cache_http1_fsm.c
@@ -392,16 +392,34 @@ HTTP1_Session(struct worker *wrk, struct req *req)
 			if (hs != HTC_S_COMPLETE)
 				WRONG("htc_status (nonbad)");
 
+			if (H2_prism_complete(req->htc) == HTC_S_COMPLETE) {
+				VSLb(req->vsl, SLT_Debug,
+				    "H2 Prior Knowledge Upgrade");
+				http1_setstate(sp, NULL);
+				req->err_code = 1;
+				SES_SetTransport(wrk, sp, req, &H2_transport);
+				return;
+			}
+
 			i = http1_dissect(wrk, req);
 			req->acct.req_hdrbytes +=
 			    req->htc->rxbuf_e - req->htc->rxbuf_b;
 			if (i) {
 				SES_Close(req->sp, req->doclose);
 				http1_setstate(sp, H1CLEANUP);
-			} else {
-				req->req_step = R_STP_RECV;
-				http1_setstate(sp, H1PROC);
+				continue;
 			}
+			if (req->htc->body_status == BS_NONE &&
+			    http_HdrIs(req->http, H_Upgrade, "h2c")) {
+				VSLb(req->vsl, SLT_Debug,
+				    "H2 Optimistic Upgrade");
+				http1_setstate(sp, NULL);
+				req->err_code = 2;
+				SES_SetTransport(wrk, sp, req, &H2_transport);
+				return;
+			}
+			req->req_step = R_STP_RECV;
+			http1_setstate(sp, H1PROC);
 		} else if (st == H1BUSY) {
 			/*
 			 * Return from waitinglist.
diff --git a/bin/varnishd/http2/cache_http2.h b/bin/varnishd/http2/cache_http2.h
new file mode 100644
index 0000000..1378ef8
--- /dev/null
+++ b/bin/varnishd/http2/cache_http2.h
@@ -0,0 +1,138 @@
+/*-
+ * Copyright (c) 2016 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+struct h2_sess;
+
+#include "hpack/vhp.h"
+
+enum h2_error_e {
+#define H2_ERROR(NAME, val, desc)		\
+	H2E_##NAME = val,
+#include "tbl/h2_error.h"
+};
+
+enum h2_frame_e {
+#define H2_FRAME(l,u,t,f) H2_FRAME_##u = t,
+#include "tbl/h2_frames.h"
+};
+
+enum h2_stream_e {
+#define H2_STREAM(U,s,d) H2_S_##U,
+#include "tbl/h2_stream.h"
+};
+
+#define H2_FRAME_FLAGS(l,u,v)   extern const uint8_t H2FF_##u;
+#include "tbl/h2_frames.h"
+
+#define H2_SETTINGS_N			7
+
+struct h2_req {
+	unsigned			magic;
+#define H2_REQ_MAGIC			0x03411584
+	uint32_t			stream;
+	enum h2_stream_e		state;
+	struct h2_sess			*h2sess;
+	struct req			*req;
+	VTAILQ_ENTRY(h2_req)		list;
+	int64_t				window;
+};
+
+VTAILQ_HEAD(h2_req_s, h2_req);
+
+struct h2_sess {
+	unsigned			magic;
+#define H2_SESS_MAGIC			0xa16f7e4b
+
+	struct sess			*sess;
+	int				refcnt;
+	uint32_t			highest_stream;
+
+	struct h2_req_s			streams;
+
+	struct req			*srq;
+	struct ws			*ws;
+	struct http_conn		*htc;
+	struct vsl_log			*vsl;
+	struct vht_table		dectbl[1];
+
+	unsigned			rxf_len;
+	unsigned			rxf_flags;
+	unsigned			rxf_stream;
+	uint8_t				*rxf_data;
+
+	uint32_t			their_settings[H2_SETTINGS_N];
+	uint32_t			our_settings[H2_SETTINGS_N];
+
+	struct req			*new_req;
+	int				go_away;
+	uint32_t			go_away_last_stream;
+};
+
+/* http2/cache_http2_panic.c */
+#ifdef TRANSPORT_MAGIC
+vtr_sess_panic_f h2_sess_panic;
+#endif
+
+/* http2/cache_http2_deliver.c */
+#ifdef TRANSPORT_MAGIC
+vtr_deliver_f h2_deliver;
+#endif /* TRANSPORT_MAGIC */
+
+/* http2/cache_http2_hpack.c */
+struct h2h_decode {
+	unsigned			magic;
+#define H2H_DECODE_MAGIC		0xd092bde4
+
+	int				error;
+	enum vhd_ret_e			vhd_ret;
+	char				*out;
+	char				*reset;
+	size_t				out_l;
+	size_t				out_u;
+	size_t				namelen;
+	struct vhd_decode		vhd[1];
+};
+
+void h2h_decode_init(const struct h2_sess *h2, struct h2h_decode *d);
+int h2h_decode_fini(const struct h2_sess *h2, struct h2h_decode *d);
+int h2h_decode_bytes(struct h2_sess *h2, struct h2h_decode *d,
+    const uint8_t *ptr, size_t len);
+
+int H2_Send_Frame(struct worker *, const struct h2_sess *,
+    enum h2_frame_e type, uint8_t flags, uint32_t len, uint32_t stream,
+    const void *);
+
+int H2_Send(struct worker *, struct h2_req *, int flush,
+    enum h2_frame_e type, uint8_t flags, uint32_t len, const void *);
+
+typedef void h2_frame_f(struct worker *, struct h2_sess *,
+    struct h2_req *);
+#define H2_FRAME(l,u,t,f) h2_frame_f h2_rx_##l ;
+#include "tbl/h2_frames.h"
+
diff --git a/bin/varnishd/http2/cache_http2_deliver.c b/bin/varnishd/http2/cache_http2_deliver.c
new file mode 100644
index 0000000..5d28140
--- /dev/null
+++ b/bin/varnishd/http2/cache_http2_deliver.c
@@ -0,0 +1,227 @@
+/*-
+ * Copyright (c) 2016 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../cache/cache.h"
+#include "../cache/cache_filter.h"
+#include "../cache/cache_transport.h"
+
+#include "../http1/cache_http1.h"
+#include "../http2/cache_http2.h"
+
+#include "vct.h"
+#include "vend.h"
+
+/**********************************************************************/
+
+struct hpack_static {
+	uint8_t			idx;
+	const char *		name;
+	const char *		val;
+};
+
+static const struct hpack_static hp_static[] = {
+#define HPS(I,N,V) [I] = { I, N ":", V },
+#include "tbl/vhp_static.h"
+#undef HPS
+	{ 0, "\377:", ""}		// Terminator
+};
+
+static const struct hpack_static *hp_idx[256];
+
+void
+V2D_Init(void)
+{
+	int i;
+#define HPS(I,N,V)						\
+	i = hp_static[I].name[0];				\
+	if (hp_idx[i] == NULL) hp_idx[i] = &hp_static[I];
+#include "tbl/vhp_static.h"
+#undef HPS
+}
+
+/**********************************************************************/
+
+static int __match_proto__(vdp_bytes)
+h2_bytes(struct req *req, enum vdp_action act, void **priv,
+    const void *ptr, ssize_t len)
+{
+	struct h2_req *r2;
+
+	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
+	CAST_OBJ_NOTNULL(r2, req->transport_priv, H2_REQ_MAGIC);
+	(void)priv;
+	if (act == VDP_INIT || act == VDP_FINI)
+		return (0);
+	AZ(req->vdp_nxt);	       /* always at the bottom of the pile */
+
+	H2_Send(req->wrk, r2,
+	    act == VDP_FLUSH ? 1 : 0,
+	    H2_FRAME_DATA, H2FF_NONE, len, ptr);
+
+	return (0);
+}
+
+void __match_proto__(vtr_deliver_f)
+h2_deliver(struct req *req, struct boc *boc, int sendbody)
+{
+	ssize_t sz, sz1;
+	uint8_t *p;
+	unsigned u;
+	const char *r;
+	struct http *hp;
+	struct sess *sp;
+	struct h2_req *r2;
+	int i, err = 0;
+	const struct hpack_static *hps;
+
+	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
+	CHECK_OBJ_ORNULL(boc, BOC_MAGIC);
+	CHECK_OBJ_NOTNULL(req->objcore, OBJCORE_MAGIC);
+	CAST_OBJ_NOTNULL(r2, req->transport_priv, H2_REQ_MAGIC);
+	sp = req->sp;
+	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+	(void)sendbody;
+
+	VSLb(req->vsl, SLT_Debug, "H2: Deliver");
+
+	(void)WS_Reserve(req->ws, 0);
+	p = (void*)req->ws->f;
+
+	switch (req->resp->status) {
+	case 200: *p++ = 0x80 |  8; break;
+	case 204: *p++ = 0x80 |  9; break;
+	case 206: *p++ = 0x80 | 10; break;
+	case 304: *p++ = 0x80 | 11; break;
+	case 400: *p++ = 0x80 | 12; break;
+	case 404: *p++ = 0x80 | 13; break;
+	case 500: *p++ = 0x80 | 14; break;
+	default:
+		*p++ = 0x18;
+		*p++ = 0x03;
+
+		(void)sprintf((char*)p, "%03d", req->resp->status);
+		p += 3;
+		break;
+	}
+
+	hp = req->resp;
+	for (u = HTTP_HDR_FIRST; u < hp->nhd; u++) {
+		assert((char*)p < req->ws->e);
+
+		r = strchr(hp->hd[u].b, ':');
+		AN(r);
+
+		hps = hp_idx[tolower(*hp->hd[u].b)];
+		sz = 1 + r - hp->hd[u].b;
+		assert(sz > 0);
+		while (hps != NULL && hps->idx > 0) {
+			i = strncasecmp(hps->name, hp->hd[u].b, sz);
+			if (i < 0) {
+				hps++;
+				continue;
+			}
+			if (i > 0)
+				hps = NULL;
+			break;
+		}
+		if (hps != NULL) {
+			VSLb(req->vsl, SLT_Debug,
+			    "HP {%d, \"%s\", \"%s\"} <%s>",
+			    hps->idx, hps->name, hps->val, hp->hd[u].b);
+			if (hps->idx < 15) {
+				*p++ = 0x10 | hps->idx;
+			} else {
+				*p++ = 0x1f;
+				*p++ = hps->idx - 0x0f;
+			}
+		} else {
+
+			*p++ = 0x10;
+			sz--;
+			if (sz < 127) {
+				*p++ = (uint8_t)sz;
+			} else {
+				*p++ = 0x7f;
+				*p++ = (uint8_t)sz - 0x7f;
+			}
+
+			for(sz1 = 0; sz1 < sz; sz1++)
+				*p++ = (uint8_t)tolower(hp->hd[u].b[sz1]);
+
+		}
+
+		while(vct_islws(*++r))
+			continue;
+		sz = hp->hd[u].e - r;
+		assert(sz <= 254);
+		if (sz < 127) {
+			*p++ = (uint8_t)sz;
+		} else if (sz < 127 * 2) {
+			*p++ = 0x7f;
+			*p++ = (uint8_t)sz - 0x7f;
+		}
+
+		memcpy(p, r, sz);
+		p += sz;
+		assert((char*)p < req->ws->e);
+	}
+	sz = (char*)p - req->ws->f;
+
+	/* XXX: Optimize !sendbody case */
+	H2_Send(req->wrk, r2, 1, H2_FRAME_HEADERS, H2FF_HEADERS_END_HEADERS,
+	    sz, req->ws->f);
+
+	WS_Release(req->ws, 0);
+
+	if (sendbody && req->resp_len != 0)
+		VDP_push(req, h2_bytes, NULL, 1, "H2");
+
+	AZ(req->wrk->v1l);
+
+	if (sendbody && req->resp_len != 0)
+		err = VDP_DeliverObj(req);
+
+	H2_Send(req->wrk, r2, 1, H2_FRAME_DATA, H2FF_DATA_END_STREAM, 0, NULL);
+
+	AZ(req->wrk->v1l);
+	VDP_close(req);
+}
diff --git a/bin/varnishd/http2/cache_http2_hpack.c b/bin/varnishd/http2/cache_http2_hpack.c
new file mode 100644
index 0000000..a1470d7
--- /dev/null
+++ b/bin/varnishd/http2/cache_http2_hpack.c
@@ -0,0 +1,313 @@
+/*-
+ * Copyright (c) 2016 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Martin Blix Grydeland <martin at varnish-software.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include "config.h"
+
+#include <ctype.h>
+#include <stdio.h>
+
+#include "../cache/cache.h"
+#include "../http2/cache_http2.h"
+#include "vct.h"
+
+static int
+h2h_checkhdr(const struct http *hp, const char *b, size_t namelen, size_t len)
+{
+	const char *p;
+
+	CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
+	AN(b);
+	assert(namelen >= 2);	/* 2 chars from the ': ' that we added */
+	assert(namelen <= len);
+
+	if (namelen == 2) {
+		VSLb(hp->vsl, SLT_BogoHeader, "Empty name");
+		return (H2E_PROTOCOL_ERROR);
+	}
+
+	for (p = b; p < b + len; p++) {
+		if (p < b + (namelen - 2)) {
+			/* Check valid name characters */
+			if (p == b && *p == ':')
+				continue; /* pseudo-header */
+			if (vct_istchar(*p) && (!isupper(*p)))
+				/* XXX: vct should have a proper class for
+				   this avoiding two checks */
+				continue;
+			VSLb(hp->vsl, SLT_BogoHeader,
+			    "Illegal header name: %.*s",
+			    (int)(len > 20 ? 20 : len), b);
+			return (H2E_PROTOCOL_ERROR);
+		} else if (p < b + namelen) {
+			/* ': ' added by us */
+			assert(*p == ':' || *p == ' ');
+		} else {
+			/* Check valid value characters */
+			if (!vct_isctl(*p) || vct_issp(*p))
+				continue;
+			VSLb(hp->vsl, SLT_BogoHeader,
+			    "Illegal header value: %.*s",
+			    (int)(len > 20 ? 20 : len), b);
+			return (H2E_PROTOCOL_ERROR);
+		}
+	}
+
+	return (0);
+}
+
+static int
+h2h_addhdr(struct http *hp, char *b, size_t namelen, size_t len)
+{
+	/* XXX: This might belong in cache/cache_http.c */
+	unsigned n;
+
+	CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
+	AN(b);
+	assert(namelen >= 2);	/* 2 chars from the ': ' that we added */
+	assert(namelen <= len);
+
+	if (len > UINT_MAX) {	/* XXX: cache_param max header size */
+		VSLb(hp->vsl, SLT_BogoHeader, "Header too large: %.*s",
+		    (int)(len > 20 ? 20 : len), b);
+		return (H2E_ENHANCE_YOUR_CALM);
+	}
+
+	if (b[0] == ':') {
+		/* Match H/2 pseudo headers */
+		/* XXX: Should probably have some include tbl for
+		   pseudo-headers */
+		if (!strncmp(b, ":method: ", namelen)) {
+			b += namelen;
+			len -= namelen;
+			n = HTTP_HDR_METHOD;
+		} else if (!strncmp(b, ":path: ", namelen)) {
+			b += namelen;
+			len -= namelen;
+			n = HTTP_HDR_URL;
+		} else if (!strncmp(b, ":scheme: ", namelen)) {
+			/* XXX: What to do about this one? (typically
+			   "http" or "https"). For now set it as a normal
+			   header, stripping the first ':'. */
+			b++;
+			n = hp->nhd;
+		} else if (!strncmp(b, ":authority: ", namelen)) {
+			b+=6;
+			memcpy(b, "host", 4);
+			n = hp->nhd;
+		} else {
+			/* Unknown pseudo-header */
+			VSLb(hp->vsl, SLT_BogoHeader,
+			    "Unknown pseudo-header: %.*s",
+			    (int)(len > 20 ? 20 : len), b);
+			return (H2E_PROTOCOL_ERROR);
+		}
+	} else
+		n = hp->nhd;
+
+	if (n < HTTP_HDR_FIRST) {
+		/* Check for duplicate pseudo-header */
+		if (n < HTTP_HDR_FIRST && hp->hd[n].b != NULL) {
+			VSLb(hp->vsl, SLT_BogoHeader,
+			    "Duplicate pseudo-header: %.*s",
+			    (int)(len > 20 ? 20 : len), b);
+			return (H2E_PROTOCOL_ERROR);
+		}
+	} else {
+		/* Check for space in struct http */
+		if (n >= hp->shd) {
+			VSLb(hp->vsl, SLT_LostHeader, "Too many headers: %.*s",
+			    (int)(len > 20 ? 20 : len), b);
+			return (H2E_ENHANCE_YOUR_CALM);
+		}
+		hp->nhd++;
+	}
+
+	hp->hd[n].b = b;
+	hp->hd[n].e = b + len;
+
+	return (0);
+}
+
+void
+h2h_decode_init(const struct h2_sess *h2, struct h2h_decode *d)
+{
+
+	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
+	CHECK_OBJ_NOTNULL(h2->new_req, REQ_MAGIC);
+	CHECK_OBJ_NOTNULL(h2->new_req->http, HTTP_MAGIC);
+	AN(d);
+	INIT_OBJ(d, H2H_DECODE_MAGIC);
+	VHD_Init(d->vhd);
+	d->out_l = WS_Reserve(h2->new_req->http->ws, 0);
+	assert(d->out_l > 0);	/* Can't do any work without any buffer
+				   space. Require non-zero size. */
+	d->out = h2->new_req->http->ws->f;
+	d->reset = d->out;
+}
+
+/* Possible error returns:
+ *
+ * H2E_COMPRESSION_ERROR: Lost compression state due to incomplete header
+ * block. This is a connection level error.
+ *
+ * H2E_ENHANCE_YOUR_CALM: Ran out of workspace or http header space. This
+ * is a stream level error.
+ */
+int
+h2h_decode_fini(const struct h2_sess *h2, struct h2h_decode *d)
+{
+	int ret;
+
+	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
+	CHECK_OBJ_NOTNULL(h2->new_req, REQ_MAGIC);
+	CHECK_OBJ_NOTNULL(d, H2H_DECODE_MAGIC);
+	WS_ReleaseP(h2->new_req->http->ws, d->out);
+	if (d->vhd_ret != VHD_OK) {
+		/* HPACK header block didn't finish at an instruction
+		   boundary */
+		VSLb(h2->new_req->http->vsl, SLT_BogoHeader,
+		    "HPACK compression error (%s)", VHD_Error(d->vhd_ret));
+		ret = H2E_COMPRESSION_ERROR;
+	} else
+		ret = d->error;
+	d->magic = 0;
+	return (ret);
+}
+
+/* Possible error returns:
+ *
+ * H2E_COMPRESSION_ERROR: Lost compression state due to invalid header
+ * block. This is a connection level error.
+ *
+ * H2E_PROTOCOL_ERROR: Malformed header or duplicate pseudo-header.
+ */
+int
+h2h_decode_bytes(struct h2_sess *h2, struct h2h_decode *d,
+    const uint8_t *in, size_t in_l)
+{
+	struct http *hp;
+	size_t in_u = 0;
+
+	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
+	CHECK_OBJ_NOTNULL(h2->new_req, REQ_MAGIC);
+	hp = h2->new_req->http;
+	CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
+	CHECK_OBJ_NOTNULL(hp->ws, WS_MAGIC);
+	AN(hp->ws->r);
+	CHECK_OBJ_NOTNULL(d, H2H_DECODE_MAGIC);
+
+	/* Only H2E_ENHANCE_YOUR_CALM indicates that we should continue
+	   processing. Other errors should have been returned and handled
+	   by the caller. */
+	assert(d->error == 0 || d->error == H2E_ENHANCE_YOUR_CALM);
+
+	while (1) {
+		AN(d->out);
+		assert(d->out_u <= d->out_l);
+		d->vhd_ret = VHD_Decode(d->vhd, h2->dectbl, in, in_l, &in_u,
+		    d->out, d->out_l, &d->out_u);
+
+		if (d->vhd_ret < 0) {
+			VSLb(hp->vsl, SLT_BogoHeader,
+			    "HPACK compression error (%s)",
+			    VHD_Error(d->vhd_ret));
+			d->error = H2E_COMPRESSION_ERROR;
+			break;
+		} else if (d->vhd_ret == VHD_OK || d->vhd_ret == VHD_MORE) {
+			assert(in_u == in_l);
+			break;
+		}
+
+		if (d->error == H2E_ENHANCE_YOUR_CALM) {
+			d->out_u = 0;
+			assert(d->out_u < d->out_l);
+			continue;
+		}
+
+		switch (d->vhd_ret) {
+		case VHD_NAME_SEC:
+			/* XXX: header flag for never-indexed header */
+		case VHD_NAME:
+			assert(d->namelen == 0);
+			if (d->out_l - d->out_u < 2) {
+				d->error = H2E_ENHANCE_YOUR_CALM;
+				break;
+			}
+			d->out[d->out_u++] = ':';
+			d->out[d->out_u++] = ' ';
+			d->namelen = d->out_u;
+			break;
+
+		case VHD_VALUE_SEC:
+			/* XXX: header flag for never-indexed header */
+		case VHD_VALUE:
+			assert(d->namelen > 0);
+			if (d->out_l - d->out_u < 1) {
+				d->error = H2E_ENHANCE_YOUR_CALM;
+				break;
+			}
+			d->error = h2h_checkhdr(hp, d->out, d->namelen,
+			    d->out_u);
+			if (d->error)
+				break;
+			d->error = h2h_addhdr(hp, d->out, d->namelen, d->out_u);
+			if (d->error)
+				break;
+			d->out[d->out_u++] = '\0'; /* Zero guard */
+			d->out += d->out_u;
+			d->out_l -= d->out_u;
+			d->out_u = 0;
+			d->namelen = 0;
+			break;
+
+		case VHD_BUF:
+			d->error = H2E_ENHANCE_YOUR_CALM;
+			break;
+
+		default:
+			WRONG("Unhandled return value");
+			break;
+		}
+
+		if (d->error == H2E_ENHANCE_YOUR_CALM) {
+			http_Teardown(hp);
+			d->out = d->reset;
+			d->out_l = hp->ws->r - d->out;
+			d->out_u = 0;
+			assert(d->out_u < d->out_l);
+		} else if (d->error)
+			break;
+	}
+
+	if (d->error == H2E_ENHANCE_YOUR_CALM)
+		return (0); /* Stream error, delay reporting until
+			       h2h_decode_fini so that we can process the
+			       complete header block */
+	return (d->error);
+}
diff --git a/bin/varnishd/http2/cache_http2_panic.c b/bin/varnishd/http2/cache_http2_panic.c
new file mode 100644
index 0000000..085c5c4
--- /dev/null
+++ b/bin/varnishd/http2/cache_http2_panic.c
@@ -0,0 +1,79 @@
+/*-
+ * Copyright (c) 2016 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../cache/cache.h"
+#include "../cache/cache_filter.h"
+#include "../cache/cache_transport.h"
+#include "../http2/cache_http2.h"
+
+#include "vend.h"
+#include "vsb.h"
+#include "vtcp.h"
+#include "vtim.h"
+
+void
+h2_sess_panic(struct vsb *vsb, const struct sess *sp)
+{
+	uintptr_t *up;
+	struct h2_sess *h2;
+	struct h2_req *r2;
+
+	AZ(SES_Get_xport_priv(sp, &up));
+
+	h2 = (void*)*up;
+	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
+	VSB_printf(vsb, "streams {\n");
+	VSB_indent(vsb, 2);
+	VTAILQ_FOREACH(r2, &h2->streams, list) {
+		VSB_printf(vsb, "0x%08x", r2->stream);
+		switch(r2->state) {
+#define H2_STREAM(U,sd,d) case H2_S_##U: VSB_printf(vsb, " %-6s", sd); break;
+#include <tbl/h2_stream.h>
+		default:
+			VSB_printf(vsb, " State %d", r2->state);
+			break;
+		}
+		VSB_printf(vsb, "\n");
+	}
+	VSB_indent(vsb, -2);
+	VSB_printf(vsb, "}\n");
+}
diff --git a/bin/varnishd/http2/cache_http2_proto.c b/bin/varnishd/http2/cache_http2_proto.c
new file mode 100644
index 0000000..e7884b0
--- /dev/null
+++ b/bin/varnishd/http2/cache_http2_proto.c
@@ -0,0 +1,745 @@
+/*-
+ * Copyright (c) 2016 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../cache/cache.h"
+#include "../cache/cache_filter.h"
+#include "../cache/cache_transport.h"
+#include "../http2/cache_http2.h"
+
+#include "vend.h"
+#include "vsb.h"
+#include "vtcp.h"
+#include "vtim.h"
+
+enum h2frame {
+#define H2_FRAME(l,u,t,f)	H2F_##u = t,
+#include "tbl/h2_frames.h"
+};
+
+enum h2setting {
+#define H2_SETTINGS(n,v,d) H2S_##n = v,
+#include "tbl/h2_settings.h"
+#undef H2_SETTINGS
+};
+
+static const char *
+h2_framename(enum h2frame h2f)
+{
+
+	switch(h2f) {
+#define H2_FRAME(l,u,t,f)	case H2F_##u: return #u;
+#include "tbl/h2_frames.h"
+	default:
+		return (NULL);
+	}
+}
+
+static const char *
+h2_settingname(enum h2setting h2f)
+{
+
+	switch(h2f) {
+#define H2_SETTINGS(n,v,d) case H2S_##n: return #n;
+#include "tbl/h2_settings.h"
+#undef H2_SETTINGS
+	default:
+		return (NULL);
+	}
+}
+
+#define H2_FRAME_FLAGS(l,u,v)	const uint8_t H2FF_##u = v;
+#include "tbl/h2_frames.h"
+
+static const char h2_resp_101[] =
+	"HTTP/1.1 101 Switching Protocols\r\n"
+	"Connection: Upgrade\r\n"
+	"Upgrade: h2c\r\n"
+	"\r\n";
+
+static const char H2_prism[24] = {
+	0x50, 0x52, 0x49, 0x20, 0x2a, 0x20, 0x48, 0x54,
+	0x54, 0x50, 0x2f, 0x32, 0x2e, 0x30, 0x0d, 0x0a,
+	0x0d, 0x0a, 0x53, 0x4d, 0x0d, 0x0a, 0x0d, 0x0a
+};
+
+static const uint8_t H2_settings[] = {
+	0x00, 0x03,
+	0x00, 0x00, 0x00, 0x64,
+	0x00, 0x04,
+	0x00, 0x00, 0xff, 0xff
+};
+
+/**********************************************************************/
+#define DUMMY_FRAME(l) \
+	void \
+	h2_rx_##l(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2) \
+	__match_proto__(h2_frame_f) \
+	{ (void)wrk; (void)r2; VSLb(h2->vsl, SLT_Debug, "XXX implement " #l); INCOMPL(); }
+
+DUMMY_FRAME(data)
+DUMMY_FRAME(rst_stream)
+DUMMY_FRAME(ping)
+DUMMY_FRAME(push_promise)
+DUMMY_FRAME(continuation)
+
+/**********************************************************************
+ * The h2_sess struct needs many of the same things as a request,
+ * WS, VSL, HTC &c,  but rather than implement all that stuff over, we
+ * grab an actual struct req, and mirror the relevant fields into
+ * struct h2_sess.
+ * To make things really incestuous, we allocate the h2_sess on
+ * the WS of that "Session ReQuest".
+ */
+
+static struct h2_sess *
+h2_new_sess(const struct worker *wrk, struct sess *sp, struct req *srq)
+{
+	uintptr_t *up;
+	struct h2_sess *h2;
+
+	if (SES_Get_xport_priv(sp, &up)) {
+		/* Already reserved if we came via H1 */
+		SES_Reserve_xport_priv(sp, &up);
+		*up = 0;
+	}
+	if (*up == 0) {
+		if (srq == NULL)
+			srq = Req_New(wrk, sp);
+		AN(srq);
+		h2 = WS_Alloc(srq->ws, sizeof *h2);
+		AN(h2);
+		INIT_OBJ(h2, H2_SESS_MAGIC);
+		h2->refcnt = 1;
+		h2->srq = srq;
+		h2->htc = srq->htc;
+		h2->ws = srq->ws;
+		h2->vsl = srq->vsl;
+		h2->vsl->wid = sp->vxid;
+		h2->htc->fd = sp->fd;
+		h2->sess = sp;
+		VTAILQ_INIT(&h2->streams);
+#define H2_SETTINGS(n,v,d)					\
+		do {						\
+			assert(v < H2_SETTINGS_N);		\
+			h2->their_settings[v] = d;		\
+			h2->our_settings[v] = d;		\
+		} while (0);
+#include "tbl/h2_settings.h"
+#undef H2_SETTINGS
+
+		/* XXX: Lacks a VHT_Fini counterpart. Will leak memory. */
+		AZ(VHT_Init(h2->dectbl,
+			h2->our_settings[H2S_HEADER_TABLE_SIZE]));
+
+		SES_Reserve_xport_priv(sp, &up);
+		*up = (uintptr_t)h2;
+	}
+	AN(up);
+	CAST_OBJ_NOTNULL(h2, (void*)(*up), H2_SESS_MAGIC);
+	return (h2);
+}
+
+/**********************************************************************
+ */
+
+static struct h2_req *
+h2_new_req(const struct worker *wrk, struct h2_sess *h2,
+    unsigned stream, struct req *req)
+{
+	struct h2_req *r2;
+
+	Lck_AssertHeld(&h2->sess->mtx);
+	if (req == NULL)
+		req = Req_New(wrk, h2->sess);
+	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
+
+	r2 = WS_Alloc(req->ws, sizeof *r2);
+	AN(r2);
+	INIT_OBJ(r2, H2_REQ_MAGIC);
+	r2->h2sess = h2;
+	r2->stream = stream;
+	r2->req = req;
+	req->transport_priv = r2;
+	// XXX: ordering ?
+	VTAILQ_INSERT_TAIL(&h2->streams, r2, list);
+	h2->refcnt++;
+	return (r2);
+}
+
+static void
+h2_del_req(struct worker *wrk, struct h2_req *r2, enum h2_error_e err)
+{
+	struct h2_sess *h2;
+	int r;
+
+	(void)err;
+	h2 = r2->h2sess;
+	Lck_Lock(&h2->sess->mtx);
+	assert(h2->refcnt > 0);
+	r = --h2->refcnt;
+	/* XXX: PRIORITY reshuffle */
+	VTAILQ_REMOVE(&h2->streams, r2, list);
+	Lck_Unlock(&h2->sess->mtx);
+	Req_Cleanup(h2->sess, wrk, r2->req);
+	if (r)
+		return;
+	SES_Delete(h2->sess, SC_RX_JUNK, NAN);
+}
+
+/**********************************************************************
+ * Update and VSL a single SETTING rx'ed from the other side
+ * 'd' must point to six bytes.
+ */
+
+static void
+h2_setting(struct h2_sess *h2, const uint8_t *d)
+{
+	uint16_t x;
+	uint32_t y;
+	const char *n;
+	char nb[8];
+
+	x = vbe16dec(d);
+	y = vbe32dec(d + 2);
+	n = h2_settingname((enum h2setting)x);
+	if (n == NULL) {
+		bprintf(nb, "0x%04x", x);
+		n = nb;
+	}
+	VSLb(h2->vsl, SLT_Debug, "H2SETTING %s 0x%08x", n, y);
+	if (x > 0 && x < H2_SETTINGS_N)
+		h2->their_settings[x] = y;
+}
+
+/**********************************************************************/
+
+static void
+h2_vsl_frame(const struct h2_sess *h2, const void *ptr, size_t len)
+{
+	const uint8_t *b;
+	struct vsb *vsb;
+	const char *p;
+	unsigned u;
+
+	AN(ptr);
+	assert(len >= 9);
+	b = ptr;
+
+	VSLb_bin(h2->vsl, SLT_H2RxHdr, 9, b);
+	if (len > 9)
+		VSLb_bin(h2->vsl, SLT_H2RxBody, len - 9, b + 9);
+
+	u = vbe32dec(b) >> 8;
+
+	vsb = VSB_new_auto();
+	AN(vsb);
+	p = h2_framename((enum h2frame)b[3]);
+	if (p != NULL)
+		VSB_cat(vsb, p);
+	else
+		VSB_quote(vsb, b + 3, 1, VSB_QUOTE_HEX);
+	VSB_printf(vsb, "[%u] ", u);
+	VSB_quote(vsb, b + 4, 1, VSB_QUOTE_HEX);
+	VSB_putc(vsb, ' ');
+	VSB_quote(vsb, b + 5, 4, VSB_QUOTE_HEX);
+	if (u > 0) {
+		VSB_putc(vsb, ' ');
+		VSB_quote(vsb, b + 9, u, VSB_QUOTE_HEX);
+	}
+	AZ(VSB_finish(vsb));
+	VSLb(h2->vsl, SLT_Debug, "H2RXF %s", VSB_data(vsb));
+	VSB_destroy(&vsb);
+}
+
+
+/**********************************************************************
+ */
+
+void __match_proto__(h2_frame_f)
+h2_rx_goaway(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
+{
+	uint32_t	error;
+
+	(void)wrk;
+	(void)r2;
+	h2->go_away_last_stream = vbe32dec(h2->rxf_data);
+	error = vbe32dec(h2->rxf_data + 4);
+	h2->go_away = 1;
+}
+
+void __match_proto__(h2_frame_f)
+h2_rx_window_update(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
+{
+	uint32_t wu;
+
+	(void)wrk;
+	Lck_AssertHeld(&h2->sess->mtx);
+	xxxassert(h2->rxf_len == 4);		// conn FRAME_SIZE_ERROR
+	wu = vbe32dec(h2->rxf_data);
+	xxxassert(wu != 0);			// stream PROTOCOL_ERROR
+	r2->window += wu;
+	xxxassert(r2->window < (1LLU<<32));	// FLOW_CONTROL_ERROR
+}
+
+/**********************************************************************
+ * Incoming PRIORITY, possibly an ACK of one we sent.
+ */
+
+void __match_proto__(h2_frame_f)
+h2_rx_priority(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
+{
+
+	(void)wrk;
+	(void)h2;
+	xxxassert(r2->stream & 1);
+}
+
+/**********************************************************************
+ * Incoming SETTINGS, possibly an ACK of one we sent.
+ */
+
+void __match_proto__(h2_frame_f)
+h2_rx_settings(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
+{
+	const uint8_t *p = h2->rxf_data;
+	unsigned l = h2->rxf_len;
+
+	(void)wrk;
+	(void)r2;
+	AZ(h2->rxf_stream);
+	if (h2->rxf_flags == H2FF_SETTINGS_ACK) {
+		XXXAZ(h2->rxf_len);
+	} else if (h2->rxf_flags == 0) {
+		for (;l >= 6; l -= 6, p += 6)
+			h2_setting(h2, p);
+		if (l > 0)
+			VSLb(h2->vsl, SLT_Debug,
+			    "NB: SETTINGS had %u dribble-bytes", l);
+		H2_Send_Frame(wrk, h2,
+		    H2_FRAME_SETTINGS, H2FF_SETTINGS_ACK, 0, 0, NULL);
+	} else {
+		WRONG("SETTINGS FRAME");
+	}
+}
+
+/**********************************************************************
+ * Incoming HEADERS, this is where the partys at...
+ */
+
+static void __match_proto__(task_func_t)
+h2_do_req(struct worker *wrk, void *priv)
+{
+	struct req *req;
+	struct h2_req *r2;
+
+	CAST_OBJ_NOTNULL(req, priv, REQ_MAGIC);
+	CAST_OBJ_NOTNULL(r2, req->transport_priv, H2_REQ_MAGIC);
+	assert(CNT_Request(wrk, req) != REQ_FSM_DISEMBARK);
+	VSL(SLT_Debug, 0, "H2REQ CNT done");
+	/* XXX clean up req */
+	h2_del_req(wrk, r2, H2E_NO_ERROR);
+}
+
+void __match_proto__(h2_frame_f)
+h2_rx_headers(struct worker *wrk, struct h2_sess *h2, struct h2_req *r2)
+{
+	struct req *req;
+	struct h2h_decode d[1];
+	const uint8_t *p;
+	size_t l;
+	int i;
+
+	/* XXX: This still lacks support for CONTINUATION frames, half
+	 * read frames and proper error handling.
+	 */
+
+	assert(h2->rxf_stream & 1);
+
+	req = r2->req;
+	CHECK_OBJ_NOTNULL(req, REQ_MAGIC);
+
+	req->vsl->wid = VXID_Get(wrk, VSL_CLIENTMARKER);
+	VSLb(req->vsl, SLT_Begin, "req %u rxreq", VXID(req->sp->vxid));
+	VSL(SLT_Link, req->sp->vxid, "req %u rxreq", VXID(req->vsl->wid));
+
+	h2->new_req = req;
+	req->sp = h2->sess;
+	req->req_step = R_STP_RECV;
+	req->transport = &H2_transport;
+
+	req->t_first = VTIM_real();
+	req->t_req = VTIM_real();
+	req->t_prev = req->t_first;
+	VSLb_ts_req(req, "Start", req->t_first);
+	VCL_Refresh(&wrk->vcl);
+	req->vcl = wrk->vcl;
+	wrk->vcl = NULL;
+
+	HTTP_Setup(req->http, req->ws, req->vsl, SLT_ReqMethod);
+	h2h_decode_init(h2, d);
+	/* XXX: Error handling */
+	p = h2->rxf_data;
+	l = h2->rxf_len;
+	if (h2->rxf_flags & 0x20) {
+		p += 5;
+		l -= 5;
+	}
+	i = h2h_decode_bytes(h2, d, p, l);
+	if (i)
+		VSLb(h2->vsl, SLT_Debug, "H2H_decode_bytes = %d", i);
+	XXXAZ(i);
+	i = h2h_decode_fini(h2, d);
+	if (i)
+		VSLb(h2->vsl, SLT_Debug, "H2H_decode_fini = %d", i);
+	XXXAZ(i);
+	VSLb_ts_req(req, "Req", req->t_req);
+	http_SetH(req->http, HTTP_HDR_PROTO, "HTTP/2.0");
+
+	req->req_body_status = REQ_BODY_NONE;
+	wrk->stats->client_req++;
+	wrk->stats->s_req++;
+	req->ws_req = WS_Snapshot(req->ws);
+	HTTP_Copy(req->http0, req->http);
+
+	req->task.func = h2_do_req;
+	req->task.priv = req;
+	XXXAZ(Pool_Task(wrk->pool, &req->task, TASK_QUEUE_REQ));
+}
+
+/**********************************************************************/
+
+enum htc_status_e __match_proto__(htc_complete_f)
+H2_prism_complete(struct http_conn *htc)
+{
+	int l;
+
+	CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
+	l = htc->rxbuf_e - htc->rxbuf_b;
+	if (l < strlen(H2_prism))
+		return (HTC_S_MORE);
+	if (!memcmp(htc->rxbuf_b, H2_prism, sizeof(H2_prism)))
+		return (HTC_S_COMPLETE);
+	return (HTC_S_JUNK);
+}
+
+static enum htc_status_e __match_proto__(htc_complete_f)
+h2_frame_complete(struct http_conn *htc)
+{
+	int l;
+	unsigned u;
+
+	CHECK_OBJ_NOTNULL(htc, HTTP_CONN_MAGIC);
+	l = htc->rxbuf_e - htc->rxbuf_b;
+	if (l < 9)
+		return (HTC_S_MORE);
+	u = vbe32dec(htc->rxbuf_b) >> 8;
+	VSL(SLT_Debug, 0, "RX %p %d %u", htc->rxbuf_b, l, u);
+	if (l < u + 9)	// XXX: Only for !DATA frames
+		return (HTC_S_MORE);
+	return (HTC_S_COMPLETE);
+}
+
+static int
+h2_rxframe(struct worker *wrk, struct h2_sess *h2)
+{
+	enum htc_status_e hs;
+	enum h2frame ft;
+	struct h2_req *r2 = NULL;
+
+	(void)VTCP_blocking(h2->htc->fd);
+	h2->sess->t_idle = VTIM_real();
+	hs = HTC_RxStuff(h2->htc, h2_frame_complete,
+	    NULL, NULL, NAN,
+	    h2->sess->t_idle + cache_param->timeout_idle + 100,
+	    1024);
+	if (hs != HTC_S_COMPLETE) {
+		Lck_Lock(&h2->sess->mtx);
+		VSLb(h2->vsl, SLT_Debug, "H2: No frame (hs=%d)", hs);
+		Lck_Unlock(&h2->sess->mtx);
+		return (0);
+	}
+
+	h2->rxf_len =  vbe32dec(h2->htc->rxbuf_b) >> 8;
+	h2->rxf_flags = h2->htc->rxbuf_b[4];
+	h2->rxf_stream = vbe32dec(h2->htc->rxbuf_b + 5);
+	h2->rxf_data = (void*)(h2->htc->rxbuf_b + 9);
+	/* XXX: later full DATA will not be rx'ed yet. */
+	HTC_RxPipeline(h2->htc, h2->htc->rxbuf_b + h2->rxf_len + 9);
+
+	Lck_Lock(&h2->sess->mtx);
+	VTAILQ_FOREACH(r2, &h2->streams, list)
+		if (r2->stream == h2->rxf_stream)
+			break;
+	if (r2 == NULL) {
+		xxxassert(h2->rxf_stream > h2->highest_stream);
+		h2->highest_stream = h2->rxf_stream;
+		r2 = h2_new_req(wrk, h2, h2->rxf_stream, NULL);
+	}
+
+	h2_vsl_frame(h2, h2->htc->rxbuf_b, 9L + h2->rxf_len);
+
+	ft = (enum h2frame)h2->htc->rxbuf_b[3];
+	switch (ft) {
+#define H2_FRAME(l,u,t,f)					\
+	case H2F_##u:						\
+		if (!(h2->rxf_flags & ~f)) {			\
+			h2_rx_##l(wrk, h2, r2);			\
+		} else {					\
+			VSLb(h2->vsl, SLT_Debug,		\
+			    "H2: Bad flags 0x%02x on " #u,	\
+			    h2->rxf_flags);			\
+			Lck_Unlock(&h2->sess->mtx);		\
+			return (0);				\
+		}						\
+		break;
+#include "tbl/h2_frames.h"
+	default:
+		INCOMPL();
+	}
+	Lck_Unlock(&h2->sess->mtx);
+	return (1);
+}
+
+
+/**********************************************************************
+ * Deal with the base64url (NB: ...url!) encoded SETTINGS in the H1 req
+ * of a H2C upgrade.
+ */
+
+static int
+h2_b64url_settings(struct h2_sess *h2, struct req *req)
+{
+	const char *p, *q;
+	uint8_t u[6], *up;
+	unsigned x;
+	int i, n;
+	static const char s[] =
+	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+	    "abcdefghijklmnopqrstuvwxyz"
+	    "0123456789"
+	    "-_=";
+
+	/*
+	 * If there is trouble with this, we could reject the upgrade
+	 * but putting this on the H1 side is just plain wrong...
+	 */
+	AN(http_GetHdr(req->http, H_HTTP2_Settings, &p));
+	if (p == NULL)
+		return (-1);
+	VSLb(req->vsl, SLT_Debug, "H2CS %s", p);
+
+	n = 0;
+	x = 0;
+	up = u;
+	for(;*p; p++) {
+		q = strchr(s, *p);
+		if (q == NULL)
+			return (-1);
+		i = q - s;
+		assert(i >= 0 && i <= 63);
+		x <<= 6;
+		x |= i;
+		n += 6;
+		if (n < 8)
+			continue;
+		*up++ = (uint8_t)(x >> (n - 8));
+		n -= 8;
+		if (up == u + sizeof u) {
+			AZ(n);
+			h2_setting(h2, (void*)u);
+			up = u;
+		}
+	}
+	if (up != u)
+		return (-1);
+	return (0);
+}
+
+/**********************************************************************/
+
+static int
+h2_new_pu_session(struct worker *wrk, const struct h2_sess *h2)
+{
+	enum htc_status_e hs;
+
+	(void)wrk;
+
+	hs = H2_prism_complete(h2->htc);
+	if (hs == HTC_S_MORE) {
+		VSLb(h2->vsl, SLT_Debug, "Short pu PRISM");
+		return (0);
+	}
+	if (hs != HTC_S_COMPLETE) {
+		VSLb(h2->vsl, SLT_Debug, "Wrong pu PRISM");
+		return (0);
+	}
+	HTC_RxPipeline(h2->htc, h2->htc->rxbuf_b + sizeof(H2_prism));
+	HTC_RxInit(h2->htc, wrk->aws);
+
+	VSLb(h2->vsl, SLT_Debug, "H2: Got pu PRISM");
+	return (1);
+}
+
+/**********************************************************************/
+
+static int
+h2_new_ou_session(struct worker *wrk, struct h2_sess *h2,
+    struct req *req)
+{
+	ssize_t sz;
+	enum htc_status_e hs;
+
+	sz = write(h2->sess->fd, h2_resp_101, strlen(h2_resp_101));
+	assert(sz == strlen(h2_resp_101));
+
+	AZ(h2_b64url_settings(h2, req));
+	http_Unset(req->http, H_Upgrade);
+	http_Unset(req->http, H_HTTP2_Settings);
+
+	/* Steal pipelined read-ahead, if any */
+	h2->htc->pipeline_b = req->htc->pipeline_b;
+	h2->htc->pipeline_e = req->htc->pipeline_e;
+	req->htc->pipeline_b = NULL;
+	req->htc->pipeline_e = NULL;
+	HTC_RxInit(h2->htc, wrk->aws);
+
+	/* Start req thread */
+	(void)h2_new_req(wrk, h2, 1, req);
+	req->req_step = R_STP_RECV;
+	req->transport = &H2_transport;
+	req->task.func = h2_do_req;
+	req->task.priv = req;
+	req->err_code = 0;
+	http_SetH(req->http, HTTP_HDR_PROTO, "HTTP/2.0");
+	XXXAZ(Pool_Task(wrk->pool, &req->task, TASK_QUEUE_REQ));
+
+	/* Wait for PRISM response */
+	hs = HTC_RxStuff(h2->htc, H2_prism_complete,
+	    NULL, NULL, NAN, h2->sess->t_idle + cache_param->timeout_idle, 256);
+	if (hs != HTC_S_COMPLETE) {
+		/* XXX clean up req thread */
+		VSLb(h2->vsl, SLT_Debug, "H2: No OU PRISM (hs=%d)", hs);
+		Req_Release(req);
+		Lck_Unlock(&h2->sess->mtx);
+		SES_Delete(h2->sess, SC_RX_JUNK, NAN);
+		return (0);
+	}
+	HTC_RxPipeline(h2->htc, h2->htc->rxbuf_b + sizeof(H2_prism));
+	HTC_RxInit(h2->htc, wrk->aws);
+	VSLb(h2->vsl, SLT_Debug, "H2: Got PRISM");
+	return (1);
+}
+
+static void __match_proto__(task_func_t)
+h2_new_session(struct worker *wrk, void *arg)
+{
+	struct req *req;
+	struct sess *sp;
+	struct h2_sess *h2;
+	struct h2_req *r2;
+	char *wsp;
+
+	CHECK_OBJ_NOTNULL(wrk, WORKER_MAGIC);
+	CAST_OBJ_NOTNULL(req, arg, REQ_MAGIC);
+	sp = req->sp;
+	CHECK_OBJ_NOTNULL(sp, SESS_MAGIC);
+
+	assert(req->transport == &H2_transport);
+
+	THR_SetRequest(req);
+	wsp = WS_Snapshot(wrk->aws);
+
+	switch(req->err_code) {
+	case 0:
+		/* Direct H2 connection (via Proxy) */
+		h2 = h2_new_sess(wrk, sp, req);
+		Lck_Lock(&h2->sess->mtx);
+		(void)h2_new_req(wrk, h2, 0, NULL);
+		break;
+	case 1:
+		/* Prior Knowledge H1->H2 upgrade */
+		h2 = h2_new_sess(wrk, sp, req);
+		Lck_Lock(&h2->sess->mtx);
+		(void)h2_new_req(wrk, h2, 0, NULL);
+
+		if (!h2_new_pu_session(wrk, h2))
+			return;
+		break;
+	case 2:
+		/* Optimistic H1->H2 upgrade */
+		h2 = h2_new_sess(wrk, sp, NULL);
+		Lck_Lock(&h2->sess->mtx);
+		(void)h2_new_req(wrk, h2, 0, NULL);
+
+		if (!h2_new_ou_session(wrk, h2, req))
+			return;
+		break;
+	default:
+		WRONG("Bad req->err_code");
+	}
+
+	THR_SetRequest(h2->srq);
+
+	H2_Send_Frame(wrk, h2,
+	    H2_FRAME_SETTINGS, H2FF_NONE, sizeof H2_settings, 0, H2_settings);
+
+	/* and off we go... */
+	Lck_Unlock(&h2->sess->mtx);
+
+	while (h2_rxframe(wrk, h2)) {
+		WS_Reset(wrk->aws, wsp);
+		HTC_RxInit(h2->htc, wrk->aws);
+	}
+
+	r2 = VTAILQ_FIRST(&h2->streams);
+	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
+	assert(r2->stream == 0);
+	h2_del_req(wrk, r2, H2E_NO_ERROR);
+}
+
+struct transport H2_transport = {
+	.name =			"H2",
+	.magic =		TRANSPORT_MAGIC,
+	.new_session =		h2_new_session,
+	.sess_panic =		h2_sess_panic,
+	.deliver =		h2_deliver,
+};
diff --git a/bin/varnishd/http2/cache_http2_send.c b/bin/varnishd/http2/cache_http2_send.c
new file mode 100644
index 0000000..70f48bf
--- /dev/null
+++ b/bin/varnishd/http2/cache_http2_send.c
@@ -0,0 +1,114 @@
+/*-
+ * Copyright (c) 2016 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Poul-Henning Kamp <phk at phk.freebsd.dk>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED.  IN NO EVENT SHALL AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ *
+ */
+
+#include "config.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <netinet/in.h>
+
+#include <ctype.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "../cache/cache.h"
+#include "../cache/cache_filter.h"
+#include "../cache/cache_transport.h"
+#include "../http2/cache_http2.h"
+
+#include "vend.h"
+#include "vsb.h"
+
+static void
+h2_mk_hdr(uint8_t *hdr, enum h2_frame_e type, uint8_t flags,
+    uint32_t len, uint32_t stream)
+{
+
+	AN(hdr);
+	assert(len < (1U << 24));
+	vbe32enc(hdr, len << 8);
+	hdr[3] = (uint8_t)type;
+	hdr[4] = flags;
+	vbe32enc(hdr + 5, stream);
+}
+
+/*
+ * This is the "raw" frame sender, all per stream accounting and
+ * prioritization must have happened before this is called, and
+ * the session mtx must be held.
+ */
+
+int
+H2_Send_Frame(struct worker *wrk, const struct h2_sess *h2,
+    enum h2_frame_e type, uint8_t flags,
+    uint32_t len, uint32_t stream, const void *ptr)
+{
+	uint8_t hdr[9];
+
+	Lck_AssertHeld(&h2->sess->mtx);
+	(void)wrk;
+
+	h2_mk_hdr(hdr, type, flags, len, stream);
+	VSLb_bin(h2->vsl, SLT_H2TxHdr, 9, hdr);
+
+	/*XXX*/(void)write(h2->sess->fd, hdr, sizeof hdr);
+	if (len > 0) {
+		/*XXX*/(void)write(h2->sess->fd, ptr, len);
+		VSLb_bin(h2->vsl, SLT_H2TxBody, len, ptr);
+	}
+	return (0);
+}
+
+/*
+ * This is the per-stream frame sender.
+ * XXX: windows
+ * XXX: priority
+ */
+
+int
+H2_Send(struct worker *wrk, struct h2_req *r2, int flush,
+    enum h2_frame_e type, uint8_t flags, uint32_t len, const void *ptr)
+{
+	int retval;
+	struct h2_sess *h2;
+
+	(void)flush;
+
+	CHECK_OBJ_NOTNULL(r2, H2_REQ_MAGIC);
+	h2 = r2->h2sess;
+	CHECK_OBJ_NOTNULL(h2, H2_SESS_MAGIC);
+
+	Lck_Lock(&h2->sess->mtx);
+	retval = H2_Send_Frame(wrk, h2, type, flags, len, r2->stream, ptr);
+	Lck_Unlock(&h2->sess->mtx);
+	return (retval);
+}



More information about the varnish-commit mailing list