You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficserver.apache.org by ma...@apache.org on 2018/08/27 07:05:55 UTC
[trafficserver] 05/05: Add tests for QPACK
This is an automated email from the ASF dual-hosted git repository.
maskit pushed a commit to branch quic-latest
in repository https://gitbox.apache.org/repos/asf/trafficserver.git
commit 4c0dc178fa412e48dc0a043d0e97df88986a0e4a
Author: Masakazu Kitajo <ma...@apache.org>
AuthorDate: Fri Jul 27 12:23:49 2018 +0900
Add tests for QPACK
---
proxy/hq/Makefile.am | 31 +++-
proxy/hq/test/main_qpack.cc | 100 ++++++++++
proxy/hq/test/stub.cc | 88 +++++++++
proxy/hq/test/test_QPACK.cc | 433 ++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 651 insertions(+), 1 deletion(-)
diff --git a/proxy/hq/Makefile.am b/proxy/hq/Makefile.am
index c8dac9a..0afc84f 100644
--- a/proxy/hq/Makefile.am
+++ b/proxy/hq/Makefile.am
@@ -52,7 +52,8 @@ libhq_a_SOURCES = \
# Check Programs
#
check_PROGRAMS = \
- test_libhq
+ test_libhq \
+ test_qpack
TESTS = $(check_PROGRAMS)
@@ -73,6 +74,34 @@ test_libhq_LDADD = \
$(top_builddir)/iocore/net/quic/libquic.a \
$(top_builddir)/lib/ts/libtsutil.la
+test_qpack_CPPFLAGS = \
+ $(AM_CPPFLAGS) \
+ -I$(abs_top_srcdir)/tests/include
+
+test_qpack_LDFLAGS = \
+ @AM_LDFLAGS@
+
+test_qpack_SOURCES = \
+ ./test/main_qpack.cc \
+ ./test/test_HQFrame.cc \
+ ./test/test_HQFrameDispatcher.cc \
+ ./test/test_QPACK.cc \
+ $(top_builddir)/proxy/http/HttpConfig.cc \
+ $(top_builddir)/proxy/http/HttpConnectionCount.cc \
+ $(top_builddir)/proxy/http/ForwardedConfig.cc \
+ ./test/stub.cc
+
+test_qpack_LDADD = \
+ libhq.a \
+ $(top_builddir)/mgmt/libmgmt_p.la \
+ $(top_builddir)/iocore/net/quic/libquic.a \
+ $(top_builddir)/iocore/eventsystem/libinkevent.a \
+ $(top_builddir)/lib/ts/libtsutil.la \
+ $(top_builddir)/lib/records/librecords_p.a \
+ $(top_builddir)/proxy/libproxy.a \
+ $(top_builddir)/proxy/hdrs/libhdrs.a \
+ $(top_builddir)/proxy/shared/libUglyLogStubs.a
+
#
# clang-tidy
#
diff --git a/proxy/hq/test/main_qpack.cc b/proxy/hq/test/main_qpack.cc
new file mode 100644
index 0000000..b5c7380
--- /dev/null
+++ b/proxy/hq/test/main_qpack.cc
@@ -0,0 +1,100 @@
+/** @file
+ *
+ * A brief file description
+ *
+ * @section license License
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// To make compile faster
+// https://github.com/philsquared/Catch/blob/master/docs/slow-compiles.md
+// #define CATCH_CONFIG_MAIN
+#define CATCH_CONFIG_RUNNER
+#include "catch.hpp"
+
+#include "ts/I_Layout.h"
+#include "ts/Diags.h"
+
+#include "I_EventSystem.h"
+#include "RecordsConfig.h"
+
+#include "QUICConfig.h"
+#include "HuffmanCodec.h"
+#include "QPACK.h"
+#include "HTTP.h"
+
+#define TEST_THREADS 1
+
+char qifdir[256] = "test/qif/";
+int tablesize = 4096;
+int streams = 100;
+int ackmode = 0;
+char appname[256] = "ats";
+
+struct EventProcessorListener : Catch::TestEventListenerBase {
+ using TestEventListenerBase::TestEventListenerBase; // inherit constructor
+
+ virtual void
+ testRunStarting(Catch::TestRunInfo const &testRunInfo) override
+ {
+ BaseLogFile *base_log_file = new BaseLogFile("stderr");
+ diags = new Diags(testRunInfo.name.c_str(), "" /* tags */, "" /* actions */, base_log_file);
+ diags->activate_taglist("qpack", DiagsTagType_Debug);
+ diags->config.enabled[DiagsTagType_Debug] = true;
+ diags->show_location = SHOW_LOCATION_DEBUG;
+
+ Layout::create();
+ RecProcessInit(RECM_STAND_ALONE);
+ LibRecordsConfigInit();
+
+ QUICConfig::startup();
+
+ ink_event_system_init(EVENT_SYSTEM_MODULE_VERSION);
+ eventProcessor.start(TEST_THREADS);
+
+ Thread *main_thread = new EThread;
+ main_thread->set_specific();
+
+ url_init();
+ mime_init();
+ http_init();
+ hpack_huffman_init();
+ }
+};
+CATCH_REGISTER_LISTENER(EventProcessorListener);
+
+int
+main(int argc, char *argv[])
+{
+ Catch::Session session;
+ using namespace Catch::clara;
+ auto cli = session.cli() | Opt(qifdir, "dir")["--q-qif-dir"]("path for a directory that contains QIF files (default:test/qif/") |
+ Opt(tablesize, "size")["--q-dynamic-table-size"]("dynamic table size: 0-65535 (default:4096)") |
+ Opt(streams, "n")["--q-max-blocked-streams"]("max blocked streams: 0-65535 (default:100)") |
+ Opt(ackmode, "mode")["--q-ack-mode"]("acknowledgement mode: none(default:0) or immediate(1)") |
+ Opt(appname, "app")["--q-app"]("app name: app name (default:ats)");
+
+ session.cli(cli);
+
+ int returnCode = session.applyCommandLine(argc, argv);
+ if (returnCode != 0) {
+ return returnCode;
+ }
+
+ return session.run();
+}
diff --git a/proxy/hq/test/stub.cc b/proxy/hq/test/stub.cc
new file mode 100644
index 0000000..5949178
--- /dev/null
+++ b/proxy/hq/test/stub.cc
@@ -0,0 +1,88 @@
+/** @file
+ *
+ * Stubs for QUICConfig
+ *
+ * @section license License
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+#include "ts/ink_assert.h"
+
+#include "P_SSLConfig.h"
+
+bool
+SSLParseCertificateConfiguration(const SSLConfigParams *, SSL_CTX *)
+{
+ return false;
+}
+
+SSLConfigParams *
+SSLConfig::acquire()
+{
+ return nullptr;
+}
+
+void
+SSLConfig::release(SSLConfigParams *)
+{
+ return;
+}
+
+#include "P_SSLNextProtocolSet.h"
+
+bool
+SSLNextProtocolSet::advertiseProtocols(const unsigned char **out, unsigned *len) const
+{
+ return true;
+}
+
+#include "InkAPIInternal.h"
+int
+APIHook::invoke(int, void *)
+{
+ ink_assert(false);
+ return 0;
+}
+
+APIHook *
+APIHook::next() const
+{
+ ink_assert(false);
+ return nullptr;
+}
+
+APIHook *
+APIHooks::get() const
+{
+ ink_assert(false);
+ return nullptr;
+}
+
+void
+APIHooks::clear()
+{
+}
+
+void
+APIHooks::prepend(INKContInternal *cont)
+{
+}
+
+void
+APIHooks::append(INKContInternal *cont)
+{
+}
diff --git a/proxy/hq/test/test_QPACK.cc b/proxy/hq/test/test_QPACK.cc
new file mode 100644
index 0000000..2e1e91c
--- /dev/null
+++ b/proxy/hq/test/test_QPACK.cc
@@ -0,0 +1,433 @@
+/** @file
+ *
+ * A brief file description
+ *
+ * @section license License
+ *
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "catch.hpp"
+#include <cstdio>
+#include <cstdlib>
+#include <fstream>
+#include "XPACK.h"
+#include "QPACK.h"
+#include "HTTP.h"
+#include "../../iocore/net/quic/Mock.h"
+
+// Declared in main_qpack.cc
+extern char qifdir[256];
+extern int tablesize;
+extern int streams;
+extern int ackmode;
+extern char appname[256];
+
+constexpr int ACK_MODE_IMMEDIATE = 1;
+constexpr int ACK_MODE_NONE = 0;
+
+class TestQUICConnection : public MockQUICConnection
+{
+};
+
+class QUICApplicationDriver
+{
+public:
+ QUICApplicationDriver() {}
+
+ QUICConnection *
+ get_connection()
+ {
+ return &this->_connection;
+ }
+
+private:
+ TestQUICConnection _connection;
+};
+
+class TestQUICStream : public QUICStream
+{
+public:
+ TestQUICStream(QUICStreamId sid) : QUICStream(new MockQUICRTTProvider(), new MockQUICConnectionInfoProvider(), sid) {}
+
+ void
+ write(const uint8_t *buf, size_t buf_len, QUICOffset offset, bool last)
+ {
+ this->_write_to_read_vio(offset, buf, buf_len, last);
+ this->_signal_read_event();
+ }
+
+ size_t
+ read(uint8_t *buf, size_t buf_len)
+ {
+ this->_signal_write_event();
+ IOBufferReader *reader = this->_write_vio.get_reader();
+ return reader->read(buf, buf_len);
+ }
+};
+
+class TestQPACKEventHandler : public Continuation
+{
+public:
+ TestQPACKEventHandler() : Continuation() { SET_HANDLER(&TestQPACKEventHandler::event_handler); }
+
+ int
+ event_handler(int event, Event *data)
+ {
+ this->_event = event;
+ return 0;
+ }
+
+ int
+ last_event()
+ {
+ return this->_event;
+ }
+
+private:
+ int _event = 0;
+};
+
+static int
+load_qif_file(const char *filename, HTTPHdr **headers)
+{
+ HTTPHdr *hdr = nullptr;
+ int n = 0;
+ std::ifstream ifs(filename);
+ std::string line;
+
+ while (std::getline(ifs, line)) {
+ if (line.empty()) {
+ if (hdr) {
+ headers[n++] = hdr;
+ hdr = nullptr;
+ } else {
+ continue;
+ }
+ } else if (line.at(0) == '#') {
+ continue;
+ } else {
+ if (!hdr) {
+ hdr = new HTTPHdr();
+ hdr->create(HTTP_TYPE_REQUEST);
+ }
+ auto tab = line.find_first_of('\t');
+ auto name = line.substr(0, tab);
+ auto value = line.substr(tab + 1);
+ hdr->value_set(name.c_str(), tab, value.c_str(), line.length() - tab - 1);
+ }
+ }
+ if (hdr) {
+ headers[n++] = hdr;
+ }
+
+ return n;
+}
+
+void
+output_encoder_stream_data(FILE *fd, TestQUICStream *stream)
+{
+ uint8_t buf[1024];
+
+ // Write StreamId (0)
+ uint64_t stream_id = 0;
+ fwrite(reinterpret_cast<uint8_t *>(&stream_id), 8, 1, fd);
+
+ // Skip 32 bits for Legnth
+ fseek(fd, 4, SEEK_CUR);
+
+ // Write QPACKData
+ uint64_t total, nread;
+ total = 0;
+ while ((nread = stream->read(buf, sizeof(buf))) > 0) {
+ fwrite(buf, nread, 1, fd);
+ total += nread;
+ }
+
+ // Back to the posistion for Length
+ fseek(fd, -(total + 4), SEEK_CUR);
+
+ // Write Length
+ uint32_t len = htobe32(total);
+ fwrite(reinterpret_cast<uint8_t *>(&len), 4, 1, fd);
+
+ // Back to the tail
+ fseek(fd, 0, SEEK_END);
+}
+
+void
+output_encoded_data(FILE *fd, uint64_t stream_id, IOBufferReader *header_block_reader)
+{
+ uint8_t buf[1024];
+
+ // Write StreamId
+ stream_id = htobe64(stream_id);
+ fwrite(reinterpret_cast<uint8_t *>(&stream_id), 8, 1, fd);
+
+ // Skip 32 bits for Legnth
+ fseek(fd, 4, SEEK_CUR);
+
+ // Write QPACKData
+ int64_t total, nread;
+ total = 0;
+ while ((nread = header_block_reader->read(buf, sizeof(buf))) > 0) {
+ fwrite(buf, nread, 1, fd);
+ total += nread;
+ }
+
+ // Back to the posistion for Length
+ fseek(fd, -(total + 4), SEEK_CUR);
+
+ // Write Length
+ uint32_t len = htobe32(total);
+ fwrite(reinterpret_cast<uint8_t *>(&len), 4, 1, fd);
+
+ // Back to the tail
+ fseek(fd, 0, SEEK_END);
+}
+
+void
+output_decoded_headers(FILE *fd, HTTPHdr **headers, uint64_t n)
+{
+ for (uint64_t i = 0; i < n; ++i) {
+ HTTPHdr *header_set = headers[i];
+ if (!header_set) {
+ continue;
+ }
+ fprintf(fd, "# stream %llu\n", i + 1);
+ MIMEFieldIter field_iter;
+ for (MIMEField *field = header_set->iter_get_first(&field_iter); field != nullptr;
+ field = header_set->iter_get_next(&field_iter)) {
+ int name_len;
+ int value_len;
+ const char *name = field->name_get(&name_len);
+ const char *value = field->value_get(&value_len);
+ fprintf(fd, "%.*s\t%.*s\n", name_len, name, value_len, value);
+ }
+ fprintf(fd, "\n");
+ }
+}
+
+static int
+read_block(FILE *fd, uint64_t &stream_id, uint8_t **head, uint32_t &block_len)
+{
+ size_t len;
+
+ // Read Stream ID
+ len = fread(&stream_id, 1, 8, fd);
+ if (len != 8) {
+ return -1;
+ }
+ stream_id = be64toh(stream_id);
+
+ // Read Length
+ len = fread(&block_len, 1, 4, fd);
+ if (len != 4) {
+ return -1;
+ }
+ block_len = be32toh(block_len);
+
+ // Set the head of block
+ *head = reinterpret_cast<uint8_t *>(ats_malloc(block_len));
+ len = fread(*head, 1, block_len, fd);
+ if (len != block_len) {
+ ats_free(*head);
+ return -1;
+ }
+
+ return 0;
+}
+
+void
+acknowledge_header_block(TestQUICStream *stream, uint64_t stream_id)
+{
+ uint8_t buf[128];
+ int buf_len = 0;
+
+ buf[0] = 0x80;
+ int ret = xpack_encode_integer(buf, buf + sizeof(buf), stream_id, 7);
+ stream->write(buf, ret, 0, stream_id);
+}
+
+static int
+test_encode(const char *qif_file, int dts, int mbs, int am)
+{
+ int ret = 0;
+
+ char output_filename[256];
+ sprintf(output_filename, "%s.ats.%d.%d.%d", qif_file, dts, mbs, am);
+ FILE *fd = fopen(output_filename, "w");
+
+ HTTPHdr *requests[256] = {nullptr};
+ int n_requests = load_qif_file(qif_file, requests);
+
+ QUICApplicationDriver driver;
+ QPACK *qpack = new QPACK(driver.get_connection(), dts);
+ TestQUICStream *encoder_stream = new TestQUICStream(0);
+ TestQUICStream *decoder_stream = new TestQUICStream(9999);
+ qpack->set_stream(encoder_stream);
+ qpack->set_stream(decoder_stream);
+
+ uint64_t stream_id = 1;
+ MIOBuffer *header_block = new_MIOBuffer();
+ IOBufferReader *header_block_reader = header_block->alloc_reader();
+ for (int i = 0; i < n_requests; ++i) {
+ HTTPHdr *hdr = requests[i];
+ ret = qpack->encode(stream_id, *hdr, header_block);
+ if (ret < 0) {
+ break;
+ }
+
+ output_encoder_stream_data(fd, encoder_stream);
+ output_encoded_data(fd, stream_id, header_block_reader);
+
+ if (am == ACK_MODE_IMMEDIATE) {
+ acknowledge_header_block(decoder_stream, stream_id);
+ }
+
+ ++stream_id;
+ }
+
+ fflush(fd);
+ fclose(fd);
+
+ return ret;
+}
+
+static int
+test_decode(const char *qif_file, int dts, int mbs, int am, const char *app_name)
+{
+ int ret = 0;
+
+ char data_filename[256];
+ sprintf(data_filename, "%s.%s.%d.%d.%d", qif_file, app_name, dts, mbs, am);
+ FILE *fd_in = fopen(data_filename, "r");
+
+ char output_filename[256];
+ sprintf(output_filename, "%s.decoded", data_filename);
+ FILE *fd_out = fopen(output_filename, "w");
+
+ HTTPHdr *requests[256];
+ int n_requests = load_qif_file(qif_file, requests);
+
+ TestQPACKEventHandler *event_handler = new TestQPACKEventHandler();
+
+ QUICApplicationDriver driver;
+ QPACK *qpack = new QPACK(driver.get_connection(), dts);
+ TestQUICStream *encoder_stream = new TestQUICStream(0);
+ qpack->set_stream(encoder_stream);
+
+ int offset = 0;
+ uint8_t *block = nullptr;
+ uint32_t block_len;
+ int read_len = 0;
+
+ uint64_t stream_id = 1;
+ HTTPHdr *header_sets[256] = {nullptr};
+ int n_headers = 0;
+ while ((read_len = read_block(fd_in, stream_id, &block, block_len)) >= 0) {
+ if (stream_id == encoder_stream->id()) {
+ encoder_stream->write(block, block_len, offset, false);
+ offset += block_len;
+ } else {
+ if (!header_sets[stream_id - 1]) {
+ header_sets[stream_id - 1] = new HTTPHdr();
+ header_sets[stream_id - 1]->create(HTTP_TYPE_REQUEST);
+ ++n_headers;
+ }
+ qpack->decode(stream_id, block, block_len, *header_sets[stream_id - 1], event_handler, eventProcessor.all_ethreads[0]);
+ }
+ ats_free(block);
+ }
+
+ if (!feof(fd_in)) {
+ return -1;
+ }
+
+ sleep(3);
+
+ CHECK(event_handler->last_event() == QPACK_EVENT_DECODE_COMPLETE);
+
+ output_decoded_headers(fd_out, header_sets, n_headers);
+
+ for (unsigned int i = 0; i < countof(header_sets); ++i) {
+ if (header_sets[i]) {
+ header_sets[i]->destroy();
+ delete header_sets[i];
+ }
+ }
+
+ return ret;
+}
+
+TEST_CASE("Encoding", "[qpack]")
+{
+ struct dirent *d;
+ DIR *dir = opendir(qifdir);
+
+ if (dir == nullptr) {
+ return;
+ }
+
+ struct stat st;
+ char qif_file[PATH_MAX + 1] = "";
+ strcat(qif_file, qifdir);
+
+ while ((d = readdir(dir)) != nullptr) {
+ char section_name[128];
+ sprintf(section_name, "%s: DTS=%d, MBS=%d, AM=%d", d->d_name, tablesize, streams, ackmode);
+ SECTION(section_name)
+ {
+ qif_file[strlen(qifdir)] = '/';
+ qif_file[strlen(qifdir) + 1] = '\0';
+ ink_strlcat(qif_file, d->d_name, sizeof(qif_file));
+ stat(qif_file, &st);
+ if (S_ISREG(st.st_mode) && strstr(d->d_name, ".qif") == (d->d_name + (strlen(d->d_name) - 4))) {
+ CHECK(test_encode(qif_file, tablesize, streams, ackmode) == 0);
+ }
+ }
+ }
+}
+
+TEST_CASE("Decoding", "[qpack]")
+{
+ struct dirent *d;
+ DIR *dir = opendir(qifdir);
+
+ if (dir == nullptr) {
+ return;
+ }
+
+ struct stat st;
+ char qif_file[PATH_MAX + 1] = "";
+ strcat(qif_file, qifdir);
+
+ while ((d = readdir(dir)) != nullptr) {
+ char section_name[128];
+ sprintf(section_name, "%s: DTS=%d, MBS=%d, AM=%d, APP=%s", d->d_name, tablesize, streams, ackmode, appname);
+ SECTION(section_name)
+ {
+ qif_file[strlen(qifdir)] = '/';
+ qif_file[strlen(qifdir) + 1] = '\0';
+ ink_strlcat(qif_file, d->d_name, sizeof(qif_file));
+ stat(qif_file, &st);
+ if (S_ISREG(st.st_mode) && strstr(d->d_name, ".qif") == (d->d_name + (strlen(d->d_name) - 4))) {
+ CHECK(test_decode(qif_file, tablesize, streams, ackmode, appname) == 0);
+ }
+ }
+ }
+}