From da9bbb58fb3cfc87f5b20f13c72320097a3642b6 Mon Sep 17 00:00:00 2001 From: Tatsuhiro Tsujikawa Date: Sat, 11 Jan 2014 15:31:36 +0900 Subject: [PATCH] HPACK tools: Use JSON data format used in hpack-test-case The input and output data format now use same JSON format used in hpack-test-case. --- src/comp_helper.c | 40 +++++++++- src/comp_helper.h | 6 ++ src/deflatehd.c | 190 +++++++++++++++++++++++++++++----------------- src/inflatehd.c | 129 +++++++++++++++++++------------ 4 files changed, 241 insertions(+), 124 deletions(-) diff --git a/src/comp_helper.c b/src/comp_helper.c index 3f4606a0..909e9a9b 100644 --- a/src/comp_helper.c +++ b/src/comp_helper.c @@ -23,6 +23,7 @@ * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #include "comp_helper.h" +#include static void dump_val(json_t *jent, const char *key, uint8_t *val, size_t len) { @@ -55,13 +56,46 @@ json_t* dump_header_table(nghttp2_hd_context *context) } json_object_set_new(obj, "entries", entries); json_object_set_new(obj, "size", json_integer(context->hd_table_bufsize)); - json_object_set_new(obj, "maxSize", + json_object_set_new(obj, "max_size", json_integer(context->hd_table_bufsize_max)); if(context->role == NGHTTP2_HD_ROLE_DEFLATE) { - json_object_set_new(obj, "deflateSize", + json_object_set_new(obj, "deflate_size", json_integer(context->deflate_hd_table_bufsize)); - json_object_set_new(obj, "maxDeflateSize", + json_object_set_new(obj, "max_deflate_size", json_integer(context->deflate_hd_table_bufsize_max)); } return obj; } + +json_t* dump_headers(const nghttp2_nv *nva, size_t nvlen) +{ + json_t *headers; + size_t i; + + headers = json_array(); + for(i = 0; i < nvlen; ++i) { + json_t *nv_pair = json_object(); + char *name = strndup((const char*)nva[i].name, nva[i].namelen); + name[nva[i].namelen] = '\0'; + json_object_set_new(nv_pair, name, + json_pack("s#", nva[i].value, nva[i].valuelen)); + free(name); + json_array_append_new(headers, nv_pair); + } + return headers; +} + +void output_json_header(int side) +{ + printf("{\n" + " \"context\": \"%s\",\n" + " \"cases\":\n" + " [\n", + (side == NGHTTP2_HD_SIDE_REQUEST ? "request" : "response")); +} + +void output_json_footer(void) +{ + printf(" ]\n" + "}\n"); +} diff --git a/src/comp_helper.h b/src/comp_helper.h index 43d8fa6c..c17aaa76 100644 --- a/src/comp_helper.h +++ b/src/comp_helper.h @@ -35,4 +35,10 @@ json_t* dump_header_table(nghttp2_hd_context *context); +json_t* dump_headers(const nghttp2_nv *nva, size_t nvlen); + +void output_json_header(int side); + +void output_json_footer(); + #endif /* NGHTTP2_COMP_HELPER_H */ diff --git a/src/deflatehd.c b/src/deflatehd.c index 62c89a87..fcce5bc0 100644 --- a/src/deflatehd.c +++ b/src/deflatehd.c @@ -55,40 +55,52 @@ static deflate_config config; static size_t input_sum; static size_t output_sum; +static char to_hex_digit(uint8_t n) +{ + if(n > 9) { + return n - 10 + 'a'; + } + return n + '0'; +} + 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; + *dest++ = to_hex_digit(src[i] >> 4); + *dest++ = to_hex_digit(src[i] & 0xf); } } static void output_to_json(nghttp2_hd_context *deflater, const uint8_t *buf, size_t len, size_t inputlen, + nghttp2_nv *nva, size_t nvlen, int seq) { json_t *obj; - char hex[16*1024]; + char *hex = NULL; - if(len * 2 > sizeof(hex)) { - fprintf(stderr, "Output too large at %d\n", seq); - exit(EXIT_FAILURE); + if(len > 0) { + hex = malloc(len * 2); } 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_object_set_new(obj, "input_length", json_integer(inputlen)); + json_object_set_new(obj, "output_length", json_integer(len)); + json_object_set_new(obj, "percentage_of_original_size", json_real((double)len / inputlen * 100)); to_hex(hex, buf, len); - json_object_set_new(obj, "output", json_pack("s#", hex, len * 2)); + json_object_set_new(obj, "wire", json_pack("s#", hex, len * 2)); + json_object_set_new(obj, "headers", dump_headers(nva, nvlen)); + json_object_set_new(obj, "header_table_size", + json_integer(config.table_size)); if(config.dump_header_table) { - json_object_set_new(obj, "headerTable", dump_header_table(deflater)); + json_object_set_new(obj, "header_table", dump_header_table(deflater)); } json_dumpf(obj, stdout, JSON_PRESERVE_ORDER | JSON_INDENT(2)); printf("\n"); json_decref(obj); + free(hex); } static void deflate_hd(nghttp2_hd_context *deflater, @@ -104,7 +116,7 @@ static void deflate_hd(nghttp2_hd_context *deflater, } input_sum += inputlen; output_sum += rv; - output_to_json(deflater, buf, rv, inputlen, seq); + output_to_json(deflater, buf, rv, inputlen, nva, nvlen, seq); nghttp2_hd_end_headers(deflater); free(buf); } @@ -119,11 +131,12 @@ static int deflate_hd_json(json_t *obj, nghttp2_hd_context *deflater, int seq) js = json_object_get(obj, "headers"); if(js == NULL) { - fprintf(stderr, "headers key is missing at %d\n", seq); + 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); + fprintf(stderr, + "The value of 'headers' key must be an array at %d\n", seq); return -1; } len = json_array_size(js); @@ -134,70 +147,101 @@ static int deflate_hd_json(json_t *obj, nghttp2_hd_context *deflater, int seq) } 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); + const char *name; + json_t *value; + if(!json_is_object(nv_pair) || json_object_size(nv_pair) != 1) { + fprintf(stderr, "bad formatted name/value pair object 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)); + json_object_foreach(nv_pair, name, value) { + nva[i].name = (uint8_t*)name; + nva[i].namelen = strlen(name); - 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; + if(!json_is_string(value)) { + fprintf(stderr, "value is not string at %d\n", seq); + return -1; + } + nva[i].value = (uint8_t*)json_string_value(value); + nva[i].valuelen = strlen(json_string_value(value)); } - nva[i].value = (uint8_t*)json_string_value(s); - nva[i].valuelen = strlen(json_string_value(s)); inputlen += nva[i].namelen + nva[i].valuelen; } deflate_hd(deflater, nva, len, inputlen, seq); return 0; } -static int perform(nghttp2_hd_context *deflater) +static void init_deflater(nghttp2_hd_context *deflater, nghttp2_hd_side side) +{ + nghttp2_hd_deflate_init2(deflater, side, config.deflate_table_size); + nghttp2_hd_deflate_set_no_refset(deflater, config.no_refset); + nghttp2_hd_change_table_size(deflater, config.table_size); +} + +static void deinit_deflater(nghttp2_hd_context *deflater) +{ + nghttp2_hd_deflate_free(deflater); +} + +static int perform(void) { size_t i; - json_t *json; + json_t *json, *cases; json_error_t error; size_t len; + nghttp2_hd_context deflater; + nghttp2_hd_side side; + json = json_loadf(stdin, 0, &error); if(json == NULL) { fprintf(stderr, "JSON loading failed\n"); exit(EXIT_FAILURE); } - printf("[\n"); - len = json_array_size(json); + if(strcmp("request", json_string_value(json_object_get(json, "context"))) + == 0) { + side = NGHTTP2_HD_SIDE_REQUEST; + } else { + side = NGHTTP2_HD_SIDE_RESPONSE; + } + cases = json_object_get(json, "cases"); + if(cases == NULL) { + fprintf(stderr, "Missing 'cases' key in root object\n"); + exit(EXIT_FAILURE); + } + if(!json_is_array(cases)) { + fprintf(stderr, "'cases' must be JSON array\n"); + exit(EXIT_FAILURE); + } + init_deflater(&deflater, side); + output_json_header(side); + len = json_array_size(cases); for(i = 0; i < len; ++i) { - json_t *obj = json_array_get(json, i); + json_t *obj = json_array_get(cases, i); if(!json_is_object(obj)) { fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i); continue; } - if(deflate_hd_json(obj, deflater, i) != 0) { + if(deflate_hd_json(obj, &deflater, i) != 0) { continue; } if(i + 1 < len) { printf(",\n"); } } - printf("]\n"); + output_json_footer(); + deinit_deflater(&deflater); json_decref(json); return 0; } -static int perform_from_http1text(nghttp2_hd_context *deflater) +static int perform_from_http1text(void) { char line[1 << 14]; nghttp2_nv nva[256]; int seq = 0; - printf("[\n"); + nghttp2_hd_context deflater; + init_deflater(&deflater, config.side); + output_json_header(config.side); for(;;) { size_t nvlen = 0; int end = 0; @@ -239,7 +283,7 @@ static int perform_from_http1text(nghttp2_hd_context *deflater) if(seq > 0) { printf(",\n"); } - deflate_hd(deflater, nva, nvlen, inputlen, seq); + deflate_hd(&deflater, nva, nvlen, inputlen, seq); } for(i = 0; i < nvlen; ++i) { @@ -249,7 +293,8 @@ static int perform_from_http1text(nghttp2_hd_context *deflater) if(end) break; ++seq; } - printf("]\n"); + output_json_footer(); + deinit_deflater(&deflater); return 0; } @@ -258,32 +303,38 @@ static void print_help(void) printf("HPACK HTTP/2.0 header encoder\n" "Usage: deflatehd [OPTIONS] < INPUT\n" "\n" - "Reads JSON array or HTTP/1-style header fields from stdin and\n" + "Reads JSON data or HTTP/1-style header fields from stdin and\n" "outputs deflated header block in JSON array.\n" "\n" - "For the JSON input, the element of input array must be a JSON\n" - "object. Each object must have at least 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" + "For the JSON input, the root JSON object must contain \"context\"\n" + "key, which indicates which compression context is used. If it is\n" + "\"request\", request compression context is used. Otherwise,\n" + "response compression context is used. The value of \"cases\" key\n" + "contains the sequence of input header set. They share the same\n" + "compression context and are processed in the order they appear.\n" + "Each item in the sequence is a JSON object and it must have at\n" + "least \"headers\" key. Its value is an array of a JSON object\n" + "containing exactly one name/value pair.\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" + " \"context\": \"request\",\n" + " \"cases\":\n" + " [\n" + " {\n" + " \"headers\": [\n" + " { \":method\": \"GET\" },\n" + " { \":path\": \"/\" }\n" + " ]\n" + " },\n" + " {\n" + " \"headers\": [\n" + " { \":method\": \"POST\" },\n" + " { \":path\": \"/\" }\n" + " ]\n" + " }\n" + " ]\n" + "}\n" "\n" "With -t option, the program can accept more familiar HTTP/1 style\n" "header field block. Each header set must be followed by one empty\n" @@ -301,7 +352,9 @@ static void print_help(void) "\n" "OPTIONS:\n" " -r, --response Use response compression context instead of\n" - " request.\n" + " request if -t is used. For JSON input, it is\n" + " determined by inspecting \"context\" key in\n" + " root JSON object.\n" " -t, --http1text Use HTTP/1 style header field text as input.\n" " Each header set is delimited by single empty\n" " line.\n" @@ -331,7 +384,6 @@ static struct option long_options[] = { int main(int argc, char **argv) { - nghttp2_hd_context deflater; char *end; config.side = NGHTTP2_HD_SIDE_REQUEST; @@ -388,16 +440,12 @@ int main(int argc, char **argv) break; } } - nghttp2_hd_deflate_init2(&deflater, config.side, config.deflate_table_size); - nghttp2_hd_deflate_set_no_refset(&deflater, config.no_refset); - nghttp2_hd_change_table_size(&deflater, config.table_size); if(config.http1text) { - perform_from_http1text(&deflater); + perform_from_http1text(); } else { - perform(&deflater); + perform(); } fprintf(stderr, "Overall: input=%zu output=%zu ratio=%.02f\n", input_sum, output_sum, (double)output_sum / input_sum); - nghttp2_hd_deflate_free(&deflater); return 0; } diff --git a/src/inflatehd.c b/src/inflatehd.c index 0c71fc5f..0891d716 100644 --- a/src/inflatehd.c +++ b/src/inflatehd.c @@ -43,7 +43,6 @@ typedef struct { size_t table_size; - nghttp2_hd_side side; int dump_header_table; } inflate_config; @@ -69,24 +68,19 @@ static void decode_hex(uint8_t *dest, const char *src, size_t len) } static void nva_to_json(nghttp2_hd_context *inflater, - const nghttp2_nv *nva, size_t nvlen, int seq) + const nghttp2_nv *nva, size_t nvlen, + json_t *wire, 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_object_set(obj, "wire", wire); + json_object_set_new(obj, "headers", dump_headers(nva, nvlen)); + json_object_set_new(obj, "header_table_size", + json_integer(inflater->hd_table_bufsize_max)); if(config.dump_header_table) { - json_object_set_new(obj, "headerTable", dump_header_table(inflater)); + json_object_set_new(obj, "header_table", dump_header_table(inflater)); } json_dumpf(obj, stdout, JSON_INDENT(2) | JSON_PRESERVE_ORDER); json_decref(obj); @@ -95,27 +89,42 @@ static void nva_to_json(nghttp2_hd_context *inflater, static int inflate_hd(json_t *obj, nghttp2_hd_context *inflater, int seq) { - json_t *js; + json_t *wire, *table_size; size_t inputlen; - uint8_t buf[16*1024]; + uint8_t *buf; ssize_t resnvlen; nghttp2_nv *resnva; + int rv; - js = json_object_get(obj, "output"); - if(js == NULL) { - fprintf(stderr, "output key is missing at %d\n", seq); + wire = json_object_get(obj, "wire"); + if(wire == NULL) { + fprintf(stderr, "'wire' key is missing at %d\n", seq); return -1; } - inputlen = strlen(json_string_value(js)); + table_size = json_object_get(obj, "header_table_size"); + if(table_size) { + if(!json_is_integer(table_size)) { + fprintf(stderr, + "The value of 'header_table_size key' is not integer at %d\n", + seq); + return -1; + } + rv = nghttp2_hd_change_table_size(inflater, + json_integer_value(table_size)); + if(rv != 0) { + fprintf(stderr, + "nghttp2_hd_change_table_size() failed with error %s at %d\n", + nghttp2_strerror(rv), seq); + return -1; + } + } + inputlen = strlen(json_string_value(wire)); 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); + buf = malloc(inputlen / 2); + decode_hex(buf, json_string_value(wire), inputlen); resnvlen = nghttp2_hd_inflate_hd(inflater, &resnva, buf, inputlen / 2); if(resnvlen < 0) { @@ -123,9 +132,10 @@ static int inflate_hd(json_t *obj, nghttp2_hd_context *inflater, int seq) resnvlen, seq); exit(EXIT_FAILURE); } - nva_to_json(inflater, resnva, resnvlen, seq); + nva_to_json(inflater, resnva, resnvlen, wire, seq); nghttp2_hd_end_headers(inflater); nghttp2_nv_array_del(resnva); + free(buf); return 0; } @@ -133,21 +143,38 @@ static int perform(void) { nghttp2_hd_context inflater; size_t i; - json_t *json; + json_t *json, *cases; json_error_t error; size_t len; + nghttp2_hd_side side; json = json_loadf(stdin, 0, &error); if(json == NULL) { fprintf(stderr, "JSON loading failed\n"); exit(EXIT_FAILURE); } - nghttp2_hd_inflate_init(&inflater, config.side); + if(strcmp("request", json_string_value(json_object_get(json, "context"))) + == 0) { + side = NGHTTP2_HD_SIDE_REQUEST; + } else { + side = NGHTTP2_HD_SIDE_RESPONSE; + } + cases = json_object_get(json, "cases"); + if(cases == NULL) { + fprintf(stderr, "Missing 'cases' key in root object\n"); + exit(EXIT_FAILURE); + } + if(!json_is_array(cases)) { + fprintf(stderr, "'cases' must be JSON array\n"); + exit(EXIT_FAILURE); + } + nghttp2_hd_inflate_init(&inflater, side); nghttp2_hd_change_table_size(&inflater, config.table_size); - printf("[\n"); - len = json_array_size(json); + + output_json_header(side); + len = json_array_size(cases); for(i = 0; i < len; ++i) { - json_t *obj = json_array_get(json, i); + json_t *obj = json_array_get(cases, i); if(!json_is_object(obj)) { fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i); @@ -160,7 +187,7 @@ static int perform(void) printf(",\n"); } } - printf("]\n"); + output_json_footer(); nghttp2_hd_inflate_free(&inflater); json_decref(json); return 0; @@ -171,24 +198,32 @@ static void print_help(void) printf("HPACK HTTP/2.0 header decoder\n" "Usage: inflatehd [OPTIONS] < 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 at least following key:\n" + "Reads JSON data from stdin and outputs inflated name/value pairs\n" + "in JSON.\n" "\n" - " output: deflated header block in hex-string.\n" + "The root JSON object must contain \"context\" key, which indicates\n" + "which compression context is used. If it is \"request\", request\n" + "compression context is used. Otherwise, response compression\n" + "context is used. The value of \"cases\" key contains the sequence\n" + "of compressed header block. They share the same compression\n" + "context and are processed in the order they appear. Each item in\n" + "the sequence is a JSON object and it must have at least \"wire\"\n" + "key. Its value is a string containing compressed header block in\n" + "hex string.\n" "\n" "Example:\n" - "[\n" - " { \"output\": \"0284f77778ff\" },\n" - " { \"output\": \"0185fafd3c3c7f81\" }\n" - "]\n" + "{\n" + " \"context\": \"request\",\n" + " \"cases\":\n" + " [\n" + " { \"wire\": \"0284f77778ff\" },\n" + " { \"wire\": \"0185fafd3c3c7f81\" }\n" + " ]\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" " -s, --table-size=\n" " Set dynamic table size. In the HPACK\n" " specification, this value is denoted by\n" @@ -199,7 +234,6 @@ static void print_help(void) } static struct option long_options[] = { - {"response", no_argument, NULL, 'r'}, {"table-size", required_argument, NULL, 's'}, {"dump-header-table", no_argument, NULL, 'd'}, {NULL, 0, NULL, 0 } @@ -208,20 +242,15 @@ static struct option long_options[] = { int main(int argc, char **argv) { char *end; - config.side = NGHTTP2_HD_SIDE_REQUEST; config.table_size = NGHTTP2_HD_DEFAULT_MAX_BUFFER_SIZE; config.dump_header_table = 0; while(1) { int option_index = 0; - int c = getopt_long(argc, argv, "dhrs:", long_options, &option_index); + int c = getopt_long(argc, argv, "dhs:", long_options, &option_index); if(c == -1) { break; } switch(c) { - case 'r': - /* --response */ - config.side = NGHTTP2_HD_SIDE_RESPONSE; - break; case 'h': print_help(); exit(EXIT_SUCCESS);