summaryrefslogtreecommitdiffstats
path: root/src/deflatehd.cc
diff options
context:
space:
mode:
Diffstat (limited to 'src/deflatehd.cc')
-rw-r--r--src/deflatehd.cc450
1 files changed, 450 insertions, 0 deletions
diff --git a/src/deflatehd.cc b/src/deflatehd.cc
new file mode 100644
index 0000000..7dcfccf
--- /dev/null
+++ b/src/deflatehd.cc
@@ -0,0 +1,450 @@
+/*
+ * nghttp2 - HTTP/2 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.
+ */
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif // HAVE_CONFIG_H
+
+#ifdef HAVE_UNISTD_H
+# include <unistd.h>
+#endif // HAVE_UNISTD_H
+#include <getopt.h>
+
+#include <cstdio>
+#include <cstring>
+#include <cassert>
+#include <cerrno>
+#include <cstdlib>
+#include <vector>
+#include <iostream>
+
+#include <jansson.h>
+
+#include <nghttp2/nghttp2.h>
+
+#include "template.h"
+#include "comp_helper.h"
+#include "util.h"
+
+namespace nghttp2 {
+
+typedef struct {
+ size_t table_size;
+ size_t deflate_table_size;
+ int http1text;
+ int dump_header_table;
+} deflate_config;
+
+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) {
+ *dest++ = to_hex_digit(src[i] >> 4);
+ *dest++ = to_hex_digit(src[i] & 0xf);
+ }
+}
+
+static void output_to_json(nghttp2_hd_deflater *deflater, const uint8_t *buf,
+ size_t buflen, size_t inputlen,
+ const std::vector<nghttp2_nv> &nva, int seq) {
+ auto hex = std::vector<char>(buflen * 2);
+ auto obj = json_object();
+ auto comp_ratio = inputlen == 0 ? 0.0 : (double)buflen / inputlen * 100;
+
+ 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(buflen));
+ json_object_set_new(obj, "percentage_of_original_size",
+ json_real(comp_ratio));
+
+ if (buflen == 0) {
+ json_object_set_new(obj, "wire", json_string(""));
+ } else {
+ to_hex(hex.data(), buf, buflen);
+ json_object_set_new(obj, "wire", json_pack("s#", hex.data(), hex.size()));
+ }
+ 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",
+ json_integer(config.table_size));
+ }
+ if (config.dump_header_table) {
+ json_object_set_new(obj, "header_table",
+ dump_deflate_header_table(deflater));
+ }
+ json_dumpf(obj, stdout, JSON_PRESERVE_ORDER | JSON_INDENT(2));
+ printf("\n");
+ json_decref(obj);
+}
+
+static void deflate_hd(nghttp2_hd_deflater *deflater,
+ const std::vector<nghttp2_nv> &nva, size_t inputlen,
+ int seq) {
+ ssize_t rv;
+ std::array<uint8_t, 64_k> buf;
+
+ rv = nghttp2_hd_deflate_hd(deflater, buf.data(), buf.size(),
+ (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_to_json(deflater, buf.data(), rv, inputlen, nva, seq);
+}
+
+static int deflate_hd_json(json_t *obj, nghttp2_hd_deflater *deflater,
+ int seq) {
+ size_t inputlen = 0;
+
+ auto js = json_object_get(obj, "headers");
+ if (js == nullptr) {
+ fprintf(stderr, "'headers' key is missing at %d\n", seq);
+ return -1;
+ }
+ if (!json_is_array(js)) {
+ fprintf(stderr, "The value of 'headers' key must be an array at %d\n", seq);
+ return -1;
+ }
+
+ auto len = json_array_size(js);
+ auto nva = std::vector<nghttp2_nv>(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);
+
+ 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].flags = NGHTTP2_NV_FLAG_NONE;
+ }
+
+ inputlen += nva[i].namelen + nva[i].valuelen;
+ }
+
+ deflate_hd(deflater, nva, inputlen, seq);
+
+ return 0;
+}
+
+static nghttp2_hd_deflater *init_deflater() {
+ nghttp2_hd_deflater *deflater;
+ nghttp2_hd_deflate_new(&deflater, config.deflate_table_size);
+ if (config.table_size != NGHTTP2_DEFAULT_HEADER_TABLE_SIZE) {
+ nghttp2_hd_deflate_change_table_size(deflater, config.table_size);
+ }
+ return deflater;
+}
+
+static void deinit_deflater(nghttp2_hd_deflater *deflater) {
+ nghttp2_hd_deflate_del(deflater);
+}
+
+static int perform(void) {
+ json_error_t error;
+
+ auto json = json_loadf(stdin, 0, &error);
+
+ if (json == nullptr) {
+ fprintf(stderr, "JSON loading failed\n");
+ exit(EXIT_FAILURE);
+ }
+
+ 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);
+ }
+
+ auto deflater = init_deflater();
+ output_json_header();
+ 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);
+ continue;
+ }
+ if (deflate_hd_json(obj, deflater, i) != 0) {
+ continue;
+ }
+ if (i + 1 < len) {
+ printf(",\n");
+ }
+ }
+ output_json_footer();
+ deinit_deflater(deflater);
+ json_decref(json);
+ return 0;
+}
+
+static int perform_from_http1text(void) {
+ char line[1 << 14];
+ int seq = 0;
+
+ auto deflater = init_deflater();
+ output_json_header();
+ for (;;) {
+ std::vector<nghttp2_nv> nva;
+ int end = 0;
+ size_t inputlen = 0;
+
+ for (;;) {
+ char *rv = fgets(line, sizeof(line), stdin);
+ char *val, *val_end;
+ if (rv == nullptr) {
+ end = 1;
+ break;
+ } else if (line[0] == '\n') {
+ break;
+ }
+
+ nva.emplace_back();
+ auto &nv = nva.back();
+
+ val = strchr(line + 1, ':');
+ if (val == nullptr) {
+ fprintf(stderr, "Bad HTTP/1 header field format at %d.\n", seq);
+ exit(EXIT_FAILURE);
+ }
+ *val = '\0';
+ ++val;
+ for (; *val && (*val == ' ' || *val == '\t'); ++val)
+ ;
+ for (val_end = val; *val_end && (*val_end != '\r' && *val_end != '\n');
+ ++val_end)
+ ;
+ *val_end = '\0';
+
+ nv.namelen = strlen(line);
+ nv.valuelen = strlen(val);
+ nv.name = (uint8_t *)strdup(line);
+ nv.value = (uint8_t *)strdup(val);
+ nv.flags = NGHTTP2_NV_FLAG_NONE;
+
+ inputlen += nv.namelen + nv.valuelen;
+ }
+
+ if (!end) {
+ if (seq > 0) {
+ printf(",\n");
+ }
+ deflate_hd(deflater, nva, inputlen, seq);
+ }
+
+ for (auto &nv : nva) {
+ free(nv.name);
+ free(nv.value);
+ }
+
+ if (end)
+ break;
+ ++seq;
+ }
+ output_json_footer();
+ deinit_deflater(deflater);
+ return 0;
+}
+
+static void print_help(void) {
+ 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=<N>
+ Set dynamic table size. In the HPACK
+ specification, this value is denoted by
+ SETTINGS_HEADER_TABLE_SIZE.
+ Default: 4096
+ -S, --deflate-table-size=<N>
+ Use first N bytes of dynamic header table
+ buffer.
+ Default: 4096
+ -d, --dump-header-table
+ Output dynamic header table.)"
+ << std::endl;
+}
+
+constexpr static struct option long_options[] = {
+ {"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'},
+ {nullptr, 0, nullptr, 0}};
+
+int main(int argc, char **argv) {
+ config.table_size = 4_k;
+ config.deflate_table_size = 4_k;
+ config.http1text = 0;
+ config.dump_header_table = 0;
+ while (1) {
+ int option_index = 0;
+ int c = getopt_long(argc, argv, "S:dhs:t", long_options, &option_index);
+ if (c == -1) {
+ break;
+ }
+ switch (c) {
+ case 'h':
+ print_help();
+ exit(EXIT_SUCCESS);
+ case 't':
+ // --http1text
+ config.http1text = 1;
+ break;
+ case 's': {
+ // --table-size
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ fprintf(stderr, "-s: Bad option value\n");
+ exit(EXIT_FAILURE);
+ }
+ config.table_size = n;
+ break;
+ }
+ case 'S': {
+ // --deflate-table-size
+ auto n = util::parse_uint(optarg);
+ if (n == -1) {
+ fprintf(stderr, "-S: Bad option value\n");
+ exit(EXIT_FAILURE);
+ }
+ config.deflate_table_size = n;
+ break;
+ }
+ case 'd':
+ // --dump-header-table
+ config.dump_header_table = 1;
+ break;
+ case '?':
+ exit(EXIT_FAILURE);
+ default:
+ break;
+ }
+ }
+ if (config.http1text) {
+ perform_from_http1text();
+ } else {
+ perform();
+ }
+
+ auto comp_ratio = input_sum == 0 ? 0.0 : (double)output_sum / input_sum;
+
+ fprintf(stderr, "Overall: input=%zu output=%zu ratio=%.02f\n", input_sum,
+ output_sum, comp_ratio);
+ return 0;
+}
+
+} // namespace nghttp2
+
+int main(int argc, char **argv) {
+ return nghttp2::run_app(nghttp2::main, argc, argv);
+}