You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
lighttpd1.4/src/ls-hpack/lshpack.c

2057 lines
62 KiB
C

/*
MIT License
Copyright (c) 2018 LiteSpeed Technologies Inc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
*/
/*(lighttpd customization)*/
#ifndef XXH_HEADER_NAME
#define XXH_HEADER_NAME "ls-hpack/deps/xxhash/xxhash.h"
#endif
#ifndef LS_HPACK_USE_LARGE_TABLES
#define LS_HPACK_USE_LARGE_TABLES 0
#endif
#define NDEBUG
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/queue.h>
#include "lshpack.h"
#if LS_HPACK_EMIT_TEST_CODE
#include "lshpack-test.h"
#endif
#include XXH_HEADER_NAME
#ifndef LS_HPACK_USE_LARGE_TABLES
#define LS_HPACK_USE_LARGE_TABLES 1
#endif
#include "huff-tables.h"
#define HPACK_STATIC_TABLE_SIZE 61
#define INITIAL_DYNAMIC_TABLE_SIZE 4096
/* RFC 7541, Section 4.1:
*
* " The size of the dynamic table is the sum of the size of its entries.
* "
* " The size of an entry is the sum of its name's length in octets (as
* " defined in Section 5.2), its value's length in octets, and 32.
*/
#define DYNAMIC_ENTRY_OVERHEAD 32
#define NAME_VAL(a, b) sizeof(a) - 1, sizeof(b) - 1, (a), (b)
static const struct
{
unsigned name_len;
unsigned val_len;
const char *name;
const char *val;
}
static_table[HPACK_STATIC_TABLE_SIZE] =
{
{ NAME_VAL(":authority", "") },
{ NAME_VAL(":method", "GET") },
{ NAME_VAL(":method", "POST") },
{ NAME_VAL(":path", "/") },
{ NAME_VAL(":path", "/index.html") },
{ NAME_VAL(":scheme", "http") },
{ NAME_VAL(":scheme", "https") },
{ NAME_VAL(":status", "200") },
{ NAME_VAL(":status", "204") },
{ NAME_VAL(":status", "206") },
{ NAME_VAL(":status", "304") },
{ NAME_VAL(":status", "400") },
{ NAME_VAL(":status", "404") },
{ NAME_VAL(":status", "500") },
{ NAME_VAL("accept-charset", "") },
{ NAME_VAL("accept-encoding", "gzip, deflate") },
{ NAME_VAL("accept-language", "") },
{ NAME_VAL("accept-ranges", "") },
{ NAME_VAL("accept", "") },
{ NAME_VAL("access-control-allow-origin", "") },
{ NAME_VAL("age", "") },
{ NAME_VAL("allow", "") },
{ NAME_VAL("authorization", "") },
{ NAME_VAL("cache-control", "") },
{ NAME_VAL("content-disposition", "") },
{ NAME_VAL("content-encoding", "") },
{ NAME_VAL("content-language", "") },
{ NAME_VAL("content-length", "") },
{ NAME_VAL("content-location", "") },
{ NAME_VAL("content-range", "") },
{ NAME_VAL("content-type", "") },
{ NAME_VAL("cookie", "") },
{ NAME_VAL("date", "") },
{ NAME_VAL("etag", "") },
{ NAME_VAL("expect", "") },
{ NAME_VAL("expires", "") },
{ NAME_VAL("from", "") },
{ NAME_VAL("host", "") },
{ NAME_VAL("if-match", "") },
{ NAME_VAL("if-modified-since", "") },
{ NAME_VAL("if-none-match", "") },
{ NAME_VAL("if-range", "") },
{ NAME_VAL("if-unmodified-since", "") },
{ NAME_VAL("last-modified", "") },
{ NAME_VAL("link", "") },
{ NAME_VAL("location", "") },
{ NAME_VAL("max-forwards", "") },
{ NAME_VAL("proxy-authenticate", "") },
{ NAME_VAL("proxy-authorization", "") },
{ NAME_VAL("range", "") },
{ NAME_VAL("referer", "") },
{ NAME_VAL("refresh", "") },
{ NAME_VAL("retry-after", "") },
{ NAME_VAL("server", "") },
{ NAME_VAL("set-cookie", "") },
{ NAME_VAL("strict-transport-security", "") },
{ NAME_VAL("transfer-encoding", "") },
{ NAME_VAL("user-agent", "") },
{ NAME_VAL("vary", "") },
{ NAME_VAL("via", "") },
{ NAME_VAL("www-authenticate", "") }
};
static const uint32_t static_table_name_hash[HPACK_STATIC_TABLE_SIZE] =
{
0x653A915Bu, 0xC7742BE4u, 0xC7742BE4u, 0x3513518Du, 0x3513518Du,
0xF49F1451u, 0xF49F1451u, 0x672BDA53u, 0x672BDA53u, 0x672BDA53u,
0x672BDA53u, 0x672BDA53u, 0x672BDA53u, 0x672BDA53u, 0xCD2C0296u,
0xF93AD8A9u, 0x98BD32D3u, 0x1DC691C8u, 0x1AB214F8u, 0x7D3B7A3Bu,
0xBEC8E440u, 0xE9C1D9E1u, 0x19D88141u, 0xC25511F2u, 0x16020A90u,
0x48011191u, 0x7D9AAB7Eu, 0x48F5CC19u, 0x8847A08Cu, 0x0D19F766u,
0x085EF7C5u, 0x0B486ED8u, 0x1A7AA369u, 0x6DE855BAu, 0xA6006EFDu,
0xA1BB4284u, 0xAE56E25Fu, 0xB6787110u, 0x791C6A0Du, 0xF2BADABEu,
0xD8CA2594u, 0xFBA64C54u, 0x4BEB0951u, 0x6B86C0B5u, 0xC62FECD2u,
0x8DA64A26u, 0x6CA35045u, 0xF614D165u, 0xE4D1DF14u, 0xB396750Au,
0x01F10233u, 0x798BEE18u, 0x5239F142u, 0x82E1B4E1u, 0x8F7E493Eu,
0x85E74C58u, 0xBD17F160u, 0x34C0456Au, 0x1A04DF3Du, 0xB1B15AB2u,
0xDDDAB6FFu,
};
static const uint32_t static_table_nameval_hash[HPACK_STATIC_TABLE_SIZE] =
{
0xF8614896u, 0x25D95A15u, 0x33968BB7u, 0xC8C267F6u, 0x8351136Fu,
0x98573F68u, 0x16DDE443u, 0x352A6556u, 0xD4F462D2u, 0x125E66E0u,
0xD7988BC9u, 0x4C3C90DEu, 0x65E6ECA1u, 0xB05B7B87u, 0x96816317u,
0x8BBF5398u, 0x97E01849u, 0xD7B48DD4u, 0x9C180569u, 0xC7C63B45u,
0xF4223EE5u, 0x12C8A744u, 0xAA95A0BCu, 0x14F65730u, 0x8410A906u,
0x98F440DDu, 0x627E4803u, 0x5A5CC325u, 0x137FC223u, 0x1529262Fu,
0x7950B9BDu, 0x51D448A4u, 0x52C167CFu, 0xFB22AA54u, 0x540DB9FEu,
0x75A6C685u, 0xE1C54196u, 0xDC0C3733u, 0x6D78CB84u, 0x4F5272CDu,
0x9D4170E4u, 0xD4E28BA1u, 0x028C7846u, 0x4E8C1DC3u, 0x684BDDBCu,
0xE113A2B0u, 0x55F7BBD1u, 0x15BD3710u, 0xE82B715Du, 0x3674BC1Fu,
0x5010D24Bu, 0x953DE1CBu, 0x9F2C92D9u, 0xB2DE5570u, 0xBCA5998Fu,
0x0FF5B88Eu, 0x1FED156Bu, 0xDC83E7ECu, 0x07B79E35u, 0xA6D145A9u,
0x43638CBAu,
};
#define lshpack_arr_init(a) do { \
memset((a), 0, sizeof(*(a))); \
} while (0)
#define lshpack_arr_cleanup(a) do { \
free((a)->els); \
memset((a), 0, sizeof(*(a))); \
} while (0)
#define lshpack_arr_get(a, i) ( \
assert((i) < (a)->nelem), \
(a)->els[(a)->off + (i)] \
)
#define lshpack_arr_shift(a) ( \
assert((a)->nelem > 0), \
(a)->nelem -= 1, \
(a)->els[(a)->off++] \
)
#define lshpack_arr_pop(a) ( \
assert((a)->nelem > 0), \
(a)->nelem -= 1, \
(a)->els[(a)->off + (a)->nelem] \
)
#define lshpack_arr_count(a) (+(a)->nelem)
static int
lshpack_arr_push (struct lshpack_arr *arr, uintptr_t val)
{
uintptr_t *new_els;
unsigned n;
if (arr->off + arr->nelem < arr->nalloc)
{
arr->els[arr->off + arr->nelem] = val;
++arr->nelem;
return 0;
}
if (arr->off > arr->nalloc / 2)
{
memmove(arr->els, arr->els + arr->off,
sizeof(arr->els[0]) * arr->nelem);
arr->off = 0;
arr->els[arr->nelem] = val;
++arr->nelem;
return 0;
}
if (arr->nalloc)
n = arr->nalloc * 2;
else
n = 64;
new_els = malloc(n * sizeof(arr->els[0]));
if (!new_els)
return -1;
memcpy(new_els, arr->els + arr->off, sizeof(arr->els[0]) * arr->nelem);
free(arr->els);
arr->off = 0;
arr->els = new_els;
arr->nalloc = n;
arr->els[arr->off + arr->nelem] = val;
++arr->nelem;
return 0;
}
struct lshpack_double_enc_head
{
struct lshpack_enc_head by_name;
struct lshpack_enc_head by_nameval;
};
struct lshpack_enc_table_entry
{
/* An entry always lives on the `all' and `nameval' lists. If its name
* is not in the static table, it also lives on the `name' list.
*/
STAILQ_ENTRY(lshpack_enc_table_entry)
ete_next_nameval,
ete_next_name,
ete_next_all;
unsigned ete_id;
unsigned ete_nameval_hash;
unsigned ete_name_hash;
unsigned ete_name_len;
unsigned ete_val_len;
char ete_buf[];
};
#define ETE_NAME(ete) ((ete)->ete_buf)
#define ETE_VALUE(ete) (&(ete)->ete_buf[(ete)->ete_name_len])
#define N_BUCKETS(n_bits) (1U << (n_bits))
#define BUCKNO(n_bits, hash) ((hash) & (N_BUCKETS(n_bits) - 1))
/* We estimate average number of entries in the dynamic table to be 1/3
* of the theoretical maximum. This number is used to size the history
* buffer: we want it large enough to cover recent entries, yet not too
* large to cover entries that appear with a period larger than the
* dynamic table.
*/
static unsigned
henc_hist_size (unsigned max_capacity)
{
return max_capacity / DYNAMIC_ENTRY_OVERHEAD / 3;
}
int
lshpack_enc_init (struct lshpack_enc *enc)
{
struct lshpack_double_enc_head *buckets;
unsigned nbits = 2;
unsigned i;
buckets = malloc(sizeof(buckets[0]) * N_BUCKETS(nbits));
if (!buckets)
return -1;
for (i = 0; i < N_BUCKETS(nbits); ++i)
{
STAILQ_INIT(&buckets[i].by_name);
STAILQ_INIT(&buckets[i].by_nameval);
}
memset(enc, 0, sizeof(*enc));
STAILQ_INIT(&enc->hpe_all_entries);
enc->hpe_max_capacity = INITIAL_DYNAMIC_TABLE_SIZE;
enc->hpe_buckets = buckets;
/* The initial value of the entry ID is completely arbitrary. As long as
* there are fewer than 2^32 dynamic table entries, the math to calculate
* the entry ID works. To prove to ourselves that the wraparound works
* and to have the unit tests cover it, we initialize the next ID so that
* it is just about to wrap around.
*/
enc->hpe_next_id = ~0 - 3;
enc->hpe_nbits = nbits;
enc->hpe_nelem = 0;
return 0;
}
void
lshpack_enc_cleanup (struct lshpack_enc *enc)
{
struct lshpack_enc_table_entry *entry, *next;
for (entry = STAILQ_FIRST(&enc->hpe_all_entries); entry; entry = next)
{
next = STAILQ_NEXT(entry, ete_next_all);
free(entry);
}
free(enc->hpe_hist_buf);
free(enc->hpe_buckets);
}
static int
henc_use_hist (struct lshpack_enc *enc)
{
unsigned hist_size;
if (enc->hpe_hist_buf)
return 0;
hist_size = henc_hist_size(INITIAL_DYNAMIC_TABLE_SIZE);
if (!hist_size)
return 0;
enc->hpe_hist_buf = malloc(sizeof(enc->hpe_hist_buf[0]) * (hist_size + 1));
if (!enc->hpe_hist_buf)
return -1;
enc->hpe_hist_size = hist_size;
enc->hpe_flags |= LSHPACK_ENC_USE_HIST;
return 0;
}
int
lshpack_enc_use_hist (struct lshpack_enc *enc, int on)
{
if (on)
return henc_use_hist(enc);
else
{
enc->hpe_flags &= ~LSHPACK_ENC_USE_HIST;
free(enc->hpe_hist_buf);
enc->hpe_hist_buf = NULL;
enc->hpe_hist_size = 0;
enc->hpe_hist_idx = 0;
enc->hpe_hist_wrapped = 0;
return 0;
}
}
int
lshpack_enc_hist_used (const struct lshpack_enc *enc)
{
return (enc->hpe_flags & LSHPACK_ENC_USE_HIST) != 0;
}
#define LSHPACK_XXH_SEED 39378473
#define XXH_NAMEVAL_WIDTH 9
#define XXH_NAMEVAL_SHIFT 0
#define XXH_NAME_WIDTH 9
#define XXH_NAME_SHIFT 0
static const unsigned char nameval2id[ 1 << XXH_NAMEVAL_WIDTH ] =
{
[150] = 1, [21] = 2, [439] = 3, [502] = 4, [367] = 5,
[360] = 6, [67] = 7, [342] = 8, [210] = 9, [224] = 10,
[457] = 11, [222] = 12, [161] = 13, [391] = 14, [279] = 15,
[408] = 16, [73] = 17, [468] = 18, [361] = 19, [325] = 20,
[229] = 21, [324] = 22, [188] = 23, [304] = 24, [262] = 25,
[221] = 26, [3] = 27, [293] = 28, [35] = 29, [47] = 30,
[445] = 31, [164] = 32, [463] = 33, [84] = 34, [510] = 35,
[133] = 36, [406] = 37, [307] = 38, [388] = 39, [205] = 40,
[228] = 41, [417] = 42, [70] = 43, [451] = 44, [444] = 45,
[176] = 46, [465] = 47, [272] = 48, [349] = 49, [31] = 50,
[75] = 51, [459] = 52, [217] = 53, [368] = 54, [399] = 55,
[142] = 56, [363] = 57, [492] = 58, [53] = 59, [425] = 60,
[186] = 61,
};
static const unsigned char name2id[ 1 << XXH_NAME_WIDTH ] =
{
[347] = 1, [484] = 2, [397] = 4, [81] = 6, [83] = 8,
[150] = 15, [169] = 16, [211] = 17, [456] = 18, [248] = 19,
[59] = 20, [64] = 21, [481] = 22, [321] = 23, [498] = 24,
[144] = 25, [401] = 26, [382] = 27, [25] = 28, [140] = 29,
[358] = 30, [453] = 31, [216] = 32, [361] = 33, [442] = 34,
[253] = 35, [132] = 36, [95] = 37, [272] = 38, [13] = 39,
[190] = 40, [404] = 41, [84] = 42, [337] = 43, [181] = 44,
[210] = 45, [38] = 46, [69] = 47, [357] = 48, [276] = 49,
[266] = 50, [51] = 51, [24] = 52, [322] = 53, [225] = 54,
[318] = 55, [88] = 56, [352] = 57, [362] = 58, [317] = 59,
[178] = 60, [255] = 61,
};
//not find return 0, otherwise return the index
#if !LS_HPACK_EMIT_TEST_CODE
static
#endif
unsigned
lshpack_enc_get_static_nameval (const struct lsxpack_header *input)
{
unsigned i;
assert(input->name_len > 0);
assert(input->flags & LSXPACK_NAMEVAL_HASH);
i = (input->nameval_hash >> XXH_NAMEVAL_SHIFT) & ((1 << XXH_NAMEVAL_WIDTH) - 1);
if (nameval2id[i])
{
i = nameval2id[i] - 1;
if (static_table[i].name_len == input->name_len
&& static_table[i].val_len == input->val_len
&& memcmp(lsxpack_header_get_name(input), static_table[i].name, input->name_len) == 0
&& memcmp(lsxpack_header_get_value(input), static_table[i].val, input->val_len) == 0)
{
return i + 1;
}
}
return 0;
}
#if !LS_HPACK_EMIT_TEST_CODE
static
#endif
unsigned
lshpack_enc_get_static_name (const struct lsxpack_header *input)
{
unsigned i;
assert(input->flags & LSXPACK_NAME_HASH);
i = (input->name_hash >> XXH_NAME_SHIFT) & ((1 << XXH_NAME_WIDTH) - 1);
if (name2id[i])
{
i = name2id[i] - 1;
if (static_table[i].name_len == input->name_len
&& memcmp(lsxpack_header_get_name(input), static_table[i].name,
input->name_len) == 0)
{
return i + 1;
}
}
return 0;
}
static void
update_hash (struct lsxpack_header *input)
{
if (!(input->flags & LSXPACK_NAME_HASH))
input->name_hash = XXH32(lsxpack_header_get_name(input),
input->name_len, LSHPACK_XXH_SEED);
else
assert(input->name_hash == XXH32(lsxpack_header_get_name(input),
input->name_len, LSHPACK_XXH_SEED));
if (!(input->flags & LSXPACK_NAMEVAL_HASH))
input->nameval_hash = XXH32(input->buf + input->val_offset,
input->val_len, input->name_hash);
else
assert(input->nameval_hash == XXH32(input->buf + input->val_offset,
input->val_len, input->name_hash));
input->flags |= (LSXPACK_NAME_HASH | LSXPACK_NAMEVAL_HASH);
}
unsigned
lshpack_enc_get_stx_tab_id (struct lsxpack_header *input)
{
unsigned i;
update_hash(input);
i = (input->nameval_hash >> XXH_NAMEVAL_SHIFT) & ((1 << XXH_NAMEVAL_WIDTH) - 1);
if (nameval2id[i])
{
i = nameval2id[i] - 1;
if (static_table[i].name_len == input->name_len
&& static_table[i].val_len == input->val_len
&& memcmp(lsxpack_header_get_name(input), static_table[i].name,
input->name_len) == 0
&& memcmp(input->buf + input->val_offset, static_table[i].val,
input->val_len) == 0)
{
return i + 1;
}
}
i = (input->name_hash >> XXH_NAME_SHIFT) & ((1 << XXH_NAME_WIDTH) - 1);
if (name2id[i])
{
i = name2id[i] - 1;
if (static_table[i].name_len == input->name_len
&& memcmp(lsxpack_header_get_name(input), static_table[i].name,
input->name_len) == 0)
{
return i + 1;
}
}
return 0;
}
/* Given a dynamic entry, return its table ID */
static unsigned
henc_calc_table_id (const struct lshpack_enc *enc,
const struct lshpack_enc_table_entry *entry)
{
return HPACK_STATIC_TABLE_SIZE
+ (enc->hpe_next_id - entry->ete_id)
;
}
static unsigned
henc_find_table_id (struct lshpack_enc *enc, lsxpack_header_t *input,
int *val_matched)
{
struct lshpack_enc_table_entry *entry;
unsigned buckno, id;
const char *val_ptr = input->buf + input->val_offset;
const char *name;
unsigned int name_len;
name_len = input->name_len;
name = lsxpack_header_get_name(input);
/* First, look for a match in the static table: */
if (input->hpack_index)
{
id = input->hpack_index - 1;
#ifndef NDEBUG
if (name_len)
{
lsxpack_header_t input_copy = *input;
const unsigned hpack_index = lshpack_enc_get_stx_tab_id(&input_copy);
assert(input_copy.hpack_index == hpack_index);
}
#endif
if (id <= LSHPACK_HDR_ACCEPT_ENCODING || input->val_len == 0)
{
if (static_table[id].val_len == input->val_len
&& memcmp(val_ptr, static_table[id].val, input->val_len) == 0)
{
input->flags |= LSXPACK_HPACK_VAL_MATCHED;
*val_matched = 1;
return input->hpack_index;
}
}
if (!name_len)
{
name = static_table[id].name;
name_len = static_table[id].name_len;
}
if (!(input->flags & LSXPACK_NAME_HASH))
input->name_hash = static_table_name_hash[id];
else
assert(input->name_hash == static_table_name_hash[id]);
if (!(input->flags & LSXPACK_NAMEVAL_HASH))
input->nameval_hash = XXH32(val_ptr, input->val_len,
input->name_hash);
else
assert(input->nameval_hash == XXH32(val_ptr, input->val_len,
input->name_hash));
input->flags |= (LSXPACK_NAME_HASH | LSXPACK_NAMEVAL_HASH);
}
else
{
update_hash(input);
input->hpack_index = lshpack_enc_get_static_nameval(input);
if (input->hpack_index != LSHPACK_HDR_UNKNOWN)
{
input->flags |= LSXPACK_HPACK_VAL_MATCHED;
*val_matched = 1;
return input->hpack_index;
}
}
/* Search by name and value: */
buckno = BUCKNO(enc->hpe_nbits, input->nameval_hash);
STAILQ_FOREACH(entry, &enc->hpe_buckets[buckno].by_nameval,
ete_next_nameval)
if (input->nameval_hash == entry->ete_nameval_hash &&
name_len == entry->ete_name_len &&
input->val_len == entry->ete_val_len &&
0 == memcmp(name, ETE_NAME(entry), name_len) &&
0 == memcmp(val_ptr, ETE_VALUE(entry), input->val_len))
{
*val_matched = 1;
return henc_calc_table_id(enc, entry);
}
/* Name/value match is not found, look for header: */
if (input->hpack_index == LSHPACK_HDR_UNKNOWN)
input->hpack_index = lshpack_enc_get_static_name(input);
if (input->hpack_index != LSHPACK_HDR_UNKNOWN)
{
input->flags &= ~LSXPACK_HPACK_VAL_MATCHED;
return input->hpack_index;
}
/* Search by name only: */
buckno = BUCKNO(enc->hpe_nbits, input->name_hash);
STAILQ_FOREACH(entry, &enc->hpe_buckets[buckno].by_name, ete_next_name)
if (input->name_hash == entry->ete_name_hash &&
input->name_len == entry->ete_name_len &&
0 == memcmp(name, ETE_NAME(entry), name_len))
{
input->flags &= ~LSXPACK_HPACK_VAL_MATCHED;
return henc_calc_table_id(enc, entry);
}
return 0;
}
#if !LS_HPACK_EMIT_TEST_CODE
static
#endif
unsigned char *
lshpack_enc_enc_int (unsigned char *dst, unsigned char *const end,
uint32_t value, uint8_t prefix_bits)
{
unsigned char *const dst_orig = dst;
/* This function assumes that at least one byte is available */
assert(dst < end);
if (value < (uint32_t)(1 << prefix_bits) - 1)
*dst++ |= value;
else
{
*dst++ |= (1 << prefix_bits) - 1;
value -= (1 << prefix_bits) - 1;
while (value >= 128)
{
if (dst < end)
{
*dst++ = (0x80 | value);
value >>= 7;
}
else
return dst_orig;
}
if (dst < end)
*dst++ = value;
else
return dst_orig;
}
return dst;
}
/* This whole pragma business has to do with turning off uninitialized warnings.
* We do it for gcc and clang. Other compilers get slightly slower code, where
* unnecessary initialization is performed.
*/
#if __GNUC__
#pragma GCC diagnostic ignored "-Wunknown-pragmas"
#if __clang__
#pragma GCC diagnostic ignored "-Wunknown-warning-option"
#endif
#endif
static int
lshpack_enc_huff_encode (const unsigned char *src,
const unsigned char *const src_end, unsigned char *const dst, int dst_len)
{
unsigned char *p_dst = dst;
unsigned char *dst_end = p_dst + dst_len;
uintptr_t bits; /* OK not to initialize this variable */
unsigned bits_used = 0, adj;
struct encode_el cur_enc_code;
#if __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wmaybe-uninitialized"
#pragma GCC diagnostic ignored "-Wuninitialized"
#else
bits = 0;
#endif
#if LS_HPACK_USE_LARGE_TABLES
const struct henc *henc;
uint16_t idx;
while (src + sizeof(bits) * 8 / 5 + sizeof(idx) < src_end
&& p_dst + sizeof(bits) <= dst_end)
{
memcpy(&idx, src, 2);
henc = &hencs[idx];
src += 2;
while (bits_used + henc->lens < sizeof(bits) * 8)
{
bits <<= henc->lens;
bits |= henc->code;
bits_used += henc->lens;
memcpy(&idx, src, 2);
henc = &hencs[idx];
src += 2;
}
if (henc->lens < 64)
{
bits <<= sizeof(bits) * 8 - bits_used;
bits_used = henc->lens - (sizeof(bits) * 8 - bits_used);
bits |= henc->code >> bits_used;
#if UINTPTR_MAX == 18446744073709551615ull
*p_dst++ = bits >> 56;
*p_dst++ = bits >> 48;
*p_dst++ = bits >> 40;
*p_dst++ = bits >> 32;
#endif
*p_dst++ = bits >> 24;
*p_dst++ = bits >> 16;
*p_dst++ = bits >> 8;
*p_dst++ = bits;
bits = henc->code; /* OK not to clear high bits */
}
else
{
src -= 2;
break;
}
}
#endif
while (src != src_end)
{
cur_enc_code = encode_table[*src++];
if (bits_used + cur_enc_code.bits < sizeof(bits) * 8)
{
bits <<= cur_enc_code.bits;
bits |= cur_enc_code.code;
bits_used += cur_enc_code.bits;
continue;
}
else if (p_dst + sizeof(bits) <= dst_end)
{
bits <<= sizeof(bits) * 8 - bits_used;
bits_used = cur_enc_code.bits - (sizeof(bits) * 8 - bits_used);
bits |= cur_enc_code.code >> bits_used;
#if UINTPTR_MAX == 18446744073709551615ull
*p_dst++ = bits >> 56;
*p_dst++ = bits >> 48;
*p_dst++ = bits >> 40;
*p_dst++ = bits >> 32;
#endif
*p_dst++ = bits >> 24;
*p_dst++ = bits >> 16;
*p_dst++ = bits >> 8;
*p_dst++ = bits;
bits = cur_enc_code.code; /* OK not to clear high bits */
}
else
return -1;
}
adj = bits_used + (-bits_used & 7); /* Round up to 8 */
if (bits_used && p_dst + (adj >> 3) <= dst_end)
{
bits <<= -bits_used & 7; /* Align to byte boundary */
bits |= ((1 << (-bits_used & 7)) - 1); /* EOF */
switch (adj >> 3)
{ /* Write out */
#if UINTPTR_MAX == 18446744073709551615ull
case 8: *p_dst++ = bits >> 56;
/* fall through */
case 7: *p_dst++ = bits >> 48;
/* fall through */
case 6: *p_dst++ = bits >> 40;
/* fall through */
case 5: *p_dst++ = bits >> 32;
#endif
/* fall through */
case 4: *p_dst++ = bits >> 24;
/* fall through */
case 3: *p_dst++ = bits >> 16;
/* fall through */
case 2: *p_dst++ = bits >> 8;
/* fall through */
default: *p_dst++ = bits;
}
return p_dst - dst;
}
else if (p_dst + (adj >> 3) <= dst_end)
return p_dst - dst;
else
return -1;
#if __GNUC__
#pragma GCC diagnostic pop
#endif
}
#if !LS_HPACK_EMIT_TEST_CODE
static
#endif
int
lshpack_enc_enc_str (unsigned char *const dst, size_t dst_len,
const unsigned char *str, unsigned str_len)
{
unsigned char size_buf[4];
unsigned char *p;
unsigned size_len;
int rc;
if (dst_len > 1)
/* We guess that the string size fits into a single byte -- meaning
* compressed string of size 126 and smaller -- which is the normal
* case. Thus, we immediately write compressed string to the output
* buffer. If our guess is not correct, we fix it later.
*/
rc = lshpack_enc_huff_encode(str, str + str_len, dst + 1, dst_len - 1);
else if (dst_len == 1)
/* Here, the call can only succeed if the string to encode is empty. */
rc = 0;
else
return -1;
/*
* Check if need huffman encoding or not
* Comment: (size_t)rc <= str_len = means if same length, still use
* Huffman
* ^
*/
if (rc > 0 && (size_t)rc <= str_len)
{
if (rc < 127)
{
*dst = 0x80 | rc;
return 1 + rc;
}
size_buf[0] = 0x80;
str_len = rc;
str = dst + 1;
}
else if (str_len <= dst_len - 1)
{
if (str_len < 127)
{
*dst = (unsigned char) str_len;
memcpy(dst + 1, str, str_len);
return 1 + str_len;
}
size_buf[0] = 0x00;
}
else
return -1;
/* The guess of one-byte size was incorrect. Perform necessary
* adjustments.
*/
p = lshpack_enc_enc_int(size_buf, size_buf + sizeof(size_buf), str_len, 7);
if (p == size_buf)
return -1;
size_len = p - size_buf;
assert(size_len > 1);
/* Check if there is enough room in the output buffer for both
* encoded size and the string.
*/
if (size_len + str_len > dst_len)
return -1;
memmove(dst + size_len, str, str_len);
memcpy(dst, size_buf, size_len);
return size_len + str_len;
}
static void
henc_drop_oldest_entry (struct lshpack_enc *enc)
{
struct lshpack_enc_table_entry *entry;
unsigned buckno;
entry = STAILQ_FIRST(&enc->hpe_all_entries);
assert(entry);
STAILQ_REMOVE_HEAD(&enc->hpe_all_entries, ete_next_all);
buckno = BUCKNO(enc->hpe_nbits, entry->ete_nameval_hash);
assert(entry == STAILQ_FIRST(&enc->hpe_buckets[buckno].by_nameval));
STAILQ_REMOVE_HEAD(&enc->hpe_buckets[buckno].by_nameval, ete_next_nameval);
buckno = BUCKNO(enc->hpe_nbits, entry->ete_name_hash);
if (entry == STAILQ_FIRST(&enc->hpe_buckets[buckno].by_name))
STAILQ_REMOVE_HEAD(&enc->hpe_buckets[buckno].by_name, ete_next_name);
enc->hpe_cur_capacity -= DYNAMIC_ENTRY_OVERHEAD + entry->ete_name_len
+ entry->ete_val_len;
--enc->hpe_nelem;
free(entry);
}
static void
henc_remove_overflow_entries (struct lshpack_enc *enc)
{
while (enc->hpe_cur_capacity > enc->hpe_max_capacity)
henc_drop_oldest_entry(enc);
}
static int
henc_grow_tables (struct lshpack_enc *enc)
{
struct lshpack_double_enc_head *new_buckets, *new[2];
struct lshpack_enc_table_entry *entry;
unsigned n, old_nbits;
int idx;
old_nbits = enc->hpe_nbits;
new_buckets = malloc(sizeof(enc->hpe_buckets[0])
* N_BUCKETS(old_nbits + 1));
if (!new_buckets)
return -1;
for (n = 0; n < N_BUCKETS(old_nbits); ++n)
{
new[0] = &new_buckets[n];
new[1] = &new_buckets[n + N_BUCKETS(old_nbits)];
STAILQ_INIT(&new[0]->by_name);
STAILQ_INIT(&new[1]->by_name);
STAILQ_INIT(&new[0]->by_nameval);
STAILQ_INIT(&new[1]->by_nameval);
while ((entry = STAILQ_FIRST(&enc->hpe_buckets[n].by_name)))
{
STAILQ_REMOVE_HEAD(&enc->hpe_buckets[n].by_name, ete_next_name);
idx = (BUCKNO(old_nbits + 1, entry->ete_name_hash)
>> old_nbits) & 1;
STAILQ_INSERT_TAIL(&new[idx]->by_name, entry, ete_next_name);
}
while ((entry = STAILQ_FIRST(&enc->hpe_buckets[n].by_nameval)))
{
STAILQ_REMOVE_HEAD(&enc->hpe_buckets[n].by_nameval,
ete_next_nameval);
idx = (BUCKNO(old_nbits + 1, entry->ete_nameval_hash)
>> old_nbits) & 1;
STAILQ_INSERT_TAIL(&new[idx]->by_nameval, entry,
ete_next_nameval);
}
}
free(enc->hpe_buckets);
enc->hpe_nbits = old_nbits + 1;
enc->hpe_buckets = new_buckets;
return 0;
}
#if !LS_HPACK_EMIT_TEST_CODE
static
#endif
int
lshpack_enc_push_entry (struct lshpack_enc *enc,
const struct lsxpack_header *input)
{
unsigned buckno;
struct lshpack_enc_table_entry *entry;
size_t size;
const char *name;
unsigned int name_len;
if (enc->hpe_nelem >= N_BUCKETS(enc->hpe_nbits) / 2 &&
0 != henc_grow_tables(enc))
return -1;
name_len = input->name_len;
if (name_len == 0)
{
assert(input->hpack_index != LSHPACK_HDR_UNKNOWN);
name = static_table[input->hpack_index - 1].name;
name_len = static_table[input->hpack_index - 1].name_len;
}
else
name = lsxpack_header_get_name(input);
size = sizeof(*entry) + name_len + input->val_len;
entry = malloc(size);
if (!entry)
return -1;
entry->ete_name_hash = input->name_hash;
entry->ete_nameval_hash = input->nameval_hash;
entry->ete_name_len = name_len;
entry->ete_val_len = input->val_len;
entry->ete_id = enc->hpe_next_id++;
memcpy(ETE_NAME(entry), name, name_len);
memcpy(ETE_VALUE(entry), input->buf + input->val_offset, input->val_len);
STAILQ_INSERT_TAIL(&enc->hpe_all_entries, entry, ete_next_all);
buckno = BUCKNO(enc->hpe_nbits, input->nameval_hash);
STAILQ_INSERT_TAIL(&enc->hpe_buckets[buckno].by_nameval, entry,
ete_next_nameval);
if (input->hpack_index == LSHPACK_HDR_UNKNOWN)
{
buckno = BUCKNO(enc->hpe_nbits, input->name_hash);
STAILQ_INSERT_TAIL(&enc->hpe_buckets[buckno].by_name, entry,
ete_next_name);
}
enc->hpe_cur_capacity += DYNAMIC_ENTRY_OVERHEAD + name_len
+ input->val_len;
++enc->hpe_nelem;
henc_remove_overflow_entries(enc);
return 0;
}
static void
henc_resize_history (struct lshpack_enc *enc)
{
uint32_t *hist_buf;
unsigned hist_size, first, count, i, j;
hist_size = henc_hist_size(enc->hpe_max_capacity);
if (hist_size == enc->hpe_hist_size)
return;
if (hist_size == 0)
{
free(enc->hpe_hist_buf);
enc->hpe_hist_buf = NULL;
enc->hpe_hist_size = 0;
enc->hpe_hist_idx = 0;
enc->hpe_hist_wrapped = 0;
return;
}
hist_buf = malloc(sizeof(hist_buf[0]) * (hist_size + 1));
if (!hist_buf)
return;
if (enc->hpe_hist_wrapped)
{
first = (enc->hpe_hist_idx + 1) % enc->hpe_hist_size;
count = enc->hpe_hist_size;
}
else
{
first = 0;
count = enc->hpe_hist_idx;
}
for (i = 0, j = 0; count > 0 && j < hist_size; ++i, ++j, --count)
hist_buf[j] = enc->hpe_hist_buf[ (first + i) % enc->hpe_hist_size ];
enc->hpe_hist_size = hist_size;
enc->hpe_hist_idx = j % hist_size;
enc->hpe_hist_wrapped = enc->hpe_hist_idx == 0;
free(enc->hpe_hist_buf);
enc->hpe_hist_buf = hist_buf;
}
/* Returns true if `nameval_hash' was already in history, false otherwise. */
static int
henc_hist_add (struct lshpack_enc *enc, uint32_t nameval_hash)
{
unsigned last;
uint32_t *p;
if (enc->hpe_hist_wrapped)
last = enc->hpe_hist_size;
else
last = enc->hpe_hist_idx;
enc->hpe_hist_buf[ last ] = nameval_hash;
for (p = enc->hpe_hist_buf; *p != nameval_hash; ++p)
;
enc->hpe_hist_buf[ enc->hpe_hist_idx ] = nameval_hash;
enc->hpe_hist_idx = (enc->hpe_hist_idx + 1) % enc->hpe_hist_size;
enc->hpe_hist_wrapped |= enc->hpe_hist_idx == 0;
return p < enc->hpe_hist_buf + last;
}
unsigned char *
lshpack_enc_encode (struct lshpack_enc *enc, unsigned char *dst,
unsigned char *dst_end, lsxpack_header_t *input)
{