HPACK Header Compression HTTP/2 decompression support
authorRémi Denis-Courmont <remi@remlab.net>
Wed, 4 Mar 2015 18:01:40 +0000 (20:01 +0200)
committerRémi Denis-Courmont <remi@remlab.net>
Sun, 13 Dec 2015 14:42:43 +0000 (16:42 +0200)
modules/Makefile.am
modules/access/http/Makefile.am [new file with mode: 0644]
modules/access/http/hpack.c [new file with mode: 0644]
modules/access/http/hpack.h [new file with mode: 0644]

index 2022568..e6de1d0 100644 (file)
@@ -21,6 +21,7 @@ EXTRA_LTLIBRARIES =
 
 include common.am
 include access/Makefile.am
+include access/http/Makefile.am
 include access/rtp/Makefile.am
 include arm_neon/Makefile.am
 include audio_filter/Makefile.am
diff --git a/modules/access/http/Makefile.am b/modules/access/http/Makefile.am
new file mode 100644 (file)
index 0000000..9a0c96a
--- /dev/null
@@ -0,0 +1,4 @@
+hpack_test_SOURCES = access/http/hpack.c access/http/hpack.h
+hpack_test_CFLAGS = -DDEC_TEST
+check_PROGRAMS += hpack_test
+TESTS += hpack_test
diff --git a/modules/access/http/hpack.c b/modules/access/http/hpack.c
new file mode 100644 (file)
index 0000000..8e987fe
--- /dev/null
@@ -0,0 +1,845 @@
+/*****************************************************************************
+ * hpack.c: HPACK Header Compression for HTTP/2
+ *****************************************************************************
+ * Copyright (C) 2015 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#ifdef DEC_TEST
+# undef NDEBUG
+#endif
+
+#include <assert.h>
+#include <errno.h>
+#include <limits.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "hpack.h"
+
+/** Static Table header names */
+static const char hpack_names[][28] =
+{
+    ":authority", ":method", ":method", ":path", ":path", ":scheme", ":scheme",
+    ":status", ":status", ":status", ":status", ":status", ":status",
+    ":status", "accept-charset", "accept-encoding", "accept-language",
+    "accept-ranges", "accept", "access-control-allow-origin", "age", "allow",
+    "authorization", "cache-control", "content-disposition",
+    "content-encoding", "content-language", "content-length",
+    "content-location", "content-range", "content-type", "cookie", "date",
+    "etag", "expect", "expires", "from", "host", "if-match",
+    "if-modified-since", "if-none-match", "if-range", "if-unmodified-since",
+    "last-modified", "link", "location", "max-forwards", "proxy-authenticate",
+    "proxy-authorization", "range", "referer", "refresh", "retry-after",
+    "server", "set-cookie", "strict-transport-security", "transfer-encoding",
+    "user-agent", "vary", "via", "www-authenticate",
+};
+
+/** Static Table header values */
+static const char hpack_values[][14] =
+{
+    "", "GET", "POST", "/", "/index.html", "http", "https", "200", "204",
+    "206", "304", "400", "404", "500", "", "gzip, deflate"
+};
+
+struct hpack_decoder
+{
+    char **table;
+    size_t entries;
+    size_t size;
+    size_t max_size;
+};
+
+struct hpack_decoder *hpack_decode_init(size_t header_table_size)
+{
+    struct hpack_decoder *dec = malloc(sizeof (*dec));
+    if (dec == NULL)
+        return NULL;
+
+    dec->table = NULL;
+    dec->entries = 0;
+    dec->size = 0;
+    dec->max_size = header_table_size;
+    return dec;
+}
+
+void hpack_decode_destroy(struct hpack_decoder *dec)
+{
+    for (unsigned i = 0; i < dec->entries; i++)
+        free(dec->table[i]);
+    free(dec->table);
+    free(dec);
+}
+
+/**
+ * Decodes an HPACK unsigned variable length integer.
+ * @return the value on success, -1 on error (and sets errno).
+ */
+static int_fast32_t hpack_decode_int(unsigned n,
+                                     const uint8_t **restrict datap,
+                                     size_t *restrict lengthp)
+{
+    const uint8_t *p = *datap;
+    size_t length = *lengthp;
+
+    assert(n >= 1 && n <= 8);
+    assert(length >= 1);
+
+    unsigned mask = (1 << n) - 1;
+    uint_fast32_t i = *(p++) & mask;
+    length--;
+
+    if (i == mask)
+    {
+        unsigned shift = 0;
+        uint8_t b;
+
+        do
+        {
+            if (length-- < 1)
+            {
+                errno = EINVAL;
+                return -1;
+            }
+
+            if (shift >= 28)
+            {
+                errno = ERANGE;
+                return -1;
+            }
+
+            b = *(p++);
+            i += (b & 0x7F) << shift;
+            shift += 7;
+        }
+        while (b & 0x80);
+    }
+
+    *datap = p;
+    *lengthp = length;
+    return i;
+}
+
+/**
+ * Decodes a raw string literal.
+ */
+static char *hpack_decode_str_raw(const uint8_t *data, size_t length)
+{
+    char *s = malloc(length + 1);
+    if (s != NULL)
+    {
+        memcpy(s, data, length);
+        s[length] = '\0';
+    }
+    return s;
+}
+
+static int hpack_decode_byte_huffman(const uint8_t *restrict end,
+                                     int *restrict bit_offset)
+{
+    static const unsigned char tab[256] = {
+        /*  5 bits */
+         48,  49,  50,  97,  99, 101, 105, 111, 115, 116,
+        /*  6 bits */
+         32,  37,  45,  46,  47,  51,  52,  53,  54,  55,  56,  57,  61,  65,
+         95,  98, 100, 102, 103, 104, 108, 109, 110, 112, 114, 117,
+        /*  7 bits */
+         58,  66,  67,  68,  69,  70,  71,  72,  73,  74,  75,  76,  77,  78,
+         79,  80,  81,  82,  83,  84,  85,  86,  87,  89, 106, 107, 113, 118,
+        119, 120, 121, 122,
+        /*  8 bits */
+         38,  42,  44,  59,  88,  90,
+        /* 10 bits */
+         33,  34,  40,  41,  63,
+        /* 11 bits */
+         39,  43, 124,
+        /* 12 bits */
+         35,  62,
+        /* 13 bits */
+          0,  36,  64,  91,  93, 126,
+        /* 14 bits */
+         94, 125,
+        /* 15 bits */
+         60,  96, 123,
+        /* 19 bits */
+         92, 195, 208,
+        /* 20 bits */
+        128, 130, 131, 162, 184, 194, 224, 226,
+        /* 21 bits */
+        153, 161, 167, 172, 176, 177, 179, 209, 216, 217, 227, 229, 230,
+        /* 22 bits */
+        129, 132, 133, 134, 136, 146, 154, 156, 160, 163, 164, 169, 170, 173,
+        178, 181, 185, 186, 187, 189, 190, 196, 198, 228, 232, 233,
+        /* 23 bits */
+          1, 135, 137, 138, 139, 140, 141, 143, 147, 149, 150, 151, 152, 155,
+        157, 158, 165, 166, 168, 174, 175, 180, 182, 183, 188, 191, 197, 231,
+        239,
+        /* 24 bits */
+          9, 142, 144, 145, 148, 159, 171, 206, 215, 225, 236, 237,
+        /* 25 bits */
+        199, 207, 234, 235,
+        /* 26 bits */
+        192, 193, 200, 201, 202, 205, 210, 213, 218, 219, 238, 240, 242, 243,
+        255,
+        /* 27 bits */
+        203, 204, 211, 212, 214, 221, 222, 223, 241, 244, 245, 246, 247, 248,
+        250, 251, 252, 253, 254,
+        /* 28 bits */
+          2,   3,   4,   5,   6,   7,   8,  11,  12,  14,  15,  16,  17,  18,
+         19,  20,  21,  23,  24,  25,  26,  27,  28,  29,  30,  31, 127, 220,
+        249,
+        /* 30 bits */
+         10,  13,  22,
+    };
+    static const unsigned char values[30] = {
+        0,  0,  0,  0, 10, 26, 32,  6,  0,  5,  3,  2,  6,  2,  3,
+        0,  0,  0,  3,  8, 13, 26, 29, 12,  4, 15, 19, 29,  0,  3
+    };
+    const unsigned char *p = tab;
+    uint_fast32_t code = 0, offset = 0;
+    unsigned shift = -*bit_offset;
+
+    for (unsigned i = 0; i < 30; i++)
+    {
+        code <<= 1;
+
+        /* Read one bit */
+        if (*bit_offset)
+        {
+            shift = (shift - 1) & 7;
+            code |= (end[*bit_offset >> 3] >> shift) & 1;
+            (*bit_offset)++;
+        }
+        else
+            code |= 1; /* EOS is all ones */
+
+        assert(code >= offset);
+        if ((code - offset) < values[i])
+            return p[code - offset];
+        p += values[i];
+        offset = (offset + values[i]) * 2;
+    }
+
+    assert(p - tab == 256);
+
+    if (code == 0x3fffffff)
+        return 256; /* EOS */
+
+    errno = EINVAL;
+    return -1;
+}
+
+/**
+ * Decodes an Huffman-encoded string literal.
+ */
+static char *hpack_decode_str_huffman(const uint8_t *data, size_t length)
+{
+    unsigned char *str = malloc(length * 2 + 1);
+    if (str == NULL)
+        return NULL;
+
+    size_t len = 0;
+    int bit_offset = -8 * length;
+    data += length;
+
+    for (;;)
+    {
+        int c = hpack_decode_byte_huffman(data, &bit_offset);
+        if (c < 0)
+        {
+            errno = EINVAL;
+            goto error;
+        }
+
+        /* NOTE: EOS (256) is converted to nul terminator */
+        str[len++] = c;
+
+        if (c == 256)
+            break;
+    }
+
+    return (char *)str;
+
+error:
+    free(str);
+    return NULL;
+}
+
+/**
+ * Decodes a string literal.
+ * @return a heap-allocated nul-terminated string success,
+ *         NULL on error (and sets errno).
+ */
+static char *hpack_decode_str(const uint8_t **restrict datap,
+                              size_t *restrict lengthp)
+{
+    if (*lengthp < 1)
+    {
+        errno = EINVAL;
+        return NULL;
+    }
+
+    bool huffman = ((*datap)[0] & 0x80) != 0;
+    int_fast32_t len = hpack_decode_int(7, datap, lengthp);
+    if (len < 0)
+        return NULL;
+
+    if ((size_t)len > *lengthp)
+    {
+        errno = EINVAL;
+        return NULL;
+    }
+
+    if (len > 65535) /* Stick to a sane limit */
+    {
+        errno = ERANGE;
+        return NULL;
+    }
+
+    const uint8_t *buf = *datap;
+
+    *datap += len;
+    *lengthp -= len;
+
+    return (huffman ? hpack_decode_str_huffman : hpack_decode_str_raw)
+            (buf, len);
+}
+
+static char *hpack_lookup_name(const struct hpack_decoder *dec,
+                               uint_fast32_t idx)
+{
+    if (idx == 0)
+    {
+        errno = EINVAL;
+        return NULL;
+    }
+
+    idx--;
+    if (idx < sizeof (hpack_names) / sizeof (hpack_names[0]))
+        return strdup(hpack_names[idx]);
+
+    idx -= sizeof (hpack_names) / sizeof (hpack_names[0]);
+    if (idx < dec->entries)
+    {
+        const char *entry = dec->table[dec->entries - (idx + 1)];
+        return strdup(entry);
+    }
+
+    errno = EINVAL;
+    return NULL;
+}
+
+static char *hpack_lookup_value(const struct hpack_decoder *dec,
+                                uint_fast32_t idx)
+{
+    if (idx == 0)
+    {
+        errno = EINVAL;
+        return NULL;
+    }
+
+    idx--;
+    if (idx < sizeof (hpack_values) / sizeof (hpack_values[0]))
+        return strdup(hpack_values[idx]);
+    if (idx < sizeof (hpack_names) / sizeof (hpack_names[0]))
+        return strdup("");
+
+    idx -= sizeof (hpack_names) / sizeof (hpack_names[0]);
+    if (idx < dec->entries)
+    {
+        const char *entry = dec->table[dec->entries - (idx + 1)];
+        return strdup(entry + strlen(entry) + 1);
+    }
+
+    errno = EINVAL;
+    return NULL;
+}
+
+static void hpack_decode_evict(struct hpack_decoder *dec)
+{
+    /* Eviction: count how many entries to evict */
+    size_t evicted = 0;
+    while (dec->size > dec->max_size)
+    {
+        assert(evicted < dec->entries);
+
+        size_t namelen = strlen(dec->table[evicted]);
+        size_t valuelen = strlen(dec->table[evicted] + namelen + 1);
+
+        assert(dec->size >= 32 + namelen + valuelen);
+        dec->size -= 32 + namelen + valuelen;
+        evicted++;
+    }
+
+    /* Eviction: remove oldest entries */
+    if (evicted > 0)
+    {
+        for (size_t i = 0; i < evicted; i++)
+            free(dec->table[i]);
+
+        dec->entries -= evicted;
+        memmove(dec->table, dec->table + evicted,
+                sizeof (dec->table[0]) * dec->entries);
+    }
+}
+
+static int hpack_append_hdr(struct hpack_decoder *dec,
+                            const char *name, const char *value)
+{
+    size_t namelen = strlen(name), valuelen = strlen(value);
+    char *entry = malloc(namelen + valuelen + 2);
+    if (entry == NULL)
+        return -1;
+    memcpy(entry, name, namelen + 1);
+    memcpy(entry + namelen + 1, value, valuelen + 1);
+
+    char **newtab = realloc(dec->table,
+                            sizeof (dec->table[0]) * (dec->entries + 1));
+    if (newtab == NULL)
+        return -1;
+
+    dec->table = newtab;
+    dec->table[dec->entries] = entry;
+    dec->entries++;
+    dec->size += 32 + namelen + valuelen;
+
+    hpack_decode_evict(dec);
+    return 0;
+}
+
+static int hpack_decode_hdr_indexed(struct hpack_decoder *dec,
+                                    const uint8_t **restrict datap,
+                                    size_t *restrict lengthp,
+                                    char **restrict namep,
+                                    char **restrict valuep)
+{
+    int_fast32_t idx = hpack_decode_int(7, datap, lengthp);
+    if (idx < 0)
+        return -1;
+
+    char *name = hpack_lookup_name(dec, idx);
+    if (name == NULL)
+        return -1;
+
+    char *value = hpack_lookup_value(dec, idx);
+    if (value == NULL)
+    {
+        free(name);
+        return -1;
+    }
+
+    *namep = name;
+    *valuep = value;
+    return 0;
+}
+
+static int hpack_decode_hdr_index(struct hpack_decoder *dec,
+                                  const uint8_t **restrict datap,
+                                  size_t *restrict lengthp,
+                                  char **restrict namep,
+                                  char **restrict valuep)
+{
+    int_fast32_t idx = hpack_decode_int(6, datap, lengthp);
+    if (idx < 0)
+        return -1;
+
+    char *name;
+
+    if (idx != 0)
+        name = hpack_lookup_name(dec, idx);
+    else
+        name = hpack_decode_str(datap, lengthp);
+    if (name == NULL)
+        return -1;
+
+    char *value = hpack_decode_str(datap, lengthp);
+    if (value == NULL)
+    {
+        free(name);
+        return -1;
+    }
+
+    if (hpack_append_hdr(dec, name, value))
+    {
+        free(value);
+        free(name);
+        return -1;
+    }
+
+    *namep = name;
+    *valuep = value;
+    return 0;
+}
+
+static int hpack_decode_hdr_noindex(struct hpack_decoder *dec,
+                                    const uint8_t **restrict datap,
+                                    size_t *restrict lengthp,
+                                    char **restrict namep,
+                                    char **restrict valuep)
+{
+    int_fast32_t idx = hpack_decode_int(4, datap, lengthp);
+    if (idx < 0)
+        return -1;
+
+    char *name;
+
+    if (idx != 0)
+        name = hpack_lookup_name(dec, idx);
+    else
+        name = hpack_decode_str(datap, lengthp);
+    if (name == NULL)
+        return -1;
+
+    char *value = hpack_decode_str(datap, lengthp);
+    if (value == NULL)
+    {
+        free(name);
+        return -1;
+    }
+
+    *namep = name;
+    *valuep = value;
+    return 0;
+}
+
+static int hpack_decode_tbl_update(struct hpack_decoder *dec,
+                                   const uint8_t **restrict datap,
+                                   size_t *restrict lengthp,
+                                   char **restrict name,
+                                   char **restrict value)
+{
+    int_fast32_t max = hpack_decode_int(5, datap, lengthp);
+    if (max < 0)
+        return -1;
+
+    if ((size_t)max > dec->max_size)
+    {   /* Increasing the maximum is not permitted per the specification */
+        errno = EINVAL;
+        return -1;
+    }
+
+    *value = *name = NULL;
+    dec->max_size = max;
+    hpack_decode_evict(dec);
+    return 0;
+}
+
+static int hpack_decode_hdr(struct hpack_decoder *dec,
+                            const uint8_t **restrict datap,
+                            size_t *restrict lengthp,
+                            char **restrict namep,
+                            char **restrict valuep)
+{
+    int (*cb)(struct hpack_decoder *, const uint8_t **, size_t *,
+              char **, char **);
+
+    assert(*lengthp >= 1);
+
+    uint8_t b = **datap;
+
+    if (b & 0x80)
+        cb = hpack_decode_hdr_indexed;
+    else if (b & 0x40)
+        cb = hpack_decode_hdr_index;
+    else if (b & 0x20)
+        cb = hpack_decode_tbl_update;
+    else
+    /* NOTE: never indexed and not indexed are treated identically */
+        cb = hpack_decode_hdr_noindex;
+
+    return cb(dec, datap, lengthp, namep, valuep);
+}
+
+int hpack_decode(struct hpack_decoder *dec, const uint8_t *data,
+                 size_t length, char *headers[][2], unsigned max)
+{
+    unsigned count = 0;
+
+    while (length > 0)
+    {
+        char *name, *value;
+        int val = hpack_decode_hdr(dec, &data, &length, &name, &value);
+        if (val < 0)
+            goto error;
+
+        assert((name == NULL) == (value == NULL));
+        if (name == NULL)
+            continue;
+
+        if (count < max)
+        {
+            headers[count][0] = name;
+            headers[count][1] = value;
+        }
+        else
+        {
+            free(value);
+            free(name);
+        }
+        count++;
+    }
+    return count;
+
+error:
+    while (count > 0)
+    {
+        count--;
+        free(headers[count][1]);
+        free(headers[count][0]);
+    }
+    return -1;
+}
+
+/*** Test cases ***/
+#ifdef DEC_TEST
+# include <stdarg.h>
+# include <stdio.h>
+
+static void test_integer(unsigned n, const uint8_t *buf, size_t len,
+                         int_fast32_t value)
+{
+    printf("%s(%u, %zu byte(s))...\n", __func__, n, len);
+
+    /* Check too short buffers */
+    for (size_t i = 1; i < len; i++)
+    {
+        const uint8_t *cutbuf = buf;
+        size_t cutlen = i;
+        assert(hpack_decode_int(n, &cutbuf, &cutlen) == -1);
+    }
+
+    /* Check succesful decoding */
+    const uint8_t *end = buf + len;
+    int_fast32_t v = hpack_decode_int(n, &buf, &len);
+
+    assert(v == value);
+    assert(buf == end);
+    assert(len == 0);
+}
+
+static void test_integers(void)
+{
+    /* Decoding 10 using a 5-bits prefix */
+    for (unsigned i = 0; i < 8; i++)
+    {
+        uint8_t data[1] = { (i << 5) | 0xA };
+        test_integer(5, data, 1, 10);
+    }
+
+    /* Decoding 1337 using a 5-bits prefix */
+    for (unsigned i = 0; i < 8; i++)
+    {
+        uint8_t data[3] = { (i << 5) | 0x1F, 0x9A, 0x0A };
+        test_integer(5, data, 3, 1337);
+    }
+
+    /* Decoding 42 using a 8-bits prefix */
+    uint8_t data[1] = { 42 };
+    test_integer(8, data, 1, 42);
+}
+
+static void test_header(const char *str, size_t len,
+                        const char *name, const char *value)
+{
+    printf("%s(%zu bytes, \"%s\", \"%s\")...\n", __func__, len, name, value);
+
+    struct hpack_decoder *dec = hpack_decode_init(4096);
+    assert(dec != NULL);
+
+    const uint8_t *buf = (const uint8_t *)str;
+    char *n, *v;
+
+    /* Check too short buffers */
+    for (size_t i = 1; i < len; i++)
+    {
+        const uint8_t *cutbuf = buf;
+        size_t cutlen = i;
+
+        assert(hpack_decode_hdr(dec, &cutbuf, &cutlen, &n, &v) == -1);
+    }
+
+    /* Check succesful decoding */
+    int ret = hpack_decode_hdr(dec, &buf, &len, &n, &v);
+    assert(ret == 0);
+    assert(!strcmp(name, n));
+    assert(!strcmp(value, v));
+    free(v);
+    free(n);
+
+    hpack_decode_destroy(dec);
+}
+
+static void test_headers(void)
+{
+    test_header("@\x0a""custom-key""\x0d""custom-header", 26,
+                "custom-key", "custom-header");
+    test_header("\x04\x0c""/sample/path", 14, ":path", "/sample/path");
+    test_header("\x10\x08""password""\x06""secret", 17, "password", "secret");
+    test_header("\x82", 1, ":method", "GET");
+}
+
+static void test_block(struct hpack_decoder *dec, const char *req, size_t len,
+                       ...)
+{
+    printf("%s(%zu bytes)...\n", __func__, len);
+
+    va_list ap;
+
+    const uint8_t *buf = (const uint8_t *)req;
+    char *headers[16][2];
+    int count = hpack_decode(dec, buf, len, headers, 16);
+
+    printf(" %d headers:\n", count);
+    assert(count >= 0);
+
+    va_start(ap, len);
+    for (int i = 0; i < count; i++)
+    {
+        const char *name = va_arg(ap, const char *);
+        const char *value = va_arg(ap, const char *);
+
+        printf("  %s: %s\n", headers[i][0], headers[i][1]);
+        assert(!strcmp(name, headers[i][0]));
+        assert(!strcmp(value, headers[i][1]));
+        free(headers[i][1]);
+        free(headers[i][0]);
+    }
+    assert(va_arg(ap, const char *) == NULL);
+}
+
+static void test_reqs(void)
+{
+    struct hpack_decoder *dec = hpack_decode_init(4096);
+    assert(dec != NULL);
+
+    test_block(dec, NULL, 0, NULL);
+    test_block(dec, "\x82\x86\x84\x41\x0f""www.example.com", 20,
+               ":method", "GET", ":scheme", "http", ":path", "/",
+               ":authority", "www.example.com", NULL);
+    test_block(dec, "\x82\x86\x84\xbe\x58\x08""no-cache", 14,
+               ":method", "GET", ":scheme", "http", ":path", "/",
+               ":authority", "www.example.com", "cache-control", "no-cache",
+               NULL);
+    test_block(dec,
+               "\x82\x87\x85\xbf\x40\x0a""custom-key""\x0c""custom-value", 29,
+               ":method", "GET", ":scheme", "https", ":path", "/index.html",
+               ":authority", "www.example.com", "custom-key", "custom-value",
+               NULL);
+
+    hpack_decode_destroy(dec);
+}
+
+static void test_reqs_huffman(void)
+{
+    struct hpack_decoder *dec = hpack_decode_init(4096);
+    assert(dec != NULL);
+
+    test_block(dec, "\x82\x86\x84\x41\x8c\xf1\xe3\xc2\xe5\xf2\x3a\x6b\xa0\xab"
+               "\x90\xf4\xff", 17,
+               ":method", "GET", ":scheme", "http", ":path", "/",
+               ":authority", "www.example.com", NULL);
+    test_block(dec, "\x82\x86\x84\xbe\x58\x86\xa8\xeb\x10\x64\x9c\xbf", 12,
+               ":method", "GET", ":scheme", "http", ":path", "/",
+               ":authority", "www.example.com", "cache-control", "no-cache",
+               NULL);
+    test_block(dec, "\x82\x87\x85\xbf\x40\x88\x25\xa8\x49\xe9\x5b\xa9\x7d\x7f"
+               "\x89\x25\xa8\x49\xe9\x5b\xb8\xe8\xb4\xbf", 24,
+               ":method", "GET", ":scheme", "https", ":path", "/index.html",
+               ":authority", "www.example.com", "custom-key", "custom-value",
+               NULL);
+
+    hpack_decode_destroy(dec);
+}
+
+static void test_resps(void)
+{
+    struct hpack_decoder *dec = hpack_decode_init(256);
+    assert(dec != NULL);
+
+    test_block(dec, "\x48\x03""302""\x58\x07""private"
+               "\x61\x1d""Mon, 21 Oct 2013 20:13:21 GMT"
+               "\x6e\x17""https://www.example.com", 70,
+               ":status", "302", "cache-control", "private",
+               "date", "Mon, 21 Oct 2013 20:13:21 GMT",
+               "location", "https://www.example.com", NULL);
+    test_block(dec, "\x48\x03""307""\xc1\xc0\xbf", 8,
+               ":status", "307", "cache-control", "private",
+               "date", "Mon, 21 Oct 2013 20:13:21 GMT",
+               "location", "https://www.example.com", NULL);
+    test_block(dec, "\x88\xc1\x61\x1d""Mon, 21 Oct 2013 20:13:22 GMT"
+               "\xc0\x5a\x04""gzip""\x77\x38""foo=ASDJKHQKBZXOQWEOPIUAXQWEOIU;"
+               " max-age=3600; version=1", 98,
+               ":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);
+
+    hpack_decode_destroy(dec);
+}
+
+static void test_resps_huffman(void)
+{
+    struct hpack_decoder *dec = hpack_decode_init(256);
+    assert(dec != NULL);
+
+    test_block(dec, "\x48\x82\x64\x02\x58\x85\xae\xc3\x77\x1a\x4b\x61\x96\xd0"
+               "\x7a\xbe\x94\x10\x54\xd4\x44\xa8\x20\x05\x95\x04\x0b\x81\x66"
+               "\xe0\x82\xa6\x2d\x1b\xff\x6e\x91\x9d\x29\xad\x17\x18\x63\xc7"
+               "\x8f\x0b\x97\xc8\xe9\xae\x82\xae\x43\xd3", 54,
+               ":status", "302", "cache-control", "private",
+               "date", "Mon, 21 Oct 2013 20:13:21 GMT",
+               "location", "https://www.example.com", NULL);
+    test_block(dec, "\x48\x83\x64\x0e\xff\xc1\xc0\xbf", 8,
+               ":status", "307", "cache-control", "private",
+               "date", "Mon, 21 Oct 2013 20:13:21 GMT",
+               "location", "https://www.example.com", NULL);
+    test_block(dec, "\x88\xc1\x61\x96\xd0\x7a\xbe\x94\x10\x54\xd4\x44\xa8\x20"
+               "\x05\x95\x04\x0b\x81\x66\xe0\x84\xa6\x2d\x1b\xff\xc0\x5a\x83"
+               "\x9b\xd9\xab\x77\xad\x94\xe7\x82\x1d\xd7\xf2\xe6\xc7\xb3\x35"
+               "\xdf\xdf\xcd\x5b\x39\x60\xd5\xaf\x27\x08\x7f\x36\x72\xc1\xab"
+               "\x27\x0f\xb5\x29\x1f\x95\x87\x31\x60\x65\xc0\x03\xed\x4e\xe5"
+               "\xb1\x06\x3d\x50\x07", 79,
+               ":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);
+
+    hpack_decode_destroy(dec);
+}
+
+
+int main(void)
+{
+    test_integers();
+    test_headers();
+    test_reqs();
+    test_reqs_huffman();
+    test_resps();
+    test_resps_huffman();
+}
+#endif /* TEST */
diff --git a/modules/access/http/hpack.h b/modules/access/http/hpack.h
new file mode 100644 (file)
index 0000000..0fcc234
--- /dev/null
@@ -0,0 +1,27 @@
+/*****************************************************************************
+ * hpack.h: HPACK Header Compression for HTTP/2
+ *****************************************************************************
+ * Copyright (C) 2015 Rémi Denis-Courmont
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation; either version 2.1 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA.
+ *****************************************************************************/
+
+struct hpack_decoder;
+
+struct hpack_decoder *hpack_decode_init(size_t header_table_size);
+void hpack_decode_destroy(struct hpack_decoder *);
+
+int hpack_decode(struct hpack_decoder *dec, const uint8_t *data,
+                 size_t length, char *headers[][2], unsigned max);