diff --git a/Makefile.am b/Makefile.am index f164b798..d0c93238 100644 --- a/Makefile.am +++ b/Makefile.am @@ -20,7 +20,7 @@ # 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. -SUBDIRS = lib src examples tests doc +SUBDIRS = lib src examples hdtest tests doc ACLOCAL_AMFLAGS = -I m4 diff --git a/configure.ac b/configure.ac index 17f826fa..e4c6b382 100644 --- a/configure.ac +++ b/configure.ac @@ -187,6 +187,13 @@ if test "x${have_libevent_openssl}" = "xno"; then AC_MSG_NOTICE($LIBEVENT_OPENSSL_PKG_ERRORS) 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) have_libxml2=no if test "x$with_libxml2" != "xno"; then @@ -227,6 +234,13 @@ fi 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. AC_CHECK_HEADERS([ \ arpa/inet.h \ @@ -301,6 +315,7 @@ AC_CONFIG_FILES([ tests/testdata/Makefile src/Makefile examples/Makefile + hdtest/Makefile doc/Makefile doc/conf.py ]) diff --git a/hdtest/Makefile.am b/hdtest/Makefile.am new file mode 100644 index 00000000..1b1deb88 --- /dev/null +++ b/hdtest/Makefile.am @@ -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 diff --git a/hdtest/README.rst b/hdtest/README.rst new file mode 100644 index 00000000..56a35132 --- /dev/null +++ b/hdtest/README.rst @@ -0,0 +1 @@ +Tools for header compression diff --git a/hdtest/deflatehd.c b/hdtest/deflatehd.c new file mode 100644 index 00000000..512c9af3 --- /dev/null +++ b/hdtest/deflatehd.c @@ -0,0 +1,205 @@ +#include +#include +#include +#include +#include + +#include + +#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; +} diff --git a/hdtest/inflatehd.c b/hdtest/inflatehd.c new file mode 100644 index 00000000..b7b4ee9e --- /dev/null +++ b/hdtest/inflatehd.c @@ -0,0 +1,175 @@ +#include +#include +#include +#include +#include + +#include + +#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; +}