diff --git a/src/Makefile.am b/src/Makefile.am index 757f4279..9f9a7e41 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -141,8 +141,8 @@ bin_PROGRAMS += inflatehd deflatehd HPACK_TOOLS_COMMON_SRCS = comp_helper.c comp_helper.h -inflatehd_SOURCES = inflatehd.c $(HPACK_TOOLS_COMMON_SRCS) +inflatehd_SOURCES = inflatehd.cc $(HPACK_TOOLS_COMMON_SRCS) -deflatehd_SOURCES = deflatehd.c $(HPACK_TOOLS_COMMON_SRCS) +deflatehd_SOURCES = deflatehd.cc $(HPACK_TOOLS_COMMON_SRCS) endif # ENABLE_HPACK_TOOLS diff --git a/src/deflatehd.c b/src/deflatehd.cc similarity index 63% rename from src/deflatehd.c rename to src/deflatehd.cc index 2ed602a5..f524e703 100644 --- a/src/deflatehd.c +++ b/src/deflatehd.cc @@ -26,21 +26,28 @@ # include #endif /* HAVE_CONFIG_H */ -#include -#include #include #include -#include -#include -#include + +#include +#include +#include +#include +#include +#include +#include #include +extern "C" { + #include "nghttp2_hd.h" #include "nghttp2_frame.h" #include "comp_helper.h" +} + typedef struct { size_t table_size; size_t deflate_table_size; @@ -73,31 +80,22 @@ static void to_hex(char *dest, const uint8_t *src, size_t len) static void output_to_json(nghttp2_hd_deflater *deflater, nghttp2_bufs *bufs, size_t inputlen, - nghttp2_nv *nva, size_t nvlen, + const std::vector nva, int seq) { - json_t *obj; - char *hex = NULL, *hexp; - size_t len; - nghttp2_buf_chain *ci; - nghttp2_buf *buf; + auto len = nghttp2_bufs_len(bufs); + auto hex = std::vector(len * 2); + auto obj = json_object(); - len = nghttp2_bufs_len(bufs); - - if(len > 0) { - hex = malloc(len * 2); - } - - obj = json_object(); json_object_set_new(obj, "seq", json_integer(seq)); 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)); - hexp = hex; - for(ci = bufs->head; ci; ci = ci->next) { - buf = &ci->buf; + auto hexp = hex.data(); + for(auto ci = bufs->head; ci; ci = ci->next) { + auto buf = &ci->buf; to_hex(hexp, buf->pos, nghttp2_buf_len(buf)); hexp += nghttp2_buf_len(buf); } @@ -105,9 +103,9 @@ static void output_to_json(nghttp2_hd_deflater *deflater, if(len == 0) { json_object_set_new(obj, "wire", json_string("")); } else { - json_object_set_new(obj, "wire", json_pack("s#", hex, len * 2)); + json_object_set_new(obj, "wire", json_pack("s#", hex.data(), hex.size())); } - json_object_set_new(obj, "headers", dump_headers(nva, nvlen)); + json_object_set_new(obj, "headers", dump_headers(nva.data(), nva.size())); if(seq == 0) { /* We only change the header table size only once at the beginning */ json_object_set_new(obj, "header_table_size", @@ -120,40 +118,37 @@ static void output_to_json(nghttp2_hd_deflater *deflater, json_dumpf(obj, stdout, JSON_PRESERVE_ORDER | JSON_INDENT(2)); printf("\n"); json_decref(obj); - free(hex); } static void deflate_hd(nghttp2_hd_deflater *deflater, - nghttp2_nv *nva, size_t nvlen, size_t inputlen, int seq) + const std::vector& nva, + size_t inputlen, int seq) { ssize_t rv; nghttp2_bufs bufs; nghttp2_bufs_init2(&bufs, 4096, 16, 0); - rv = nghttp2_hd_deflate_hd(deflater, &bufs, nva, nvlen); + rv = nghttp2_hd_deflate_hd(deflater, &bufs, + (nghttp2_nv*)nva.data(), nva.size()); if(rv < 0) { fprintf(stderr, "deflate failed with error code %zd at %d\n", rv, seq); exit(EXIT_FAILURE); } input_sum += inputlen; - output_sum += rv; + output_sum += nghttp2_bufs_len(&bufs); - output_to_json(deflater, &bufs, inputlen, nva, nvlen, seq); + output_to_json(deflater, &bufs, inputlen, nva, seq); nghttp2_bufs_free(&bufs); } static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater, int seq) { - json_t *js; - nghttp2_nv nva[128]; - size_t len; - size_t i; size_t inputlen = 0; - js = json_object_get(obj, "headers"); - if(js == NULL) { + auto js = json_object_get(obj, "headers"); + if(js == nullptr) { fprintf(stderr, "'headers' key is missing at %d\n", seq); return -1; } @@ -162,20 +157,20 @@ static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater, int seq) "The value of 'headers' key 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); + + auto len = json_array_size(js); + auto nva = std::vector(len); + + for(size_t i = 0; i < len; ++i) { + auto nv_pair = json_array_get(js, i); 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; } + json_object_foreach(nv_pair, name, value) { nva[i].name = (uint8_t*)name; nva[i].namelen = strlen(name); @@ -184,12 +179,16 @@ static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater, int seq) 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)); } + inputlen += nva[i].namelen + nva[i].valuelen; } - deflate_hd(deflater, nva, len, inputlen, seq); + + deflate_hd(deflater, nva, inputlen, seq); + return 0; } @@ -207,31 +206,34 @@ static void deinit_deflater(nghttp2_hd_deflater *deflater) static int perform(void) { - size_t i; - json_t *json, *cases; json_error_t error; - size_t len; nghttp2_hd_deflater deflater; - json = json_loadf(stdin, 0, &error); - if(json == NULL) { + auto json = json_loadf(stdin, 0, &error); + + if(json == nullptr) { fprintf(stderr, "JSON loading failed\n"); exit(EXIT_FAILURE); } - cases = json_object_get(json, "cases"); - if(cases == NULL) { + + auto cases = json_object_get(json, "cases"); + + if(cases == nullptr) { 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); output_json_header(); - len = json_array_size(cases); - for(i = 0; i < len; ++i) { - json_t *obj = json_array_get(cases, i); + auto len = json_array_size(cases); + + for(size_t i = 0; i < len; ++i) { + auto obj = json_array_get(cases, i); if(!json_is_object(obj)) { fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i); @@ -253,7 +255,7 @@ static int perform(void) static int perform_from_http1text(void) { char line[1 << 14]; - nghttp2_nv nva[256]; + std::vector nva; int seq = 0; nghttp2_hd_deflater deflater; init_deflater(&deflater); @@ -267,16 +269,18 @@ static int perform_from_http1text(void) nghttp2_nv *nv; char *rv = fgets(line, sizeof(line), stdin); char *val, *val_end; - if(rv == NULL) { + if(rv == nullptr) { end = 1; break; } else if(line[0] == '\n') { break; } - assert(nvlen < sizeof(nva)/sizeof(nva[0])); + + nva.resize(nvlen); + nv = &nva[nvlen]; val = strchr(line+1, ':'); - if(val == NULL) { + if(val == nullptr) { fprintf(stderr, "Bad HTTP/1 header field format at %d.\n", seq); exit(EXIT_FAILURE); } @@ -299,7 +303,7 @@ static int perform_from_http1text(void) if(seq > 0) { printf(",\n"); } - deflate_hd(&deflater, nva, nvlen, inputlen, seq); + deflate_hd(&deflater, nva, inputlen, seq); } for(i = 0; i < nvlen; ++i) { @@ -316,81 +320,82 @@ static int perform_from_http1text(void) static void print_help(void) { - printf("HPACK HTTP/2 header encoder\n" - "Usage: deflatehd [OPTIONS] < INPUT\n" - "\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 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" - " \"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" - "line:\n" - "\n" - "Example:\n" - ":method: GET\n" - ":scheme: https\n" - ":path: /\n" - "\n" - ":method: POST\n" - "user-agent: nghttp2\n" - "\n" - "The output of this program can be used as input for inflatehd.\n" - "\n" - "OPTIONS:\n" - " -t, --http1text Use HTTP/1 style header field text as input.\n" - " Each header set is delimited by single empty\n" - " line.\n" - " -s, --table-size=\n" - " Set dynamic table size. In the HPACK\n" - " specification, this value is denoted by\n" - " SETTINGS_HEADER_TABLE_SIZE.\n" - " Default: 4096\n" - " -S, --deflate-table-size=\n" - " Use first N bytes of dynamic header table\n" - " buffer.\n" - " Default: 4096\n" - " -d, --dump-header-table\n" - " Output dynamic header table.\n" - " -c, --no-refset Don't use reference set.\n"); + std::cout << R"(HPACK HTTP/2 header encoder +Usage: deflatehd [OPTIONS] < INPUT + +Reads JSON data or HTTP/1-style header fields from stdin and outputs +deflated header block in JSON array. + +For the JSON input, the root JSON object must contain "context" key, +which indicates which compression context is used. If it is +"request", request compression context is used. Otherwise, response +compression context is used. The value of "cases" key contains the +sequence of input header set. They share the same compression context +and are processed in the order they appear. Each item in the sequence +is a JSON object and it must have at least "headers" key. Its value +is an array of a JSON object containing exactly one name/value pair. + +Example: +{ + "context": "request", + "cases": + [ + { + "headers": [ + { ":method": "GET" }, + { ":path": "/" } + ] + }, + { + "headers": [ + { ":method": "POST" }, + { ":path": "/" } + ] + } + ] +} + +With -t option, the program can accept more familiar HTTP/1 style +header field block. Each header set must be followed by one empty +line: + +Example: + +:method: GET +:scheme: https +:path: / + +:method: POST +user-agent: nghttp2 + +The output of this program can be used as input for inflatehd. + +OPTIONS: + -t, --http1text Use HTTP/1 style header field text as input. + Each header set is delimited by single empty + line. + -s, --table-size= + Set dynamic table size. In the HPACK + specification, this value is denoted by + SETTINGS_HEADER_TABLE_SIZE. + Default: 4096 + -S, --deflate-table-size= + Use first N bytes of dynamic header table + buffer. + Default: 4096 + -d, --dump-header-table + Output dynamic header table. + -c, --no-refset Don't use reference set.)" + << std::endl; } static struct option long_options[] = { - {"http1text", no_argument, NULL, 't'}, - {"table-size", required_argument, NULL, 's'}, - {"deflate-table-size", required_argument, NULL, 'S'}, - {"dump-header-table", no_argument, NULL, 'd'}, - {"no-refset", no_argument, NULL, 'c'}, - {NULL, 0, NULL, 0 } + {"http1text", no_argument, nullptr, 't'}, + {"table-size", required_argument, nullptr, 's'}, + {"deflate-table-size", required_argument, nullptr, 'S'}, + {"dump-header-table", no_argument, nullptr, 'd'}, + {"no-refset", no_argument, nullptr, 'c'}, + {nullptr, 0, nullptr, 0 } }; int main(int argc, char **argv) diff --git a/src/inflatehd.c b/src/inflatehd.cc similarity index 71% rename from src/inflatehd.c rename to src/inflatehd.cc index f3570f68..1d36be6e 100644 --- a/src/inflatehd.c +++ b/src/inflatehd.cc @@ -26,21 +26,28 @@ # include #endif /* HAVE_CONFIG_H */ -#include -#include #include #include + +#include +#include #include -#include -#include +#include +#include +#include +#include #include +extern "C" { + #include "nghttp2_hd.h" #include "nghttp2_frame.h" #include "comp_helper.h" +} + typedef struct { int dump_header_table; } inflate_config; @@ -70,9 +77,7 @@ static void to_json(nghttp2_hd_inflater *inflater, json_t *headers, json_t *wire, int seq, size_t old_settings_table_size) { - json_t *obj; - - obj = json_object(); + auto obj = json_object(); json_object_set_new(obj, "seq", json_integer(seq)); json_object_set(obj, "wire", wire); json_object_set(obj, "headers", headers); @@ -91,21 +96,20 @@ static void to_json(nghttp2_hd_inflater *inflater, static int inflate_hd(json_t *obj, nghttp2_hd_inflater *inflater, int seq) { - json_t *wire, *table_size, *headers; - size_t inputlen; - uint8_t *buf, *p; - size_t buflen; ssize_t rv; nghttp2_nv nv; int inflate_flags; size_t old_settings_table_size = inflater->settings_hd_table_bufsize_max; - wire = json_object_get(obj, "wire"); - if(wire == NULL) { + auto wire = json_object_get(obj, "wire"); + + if(wire == nullptr) { fprintf(stderr, "'wire' key is missing at %d\n", seq); return -1; } - table_size = json_object_get(obj, "header_table_size"); + + auto table_size = json_object_get(obj, "header_table_size"); + if(table_size) { if(!json_is_integer(table_size)) { fprintf(stderr, @@ -122,18 +126,22 @@ static int inflate_hd(json_t *obj, nghttp2_hd_inflater *inflater, int seq) return -1; } } - inputlen = strlen(json_string_value(wire)); + + auto inputlen = strlen(json_string_value(wire)); + if(inputlen & 1) { fprintf(stderr, "Badly formatted output value at %d\n", seq); exit(EXIT_FAILURE); } - buflen = inputlen / 2; - buf = malloc(buflen); - decode_hex(buf, json_string_value(wire), inputlen); - headers = json_array(); + auto buflen = inputlen / 2; + auto buf = std::vector(buflen); - p = buf; + decode_hex(buf.data(), json_string_value(wire), inputlen); + + auto headers = json_array(); + + auto p = buf.data(); for(;;) { inflate_flags = 0; rv = nghttp2_hd_inflate_hd(inflater, &nv, &inflate_flags, p, buflen, 1); @@ -155,37 +163,40 @@ static int inflate_hd(json_t *obj, nghttp2_hd_inflater *inflater, int seq) nghttp2_hd_inflate_end_headers(inflater); to_json(inflater, headers, wire, seq, old_settings_table_size); json_decref(headers); - free(buf); + return 0; } static int perform(void) { nghttp2_hd_inflater inflater; - size_t i; - json_t *json, *cases; json_error_t error; - size_t len; - json = json_loadf(stdin, 0, &error); - if(json == NULL) { + auto json = json_loadf(stdin, 0, &error); + + if(json == nullptr) { fprintf(stderr, "JSON loading failed\n"); exit(EXIT_FAILURE); } - cases = json_object_get(json, "cases"); - if(cases == NULL) { + + auto cases = json_object_get(json, "cases"); + + if(cases == nullptr) { 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); output_json_header(); - len = json_array_size(cases); - for(i = 0; i < len; ++i) { - json_t *obj = json_array_get(cases, i); + auto len = json_array_size(cases); + + for(size_t i = 0; i < len; ++i) { + auto obj = json_array_get(cases, i); if(!json_is_object(obj)) { fprintf(stderr, "Unexpected JSON type at %zu. It should be object.\n", i); @@ -201,47 +212,49 @@ static int perform(void) output_json_footer(); nghttp2_hd_inflate_free(&inflater); json_decref(json); + return 0; } static void print_help(void) { - printf("HPACK HTTP/2 header decoder\n" - "Usage: inflatehd [OPTIONS] < INPUT\n" - "\n" - "Reads JSON data from stdin and outputs inflated name/value pairs\n" - "in JSON.\n" - "\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" - " \"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" - " -d, --dump-header-table\n" - " Output dynamic header table.\n"); + std::cout << R"(HPACK HTTP/2 header decoder +Usage: inflatehd [OPTIONS] < INPUT + +Reads JSON data from stdin and outputs inflated name/value pairs in +JSON. + +The root JSON object must contain "context" key, which indicates which +compression context is used. If it is "request", request compression +context is used. Otherwise, response compression context is used. +The value of "cases" key contains the sequence of compressed header +block. They share the same compression context and are processed in +the order they appear. Each item in the sequence is a JSON object and +it must have at least "wire" key. Its value is a string containing +compressed header block in hex string. + +Example: + +{ + "context": "request", + "cases": + [ + { "wire": "0284f77778ff" }, + { "wire": "0185fafd3c3c7f81" } + ] +} + +The output of this program can be used as input for deflatehd. + +OPTIONS: + -d, --dump-header-table + Output dynamic header table.)" + << std::endl;; } static struct option long_options[] = { - {"dump-header-table", no_argument, NULL, 'd'}, - {NULL, 0, NULL, 0 } + {"dump-header-table", no_argument, nullptr, 'd'}, + {nullptr, 0, nullptr, 0 } }; int main(int argc, char **argv)