Commit 66d26826 authored by Poul-Henning Kamp's avatar Poul-Henning Kamp

Add our skeleton HTTP2 implementation, which seems to survive

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