[master] 59c4ad8 Add gquintar's HTTP2 support for varnishtest.
Poul-Henning Kamp
phk at FreeBSD.org
Tue Aug 30 12:41:11 CEST 2016
commit 59c4ad8e8e231c4ad7acbd36d40e966c7038ee2a
Author: Poul-Henning Kamp <phk at FreeBSD.org>
Date: Tue Aug 30 10:39:40 2016 +0000
Add gquintar's HTTP2 support for varnishtest.
diff --git a/bin/varnishtest/Makefile.am b/bin/varnishtest/Makefile.am
index d6dbc10..1c519e1 100644
--- a/bin/varnishtest/Makefile.am
+++ b/bin/varnishtest/Makefile.am
@@ -23,6 +23,7 @@ AM_CPPFLAGS = \
bin_PROGRAMS = varnishtest
varnishtest_SOURCES = \
+ hpack.h \
programs.h \
vmods.h \
vtc.c \
@@ -30,7 +31,15 @@ varnishtest_SOURCES = \
vtc_barrier.c \
vtc_client.c \
vtc_http.c \
+ vtc_http.h \
vtc_log.c \
+ vtc_http2.c \
+ vtc_h2_dectbl.h \
+ vtc_h2_enctbl.h \
+ vtc_h2_hpack.c \
+ vtc_h2_priv.h \
+ vtc_h2_stattbl.h \
+ vtc_h2_tbl.c \
vtc_logexp.c \
vtc_main.c \
vtc_process.c \
diff --git a/bin/varnishtest/flint.lnt b/bin/varnishtest/flint.lnt
index b7306f1..a02b3be 100644
--- a/bin/varnishtest/flint.lnt
+++ b/bin/varnishtest/flint.lnt
@@ -32,3 +32,4 @@
-efile(451, vmods.h)
-efile(451, programs.h)
+-efile(451, vtc_h2_stattbl.h)
diff --git a/bin/varnishtest/flint.sh b/bin/varnishtest/flint.sh
index 45bbfd8..7f9c759 100755
--- a/bin/varnishtest/flint.sh
+++ b/bin/varnishtest/flint.sh
@@ -1,5 +1,11 @@
#!/bin/sh
+if [ "x$1" = "x-ok" -a -f _.fl ] ; then
+ echo "Saved as reference"
+ mv _.fl _.fl.old
+ exit 0
+fi
+
flexelint \
-DTOP_BUILDDIR='"foo"' \
-I/usr/include \
@@ -10,4 +16,14 @@ flexelint \
../../flint.lnt \
../flint.lnt \
flint.lnt \
- *.c
+ *.c \
+ 2>&1 | tee _.fl
+
+if [ -f _.fl.old ] ; then
+ diff -u _.fl.old _.fl
+fi
+
+if [ "x$1" = "x-ok" ] ; then
+ echo "Saved as reference"
+ mv _.fl _.fl.old
+fi
diff --git a/bin/varnishtest/hpack.h b/bin/varnishtest/hpack.h
new file mode 100644
index 0000000..feca984
--- /dev/null
+++ b/bin/varnishtest/hpack.h
@@ -0,0 +1,53 @@
+#include <stdint.h>
+
+enum hpk_result{
+ hpk_more = 0,
+ hpk_done,
+ hpk_err,
+};
+
+enum hpk_indexed {
+ hpk_unset = 0,
+ hpk_idx,
+ hpk_inc,
+ hpk_not,
+ hpk_never,
+};
+
+struct txt {
+ char *ptr;
+ int len;
+ int huff;
+};
+
+struct hpk_hdr {
+ struct txt key;
+ struct txt value;
+ enum hpk_indexed t;
+ int i;
+};
+
+struct hpk_ctx;
+struct hpk_iter;
+
+struct hpk_ctx * HPK_NewCtx(uint32_t tblsize);
+void HPK_FreeCtx(struct hpk_ctx *ctx);
+
+struct hpk_iter * HPK_NewIter(struct hpk_ctx *ctx, char *buf, int size);
+void HPK_FreeIter(struct hpk_iter *iter);
+
+enum hpk_result HPK_DecHdr(struct hpk_iter *iter, struct hpk_hdr *header);
+enum hpk_result HPK_EncHdr(struct hpk_iter *iter, const struct hpk_hdr *header);
+
+int gethpk_iterLen(const struct hpk_iter *iter);
+
+enum hpk_result HPK_ResizeTbl(struct hpk_ctx *ctx, uint32_t num);
+
+const struct hpk_hdr * HPK_GetHdr(const struct hpk_ctx *ctx, uint32_t index);
+
+uint32_t HPK_GetTblSize(const struct hpk_ctx *ctx);
+uint32_t HPK_GetTblMaxSize(const struct hpk_ctx *ctx);
+uint32_t HPK_GetTblLength(const struct hpk_ctx *ctx);
+
+/* DEBUG */
+void dump_dyn_tbl(const struct hpk_ctx *ctx);
diff --git a/bin/varnishtest/tests/README b/bin/varnishtest/tests/README
index 80fd68a..971ee3d 100644
--- a/bin/varnishtest/tests/README
+++ b/bin/varnishtest/tests/README
@@ -29,15 +29,3 @@ Naming scheme
id ~ [t] --> sTreaming tests
id ~ [u] --> Unusual background processes
id ~ [v] --> VCL tests: execute VRT functions
-
-
-Private test scope
-------------------
-
- Test cases matching
-
- [id]1%04d.vtc
-
- are reserved for private tests.
-
-
diff --git a/bin/varnishtest/vtc.c b/bin/varnishtest/vtc.c
index 11ab41d..f3bf538 100644
--- a/bin/varnishtest/vtc.c
+++ b/bin/varnishtest/vtc.c
@@ -262,7 +262,7 @@ parse_string(const char *spec, const struct cmds *cmd, void *priv,
{
char *token_s[MAX_TOKENS], *token_e[MAX_TOKENS];
struct vsb *token_exp[MAX_TOKENS];
- char *p, *q, *f, *buf;
+ char *e, *p, *q, *f, *buf;
int nest_brace;
int tn;
const struct cmds *cp;
@@ -270,7 +270,9 @@ parse_string(const char *spec, const struct cmds *cmd, void *priv,
AN(spec);
buf = strdup(spec);
AN(buf);
- for (p = buf; *p != '\0'; p++) {
+ e = strchr(buf, '\0');
+ AN(e);
+ for (p = buf; p < e; p++) {
if (vtc_error || vtc_stop)
break;
/* Start of line */
@@ -298,8 +300,9 @@ parse_string(const char *spec, const struct cmds *cmd, void *priv,
/* First content on line, collect tokens */
tn = 0;
f = p;
- while (*p != '\0') {
+ while (p < e) {
assert(tn < MAX_TOKENS);
+ assert(p < e);
if (*p == '\n') { /* End on NL */
break;
}
@@ -315,6 +318,7 @@ parse_string(const char *spec, const struct cmds *cmd, void *priv,
token_s[tn] = ++p;
q = p;
for (; *p != '\0'; p++) {
+ assert(p < e);
if (*p == '"')
break;
if (*p == '\\') {
@@ -334,7 +338,7 @@ parse_string(const char *spec, const struct cmds *cmd, void *priv,
} else if (*p == '{') { /* Braces */
nest_brace = 0;
token_s[tn] = p + 1;
- for (; *p != '\0'; p++) {
+ for (; p < e; p++) {
if (*p == '{')
nest_brace++;
else if (*p == '}') {
@@ -346,25 +350,26 @@ parse_string(const char *spec, const struct cmds *cmd, void *priv,
token_e[tn++] = p++;
} else { /* other tokens */
token_s[tn] = p;
- for (; *p != '\0' && !isspace(*p); p++)
- ;
+ for (; p < e && !isspace(*p); p++)
+ continue;
token_e[tn++] = p;
}
}
+
+ assert(p <= e);
assert(tn < MAX_TOKENS);
token_s[tn] = NULL;
for (tn = 0; token_s[tn] != NULL; tn++) {
token_exp[tn] = NULL;
AN(token_e[tn]); /*lint !e771 */
*token_e[tn] = '\0'; /*lint !e771 */
- if (NULL == strstr(token_s[tn], "${"))
- continue;
- token_exp[tn] = macro_expand(vl, token_s[tn]);
- if (vtc_error) {
- return;
+ if (NULL != strstr(token_s[tn], "${")) {
+ token_exp[tn] = macro_expand(vl, token_s[tn]);
+ if (vtc_error)
+ return;
+ token_s[tn] = VSB_data(token_exp[tn]);
+ token_e[tn] = strchr(token_s[tn], '\0');
}
- token_s[tn] = VSB_data(token_exp[tn]);
- token_e[tn] = strchr(token_s[tn], '\0');
}
for (cp = cmd; cp->name != NULL; cp++)
@@ -507,13 +512,14 @@ cmd_err_shell(CMD_ARGS)
VSB_destroy(&vsb);
}
-/* SECTION: delay delay
+/* SECTION: client-server.spec.delay delay
*
- * This is the equivalent of ``sleep`` in shell: the command takes one argument
- * that is the number of seconds (can be a float) to wait before continuing the
- * test.
+ * Take a float as argument and sleep for that number of seconds.
+ */
+/* SECTION: stream.spec.delay delay
+ *
+ * Take a float as argument and sleep for that number of seconds.
*/
-
void
cmd_delay(CMD_ARGS)
{
diff --git a/bin/varnishtest/vtc.h b/bin/varnishtest/vtc.h
index b872e89..a614cd0 100644
--- a/bin/varnishtest/vtc.h
+++ b/bin/varnishtest/vtc.h
@@ -80,6 +80,10 @@ void init_barrier(void);
void init_server(void);
int http_process(struct vtclog *vl, const char *spec, int sock, int *sfd);
+int http2_process(struct vtclog *vl, const char *spec, int sock, int *sfd,
+ unsigned nosettings);
+
+char * synth_body(const char *len, int rnd);
void cmd_server_genvcl(struct vsb *vsb);
@@ -107,3 +111,10 @@ struct vsb *macro_expand(struct vtclog *vl, const char *text);
void extmacro_def(const char *name, const char *fmt, ...)
__v_printflike(2, 3);
+
+struct http;
+extern const struct cmds http_cmds[];
+void cmd_stream(CMD_ARGS);
+void start_h2(struct http *hp);
+void stop_h2(struct http *hp);
+void b64_settings(const struct http *hp, const char *s);
diff --git a/bin/varnishtest/vtc_doc_hdr.rst b/bin/varnishtest/vtc_doc_hdr.rst
new file mode 100644
index 0000000..de8541f
--- /dev/null
+++ b/bin/varnishtest/vtc_doc_hdr.rst
@@ -0,0 +1,33 @@
+Syntax RFP for HTTP2 in varnishtest
+===================================
+
+This document tries to document how varnishtest would work this H/2, adapting to
+the introduction of stream. The main idea is the introduction of a new command
+(stream) inside client and server specification that naively maps to RFC7540.
+
+It provides little abstraction, allowwing great control over test scenario,
+while still retaining a familiar logic.
+
+Here's an example of test file::
+
+ server s1 {
+ non-fatal
+ stream 1 {
+ rxreq
+ expect req.http.foo == <undef>
+ txgoaway -laststream 0 -err 9 -debug "COMPRESSION_ERROR"
+ } -run
+ } -start
+
+ client c1 -connect ${s1_sock} {
+ stream 1 {
+ txreq -idxHdr 100 -litHdr inc plain "foo" plain "bar"
+ rxgoaway
+ expect goaway.err == 9
+ expect goaway.laststream == 0
+ expect goaway.debug == "COMPRESSION_ERROR"
+ } -run
+ } -run
+
+.. contents::
+
diff --git a/bin/varnishtest/vtc_h2_dectbl.h b/bin/varnishtest/vtc_h2_dectbl.h
new file mode 100644
index 0000000..03a2d73
--- /dev/null
+++ b/bin/varnishtest/vtc_h2_dectbl.h
@@ -0,0 +1,1028 @@
+struct stbl;
+
+struct ssym {
+ uint8_t csm; /* bits consumed */
+ uint8_t chr; /* character */
+ struct stbl *nxt; /* next table */
+};
+
+struct stbl {
+ int msk;
+ struct ssym syms[];
+};
+
+static struct stbl byte3_pref0110 = {
+ 1,
+ {
+ /* idx 0 */ {1, 199, NULL},
+ /* idx 1 */ {1, 207, NULL},
+ }
+};
+
+static struct stbl byte3_pref0111 = {
+ 1,
+ {
+ /* idx 0 */ {1, 234, NULL},
+ /* idx 1 */ {1, 235, NULL},
+ }
+};
+
+static struct stbl byte3_pref1000 = {
+ 2,
+ {
+ /* idx 0 */ {2, 192, NULL},
+ /* idx 1 */ {2, 193, NULL},
+ /* idx 2 */ {2, 200, NULL},
+ /* idx 3 */ {2, 201, NULL},
+ }
+};
+
+static struct stbl byte3_pref1001 = {
+ 2,
+ {
+ /* idx 0 */ {2, 202, NULL},
+ /* idx 1 */ {2, 205, NULL},
+ /* idx 2 */ {2, 210, NULL},
+ /* idx 3 */ {2, 213, NULL},
+ }
+};
+
+static struct stbl byte3_pref1010 = {
+ 2,
+ {
+ /* idx 0 */ {2, 218, NULL},
+ /* idx 1 */ {2, 219, NULL},
+ /* idx 2 */ {2, 238, NULL},
+ /* idx 3 */ {2, 240, NULL},
+ }
+};
+
+static struct stbl byte3_pref1011 = {
+ 3,
+ {
+ /* idx 0 */ {2, 242, NULL},
+ /* idx 1 */ {2, 242, NULL},
+ /* idx 2 */ {2, 243, NULL},
+ /* idx 3 */ {2, 243, NULL},
+ /* idx 4 */ {2, 255, NULL},
+ /* idx 5 */ {2, 255, NULL},
+ /* idx 6 */ {3, 203, NULL},
+ /* idx 7 */ {3, 204, NULL},
+ }
+};
+
+static struct stbl byte3_pref1100 = {
+ 3,
+ {
+ /* idx 0 */ {3, 211, NULL},
+ /* idx 1 */ {3, 212, NULL},
+ /* idx 2 */ {3, 214, NULL},
+ /* idx 3 */ {3, 221, NULL},
+ /* idx 4 */ {3, 222, NULL},
+ /* idx 5 */ {3, 223, NULL},
+ /* idx 6 */ {3, 241, NULL},
+ /* idx 7 */ {3, 244, NULL},
+ }
+};
+
+static struct stbl byte3_pref1101 = {
+ 3,
+ {
+ /* idx 0 */ {3, 245, NULL},
+ /* idx 1 */ {3, 246, NULL},
+ /* idx 2 */ {3, 247, NULL},
+ /* idx 3 */ {3, 248, NULL},
+ /* idx 4 */ {3, 250, NULL},
+ /* idx 5 */ {3, 251, NULL},
+ /* idx 6 */ {3, 252, NULL},
+ /* idx 7 */ {3, 253, NULL},
+ }
+};
+
+static struct stbl byte3_pref1110 = {
+ 4,
+ {
+ /* idx 0 */ {3, 254, NULL},
+ /* idx 1 */ {3, 254, NULL},
+ /* idx 2 */ {4, 2, NULL},
+ /* idx 3 */ {4, 3, NULL},
+ /* idx 4 */ {4, 4, NULL},
+ /* idx 5 */ {4, 5, NULL},
+ /* idx 6 */ {4, 6, NULL},
+ /* idx 7 */ {4, 7, NULL},
+ /* idx 8 */ {4, 8, NULL},
+ /* idx 9 */ {4, 11, NULL},
+ /* idx 10 */ {4, 12, NULL},
+ /* idx 11 */ {4, 14, NULL},
+ /* idx 12 */ {4, 15, NULL},
+ /* idx 13 */ {4, 16, NULL},
+ /* idx 14 */ {4, 17, NULL},
+ /* idx 15 */ {4, 18, NULL},
+ }
+};
+
+static struct stbl byte3_pref1111 = {
+ 6,
+ {
+ /* idx 0 */ {4, 19, NULL},
+ /* idx 1 */ {4, 19, NULL},
+ /* idx 2 */ {4, 19, NULL},
+ /* idx 3 */ {4, 19, NULL},
+ /* idx 4 */ {4, 20, NULL},
+ /* idx 5 */ {4, 20, NULL},
+ /* idx 6 */ {4, 20, NULL},
+ /* idx 7 */ {4, 20, NULL},
+ /* idx 8 */ {4, 21, NULL},
+ /* idx 9 */ {4, 21, NULL},
+ /* idx 10 */ {4, 21, NULL},
+ /* idx 11 */ {4, 21, NULL},
+ /* idx 12 */ {4, 23, NULL},
+ /* idx 13 */ {4, 23, NULL},
+ /* idx 14 */ {4, 23, NULL},
+ /* idx 15 */ {4, 23, NULL},
+ /* idx 16 */ {4, 24, NULL},
+ /* idx 17 */ {4, 24, NULL},
+ /* idx 18 */ {4, 24, NULL},
+ /* idx 19 */ {4, 24, NULL},
+ /* idx 20 */ {4, 25, NULL},
+ /* idx 21 */ {4, 25, NULL},
+ /* idx 22 */ {4, 25, NULL},
+ /* idx 23 */ {4, 25, NULL},
+ /* idx 24 */ {4, 26, NULL},
+ /* idx 25 */ {4, 26, NULL},
+ /* idx 26 */ {4, 26, NULL},
+ /* idx 27 */ {4, 26, NULL},
+ /* idx 28 */ {4, 27, NULL},
+ /* idx 29 */ {4, 27, NULL},
+ /* idx 30 */ {4, 27, NULL},
+ /* idx 31 */ {4, 27, NULL},
+ /* idx 32 */ {4, 28, NULL},
+ /* idx 33 */ {4, 28, NULL},
+ /* idx 34 */ {4, 28, NULL},
+ /* idx 35 */ {4, 28, NULL},
+ /* idx 36 */ {4, 29, NULL},
+ /* idx 37 */ {4, 29, NULL},
+ /* idx 38 */ {4, 29, NULL},
+ /* idx 39 */ {4, 29, NULL},
+ /* idx 40 */ {4, 30, NULL},
+ /* idx 41 */ {4, 30, NULL},
+ /* idx 42 */ {4, 30, NULL},
+ /* idx 43 */ {4, 30, NULL},
+ /* idx 44 */ {4, 31, NULL},
+ /* idx 45 */ {4, 31, NULL},
+ /* idx 46 */ {4, 31, NULL},
+ /* idx 47 */ {4, 31, NULL},
+ /* idx 48 */ {4, 127, NULL},
+ /* idx 49 */ {4, 127, NULL},
+ /* idx 50 */ {4, 127, NULL},
+ /* idx 51 */ {4, 127, NULL},
+ /* idx 52 */ {4, 220, NULL},
+ /* idx 53 */ {4, 220, NULL},
+ /* idx 54 */ {4, 220, NULL},
+ /* idx 55 */ {4, 220, NULL},
+ /* idx 56 */ {4, 249, NULL},
+ /* idx 57 */ {4, 249, NULL},
+ /* idx 58 */ {4, 249, NULL},
+ /* idx 59 */ {4, 249, NULL},
+ /* idx 60 */ {6, 10, NULL},
+ /* idx 61 */ {6, 13, NULL},
+ /* idx 62 */ {6, 22, NULL},
+ /* idx 63 */ {0, 0, NULL}
+ }
+};
+
+static struct stbl byte2_pref0 = {
+ 5,
+ {
+ /* idx 0 */ {3, 92, NULL}, /* '\' */
+ /* idx 1 */ {3, 92, NULL}, /* '\' */
+ /* idx 2 */ {3, 92, NULL}, /* '\' */
+ /* idx 3 */ {3, 92, NULL}, /* '\' */
+ /* idx 4 */ {3, 195, NULL}, /* . */
+ /* idx 5 */ {3, 195, NULL}, /* . */
+ /* idx 6 */ {3, 195, NULL}, /* . */
+ /* idx 7 */ {3, 195, NULL}, /* . */
+ /* idx 8 */ {3, 208, NULL}, /* . */
+ /* idx 9 */ {3, 208, NULL}, /* . */
+ /* idx 10 */ {3, 208, NULL}, /* . */
+ /* idx 11 */ {3, 208, NULL}, /* . */
+ /* idx 12 */ {4, 128, NULL}, /* . */
+ /* idx 13 */ {4, 128, NULL}, /* . */
+ /* idx 14 */ {4, 130, NULL}, /* . */
+ /* idx 15 */ {4, 130, NULL}, /* . */
+ /* idx 16 */ {4, 131, NULL}, /* . */
+ /* idx 17 */ {4, 131, NULL}, /* . */
+ /* idx 18 */ {4, 162, NULL}, /* . */
+ /* idx 19 */ {4, 162, NULL}, /* . */
+ /* idx 20 */ {4, 184, NULL}, /* . */
+ /* idx 21 */ {4, 184, NULL}, /* . */
+ /* idx 22 */ {4, 194, NULL}, /* . */
+ /* idx 23 */ {4, 194, NULL}, /* . */
+ /* idx 24 */ {4, 224, NULL}, /* . */
+ /* idx 25 */ {4, 224, NULL}, /* . */
+ /* idx 26 */ {4, 226, NULL}, /* . */
+ /* idx 27 */ {4, 226, NULL}, /* . */
+ /* idx 28 */ {5, 153, NULL}, /* . */
+ /* idx 29 */ {5, 161, NULL}, /* . */
+ /* idx 30 */ {5, 167, NULL}, /* . */
+ /* idx 31 */ {5, 172, NULL} /* . */
+ }
+};
+
+static struct stbl byte2_pref1 = {
+ 8,
+ {
+ /* idx 0 */ {5, 176, NULL}, /* . */
+ /* idx 1 */ {5, 176, NULL}, /* . */
+ /* idx 2 */ {5, 176, NULL}, /* . */
+ /* idx 3 */ {5, 176, NULL}, /* . */
+ /* idx 4 */ {5, 176, NULL}, /* . */
+ /* idx 5 */ {5, 176, NULL}, /* . */
+ /* idx 6 */ {5, 176, NULL}, /* . */
+ /* idx 7 */ {5, 176, NULL}, /* . */
+ /* idx 8 */ {5, 177, NULL}, /* . */
+ /* idx 9 */ {5, 177, NULL}, /* . */
+ /* idx 10 */ {5, 177, NULL}, /* . */
+ /* idx 11 */ {5, 177, NULL}, /* . */
+ /* idx 12 */ {5, 177, NULL}, /* . */
+ /* idx 13 */ {5, 177, NULL}, /* . */
+ /* idx 14 */ {5, 177, NULL}, /* . */
+ /* idx 15 */ {5, 177, NULL}, /* . */
+ /* idx 16 */ {5, 179, NULL}, /* */
+ /* idx 17 */ {5, 179, NULL}, /* */
+ /* idx 18 */ {5, 179, NULL}, /* */
+ /* idx 19 */ {5, 179, NULL}, /* */
+ /* idx 20 */ {5, 179, NULL}, /* */
+ /* idx 21 */ {5, 179, NULL}, /* */
+ /* idx 22 */ {5, 179, NULL}, /* */
+ /* idx 23 */ {5, 179, NULL}, /* */
+ /* idx 24 */ {5, 209, NULL}, /* . */
+ /* idx 25 */ {5, 209, NULL}, /* . */
+ /* idx 26 */ {5, 209, NULL}, /* . */
+ /* idx 27 */ {5, 209, NULL}, /* . */
+ /* idx 28 */ {5, 209, NULL}, /* . */
+ /* idx 29 */ {5, 209, NULL}, /* . */
+ /* idx 30 */ {5, 209, NULL}, /* . */
+ /* idx 31 */ {5, 209, NULL}, /* . */
+ /* idx 32 */ {5, 216, NULL}, /* . */
+ /* idx 33 */ {5, 216, NULL}, /* . */
+ /* idx 34 */ {5, 216, NULL}, /* . */
+ /* idx 35 */ {5, 216, NULL}, /* . */
+ /* idx 36 */ {5, 216, NULL}, /* . */
+ /* idx 37 */ {5, 216, NULL}, /* . */
+ /* idx 38 */ {5, 216, NULL}, /* . */
+ /* idx 39 */ {5, 216, NULL}, /* . */
+ /* idx 40 */ {5, 217, NULL}, /* . */
+ /* idx 41 */ {5, 217, NULL}, /* . */
+ /* idx 42 */ {5, 217, NULL}, /* . */
+ /* idx 43 */ {5, 217, NULL}, /* . */
+ /* idx 44 */ {5, 217, NULL}, /* . */
+ /* idx 45 */ {5, 217, NULL}, /* . */
+ /* idx 46 */ {5, 217, NULL}, /* . */
+ /* idx 47 */ {5, 217, NULL}, /* . */
+ /* idx 48 */ {5, 227, NULL}, /* . */
+ /* idx 49 */ {5, 227, NULL}, /* . */
+ /* idx 50 */ {5, 227, NULL}, /* . */
+ /* idx 51 */ {5, 227, NULL}, /* . */
+ /* idx 52 */ {5, 227, NULL}, /* . */
+ /* idx 53 */ {5, 227, NULL}, /* . */
+ /* idx 54 */ {5, 227, NULL}, /* . */
+ /* idx 55 */ {5, 227, NULL}, /* . */
+ /* idx 56 */ {5, 229, NULL}, /* . */
+ /* idx 57 */ {5, 229, NULL}, /* . */
+ /* idx 58 */ {5, 229, NULL}, /* . */
+ /* idx 59 */ {5, 229, NULL}, /* . */
+ /* idx 60 */ {5, 229, NULL}, /* . */
+ /* idx 61 */ {5, 229, NULL}, /* . */
+ /* idx 62 */ {5, 229, NULL}, /* . */
+ /* idx 63 */ {5, 229, NULL}, /* . */
+ /* idx 64 */ {5, 230, NULL}, /* . */
+ /* idx 65 */ {5, 230, NULL}, /* . */
+ /* idx 66 */ {5, 230, NULL}, /* . */
+ /* idx 67 */ {5, 230, NULL}, /* . */
+ /* idx 68 */ {5, 230, NULL}, /* . */
+ /* idx 69 */ {5, 230, NULL}, /* . */
+ /* idx 70 */ {5, 230, NULL}, /* . */
+ /* idx 71 */ {5, 230, NULL}, /* . */
+ /* idx 72 */ {6, 129, NULL}, /* . */
+ /* idx 73 */ {6, 129, NULL}, /* . */
+ /* idx 74 */ {6, 129, NULL}, /* . */
+ /* idx 75 */ {6, 129, NULL}, /* . */
+ /* idx 76 */ {6, 132, NULL}, /* . */
+ /* idx 77 */ {6, 132, NULL}, /* . */
+ /* idx 78 */ {6, 132, NULL}, /* . */
+ /* idx 79 */ {6, 132, NULL}, /* . */
+ /* idx 80 */ {6, 133, NULL}, /* . */
+ /* idx 81 */ {6, 133, NULL}, /* . */
+ /* idx 82 */ {6, 133, NULL}, /* . */
+ /* idx 83 */ {6, 133, NULL}, /* . */
+ /* idx 84 */ {6, 134, NULL}, /* . */
+ /* idx 85 */ {6, 134, NULL}, /* . */
+ /* idx 86 */ {6, 134, NULL}, /* . */
+ /* idx 87 */ {6, 134, NULL}, /* . */
+ /* idx 88 */ {6, 136, NULL}, /* . */
+ /* idx 89 */ {6, 136, NULL}, /* . */
+ /* idx 90 */ {6, 136, NULL}, /* . */
+ /* idx 91 */ {6, 136, NULL}, /* . */
+ /* idx 92 */ {6, 146, NULL}, /* . */
+ /* idx 93 */ {6, 146, NULL}, /* . */
+ /* idx 94 */ {6, 146, NULL}, /* . */
+ /* idx 95 */ {6, 146, NULL}, /* . */
+ /* idx 96 */ {6, 154, NULL}, /* . */
+ /* idx 97 */ {6, 154, NULL}, /* . */
+ /* idx 98 */ {6, 154, NULL}, /* . */
+ /* idx 99 */ {6, 154, NULL}, /* . */
+ /* idx 100 */ {6, 156, NULL}, /* . */
+ /* idx 101 */ {6, 156, NULL}, /* . */
+ /* idx 102 */ {6, 156, NULL}, /* . */
+ /* idx 103 */ {6, 156, NULL}, /* . */
+ /* idx 104 */ {6, 160, NULL}, /* . */
+ /* idx 105 */ {6, 160, NULL}, /* . */
+ /* idx 106 */ {6, 160, NULL}, /* . */
+ /* idx 107 */ {6, 160, NULL}, /* . */
+ /* idx 108 */ {6, 163, NULL}, /* . */
+ /* idx 109 */ {6, 163, NULL}, /* . */
+ /* idx 110 */ {6, 163, NULL}, /* . */
+ /* idx 111 */ {6, 163, NULL}, /* . */
+ /* idx 112 */ {6, 164, NULL}, /* . */
+ /* idx 113 */ {6, 164, NULL}, /* . */
+ /* idx 114 */ {6, 164, NULL}, /* . */
+ /* idx 115 */ {6, 164, NULL}, /* . */
+ /* idx 116 */ {6, 169, NULL}, /* . */
+ /* idx 117 */ {6, 169, NULL}, /* . */
+ /* idx 118 */ {6, 169, NULL}, /* . */
+ /* idx 119 */ {6, 169, NULL}, /* . */
+ /* idx 120 */ {6, 170, NULL}, /* . */
+ /* idx 121 */ {6, 170, NULL}, /* . */
+ /* idx 122 */ {6, 170, NULL}, /* . */
+ /* idx 123 */ {6, 170, NULL}, /* . */
+ /* idx 124 */ {6, 173, NULL}, /* . */
+ /* idx 125 */ {6, 173, NULL}, /* . */
+ /* idx 126 */ {6, 173, NULL}, /* . */
+ /* idx 127 */ {6, 173, NULL}, /* . */
+ /* idx 128 */ {6, 176, NULL}, /* . */
+ /* idx 129 */ {6, 176, NULL}, /* . */
+ /* idx 130 */ {6, 176, NULL}, /* . */
+ /* idx 131 */ {6, 176, NULL}, /* . */
+ /* idx 132 */ {6, 181, NULL}, /* . */
+ /* idx 133 */ {6, 181, NULL}, /* . */
+ /* idx 134 */ {6, 181, NULL}, /* . */
+ /* idx 135 */ {6, 181, NULL}, /* . */
+ /* idx 136 */ {6, 185, NULL}, /* . */
+ /* idx 137 */ {6, 185, NULL}, /* . */
+ /* idx 138 */ {6, 185, NULL}, /* . */
+ /* idx 139 */ {6, 185, NULL}, /* . */
+ /* idx 140 */ {6, 186, NULL}, /* . */
+ /* idx 141 */ {6, 186, NULL}, /* . */
+ /* idx 142 */ {6, 186, NULL}, /* . */
+ /* idx 143 */ {6, 186, NULL}, /* . */
+ /* idx 144 */ {6, 187, NULL}, /* . */
+ /* idx 145 */ {6, 187, NULL}, /* . */
+ /* idx 146 */ {6, 187, NULL}, /* . */
+ /* idx 147 */ {6, 187, NULL}, /* . */
+ /* idx 148 */ {6, 189, NULL}, /* . */
+ /* idx 149 */ {6, 189, NULL}, /* . */
+ /* idx 150 */ {6, 189, NULL}, /* . */
+ /* idx 151 */ {6, 189, NULL}, /* . */
+ /* idx 152 */ {6, 190, NULL}, /* . */
+ /* idx 153 */ {6, 190, NULL}, /* . */
+ /* idx 154 */ {6, 190, NULL}, /* . */
+ /* idx 155 */ {6, 190, NULL}, /* . */
+ /* idx 156 */ {6, 196, NULL}, /* . */
+ /* idx 157 */ {6, 196, NULL}, /* . */
+ /* idx 158 */ {6, 196, NULL}, /* . */
+ /* idx 159 */ {6, 196, NULL}, /* . */
+ /* idx 160 */ {6, 198, NULL}, /* . */
+ /* idx 161 */ {6, 198, NULL}, /* . */
+ /* idx 162 */ {6, 198, NULL}, /* . */
+ /* idx 163 */ {6, 198, NULL}, /* . */
+ /* idx 164 */ {6, 228, NULL}, /* . */
+ /* idx 165 */ {6, 228, NULL}, /* . */
+ /* idx 166 */ {6, 228, NULL}, /* . */
+ /* idx 167 */ {6, 228, NULL}, /* . */
+ /* idx 168 */ {6, 232, NULL}, /* . */
+ /* idx 169 */ {6, 232, NULL}, /* . */
+ /* idx 170 */ {6, 232, NULL}, /* . */
+ /* idx 171 */ {6, 232, NULL}, /* . */
+ /* idx 172 */ {6, 233, NULL}, /* . */
+ /* idx 173 */ {6, 233, NULL}, /* . */
+ /* idx 174 */ {6, 233, NULL}, /* . */
+ /* idx 175 */ {6, 233, NULL}, /* . */
+ /* idx 176 */ {7, 1, NULL},
+ /* idx 177 */ {7, 1, NULL},
+ /* idx 178 */ {7, 135, NULL},
+ /* idx 179 */ {7, 135, NULL},
+ /* idx 180 */ {7, 137, NULL},
+ /* idx 181 */ {7, 137, NULL},
+ /* idx 182 */ {7, 138, NULL},
+ /* idx 183 */ {7, 138, NULL},
+ /* idx 184 */ {7, 139, NULL},
+ /* idx 185 */ {7, 139, NULL},
+ /* idx 186 */ {7, 140, NULL},
+ /* idx 187 */ {7, 140, NULL},
+ /* idx 188 */ {7, 141, NULL},
+ /* idx 189 */ {7, 141, NULL},
+ /* idx 190 */ {7, 143, NULL},
+ /* idx 191 */ {7, 143, NULL},
+ /* idx 192 */ {7, 147, NULL},
+ /* idx 193 */ {7, 147, NULL},
+ /* idx 194 */ {7, 149, NULL},
+ /* idx 195 */ {7, 149, NULL},
+ /* idx 196 */ {7, 150, NULL},
+ /* idx 197 */ {7, 150, NULL},
+ /* idx 198 */ {7, 151, NULL},
+ /* idx 199 */ {7, 151, NULL},
+ /* idx 200 */ {7, 152, NULL},
+ /* idx 201 */ {7, 152, NULL},
+ /* idx 202 */ {7, 155, NULL},
+ /* idx 203 */ {7, 155, NULL},
+ /* idx 204 */ {7, 157, NULL},
+ /* idx 205 */ {7, 157, NULL},
+ /* idx 206 */ {7, 158, NULL},
+ /* idx 207 */ {7, 158, NULL},
+ /* idx 208 */ {7, 165, NULL},
+ /* idx 209 */ {7, 165, NULL},
+ /* idx 210 */ {7, 166, NULL},
+ /* idx 211 */ {7, 166, NULL},
+ /* idx 212 */ {7, 168, NULL},
+ /* idx 213 */ {7, 168, NULL},
+ /* idx 214 */ {7, 174, NULL},
+ /* idx 215 */ {7, 174, NULL},
+ /* idx 216 */ {7, 175, NULL},
+ /* idx 217 */ {7, 175, NULL},
+ /* idx 218 */ {7, 180, NULL},
+ /* idx 219 */ {7, 180, NULL},
+ /* idx 220 */ {7, 182, NULL},
+ /* idx 221 */ {7, 182, NULL},
+ /* idx 222 */ {7, 183, NULL},
+ /* idx 223 */ {7, 183, NULL},
+ /* idx 224 */ {7, 188, NULL},
+ /* idx 225 */ {7, 188, NULL},
+ /* idx 226 */ {7, 191, NULL},
+ /* idx 227 */ {7, 191, NULL},
+ /* idx 228 */ {7, 197, NULL},
+ /* idx 229 */ {7, 197, NULL},
+ /* idx 230 */ {7, 231, NULL},
+ /* idx 231 */ {7, 231, NULL},
+ /* idx 232 */ {7, 239, NULL},
+ /* idx 233 */ {7, 239, NULL},
+ /* idx 234 */ {8, 9, NULL},
+ /* idx 235 */ {8, 142, NULL},
+ /* idx 236 */ {8, 144, NULL},
+ /* idx 237 */ {8, 145, NULL},
+ /* idx 238 */ {8, 148, NULL},
+ /* idx 239 */ {8, 159, NULL},
+ /* idx 240 */ {8, 171, NULL},
+ /* idx 241 */ {8, 206, NULL},
+ /* idx 242 */ {8, 215, NULL},
+ /* idx 243 */ {8, 225, NULL},
+ /* idx 244 */ {8, 236, NULL},
+ /* idx 245 */ {8, 237, NULL},
+ /* idx 246 */ {8, 0, &byte3_pref0110 },
+ /* idx 247 */ {8, 0, &byte3_pref0111 },
+ /* idx 248 */ {8, 0, &byte3_pref1000 },
+ /* idx 249 */ {8, 0, &byte3_pref1001 },
+ /* idx 250 */ {8, 0, &byte3_pref1010 },
+ /* idx 251 */ {8, 0, &byte3_pref1011 },
+ /* idx 252 */ {8, 0, &byte3_pref1100 },
+ /* idx 253 */ {8, 0, &byte3_pref1101 },
+ /* idx 254 */ {8, 0, &byte3_pref1110 },
+ /* idx 255 */ {8, 0, &byte3_pref1111 }
+ }
+};
+
+
+static struct stbl byte1_pref0 = {
+ 2,
+ {
+ /* idx 0 */ {2, 33, NULL}, /* '!' */
+ /* idx 1 */ {2, 34, NULL}, /* '"' */
+ /* idx 2 */ {2, 40, NULL}, /* '(' */
+ /* idx 3 */ {2, 41, NULL}, /* ')' */
+ }
+};
+
+static struct stbl byte1_pref1 = {
+ 8,
+ {
+ /* idx 0 */ {2, 63, NULL}, /* '?' */
+ /* idx 1 */ {2, 63, NULL}, /* '?' */
+ /* idx 2 */ {2, 63, NULL}, /* '?' */
+ /* idx 3 */ {2, 63, NULL}, /* '?' */
+ /* idx 4 */ {2, 63, NULL}, /* '?' */
+ /* idx 5 */ {2, 63, NULL}, /* '?' */
+ /* idx 6 */ {2, 63, NULL}, /* '?' */
+ /* idx 7 */ {2, 63, NULL}, /* '?' */
+ /* idx 8 */ {2, 63, NULL}, /* '?' */
+ /* idx 9 */ {2, 63, NULL}, /* '?' */
+ /* idx 10 */ {2, 63, NULL}, /* '?' */
+ /* idx 11 */ {2, 63, NULL}, /* '?' */
+ /* idx 12 */ {2, 63, NULL}, /* '?' */
+ /* idx 13 */ {2, 63, NULL}, /* '?' */
+ /* idx 14 */ {2, 63, NULL}, /* '?' */
+ /* idx 15 */ {2, 63, NULL}, /* '?' */
+ /* idx 16 */ {2, 63, NULL}, /* '?' */
+ /* idx 17 */ {2, 63, NULL}, /* '?' */
+ /* idx 18 */ {2, 63, NULL}, /* '?' */
+ /* idx 19 */ {2, 63, NULL}, /* '?' */
+ /* idx 20 */ {2, 63, NULL}, /* '?' */
+ /* idx 21 */ {2, 63, NULL}, /* '?' */
+ /* idx 22 */ {2, 63, NULL}, /* '?' */
+ /* idx 23 */ {2, 63, NULL}, /* '?' */
+ /* idx 24 */ {2, 63, NULL}, /* '?' */
+ /* idx 25 */ {2, 63, NULL}, /* '?' */
+ /* idx 26 */ {2, 63, NULL}, /* '?' */
+ /* idx 27 */ {2, 63, NULL}, /* '?' */
+ /* idx 28 */ {2, 63, NULL}, /* '?' */
+ /* idx 29 */ {2, 63, NULL}, /* '?' */
+ /* idx 30 */ {2, 63, NULL}, /* '?' */
+ /* idx 31 */ {2, 63, NULL}, /* '?' */
+ /* idx 32 */ {2, 63, NULL}, /* '?' */
+ /* idx 33 */ {2, 63, NULL}, /* '?' */
+ /* idx 34 */ {2, 63, NULL}, /* '?' */
+ /* idx 35 */ {2, 63, NULL}, /* '?' */
+ /* idx 36 */ {2, 63, NULL}, /* '?' */
+ /* idx 37 */ {2, 63, NULL}, /* '?' */
+ /* idx 38 */ {2, 63, NULL}, /* '?' */
+ /* idx 39 */ {2, 63, NULL}, /* '?' */
+ /* idx 40 */ {2, 63, NULL}, /* '?' */
+ /* idx 41 */ {2, 63, NULL}, /* '?' */
+ /* idx 42 */ {2, 63, NULL}, /* '?' */
+ /* idx 43 */ {2, 63, NULL}, /* '?' */
+ /* idx 44 */ {2, 63, NULL}, /* '?' */
+ /* idx 45 */ {2, 63, NULL}, /* '?' */
+ /* idx 46 */ {2, 63, NULL}, /* '?' */
+ /* idx 47 */ {2, 63, NULL}, /* '?' */
+ /* idx 48 */ {2, 63, NULL}, /* '?' */
+ /* idx 49 */ {2, 63, NULL}, /* '?' */
+ /* idx 50 */ {2, 63, NULL}, /* '?' */
+ /* idx 51 */ {2, 63, NULL}, /* '?' */
+ /* idx 52 */ {2, 63, NULL}, /* '?' */
+ /* idx 53 */ {2, 63, NULL}, /* '?' */
+ /* idx 54 */ {2, 63, NULL}, /* '?' */
+ /* idx 55 */ {2, 63, NULL}, /* '?' */
+ /* idx 56 */ {2, 63, NULL}, /* '?' */
+ /* idx 57 */ {2, 63, NULL}, /* '?' */
+ /* idx 58 */ {2, 63, NULL}, /* '?' */
+ /* idx 59 */ {2, 63, NULL}, /* '?' */
+ /* idx 60 */ {2, 63, NULL}, /* '?' */
+ /* idx 61 */ {2, 63, NULL}, /* '?' */
+ /* idx 62 */ {2, 63, NULL}, /* '?' */
+ /* idx 63 */ {2, 63, NULL}, /* '?' */
+ /* idx 64 */ {3, 39, NULL}, /* ''' */
+ /* idx 65 */ {3, 39, NULL}, /* ''' */
+ /* idx 66 */ {3, 39, NULL}, /* ''' */
+ /* idx 67 */ {3, 39, NULL}, /* ''' */
+ /* idx 68 */ {3, 39, NULL}, /* ''' */
+ /* idx 69 */ {3, 39, NULL}, /* ''' */
+ /* idx 70 */ {3, 39, NULL}, /* ''' */
+ /* idx 71 */ {3, 39, NULL}, /* ''' */
+ /* idx 72 */ {3, 39, NULL}, /* ''' */
+ /* idx 73 */ {3, 39, NULL}, /* ''' */
+ /* idx 74 */ {3, 39, NULL}, /* ''' */
+ /* idx 75 */ {3, 39, NULL}, /* ''' */
+ /* idx 76 */ {3, 39, NULL}, /* ''' */
+ /* idx 77 */ {3, 39, NULL}, /* ''' */
+ /* idx 78 */ {3, 39, NULL}, /* ''' */
+ /* idx 79 */ {3, 39, NULL}, /* ''' */
+ /* idx 80 */ {3, 39, NULL}, /* ''' */
+ /* idx 81 */ {3, 39, NULL}, /* ''' */
+ /* idx 82 */ {3, 39, NULL}, /* ''' */
+ /* idx 83 */ {3, 39, NULL}, /* ''' */
+ /* idx 84 */ {3, 39, NULL}, /* ''' */
+ /* idx 85 */ {3, 39, NULL}, /* ''' */
+ /* idx 86 */ {3, 39, NULL}, /* ''' */
+ /* idx 87 */ {3, 39, NULL}, /* ''' */
+ /* idx 88 */ {3, 39, NULL}, /* ''' */
+ /* idx 89 */ {3, 39, NULL}, /* ''' */
+ /* idx 90 */ {3, 39, NULL}, /* ''' */
+ /* idx 91 */ {3, 39, NULL}, /* ''' */
+ /* idx 92 */ {3, 39, NULL}, /* ''' */
+ /* idx 93 */ {3, 39, NULL}, /* ''' */
+ /* idx 94 */ {3, 39, NULL}, /* ''' */
+ /* idx 95 */ {3, 39, NULL}, /* ''' */
+ /* idx 96 */ {3, 43, NULL}, /* '+' */
+ /* idx 97 */ {3, 43, NULL}, /* '+' */
+ /* idx 98 */ {3, 43, NULL}, /* '+' */
+ /* idx 99 */ {3, 43, NULL}, /* '+' */
+ /* idx 100 */ {3, 43, NULL}, /* '+' */
+ /* idx 101 */ {3, 43, NULL}, /* '+' */
+ /* idx 102 */ {3, 43, NULL}, /* '+' */
+ /* idx 103 */ {3, 43, NULL}, /* '+' */
+ /* idx 104 */ {3, 43, NULL}, /* '+' */
+ /* idx 105 */ {3, 43, NULL}, /* '+' */
+ /* idx 106 */ {3, 43, NULL}, /* '+' */
+ /* idx 107 */ {3, 43, NULL}, /* '+' */
+ /* idx 108 */ {3, 43, NULL}, /* '+' */
+ /* idx 109 */ {3, 43, NULL}, /* '+' */
+ /* idx 110 */ {3, 43, NULL}, /* '+' */
+ /* idx 111 */ {3, 43, NULL}, /* '+' */
+ /* idx 112 */ {3, 43, NULL}, /* '+' */
+ /* idx 113 */ {3, 43, NULL}, /* '+' */
+ /* idx 114 */ {3, 43, NULL}, /* '+' */
+ /* idx 115 */ {3, 43, NULL}, /* '+' */
+ /* idx 116 */ {3, 43, NULL}, /* '+' */
+ /* idx 117 */ {3, 43, NULL}, /* '+' */
+ /* idx 118 */ {3, 43, NULL}, /* '+' */
+ /* idx 119 */ {3, 43, NULL}, /* '+' */
+ /* idx 120 */ {3, 43, NULL}, /* '+' */
+ /* idx 121 */ {3, 43, NULL}, /* '+' */
+ /* idx 122 */ {3, 43, NULL}, /* '+' */
+ /* idx 123 */ {3, 43, NULL}, /* '+' */
+ /* idx 124 */ {3, 43, NULL}, /* '+' */
+ /* idx 125 */ {3, 43, NULL}, /* '+' */
+ /* idx 126 */ {3, 43, NULL}, /* '+' */
+ /* idx 127 */ {3, 43, NULL}, /* '+' */
+ /* idx 128 */ {3, 124, NULL}, /* '|' */
+ /* idx 129 */ {3, 124, NULL}, /* '|' */
+ /* idx 130 */ {3, 124, NULL}, /* '|' */
+ /* idx 131 */ {3, 124, NULL}, /* '|' */
+ /* idx 132 */ {3, 124, NULL}, /* '|' */
+ /* idx 133 */ {3, 124, NULL}, /* '|' */
+ /* idx 134 */ {3, 124, NULL}, /* '|' */
+ /* idx 135 */ {3, 124, NULL}, /* '|' */
+ /* idx 136 */ {3, 124, NULL}, /* '|' */
+ /* idx 137 */ {3, 124, NULL}, /* '|' */
+ /* idx 138 */ {3, 124, NULL}, /* '|' */
+ /* idx 139 */ {3, 124, NULL}, /* '|' */
+ /* idx 140 */ {3, 124, NULL}, /* '|' */
+ /* idx 141 */ {3, 124, NULL}, /* '|' */
+ /* idx 142 */ {3, 124, NULL}, /* '|' */
+ /* idx 143 */ {3, 124, NULL}, /* '|' */
+ /* idx 144 */ {3, 124, NULL}, /* '|' */
+ /* idx 145 */ {3, 124, NULL}, /* '|' */
+ /* idx 146 */ {3, 124, NULL}, /* '|' */
+ /* idx 147 */ {3, 124, NULL}, /* '|' */
+ /* idx 148 */ {3, 124, NULL}, /* '|' */
+ /* idx 149 */ {3, 124, NULL}, /* '|' */
+ /* idx 150 */ {3, 124, NULL}, /* '|' */
+ /* idx 151 */ {3, 124, NULL}, /* '|' */
+ /* idx 152 */ {3, 124, NULL}, /* '|' */
+ /* idx 153 */ {3, 124, NULL}, /* '|' */
+ /* idx 154 */ {3, 124, NULL}, /* '|' */
+ /* idx 155 */ {3, 124, NULL}, /* '|' */
+ /* idx 156 */ {3, 124, NULL}, /* '|' */
+ /* idx 157 */ {3, 124, NULL}, /* '|' */
+ /* idx 158 */ {3, 124, NULL}, /* '|' */
+ /* idx 159 */ {3, 124, NULL}, /* '|' */
+ /* idx 160 */ {4, 35, NULL}, /* '#' */
+ /* idx 161 */ {4, 35, NULL}, /* '#' */
+ /* idx 162 */ {4, 35, NULL}, /* '#' */
+ /* idx 163 */ {4, 35, NULL}, /* '#' */
+ /* idx 164 */ {4, 35, NULL}, /* '#' */
+ /* idx 165 */ {4, 35, NULL}, /* '#' */
+ /* idx 166 */ {4, 35, NULL}, /* '#' */
+ /* idx 167 */ {4, 35, NULL}, /* '#' */
+ /* idx 168 */ {4, 35, NULL}, /* '#' */
+ /* idx 169 */ {4, 35, NULL}, /* '#' */
+ /* idx 170 */ {4, 35, NULL}, /* '#' */
+ /* idx 171 */ {4, 35, NULL}, /* '#' */
+ /* idx 172 */ {4, 35, NULL}, /* '#' */
+ /* idx 173 */ {4, 35, NULL}, /* '#' */
+ /* idx 174 */ {4, 35, NULL}, /* '#' */
+ /* idx 175 */ {4, 35, NULL}, /* '#' */
+ /* idx 176 */ {4, 62, NULL}, /* '>' */
+ /* idx 177 */ {4, 62, NULL}, /* '>' */
+ /* idx 178 */ {4, 62, NULL}, /* '>' */
+ /* idx 179 */ {4, 62, NULL}, /* '>' */
+ /* idx 180 */ {4, 62, NULL}, /* '>' */
+ /* idx 181 */ {4, 62, NULL}, /* '>' */
+ /* idx 182 */ {4, 62, NULL}, /* '>' */
+ /* idx 183 */ {4, 62, NULL}, /* '>' */
+ /* idx 184 */ {4, 62, NULL}, /* '>' */
+ /* idx 185 */ {4, 62, NULL}, /* '>' */
+ /* idx 186 */ {4, 62, NULL}, /* '>' */
+ /* idx 187 */ {4, 62, NULL}, /* '>' */
+ /* idx 188 */ {4, 62, NULL}, /* '>' */
+ /* idx 189 */ {4, 62, NULL}, /* '>' */
+ /* idx 190 */ {4, 62, NULL}, /* '>' */
+ /* idx 191 */ {4, 62, NULL}, /* '>' */
+ /* idx 192 */ {5, 0, NULL}, /* . */
+ /* idx 193 */ {5, 0, NULL}, /* . */
+ /* idx 194 */ {5, 0, NULL}, /* . */
+ /* idx 195 */ {5, 0, NULL}, /* . */
+ /* idx 196 */ {5, 0, NULL}, /* . */
+ /* idx 197 */ {5, 0, NULL}, /* . */
+ /* idx 198 */ {5, 0, NULL}, /* . */
+ /* idx 199 */ {5, 0, NULL}, /* . */
+ /* idx 200 */ {5, 36, NULL}, /* $ */
+ /* idx 201 */ {5, 36, NULL}, /* $ */
+ /* idx 202 */ {5, 36, NULL}, /* $ */
+ /* idx 203 */ {5, 36, NULL}, /* $ */
+ /* idx 204 */ {5, 36, NULL}, /* $ */
+ /* idx 205 */ {5, 36, NULL}, /* $ */
+ /* idx 206 */ {5, 36, NULL}, /* $ */
+ /* idx 207 */ {5, 36, NULL}, /* $ */
+ /* idx 208 */ {5, 64, NULL}, /* '@' */
+ /* idx 209 */ {5, 64, NULL}, /* '@' */
+ /* idx 210 */ {5, 64, NULL}, /* '@' */
+ /* idx 211 */ {5, 64, NULL}, /* '@' */
+ /* idx 212 */ {5, 64, NULL}, /* '@' */
+ /* idx 213 */ {5, 64, NULL}, /* '@' */
+ /* idx 214 */ {5, 64, NULL}, /* '@' */
+ /* idx 215 */ {5, 64, NULL}, /* '@' */
+ /* idx 216 */ {5, 91, NULL}, /* '[' */
+ /* idx 217 */ {5, 91, NULL}, /* '[' */
+ /* idx 218 */ {5, 91, NULL}, /* '[' */
+ /* idx 219 */ {5, 91, NULL}, /* '[' */
+ /* idx 220 */ {5, 91, NULL}, /* '[' */
+ /* idx 221 */ {5, 91, NULL}, /* '[' */
+ /* idx 222 */ {5, 91, NULL}, /* '[' */
+ /* idx 223 */ {5, 91, NULL}, /* '[' */
+ /* idx 224 */ {5, 93, NULL}, /* ']' */
+ /* idx 225 */ {5, 93, NULL}, /* ']' */
+ /* idx 226 */ {5, 93, NULL}, /* ']' */
+ /* idx 227 */ {5, 93, NULL}, /* ']' */
+ /* idx 228 */ {5, 93, NULL}, /* ']' */
+ /* idx 229 */ {5, 93, NULL}, /* ']' */
+ /* idx 230 */ {5, 93, NULL}, /* ']' */
+ /* idx 231 */ {5, 93, NULL}, /* ']' */
+ /* idx 232 */ {5, 126, NULL}, /* '~' */
+ /* idx 233 */ {5, 126, NULL}, /* '~' */
+ /* idx 234 */ {5, 126, NULL}, /* '~' */
+ /* idx 235 */ {5, 126, NULL}, /* '~' */
+ /* idx 236 */ {5, 126, NULL}, /* '~' */
+ /* idx 237 */ {5, 126, NULL}, /* '~' */
+ /* idx 238 */ {5, 126, NULL}, /* '~' */
+ /* idx 239 */ {5, 126, NULL}, /* '~' */
+ /* idx 240 */ {6, 94, NULL}, /* '^' */
+ /* idx 241 */ {6, 94, NULL}, /* '^' */
+ /* idx 242 */ {6, 94, NULL}, /* '^' */
+ /* idx 243 */ {6, 94, NULL}, /* '^' */
+ /* idx 244 */ {6, 125, NULL}, /* '}' */
+ /* idx 245 */ {6, 125, NULL}, /* '}' */
+ /* idx 246 */ {6, 125, NULL}, /* '}' */
+ /* idx 247 */ {6, 125, NULL}, /* '}' */
+ /* idx 248 */ {7, 60, NULL}, /* '<' */
+ /* idx 249 */ {7, 60, NULL}, /* '<' */
+ /* idx 250 */ {7, 96, NULL}, /* '`' */
+ /* idx 251 */ {7, 96, NULL}, /* '`' */
+ /* idx 252 */ {7, 123, NULL}, /* '{' */
+ /* idx 253 */ {7, 123, NULL}, /* '{' */
+ /* idx 254 */ {8, 0, &byte2_pref0}, /* escape */
+ /* idx 255 */ {8, 0, &byte2_pref1} /* escape */
+ }
+};
+
+
+static struct stbl byte0 = {
+ 8,
+ {
+ /* idx 0 */ {5, 48, NULL}, /* '0' */
+ /* idx 1 */ {5, 48, NULL}, /* '0' */
+ /* idx 2 */ {5, 48, NULL}, /* '0' */
+ /* idx 3 */ {5, 48, NULL}, /* '0' */
+ /* idx 4 */ {5, 48, NULL}, /* '0' */
+ /* idx 5 */ {5, 48, NULL}, /* '0' */
+ /* idx 6 */ {5, 48, NULL}, /* '0' */
+ /* idx 7 */ {5, 48, NULL}, /* '0' */
+ /* idx 8 */ {5, 49, NULL}, /* '1' */
+ /* idx 9 */ {5, 49, NULL}, /* '1' */
+ /* idx 10 */ {5, 49, NULL}, /* '1' */
+ /* idx 11 */ {5, 49, NULL}, /* '1' */
+ /* idx 12 */ {5, 49, NULL}, /* '1' */
+ /* idx 13 */ {5, 49, NULL}, /* '1' */
+ /* idx 14 */ {5, 49, NULL}, /* '1' */
+ /* idx 15 */ {5, 49, NULL}, /* '1' */
+ /* idx 16 */ {5, 50, NULL}, /* '2' */
+ /* idx 17 */ {5, 50, NULL}, /* '2' */
+ /* idx 18 */ {5, 50, NULL}, /* '2' */
+ /* idx 19 */ {5, 50, NULL}, /* '2' */
+ /* idx 20 */ {5, 50, NULL}, /* '2' */
+ /* idx 21 */ {5, 50, NULL}, /* '2' */
+ /* idx 22 */ {5, 50, NULL}, /* '2' */
+ /* idx 23 */ {5, 50, NULL}, /* '2' */
+ /* idx 24 */ {5, 97, NULL}, /* 'a' */
+ /* idx 25 */ {5, 97, NULL}, /* 'a' */
+ /* idx 26 */ {5, 97, NULL}, /* 'a' */
+ /* idx 27 */ {5, 97, NULL}, /* 'a' */
+ /* idx 28 */ {5, 97, NULL}, /* 'a' */
+ /* idx 29 */ {5, 97, NULL}, /* 'a' */
+ /* idx 30 */ {5, 97, NULL}, /* 'a' */
+ /* idx 31 */ {5, 97, NULL}, /* 'a' */
+ /* idx 32 */ {5, 99, NULL}, /* 'c' */
+ /* idx 33 */ {5, 99, NULL}, /* 'c' */
+ /* idx 34 */ {5, 99, NULL}, /* 'c' */
+ /* idx 35 */ {5, 99, NULL}, /* 'c' */
+ /* idx 36 */ {5, 99, NULL}, /* 'c' */
+ /* idx 37 */ {5, 99, NULL}, /* 'c' */
+ /* idx 38 */ {5, 99, NULL}, /* 'c' */
+ /* idx 39 */ {5, 99, NULL}, /* 'c' */
+ /* idx 40 */ {5, 101, NULL}, /* 'e' */
+ /* idx 41 */ {5, 101, NULL}, /* 'e' */
+ /* idx 42 */ {5, 101, NULL}, /* 'e' */
+ /* idx 43 */ {5, 101, NULL}, /* 'e' */
+ /* idx 44 */ {5, 101, NULL}, /* 'e' */
+ /* idx 45 */ {5, 101, NULL}, /* 'e' */
+ /* idx 46 */ {5, 101, NULL}, /* 'e' */
+ /* idx 47 */ {5, 101, NULL}, /* 'e' */
+ /* idx 48 */ {5, 105, NULL}, /* 'i' */
+ /* idx 49 */ {5, 105, NULL}, /* 'i' */
+ /* idx 50 */ {5, 105, NULL}, /* 'i' */
+ /* idx 51 */ {5, 105, NULL}, /* 'i' */
+ /* idx 52 */ {5, 105, NULL}, /* 'i' */
+ /* idx 53 */ {5, 105, NULL}, /* 'i' */
+ /* idx 54 */ {5, 105, NULL}, /* 'i' */
+ /* idx 55 */ {5, 105, NULL}, /* 'i' */
+ /* idx 56 */ {5, 111, NULL}, /* 'o' */
+ /* idx 57 */ {5, 111, NULL}, /* 'o' */
+ /* idx 58 */ {5, 111, NULL}, /* 'o' */
+ /* idx 59 */ {5, 111, NULL}, /* 'o' */
+ /* idx 60 */ {5, 111, NULL}, /* 'o' */
+ /* idx 61 */ {5, 111, NULL}, /* 'o' */
+ /* idx 62 */ {5, 111, NULL}, /* 'o' */
+ /* idx 63 */ {5, 111, NULL}, /* 'o' */
+ /* idx 64 */ {5, 115, NULL}, /* 's' */
+ /* idx 65 */ {5, 115, NULL}, /* 's' */
+ /* idx 66 */ {5, 115, NULL}, /* 's' */
+ /* idx 67 */ {5, 115, NULL}, /* 's' */
+ /* idx 68 */ {5, 115, NULL}, /* 's' */
+ /* idx 69 */ {5, 115, NULL}, /* 's' */
+ /* idx 70 */ {5, 115, NULL}, /* 's' */
+ /* idx 71 */ {5, 115, NULL}, /* 's' */
+ /* idx 72 */ {5, 116, NULL}, /* 't' */
+ /* idx 73 */ {5, 116, NULL}, /* 't' */
+ /* idx 74 */ {5, 116, NULL}, /* 't' */
+ /* idx 75 */ {5, 116, NULL}, /* 't' */
+ /* idx 76 */ {5, 116, NULL}, /* 't' */
+ /* idx 77 */ {5, 116, NULL}, /* 't' */
+ /* idx 78 */ {5, 116, NULL}, /* 't' */
+ /* idx 79 */ {5, 116, NULL}, /* 't' */
+ /* idx 80 */ {6, 32, NULL}, /* tab */
+ /* idx 81 */ {6, 32, NULL}, /* tab */
+ /* idx 82 */ {6, 32, NULL}, /* tab */
+ /* idx 83 */ {6, 32, NULL}, /* tab */
+ /* idx 84 */ {6, 37, NULL}, /* '%' */
+ /* idx 85 */ {6, 37, NULL}, /* '%' */
+ /* idx 86 */ {6, 37, NULL}, /* '%' */
+ /* idx 87 */ {6, 37, NULL}, /* '%' */
+ /* idx 88 */ {6, 45, NULL}, /* '-' */
+ /* idx 89 */ {6, 45, NULL}, /* '-' */
+ /* idx 90 */ {6, 45, NULL}, /* '-' */
+ /* idx 91 */ {6, 45, NULL}, /* '-' */
+ /* idx 92 */ {6, 46, NULL}, /* '.' */
+ /* idx 93 */ {6, 46, NULL}, /* '.' */
+ /* idx 94 */ {6, 46, NULL}, /* '.' */
+ /* idx 95 */ {6, 46, NULL}, /* '.' */
+ /* idx 96 */ {6, 47, NULL}, /* '/' */
+ /* idx 97 */ {6, 47, NULL}, /* '/' */
+ /* idx 98 */ {6, 47, NULL}, /* '/' */
+ /* idx 99 */ {6, 47, NULL}, /* '/' */
+ /* idx 100 */ {6, 51, NULL}, /* '3' */
+ /* idx 101 */ {6, 51, NULL}, /* '3' */
+ /* idx 102 */ {6, 51, NULL}, /* '3' */
+ /* idx 103 */ {6, 51, NULL}, /* '3' */
+ /* idx 104 */ {6, 52, NULL}, /* '4' */
+ /* idx 105 */ {6, 52, NULL}, /* '4' */
+ /* idx 106 */ {6, 52, NULL}, /* '4' */
+ /* idx 107 */ {6, 52, NULL}, /* '4' */
+ /* idx 108 */ {6, 53, NULL}, /* '5' */
+ /* idx 109 */ {6, 53, NULL}, /* '5' */
+ /* idx 110 */ {6, 53, NULL}, /* '5' */
+ /* idx 111 */ {6, 53, NULL}, /* '5' */
+ /* idx 112 */ {6, 54, NULL}, /* '6' */
+ /* idx 113 */ {6, 54, NULL}, /* '6' */
+ /* idx 114 */ {6, 54, NULL}, /* '6' */
+ /* idx 115 */ {6, 54, NULL}, /* '6' */
+ /* idx 116 */ {6, 55, NULL}, /* '7' */
+ /* idx 117 */ {6, 55, NULL}, /* '7' */
+ /* idx 118 */ {6, 55, NULL}, /* '7' */
+ /* idx 119 */ {6, 55, NULL}, /* '7' */
+ /* idx 120 */ {6, 56, NULL}, /* '8' */
+ /* idx 121 */ {6, 56, NULL}, /* '8' */
+ /* idx 122 */ {6, 56, NULL}, /* '8' */
+ /* idx 123 */ {6, 56, NULL}, /* '8' */
+ /* idx 124 */ {6, 57, NULL}, /* '9' */
+ /* idx 125 */ {6, 57, NULL}, /* '9' */
+ /* idx 126 */ {6, 57, NULL}, /* '9' */
+ /* idx 127 */ {6, 57, NULL}, /* '9' */
+ /* idx 128 */ {6, 61, NULL}, /* '=' */
+ /* idx 129 */ {6, 61, NULL}, /* '=' */
+ /* idx 130 */ {6, 61, NULL}, /* '=' */
+ /* idx 131 */ {6, 61, NULL}, /* '=' */
+ /* idx 132 */ {6, 65, NULL}, /* 'A' */
+ /* idx 133 */ {6, 65, NULL}, /* 'A' */
+ /* idx 134 */ {6, 65, NULL}, /* 'A' */
+ /* idx 135 */ {6, 65, NULL}, /* 'A' */
+ /* idx 136 */ {6, 95, NULL}, /* '_' */
+ /* idx 137 */ {6, 95, NULL}, /* '_' */
+ /* idx 138 */ {6, 95, NULL}, /* '_' */
+ /* idx 139 */ {6, 95, NULL}, /* '_' */
+ /* idx 140 */ {6, 98, NULL}, /* 'b' */
+ /* idx 141 */ {6, 98, NULL}, /* 'b' */
+ /* idx 142 */ {6, 98, NULL}, /* 'b' */
+ /* idx 143 */ {6, 98, NULL}, /* 'b' */
+ /* idx 144 */ {6, 100, NULL}, /* 'd' */
+ /* idx 145 */ {6, 100, NULL}, /* 'd' */
+ /* idx 146 */ {6, 100, NULL}, /* 'd' */
+ /* idx 147 */ {6, 100, NULL}, /* 'd' */
+ /* idx 148 */ {6, 102, NULL}, /* 'f' */
+ /* idx 149 */ {6, 102, NULL}, /* 'f' */
+ /* idx 150 */ {6, 102, NULL}, /* 'f' */
+ /* idx 151 */ {6, 102, NULL}, /* 'f' */
+ /* idx 152 */ {6, 103, NULL}, /* 'g' */
+ /* idx 153 */ {6, 103, NULL}, /* 'g' */
+ /* idx 154 */ {6, 103, NULL}, /* 'g' */
+ /* idx 155 */ {6, 103, NULL}, /* 'g' */
+ /* idx 156 */ {6, 104, NULL}, /* 'h' */
+ /* idx 157 */ {6, 104, NULL}, /* 'h' */
+ /* idx 158 */ {6, 104, NULL}, /* 'h' */
+ /* idx 159 */ {6, 104, NULL}, /* 'h' */
+ /* idx 160 */ {6, 108, NULL}, /* 'l' */
+ /* idx 161 */ {6, 108, NULL}, /* 'l' */
+ /* idx 162 */ {6, 108, NULL}, /* 'l' */
+ /* idx 163 */ {6, 108, NULL}, /* 'l' */
+ /* idx 164 */ {6, 109, NULL}, /* 'm' */
+ /* idx 165 */ {6, 109, NULL}, /* 'm' */
+ /* idx 166 */ {6, 109, NULL}, /* 'm' */
+ /* idx 167 */ {6, 109, NULL}, /* 'm' */
+ /* idx 168 */ {6, 110, NULL}, /* 'n' */
+ /* idx 169 */ {6, 110, NULL}, /* 'n' */
+ /* idx 170 */ {6, 110, NULL}, /* 'n' */
+ /* idx 171 */ {6, 110, NULL}, /* 'n' */
+ /* idx 172 */ {6, 112, NULL}, /* 'p' */
+ /* idx 173 */ {6, 112, NULL}, /* 'p' */
+ /* idx 174 */ {6, 112, NULL}, /* 'p' */
+ /* idx 175 */ {6, 112, NULL}, /* 'p' */
+ /* idx 176 */ {6, 114, NULL}, /* 'r' */
+ /* idx 177 */ {6, 114, NULL}, /* 'r' */
+ /* idx 178 */ {6, 114, NULL}, /* 'r' */
+ /* idx 179 */ {6, 114, NULL}, /* 'r' */
+ /* idx 180 */ {6, 117, NULL}, /* 'u' */
+ /* idx 181 */ {6, 117, NULL}, /* 'u' */
+ /* idx 182 */ {6, 117, NULL}, /* 'u' */
+ /* idx 183 */ {6, 117, NULL}, /* 'u' */
+ /* idx 184 */ {7, 58, NULL}, /* ':' */
+ /* idx 185 */ {7, 58, NULL}, /* ':' */
+ /* idx 186 */ {7, 66, NULL}, /* 'B' */
+ /* idx 187 */ {7, 66, NULL}, /* 'B' */
+ /* idx 188 */ {7, 67, NULL}, /* 'C' */
+ /* idx 189 */ {7, 67, NULL}, /* 'C' */
+ /* idx 190 */ {7, 68, NULL}, /* 'D' */
+ /* idx 191 */ {7, 68, NULL}, /* 'D' */
+ /* idx 192 */ {7, 69, NULL}, /* 'E' */
+ /* idx 193 */ {7, 69, NULL}, /* 'E' */
+ /* idx 194 */ {7, 70, NULL}, /* 'F' */
+ /* idx 195 */ {7, 70, NULL}, /* 'F' */
+ /* idx 196 */ {7, 71, NULL}, /* 'G' */
+ /* idx 197 */ {7, 71, NULL}, /* 'G' */
+ /* idx 198 */ {7, 72, NULL}, /* 'H' */
+ /* idx 199 */ {7, 72, NULL}, /* 'H' */
+ /* idx 200 */ {7, 73, NULL}, /* 'I' */
+ /* idx 201 */ {7, 73, NULL}, /* 'I' */
+ /* idx 202 */ {7, 74, NULL}, /* 'J' */
+ /* idx 203 */ {7, 74, NULL}, /* 'J' */
+ /* idx 204 */ {7, 75, NULL}, /* 'K' */
+ /* idx 205 */ {7, 75, NULL}, /* 'K' */
+ /* idx 206 */ {7, 76, NULL}, /* 'L' */
+ /* idx 207 */ {7, 76, NULL}, /* 'L' */
+ /* idx 208 */ {7, 77, NULL}, /* 'M' */
+ /* idx 209 */ {7, 77, NULL}, /* 'M' */
+ /* idx 210 */ {7, 78, NULL}, /* 'N' */
+ /* idx 211 */ {7, 78, NULL}, /* 'N' */
+ /* idx 212 */ {7, 79, NULL}, /* 'O' */
+ /* idx 213 */ {7, 79, NULL}, /* 'O' */
+ /* idx 214 */ {7, 80, NULL}, /* 'P' */
+ /* idx 215 */ {7, 80, NULL}, /* 'P' */
+ /* idx 216 */ {7, 81, NULL}, /* 'Q' */
+ /* idx 217 */ {7, 81, NULL}, /* 'Q' */
+ /* idx 218 */ {7, 82, NULL}, /* 'R' */
+ /* idx 219 */ {7, 82, NULL}, /* 'R' */
+ /* idx 220 */ {7, 83, NULL}, /* 'S' */
+ /* idx 221 */ {7, 83, NULL}, /* 'S' */
+ /* idx 222 */ {7, 84, NULL}, /* 'T' */
+ /* idx 223 */ {7, 84, NULL}, /* 'T' */
+ /* idx 224 */ {7, 85, NULL}, /* 'U' */
+ /* idx 225 */ {7, 85, NULL}, /* 'U' */
+ /* idx 226 */ {7, 86, NULL}, /* 'V' */
+ /* idx 227 */ {7, 86, NULL}, /* 'V' */
+ /* idx 228 */ {7, 87, NULL}, /* 'W' */
+ /* idx 229 */ {7, 87, NULL}, /* 'W' */
+ /* idx 230 */ {7, 89, NULL}, /* 'Y' */
+ /* idx 231 */ {7, 89, NULL}, /* 'Y' */
+ /* idx 232 */ {7, 106, NULL}, /* 'j' */
+ /* idx 233 */ {7, 106, NULL}, /* 'j' */
+ /* idx 234 */ {7, 107, NULL}, /* 'k' */
+ /* idx 235 */ {7, 107, NULL}, /* 'k' */
+ /* idx 236 */ {7, 113, NULL}, /* 'q' */
+ /* idx 237 */ {7, 113, NULL}, /* 'q' */
+ /* idx 238 */ {7, 118, NULL}, /* 'v' */
+ /* idx 239 */ {7, 118, NULL}, /* 'v' */
+ /* idx 240 */ {7, 119, NULL}, /* 'w' */
+ /* idx 241 */ {7, 119, NULL}, /* 'w' */
+ /* idx 242 */ {7, 120, NULL}, /* 'x' */
+ /* idx 243 */ {7, 120, NULL}, /* 'x' */
+ /* idx 244 */ {7, 121, NULL}, /* 'y' */
+ /* idx 245 */ {7, 121, NULL}, /* 'y' */
+ /* idx 246 */ {7, 122, NULL}, /* 'z' */
+ /* idx 247 */ {7, 122, NULL}, /* 'z' */
+ /* idx 248 */ {8, 38, NULL}, /* '&' */
+ /* idx 249 */ {8, 42, NULL}, /* '*' */
+ /* idx 250 */ {8, 44, NULL}, /* ',' */
+ /* idx 251 */ {8, 59, NULL}, /* ';' */
+ /* idx 252 */ {8, 88, NULL}, /* 'X' */
+ /* idx 253 */ {8, 90, NULL}, /* 'Z' */
+ /* idx 254 */ {8, 0, &byte1_pref0}, /* escape */
+ /* idx 255 */ {8, 0, &byte1_pref1} /* escape */
+ }
+};
diff --git a/bin/varnishtest/vtc_h2_enctbl.h b/bin/varnishtest/vtc_h2_enctbl.h
new file mode 100644
index 0000000..5e69f33
--- /dev/null
+++ b/bin/varnishtest/vtc_h2_enctbl.h
@@ -0,0 +1,257 @@
+HPACK(0, 0x1ff8, 13)
+HPACK(1, 0x7fffd8, 23)
+HPACK(2, 0xfffffe2, 28)
+HPACK(3, 0xfffffe3, 28)
+HPACK(4, 0xfffffe4, 28)
+HPACK(5, 0xfffffe5, 28)
+HPACK(6, 0xfffffe6, 28)
+HPACK(7, 0xfffffe7, 28)
+HPACK(8, 0xfffffe8, 28)
+HPACK(9, 0xffffea, 24)
+HPACK(10, 0x3ffffffc, 30)
+HPACK(11, 0xfffffe9, 28)
+HPACK(12, 0xfffffea, 28)
+HPACK(13, 0x3ffffffd, 30)
+HPACK(14, 0xfffffeb, 28)
+HPACK(15, 0xfffffec, 28)
+HPACK(16, 0xfffffed, 28)
+HPACK(17, 0xfffffee, 28)
+HPACK(18, 0xfffffef, 28)
+HPACK(19, 0xffffff0, 28)
+HPACK(20, 0xffffff1, 28)
+HPACK(21, 0xffffff2, 28)
+HPACK(22, 0x3ffffffe, 30)
+HPACK(23, 0xffffff3, 28)
+HPACK(24, 0xffffff4, 28)
+HPACK(25, 0xffffff5, 28)
+HPACK(26, 0xffffff6, 28)
+HPACK(27, 0xffffff7, 28)
+HPACK(28, 0xffffff8, 28)
+HPACK(29, 0xffffff9, 28)
+HPACK(30, 0xffffffa, 28)
+HPACK(31, 0xffffffb, 28)
+HPACK(32, 0x14, 6) /* ' ' */
+HPACK(33, 0x3f8, 10) /* '!' */
+HPACK(34, 0x3f9, 10) /* '"' */
+HPACK(35, 0xffa, 12) /* '#' */
+HPACK(36, 0x1ff9, 13) /* '$' */
+HPACK(37, 0x15, 6) /* '%' */
+HPACK(38, 0xf8, 8) /* '&' */
+HPACK(39, 0x7fa, 11) /* ''' */
+HPACK(40, 0x3fa, 10) /* '(' */
+HPACK(41, 0x3fb, 10) /* ')' */
+HPACK(42, 0xf9, 8) /* '*' */
+HPACK(43, 0x7fb, 11) /* '+' */
+HPACK(44, 0xfa, 8) /* ',' */
+HPACK(45, 0x16, 6) /* '-' */
+HPACK(46, 0x17, 6) /* '.' */
+HPACK(47, 0x18, 6) /* '/' */
+HPACK(48, 0x0, 5) /* '0' */
+HPACK(49, 0x1, 5) /* '1' */
+HPACK(50, 0x2, 5) /* '2' */
+HPACK(51, 0x19, 6) /* '3' */
+HPACK(52, 0x1a, 6) /* '4' */
+HPACK(53, 0x1b, 6) /* '5' */
+HPACK(54, 0x1c, 6) /* '6' */
+HPACK(55, 0x1d, 6) /* '7' */
+HPACK(56, 0x1e, 6) /* '8' */
+HPACK(57, 0x1f, 6) /* '9' */
+HPACK(58, 0x5c, 7) /* ':' */
+HPACK(59, 0xfb, 8) /* ';' */
+HPACK(60, 0x7ffc, 15) /* '<' */
+HPACK(61, 0x20, 6) /* '=' */
+HPACK(62, 0xffb, 12) /* '>' */
+HPACK(63, 0x3fc, 10) /* '?' */
+HPACK(64, 0x1ffa, 13) /* '@' */
+HPACK(65, 0x21, 6) /* 'A' */
+HPACK(66, 0x5d, 7) /* 'B' */
+HPACK(67, 0x5e, 7) /* 'C' */
+HPACK(68, 0x5f, 7) /* 'D' */
+HPACK(69, 0x60, 7) /* 'E' */
+HPACK(70, 0x61, 7) /* 'F' */
+HPACK(71, 0x62, 7) /* 'G' */
+HPACK(72, 0x63, 7) /* 'H' */
+HPACK(73, 0x64, 7) /* 'I' */
+HPACK(74, 0x65, 7) /* 'J' */
+HPACK(75, 0x66, 7) /* 'K' */
+HPACK(76, 0x67, 7) /* 'L' */
+HPACK(77, 0x68, 7) /* 'M' */
+HPACK(78, 0x69, 7) /* 'N' */
+HPACK(79, 0x6a, 7) /* 'O' */
+HPACK(80, 0x6b, 7) /* 'P' */
+HPACK(81, 0x6c, 7) /* 'Q' */
+HPACK(82, 0x6d, 7) /* 'R' */
+HPACK(83, 0x6e, 7) /* 'S' */
+HPACK(84, 0x6f, 7) /* 'T' */
+HPACK(85, 0x70, 7) /* 'U' */
+HPACK(86, 0x71, 7) /* 'V' */
+HPACK(87, 0x72, 7) /* 'W' */
+HPACK(88, 0xfc, 8) /* 'X' */
+HPACK(89, 0x73, 7) /* 'Y' */
+HPACK(90, 0xfd, 8) /* 'Z' */
+HPACK(91, 0x1ffb, 13) /* '[' */
+HPACK(92, 0x7fff0, 19) /* '\' */
+HPACK(93, 0x1ffc, 13) /* ']' */
+HPACK(94, 0x3ffc, 14) /* '^' */
+HPACK(95, 0x22, 6) /* '_' */
+HPACK(96, 0x7ffd, 15) /* '`' */
+HPACK(97, 0x3, 5) /* 'a' */
+HPACK(98, 0x23, 6) /* 'b' */
+HPACK(99, 0x4, 5) /* 'c' */
+HPACK(100, 0x24, 6) /* 'd' */
+HPACK(101, 0x5, 5) /* 'e' */
+HPACK(102, 0x25, 6) /* 'f' */
+HPACK(103, 0x26, 6) /* 'g' */
+HPACK(104, 0x27, 6) /* 'h' */
+HPACK(105, 0x6, 5) /* 'i' */
+HPACK(106, 0x74, 7) /* 'j' */
+HPACK(107, 0x75, 7) /* 'k' */
+HPACK(108, 0x28, 6) /* 'l' */
+HPACK(109, 0x29, 6) /* 'm' */
+HPACK(110, 0x2a, 6) /* 'n' */
+HPACK(111, 0x7, 5) /* 'o' */
+HPACK(112, 0x2b, 6) /* 'p' */
+HPACK(113, 0x76, 7) /* 'q' */
+HPACK(114, 0x2c, 6) /* 'r' */
+HPACK(115, 0x8, 5) /* 's' */
+HPACK(116, 0x9, 5) /* 't' */
+HPACK(117, 0x2d, 6) /* 'u' */
+HPACK(118, 0x77, 7) /* 'v' */
+HPACK(119, 0x78, 7) /* 'w' */
+HPACK(120, 0x79, 7) /* 'x' */
+HPACK(121, 0x7a, 7) /* 'y' */
+HPACK(122, 0x7b, 7) /* 'z' */
+HPACK(123, 0x7ffe, 15) /* '{' */
+HPACK(124, 0x7fc, 11) /* '|' */
+HPACK(125, 0x3ffd, 14) /* '}' */
+HPACK(126, 0x1ffd, 13) /* '~' */
+HPACK(127, 0xffffffc, 28)
+HPACK(128, 0xfffe6, 20)
+HPACK(129, 0x3fffd2, 22)
+HPACK(130, 0xfffe7, 20)
+HPACK(131, 0xfffe8, 20)
+HPACK(132, 0x3fffd3, 22)
+HPACK(133, 0x3fffd4, 22)
+HPACK(134, 0x3fffd5, 22)
+HPACK(135, 0x7fffd9, 23)
+HPACK(136, 0x3fffd6, 22)
+HPACK(137, 0x7fffda, 23)
+HPACK(138, 0x7fffdb, 23)
+HPACK(139, 0x7fffdc, 23)
+HPACK(140, 0x7fffdd, 23)
+HPACK(141, 0x7fffde, 23)
+HPACK(142, 0xffffeb, 24)
+HPACK(143, 0x7fffdf, 23)
+HPACK(144, 0xffffec, 24)
+HPACK(145, 0xffffed, 24)
+HPACK(146, 0x3fffd7, 22)
+HPACK(147, 0x7fffe0, 23)
+HPACK(148, 0xffffee, 24)
+HPACK(149, 0x7fffe1, 23)
+HPACK(150, 0x7fffe2, 23)
+HPACK(151, 0x7fffe3, 23)
+HPACK(152, 0x7fffe4, 23)
+HPACK(153, 0x1fffdc, 21)
+HPACK(154, 0x3fffd8, 22)
+HPACK(155, 0x7fffe5, 23)
+HPACK(156, 0x3fffd9, 22)
+HPACK(157, 0x7fffe6, 23)
+HPACK(158, 0x7fffe7, 23)
+HPACK(159, 0xffffef, 24)
+HPACK(160, 0x3fffda, 22)
+HPACK(161, 0x1fffdd, 21)
+HPACK(162, 0xfffe9, 20)
+HPACK(163, 0x3fffdb, 22)
+HPACK(164, 0x3fffdc, 22)
+HPACK(165, 0x7fffe8, 23)
+HPACK(166, 0x7fffe9, 23)
+HPACK(167, 0x1fffde, 21)
+HPACK(168, 0x7fffea, 23)
+HPACK(169, 0x3fffdd, 22)
+HPACK(170, 0x3fffde, 22)
+HPACK(171, 0xfffff0, 24)
+HPACK(172, 0x1fffdf, 21)
+HPACK(173, 0x3fffdf, 22)
+HPACK(174, 0x7fffeb, 23)
+HPACK(175, 0x7fffec, 23)
+HPACK(176, 0x1fffe0, 21)
+HPACK(177, 0x1fffe1, 21)
+HPACK(178, 0x3fffe0, 22)
+HPACK(179, 0x1fffe2, 21)
+HPACK(180, 0x7fffed, 23)
+HPACK(181, 0x3fffe1, 22)
+HPACK(182, 0x7fffee, 23)
+HPACK(183, 0x7fffef, 23)
+HPACK(184, 0xfffea, 20)
+HPACK(185, 0x3fffe2, 22)
+HPACK(186, 0x3fffe3, 22)
+HPACK(187, 0x3fffe4, 22)
+HPACK(188, 0x7ffff0, 23)
+HPACK(189, 0x3fffe5, 22)
+HPACK(190, 0x3fffe6, 22)
+HPACK(191, 0x7ffff1, 23)
+HPACK(192, 0x3ffffe0, 26)
+HPACK(193, 0x3ffffe1, 26)
+HPACK(194, 0xfffeb, 20)
+HPACK(195, 0x7fff1, 19)
+HPACK(196, 0x3fffe7, 22)
+HPACK(197, 0x7ffff2, 23)
+HPACK(198, 0x3fffe8, 22)
+HPACK(199, 0x1ffffec, 25)
+HPACK(200, 0x3ffffe2, 26)
+HPACK(201, 0x3ffffe3, 26)
+HPACK(202, 0x3ffffe4, 26)
+HPACK(203, 0x7ffffde, 27)
+HPACK(204, 0x7ffffdf, 27)
+HPACK(205, 0x3ffffe5, 26)
+HPACK(206, 0xfffff1, 24)
+HPACK(207, 0x1ffffed, 25)
+HPACK(208, 0x7fff2, 19)
+HPACK(209, 0x1fffe3, 21)
+HPACK(210, 0x3ffffe6, 26)
+HPACK(211, 0x7ffffe0, 27)
+HPACK(212, 0x7ffffe1, 27)
+HPACK(213, 0x3ffffe7, 26)
+HPACK(214, 0x7ffffe2, 27)
+HPACK(215, 0xfffff2, 24)
+HPACK(216, 0x1fffe4, 21)
+HPACK(217, 0x1fffe5, 21)
+HPACK(218, 0x3ffffe8, 26)
+HPACK(219, 0x3ffffe9, 26)
+HPACK(220, 0xffffffd, 28)
+HPACK(221, 0x7ffffe3, 27)
+HPACK(222, 0x7ffffe4, 27)
+HPACK(223, 0x7ffffe5, 27)
+HPACK(224, 0xfffec, 20)
+HPACK(225, 0xfffff3, 24)
+HPACK(226, 0xfffed, 20)
+HPACK(227, 0x1fffe6, 21)
+HPACK(228, 0x3fffe9, 22)
+HPACK(229, 0x1fffe7, 21)
+HPACK(230, 0x1fffe8, 21)
+HPACK(231, 0x7ffff3, 23)
+HPACK(232, 0x3fffea, 22)
+HPACK(233, 0x3fffeb, 22)
+HPACK(234, 0x1ffffee, 25)
+HPACK(235, 0x1ffffef, 25)
+HPACK(236, 0xfffff4, 24)
+HPACK(237, 0xfffff5, 24)
+HPACK(238, 0x3ffffea, 26)
+HPACK(239, 0x7ffff4, 23)
+HPACK(240, 0x3ffffeb, 26)
+HPACK(241, 0x7ffffe6, 27)
+HPACK(242, 0x3ffffec, 26)
+HPACK(243, 0x3ffffed, 26)
+HPACK(244, 0x7ffffe7, 27)
+HPACK(245, 0x7ffffe8, 27)
+HPACK(246, 0x7ffffe9, 27)
+HPACK(247, 0x7ffffea, 27)
+HPACK(248, 0x7ffffeb, 27)
+HPACK(249, 0xffffffe, 28)
+HPACK(250, 0x7ffffec, 27)
+HPACK(251, 0x7ffffed, 27)
+HPACK(252, 0x7ffffee, 27)
+HPACK(253, 0x7ffffef, 27)
+HPACK(254, 0x7fffff0, 27)
+HPACK(255, 0x3ffffee, 26)
+HPACK(0, 0x3fffffff, 30)
diff --git a/bin/varnishtest/vtc_h2_hpack.c b/bin/varnishtest/vtc_h2_hpack.c
new file mode 100644
index 0000000..63034fd
--- /dev/null
+++ b/bin/varnishtest/vtc_h2_hpack.c
@@ -0,0 +1,430 @@
+/*-
+ * Copyright (c) 2008-2016 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Guillaume Quintard <guillaume.quintard 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 <stdlib.h>
+#include <string.h>
+
+#include <vas.h>
+
+#include "hpack.h"
+#include "vtc_h2_priv.h"
+
+struct symbol {
+ uint32_t val;
+ uint8_t size;
+};
+
+static struct symbol coding_table[] = {
+#define HPACK(i, v, l) {v, l},
+#include "vtc_h2_enctbl.h"
+#undef HPACK
+ {0, 0}
+};
+
+#include "vtc_h2_dectbl.h"
+
+struct symbol *EOS = &coding_table[256];
+
+#define MASK(pack, n) (pack >> (64 - n))
+static int
+huff_decode(char *str, int nm, struct hpk_iter *iter, int ilen)
+{
+ int l = 0;
+ uint64_t pack = 0;
+ int pl = 0; /* pack length*/
+ struct stbl *tbl = &byte0;
+ struct ssym *sym;
+
+ (void)nm;
+ while (ilen > 0 || pl != 0) {
+ assert(pl >= 0);
+ /* make sure we have enough data*/
+ if (pl < tbl->msk) {
+ if (ilen == 0) {
+ if (pl == 0 || (MASK(pack, pl) ==
+ (unsigned)((1 << pl) - 1))) {
+ assert(tbl == &byte0);
+ return (l);
+ }
+ }
+ /* fit as many bytes as we can in pack */
+ while (pl <= 56 && ilen > 0) {
+ pack |= (uint64_t)(*iter->buf & 0xff)
+ << (56 - pl);
+ pl += 8;
+ iter->buf++;
+ ilen--;
+ }
+ }
+ assert(tbl);
+ assert(tbl->msk);
+ sym = &tbl->syms[MASK(pack, tbl->msk)];
+
+ assert(sym->csm <= tbl->msk);
+
+ if (sym->csm == 0 || pl < sym->csm)
+ return (0);
+
+ pack <<= sym->csm;
+ pl -= sym->csm;
+ if (sym->nxt) {
+ tbl = sym->nxt;
+ continue;
+ }
+ str[l++] = sym->chr;
+ tbl = &byte0;
+ }
+ return (l);
+}
+
+/* inspired from Dridi Boukelmoune's cashpack. */
+static int
+huff_encode(struct hpk_iter *iter, const char *str, int len)
+{
+ uint64_t pack = 0;
+ int pl = 0; /* pack length*/
+ uint32_t v;
+ uint8_t s;
+
+ assert(iter->buf < iter->end);
+
+ while (len--) {
+ v = coding_table[(uint8_t)*str].val;
+ s = coding_table[(uint8_t)*str].size;
+
+ pl += s;
+ pack |= (uint64_t)v << (64 - pl);
+
+ while (pl >= 8) {
+ if (iter->buf == iter->end)
+ return (1);
+ *iter->buf = (char)(pack >> 56);
+ iter->buf++;
+ pack <<= 8;
+ pl -= 8;
+ }
+ str++;
+ }
+
+ /* padding */
+ if (pl) {
+ assert(pl < 8);
+ if (iter->buf == iter->end)
+ return (1);
+ pl += 8;
+ pack |= (uint64_t)0xff << (64 - pl);
+ *iter->buf = (char)(pack >> 56);
+ iter->buf++;
+ }
+
+ return (0);
+}
+
+static int
+huff_simulate(const char *str, int ilen, int huff)
+{
+ int olen = 0;
+ if (!huff || !ilen)
+ return (ilen);
+ while (ilen--) {
+ olen += coding_table[(unsigned char)*str].size;
+ str++;
+ }
+ return ((olen+7)/8);
+}
+
+static enum hpk_result
+num_decode(uint32_t *result, struct hpk_iter *iter, uint8_t prefix)
+{
+ uint8_t shift = 0;
+
+ assert(iter->buf < iter->end);
+ assert(prefix);
+ assert(prefix <= 8);
+
+ *result = 0;
+ *result = *iter->buf & (0xff >> (8-prefix));
+ if (*result < (1 << prefix) - 1) {
+ iter->buf++;
+ return (ITER_DONE(iter));
+ }
+ do {
+ iter->buf++;
+ if (iter->end == iter->buf)
+ return (hpk_err);
+ /* check for overflow */
+ if ((UINT32_MAX - *result) >> shift < (*iter->buf & 0x7f))
+ return (hpk_err);
+
+ *result += (uint32_t)(*iter->buf & 0x7f) << shift;
+ shift += 7;
+ } while (*iter->buf & 0x80);
+ iter->buf++;
+
+ return (ITER_DONE(iter));
+}
+
+static enum hpk_result
+num_encode(struct hpk_iter *iter, uint8_t prefix, uint32_t num)
+{
+ assert(prefix);
+ assert(prefix <= 8);
+ assert(iter->buf < iter->end);
+
+ uint8_t pmax = (1 << prefix) - 1;
+
+ *iter->buf &= 0xff << prefix;
+ if (num <= pmax) {
+ *iter->buf++ |= num;
+ return (ITER_DONE(iter));
+ } else if (iter->end - iter->buf < 2)
+ return (hpk_err);
+
+ iter->buf[0] |= pmax;
+ num -= pmax;
+ do {
+ iter->buf++;
+ if (iter->end == iter->buf)
+ return (hpk_err);
+ *iter->buf = num % 128;
+ *iter->buf |= 0x80;
+ num /= 128;
+ } while (num);
+ *iter->buf++ &= 127;
+ return (ITER_DONE(iter));
+}
+
+static enum hpk_result
+str_encode(struct hpk_iter *iter, const struct txt *t)
+{
+ int slen = huff_simulate(t->ptr, t->len, t->huff);
+ assert(iter->buf < iter->end);
+ if (t->huff)
+ *iter->buf = 0x80;
+ else
+ *iter->buf = 0;
+
+ if (hpk_err == num_encode(iter, 7, slen))
+ return (hpk_err);
+
+ if (slen > iter->end - iter->buf)
+ return (hpk_err);
+
+ if (t->huff) {
+ return (huff_encode(iter, t->ptr, t->len));
+ } else {
+ memcpy(iter->buf, t->ptr, slen);
+ iter->buf += slen;
+ return (ITER_DONE(iter));
+ }
+}
+
+static enum hpk_result
+str_decode(struct hpk_iter *iter, struct txt *t)
+{
+ uint32_t num;
+ int huff;
+ assert(iter->buf < iter->end);
+ huff = (*iter->buf & 0x80);
+ if (hpk_more != num_decode(&num, iter, 7))
+ return (hpk_err);
+ if (num > iter->end - iter->buf)
+ return (hpk_err);
+ if (huff) { /*Huffman encoding */
+ t->ptr = malloc((num * 8) / 5L + 1L);
+ AN(t->ptr);
+ num = huff_decode(t->ptr, (num * 8) / 5, iter, num);
+ if (!num) {
+ free(t->ptr);
+ return (hpk_err);
+ }
+ t->huff = 1;
+ /* XXX: do we care? */
+ t->ptr = realloc(t->ptr, num + 1L);
+ AN(t->ptr);
+ memcpy(t->ptr, t->ptr, num);
+ } else { /* literal string */
+ t->huff = 0;
+ t->ptr = malloc(num + 1L);
+ AN(t->ptr);
+ memcpy(t->ptr, iter->buf, num);
+ iter->buf += num;
+ }
+
+ t->ptr[num] = '\0';
+ t->len = num;
+
+ return (ITER_DONE(iter));
+}
+
+static inline void
+txtcpy(struct txt *to, const struct txt *from)
+{
+ //AZ(to->ptr);
+ to->ptr = malloc(from->len + 1L);
+ AN(to->ptr);
+ memcpy(to->ptr, from->ptr, from->len + 1L);
+ to->len = from->len;
+}
+
+int
+gethpk_iterLen(const struct hpk_iter *iter)
+{
+ return (iter->buf - iter->orig);
+}
+
+enum hpk_result
+HPK_DecHdr(struct hpk_iter *iter, struct hpk_hdr *header)
+{
+ int pref = 0;
+ const struct txt *t;
+ uint32_t num;
+ int must_index = 0;
+ assert(iter);
+ assert(iter->buf < iter->end);
+ /* Indexed Header Field */
+ if (*iter->buf & 128) {
+ header->t = hpk_idx;
+ if (hpk_err == num_decode(&num, iter, 7))
+ return (hpk_err);
+
+ if (num) { /* indexed key and value*/
+ t = tbl_get_key(iter->ctx, num);
+ if (!t)
+ return (hpk_err);
+ txtcpy(&header->key, t);
+
+ t = tbl_get_value(iter->ctx, num);
+ if (!t) {
+ free(header->key.ptr);
+ return (hpk_err);
+ }
+
+ txtcpy(&header->value, t);
+
+ if (iter->buf < iter->end)
+ return (hpk_more);
+ else
+ return (hpk_done);
+ } else
+ return (hpk_err);
+
+ }
+ /* Literal Header Field with Incremental Indexing */
+ else if (*iter->buf >> 6 == 1) {
+ header->t = hpk_inc;
+ pref = 6;
+ must_index = 1;
+ }
+ /* Literal Header Field without Indexing */
+ else if (*iter->buf >> 4 == 0) {
+ header->t = hpk_not;
+ pref = 4;
+ }
+ /* Literal Header Field never Indexed */
+ else if (*iter->buf >> 4 == 1) {
+ header->t = hpk_never;
+ pref = 4;
+ }
+ /* Dynamic Table Size Update */
+ /* XXX if under max allowed value */
+ else if (*iter->buf >> 5 == 1) {
+ if (hpk_done != num_decode(&num, iter, 5))
+ return (hpk_err);
+ return HPK_ResizeTbl(iter->ctx, num);
+ } else {
+ return (hpk_err);
+ }
+
+ assert(pref);
+ if (hpk_more != num_decode(&num, iter, pref))
+ return (hpk_err);
+
+ header->i = num;
+ if (num) { /* indexed key */
+ t = tbl_get_key(iter->ctx, num);
+ if (!t)
+ return (hpk_err);
+ txtcpy(&header->key, t);
+ } else {
+ if (hpk_more != str_decode(iter, &header->key))
+ return (hpk_err);
+ }
+
+ if (hpk_err == str_decode(iter, &header->value))
+ return (hpk_err);
+
+ if (must_index)
+ push_header(iter->ctx, header);
+ return (ITER_DONE(iter));
+}
+
+enum hpk_result
+HPK_EncHdr(struct hpk_iter *iter, const struct hpk_hdr *h)
+{
+ int pref;
+ int must_index = 0;
+ enum hpk_result ret;
+ switch (h->t) {
+ case hpk_idx:
+ *iter->buf = 0x80;
+ num_encode(iter, 7, h->i);
+ return (ITER_DONE(iter));
+ case hpk_inc:
+ *iter->buf = 0x40;
+ pref = 6;
+ must_index = 1;
+ break;
+ case hpk_not:
+ *iter->buf = 0x00;
+ pref = 4;
+ break;
+ case hpk_never:
+ *iter->buf = 0x10;
+ pref = 4;
+ break;
+ default:
+ INCOMPL();
+ }
+ if (h->i) {
+ if (hpk_more != num_encode(iter, pref, h->i))
+ return (hpk_err);
+ } else {
+ iter->buf++;
+ if (hpk_more != str_encode(iter, &h->key))
+ return (hpk_err);
+ }
+ ret = str_encode(iter, &h->value);
+ if (ret == hpk_err)
+ return (hpk_err);
+ if (must_index)
+ push_header(iter->ctx, h);
+ return (ret);
+
+}
diff --git a/bin/varnishtest/vtc_h2_priv.h b/bin/varnishtest/vtc_h2_priv.h
new file mode 100644
index 0000000..706ba0c
--- /dev/null
+++ b/bin/varnishtest/vtc_h2_priv.h
@@ -0,0 +1,22 @@
+#include "vqueue.h"
+
+#define ITER_DONE(iter) (iter->buf == iter->end ? hpk_done : hpk_more)
+
+struct dynhdr {
+ struct hpk_hdr header;
+ VTAILQ_ENTRY(dynhdr) list;
+};
+
+VTAILQ_HEAD(dynamic_table,dynhdr);
+
+struct hpk_iter {
+ struct hpk_ctx *ctx;
+ char *orig;
+ char *buf;
+ char *end;
+};
+
+const struct txt * tbl_get_key(const struct hpk_ctx *ctx, uint32_t index);
+
+const struct txt * tbl_get_value(const struct hpk_ctx *ctx, uint32_t index);
+void push_header (struct hpk_ctx *ctx, const struct hpk_hdr *h);
diff --git a/bin/varnishtest/vtc_h2_stattbl.h b/bin/varnishtest/vtc_h2_stattbl.h
new file mode 100644
index 0000000..1e54862
--- /dev/null
+++ b/bin/varnishtest/vtc_h2_stattbl.h
@@ -0,0 +1,61 @@
+STAT_HDRS(1, ":authority", "")
+STAT_HDRS(2, ":method", "GET")
+STAT_HDRS(3, ":method", "POST")
+STAT_HDRS(4, ":path", "/")
+STAT_HDRS(5, ":path", "/index.html")
+STAT_HDRS(6, ":scheme", "http")
+STAT_HDRS(7, ":scheme", "https")
+STAT_HDRS(8, ":status", "200")
+STAT_HDRS(9, ":status", "204")
+STAT_HDRS(10, ":status", "206")
+STAT_HDRS(11, ":status", "304")
+STAT_HDRS(12, ":status", "400")
+STAT_HDRS(13, ":status", "404")
+STAT_HDRS(14, ":status", "500")
+STAT_HDRS(15, "accept-charset", "")
+STAT_HDRS(16, "accept-encoding", "gzip,deflate")
+STAT_HDRS(17, "accept-language", "")
+STAT_HDRS(18, "accept-ranges", "")
+STAT_HDRS(19, "accept", "")
+STAT_HDRS(20, "access-control-allow-origin", "")
+STAT_HDRS(21, "age", "")
+STAT_HDRS(22, "allow", "")
+STAT_HDRS(23, "authorization", "")
+STAT_HDRS(24, "cache-control", "")
+STAT_HDRS(25, "content-disposition", "")
+STAT_HDRS(26, "content-encoding", "")
+STAT_HDRS(27, "content-language", "")
+STAT_HDRS(28, "content-length", "")
+STAT_HDRS(29, "content-location", "")
+STAT_HDRS(30, "content-range", "")
+STAT_HDRS(31, "content-type", "")
+STAT_HDRS(32, "cookie", "")
+STAT_HDRS(33, "date", "")
+STAT_HDRS(34, "etag", "")
+STAT_HDRS(35, "expect", "")
+STAT_HDRS(36, "expires", "")
+STAT_HDRS(37, "from", "")
+STAT_HDRS(38, "host", "")
+STAT_HDRS(39, "if-match", "")
+STAT_HDRS(40, "if-modified-since", "")
+STAT_HDRS(41, "if-none-match", "")
+STAT_HDRS(42, "if-range", "")
+STAT_HDRS(43, "if-unmodified-since", "")
+STAT_HDRS(44, "last-modified", "")
+STAT_HDRS(45, "link", "")
+STAT_HDRS(46, "location", "")
+STAT_HDRS(47, "max-forwards", "")
+STAT_HDRS(48, "proxy-authenticate", "")
+STAT_HDRS(49, "proxy-authorization", "")
+STAT_HDRS(50, "range", "")
+STAT_HDRS(51, "referer", "")
+STAT_HDRS(52, "refresh", "")
+STAT_HDRS(53, "retry-after", "")
+STAT_HDRS(54, "server", "")
+STAT_HDRS(55, "set-cookie", "")
+STAT_HDRS(56, "strict-transport-security", "")
+STAT_HDRS(57, "transfer-encoding", "")
+STAT_HDRS(58, "user-agent", "")
+STAT_HDRS(59, "vary", "")
+STAT_HDRS(60, "via", "")
+STAT_HDRS(61, "www-authenticate", "")
diff --git a/bin/varnishtest/vtc_h2_tbl.c b/bin/varnishtest/vtc_h2_tbl.c
new file mode 100644
index 0000000..0a50d1c
--- /dev/null
+++ b/bin/varnishtest/vtc_h2_tbl.c
@@ -0,0 +1,291 @@
+/*-
+ * Copyright (c) 2008-2016 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Guillaume Quintard <guillaume.quintard 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 <string.h>
+#include <stdio.h>
+#include <stdint.h>
+
+#include <vas.h>
+
+#include "hpack.h"
+#include "vtc_h2_priv.h"
+
+/* TODO: fix that crazy workaround */
+#define STAT_HDRS(i, k, v) \
+ static char key_ ## i[] = k; \
+ static char value_ ## i[] = v;
+#include "vtc_h2_stattbl.h"
+#undef STAT_HDRS
+
+/*lint -save -e778 */
+struct hpk_hdr sttbl[] = {
+ {{NULL, 0, 0}, {NULL, 0, 0}, hpk_idx, 0},
+#define STAT_HDRS(j, k, v) \
+{ \
+ .key = { \
+ .ptr = key_ ## j, \
+ .len = sizeof(k) - 1, \
+ .huff = 0 \
+ }, \
+ .value = { \
+ .ptr = value_ ## j, \
+ .len = sizeof(v) - 1, \
+ .huff = 0 \
+ }, \
+ .t = hpk_idx, \
+ .i = j, \
+},
+#include "vtc_h2_stattbl.h"
+#undef STAT_HDRS
+};
+/*lint -restore */
+
+struct hpk_ctx {
+ const struct hpk_hdr *sttbl;
+ struct dynamic_table dyntbl;
+ uint32_t maxsize;
+ uint32_t size;
+};
+
+
+struct hpk_iter *
+HPK_NewIter(struct hpk_ctx *ctx, char *buf, int size)
+{
+ struct hpk_iter *iter = malloc(sizeof(*iter));
+ assert(iter);
+ assert(ctx);
+ assert(buf);
+ assert(size);
+ iter->ctx = ctx;
+ iter->orig = buf;
+ iter->buf = buf;
+ iter->end = buf + size;
+ return (iter);
+}
+
+void
+HPK_FreeIter(struct hpk_iter *iter)
+{
+ free(iter);
+}
+
+static void
+pop_header(struct hpk_ctx *ctx)
+{
+ assert(!VTAILQ_EMPTY(&ctx->dyntbl));
+ struct dynhdr *h = VTAILQ_LAST(&ctx->dyntbl, dynamic_table);
+ VTAILQ_REMOVE(&ctx->dyntbl, h, list);
+ ctx->size -= h->header.key.len + h->header.value.len + 32;
+ free(h->header.key.ptr);
+ free(h->header.value.ptr);
+ free(h);
+}
+
+void
+push_header (struct hpk_ctx *ctx, const struct hpk_hdr *oh)
+{
+ const struct hpk_hdr *ih;
+ struct dynhdr *h;
+ uint32_t len;
+
+ assert(ctx->size <= ctx->maxsize);
+ AN(oh);
+
+ if (!ctx->maxsize)
+ return;
+ len = oh->value.len + 32;
+ if (oh->key.ptr)
+ len += oh->key.len;
+ else {
+ AN(oh->i);
+ ih = HPK_GetHdr(ctx, oh->i);
+ AN(ih);
+ len += ih->key.len;
+ }
+
+ while (!VTAILQ_EMPTY(&ctx->dyntbl) && ctx->maxsize - ctx->size < len)
+ pop_header(ctx);
+ if (ctx->maxsize - ctx->size >= len) {
+ h = malloc(sizeof(*h));
+ AN(h);
+ h->header.t = hpk_idx;
+
+ if (oh->key.ptr) {
+ h->header.key.len = oh->key.len;
+ h->header.key.ptr = malloc(oh->key.len + 1L);
+ AN(h->header.key.ptr);
+ memcpy(h->header.key.ptr,
+ oh->key.ptr, oh->key.len + 1L);
+ } else {
+ AN(oh->i);
+ ih = HPK_GetHdr(ctx, oh->i);
+ AN(ih);
+
+ h->header.key.len = ih->key.len;
+ h->header.key.ptr = malloc(ih->key.len + 1L);
+ AN(h->header.key.ptr);
+ memcpy(h->header.key.ptr,
+ ih->key.ptr, ih->key.len + 1L);
+ }
+
+ h->header.value.len = oh->value.len;
+ h->header.value.ptr = malloc(oh->value.len + 1L);
+ AN(h->header.value.ptr);
+ memcpy(h->header.value.ptr, oh->value.ptr, oh->value.len + 1L);
+
+ VTAILQ_INSERT_HEAD(&ctx->dyntbl, h, list);
+ ctx->size += len;
+ }
+
+}
+
+enum hpk_result
+HPK_ResizeTbl(struct hpk_ctx *ctx, uint32_t num)
+{
+ ctx->maxsize = num;
+ while (!VTAILQ_EMPTY(&ctx->dyntbl) && ctx->maxsize < ctx->size)
+ pop_header(ctx);
+ return (hpk_done);
+}
+
+static const struct txt *
+tbl_get_field(const struct hpk_ctx *ctx, uint32_t idx, int key)
+{
+ struct dynhdr *dh;
+ assert(ctx);
+ if (idx > 61 + ctx->size)
+ return (NULL);
+ else if (idx <= 61) {
+ if (key)
+ return (&ctx->sttbl[idx].key);
+ else
+ return (&ctx->sttbl[idx].value);
+ }
+
+ idx -= 62;
+ VTAILQ_FOREACH(dh, &ctx->dyntbl, list)
+ if (!idx--)
+ break;
+ if (idx && dh) {
+ if (key)
+ return (&dh->header.key);
+ else
+ return (&dh->header.value);
+ } else
+ return (NULL);
+}
+
+const struct txt *
+tbl_get_key(const struct hpk_ctx *ctx, uint32_t idx)
+{
+ return (tbl_get_field(ctx, idx, 1));
+}
+
+const struct txt *
+tbl_get_value(const struct hpk_ctx *ctx, uint32_t idx)
+{
+ return (tbl_get_field(ctx, idx, 0));
+}
+
+const struct hpk_hdr *
+HPK_GetHdr(const struct hpk_ctx *ctx, uint32_t idx)
+{
+ uint32_t oi = idx;
+ struct dynhdr *dh;
+ assert(ctx);
+ if (idx > 61 + ctx->size)
+ return (NULL);
+ else if (idx <= 61)
+ return (&ctx->sttbl[idx]);
+
+ idx -= 62;
+ VTAILQ_FOREACH(dh, &ctx->dyntbl, list)
+ if (!idx--)
+ break;
+ if (idx && dh) {
+ dh->header.i = oi;
+ return (&dh->header);
+ } else
+ return (NULL);
+}
+
+uint32_t
+HPK_GetTblSize(const struct hpk_ctx *ctx)
+{
+ return (ctx->size);
+}
+
+uint32_t
+HPK_GetTblMaxSize(const struct hpk_ctx *ctx)
+{
+ return (ctx->maxsize);
+}
+
+uint32_t
+HPK_GetTblLength(const struct hpk_ctx *ctx)
+{
+ struct dynhdr *dh;
+ uint32_t l = 0;
+ VTAILQ_FOREACH(dh, &ctx->dyntbl, list)
+ l++;
+ return (l);
+}
+
+void
+dump_dyn_tbl(const struct hpk_ctx *ctx)
+{
+ int i = 0;
+ struct dynhdr *dh;
+ printf("DUMPING %u/%u\n", ctx->size, ctx->maxsize);
+ VTAILQ_FOREACH(dh, &ctx->dyntbl, list) {
+ printf(" (%d) %s: %s\n",
+ i++, dh->header.key.ptr, dh->header.value.ptr);
+ }
+ printf("DONE\n");
+}
+
+struct hpk_ctx *
+HPK_NewCtx(uint32_t maxsize)
+{
+ struct hpk_ctx *ctx = calloc(1, sizeof(*ctx));
+ assert(ctx);
+ ctx->sttbl = sttbl;
+ ctx->maxsize = maxsize;
+ ctx->size = 0;
+ return (ctx);
+}
+
+void
+HPK_FreeCtx(struct hpk_ctx *ctx)
+{
+
+ while(!VTAILQ_EMPTY(&ctx->dyntbl))
+ pop_header(ctx);
+ free(ctx);
+}
diff --git a/bin/varnishtest/vtc_http.c b/bin/varnishtest/vtc_http.c
index f7e993f..3c85f50 100644
--- a/bin/varnishtest/vtc_http.c
+++ b/bin/varnishtest/vtc_http.c
@@ -40,14 +40,14 @@
#include <string.h>
#include "vtc.h"
+#include "vtc_http.h"
#include "vct.h"
#include "vgz.h"
#include "vnum.h"
#include "vre.h"
#include "vtcp.h"
-
-#define MAX_HDR 50
+#include "hpack.h"
/* SECTION: client-server client/server
*
@@ -120,39 +120,26 @@
* is of the form "CLIENTIP:PORT SERVERIP:PORT".
*
* SECTION: client-server.spec Specification
+ *
+ * It's a string, either double-quoted "like this", but most of the time
+ * enclosed in curly brackets, allowing multilining. Write a command per line in
+ * it, empty line are ignored, and long line can be wrapped by using a
+ * backslash. For example::
+ *
+ * client c1 {
+ * txreq -url /foo \
+ * -hdr "bar: baz"
+ *
+ * rxresp
+ * } -run
*/
-struct http {
- unsigned magic;
-#define HTTP_MAGIC 0x2f02169c
- int fd;
- int *sfd;
- int timeout;
- struct vtclog *vl;
-
- struct vsb *vsb;
-
- int nrxbuf;
- char *rxbuf;
- char *rem_ip;
- char *rem_port;
- int prxbuf;
- char *body;
- unsigned bodyl;
- char bodylen[20];
- char chunklen[20];
-
- char *req[MAX_HDR];
- char *resp[MAX_HDR];
-
- int gziplevel;
- int gzipresidual;
-
- int fatal;
-};
-
#define ONLY_CLIENT(hp, av) \
do { \
+ if (hp->h2) \
+ vtc_log(hp->vl, 0, \
+ "\"%s\" only possible before H/2 upgrade", \
+ av[0]); \
if (hp->sfd != NULL) \
vtc_log(hp->vl, 0, \
"\"%s\" only possible in client", av[0]); \
@@ -160,6 +147,10 @@ struct http {
#define ONLY_SERVER(hp, av) \
do { \
+ if (hp->h2) \
+ vtc_log(hp->vl, 0, \
+ "\"%s\" only possible before H/2 upgrade", \
+ av[0]); \
if (hp->sfd == NULL) \
vtc_log(hp->vl, 0, \
"\"%s\" only possible in server", av[0]); \
@@ -173,7 +164,7 @@ static const char * const nl = "\r\n";
* Generate a synthetic body
*/
-static char *
+char *
synth_body(const char *len, int rnd)
{
int i, j, k, l;
@@ -336,6 +327,11 @@ cmd_var_resolve(struct http *hp, char *spec)
} else if (!strncmp(spec, "resp.http.", 10)) {
hh = hp->resp;
hdr = spec + 10;
+ } else if (!strcmp(spec, "h2.state")) {
+ if (hp->h2)
+ return ("true");
+ else
+ return ("false");
} else
return (spec);
hdr = http_find_header(hh, hdr);
@@ -544,8 +540,8 @@ http_rxchar(struct http *hp, int n, int eof)
return (i);
if (i == 0)
vtc_log(hp->vl, hp->fatal,
- "HTTP rx EOF (fd:%d read: %s)",
- hp->fd, strerror(errno));
+ "HTTP rx EOF (fd:%d read: %s) %d",
+ hp->fd, strerror(errno), n);
if (i < 0)
vtc_log(hp->vl, hp->fatal,
"HTTP rx failed (fd:%d read: %s)",
@@ -729,7 +725,7 @@ cmd_http_rxresphdrs(CMD_ARGS)
av++;
for(; *av != NULL; av++)
- vtc_log(hp->vl, 0, "Unknown http rxreq spec: %s\n", *av);
+ vtc_log(hp->vl, 0, "Unknown http rxresp spec: %s\n", *av);
http_rxhdr(hp);
http_splitheader(hp, 0);
if (http_count_header(hp->resp, "Content-Length") > 1)
@@ -1035,33 +1031,75 @@ cmd_http_txresp(CMD_ARGS)
http_write(hp, 4, "txresp");
}
+static void
+cmd_http_upgrade(CMD_ARGS)
+{
+ char *h;
+ struct http *hp;
+
+ CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
+ ONLY_SERVER(hp, av);
+ AN(hp->sfd);
+
+ h = http_find_header(hp->req, "Upgrade");
+ if (!h || strcmp(h, "h2c"))
+ vtc_log(vl, 0, "Req misses \"Upgrade: h2c\" header");
+
+ h = http_find_header(hp->req, "Connection");
+ if (!h || strcmp(h, "Upgrade, HTTP2-Settings"))
+ vtc_log(vl, 0, "Req misses \"Connection: "
+ "Upgrade, HTTP2-Settings\" header");
+
+ h = http_find_header(hp->req, "HTTP2-Settings");
+ if (!h)
+ vtc_log(vl, 0, "Req misses \"HTTP2-Settings\" header");
+
+
+ parse_string("txresp -status 101 "
+ "-hdr \"Connection: Upgrade\" "
+ "-hdr \"Upgrade: h2c\"\n", cmd, hp, vl);
+
+ b64_settings(hp, h);
+
+ parse_string("rxpri\n"
+ "stream 0 {\n"
+ "txsettings\n"
+ "rxsettings\n"
+ "txsettings -ack\n"
+ "rxsettings\n"
+ "expect settings.ack == true\n"
+ "} -start\n", cmd, hp, vl);
+}
+
+/**********************************************************************
+ * Receive a request
+ */
+
/* SECTION: client-server.spec.rxreq
*
* rxreq (server only)
* Receive and parse a request's headers and body.
*/
-
static void
cmd_http_rxreq(CMD_ARGS)
{
struct http *hp;
(void)cmd;
- (void)vl;
CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
ONLY_SERVER(hp, av);
AZ(strcmp(av[0], "rxreq"));
av++;
for(; *av != NULL; av++)
- vtc_log(hp->vl, 0, "Unknown http rxreq spec: %s\n", *av);
+ vtc_log(vl, 0, "Unknown http rxreq spec: %s\n", *av);
http_rxhdr(hp);
http_splitheader(hp, 1);
if (http_count_header(hp->req, "Content-Length") > 1)
- vtc_log(hp->vl, 0,
+ vtc_log(vl, 0,
"Multiple Content-Length headers.\n");
http_swallow_body(hp, hp->req, 0);
- vtc_log(hp->vl, 4, "bodylen = %s", hp->bodylen);
+ vtc_log(vl, 4, "bodylen = %s", hp->bodylen);
}
/* SECTION: client-server.spec.rxreqhdrs
@@ -1133,7 +1171,7 @@ cmd_http_rxrespbody(CMD_ARGS)
av++;
for(; *av != NULL; av++)
- vtc_log(hp->vl, 0, "Unknown http rxreq spec: %s\n", *av);
+ vtc_log(hp->vl, 0, "Unknown http rxrespbody spec: %s\n", *av);
http_swallow_body(hp, hp->resp, 0);
vtc_log(hp->vl, 4, "bodylen = %s", hp->bodylen);
}
@@ -1175,6 +1213,7 @@ cmd_http_txreq(CMD_ARGS)
const char *req = "GET";
const char *url = "/";
const char *proto = "HTTP/1.1";
+ const char *up = NULL;
(void)cmd;
(void)vl;
@@ -1195,15 +1234,39 @@ cmd_http_txreq(CMD_ARGS)
} else if (!strcmp(*av, "-req")) {
req = av[1];
av++;
+ } else if (!hp->sfd && !strcmp(*av, "-up")) {
+ up = av[1];
+ av++;
} else
break;
}
VSB_printf(hp->vsb, "%s %s %s%s", req, url, proto, nl);
+ if (up)
+ VSB_printf(hp->vsb, "Connection: Upgrade, HTTP2-Settings%s"
+ "Upgrade: h2c%s"
+ "HTTP2-Settings: %s%s", nl, nl, up, nl);
+
av = http_tx_parse_args(av, vl, hp, NULL);
if (*av != NULL)
vtc_log(hp->vl, 0, "Unknown http txreq spec: %s\n", *av);
http_write(hp, 4, "txreq");
+
+ if (up) {
+ parse_string("rxresp\n"
+ "expect resp.status == 101\n"
+ "expect resp.http.connection == Upgrade\n"
+ "expect resp.http.upgrade == h2c\n"
+ "txpri\n", http_cmds, hp, vl);
+ b64_settings(hp, up);
+ parse_string("stream 0 {\n"
+ "txsettings\n"
+ "rxsettings\n"
+ "txsettings -ack\n"
+ "rxsettings\n"
+ "expect settings.ack == true"
+ "} -start\n", http_cmds, hp, vl);
+ }
}
/* SECTION: client-server.spec.recv
@@ -1453,6 +1516,11 @@ cmd_http_timeout(CMD_ARGS)
* Wait for the connected client to close the connection.
*/
+/* SECTION: client-server.spec.expect_close expect_close (server)
+ *
+ * Reads from the connection, expecting nothing to read but an EOF.
+ *
+ */
static void
cmd_http_expect_close(CMD_ARGS)
{
@@ -1467,6 +1535,8 @@ cmd_http_expect_close(CMD_ARGS)
AZ(av[1]);
vtc_log(vl, 4, "Expecting close (fd = %d)", hp->fd);
+ if (hp->h2)
+ stop_h2(hp);
while (1) {
fds[0].fd = hp->fd;
fds[0].events = POLLIN | POLLERR;
@@ -1497,6 +1567,12 @@ cmd_http_expect_close(CMD_ARGS)
* Close the active TCP connection
*/
+/* SECTION: client-server.spec.close close (server)
+ *
+ * Close the connection. Not that if operating in H/2 mode, no extra (GOAWAY)
+ * frame is sent, it's simply a TCP close.
+ *
+ */
static void
cmd_http_close(CMD_ARGS)
{
@@ -1508,6 +1584,8 @@ cmd_http_close(CMD_ARGS)
AZ(av[1]);
assert(hp->sfd != NULL);
assert(*hp->sfd >= 0);
+ if (hp->h2)
+ stop_h2(hp);
VTCP_close(&hp->fd);
vtc_log(vl, 4, "Closed");
}
@@ -1518,6 +1596,11 @@ cmd_http_close(CMD_ARGS)
* Close the active connection (if any) and accept a new one.
*/
+/* SECTION: client-server.spec.accept accept (server)
+ *
+ * Close the potential current connection, and accept a new one. Note that this
+ * new connection is H/1.
+ */
static void
cmd_http_accept(CMD_ARGS)
{
@@ -1529,6 +1612,8 @@ cmd_http_accept(CMD_ARGS)
AZ(av[1]);
assert(hp->sfd != NULL);
assert(*hp->sfd >= 0);
+ if (hp->h2)
+ stop_h2(hp);
if (hp->fd >= 0)
VTCP_close(&hp->fd);
vtc_log(vl, 4, "Accepting");
@@ -1594,11 +1679,113 @@ cmd_http_fatal(CMD_ARGS)
* Same as for the top-level barrier
*/
+char PREFACE[] = "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n";
+
+/* SECTION: client-server.spec.txpri txpri (client)
+ *
+ * Send an H/2 preface ("PRI * HTTP/2.0\\r\\n\\r\\nSM\\r\\n\\r\\n") and set
+ * client to H/2.
+ */
+static void
+cmd_http_txpri(CMD_ARGS)
+{
+ size_t l;
+ struct http *hp;
+ (void)cmd;
+ CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
+ ONLY_CLIENT(hp, av);
+
+ vtc_dump(hp->vl, 4, "txpri", PREFACE, sizeof(PREFACE) - 1);
+ l = write(hp->fd, PREFACE, sizeof(PREFACE) - 1);
+ if (l != sizeof(PREFACE) - 1)
+ vtc_log(vl, hp->fatal, "Write failed: (%zd vs %zd) %s",
+ l, sizeof(PREFACE) - 1, strerror(errno));
+ start_h2(hp);
+ AN(hp->h2);
+}
+
+/* SECTION: client-server.spec.rxpri rxpri (server)
+ *
+ * Receive a preface, and if it matches, sets the server to H/2, aborts
+ * otherwise.
+ */
+static void
+cmd_http_rxpri(CMD_ARGS)
+{
+ struct http *hp;
+ (void)cmd;
+ CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
+ ONLY_SERVER(hp, av);
+
+ hp->prxbuf = 0;
+ if (!http_rxchar(hp, sizeof(PREFACE) - 1, 0))
+ vtc_log(vl, 0, "Couldn't retrieve connection preface");
+ if (strncmp(hp->rxbuf, PREFACE, sizeof(PREFACE) - 1))
+ vtc_log(vl, 0, "Received invalid preface\n");
+ start_h2(hp);
+ AN(hp->h2);
+}
+
+/* SECTION: client-server.spec.settings
+ *
+ * settings -dectbl INT
+ * Force internal H/2 settings to certain values. Currently only
+ * support setting the decoding table size.
+ */
+static void
+cmd_http_settings(CMD_ARGS)
+{
+ uint32_t n;
+ char *p;
+ struct http *hp;
+ CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
+ (void)cmd;
+
+ if (!hp->h2)
+ vtc_log(hp->vl, 0, "Only possible in H/2 mode");
+
+ CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
+
+ for(; *av != NULL; av++) {
+ if (!strcmp(*av, "-dectbl")) {
+ n = strtoul(av[1], &p, 0);
+ if (*p != '\0')
+ vtc_log(hp->vl, 0, "-dectbl takes an integer as"
+ " argument (found %s)", av[1]);
+ HPK_ResizeTbl(hp->decctx, n);
+ av++;
+ } else
+ vtc_log(vl, 0, "Unknown settings spec: %s\n", *av);
+ }
+}
+
+static void
+cmd_http_stream(CMD_ARGS)
+{
+ struct http *hp = (struct http *)priv;
+ CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
+ if (!hp->h2) {
+ vtc_log(hp->vl, 4, "Not in H/2 mode, do what's needed");
+ if (hp->sfd)
+ parse_string("rxpri", http_cmds, hp, vl);
+ else
+ parse_string("txpri", http_cmds, hp, vl);
+ parse_string("stream 0 {\n"
+ "txsettings\n"
+ "rxsettings\n"
+ "txsettings -ack\n"
+ "rxsettings\n"
+ "expect settings.ack == true"
+ "} -run\n", http_cmds, hp, vl);
+ }
+ cmd_stream(av, hp, cmd, vl);
+}
+
/**********************************************************************
* Execute HTTP specifications
*/
-static const struct cmds http_cmds[] = {
+const struct cmds http_cmds[] = {
{ "timeout", cmd_http_timeout },
{ "txreq", cmd_http_txreq },
@@ -1629,6 +1816,12 @@ static const struct cmds http_cmds[] = {
{ "loop", cmd_http_loop },
{ "fatal", cmd_http_fatal },
{ "non-fatal", cmd_http_fatal },
+
+ { "rxpri", cmd_http_rxpri },
+ { "txpri", cmd_http_txpri },
+ { "stream", cmd_http_stream },
+ { "settings", cmd_http_settings },
+ { "upgrade", cmd_http_upgrade },
{ NULL, NULL }
};
@@ -1665,6 +1858,8 @@ http_process(struct vtclog *vl, const char *spec, int sock, int *sfd)
VTCP_hisname(sock, hp->rem_ip, VTCP_ADDRBUFSIZE, hp->rem_port, VTCP_PORTBUFSIZE);
parse_string(spec, http_cmds, hp, vl);
+ if (hp->h2)
+ stop_h2(hp);
retval = hp->fd;
VSB_destroy(&hp->vsb);
free(hp->rxbuf);
diff --git a/bin/varnishtest/vtc_http.h b/bin/varnishtest/vtc_http.h
new file mode 100644
index 0000000..f944f36
--- /dev/null
+++ b/bin/varnishtest/vtc_http.h
@@ -0,0 +1,45 @@
+#define MAX_HDR 50
+
+struct http {
+ unsigned magic;
+#define HTTP_MAGIC 0x2f02169c
+ int fd;
+ int *sfd;
+ int timeout;
+ struct vtclog *vl;
+
+ struct vsb *vsb;
+
+ int nrxbuf;
+ char *rxbuf;
+ char *rem_ip;
+ char *rem_port;
+ int prxbuf;
+ char *body;
+ unsigned bodyl;
+ char bodylen[20];
+ char chunklen[20];
+
+ char *req[MAX_HDR];
+ char *resp[MAX_HDR];
+
+ int gziplevel;
+ int gzipresidual;
+
+ int fatal;
+
+ /* H/2 */
+ unsigned h2;
+ int wf;
+
+ pthread_t tp;
+ VTAILQ_HEAD(, stream) streams;
+ pthread_mutex_t mtx;
+ pthread_cond_t cond;
+ struct hpk_ctx *encctx;
+ struct hpk_ctx *decctx;
+ uint64_t iws;
+ int64_t ws;
+};
+
+
diff --git a/bin/varnishtest/vtc_http2.c b/bin/varnishtest/vtc_http2.c
new file mode 100644
index 0000000..4a90d24
--- /dev/null
+++ b/bin/varnishtest/vtc_http2.c
@@ -0,0 +1,2785 @@
+/*-
+ * Copyright (c) 2008-2016 Varnish Software AS
+ * All rights reserved.
+ *
+ * Author: Guillaume Quintard <guillaume.quintard 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 "config.h"
+
+#include <sys/types.h>
+#include <sys/socket.h>
+
+#include <errno.h>
+#include <math.h>
+#include <poll.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <string.h>
+#include <ctype.h>
+#include <netinet/in.h>
+
+#include "vtc.h"
+#include "vtc_http.h"
+
+#include "vct.h"
+#include "vgz.h"
+#include "vnum.h"
+#include "vre.h"
+#include "hpack.h"
+#include "vend.h"
+
+#define MAX_HDR 50
+#define ERR_MAX 13
+#define BUF_SIZE (1024*2048)
+
+static const char *const h2_errs[] = {
+#define H2_ERROR(n,v,t) [v] = #n,
+#include <tbl/h2_error.h>
+ NULL
+};
+
+static const char *const h2_types[] = {
+#define H2_FRAME(l,u,t,f) [t] = #u,
+#include <tbl/h2_frames.h>
+ NULL
+};
+
+#define SETTINGS_MAX 0x06
+
+static const char * const h2_settings[] = {
+ [0] = "unknown",
+#define H2_SETTINGS(U,v,d) [v] = #U,
+#include <tbl/h2_settings.h>
+ NULL
+};
+
+enum h2_type {
+#define H2_FRAME(l,u,t,f) TYPE_##u = t,
+#include <tbl/h2_frames.h>
+ TYPE_MAX
+};
+
+enum {
+ ACK = 0x1,
+ END_STREAM = 0x1,
+ PADDED = 0x8,
+ END_HEADERS = 0x4,
+ PRIORITY = 0x20,
+};
+
+struct stream {
+ unsigned magic;
+#define STREAM_MAGIC 0x63f1fac2
+ uint32_t id;
+ char *spec;
+ char *name;
+ VTAILQ_ENTRY(stream) list;
+ unsigned running;
+ pthread_cond_t cond;
+ struct frame *frame;
+ pthread_t tp;
+ unsigned reading;
+ struct http *hp;
+ int64_t ws;
+
+ VTAILQ_HEAD(, frame) fq;
+
+ char *body;
+ int bodylen;
+ struct hpk_hdr req[MAX_HDR];
+ struct hpk_hdr resp[MAX_HDR];
+
+ int dependency;
+ int weight;
+};
+
+static void
+clean_headers(struct hpk_hdr *h)
+{
+ while (h->t) {
+ if (h->key.len)
+ free(h->key.ptr);
+ if (h->value.len)
+ free(h->value.ptr);
+ memset(h, 0, sizeof(*h));
+ h++;
+ }
+}
+
+#define ONLY_CLIENT(hp, av) \
+ do { \
+ if (hp->sfd != NULL) \
+ vtc_log(hp->vl, 0, \
+ "\"%s\" only possible in client", av[0]); \
+ } while (0)
+
+#define ONLY_SERVER(hp, av) \
+ do { \
+ if (hp->sfd == NULL) \
+ vtc_log(hp->vl, 0, \
+ "\"%s\" only possible in server", av[0]); \
+ } while (0)
+
+static void
+http_write(const struct http *hp, int lvl,
+ const char *buf, int s, const char *pfx)
+{
+ ssize_t l;
+
+ CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
+ AN(buf);
+ AN(pfx);
+
+ vtc_dump(hp->vl, lvl, pfx, buf, s);
+ l = write(hp->fd, buf, s);
+ if (l != s)
+ vtc_log(hp->vl, hp->fatal, "Write failed: (%zd vs %d) %s",
+ l, s, strerror(errno));
+}
+
+static int
+get_bytes(const struct http *hp, char *buf, int n)
+{
+ int i;
+ struct pollfd pfd[1];
+
+ CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
+ AN(buf);
+
+ while (n > 0) {
+ pfd[0].fd = hp->fd;
+ pfd[0].events = POLLIN;
+ pfd[0].revents = 0;
+ i = poll(pfd, 1, hp->timeout);
+ if (i < 0 && errno == EINTR)
+ continue;
+ if (i == 0)
+ vtc_log(hp->vl, hp->fatal,
+ "HTTP2 rx timeout (fd:%d %u ms)",
+ hp->fd, hp->timeout);
+ if (i < 0)
+ vtc_log(hp->vl, hp->fatal,
+ "HTTP2 rx failed (fd:%d poll: %s)",
+ hp->fd, strerror(errno));
+ assert(i > 0);
+ i = read(hp->fd, buf, n);
+ if (!(pfd[0].revents & POLLIN))
+ vtc_log(hp->vl, 4,
+ "HTTP2 rx poll (fd:%d revents: %x n=%d, i=%d)",
+ hp->fd, pfd[0].revents, n, i);
+ if (i == 0)
+ vtc_log(hp->vl, hp->fatal,
+ "HTTP2 rx EOF (fd:%d read: %s)",
+ hp->fd, strerror(errno));
+ if (i < 0)
+ vtc_log(hp->vl, hp->fatal,
+ "HTTP2 rx failed (fd:%d read: %s)",
+ hp->fd, strerror(errno));
+ n -= i;
+ }
+ return (1);
+
+}
+
+VTAILQ_HEAD(fq_head, frame);
+
+struct frame {
+ unsigned magic;
+#define FRAME_MAGIC 0x5dd3ec4
+ uint32_t size;
+ uint32_t stid;
+ uint8_t type;
+ uint8_t flags;
+ char *data;
+
+ VTAILQ_ENTRY(frame) list;
+
+ union {
+ struct {
+ uint32_t stream;
+ uint8_t exclusive;
+ uint8_t weight;
+ } prio;
+ uint32_t rst_err;
+ double settings[SETTINGS_MAX+1];
+ struct {
+ char data[9];
+ int ack;
+ } ping;
+ struct {
+ uint32_t err;
+ uint32_t stream;
+ char *debug;
+ } goaway;
+ uint32_t winup_size;
+ uint32_t promised;
+ uint8_t padded;
+ } md;
+};
+
+static void
+readFrameHeader(struct frame *f, const char *buf)
+{
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ AN(buf);
+
+ f->size = (unsigned char)buf[0] << 16;
+ f->size += (unsigned char)buf[1] << 8;
+ f->size += (unsigned char)buf[2];
+
+ f->type = (unsigned char)buf[3];
+
+ f->flags = (unsigned char)buf[4];
+
+ f->stid = (0xff & (unsigned char)buf[5]) << 24;
+ f->stid += (0xff & (unsigned char)buf[6]) << 16;
+ f->stid += (0xff & (unsigned char)buf[7]) << 8;
+ f->stid += (0xff & (unsigned char)buf[8]);
+};
+
+static void
+writeFrameHeader(char *buf, const struct frame *f)
+{
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ AN(buf);
+ buf[0] = (f->size >> 16) & 0xff;
+ buf[1] = (f->size >> 8) & 0xff;
+ buf[2] = (f->size ) & 0xff;
+
+ buf[3] = f->type;
+
+ buf[4] = f->flags;
+
+ buf[5] = (f->stid >> 24) & 0xff;
+ buf[6] = (f->stid >> 16) & 0xff;
+ buf[7] = (f->stid >> 8) & 0xff;
+ buf[8] = (f->stid ) & 0xff;
+}
+
+#define INIT_FRAME(f, ty, sz, id, fl) \
+do { \
+ f.magic = FRAME_MAGIC; \
+ f.type = TYPE_ ## ty; \
+ f.size = sz; \
+ f.stid = id; \
+ f.flags = fl; \
+ f.data = NULL; \
+} while(0)
+
+static void
+clean_frame(struct frame **f)
+{
+ AN(f);
+ if (!*f)
+ return;
+
+ CHECK_OBJ_NOTNULL(*f, FRAME_MAGIC);
+
+ if ((*f)->type == TYPE_GOAWAY)
+ free((*f)->md.goaway.debug);
+ free((*f)->data);
+ free(*f);
+ *f = NULL;
+}
+
+static void
+write_frame(struct http *hp, const struct frame *f, const unsigned lock)
+{
+ ssize_t l;
+ char hdr[9];
+
+ CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+
+ writeFrameHeader(hdr, f);
+
+ vtc_log(hp->vl, 3, "tx: stream: %d, type: %s (%d), "
+ "flags: 0x%02x, size: %d",
+ f->stid,
+ f->type < TYPE_MAX ? h2_types[f->type] : "?",
+ f->type, f->flags, f->size);
+
+ if (lock)
+ AZ(pthread_mutex_lock(&hp->mtx));
+ l = write(hp->fd, hdr, sizeof(hdr));
+ if (l != sizeof(hdr))
+ vtc_log(hp->vl, hp->fatal, "Write failed: (%zd vs %zd) %s",
+ l, sizeof(hdr), strerror(errno));
+
+ if (f->size) {
+ AN(f->data);
+ l = write(hp->fd, f->data, f->size);
+ if (l != f->size)
+ vtc_log(hp->vl, hp->fatal,
+ "Write failed: (%zd vs %d) %s",
+ l, f->size, strerror(errno));
+ }
+ if (lock)
+ AZ(pthread_mutex_unlock(&hp->mtx));
+}
+
+static void
+exclusive_stream_dependency(const struct stream *s)
+{
+ struct stream *target = NULL;
+ struct http *hp = s->hp;
+
+ if (s->id == 0)
+ return;
+
+ VTAILQ_FOREACH(target, &hp->streams, list) {
+ if (target->id != s->id && target->dependency == s->dependency)
+ target->dependency = s->id;
+ }
+}
+
+static void
+explain_flags(uint8_t flags, uint8_t type, struct vtclog *vl)
+{
+ if (flags & ACK && (type == TYPE_PING || type == TYPE_SETTINGS)) {
+ vtc_log(vl, 3, "flag: ACK");
+ flags &= ~ACK;
+ } else if (flags & END_STREAM &&
+ (type == TYPE_HEADERS ||
+ type == TYPE_PUSH_PROMISE ||
+ type == TYPE_DATA)) {
+ vtc_log(vl, 3, "flag: END_STREAM");
+ flags &= ~END_STREAM;
+ } else if (flags & END_HEADERS &&
+ (type == TYPE_HEADERS ||
+ type == TYPE_PUSH_PROMISE ||
+ type == TYPE_CONTINUATION)) {
+ vtc_log(vl, 3, "flag: END_TYPE_HEADERS");
+ flags &= ~END_HEADERS;
+ } else if (flags & PRIORITY &&
+ (type == TYPE_HEADERS ||
+ type == TYPE_PUSH_PROMISE)) {
+ vtc_log(vl, 3, "flag: END_PRIORITY");
+ flags &= ~PRIORITY;
+ } else if (flags & PADDED &&
+ (type == TYPE_DATA ||
+ type == TYPE_HEADERS ||
+ type == TYPE_PUSH_PROMISE)) {
+ vtc_log(vl, 3, "flag: PADDED");
+ flags &= ~PADDED;
+ } else if (flags)
+ vtc_log(vl, 3, "UNKNOWN FLAG(S): 0x%02x", flags);
+}
+
+static void
+parse_data(struct stream *s, struct frame *f)
+{
+ struct http *hp;
+ uint32_t size = f->size;
+ char *data = f->data;
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);;
+
+ if (f->flags & PADDED) {
+ f->md.padded = *((uint8_t *)data);
+ if (f->md.padded >= size) {
+ vtc_log(hp->vl, hp->fatal,
+ "invalid padding: %d reported,"
+ "but size is only %d",
+ f->md.padded, size);
+ size = 0;
+ f->md.padded = 0;
+ }
+ data++;
+ size -= f->md.padded + 1;
+ vtc_log(hp->vl, 4, "padding: %3d", f->md.padded);
+ }
+
+ if (!size)
+ vtc_log(hp->vl, 4, "s%u - no data", s->id);
+
+ if (s->id)
+ s->ws -= size;
+ s->hp->ws -= size;
+
+ if (s->body) {
+ s->body = realloc(s->body, s->bodylen + size + 1L);
+ } else {
+ AZ(s->bodylen);
+ s->body = malloc(size + 1L);
+ }
+ AN(s->body);
+ memcpy(s->body + s->bodylen, data, size);
+ s->bodylen += size;
+ s->body[s->bodylen] = '\0';
+}
+
+static void
+decode_hdr(struct http *hp, struct hpk_hdr *h, const struct vsb *vsb)
+{
+ struct hpk_iter *iter;
+ enum hpk_result r = hpk_err;
+ int n;
+
+ CHECK_OBJ_NOTNULL(vsb, VSB_MAGIC);
+ CAST_OBJ_NOTNULL(hp, hp, HTTP_MAGIC);;
+
+ if (VSB_len(vsb) == 0)
+ return;
+
+ iter = HPK_NewIter(hp->decctx, VSB_data(vsb), VSB_len(vsb));
+
+ n = 0;
+ while (n < MAX_HDR && h[n].t)
+ n++;
+ while (n < MAX_HDR) {
+ r = HPK_DecHdr(iter, h + n);
+ if (r == hpk_err )
+ break;
+ vtc_log(hp->vl, 4,
+ "header[%2d]: %s : %s",
+ n,
+ h[n].key.ptr,
+ h[n].value.ptr);
+ n++;
+ if (r == hpk_done)
+ break;
+ }
+
+ if (r != hpk_done)
+ vtc_log(hp->vl, hp->fatal ? 4 : 0,
+ "Header decoding failed (%d) %d", r, hp->fatal);
+ else if (n == MAX_HDR)
+ vtc_log(hp->vl, hp->fatal,
+ "Max number of headers reached (%d)", MAX_HDR);
+
+ HPK_FreeIter(iter);
+}
+
+static void
+parse_hdr(struct stream *s, struct frame *f, struct vsb *vsb)
+{
+ int shift = 0;
+ int exclusive = 0;
+ uint32_t size = f->size;
+ char *data = f->data;
+ struct http *hp;
+ uint32_t n;
+
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ CHECK_OBJ_NOTNULL(vsb, VSB_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);;
+
+ if (f->flags & PADDED && f->type != TYPE_CONTINUATION) {
+ f->md.padded = *((uint8_t *)data);
+ if (f->md.padded >= size) {
+ vtc_log(hp->vl, hp->fatal,
+ "invalid padding: %d reported,"
+ "but size is only %d",
+ f->md.padded, size);
+ size = 0;
+ f->md.padded = 0;
+ }
+ shift += 1;
+ size -= f->md.padded;
+ vtc_log(hp->vl, 4, "padding: %3d", f->md.padded);
+ }
+
+ if (f->type == TYPE_HEADERS && f->flags & PRIORITY){
+ shift += 5;
+ n = vbe32dec(f->data);
+ s->dependency = n & ~(1 << 31);
+ exclusive = n >> 31;
+
+ s->weight = f->data[4];
+ if (exclusive)
+ exclusive_stream_dependency(s);
+
+ vtc_log(hp->vl, 4, "stream->dependency: %u", s->dependency);
+ vtc_log(hp->vl, 4, "stream->weight: %u", s->weight);
+ } else if (f->type == TYPE_PUSH_PROMISE){
+ shift += 4;
+ n = vbe32dec(f->data);
+ f->md.promised = n & ~(1 << 31);
+ }
+
+ AZ(VSB_bcat(vsb, data + shift, size - shift));
+}
+
+static void
+parse_prio(struct stream *s, struct frame *f)
+{
+ struct http *hp;
+ char *buf;
+ uint32_t n;
+
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);;
+
+ if (f->size != 5)
+ vtc_log(hp->vl, 0, "Size should be 5, but isn't (%d)", f->size);
+
+ buf = f->data;
+ AN(buf);
+
+ n = vbe32dec(f->data);
+ f->md.prio.stream = n & ~(1 << 31);
+
+ s->dependency = f->md.prio.stream;
+ if (n >> 31){
+ f->md.prio.exclusive = 1;
+ exclusive_stream_dependency(s);
+ }
+
+ buf += 4;
+ f->md.prio.weight = *buf;
+ s->weight = f->md.prio.weight;
+
+ vtc_log(hp->vl, 3, "prio->stream: %u", f->md.prio.stream);
+ vtc_log(hp->vl, 3, "prio->weight: %u", f->md.prio.weight);
+}
+
+static void
+parse_rst(const struct stream *s, struct frame *f) {
+ struct http *hp;
+ uint32_t err;
+ const char *buf;
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);;
+
+ if (f->size != 4)
+ vtc_log(hp->vl, 0, "Size should be 4, but isn't (%d)", f->size);
+
+ err = vbe32dec(f->data);
+ f->md.rst_err = err;
+
+ vtc_log(hp->vl, 2, "ouch");
+ if (err <= ERR_MAX)
+ buf = h2_errs[err];
+ else
+ buf = "unknown";
+ vtc_log(hp->vl, 4, "rst->err: %s (%d)", buf, err);
+
+}
+
+static void
+parse_settings(const struct stream *s, struct frame *f)
+{
+ struct http *hp;
+ int i, t, v;
+ const char *buf;
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);;
+
+ if (f->size % 6)
+ vtc_log(hp->vl, 0, "Size should be a multiple of 6, but isn't (%d)", f->size);
+
+ for (i = 0; i <= SETTINGS_MAX; i++)
+ f->md.settings[i] = NAN;
+
+ for (i = 0; i < f->size;) {
+ t = vbe16dec(f->data + i);
+ i += 2;
+ v = vbe32dec(f->data + i);
+ if (t <= SETTINGS_MAX) {
+ buf = h2_settings[t];
+ f->md.settings[t] = v;
+ } else
+ buf = "unknown";
+ i += 4;
+
+ if (t == 1 )
+ HPK_ResizeTbl(s->hp->encctx, v);
+
+ vtc_log(hp->vl, 4, "settings->%s (%d): %d", buf, t, v);
+ }
+
+}
+
+static void
+parse_ping(const struct stream *s, struct frame *f)
+{
+ struct http *hp;
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);;
+ if (f->size != 8)
+ vtc_log(hp->vl, 0, "Size should be 8, but isn't (%d)", f->size);
+ f->md.ping.ack = f->flags & ACK;
+ memcpy(f->md.ping.data, f->data, 8);
+ f->md.ping.data[8] = '\0';
+
+ vtc_log(hp->vl, 4, "ping->data: %s", f->md.ping.data);
+
+}
+
+static void
+parse_goaway(const struct stream *s, struct frame *f)
+{
+ struct http *hp;
+ const char *err_buf;
+ uint32_t err, stid;
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);;
+
+ if (f->size < 8)
+ vtc_log(hp->vl, 0, "Size should be at least 8, but isn't (%d)", f->size);
+ if (f->data[0] & (1<<7))
+ vtc_log(hp->vl, 0, "First bit of data is reserved and should be 0");
+
+ stid = vbe32dec(f->data);
+ err = vbe32dec(f->data + 4);
+ f->md.goaway.err = err;
+ f->md.goaway.stream = stid;
+
+ if (err <= ERR_MAX)
+ err_buf = h2_errs[err];
+ else
+ err_buf = "unknown";
+
+ if (f->size > 8) {
+ f->md.goaway.debug = malloc(f->size - 8 + 1L);
+ AN(f->md.goaway.debug);
+ f->md.goaway.debug[f->size - 8] = '\0';
+
+ memcpy(f->md.goaway.debug, f->data + 8, f->size - 8);
+ }
+
+ vtc_log(hp->vl, 3, "goaway->laststream: %d", stid);
+ vtc_log(hp->vl, 3, "goaway->err: %s (%d)", err_buf, err);
+ if (f->md.goaway.debug)
+ vtc_log(hp->vl, 3, "goaway->debug: %s", f->md.goaway.debug);
+}
+
+static void
+parse_winup(const struct stream *s, struct frame *f)
+{
+ struct http *hp;
+ uint32_t size;
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);;
+
+ if (f->size != 4)
+ vtc_log(hp->vl, 0, "Size should be 4, but isn't (%d)", f->size);
+ if (f->data[0] & (1<<7))
+ vtc_log(hp->vl, s->hp->fatal, "First bit of data is reserved and should be 0");
+
+ size = vbe32dec(f->data);
+ f->md.winup_size = size;
+
+ vtc_log(hp->vl, 3, "winup->size: %d", size);
+}
+
+/* read a frame and queue it in the relevant stream, wait if not present yet.
+ */
+static void *
+receive_frame(void *priv)
+{
+ struct http *hp;
+ char hdr[9];
+ struct frame *f;
+ struct stream *s;
+ int expect_cont = 0;
+ struct vsb *vsb = NULL;
+ struct hpk_hdr *hdrs = NULL;
+
+ CAST_OBJ_NOTNULL(hp, priv, HTTP_MAGIC);
+
+ AZ(pthread_mutex_lock(&hp->mtx));
+ while (hp->h2) {
+ /*no wanted frames? */
+ if (hp->wf == 0) {
+ AZ(pthread_cond_wait(&hp->cond, &hp->mtx));
+ continue;
+ }
+ AZ(pthread_mutex_unlock(&hp->mtx));
+
+ if (!get_bytes(hp, hdr, 9)) {
+ vtc_log(hp->vl, 1, "could not get header");
+ return (NULL);
+ }
+ ALLOC_OBJ(f, FRAME_MAGIC);
+ AN(f);
+ readFrameHeader(f, hdr);
+
+ vtc_log(hp->vl, 3, "rx: stream: %d, type: %s (%d), "
+ "flags: 0x%02x, size: %d",
+ f->stid,
+ f->type < TYPE_MAX ? h2_types[f->type] : "?",
+ f->type, f->flags, f->size);
+ explain_flags(f->flags, f->type, hp->vl);
+
+ if (f->size) {
+ f->data = malloc(f->size + 1L);
+ AN(f->data);
+ f->data[f->size] = '\0';
+ get_bytes(hp, f->data, f->size);
+ }
+
+ /* is the corresponding stream waiting? */
+ AZ(pthread_mutex_lock(&hp->mtx));
+ s = NULL;
+ while (!s) {
+ VTAILQ_FOREACH(s, &hp->streams, list) {
+ if (s->id == f->stid)
+ break;
+ }
+ if (!s)
+ AZ(pthread_cond_wait(&hp->cond, &hp->mtx));
+ if (!hp->h2) {
+ clean_frame(&f);
+ AZ(pthread_mutex_unlock(&hp->mtx));
+ return (NULL);
+ }
+ }
+ AZ(pthread_mutex_unlock(&hp->mtx));
+
+ if (expect_cont && (f->type != TYPE_CONTINUATION ||
+ expect_cont != s->id))
+ vtc_log(hp->vl, 0, "Expected CONTINUATION frame for"
+ " stream %u", expect_cont);
+
+ /* parse the frame according to it type, and fill the metada */
+ switch (f->type) {
+ case TYPE_DATA:
+ parse_data(s, f);
+ break;
+ case TYPE_PUSH_PROMISE:
+ hdrs = s->req;
+ /*FALLTHROUGH*/
+ case TYPE_HEADERS:
+ if (!hdrs) {
+ if (hp->sfd)
+ hdrs = s->req;
+ else
+ hdrs = s->resp;
+ }
+ AZ(vsb);
+ vsb = VSB_new_auto();
+ /*FALLTHROUGH*/
+ case TYPE_CONTINUATION:
+ AN(hdrs);
+ expect_cont = s->id;
+ parse_hdr(s, f, vsb);
+ if (f->flags & END_HEADERS) {
+ expect_cont = 0;
+ VSB_finish(vsb);
+ decode_hdr(hp, hdrs, vsb);
+ VSB_destroy(&vsb);
+ hdrs = NULL;
+ }
+ break;
+ case TYPE_PRIORITY:
+ parse_prio(s, f);
+ break;
+ case TYPE_RST_STREAM:
+ parse_rst(s, f);
+ break;
+ case TYPE_SETTINGS:
+ parse_settings(s, f);
+ break;
+ case TYPE_PING:
+ parse_ping(s, f);
+ break;
+ case TYPE_GOAWAY:
+ parse_goaway(s, f);
+ break;
+ case TYPE_WINDOW_UPDATE:
+ parse_winup(s, f);
+ break;
+ default:
+ WRONG("wrong frame type");
+ }
+
+ AZ(pthread_mutex_lock(&hp->mtx));
+ VTAILQ_INSERT_HEAD(&s->fq, f, list);
+ hp->wf--;
+ AZ(pthread_cond_signal(&s->cond));
+ continue;
+ }
+ AZ(pthread_mutex_unlock(&hp->mtx));
+ AZ(vsb);
+ return (NULL);
+}
+
+#define STRTOU32(n, s, p, v, c) \
+ do { \
+ n = strtoul(s, &p, 0); \
+ if (*p != '\0') { \
+ vtc_log(v, 0, "%s takes an integer as argument" \
+ "(found %s)", c, s); \
+ WRONG("Couldn't convert to integer");\
+ } \
+ } while (0)
+
+#define STRTOU32_CHECK(n, sp, p, v, c, l) \
+do { \
+ sp++; \
+ AN(*sp); \
+ STRTOU32(n, *sp, p, v, c); \
+ if (l && n >= (1 << l)) \
+ vtc_log(v, 0, c " must be a %d-bits integer (found %s)", \
+ l, *sp); \
+} while (0)
+
+#define CHECK_LAST_FRAME(TYPE) \
+ if (!f || f->type != TYPE_ ## TYPE) { \
+ vtc_log(s->hp->vl, 0, "Last frame was not of type " #TYPE); \
+ }
+
+#define RETURN_SETTINGS(idx) \
+do { \
+ if (isnan(f->md.settings[idx])) { \
+ return (NULL); \
+ } \
+ snprintf(buf, 20, "%.0f", f->md.settings[idx]); \
+ return (buf); \
+} while (0);
+
+#define RETURN_BUFFED(val) \
+do { \
+ snprintf(buf, 20, "%d", val); \
+ return (buf); \
+} while (0)
+
+static char *
+find_header(const struct hpk_hdr *h, const char *k)
+{
+ AN(k);
+
+ int kl = strlen(k);
+ while (h->t) {
+ if (kl == h->key.len && !memcmp(h->key.ptr, k, kl))
+ return h->value.ptr;
+ h++;
+ }
+ return (NULL);
+}
+/* SECTION: stream.spec.zexpect expect
+ *
+ * expect in stream works as it does in client or server, except that the
+ * elements compared will be different.
+ *
+ * Most of these elements will be frame specific, meaning that the last frame
+ * received on that stream must of the correct type.
+ *
+ * Here the list of keywords you can look at.
+ */
+static const char *
+cmd_var_resolve(const struct stream *s, const char *spec, char *buf)
+{
+ uint32_t idx;
+ int n;
+ const struct hpk_hdr *h;
+ struct hpk_ctx *ctx;
+ struct frame *f = s->frame;
+
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ CHECK_OBJ_NOTNULL(s->hp, HTTP_MAGIC);
+ AN(spec);
+ AN(buf);
+
+ n = 0;
+ /* SECTION: stream.spec.zexpect.ping PING specific
+ * ping.data
+ * The 8-bytes string of the PING frame payload.
+ * ping.ack (PING)
+ * "true" if the ACK flag was set, "false" otherwise.
+ */
+ if (!strcmp(spec, "ping.data")) {
+ CHECK_LAST_FRAME(PING);
+ return (f->md.ping.data);
+ }
+ else if (!strcmp(spec, "ping.ack")) {
+ CHECK_LAST_FRAME(PING);
+ snprintf(buf, 20, (f->flags & ACK) ? "true" : "false");
+ return (buf);
+ }
+ /* SECTION: stream.spec.zexpect.winup WINDOW_UPDATE specific
+ * winup.size
+ * The size of the upgrade given by the WINDOW_UPDATE frame.
+ */
+ else if (!strcmp(spec, "winup.size")) {
+ CHECK_LAST_FRAME(WINDOW_UPDATE);
+ RETURN_BUFFED(f->md.winup_size);
+ }
+ /* SECTION: stream.spec.zexpect.prio PRIORITY specific
+ * prio.stream
+ * The stream ID announced.
+ *
+ * prio.exclusive
+ * "true" if the priority is exclusive, else "false".
+ *
+ * prio.weight
+ * The dependency weight.
+ */
+ else if (!strcmp(spec, "prio.stream")) {
+ CHECK_LAST_FRAME(PRIORITY);
+ RETURN_BUFFED(f->md.prio.stream);
+ }
+ else if (!strcmp(spec, "prio.exclusive")) {
+ CHECK_LAST_FRAME(PRIORITY);
+ snprintf(buf, 20, f->md.prio.exclusive ? "true" : "false");
+ return (buf);
+ }
+ else if (!strcmp(spec, "prio.weight")) {
+ CHECK_LAST_FRAME(PRIORITY);
+ RETURN_BUFFED(f->md.prio.weight);
+ }
+ /* SECTION: stream.spec.zexpect.rst RESET_STREAM specific
+ * rst.err
+ * The error code (as integer) of the RESET_STREAM frame.
+ */
+ else if (!strcmp(spec, "rst.err")) {
+ CHECK_LAST_FRAME(RST_STREAM);
+ RETURN_BUFFED(f->md.rst_err);
+ }
+ /* SECTION: stream.spec.zexpect.settings SETTINGS specific
+ *
+ * settings.ack
+ * "true" if the ACK flag was set, else ""false.
+ *
+ * settings.push
+ * "true" if the push settings was set to yes, "false" if set to
+ * no, and <undef> if not present.
+ *
+ * settings.hdrtbl
+ * Value of HEADER_TABLE_SIZE if set, <undef> otherwise.
+ *
+ * settings.maxstreams
+ * Value of MAX_CONCURRENT_STREAMS if set, <undef> otherwise.
+ *
+ * settings.winsize
+ * Value of INITIAL_WINDOW_SIZE if set, <undef> otherwise.
+ *
+ * setting.framesize
+ * Value of MAX_FRAME_SIZE if set, <undef> otherwise.
+ *
+ * settings.hdrsize
+ * Value of MAX_HEADER_LIST_SIZE if set, <undef> otherwise.
+ */
+ else if (!strncmp(spec, "settings.", 9)) {
+ CHECK_LAST_FRAME(SETTINGS);
+ spec += 9;
+ if (!strcmp(spec, "ack")) {
+ snprintf(buf, 20, (f->flags & ACK) ? "true" : "false");
+ return (buf);
+ }
+ else if (!strcmp(spec, "push")) {
+ if (isnan(f->md.settings[2]))
+ return (NULL);
+ else if (f->md.settings[2] == 1)
+ snprintf(buf, 20, "true");
+ else
+ snprintf(buf, 20, "false");
+ return (buf);
+ }
+ else if (!strcmp(spec, "hdrtbl")) { RETURN_SETTINGS(1); }
+ else if (!strcmp(spec, "maxstreams")) { RETURN_SETTINGS(3); }
+ else if (!strcmp(spec, "winsize")) { RETURN_SETTINGS(4); }
+ else if (!strcmp(spec, "framesize")) { RETURN_SETTINGS(5); }
+ else if (!strcmp(spec, "hdrsize")) { RETURN_SETTINGS(6); }
+ }
+ /* SECTION: stream.spec.zexpect.push PUSH_PROMISE specific
+ * push.id
+ * The id of the promised stream.
+ */
+ else if (!strcmp(spec, "push.id")) {
+ CHECK_LAST_FRAME(PUSH_PROMISE);
+ RETURN_BUFFED(f->md.promised);
+ }
+ /* SECTION: stream.spec.zexpect.goaway GOAWAY specific
+ * goaway.err
+ * The error code (as integer) of the GOAWAY frame.
+ *
+ * goaway.laststream
+ * Last-Stream-ID
+ *
+ * goaway.debug
+ * Debug data, if any.
+ */
+ else if (!strncmp(spec, "goaway.", 7)) {
+ spec += 7;
+ CHECK_LAST_FRAME(GOAWAY);
+
+ if (!strcmp(spec, "err")) {
+ RETURN_BUFFED(f->md.goaway.err);
+ }
+ else if (!strcmp(spec, "laststream")) {
+ RETURN_BUFFED(f->md.goaway.stream);
+ }
+ else if (!strcmp(spec, "debug")) {
+ return (f->md.goaway.debug);
+ }
+ }
+ /* SECTION: stream.spec.zexpect.zframe Generic frame
+ * frame.data
+ * Payload of the last frame
+ *
+ * frame.type
+ * Type of the frame, as integer.
+ *
+ * frame.size
+ * Size of the frame.
+ *
+ * frame.stream
+ * Stream of the frame (correspond to the one you are executing
+ * this from, obviously).
+ *
+ * frame.padding (for DATA, HEADERS, PUSH_PROMISE frames)
+ * Number of padded bytes.
+ */
+ else if (!strncmp(spec, "frame.", 6)) {
+ spec += 6;
+ if (!f)
+ vtc_log(s->hp->vl, 0, "No frame received yet.");
+ if (!strcmp(spec, "data")) { return (f->data); }
+ else if (!strcmp(spec, "type")) { RETURN_BUFFED(f->type); }
+ else if (!strcmp(spec, "size")) { RETURN_BUFFED(f->size); }
+ else if (!strcmp(spec, "stream")) { RETURN_BUFFED(f->stid); }
+ else if (!strcmp(spec, "padding")) {
+ if (f->type != TYPE_DATA &&
+ f->type != TYPE_HEADERS &&
+ f->type != TYPE_PUSH_PROMISE)
+ vtc_log(s->hp->vl, 0,
+ "Last frame was not of type "
+ "DATA, HEADERS or PUSH");
+ RETURN_BUFFED(f->md.padded);
+ }
+ }
+ /* SECTION: stream.spec.zexpect.zstream Stream
+ * stream.window
+ * The current window size of the stream, or, if on stream 0,
+ * of the connection.
+ *
+ * stream.weight
+ * Weight of the stream
+ *
+ * stream.dependency
+ * Id of the stream this one depends on.
+ */
+ else if (!strcmp(spec, "stream.window")) {
+ snprintf(buf, 20, "%ld", s->id ? s->ws : s->hp->ws);
+ return (buf);
+ }
+ else if (!strcmp(spec, "stream.weight")) {
+ if (s->id) {
+ snprintf(buf, 20, "%d", s->weight);
+ return (buf);
+ } else {
+ return NULL;
+ }
+ }
+ else if (!strcmp(spec, "stream.dependency")) {
+ if (s->id) {
+ snprintf(buf, 20, "%d", s->dependency);
+ return (buf);
+ } else {
+ return NULL;
+ }
+ }
+ /* SECTION: stream.spec.zexpect.ztable Index tables
+ * tbl.dec.size / tbl.enc.size
+ * Size (bytes) of the decoding/encoding table.
+ *
+ * tbl.dec.size / tbl.enc.maxsize
+ * Maximum size (bytes) of the decoding/encoding table.
+ *
+ * tbl.dec.length / tbl.enc.length
+ * Number of headers in decoding/encoding table.
+ *
+ * tbl.dec[INT].key / tbl.enc[INT].key
+ * Name of the header at index INT of the decoding/encoding
+ * table.
+ *
+ * tbl.dec[INT].value / tbl.enc[INT].value
+ * Value of the header at index INT of the decoding/encoding
+ * table.
+ */
+ else if (!strncmp(spec, "tbl.dec", 7) ||
+ !strncmp(spec, "tbl.enc", 7)) {
+ if (spec[4] == 'd')
+ ctx = s->hp->decctx;
+ else
+ ctx = s->hp->encctx;
+ spec += 7;
+
+ if (1 == sscanf(spec, "[%u].key%n", &idx, &n) &&
+ spec[n] == '\0') {
+ h = HPK_GetHdr(ctx, idx + 61);
+ return (h ? h->key.ptr : NULL);
+ }
+ else if (1 == sscanf(spec, "[%u].value%n", &idx, &n) &&
+ spec[n] == '\0') {
+ h = HPK_GetHdr(ctx, idx + 61);
+ return (h ? h->value.ptr : NULL);
+ }
+ else if (!strcmp(spec, ".size")) {
+ RETURN_BUFFED(HPK_GetTblSize(ctx));
+ }
+ else if (!strcmp(spec, ".maxsize")) {
+ RETURN_BUFFED(HPK_GetTblMaxSize(ctx));
+ }
+ else if (!strcmp(spec, ".length")) {
+ RETURN_BUFFED(HPK_GetTblLength(ctx));
+ }
+ }
+ /* SECTION: stream.spec.zexpect.zre Request and response
+ *
+ * Note: it's possible to inspect a request or response while it is
+ * still being construct (in-between two frames for example).
+ *
+ * req.bodylen / resp.bodylen
+ * Length in bytes of the request/response so far.
+ *
+ * req.body / resp.body
+ * Body of the request/response so far.
+ *
+ * req.http.STRING / resp.http.STRING
+ * Value of the header STRING in the request/response.
+ *
+ * req.status / resp.status
+ * :status pseudo-header's value.
+ *
+ * req.url / resp.url
+ * :path pseudo-header's value.
+ *
+ * req.method / resp.method
+ * :method pseudo-header's value.
+ *
+ * req.authority / resp.authority
+ * :method pseudo-header's value.
+ *
+ * req.scheme / resp.scheme
+ * :method pseudo-header's value.
+ */
+ else if (!strncmp(spec, "req.", 4) || !strncmp(spec, "resp.", 5)) {
+ if (spec[2] == 'q') {
+ h = s->req;
+ spec += 4;
+ } else {
+ h = s->resp;
+ spec += 5;
+ }
+ if (!strcmp(spec, "body"))
+ return (s->body);
+ else if (!strcmp(spec, "bodylen"))
+ RETURN_BUFFED(s->bodylen);
+ else if (!strcmp(spec, "status"))
+ return (find_header(h, ":status"));
+ else if (!strcmp(spec, "url"))
+ return (find_header(h, ":path"));
+ else if (!strcmp(spec, "method"))
+ return (find_header(h, ":method"));
+ else if (!strcmp(spec, "authority"))
+ return (find_header(h, ":authority"));
+ else if (!strcmp(spec, "scheme"))
+ return (find_header(h, ":scheme"));
+ else if (!strncmp(spec, "http.", 5))
+ return (find_header(h, spec + 5));
+ else
+ return (NULL);
+ }
+ else
+ return (spec);
+ return(NULL);
+}
+
+/* SECTION: stream.spec.frame_sendhex sendhex
+ *
+ * Push bytes directly on the wire. sendhex takes exactly one argument: a string
+ * describing the bytes, in hex notation, will possible whitespaces between
+ * them. Here's an example::
+ *
+ * sendhex "00 00 08 00 0900 8d"
+ */
+static void
+cmd_sendhex(CMD_ARGS)
+{
+ struct http *hp;
+ struct stream *s;
+ char *q;
+ char *buf;
+ char tmp[3];
+ int i;
+ unsigned size = 0;
+ (void)cmd;
+
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);
+ AN(av[1]);
+ AZ(av[2]);
+
+ q = av[1];
+ size = strlen(q)/2;
+ buf = malloc(size);
+ AN(buf);
+ for (i = 0; i < size; i++) {
+ while (vct_issp(*q))
+ q++;
+ if (*q == '\0')
+ break;
+ memcpy(tmp, q, 2);
+ q += 2;
+ tmp[2] = '\0';
+ if (!vct_ishex(tmp[0]) || !vct_ishex(tmp[1]))
+ vtc_log(vl, 0, "Illegal Hex char \"%c%c\"",
+ tmp[0], tmp[1]);
+ buf[i] = (uint8_t)strtoul(tmp, NULL, 16);
+ }
+ AZ(pthread_mutex_lock(&hp->mtx));
+ http_write(hp, 4, buf, i, "sendhex");
+
+ AZ(pthread_mutex_unlock(&hp->mtx));
+ vtc_hexdump(vl, 4, "sendhex", (void *)buf, size);
+ free(buf);
+}
+
+#define ENC(hdr, k, v) \
+{ \
+ AN(k); \
+ hdr.key.ptr = strdup(k); \
+ AN(hdr.key.ptr); \
+ hdr.key.len = strlen(k); \
+ AN(v); \
+ hdr.value.ptr = strdup(v); \
+ AN(hdr.value.ptr); \
+ hdr.value.len = strlen(v); \
+ HPK_EncHdr(iter, &hdr); \
+ free(hdr.key.ptr); \
+ free(hdr.value.ptr); \
+}
+
+#define STR_ENC(av, field, str) \
+{ \
+ av++; \
+ if (AV_IS("plain")) { hdr.field.huff = 0; } \
+ else if (AV_IS("huf")) { hdr.field.huff = 1; } \
+ else \
+ vtc_log(vl, 0, str " arg can be huf or plain (got: %s)", *av); \
+ av++; \
+ AN(*av); \
+ hdr.field.ptr = *av; \
+ hdr.field.len = strlen(*av); \
+}
+
+
+/* SECTION: stream.spec.data_0 txreq, txresp, txcont, txpush
+ *
+ * These four commands are about sending headers. txreq, txresp will send
+ * HEADER frames, txcont will send CONTINUATION frames, and txpush PUSH frames.
+ * The only difference between txreq and txresp are the default headers set by
+ * each of them.
+ *
+ * \-noadd
+ * Do not add default headers. Useful to avoid duplicates when sending
+ * default headers using ``-hdr``, ``-idxHdr`` and ``-litIdxHdr``.
+ *
+ * \-status INT (txresp)
+ * Set the :status pseudo-header.
+ *
+ * \-url STRING (txreq, txpush)
+ * Set the :path pseudo-header.
+ *
+ * \-req STRING (txreq, txpush)
+ * Set the :method pseudo-header.
+ *
+ * \-scheme STRING (txreq, txpush)
+ * Set the :scheme pseudo-header.
+ *
+ * \-hdr STRING1 STRING2
+ * Insert a header, STRING1 being the name, and STRING2 the value.
+ *
+ * \-idxHdr INT
+ * Insert an indexed header, using INT as index.
+ *
+ * \-litIdxHdr inc|not|never INT huf|plain STRING
+ * Insert an literal, indexed header. The first argument specify if the
+ * header should be added to the table, shouldn't, or mustn't be
+ * compressed if/when retransmitted.
+ *
+ * INT is the idex of the header name to use.
+ *
+ * The third argument informs about the Huffman encoding: yes (huf) or
+ * no (plain).
+ *
+ * The last term is the literal value of the header.
+ *
+ * \-litHdr inc|not|never huf|plain STRING1 huf|plain STRING2
+ * Insert a literal header, with the same first argument as
+ * ``-litIdxHdr``.
+ *
+ * The second and third terms tell what the name of the header is and if
+ * it should be Huffman-encoded, while the last two do the same
+ * regarding the value.
+ *
+ * \-body STRING (txreq, txresp)
+ * Specify a body, effectively putting STRING into a DATA frame after
+ * the HEADER frame is sent.
+ *
+ * \-bodylen INT (txreq, txresp)
+ * Do the same thing as ``-body`` but generate an string of INT length
+ * for you.
+ *
+ * \-nostrend (txreq, txresp)
+ * Don't set the END_STREAM flag automatically, making the peer expect
+ * a body after the headers.
+ *
+ * \-nohdrend
+ * Don't set the END_HEADERS flag automatically, making the peer expect
+ * more HEADER frames.
+ *
+ * \-dep INT (txreq, txresp)
+ * Tell the peer that this content depends on the stream with the INT
+ * id.
+ *
+ * \-ex (txreq, txresp)
+ * Make the dependency exclusive (``-dep`` is still needed).
+ *
+ * \-weight (txreq, txresp)
+ * Set the weight for the dependency.
+ *
+ * \-promised INT (txpush)
+ * The id of the promised stream.
+ *
+ * \-pad STRING / -padlen INT (txreq, txresp, txpush)
+ * Add string as padding to the frame, either the one you provided with
+ * \-pad, or one that is generated for you, of length INT is -padlen
+ * case.
+ */
+static void
+cmd_tx11obj(CMD_ARGS)
+{
+ struct stream *s;
+ int status_done = 1;
+ int method_done = 1;
+ int path_done = 1;
+ int scheme_done = 1;
+ uint32_t stid = 0, pstid;
+ uint32_t weight = 16;
+ int exclusive = 0;
+ char *buf;
+ struct hpk_iter *iter;
+ struct frame f;
+ char *body = NULL, *pad = NULL;
+ /*XXX: do we need a better api? yes we do */
+ struct hpk_hdr hdr;
+ char *cmd_str = *av;
+ char *p;
+ (void)cmd;
+
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+ INIT_FRAME(f, CONTINUATION, 0, s->id, END_HEADERS);
+ buf = malloc(BUF_SIZE);
+ AN(buf);
+
+ if (!strcmp(cmd_str, "txreq")) {
+ ONLY_CLIENT(s->hp, av);
+ f.type = TYPE_HEADERS;
+ f.flags |= END_STREAM;
+ method_done = 0;
+ path_done = 0;
+ scheme_done = 0;
+ } else if (!strcmp(cmd_str, "txresp")) {
+ ONLY_SERVER(s->hp, av);
+ f.type = TYPE_HEADERS;
+ f.flags |= END_STREAM;
+ status_done = 0;
+ } else if (!strcmp(cmd_str, "txpush")) {
+ ONLY_SERVER(s->hp, av);
+ f.type = TYPE_PUSH_PROMISE;
+ method_done = 0;
+ path_done = 0;
+ scheme_done = 0;
+ }
+
+ if (f.type == TYPE_PUSH_PROMISE) {
+ *buf = 0;
+ iter = HPK_NewIter(s->hp->encctx, buf + 4, BUF_SIZE - 4);
+ } else
+ iter = HPK_NewIter(s->hp->encctx, buf, BUF_SIZE);
+
+#define AV_IS(str) !strcmp(*av, str)
+#define CMD_IS(str) !strcmp(cmd_str, str)
+ while (*++av) {
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.t = hpk_not;
+ if (AV_IS("-noadd")) {
+ path_done = 1;
+ status_done = 1;
+ method_done = 1;
+ scheme_done = 1;
+ }
+ else if (AV_IS("-status") && CMD_IS("txresp")) {
+ ENC(hdr, ":status", av[1]);
+ av++;
+ status_done = 1;
+ }
+ else if (AV_IS("-url") &&
+ (CMD_IS("txreq") || CMD_IS("txpush"))) {
+ ENC(hdr, ":path", av[1]);
+ av++;
+ path_done = 1;
+ }
+ else if (AV_IS("-req") &&
+ (CMD_IS("txreq") || CMD_IS("txpush"))) {
+ ENC(hdr, ":method", av[1]);
+ av++;
+ method_done = 1;
+ }
+ else if (AV_IS("-scheme") &&
+ (CMD_IS("txreq") || CMD_IS("txpush"))) {
+ ENC(hdr, ":scheme", av[1]);
+ av++;
+ scheme_done = 1;
+ }
+ else if (AV_IS("-hdr")) {
+ ENC(hdr, av[1], av[2]);
+ av += 2;
+ }
+ else if (AV_IS("-idxHdr")) {
+ hdr.t = hpk_idx;
+ STRTOU32_CHECK(hdr.i, av, p, vl, "-idxHdr", 0);
+ HPK_EncHdr(iter, &hdr);
+ }
+ else if (AV_IS("-litIdxHdr")) {
+ av++;
+ if (AV_IS("inc")) { hdr.t = hpk_inc; }
+ else if (AV_IS("not")) { hdr.t = hpk_not; }
+ else if (AV_IS("never")) { hdr.t = hpk_never; }
+ else
+ vtc_log(vl, 0, "first -litidxHdr arg can be "
+ "inc, not, never (got: %s)",
+ *av);
+
+ STRTOU32_CHECK(hdr.i, av, p, vl,
+ "second -litidxHdr arg", 0);
+
+ hdr.key.ptr = NULL;
+ hdr.key.len = 0;
+ STR_ENC(av, value, "third -litHdr");
+ HPK_EncHdr(iter, &hdr);
+ }
+ else if (AV_IS("-litHdr")) {
+ av++;
+ if (AV_IS("inc")) { hdr.t = hpk_inc; }
+ else if (AV_IS("not")) { hdr.t = hpk_not; }
+ else if (AV_IS("never")) { hdr.t = hpk_never; }
+ else
+ vtc_log(vl, 0, "first -litHdr arg can be inc, "
+ "not, never (got: %s)", *av);
+
+ STR_ENC(av, key, "second -litHdr");
+ STR_ENC(av, value, "fourth -litHdr");
+ HPK_EncHdr(iter, &hdr);
+ }
+ else if (AV_IS("-nostrend")) {
+ f.flags &= ~END_STREAM;
+ }
+ else if (AV_IS("-nohdrend")) {
+ f.flags &= ~END_HEADERS;
+ }
+ else if (AV_IS("-promised") && CMD_IS("txpush")) {
+ STRTOU32_CHECK(pstid, av, p, vl, "-promised", 31);
+ vbe32enc(buf, pstid);
+ }
+ else if (AV_IS("-pad") && !CMD_IS("txcont")) {
+ AZ(pad);
+ av++;
+ AN(*av);
+ pad = strdup(*av);
+ }
+ else if (AV_IS("-padlen") && !CMD_IS("txcont")) {
+ AZ(pad);
+ av++;
+ pad = synth_body(*av, 0);
+ }
+ else if (CMD_IS("txreq") || CMD_IS("txresp")) {
+ if (AV_IS("-body")) {
+ AZ(body);
+ REPLACE(body, av[1]);
+ f.flags &= ~END_STREAM;
+ av++;
+ }
+ else if (AV_IS("-bodylen")) {
+ AZ(body);
+ body = synth_body(av[1], 0);
+ f.flags &= ~END_STREAM;
+ av++;
+ }else if (AV_IS("-dep")) {
+ STRTOU32_CHECK(stid, av, p, vl, "-dep", 0);
+ f.flags |= PRIORITY;
+ }
+ else if (AV_IS("-ex")) {
+ exclusive = 1 << 31;
+ f.flags |= PRIORITY;
+ }
+ else if (AV_IS("-weight")) {
+ STRTOU32_CHECK(weight, av, p, vl, "-weight", 8);
+ f.flags |= PRIORITY;
+ } else
+ break;
+ } else
+ break;
+ }
+#undef CMD_IS
+#undef AV_IS
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown %s spec: %s\n", cmd_str, *av);
+
+ memset(&hdr, 0, sizeof(hdr));
+ hdr.t = hpk_not;
+
+ if (!status_done) { ENC(hdr, ":status", "200"); }
+ if (!path_done) { ENC(hdr, ":path", "/"); }
+ if (!method_done) { ENC(hdr, ":method", "GET"); }
+ if (!scheme_done) { ENC(hdr, ":scheme", "http"); }
+
+ f.size = gethpk_iterLen(iter);
+ if (f.flags & PRIORITY) {
+ s->weight = weight & 0xff;
+ s->dependency = stid;
+
+ assert(f.size + 5 < BUF_SIZE);
+ memmove(buf + 5, buf, f.size);
+ vbe32enc(buf, (stid | exclusive));
+ buf[4] = s->weight;
+ f.size += 5;
+
+ vtc_log(vl, 4, "stream->dependency: %u", s->dependency);
+ vtc_log(vl, 4, "stream->weight: %u", s->weight);
+ if (exclusive)
+ exclusive_stream_dependency(s);
+ }
+ if (pad) {
+ if (strlen(pad) >= 128)
+ vtc_log(vl, 0, "Padding is limited to 128 bytes");
+ f.flags |= PADDED;
+ assert(f.size + strlen(pad) < BUF_SIZE);
+ memmove(buf + 1, buf, f.size);
+ buf[0] = strlen(pad);
+ f.size += 1;
+ memcpy(buf + f.size, pad, strlen(pad));
+ f.size += strlen(pad);
+ free(pad);
+ }
+ if (f.type == TYPE_PUSH_PROMISE)
+ f.size += 4;
+ f.data = buf;
+ HPK_FreeIter(iter);
+ write_frame(s->hp, &f, 1);
+ free(buf);
+
+ if (!body)
+ return;
+
+ INIT_FRAME(f, DATA, strlen(body), s->id, END_STREAM);
+ f.data = body;
+
+ write_frame(s->hp, &f, 1);
+ free(body);
+}
+
+/* SECTION: stream.spec.data_1 txdata
+ *
+ * By default, data frames are empty. The receiving end will know the whole body
+ * has been delivered thanks to the END_STREAM flag set in the last DATA frame,
+ * and txdata automatically set it.
+ *
+ * \-data STRING
+ * Data to be embedded into the frame.
+ *
+ * \-datalen INT
+ * Generate and INT-bytes long string to be sent in the frame.
+ *
+ * \-pad STRING / -padlen INT
+ * Add string as padding to the frame, either the one you provided with
+ * \-pad, or one that is generated for you, of length INT is -padlen
+ * case.
+ *
+ * \-nostrend
+ * Don't set the END_STREAM flag, allowing to send more data on this
+ * stream.
+ */
+static void
+cmd_txdata(CMD_ARGS)
+{
+ struct stream *s;
+ char *pad = NULL;
+ struct frame f;
+ char *body = NULL;
+ char *data = NULL;
+ (void)cmd;
+
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+
+ INIT_FRAME(f, DATA, 0, s->id, END_STREAM);
+
+ while (*++av) {
+ if (!strcmp(*av, "-data")) {
+ AZ(body);
+ av++;
+ body = strdup(*av);
+ } else if (!strcmp(*av, "-datalen")) {
+ AZ(data);
+ av++;
+ body = synth_body(*av, 0);
+ } else if (!strcmp(*av, "-pad")) {
+ AZ(pad);
+ av++;
+ AN(*av);
+ pad = strdup(*av);
+ } else if (!strcmp(*av, "-padlen")) {
+ AZ(pad);
+ av++;
+ pad = synth_body(*av, 0);
+ } else if (!strcmp(*av, "-nostrend"))
+ f.flags &= ~END_STREAM;
+ else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown txdata spec: %s\n", *av);
+
+ if (!body)
+ body = strdup("");
+
+ if (pad) {
+ f.flags |= PADDED;
+ if (strlen(pad) >= 128)
+ vtc_log(vl, 0, "Padding is limited to 128 bytes");
+ data = malloc( 1 + strlen(body) + strlen(pad));
+ AN(data);
+ *((uint8_t *)data) = strlen(pad);
+ f.size = 1;
+ memcpy(data + f.size, body, strlen(body));
+ f.size += strlen(body);
+ memcpy(data + f.size, pad, strlen(pad));
+ f.size += strlen(pad);
+ f.data = data;
+ } else {
+ f.size = strlen(body);
+ f.data = body;
+ }
+ write_frame(s->hp, &f, 1);
+ free(body);
+ free(pad);
+ free(data);
+}
+
+/* SECTION: stream.spec.reset_txrst txrst
+ *
+ * Send a RST_STREAM frame. By default, txrst will send a 0 error code
+ * (NO_ERROR).
+ *
+ * \-err STRING|INT
+ * Sets the error code to be sent. The argument can be an integer or a
+ * string describing the error, such as NO_ERROR, or CANCEL (see
+ * rfc7540#11.4 for more strings).
+ */
+static void
+cmd_txrst(CMD_ARGS)
+{
+ struct stream *s;
+ char *p;
+ uint32_t err;
+ struct frame f;
+ (void)cmd;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+
+ INIT_FRAME(f, RST_STREAM, 4, s->id, 0);
+
+ while (*++av) {
+ if (!strcmp(*av, "-err")) {
+ ++av;
+ for (err=0; h2_errs[err]; err++) {
+ if (!strcmp(h2_errs[err], *av))
+ break;
+ }
+
+ if (h2_errs[err])
+ continue;
+
+ STRTOU32(err, *av, p, vl, "-err");
+ } else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown txrst spec: %s\n", *av);
+
+ err = htonl(err);
+ f.data = (void *)&err;
+ write_frame(s->hp, &f, 1);
+}
+
+/* SECTION: stream.spec.prio_txprio txprio
+ *
+ * Send a PRIORITY frame
+ *
+ * \-stream INT
+ * indicate the id of the stream the sender stream depends on.
+ *
+ * \-ex
+ * the dependency should be made exclusive (only this streams depends on
+ * the parent stream).
+ *
+ * \-weight INT
+ * an 8-bits integer is used to balance priority between streams
+ * depending on the same streams.
+ */
+static void
+cmd_txprio(CMD_ARGS)
+{
+ struct stream *s;
+ char *p;
+ uint32_t stid = 0;
+ struct frame f;
+ uint32_t weight = 0;
+ uint32_t exclusive = 0;
+ uint8_t buf[5];
+
+ (void)cmd;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+
+ INIT_FRAME(f, PRIORITY, 5, s->id, 0);
+ f.data = (void *)buf;
+
+ while (*++av) {
+ if (!strcmp(*av, "-stream")) {
+ STRTOU32_CHECK(stid, av, p, vl, "-stream", 0);
+ } else if (!strcmp(*av, "-ex")) {
+ exclusive = 1U << 31;
+ } else if (!strcmp(*av, "-weight")) {
+ STRTOU32_CHECK(weight, av, p, vl, "-weight", 8);
+ } else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown txprio spec: %s\n", *av);
+ s->weight = weight & 0xff;
+ s->dependency = stid;
+
+ if(exclusive)
+ exclusive_stream_dependency(s);
+
+ vbe32enc(buf, (stid | exclusive));
+ buf[4] = s->weight;
+ write_frame(s->hp, &f, 1);
+}
+
+#define PUT_KV(av, vl, name, val, code) \
+ do {\
+ STRTOU32_CHECK(val, av, p, vl, #name, 0); \
+ vbe16enc(cursor, code); \
+ cursor += sizeof(uint16_t); \
+ vbe32enc(cursor, val); \
+ cursor += sizeof(uint32_t); \
+ f.size += 6; \
+ } while(0)
+
+/* SECTION: stream.spec.settings_txsettings txsettings
+ *
+ * SETTINGS frames must be acknowledge, arguments are as follow (most of them
+ * are from rfc7540#6.5.2):
+ *
+ * \-hdrtbl INT
+ * headers table size
+ *
+ * \-push BOOL
+ * whether push frames are accepted or not
+ *
+ * \-maxstreams INT
+ * maximum concurrent streams allowed
+ *
+ * \-winsize INT
+ * sender's initial window size
+ *
+ * \-framesize INT
+ * largest frame size authorized
+ *
+ * \-hdrsize INT
+ * maximum size of the header list authorized
+ *
+ * \-ack
+ * set the ack bit
+ */
+static void
+cmd_txsettings(CMD_ARGS)
+{
+ struct stream *s, *_s;
+ struct http *hp;
+ char *p;
+ uint32_t val = 0;
+ struct frame f;
+ //TODO dynamic alloc
+ char buf[512];
+ char *cursor = buf;
+
+ (void)cmd;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);
+
+ memset(buf, 0, 512);
+ INIT_FRAME(f, SETTINGS, 0, s->id, 0);
+ f.data = buf;
+
+ AZ(pthread_mutex_lock(&hp->mtx));
+ while (*++av) {
+ if (!strcmp(*av, "-push")) {
+ ++av;
+ vbe16enc(cursor, 0x2);
+ cursor += sizeof(uint16_t);
+ if (!strcmp(*av, "false"))
+ vbe32enc(cursor, 0);
+ else if (!strcmp(*av, "true"))
+ vbe32enc(cursor, 1);
+ else
+ vtc_log(vl, 0, "Push parameter is either "
+ "\"true\" or \"false\", not %s",
+ *av);
+ cursor += sizeof(uint32_t);
+ f.size += 6;
+ }
+ else if (!strcmp(*av, "-hdrtbl")) {
+ PUT_KV(av, vl, hdrtbl, val, 0x1);
+ HPK_ResizeTbl(s->hp->decctx, val);
+ }
+ else if (!strcmp(*av, "-maxstreams")) {
+ PUT_KV(av, vl, maxstreams, val, 0x3);
+ }
+ else if (!strcmp(*av, "-winsize")) {
+ PUT_KV(av, vl, winsize, val, 0x4);
+ VTAILQ_FOREACH(_s, &hp->streams, list)
+ _s->ws += (val - hp->iws);
+ hp->iws = val;
+ }
+ else if (!strcmp(*av, "-framesize")) {
+ PUT_KV(av, vl, framesize, val, 0x5);
+ }
+ else if (!strcmp(*av, "-hdrsize")){
+ PUT_KV(av, vl, hdrsize, val, 0x6);
+ }
+ else if (!strcmp(*av, "-ack")) {
+ f.flags |= 1;
+ } else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown txsettings spec: %s\n", *av);
+
+ write_frame(hp, &f, 0);
+ AZ(pthread_mutex_unlock(&hp->mtx));
+}
+
+/* SECTION: stream.spec.ping_txping txping
+ *
+ * Send PING frame.
+ *
+ * \-data STRING
+ * specify the payload of the frame, with STRING being an 8-char string.
+ *
+ * \-ack
+ * set the ACK flag.
+ */
+static void
+cmd_txping(CMD_ARGS)
+{
+ struct stream *s;
+ struct frame f;
+ char buf[8];
+
+ (void)cmd;
+ memset(buf, 0, 8);
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+ INIT_FRAME(f, PING, 8, s->id, 0);
+
+ while (*++av) {
+ if (!strcmp(*av, "-data")) {
+ av++;
+ if (f.data)
+ vtc_log(vl, 0, "this frame already has data");
+ if (strlen(*av) != 8)
+ vtc_log(vl, 0, "data must be a 8-char string, found (%s)", *av);
+ f.data = *av;
+ } else if (!strcmp(*av, "-ack")) {
+ f.flags |= 1;
+ } else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown txping spec: %s\n", *av);
+ if (!f.data)
+ f.data = buf;
+ write_frame(s->hp, &f, 1);
+}
+
+/*
+ * SECTION: stream.spec.goaway_txgoaway rxgoaway
+ *
+ * Possible options include:
+ *
+ * \-err STRING|INT
+ * set the error code to eplain the termination. The second argument
+ * can be a integer or the string version of the error code as found
+ * in rfc7540#7.
+ *
+ * \-laststream INT
+ * the id of the "highest-numbered stream identifier for which the
+ * sender of the GOAWAY frame might have taken some action on or might
+ * yet take action on".
+ *
+ * \-debug
+ * specify the debug data, if any to append to the frame.
+ */
+static void
+cmd_txgoaway(CMD_ARGS)
+{
+ struct stream *s;
+ char *p;
+ uint32_t err = 0;
+ uint32_t ls = 0;
+ struct frame f;
+ char buf[8];
+
+ (void)cmd;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+ memset(buf, 0, 8);
+
+ INIT_FRAME(f, GOAWAY, 8, s->id, 0);
+
+ while (*++av) {
+ if (!strcmp(*av, "-err")) {
+ ++av;
+ for (err=0; h2_errs[err]; err++) {
+ if (!strcmp(h2_errs[err], *av))
+ break;
+ }
+
+ if (h2_errs[err])
+ continue;
+
+ STRTOU32(err, *av, p, vl, "-err");
+ } else if (!strcmp(*av, "-laststream")) {
+ STRTOU32_CHECK(ls, av, p, vl, "-laststream", 31);
+ } else if (!strcmp(*av, "-debug")) {
+ ++av;
+ if (f.data)
+ vtc_log(vl, 0, "this frame already has debug data");
+ f.size = 8 + strlen(*av);
+ f.data = malloc(f.size);
+ memcpy(f.data + 8, *av, f.size - 8);
+ } else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown txgoaway spec: %s\n", *av);
+
+ if (!f.data)
+ f.data = malloc(2);
+ vbe32enc(f.data, ls);
+ vbe32enc(f.data + 4, err);
+ write_frame(s->hp, &f, 1);
+ free(f.data);
+}
+
+/* SECTION: stream.spec.winup_txwinup txwinup
+ *
+ * Transmit a WINDOW_UPDATE frame, increasing the amount of credit of the
+ * connection (from stream 0) or of the stream (any other stream).
+ *
+ * \-size INT
+ * give INT credits to the peer.
+ */
+static void
+cmd_txwinup(CMD_ARGS)
+{
+ struct http *hp;
+ struct stream *s;
+ char *p;
+ struct frame f;
+ char buf[8];
+ uint32_t size = 0;
+
+ (void)cmd;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+ CAST_OBJ_NOTNULL(hp, s->hp, HTTP_MAGIC);
+ memset(buf, 0, 8);
+
+ AN(av[1]);
+ AN(av[2]);
+
+ INIT_FRAME(f, WINDOW_UPDATE, 4, s->id, 0);
+ f.data = buf;
+
+ while (*++av) {
+ if (!strcmp(*av, "-size")) {
+ STRTOU32_CHECK(size, av, p, vl, "-size", 0);
+ } else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown txwinup spec: %s\n", *av);
+
+ AZ(pthread_mutex_lock(&hp->mtx));
+ if (s->id == 0)
+ hp->ws += size;
+ s->ws += size;
+ AZ(pthread_mutex_unlock(&hp->mtx));
+
+ size = htonl(size);
+ f.data = (void *)&size;
+ write_frame(hp, &f, 1);
+}
+
+static struct frame *
+rxstuff(struct stream *s) {
+ struct frame *f;
+
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+
+ AZ(pthread_mutex_lock(&s->hp->mtx));
+ s->hp->wf++;
+ if (VTAILQ_EMPTY(&s->fq)) {
+ AZ(pthread_cond_signal(&s->hp->cond));
+ AZ(pthread_cond_wait(&s->cond, &s->hp->mtx));
+ }
+ if (VTAILQ_EMPTY(&s->fq)) {
+ AZ(pthread_mutex_unlock(&s->hp->mtx));
+ return (NULL);
+ }
+ clean_frame(&s->frame);
+ f = VTAILQ_LAST(&s->fq, fq_head);
+ VTAILQ_REMOVE(&s->fq, f, list);
+ AZ(pthread_mutex_unlock(&s->hp->mtx));
+
+ CHECK_OBJ_NOTNULL(f, FRAME_MAGIC);
+ return (f);
+}
+
+#define CHKFRAME(rt, wt, rcv, func) \
+ do { \
+ if (rt != wt) \
+ vtc_log(vl, 0, "Frame #%d for %s was of type %s (%d) " \
+ "instead of %s (%d)", \
+ rcv, func, \
+ rt < TYPE_MAX ? h2_types[rt] : "?", rt, \
+ wt < TYPE_MAX ? h2_types[wt] : "?", wt); \
+ } while (0);
+
+/* SECTION: stream.spec.data_12 rxhdrs
+ *
+ * ``rxhdrs`` will expect one HEADER frame, then, depending on the arguments,
+ * zero or more CONTINUATION frame.
+ *
+ * \-all
+ * Keep waiting for CONTINUATION frames until END_HEADERS flag is seen.
+ *
+ * \-some INT
+ * Retrieve INT - 1 CONTINUATION frames after the HEADER frame.
+ *
+ */
+static void
+cmd_rxhdrs(CMD_ARGS)
+{
+ struct stream *s;
+ struct frame *f = NULL;
+ char *p;
+ int loop = 0;
+ int times = 1;
+ int rcv = 0;
+ enum h2_type expect = TYPE_HEADERS;
+
+ (void)cmd;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+
+ while (*++av) {
+ if (!strcmp(*av, "-some")) {
+ STRTOU32_CHECK(times, av, p, vl, "-some", 0);
+ AN(times);
+ } else if (!strcmp(*av, "-all")) {
+ loop = 1;
+ } else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown rxhdrs spec: %s\n", *av);
+
+ while (rcv++ < times || (loop && !(f->flags & END_HEADERS))) {
+ f = rxstuff(s);
+ if (!f)
+ return;
+ CHKFRAME(f->type, expect, rcv, "rxhdrs");
+ expect = TYPE_CONTINUATION;
+ }
+ s->frame = f;
+}
+
+static void
+cmd_rxcont(CMD_ARGS)
+{
+ struct stream *s;
+ struct frame *f = NULL;
+ char *p;
+ int loop = 0;
+ int times = 1;
+ int rcv = 0;
+
+ (void)cmd;
+ (void)av;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+
+ while (*++av) {
+ if (!strcmp(*av, "-some")) {
+ STRTOU32_CHECK(times, av, p, vl, "-some", 0);
+ } else if (!strcmp(*av, "-all")) {
+ loop = 1;
+ } else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown rxcont spec: %s\n", *av);
+
+ while (rcv++ < times || (loop && !(f->flags & END_HEADERS))) {
+ f = rxstuff(s);
+ if (!f)
+ return;
+ CHKFRAME(f->type, TYPE_CONTINUATION, rcv, "rxcont");
+ }
+ s->frame = f;
+}
+
+
+/* SECTION: stream.spec.data_13 rxdata
+ *
+ * Receiving data is done using the ``rxdata`` keywords and will retrieve one
+ * DATA frame, if you wish to receive more, you can use these two convenience
+ * arguments:
+ *
+ * \-all
+ * keep waiting for DATA frame until one sets the END_STREAM flag
+ *
+ * \-some INT
+ * retrieve INT DATA frames.
+ *
+ */
+static void
+cmd_rxdata(CMD_ARGS)
+{
+ struct stream *s;
+ struct frame *f = NULL;
+ char *p;
+ int loop = 0;
+ int times = 1;
+ int rcv = 0;
+
+ (void)cmd;
+ (void)av;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+
+ while (*++av) {
+ if (!strcmp(*av, "-some")) {
+ STRTOU32_CHECK(times, av, p, vl, "-some", 0);
+ } else if (!strcmp(*av, "-all")) {
+ loop = 1;
+ } else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown rxdata spec: %s\n", *av);
+
+ while (rcv++ < times || (loop && !(f->flags & END_STREAM))) {
+ f = rxstuff(s);
+ if (!f)
+ return;
+ CHKFRAME(f->type, TYPE_DATA, rcv, "rxhdata");
+ }
+ s->frame = f;
+}
+
+/* SECTION: stream.spec.data_10 rxreq, rxresp
+ *
+ * These are two convenience functions to receive headers and body of an
+ * incoming request or response. The only difference is that rxreq can only be
+ * by a server, and rxresp by a client.
+ *
+ */
+static void
+cmd_rxreqsp(CMD_ARGS)
+{
+ struct stream *s;
+ struct frame *f;
+ int end_stream;
+ int rcv = 0;
+
+ (void)cmd;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+
+ if (!strcmp(av[0], "rxreq"))
+ ONLY_SERVER(s->hp, av);
+ else
+ ONLY_CLIENT(s->hp, av);
+
+ f = rxstuff(s);
+ if (!f)
+ return;
+
+ rcv++;
+ CHKFRAME(f->type, TYPE_HEADERS, rcv, *av);
+
+ end_stream = f->flags & END_STREAM;
+
+ while (!(f->flags & END_HEADERS)) {
+ f = rxstuff(s);
+ if (!f)
+ return;
+ rcv++;
+ CHKFRAME(f->type, TYPE_CONTINUATION, rcv, *av);
+ }
+
+ while (!end_stream && (f = rxstuff(s))) {
+ rcv++;
+ CHKFRAME(f->type, TYPE_DATA, rcv, *av);
+ end_stream = f->flags & END_STREAM;
+ }
+ s->frame = f;
+}
+
+/* SECTION: stream.spec.data_11 rxpush
+ *
+ * This works like ``rxhdrs``, expecting a PUSH frame and then zero or more
+ * CONTINUATION frames.
+ *
+ * \-all
+ * Keep waiting for CONTINUATION frames until END_HEADERS flag is seen.
+ *
+ * \-some INT
+ * Retrieve INT - 1 CONTINUATION frames after the PUSH frame.
+ *
+ */
+static void
+cmd_rxpush(CMD_ARGS) {
+ struct stream *s;
+ struct frame *f = NULL;
+ char *p;
+ int loop = 0;
+ int times = 1;
+ int rcv = 0;
+ enum h2_type expect = TYPE_PUSH_PROMISE;
+
+ (void)cmd;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+
+ while (*++av) {
+ if (!strcmp(*av, "-some")) {
+ STRTOU32_CHECK(times, av, p, vl, "-some", 0);
+ AN(times);
+ } else if (!strcmp(*av, "-all")) {
+ loop = 1;
+ } else
+ break;
+ }
+ if (*av != NULL)
+ vtc_log(vl, 0, "Unknown rxpush spec: %s\n", *av);
+
+ while (rcv++ < times || (loop && !(f->flags & END_HEADERS))) {
+ f = rxstuff(s);
+ if (!f)
+ return;
+ CHKFRAME(f->type, expect, rcv, "rxpush");
+ expect = TYPE_CONTINUATION;
+ }
+ s->frame = f;
+}
+
+#define RXFUNC(lctype, upctype) \
+ static void \
+ cmd_rx ## lctype(CMD_ARGS) { \
+ struct stream *s; \
+ (void)cmd; \
+ (void)av; \
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC); \
+ if ((s->frame = rxstuff(s))) \
+ return; \
+ if (s->frame->type != TYPE_ ## upctype) \
+ vtc_log(vl, 0, "Received frame of type %d " \
+ "is invalid for %s", \
+ s->frame->type, "rx ## lctype"); \
+ }
+
+/* SECTION: stream.spec.prio_rxprio rxprio
+ *
+ * Receive a PRIORITY frame
+ */
+RXFUNC(prio, PRIORITY)
+
+/* SECTION: stream.spec.reset_rxrst rxrst
+ *
+ * Receive a RST_STREAM frame
+ */
+RXFUNC(rst, RST_STREAM)
+
+/* SECTION: stream.spec.settings_rxsettings rxsettings
+ *
+ * Receive a SETTINGS frame
+ */
+RXFUNC(settings,SETTINGS)
+
+/* SECTION: stream.spec.ping_rxping rxping
+ *
+ * Receive a PING frame
+ */
+RXFUNC(ping, PING)
+
+/* SECTION: stream.spec.goaway_rxgoaway rxgoaway
+ *
+ * Receive a GOAWAY frame
+ */
+RXFUNC(goaway, GOAWAY)
+
+/* SECTION: stream.spec.winup_rxwinup rxwinup
+ *
+ * Receive a WINDOW_UPDATE frame
+ */
+RXFUNC(winup, WINDOW_UPDATE)
+
+/* SECTION: stream.spec.frame_rxframe
+ *
+ * Receive a frame, any frame.
+ */
+static void
+cmd_rxframe(CMD_ARGS) {
+ struct stream *s;
+ (void)cmd;
+ (void)vl;
+ (void)av;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+ rxstuff(s);
+}
+
+
+
+static void
+cmd_http_expect(CMD_ARGS)
+{
+ struct http *hp;
+ struct stream *s;
+ const char *lhs, *clhs;
+ char *cmp;
+ const char *rhs, *crhs;
+ vre_t *vre;
+ const char *error;
+ int erroroffset;
+ int i, retval = -1;
+ char buf[20];
+
+ (void)cmd;
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+ hp = s->hp;
+ CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
+
+ AZ(strcmp(av[0], "expect"));
+ av++;
+
+ AN(av[0]);
+ AN(av[1]);
+ AN(av[2]);
+ AZ(av[3]);
+ AZ(pthread_mutex_lock(&s->hp->mtx));
+ lhs = cmd_var_resolve(s, av[0], buf);
+ cmp = av[1];
+ rhs = cmd_var_resolve(s, av[2], buf);
+
+ clhs = lhs ? lhs : "<undef>";
+ crhs = rhs ? rhs : "<undef>";
+
+ if (!strcmp(cmp, "~") || !strcmp(cmp, "!~")) {
+ vre = VRE_compile(crhs, 0, &error, &erroroffset);
+ if (vre == NULL)
+ vtc_log(vl, 0, "REGEXP error: %s (@%d) (%s)",
+ error, erroroffset, crhs);
+ i = VRE_exec(vre, clhs, strlen(clhs), 0, 0, NULL, 0, 0);
+ retval = (i >= 0 && *cmp == '~') || (i < 0 && *cmp == '!');
+ VRE_free(&vre);
+ } else if (!strcmp(cmp, "==")) {
+ retval = strcmp(clhs, crhs) == 0;
+ } else if (!strcmp(cmp, "!=")) {
+ retval = strcmp(clhs, crhs) != 0;
+ } else if (lhs == NULL || rhs == NULL) {
+ // fail inequality comparisons if either side is undef'ed
+ retval = 0;
+ } else if (!strcmp(cmp, "<")) {
+ retval = isless(VNUM(lhs), VNUM(rhs));
+ } else if (!strcmp(cmp, ">")) {
+ retval = isgreater(VNUM(lhs), VNUM(rhs));
+ } else if (!strcmp(cmp, "<=")) {
+ retval = islessequal(VNUM(lhs), VNUM(rhs));
+ } else if (!strcmp(cmp, ">=")) {
+ retval = isgreaterequal(VNUM(lhs), VNUM(rhs));
+ }
+
+ if (retval == -1)
+ vtc_log(vl, 0,
+ "EXPECT %s (%s) %s %s (%s) test not implemented",
+ av[0], clhs, av[1], av[2], crhs);
+ else
+ vtc_log(vl, retval ? 4 : 0, "(s%d) EXPECT %s (%s) %s \"%s\" %s",
+ s->id, av[0], clhs, cmp, crhs, retval ? "match" : "failed");
+ AZ(pthread_mutex_unlock(&s->hp->mtx));
+}
+
+/* SECTION: stream.spec Specification
+ *
+ * The specification of a stream follows the exact same rules as one for a
+ * client or a server.
+ */
+static const struct cmds stream_cmds[] = {
+ { "expect", cmd_http_expect },
+ { "sendhex", cmd_sendhex },
+ { "rxframe", cmd_rxframe },
+ { "txdata", cmd_txdata },
+ { "rxdata", cmd_rxdata },
+ { "rxhdrs", cmd_rxhdrs },
+ { "txreq", cmd_tx11obj },
+ { "rxreq", cmd_rxreqsp },
+ { "txresp", cmd_tx11obj },
+ { "rxresp", cmd_rxreqsp },
+ { "txprio", cmd_txprio },
+ { "rxprio", cmd_rxprio },
+ { "txrst", cmd_txrst },
+ { "rxrst", cmd_rxrst },
+ { "txsettings", cmd_txsettings },
+ { "rxsettings", cmd_rxsettings },
+ { "txpush", cmd_tx11obj },
+ { "rxpush", cmd_rxpush },
+ { "txping", cmd_txping },
+ { "rxping", cmd_rxping },
+ { "txgoaway", cmd_txgoaway },
+ { "rxgoaway", cmd_rxgoaway },
+ { "txwinup", cmd_txwinup },
+ { "rxwinup", cmd_rxwinup },
+ { "txcont", cmd_tx11obj },
+ { "rxcont", cmd_rxcont },
+ { "delay", cmd_delay },
+ { "barrier", cmd_barrier },
+ { NULL, NULL }
+};
+
+static void *
+stream_thread(void *priv)
+{
+ struct stream *s;
+
+ CAST_OBJ_NOTNULL(s, priv, STREAM_MAGIC);
+
+ parse_string(s->spec, stream_cmds, s, s->hp->vl);
+
+ clean_headers(s->req);
+ clean_headers(s->resp);
+ vtc_log(s->hp->vl, 2, "Ending stream %u", s->id);
+ return (NULL);
+}
+/**********************************************************************
+ * Allocate and initialize a stream
+ */
+
+static struct stream *
+stream_new(const char *name, struct http *h)
+{
+ char *p;
+ struct stream *s;
+
+ ALLOC_OBJ(s, STREAM_MAGIC);
+ AN(s);
+ pthread_cond_init(&s->cond, NULL);
+ REPLACE(s->name, name);
+ AN(name);
+ VTAILQ_INIT(&s->fq);
+ s->ws = h->iws;
+
+ s->weight = 16;
+ s->dependency = 0;
+
+ STRTOU32(s->id, name, p, h->vl, "stream");
+ if (s->id & (1 << 31))
+ vtc_log(h->vl, 0, "Stream id must be a 31-bits integer "
+ "(found %s)", name);
+
+ CHECK_OBJ_NOTNULL(h, HTTP_MAGIC);
+ s->hp = h;
+
+ //bprintf(s->connect, "%s", "${v1_sock}");
+ AZ(pthread_mutex_lock(&h->mtx));
+ VTAILQ_INSERT_HEAD(&h->streams, s, list);
+ AZ(pthread_mutex_unlock(&h->mtx));
+ return (s);
+}
+
+/**********************************************************************
+ * Clean up stream
+ */
+
+static void
+stream_delete(struct stream *s)
+{
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ free(s->spec);
+ free(s->name);
+ FREE_OBJ(s);
+}
+
+/**********************************************************************
+ * Start the stream thread
+ */
+
+static void
+stream_start(struct stream *s)
+{
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ vtc_log(s->hp->vl, 2, "Starting stream %p", s);
+ AZ(pthread_create(&s->tp, NULL, stream_thread, s));
+ s->running = 1;
+}
+
+/**********************************************************************
+ * Wait for stream thread to stop
+ */
+static void
+stream_wait(struct stream *s)
+{
+ void *res;
+ struct frame *f, *f2;
+
+ CHECK_OBJ_NOTNULL(s, STREAM_MAGIC);
+ vtc_log(s->hp->vl, 2, "Waiting for stream %u", s->id);
+ AZ(pthread_join(s->tp, &res));
+ if (res != NULL)
+ vtc_log(s->hp->vl, 0, "Stream %u returned \"%s\"", s->id,
+ (char *)res);
+
+ VTAILQ_FOREACH_SAFE(f, &s->fq, list, f2)
+ clean_frame(&f);
+ clean_frame(&s->frame);
+ s->tp = 0;
+ s->running = 0;
+}
+
+/**********************************************************************
+ * Run the stream thread
+ */
+
+static void
+stream_run(struct stream *s)
+{
+ stream_start(s);
+ stream_wait(s);
+}
+
+
+
+/* SECTION: client-server.spec.zstream stream
+ *
+ * H/2 introduces the concept of streams, and these come with their own
+ * specification, and as it's quite big, have bee move to their own chapter.
+ *
+ * SECTION: stream stream
+ *
+ * (note: this section is at the top-level for easier navigation, but it's part
+ * of the client/server specification)
+ *
+ * Streams map roughly to a request in H/2, a request is sent on stream N,
+ * the response too, then the stream is discarded. The main exception is the
+ * first stream, 0, that serves as coordinator.
+ *
+ * Stream syntax follow the client/server one::
+ *
+ * stream ID [SPEC] [ACTION]
+ *
+ * ID is the H/2 stream number, while SPEC describes what will be done in that
+ * stream.
+ *
+ * Note that, when parsing a stream action, if the entity isn't operating in H/2
+ * mode, these spec is ran before::
+ *
+ * txpri/rxpri # client/server
+ * stream 0 {
+ * txsettings
+ * rxsettings
+ * txsettings -ack
+ * rxsettings
+ * expect settings.ack == true
+ * } -run
+ *
+ * And H/2 mode is then activated before parsing the specification.
+ *
+ * SECTION: stream.actions Actions
+ *
+ * \-start
+ * Run the specification in a thread, giving back control immediately.
+ *
+ * \-wait
+ * Wait for the started thread to finish running the spec.
+ *
+ * \-run
+ * equivalent to calling ``-start`` then ``-wait``.
+ */
+
+void
+cmd_stream(CMD_ARGS)
+{
+ struct stream *s;
+ struct http *h;
+
+ (void)cmd;
+ (void)vl;
+ CAST_OBJ_NOTNULL(h, priv, HTTP_MAGIC);
+
+ AZ(strcmp(av[0], "stream"));
+ av++;
+
+ VTAILQ_FOREACH(s, &h->streams, list)
+ if (!strcmp(s->name, av[0]))
+ break;
+ if (s == NULL)
+ s = stream_new(av[0], h);
+ av++;
+
+ for (; *av != NULL; av++) {
+ if (vtc_error)
+ break;
+
+ if (!strcmp(*av, "-wait")) {
+ stream_wait(s);
+ continue;
+ }
+
+ /* Don't muck about with a running client */
+ if (s->running)
+ stream_wait(s);
+
+ if (!strcmp(*av, "-start")) {
+ stream_start(s);
+ continue;
+ }
+ if (!strcmp(*av, "-run")) {
+ stream_run(s);
+ continue;
+ }
+ if (**av == '-')
+ vtc_log(vl, 0, "Unknown client argument: %s", *av);
+ REPLACE(s->spec, *av);
+ }
+}
+
+void
+b64_settings(const struct http *hp, const char *s)
+{
+ uint16_t i;
+ uint64_t v;
+ const char *buf;
+ int shift;
+ while (*s) {
+ v = 0;
+ for (shift = 42; shift >= 0; shift -= 6) {
+ if (*s >= 'A' && *s <= 'Z') {
+ v |= (uint64_t)(*s - 'A') << shift;
+ } else if (*s >= 'a' && *s <= 'z') {
+ v |= (uint64_t)(*s - 'a' + 26) << shift;
+ } else if (*s >= '0' && *s <= '9') {
+ v |= (uint64_t)(*s - '0' + 52) << shift;
+ } else if (*s == '-')
+ v |= (uint64_t)62 << shift;
+ else if (*s == '_') {
+ v |= (uint64_t)63 << shift;
+ } else
+ vtc_log(hp->vl, 0, "Bad \"HTTP2-Settings\" "
+ "header");
+ s++;
+ }
+ i = v >> 32;
+ v &= 0xffff;
+
+ if (i <= SETTINGS_MAX) {
+ buf = h2_settings[i];
+ } else
+ buf = "unknown";
+
+ if (v == 1 ) {
+ if (hp->sfd)
+ HPK_ResizeTbl(hp->encctx, v);
+ else
+ HPK_ResizeTbl(hp->decctx, v);
+ }
+
+ vtc_log(hp->vl, 4, "Upgrade: %s (%d): %ld", buf, i, v);
+ }
+}
+
+void
+start_h2(struct http *hp)
+{
+ CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
+ AZ(pthread_mutex_init(&hp->mtx, NULL));
+ AZ(pthread_cond_init(&hp->cond, NULL));
+ VTAILQ_INIT(&hp->streams);
+ hp->iws = 0xffff;
+ hp->ws = 0xffff;
+
+ hp->h2 = 1;
+
+ hp->decctx = HPK_NewCtx(4096);
+ hp->encctx = HPK_NewCtx(4096);
+ AZ(pthread_create(&hp->tp, NULL, receive_frame, hp));
+}
+
+void
+stop_h2(struct http *hp)
+{
+ struct stream *s, *s2;
+
+ CHECK_OBJ_NOTNULL(hp, HTTP_MAGIC);
+ VTAILQ_FOREACH_SAFE(s, &hp->streams, list, s2) {
+ if (s->running)
+ stream_wait(s);
+ AZ(pthread_mutex_lock(&hp->mtx));
+ VTAILQ_REMOVE(&hp->streams, s, list);
+ AZ(pthread_mutex_unlock(&hp->mtx));
+ stream_delete(s);
+ }
+
+ AZ(pthread_mutex_lock(&hp->mtx));
+ hp->h2 = 0;
+ AZ(pthread_cond_signal(&hp->cond));
+ AZ(pthread_mutex_unlock(&hp->mtx));
+ AZ(pthread_join(hp->tp, NULL));
+
+ HPK_FreeCtx(hp->decctx);
+ HPK_FreeCtx(hp->encctx);
+
+ AZ(pthread_mutex_destroy(&hp->mtx));
+ AZ(pthread_cond_destroy(&hp->cond));
+}
diff --git a/bin/varnishtest/vtc_server.c b/bin/varnishtest/vtc_server.c
index 21fe298..22b9849 100644
--- a/bin/varnishtest/vtc_server.c
+++ b/bin/varnishtest/vtc_server.c
@@ -38,7 +38,6 @@
#include "vtc.h"
-#include "vss.h"
#include "vtcp.h"
struct server {
diff --git a/doc/sphinx/Makefile.am b/doc/sphinx/Makefile.am
index f929a6f..c88b46b 100644
--- a/doc/sphinx/Makefile.am
+++ b/doc/sphinx/Makefile.am
@@ -179,6 +179,7 @@ BUILT_SOURCES += include/vsl-tags.rst
VTCSYN_SRC = $(top_srcdir)/bin/varnishtest/vtc.c \
$(top_srcdir)/bin/varnishtest/vtc_barrier.c \
$(top_srcdir)/bin/varnishtest/vtc_http.c \
+ $(top_srcdir)/bin/varnishtest/vtc_http2.c \
$(top_srcdir)/bin/varnishtest/vtc_logexp.c \
$(top_srcdir)/bin/varnishtest/vtc_varnish.c
include/vtc-syntax.rst: vtc-syntax.py $(VTCSYN_SRC)
diff --git a/doc/sphinx/vtc-syntax.py b/doc/sphinx/vtc-syntax.py
index 45a5102..298f4bb 100644
--- a/doc/sphinx/vtc-syntax.py
+++ b/doc/sphinx/vtc-syntax.py
@@ -34,13 +34,10 @@ import sys
import re
-def parse_file(fn):
+def parse_file(fn, cl, tl, sl):
p = False
section = ""
resec = re.compile("[ /]\* SECTION: ")
- cl = {}
- tl = {}
- sl = []
f = open(fn, "r")
@@ -63,6 +60,12 @@ def parse_file(fn):
cl[section].append(re.sub(r"^ \* ?", "", l))
f.close()
+if __name__ == "__main__":
+ cl = {}
+ tl = {}
+ sl = []
+ for fn in sys.argv[1:]:
+ parse_file(fn, cl, tl, sl)
sl.sort()
for section in sl:
print(tl[section], end="")
@@ -72,11 +75,10 @@ def parse_file(fn):
r = "-"
elif c == 1:
r = "~"
- else:
+ elif c == 2:
r = "."
+ else:
+ r = "*"
print(re.sub(r".", r, tl[section]), end="")
print("".join(cl[section]))
-if __name__ == "__main__":
- for fn in sys.argv[1:]:
- parse_file(fn)
More information about the varnish-commit
mailing list