nghttp_hd: Use huffman encoding only when it is usable

We use huffman encoding only when its encoded length is strictly less
than the original length.
As noted in the HPACK draft, the length of name/value is now 7-bit
prefix and its MSB is 1 if the following string is huffman encoded.
This commit is contained in:
Tatsuhiro Tsujikawa 2013-10-20 23:59:15 +09:00
parent a658f1367e
commit 28aea5c6b7
1 changed files with 141 additions and 52 deletions

View File

@ -411,16 +411,21 @@ static int emit_indexed_header(nghttp2_hd_context *context,
static int emit_newname_header(nghttp2_hd_context *context, static int emit_newname_header(nghttp2_hd_context *context,
nghttp2_nva_out *nva_out_ptr, nghttp2_nva_out *nva_out_ptr,
nghttp2_nv *nv) nghttp2_nv *nv,
uint8_t flags)
{ {
int rv; int rv;
rv = track_decode_buf(context, nv->name); if(flags & NGHTTP2_HD_FLAG_NAME_GIFT) {
if(rv != 0) { rv = track_decode_buf(context, nv->name);
return rv; if(rv != 0) {
return rv;
}
} }
rv = track_decode_buf(context, nv->value); if(flags & NGHTTP2_HD_FLAG_VALUE_GIFT) {
if(rv != 0) { rv = track_decode_buf(context, nv->value);
return rv; if(rv != 0) {
return rv;
}
} }
return add_nva(nva_out_ptr, return add_nva(nva_out_ptr,
nv->name, nv->namelen, nv->value, nv->valuelen); nv->name, nv->namelen, nv->value, nv->valuelen);
@ -429,16 +434,19 @@ static int emit_newname_header(nghttp2_hd_context *context,
static int emit_indname_header(nghttp2_hd_context *context, static int emit_indname_header(nghttp2_hd_context *context,
nghttp2_nva_out *nva_out_ptr, nghttp2_nva_out *nva_out_ptr,
nghttp2_hd_entry *ent, nghttp2_hd_entry *ent,
uint8_t *value, size_t valuelen) uint8_t *value, size_t valuelen,
uint8_t flags)
{ {
int rv; int rv;
rv = add_emit_set(context, ent); rv = add_emit_set(context, ent);
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
rv = track_decode_buf(context, value); if(NGHTTP2_HD_FLAG_VALUE_GIFT) {
if(rv != 0) { rv = track_decode_buf(context, value);
return rv; if(rv != 0) {
return rv;
}
} }
return add_nva(nva_out_ptr, ent->nv.name, ent->nv.namelen, value, valuelen); return add_nva(nva_out_ptr, ent->nv.name, ent->nv.namelen, value, valuelen);
} }
@ -576,8 +584,12 @@ static int emit_indname_block(uint8_t **buf_ptr, size_t *buflen_ptr,
int rv; int rv;
uint8_t *bufp; uint8_t *bufp;
size_t encvallen = nghttp2_hd_huff_encode_count(value, valuelen, side); size_t encvallen = nghttp2_hd_huff_encode_count(value, valuelen, side);
size_t blocklen = count_encoded_length(index + 1, 6) + size_t blocklen = count_encoded_length(index + 1, 6);
count_encoded_length(encvallen, 8) + encvallen; int huffman = encvallen < valuelen;
if(!huffman) {
encvallen = valuelen;
}
blocklen += count_encoded_length(encvallen, 7) + encvallen;
rv = ensure_write_buffer(buf_ptr, buflen_ptr, *offset_ptr, blocklen); rv = ensure_write_buffer(buf_ptr, buflen_ptr, *offset_ptr, blocklen);
if(rv != 0) { if(rv != 0) {
return rv; return rv;
@ -585,9 +597,14 @@ static int emit_indname_block(uint8_t **buf_ptr, size_t *buflen_ptr,
bufp = *buf_ptr + *offset_ptr; bufp = *buf_ptr + *offset_ptr;
*bufp = inc_indexing ? 0 : 0x40u; *bufp = inc_indexing ? 0 : 0x40u;
bufp += encode_length(bufp, index + 1, 6); bufp += encode_length(bufp, index + 1, 6);
bufp += encode_length(bufp, encvallen, 8); *bufp = huffman ? 1 << 7 : 0;
nghttp2_hd_huff_encode(bufp, *buflen_ptr - (bufp - *buf_ptr), bufp += encode_length(bufp, encvallen, 7);
value, valuelen, side); if(huffman) {
nghttp2_hd_huff_encode(bufp, *buflen_ptr - (bufp - *buf_ptr),
value, valuelen, side);
} else {
memcpy(bufp, value, valuelen);
}
assert(bufp+encvallen - (*buf_ptr + *offset_ptr) == (ssize_t)blocklen); assert(bufp+encvallen - (*buf_ptr + *offset_ptr) == (ssize_t)blocklen);
*offset_ptr += blocklen; *offset_ptr += blocklen;
return 0; return 0;
@ -604,21 +621,40 @@ static int emit_newname_block(uint8_t **buf_ptr, size_t *buflen_ptr,
nghttp2_hd_huff_encode_count(nv->name, nv->namelen, side); nghttp2_hd_huff_encode_count(nv->name, nv->namelen, side);
size_t encvallen = size_t encvallen =
nghttp2_hd_huff_encode_count(nv->value, nv->valuelen, side); nghttp2_hd_huff_encode_count(nv->value, nv->valuelen, side);
size_t blocklen = 1 + count_encoded_length(encnamelen, 8) + encnamelen + size_t blocklen = 1;
count_encoded_length(encvallen, 8) + encvallen; int name_huffman = encnamelen < nv->namelen;
int value_huffman = encvallen < nv->valuelen;
if(!name_huffman) {
encnamelen = nv->namelen;
}
if(!value_huffman) {
encvallen = nv->valuelen;
}
blocklen += count_encoded_length(encnamelen, 7) + encnamelen +
count_encoded_length(encvallen, 7) + encvallen;
rv = ensure_write_buffer(buf_ptr, buflen_ptr, *offset_ptr, blocklen); rv = ensure_write_buffer(buf_ptr, buflen_ptr, *offset_ptr, blocklen);
if(rv != 0) { if(rv != 0) {
return rv; return rv;
} }
bufp = *buf_ptr + *offset_ptr; bufp = *buf_ptr + *offset_ptr;
*bufp++ = inc_indexing ? 0 : 0x40u; *bufp++ = inc_indexing ? 0 : 0x40u;
bufp += encode_length(bufp, encnamelen, 8); *bufp = name_huffman ? 1 << 7 : 0;
nghttp2_hd_huff_encode(bufp, *buflen_ptr - (bufp - *buf_ptr), bufp += encode_length(bufp, encnamelen, 7);
nv->name, nv->namelen, side); if(name_huffman) {
nghttp2_hd_huff_encode(bufp, *buflen_ptr - (bufp - *buf_ptr),
nv->name, nv->namelen, side);
} else {
memcpy(bufp, nv->name, nv->namelen);
}
bufp += encnamelen; bufp += encnamelen;
bufp += encode_length(bufp, encvallen, 8); *bufp = value_huffman ? 1 << 7 : 0;
nghttp2_hd_huff_encode(bufp, *buflen_ptr - (bufp - *buf_ptr), bufp += encode_length(bufp, encvallen, 7);
nv->value, nv->valuelen, side); if(value_huffman) {
nghttp2_hd_huff_encode(bufp, *buflen_ptr - (bufp - *buf_ptr),
nv->value, nv->valuelen, side);
} else {
memcpy(bufp, nv->value, nv->valuelen);
}
*offset_ptr += blocklen; *offset_ptr += blocklen;
return 0; return 0;
} }
@ -975,20 +1011,27 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater,
/* Literal Header Repr - New Name */ /* Literal Header Repr - New Name */
nghttp2_nv nv; nghttp2_nv nv;
ssize_t namelen, valuelen; ssize_t namelen, valuelen;
int name_huffman, value_huffman;
if(++in == last) { if(++in == last) {
rv = NGHTTP2_ERR_HEADER_COMP; rv = NGHTTP2_ERR_HEADER_COMP;
goto fail; goto fail;
} }
in = decode_length(&namelen, in, last, 8); name_huffman = *in & (1 << 7);
in = decode_length(&namelen, in, last, 7);
if(namelen < 0 || in + namelen > last) { if(namelen < 0 || in + namelen > last) {
rv = NGHTTP2_ERR_HEADER_COMP; rv = NGHTTP2_ERR_HEADER_COMP;
goto fail; goto fail;
} }
rv = inflate_decode(&nv.name, in, namelen, inflater->side); if(name_huffman) {
if(rv < 0) { rv = inflate_decode(&nv.name, in, namelen, inflater->side);
goto fail; if(rv < 0) {
goto fail;
}
nv.namelen = rv;
} else {
nv.name = in;
nv.namelen = namelen;
} }
nv.namelen = rv;
in += namelen; in += namelen;
if(!nghttp2_check_header_name(nv.name, nv.namelen)) { if(!nghttp2_check_header_name(nv.name, nv.namelen)) {
@ -997,35 +1040,61 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater,
goto fail; goto fail;
} }
in = decode_length(&valuelen, in, last, 8); if(in == last) {
rv = NGHTTP2_ERR_HEADER_COMP;
goto fail;
}
value_huffman = *in & (1 << 7);
in = decode_length(&valuelen, in, last, 7);
if(valuelen < 0 || in + valuelen > last) { if(valuelen < 0 || in + valuelen > last) {
free(nv.name); free(nv.name);
rv = NGHTTP2_ERR_HEADER_COMP; rv = NGHTTP2_ERR_HEADER_COMP;
goto fail; goto fail;
} }
rv = inflate_decode(&nv.value, in, valuelen, inflater->side); if(value_huffman) {
if(rv < 0) { rv = inflate_decode(&nv.value, in, valuelen, inflater->side);
free(nv.name); if(rv < 0) {
goto fail; free(nv.name);
goto fail;
}
nv.valuelen = rv;
} else {
nv.value = in;
nv.valuelen = valuelen;
} }
nv.valuelen = rv;
in += valuelen; in += valuelen;
nghttp2_downcase(nv.name, nv.namelen); nghttp2_downcase(nv.name, nv.namelen);
if(c == 0x40u) { if(c == 0x40u) {
rv = emit_newname_header(inflater, &nva_out, &nv); int flags = NGHTTP2_HD_FLAG_NONE;
if(name_huffman) {
flags |= NGHTTP2_HD_FLAG_NAME_GIFT;
}
if(value_huffman) {
flags |= NGHTTP2_HD_FLAG_VALUE_GIFT;
}
rv = emit_newname_header(inflater, &nva_out, &nv, flags);
} else { } else {
nghttp2_hd_entry *new_ent; nghttp2_hd_entry *new_ent;
uint8_t ent_flags = NGHTTP2_HD_FLAG_NAME_ALLOC |
NGHTTP2_HD_FLAG_VALUE_ALLOC;
if(name_huffman) {
ent_flags |= NGHTTP2_HD_FLAG_NAME_GIFT;
}
if(value_huffman) {
ent_flags |= NGHTTP2_HD_FLAG_VALUE_GIFT;
}
new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv, new_ent = add_hd_table_incremental(inflater, NULL, NULL, NULL, &nv,
NGHTTP2_HD_FLAG_NAME_ALLOC | ent_flags);
NGHTTP2_HD_FLAG_VALUE_ALLOC |
NGHTTP2_HD_FLAG_NAME_GIFT |
NGHTTP2_HD_FLAG_VALUE_GIFT);
if(new_ent) { if(new_ent) {
rv = emit_indexed_header(inflater, &nva_out, new_ent); rv = emit_indexed_header(inflater, &nva_out, new_ent);
} else { } else {
free(nv.value); if(value_huffman) {
free(nv.name); free(nv.value);
}
if(name_huffman) {
free(nv.name);
}
rv = NGHTTP2_ERR_HEADER_COMP; rv = NGHTTP2_ERR_HEADER_COMP;
} }
} }
@ -1037,6 +1106,7 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater,
nghttp2_hd_entry *ent; nghttp2_hd_entry *ent;
uint8_t *value; uint8_t *value;
ssize_t valuelen, index; ssize_t valuelen, index;
int value_huffman;
in = decode_length(&index, in, last, 6); in = decode_length(&index, in, last, 6);
if(index < 0) { if(index < 0) {
rv = NGHTTP2_ERR_HEADER_COMP; rv = NGHTTP2_ERR_HEADER_COMP;
@ -1048,24 +1118,41 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater,
goto fail; goto fail;
} }
ent = nghttp2_hd_table_get(inflater, index); ent = nghttp2_hd_table_get(inflater, index);
in = decode_length(&valuelen, in , last, 8); if(in == last) {
rv = NGHTTP2_ERR_HEADER_COMP;
goto fail;
}
value_huffman = *in & (1 << 7);
in = decode_length(&valuelen, in , last, 7);
if(valuelen < 0 || in + valuelen > last) { if(valuelen < 0 || in + valuelen > last) {
rv = NGHTTP2_ERR_HEADER_COMP; rv = NGHTTP2_ERR_HEADER_COMP;
goto fail; goto fail;
} }
rv = inflate_decode(&value, in, valuelen, inflater->side); if(value_huffman) {
if(rv < 0) { rv = inflate_decode(&value, in, valuelen, inflater->side);
goto fail; if(rv < 0) {
goto fail;
}
in += valuelen;
valuelen = rv;
} else {
value = in;
in += valuelen;
} }
in += valuelen;
valuelen = rv;
if((c & 0x40u) == 0x40u) { if((c & 0x40u) == 0x40u) {
rv = emit_indname_header(inflater, &nva_out, ent, value, valuelen); uint8_t flags = NGHTTP2_HD_FLAG_NONE;
if(value_huffman) {
flags = NGHTTP2_HD_FLAG_VALUE_GIFT;
}
rv = emit_indname_header(inflater, &nva_out, ent, value, valuelen,
flags);
} else { } else {
nghttp2_nv nv; nghttp2_nv nv;
nghttp2_hd_entry *new_ent; nghttp2_hd_entry *new_ent;
uint8_t ent_flags = NGHTTP2_HD_FLAG_VALUE_GIFT | uint8_t ent_flags = NGHTTP2_HD_FLAG_VALUE_ALLOC;
NGHTTP2_HD_FLAG_VALUE_ALLOC; if(value_huffman) {
ent_flags |= NGHTTP2_HD_FLAG_VALUE_GIFT;
}
++ent->ref; ++ent->ref;
nv.name = ent->nv.name; nv.name = ent->nv.name;
if((size_t)index < inflater->hd_table.len) { if((size_t)index < inflater->hd_table.len) {
@ -1083,7 +1170,9 @@ ssize_t nghttp2_hd_inflate_hd(nghttp2_hd_context *inflater,
if(new_ent) { if(new_ent) {
rv = emit_indexed_header(inflater, &nva_out, new_ent); rv = emit_indexed_header(inflater, &nva_out, new_ent);
} else { } else {
free(nv.value); if(value_huffman) {
free(nv.value);
}
rv = NGHTTP2_ERR_HEADER_COMP; rv = NGHTTP2_ERR_HEADER_COMP;
} }
} }