diff options
Diffstat (limited to 'examples')
-rw-r--r-- | examples/.gitignore | 1 | ||||
-rw-r--r-- | examples/CMakeLists.txt | 50 | ||||
-rw-r--r-- | examples/Makefile.am | 46 | ||||
-rw-r--r-- | examples/qpack.cc | 143 | ||||
-rw-r--r-- | examples/qpack.h | 44 | ||||
-rw-r--r-- | examples/qpack_decode.cc | 299 | ||||
-rw-r--r-- | examples/qpack_decode.h | 93 | ||||
-rw-r--r-- | examples/qpack_encode.cc | 221 | ||||
-rw-r--r-- | examples/qpack_encode.h | 59 | ||||
-rw-r--r-- | examples/template.h | 77 | ||||
-rw-r--r-- | examples/util.cc | 27 | ||||
-rw-r--r-- | examples/util.h | 64 |
12 files changed, 1124 insertions, 0 deletions
diff --git a/examples/.gitignore b/examples/.gitignore new file mode 100644 index 0000000..20a9df3 --- /dev/null +++ b/examples/.gitignore @@ -0,0 +1 @@ +/qpack diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..31ec1aa --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,50 @@ +# ngtcp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2017 ngtcp2 contributors +# +# 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_EXAMPLES) + include_directories( + ${CMAKE_SOURCE_DIR}/lib/includes + ${CMAKE_BINARY_DIR}/lib/includes + ) + + link_libraries( + nghttp3 + ) + + set(qpack_SOURCES + qpack.cc + qpack_encode.cc + qpack_decode.cc + util.cc + ) + + add_executable(qpack ${qpack_SOURCES}) + set_target_properties(qpack PROPERTIES + COMPILE_FLAGS "${WARNCXXFLAGS}" + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED ON + ) + + # TODO prevent qpack example from being installed? +endif() diff --git a/examples/Makefile.am b/examples/Makefile.am new file mode 100644 index 0000000..8c90892 --- /dev/null +++ b/examples/Makefile.am @@ -0,0 +1,46 @@ +# ngtcp3 +# +# Copyright (c) 2019 nghttp3 contributors +# Copyright (c) 2017 ngtcp2 contributors +# +# 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. +EXTRA_DIST = CMakeLists.txt + +if ENABLE_EXAMPLES + +AM_CFLAGS = $(WARNCFLAGS) $(DEBUGCFLAGS) +AM_CXXFLAGS = $(WARNCXXFLAGS) $(DEBUGCFLAGS) +AM_CPPFLAGS = \ + -I$(top_srcdir)/lib/includes \ + -I$(top_builddir)/lib/includes \ + @DEFS@ +AM_LDFLAGS = -no-install +LDADD = $(top_builddir)/lib/libnghttp3.la + +noinst_PROGRAMS = qpack + +qpack_SOURCES = \ + qpack.cc qpack.h \ + qpack_encode.cc qpack_encode.h \ + qpack_decode.cc qpack_decode.h \ + template.h \ + util.cc util.h + +endif # ENABLE_EXAMPLES diff --git a/examples/qpack.cc b/examples/qpack.cc new file mode 100644 index 0000000..1353ad9 --- /dev/null +++ b/examples/qpack.cc @@ -0,0 +1,143 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * 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. + */ +#include "qpack.h" + +#include <cstring> +#include <iostream> +#include <string> + +#include <getopt.h> + +#include "qpack_encode.h" +#include "qpack_decode.h" + +namespace nghttp3 { + +Config config{}; + +namespace { +void print_usage() { + std::cerr << "Usage: qpack [OPTIONS] <COMMAND> <INFILE> <OUTFILE>" + << std::endl; +} +} // namespace + +namespace { +void print_help() { + print_usage(); + + std::cerr << R"( + <COMMAND> "encode" or "decode" + <INFILE> Path to an input file + <OUTFILE> Path to an output file +Options: + -h, --help Display this help and exit. + -m, --max-blocked=<N> + The maximum number of streams which are permitted to be blocked. + -s, --max-dtable-size=<N> + The maximum size of dynamic table. + -a, --immediate-ack + Turn on immediate acknowlegement. +)"; +} +} // namespace + +int main(int argc, char **argv) { + for (;;) { + static int flag = 0; + (void)flag; + constexpr static option long_opts[] = { + {"help", no_argument, nullptr, 'h'}, + {"max-blocked", required_argument, nullptr, 'm'}, + {"max-dtable-size", required_argument, nullptr, 's'}, + {"immediate-ack", no_argument, nullptr, 'a'}, + {nullptr, 0, nullptr, 0}, + }; + + auto optidx = 0; + auto c = getopt_long(argc, argv, "hm:s:a", long_opts, &optidx); + if (c == -1) { + break; + } + switch (c) { + case 'h': + // --help + print_help(); + exit(EXIT_SUCCESS); + case 'm': { + // --max-blocked + config.max_blocked = strtoul(optarg, nullptr, 10); + break; + } + case 's': { + // --max-dtable-size + config.max_dtable_size = strtoul(optarg, nullptr, 10); + break; + } + case 'a': + // --immediate-ack + config.immediate_ack = true; + break; + case '?': + print_usage(); + exit(EXIT_FAILURE); + case 0: + break; + default: + break; + }; + } + + if (argc - optind < 3) { + std::cerr << "Too few arguments" << std::endl; + print_usage(); + exit(EXIT_FAILURE); + } + + auto command = std::string_view(argv[optind++]); + auto infile = std::string_view(argv[optind++]); + auto outfile = std::string_view(argv[optind++]); + + int rv; + if (command == "encode") { + rv = encode(outfile, infile); + } else if (command == "decode") { + rv = decode(outfile, infile); + } else { + std::cerr << "Unrecognized command: " << command << std::endl; + print_usage(); + exit(EXIT_FAILURE); + } + + if (rv != 0) { + exit(EXIT_FAILURE); + } + + return 0; +} + +} // namespace nghttp3 + +int main(int argc, char **argv) { return nghttp3::main(argc, argv); } diff --git a/examples/qpack.h b/examples/qpack.h new file mode 100644 index 0000000..d0b0ce6 --- /dev/null +++ b/examples/qpack.h @@ -0,0 +1,44 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * 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. + */ +#ifndef QPACK_H +#define QPACK_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +namespace nghttp3 { + +struct Config { + size_t max_blocked; + size_t max_dtable_size; + bool immediate_ack; +}; + +} // namespace nghttp3 + +#endif // QPACK_H diff --git a/examples/qpack_decode.cc b/examples/qpack_decode.cc new file mode 100644 index 0000000..22e44c8 --- /dev/null +++ b/examples/qpack_decode.cc @@ -0,0 +1,299 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * 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. + */ +#include "qpack_decode.h" + +#include <arpa/inet.h> + +#include <sys/types.h> +#include <sys/stat.h> +#include <unistd.h> +#include <fcntl.h> +#include <sys/mman.h> + +#include <cassert> +#include <cstring> +#include <cerrno> +#include <iostream> +#include <fstream> + +#include "qpack.h" +#include "template.h" +#include "util.h" + +namespace nghttp3 { + +extern Config config; + +Request::Request(int64_t stream_id, const nghttp3_buf *buf) + : buf(*buf), stream_id(stream_id) { + auto mem = nghttp3_mem_default(); + nghttp3_qpack_stream_context_new(&sctx, stream_id, mem); +} + +Request::~Request() { nghttp3_qpack_stream_context_del(sctx); } + +Decoder::Decoder(size_t max_dtable_size, size_t max_blocked) + : mem_(nghttp3_mem_default()), + dec_(nullptr), + max_dtable_size_(max_dtable_size), + max_blocked_(max_blocked) {} + +Decoder::~Decoder() { nghttp3_qpack_decoder_del(dec_); } + +int Decoder::init() { + if (auto rv = nghttp3_qpack_decoder_new(&dec_, max_dtable_size_, max_blocked_, + mem_); + rv != 0) { + std::cerr << "nghttp3_qpack_decoder_new: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + if (auto rv = + nghttp3_qpack_decoder_set_max_dtable_capacity(dec_, max_dtable_size_); + rv != 0) { + std::cerr << "nghttp3_qpack_decoder_set_max_dtable_capacity: " + << nghttp3_strerror(rv) << std::endl; + return -1; + } + + return 0; +} + +int Decoder::read_encoder(nghttp3_buf *buf) { + auto nread = + nghttp3_qpack_decoder_read_encoder(dec_, buf->pos, nghttp3_buf_len(buf)); + if (nread < 0) { + std::cerr << "nghttp3_qpack_decoder_read_encoder: " + << nghttp3_strerror(nread) << std::endl; + return -1; + } + + assert(static_cast<size_t>(nread) == nghttp3_buf_len(buf)); + + return 0; +} + +std::tuple<Headers, int> Decoder::read_request(nghttp3_buf *buf, + int64_t stream_id) { + auto req = std::make_shared<Request>(stream_id, buf); + + auto [headers, rv] = read_request(*req); + if (rv == -1) { + return {Headers{}, -1}; + } + if (rv == 1) { + if (blocked_reqs_.size() >= max_blocked_) { + std::cerr << "Too many blocked streams: max_blocked=" << max_blocked_ + << std::endl; + return {Headers{}, -1}; + } + blocked_reqs_.emplace(std::move(req)); + return {Headers{}, 1}; + } + return {headers, 0}; +} + +std::tuple<Headers, int> Decoder::read_request(Request &req) { + nghttp3_qpack_nv nv; + uint8_t flags; + Headers headers; + + for (;;) { + auto nread = nghttp3_qpack_decoder_read_request( + dec_, req.sctx, &nv, &flags, req.buf.pos, nghttp3_buf_len(&req.buf), 1); + if (nread < 0) { + std::cerr << "nghttp3_qpack_decoder_read_request: " + << nghttp3_strerror(nread) << std::endl; + return {Headers{}, -1}; + } + + req.buf.pos += nread; + + if (flags & NGHTTP3_QPACK_DECODE_FLAG_FINAL) { + break; + } + if (flags & NGHTTP3_QPACK_DECODE_FLAG_BLOCKED) { + return {Headers{}, 1}; + } + if (flags & NGHTTP3_QPACK_DECODE_FLAG_EMIT) { + auto name = nghttp3_rcbuf_get_buf(nv.name); + auto value = nghttp3_rcbuf_get_buf(nv.value); + headers.emplace_back(std::string{name.base, name.base + name.len}, + std::string{value.base, value.base + value.len}); + nghttp3_rcbuf_decref(nv.name); + nghttp3_rcbuf_decref(nv.value); + } + } + + return {headers, 0}; +} + +std::tuple<int64_t, Headers, int> Decoder::process_blocked() { + if (!blocked_reqs_.empty()) { + auto &top = blocked_reqs_.top(); + if (nghttp3_qpack_stream_context_get_ricnt(top->sctx) > + nghttp3_qpack_decoder_get_icnt(dec_)) { + return {-1, {}, 0}; + } + + auto req = top; + blocked_reqs_.pop(); + + auto [headers, rv] = read_request(*req); + if (rv < 0) { + return {-1, {}, -1}; + } + assert(rv == 0); + + return {req->stream_id, headers, 0}; + } + return {-1, {}, 0}; +} + +size_t Decoder::get_num_blocked() const { return blocked_reqs_.size(); } + +namespace { +void write_header( + std::ostream &out, + const std::vector<std::pair<std::string, std::string>> &headers) { + for (auto &nv : headers) { + out.write(nv.first.c_str(), nv.first.size()); + out.put('\t'); + out.write(nv.second.c_str(), nv.second.size()); + out.put('\n'); + } + out.put('\n'); +} +} // namespace + +int decode(const std::string_view &outfile, const std::string_view &infile) { + auto fd = open(infile.data(), O_RDONLY); + if (fd == -1) { + std::cerr << "Could not open " << infile << ": " << strerror(errno) + << std::endl; + return -1; + } + + auto fd_closer = defer(close, fd); + + struct stat st; + if (fstat(fd, &st) == -1) { + std::cerr << "fstat: " << strerror(errno) << std::endl; + return -1; + } + + auto out = std::ofstream(outfile.data(), std::ios::trunc | std::ios::binary); + if (!out) { + std::cerr << "Could not open file " << outfile << ": " << strerror(errno) + << std::endl; + return -1; + } + + auto in = reinterpret_cast<uint8_t *>( + mmap(nullptr, st.st_size, PROT_READ, MAP_SHARED, fd, 0)); + if (in == MAP_FAILED) { + std::cerr << "mmap: " << strerror(errno) << std::endl; + return -1; + } + + auto unmapper = defer(munmap, in, st.st_size); + + auto dec = Decoder(config.max_dtable_size, config.max_blocked); + if (auto rv = dec.init(); rv != 0) { + return rv; + } + + for (auto p = in, end = in + st.st_size; p != end;) { + int64_t stream_id; + uint32_t size; + + if (static_cast<size_t>(end - p) < sizeof(stream_id) + sizeof(size)) { + std::cerr << "Could not read stream ID and size" << std::endl; + return -1; + } + + memcpy(&stream_id, p, sizeof(stream_id)); + stream_id = nghttp3_ntohl64(stream_id); + p += sizeof(stream_id); + + memcpy(&size, p, sizeof(size)); + size = ntohl(size); + p += sizeof(size); + + if ((size_t)(end - p) < size) { + std::cerr << "Insufficient input: require " << size << " but " + << (end - p) << " is available" << std::endl; + return -1; + } + + nghttp3_buf buf; + buf.begin = buf.pos = p; + buf.end = buf.last = p + size; + + p += size; + + if (stream_id == 0) { + if (auto rv = dec.read_encoder(&buf); rv != 0) { + return rv; + } + + for (;;) { + auto [stream_id, headers, rv] = dec.process_blocked(); + if (rv != 0) { + return rv; + } + + if (stream_id == -1) { + break; + } + + write_header(out, headers); + } + + continue; + } + + auto [headers, rv] = dec.read_request(&buf, stream_id); + if (rv == -1) { + return rv; + } + if (rv == 1) { + // Stream blocked + continue; + } + + write_header(out, headers); + } + + if (auto n = dec.get_num_blocked(); n) { + std::cerr << "Still " << n << " stream(s) blocked" << std::endl; + return -1; + } + + return 0; +} + +} // namespace nghttp3 diff --git a/examples/qpack_decode.h b/examples/qpack_decode.h new file mode 100644 index 0000000..8641b3a --- /dev/null +++ b/examples/qpack_decode.h @@ -0,0 +1,93 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * 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. + */ +#ifndef QPACK_DECODE_H +#define QPACK_DECODE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include <vector> +#include <queue> +#include <functional> +#include <utility> +#include <memory> +#include <string> + +namespace nghttp3 { +struct Request { + Request(int64_t stream_id, const nghttp3_buf *buf); + ~Request(); + + nghttp3_buf buf; + nghttp3_qpack_stream_context *sctx; + int64_t stream_id; +}; +} // namespace nghttp3 + +namespace std { +template <> struct greater<std::shared_ptr<nghttp3::Request>> { + bool operator()(const std::shared_ptr<nghttp3::Request> &lhs, + const std::shared_ptr<nghttp3::Request> &rhs) const { + return nghttp3_qpack_stream_context_get_ricnt(lhs->sctx) > + nghttp3_qpack_stream_context_get_ricnt(rhs->sctx); + } +}; +} // namespace std + +namespace nghttp3 { + +using Headers = std::vector<std::pair<std::string, std::string>>; + +class Decoder { +public: + Decoder(size_t max_dtable_size, size_t max_blocked); + ~Decoder(); + + int init(); + int read_encoder(nghttp3_buf *buf); + std::tuple<Headers, int> read_request(nghttp3_buf *buf, int64_t stream_id); + std::tuple<Headers, int> read_request(Request &req); + std::tuple<int64_t, Headers, int> process_blocked(); + size_t get_num_blocked() const; + +private: + const nghttp3_mem *mem_; + nghttp3_qpack_decoder *dec_; + std::priority_queue<std::shared_ptr<Request>, + std::vector<std::shared_ptr<Request>>, + std::greater<std::shared_ptr<Request>>> + blocked_reqs_; + size_t max_dtable_size_; + size_t max_blocked_; +}; + +int decode(const std::string_view &outfile, const std::string_view &infile); + +} // namespace nghttp3 + +#endif // QPACK_ENCODE_H diff --git a/examples/qpack_encode.cc b/examples/qpack_encode.cc new file mode 100644 index 0000000..867a085 --- /dev/null +++ b/examples/qpack_encode.cc @@ -0,0 +1,221 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * 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. + */ +#include "qpack_encode.h" + +#include <arpa/inet.h> + +#include <cerrno> +#include <cstring> +#include <cassert> +#include <iostream> +#include <fstream> +#include <algorithm> +#include <array> +#include <iomanip> +#include <vector> + +#include "qpack.h" +#include "template.h" +#include "util.h" + +namespace nghttp3 { + +extern Config config; + +Encoder::Encoder(size_t max_dtable_size, size_t max_blocked, bool immediate_ack) + : mem_(nghttp3_mem_default()), + enc_(nullptr), + max_dtable_size_(max_dtable_size), + max_blocked_(max_blocked), + immediate_ack_(immediate_ack) {} + +Encoder::~Encoder() { nghttp3_qpack_encoder_del(enc_); } + +int Encoder::init() { + int rv; + + rv = nghttp3_qpack_encoder_new(&enc_, max_dtable_size_, mem_); + if (rv != 0) { + std::cerr << "nghttp3_qpack_encoder_new: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + + nghttp3_qpack_encoder_set_max_dtable_capacity(enc_, max_dtable_size_); + nghttp3_qpack_encoder_set_max_blocked_streams(enc_, max_blocked_); + + return 0; +} + +int Encoder::encode(nghttp3_buf *pbuf, nghttp3_buf *rbuf, nghttp3_buf *ebuf, + int64_t stream_id, const nghttp3_nv *nva, size_t len) { + auto rv = + nghttp3_qpack_encoder_encode(enc_, pbuf, rbuf, ebuf, stream_id, nva, len); + if (rv != 0) { + std::cerr << "nghttp3_qpack_encoder_encode: " << nghttp3_strerror(rv) + << std::endl; + return -1; + } + if (immediate_ack_) { + nghttp3_qpack_encoder_ack_everything(enc_); + } + return 0; +} + +namespace { +void write_encoder_stream(std::ostream &out, nghttp3_buf *ebuf) { + uint64_t stream_id = 0; + out.write(reinterpret_cast<char *>(&stream_id), sizeof(stream_id)); + uint32_t size = htonl(nghttp3_buf_len(ebuf)); + out.write(reinterpret_cast<char *>(&size), sizeof(size)); + out.write(reinterpret_cast<char *>(ebuf->pos), nghttp3_buf_len(ebuf)); +} +} // namespace + +namespace { +void write_request_stream(std::ostream &out, int64_t stream_id, + nghttp3_buf *pbuf, nghttp3_buf *rbuf) { + stream_id = nghttp3_htonl64(stream_id); + out.write(reinterpret_cast<char *>(&stream_id), sizeof(stream_id)); + uint32_t size = htonl(nghttp3_buf_len(pbuf) + nghttp3_buf_len(rbuf)); + out.write(reinterpret_cast<char *>(&size), sizeof(size)); + out.write(reinterpret_cast<char *>(pbuf->pos), nghttp3_buf_len(pbuf)); + out.write(reinterpret_cast<char *>(rbuf->pos), nghttp3_buf_len(rbuf)); +} +} // namespace + +int encode(const std::string_view &outfile, const std::string_view &infile) { + auto in = std::ifstream(infile.data(), std::ios::binary); + + if (!in) { + std::cerr << "Could not open file " << infile << ": " << strerror(errno) + << std::endl; + return -1; + } + + auto out = std::ofstream(outfile.data(), std::ios::trunc | std::ios::binary); + if (!out) { + std::cerr << "Could not open file " << outfile << ": " << strerror(errno) + << std::endl; + return -1; + } + + auto enc = + Encoder(config.max_dtable_size, config.max_blocked, config.immediate_ack); + if (enc.init() != 0) { + return -1; + } + + nghttp3_buf pbuf, rbuf, ebuf; + nghttp3_buf_init(&pbuf); + nghttp3_buf_init(&rbuf); + nghttp3_buf_init(&ebuf); + + auto mem = nghttp3_mem_default(); + auto pbufd = defer(nghttp3_buf_free, &pbuf, mem); + auto rbufd = defer(nghttp3_buf_free, &rbuf, mem); + auto ebufd = defer(nghttp3_buf_free, &ebuf, mem); + + int64_t stream_id = 1; + std::array<std::string, 1024> sarray; + + size_t srclen = 0; + size_t enclen = 0; + size_t rslen = 0; + size_t eslen = 0; + + for (; in;) { + auto nva = std::vector<nghttp3_nv>(); + for (std::string line; std::getline(in, line);) { + if (line == "") { + break; + } + + if (sarray.size() == nva.size()) { + std::cerr << "Too many headers: " << nva.size() << std::endl; + return -1; + } + + sarray[nva.size()] = line; + const auto &s = sarray[nva.size()]; + + auto d = s.find('\t'); + if (d == std::string_view::npos) { + std::cerr << "Could not find TAB in " << s << std::endl; + return -1; + } + auto name = std::string_view(s.c_str(), d); + auto value = std::string_view(s.c_str() + d + 1, s.size() - d - 1); + value.remove_prefix(std::min(value.find_first_not_of(" "), value.size())); + + srclen += name.size() + value.size(); + + nva.emplace_back(nghttp3_nv{ + const_cast<uint8_t *>(reinterpret_cast<const uint8_t *>(name.data())), + const_cast<uint8_t *>( + reinterpret_cast<const uint8_t *>(value.data())), + name.size(), value.size()}); + } + + if (nva.empty()) { + break; + } + + if (auto rv = + enc.encode(&pbuf, &rbuf, &ebuf, stream_id, nva.data(), nva.size()); + rv != 0) { + return -1; + } + + enclen += nghttp3_buf_len(&pbuf) + nghttp3_buf_len(&rbuf) + + nghttp3_buf_len(&ebuf); + + if (nghttp3_buf_len(&ebuf)) { + write_encoder_stream(out, &ebuf); + } + write_request_stream(out, stream_id, &pbuf, &rbuf); + + rslen += nghttp3_buf_len(&pbuf) + nghttp3_buf_len(&rbuf); + eslen += nghttp3_buf_len(&ebuf); + + nghttp3_buf_reset(&pbuf); + nghttp3_buf_reset(&rbuf); + nghttp3_buf_reset(&ebuf); + + ++stream_id; + } + + if (srclen == 0) { + std::cerr << "No header field processed" << std::endl; + } else { + std::cerr << srclen << " -> " << enclen << " (r:" << rslen + << " + e:" << eslen << ") " << std::fixed << std::setprecision(2) + << (1. - (static_cast<double>(enclen) / srclen)) * 100 + << "% compressed" << std::endl; + } + return 0; +} + +} // namespace nghttp3 diff --git a/examples/qpack_encode.h b/examples/qpack_encode.h new file mode 100644 index 0000000..b310ecd --- /dev/null +++ b/examples/qpack_encode.h @@ -0,0 +1,59 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * 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. + */ +#ifndef QPACK_ENCODE_H +#define QPACK_ENCODE_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <nghttp3/nghttp3.h> + +#include <string> + +namespace nghttp3 { + +class Encoder { +public: + Encoder(size_t max_dtable_size, size_t max_blocked, bool immediate_ack); + ~Encoder(); + + int init(); + int encode(nghttp3_buf *pbuf, nghttp3_buf *rbuf, nghttp3_buf *ebuf, + int64_t stream_id, const nghttp3_nv *nva, size_t len); + +private: + const nghttp3_mem *mem_; + nghttp3_qpack_encoder *enc_; + size_t max_dtable_size_; + size_t max_blocked_; + bool immediate_ack_; +}; + +int encode(const std::string_view &outfile, const std::string_view &infile); + +} // namespace nghttp3 + +#endif // QPACK_ENCODE_H diff --git a/examples/template.h b/examples/template.h new file mode 100644 index 0000000..1f758b4 --- /dev/null +++ b/examples/template.h @@ -0,0 +1,77 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * Copyright (c) 2017 ngtcp2 contributors + * Copyright (c) 2015 ngttp2 contributors + * + * 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. + */ +#ifndef TEMPLATE_H +#define TEMPLATE_H + +#include <functional> +#include <utility> +#include <type_traits> + +namespace nghttp3 { + +// inspired by <http://blog.korfuri.fr/post/go-defer-in-cpp/>, but our +// template can take functions returning other than void. +template <typename F, typename... T> struct Defer { + Defer(F &&f, T &&...t) + : f(std::bind(std::forward<F>(f), std::forward<T>(t)...)) {} + Defer(Defer &&o) noexcept : f(std::move(o.f)) {} + ~Defer() { f(); } + + using ResultType = typename std::result_of<typename std::decay<F>::type( + typename std::decay<T>::type...)>::type; + std::function<ResultType()> f; +}; + +template <typename F, typename... T> Defer<F, T...> defer(F &&f, T &&...t) { + return Defer<F, T...>(std::forward<F>(f), std::forward<T>(t)...); +} + +template <typename T, size_t N> constexpr size_t array_size(T (&)[N]) { + return N; +} + +template <typename T, size_t N> constexpr size_t str_size(T (&)[N]) { + return N - 1; +} + +// User-defined literals for K, M, and G (powers of 1024) + +constexpr unsigned long long operator"" _k(unsigned long long k) { + return k * 1024; +} + +constexpr unsigned long long operator"" _m(unsigned long long m) { + return m * 1024 * 1024; +} + +constexpr unsigned long long operator"" _g(unsigned long long g) { + return g * 1024 * 1024 * 1024; +} + +} // namespace nghttp3 + +#endif // TEMPLATE_H diff --git a/examples/util.cc b/examples/util.cc new file mode 100644 index 0000000..8973825 --- /dev/null +++ b/examples/util.cc @@ -0,0 +1,27 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * 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. + */ +#include "util.h" + +namespace nghttp3 {} // namespace nghttp3 diff --git a/examples/util.h b/examples/util.h new file mode 100644 index 0000000..8e06086 --- /dev/null +++ b/examples/util.h @@ -0,0 +1,64 @@ +/* + * nghttp3 + * + * Copyright (c) 2019 nghttp3 contributors + * + * 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. + */ +#ifndef UTIL_H +#define UTIL_H + +#ifdef HAVE_CONFIG_H +# include <config.h> +#endif /* HAVE_CONFIG_H */ + +#include <stdint.h> + +#ifdef HAVE_ARPA_INET_H +# include <arpa/inet.h> +#endif /* HAVE_ARPA_INET_H */ + +#ifdef HAVE_NETINET_IN_H +# include <netinet/in.h> +#endif /* HAVE_NETINET_IN_H */ + +#ifdef HAVE_ENDIAN_H +# include <endian.h> +#endif /* HAVE_ENDIAN_H */ + +#ifdef HAVE_SYS_ENDIAN_H +# include <sys/endian.h> +#endif /* HAVE_SYS_ENDIAN_H */ + +namespace nghttp3 { + +#if defined HAVE_BE64TOH || HAVE_DECL_BE64TOH +# define nghttp3_ntohl64(N) be64toh(N) +# define nghttp3_htonl64(N) htobe64(N) +#else /* !HAVE_BE64TOH */ +# define nghttp3_bswap64(N) \ + ((uint64_t)(ntohl((uint32_t)(N))) << 32 | ntohl((uint32_t)((N) >> 32))) +# define nghttp3_ntohl64(N) nghttp3_bswap64(N) +# define nghttp3_htonl64(N) nghttp3_bswap64(N) +#endif /* !HAVE_BE64TOH */ + +} // namespace nghttp3 + +#endif // UTIL_H |