hdtest: Add HPACK test tools
These test tools uses JSON as input/output and could be handy to produce compressed header blocks.
This commit is contained in:
parent
d5ccc89fc4
commit
88348a600b
|
@ -20,7 +20,7 @@
|
||||||
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
SUBDIRS = lib src examples tests doc
|
SUBDIRS = lib src examples hdtest tests doc
|
||||||
|
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
|
|
||||||
|
|
15
configure.ac
15
configure.ac
|
@ -187,6 +187,13 @@ if test "x${have_libevent_openssl}" = "xno"; then
|
||||||
AC_MSG_NOTICE($LIBEVENT_OPENSSL_PKG_ERRORS)
|
AC_MSG_NOTICE($LIBEVENT_OPENSSL_PKG_ERRORS)
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# jansson (for hdtest/deflatehd and hdtest/inflatehd)
|
||||||
|
PKG_CHECK_MODULES([JANSSON], [jansson >= 2.5],
|
||||||
|
[have_jansson=yes], [have_jansson=no])
|
||||||
|
if test "x${have_jansson}" == "xno"; then
|
||||||
|
AC_MSG_NOTICE($JANSSON_PKG_ERRORS)
|
||||||
|
fi
|
||||||
|
|
||||||
# libxml2 (for src/nghttp)
|
# libxml2 (for src/nghttp)
|
||||||
have_libxml2=no
|
have_libxml2=no
|
||||||
if test "x$with_libxml2" != "xno"; then
|
if test "x$with_libxml2" != "xno"; then
|
||||||
|
@ -227,6 +234,13 @@ fi
|
||||||
|
|
||||||
AM_CONDITIONAL([ENABLE_EXAMPLES], [ test "x${enable_examples}" = "xyes" ])
|
AM_CONDITIONAL([ENABLE_EXAMPLES], [ test "x${enable_examples}" = "xyes" ])
|
||||||
|
|
||||||
|
# hdtest requires jansson
|
||||||
|
if test "x${have_jansson}" = "xyes"; then
|
||||||
|
enable_hdtest=yes
|
||||||
|
fi
|
||||||
|
|
||||||
|
AM_CONDITIONAL([ENABLE_HDTEST], [ test "x${enable_hdtest}" = "xyes" ])
|
||||||
|
|
||||||
# Checks for header files.
|
# Checks for header files.
|
||||||
AC_CHECK_HEADERS([ \
|
AC_CHECK_HEADERS([ \
|
||||||
arpa/inet.h \
|
arpa/inet.h \
|
||||||
|
@ -301,6 +315,7 @@ AC_CONFIG_FILES([
|
||||||
tests/testdata/Makefile
|
tests/testdata/Makefile
|
||||||
src/Makefile
|
src/Makefile
|
||||||
examples/Makefile
|
examples/Makefile
|
||||||
|
hdtest/Makefile
|
||||||
doc/Makefile
|
doc/Makefile
|
||||||
doc/conf.py
|
doc/conf.py
|
||||||
])
|
])
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
# nghttp2 - HTTP/2.0 C Library
|
||||||
|
|
||||||
|
# Copyright (c) 2013 Tatsuhiro Tsujikawa
|
||||||
|
|
||||||
|
# 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.
|
||||||
|
|
||||||
|
if ENABLE_HDTEST
|
||||||
|
|
||||||
|
AM_CFLAGS = -Wall
|
||||||
|
AM_CPPFLAGS = -Wall -I$(srcdir)/../lib/includes -I$(builddir)/../lib/includes \
|
||||||
|
-I$(srcdir)/../lib @JANSSON_CFLAGS@ @DEFS@
|
||||||
|
AM_LDFLAGS = @JANSSON_LIBS@
|
||||||
|
LDADD = $(top_builddir)/lib/libnghttp2.la
|
||||||
|
|
||||||
|
bin_PROGRAMS = inflatehd deflatehd
|
||||||
|
|
||||||
|
inflatehd_SOURCES = inflatehd.c
|
||||||
|
|
||||||
|
deflatehd_SOURCES = deflatehd.c
|
||||||
|
|
||||||
|
endif # ENABLE_HDTEST
|
||||||
|
|
||||||
|
EXTRA_DIST = README.rst
|
|
@ -0,0 +1 @@
|
||||||
|
Tools for header compression
|
|
@ -0,0 +1,205 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <jansson.h>
|
||||||
|
|
||||||
|
#include "nghttp2_hd.h"
|
||||||
|
|
||||||
|
static void to_hex(char *dest, const uint8_t *src, size_t len)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
for(i = 0; i < len; ++i) {
|
||||||
|
sprintf(dest, "%02x", src[i]);
|
||||||
|
dest += 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void output_to_json(const uint8_t *buf, size_t len, size_t inputlen,
|
||||||
|
int seq)
|
||||||
|
{
|
||||||
|
json_t *obj;
|
||||||
|
char hex[16*1024];
|
||||||
|
|
||||||
|
if(len * 2 > sizeof(hex)) {
|
||||||
|
fprintf(stderr, "Output too large at %d\n", seq);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
obj = json_object();
|
||||||
|
json_object_set_new(obj, "seq", json_integer(seq));
|
||||||
|
json_object_set_new(obj, "inputLen", json_integer(inputlen));
|
||||||
|
json_object_set_new(obj, "outputLength", json_integer(len));
|
||||||
|
json_object_set_new(obj, "PercentageOfOriginalSize",
|
||||||
|
json_real((double)len / inputlen * 100));
|
||||||
|
to_hex(hex, buf, len);
|
||||||
|
json_object_set_new(obj, "output", json_pack("s#", hex, len * 2));
|
||||||
|
json_dumpf(obj, stdout, JSON_PRESERVE_ORDER);
|
||||||
|
printf("\n");
|
||||||
|
json_decref(obj);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int deflate_hd(json_t *obj, nghttp2_hd_context *deflater, int seq)
|
||||||
|
{
|
||||||
|
json_t *js;
|
||||||
|
uint8_t *buf = NULL;
|
||||||
|
size_t buflen = 0;
|
||||||
|
nghttp2_nv nva[128];
|
||||||
|
size_t len;
|
||||||
|
size_t i;
|
||||||
|
ssize_t rv;
|
||||||
|
size_t inputlen = 0;
|
||||||
|
|
||||||
|
js = json_object_get(obj, "headers");
|
||||||
|
if(js == NULL) {
|
||||||
|
fprintf(stderr, "headers key is missing at %d\n", seq);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
if(!json_is_array(js)) {
|
||||||
|
fprintf(stderr, "headers value must be an array at %d\n", seq);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
len = json_array_size(js);
|
||||||
|
if(len > sizeof(nva)/sizeof(nva[0])) {
|
||||||
|
fprintf(stderr, "Too many headers (> %zu) at %d\n",
|
||||||
|
sizeof(nva)/sizeof(nva[0]), seq);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
for(i = 0; i < len; ++i) {
|
||||||
|
json_t *nv_pair = json_array_get(js, i);
|
||||||
|
json_t *s;
|
||||||
|
if(!json_is_array(nv_pair) || json_array_size(nv_pair) != 2) {
|
||||||
|
fprintf(stderr, "bad formatted name/value pair array at %d\n", seq);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
s = json_array_get(nv_pair, 0);
|
||||||
|
if(!json_is_string(s)) {
|
||||||
|
fprintf(stderr, "bad formatted name/value pair array at %d\n", seq);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
nva[i].name = (uint8_t*)json_string_value(s);
|
||||||
|
nva[i].namelen = strlen(json_string_value(s));
|
||||||
|
|
||||||
|
s = json_array_get(nv_pair, 1);
|
||||||
|
if(!json_is_string(s)) {
|
||||||
|
fprintf(stderr, "bad formatted name/value pair array at %d\n", seq);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
nva[i].value = (uint8_t*)json_string_value(s);
|
||||||
|
nva[i].valuelen = strlen(json_string_value(s));
|
||||||
|
inputlen += nva[i].namelen + nva[i].valuelen;
|
||||||
|
}
|
||||||
|
rv = nghttp2_hd_deflate_hd(deflater, &buf, &buflen, 0, nva, len);
|
||||||
|
if(rv < 0) {
|
||||||
|
fprintf(stderr, "deflate failed with error code %zd at %d\n", rv, seq);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
output_to_json(buf, rv, inputlen, seq);
|
||||||
|
nghttp2_hd_end_headers(deflater);
|
||||||
|
free(buf);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int perform(nghttp2_hd_side side)
|
||||||
|
{
|
||||||
|
nghttp2_hd_context deflater;
|
||||||
|
int i = 0;
|
||||||
|
json_t *json;
|
||||||
|
json_error_t error;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
json = json_loadf(stdin, 0, &error);
|
||||||
|
if(json == NULL) {
|
||||||
|
fprintf(stderr, "JSON loading failed\n");
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
nghttp2_hd_deflate_init(&deflater, side);
|
||||||
|
printf("[\n");
|
||||||
|
len = json_array_size(json);
|
||||||
|
for(i = 0; i < len; ++i) {
|
||||||
|
json_t *obj = json_array_get(json, i);
|
||||||
|
if(!json_is_object(obj)) {
|
||||||
|
fprintf(stderr, "Unexpected JSON type at %d. It should be object.\n", i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(deflate_hd(obj, &deflater, i) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(i + 1 < len) {
|
||||||
|
printf(",\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("]\n");
|
||||||
|
nghttp2_hd_deflate_free(&deflater);
|
||||||
|
json_decref(json);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_help()
|
||||||
|
{
|
||||||
|
printf("Usage: deflatehd [-r] < INPUT\n\n"
|
||||||
|
"Reads JSON array from stdin and outputs deflated header block\n"
|
||||||
|
"in JSON array.\n"
|
||||||
|
"The element of input array must be a JSON object. Each object must\n"
|
||||||
|
"have following key:\n"
|
||||||
|
"\n"
|
||||||
|
" headers: a JSON array of name/value pairs. The each element is\n"
|
||||||
|
" a JSON array of 2 strings. The index 0 must\n"
|
||||||
|
" contain header name and the index 1 must contain\n"
|
||||||
|
" header value.\n"
|
||||||
|
"\n"
|
||||||
|
"Example:\n"
|
||||||
|
"[\n"
|
||||||
|
" {\n"
|
||||||
|
" \"headers\": [\n"
|
||||||
|
" [ \":method\", \"GET\" ],\n"
|
||||||
|
" [ \":path\", \"/\" ]\n"
|
||||||
|
" ]\n"
|
||||||
|
" },\n"
|
||||||
|
" {\n"
|
||||||
|
" \"headers\": [\n"
|
||||||
|
" [ \":method\", \"POST\" ],\n"
|
||||||
|
" [ \":path\", \"/\" ]\n"
|
||||||
|
" ]\n"
|
||||||
|
" }\n"
|
||||||
|
"]\n"
|
||||||
|
"\n"
|
||||||
|
"The output of this program can be used as input for inflatehd.\n"
|
||||||
|
"\n"
|
||||||
|
"OPTIONS:\n"
|
||||||
|
" -r, --response Use response compression context instead of\n"
|
||||||
|
" request.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"response", no_argument, NULL, 'r'},
|
||||||
|
{NULL, 0, NULL, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
nghttp2_hd_side side = NGHTTP2_HD_SIDE_REQUEST;
|
||||||
|
while(1) {
|
||||||
|
int option_index = 0;
|
||||||
|
int c = getopt_long(argc, argv, "hr", long_options, &option_index);
|
||||||
|
if(c == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch(c) {
|
||||||
|
case 'r':
|
||||||
|
/* --response */
|
||||||
|
side = NGHTTP2_HD_SIDE_RESPONSE;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
print_help();
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
case '?':
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
perform(side);
|
||||||
|
return 0;
|
||||||
|
}
|
|
@ -0,0 +1,175 @@
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <getopt.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <jansson.h>
|
||||||
|
|
||||||
|
#include "nghttp2_hd.h"
|
||||||
|
#include "nghttp2_frame.h"
|
||||||
|
|
||||||
|
static uint8_t to_ud(char c)
|
||||||
|
{
|
||||||
|
if(c >= 'A' && c <= 'Z') {
|
||||||
|
return c - 'A' + 10;
|
||||||
|
} else if(c >= 'a' && c <= 'z') {
|
||||||
|
return c - 'a' + 10;
|
||||||
|
} else {
|
||||||
|
return c - '0';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void decode_hex(uint8_t *dest, const char *src, size_t len)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
for(i = 0; i < len; i += 2) {
|
||||||
|
*dest++ = to_ud(src[i]) << 4 | to_ud(src[i + 1]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void nva_to_json(const nghttp2_nv *nva, size_t nvlen, int seq)
|
||||||
|
{
|
||||||
|
size_t i;
|
||||||
|
json_t *obj;
|
||||||
|
json_t *headers;
|
||||||
|
obj = json_object();
|
||||||
|
json_object_set_new(obj, "seq", json_integer(seq));
|
||||||
|
headers = json_array();
|
||||||
|
json_object_set_new(obj, "headers", headers);
|
||||||
|
for(i = 0; i < nvlen; ++i) {
|
||||||
|
json_t *nv_pair = json_array();
|
||||||
|
const nghttp2_nv *nv = &nva[i];
|
||||||
|
json_array_append_new(nv_pair, json_pack("s#", nv->name, nv->namelen));
|
||||||
|
json_array_append_new(nv_pair, json_pack("s#", nv->value, nv->valuelen));
|
||||||
|
json_array_append_new(headers, nv_pair);
|
||||||
|
}
|
||||||
|
json_dumpf(obj, stdout, JSON_INDENT(2) | JSON_PRESERVE_ORDER);
|
||||||
|
json_decref(obj);
|
||||||
|
printf("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int inflate_hd(json_t *obj, nghttp2_hd_context *inflater, int seq)
|
||||||
|
{
|
||||||
|
json_t *js;
|
||||||
|
size_t inputlen;
|
||||||
|
uint8_t buf[16*1024];
|
||||||
|
ssize_t resnvlen;
|
||||||
|
nghttp2_nv *resnva;
|
||||||
|
|
||||||
|
js = json_object_get(obj, "output");
|
||||||
|
if(js == NULL) {
|
||||||
|
fprintf(stderr, "output key is missing at %d\n", seq);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
inputlen = strlen(json_string_value(js));
|
||||||
|
if(inputlen & 1) {
|
||||||
|
fprintf(stderr, "Badly formatted output value at %d\n", seq);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
if(inputlen / 2 > sizeof(buf)) {
|
||||||
|
fprintf(stderr, "Too big input length %zu at %d\n", inputlen / 2, seq);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
decode_hex(buf, json_string_value(js), inputlen);
|
||||||
|
|
||||||
|
resnvlen = nghttp2_hd_inflate_hd(inflater, &resnva, buf, inputlen / 2);
|
||||||
|
if(resnvlen < 0) {
|
||||||
|
fprintf(stderr, "inflate failed with error code %zd at %d\n",
|
||||||
|
resnvlen, seq);
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
nva_to_json(resnva, resnvlen, seq);
|
||||||
|
nghttp2_hd_end_headers(inflater);
|
||||||
|
nghttp2_nv_array_del(resnva);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int perform(nghttp2_hd_side side)
|
||||||
|
{
|
||||||
|
nghttp2_hd_context inflater;
|
||||||
|
int i = 0;
|
||||||
|
json_t *json;
|
||||||
|
json_error_t error;
|
||||||
|
size_t len;
|
||||||
|
|
||||||
|
json = json_loadf(stdin, 0, &error);
|
||||||
|
if(json == NULL) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
nghttp2_hd_inflate_init(&inflater, side);
|
||||||
|
printf("[\n");
|
||||||
|
len = json_array_size(json);
|
||||||
|
for(i = 0; i < len; ++i) {
|
||||||
|
json_t *obj = json_array_get(json, i);
|
||||||
|
if(!json_is_object(obj)) {
|
||||||
|
fprintf(stderr, "Unexpected JSON type at %d. It should be object.\n", i);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(inflate_hd(obj, &inflater, i) != 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if(i + 1 < len) {
|
||||||
|
printf(",\n");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
printf("]\n");
|
||||||
|
nghttp2_hd_inflate_free(&inflater);
|
||||||
|
json_decref(json);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_help()
|
||||||
|
{
|
||||||
|
printf("Usage: inflatehd [-r] < INPUT\n\n"
|
||||||
|
"Reads JSON array from stdin and outputs inflated name/value pairs\n"
|
||||||
|
"in JSON array.\n"
|
||||||
|
"The element of input array must be a JSON object. Each object must\n"
|
||||||
|
"have following key:\n"
|
||||||
|
"\n"
|
||||||
|
" output: deflated header block in hex-string.\n"
|
||||||
|
"\n"
|
||||||
|
"Example:\n"
|
||||||
|
"[\n"
|
||||||
|
" { \"output\": \"0284f77778ff\" },\n"
|
||||||
|
" { \"output\": \"0185fafd3c3c7f81\" }\n"
|
||||||
|
"]\n"
|
||||||
|
"\n"
|
||||||
|
"The output of this program can be used as input for deflatehd.\n"
|
||||||
|
"\n"
|
||||||
|
"OPTIONS:\n"
|
||||||
|
" -r, --response Use response compression context instead of\n"
|
||||||
|
" request.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static struct option long_options[] = {
|
||||||
|
{"response", no_argument, NULL, 'r'},
|
||||||
|
{NULL, 0, NULL, 0 }
|
||||||
|
};
|
||||||
|
|
||||||
|
int main(int argc, char **argv)
|
||||||
|
{
|
||||||
|
nghttp2_hd_side side = NGHTTP2_HD_SIDE_REQUEST;
|
||||||
|
while(1) {
|
||||||
|
int option_index = 0;
|
||||||
|
int c = getopt_long(argc, argv, "hr", long_options, &option_index);
|
||||||
|
if(c == -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch(c) {
|
||||||
|
case 'r':
|
||||||
|
/* --response */
|
||||||
|
side = NGHTTP2_HD_SIDE_RESPONSE;
|
||||||
|
break;
|
||||||
|
case 'h':
|
||||||
|
print_help();
|
||||||
|
exit(EXIT_SUCCESS);
|
||||||
|
case '?':
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
perform(side);
|
||||||
|
return 0;
|
||||||
|
}
|
Loading…
Reference in New Issue