Deserialize transport knob
Summary: Temporarily, we use a simple json-format for transport knob. Currently it's limited to a single packet, but that should be enough for current usage.
MODIFY
quic/api/QuicTransportBase.h
quic/api/QuicTransportBase.h
@@ -729,7 +729,7 @@ class QuicTransportBase : public QuicSocket {
/**
* Callback when we receive a transport knob
*/
- void onTransportKnobs(Buf knobBlob);
+ virtual void onTransportKnobs(Buf knobBlob);
struct ByteEventDetail {
ByteEventDetail(uint64_t offsetIn, ByteEventCallback* callbackIn)

MODIFY
quic/common/CMakeLists.txt
quic/common/CMakeLists.txt
@@ -92,6 +92,28 @@ target_link_libraries(
Folly::folly
)
+add_library(
+ mvfst_transport_knobs STATIC
+ TransportKnobs.cpp
+)
+
+target_include_directories(
+ mvfst_transport_knobs PUBLIC
+ $<BUILD_INTERFACE:${QUIC_FBCODE_ROOT}>
+ $<INSTALL_INTERFACE:include/>
+)
+
+target_compile_options(
+ mvfst_transport_knobs
+ PRIVATE
+ ${_QUIC_COMMON_COMPILE_OPTIONS}
+)
+
+target_link_libraries(
+ mvfst_transport_knobs PUBLIC
+ Folly::folly
+)
+
file(
GLOB_RECURSE QUIC_API_HEADERS_TOINSTALL
RELATIVE ${CMAKE_CURRENT_SOURCE_DIR}
@@ -127,4 +149,10 @@ install(
DESTINATION lib
)
+install(
+ TARGETS mvfst_transport_knobs
+ EXPORT mvfst-exports
+ DESTINATION lib
+)
+
add_subdirectory(test)

ADD
quic/common/TransportKnobs.cpp
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#include "quic/common/TransportKnobs.h"
+#include <folly/json.h>
+#include <glog/logging.h>
+
+namespace quic {
+
+namespace {
+
+bool compareTransportKnobParam(
+ const TransportKnobParam& lhs,
+ const TransportKnobParam& rhs) {
+ // Sort param by id, then value
+ if (lhs.id != rhs.id) {
+ return lhs.id < rhs.id;
+ }
+ return lhs.val < rhs.val;
+}
+
+} // namespace
+
+folly::Optional<TransportKnobParams> parseTransportKnobs(
+ const std::string& serializedParams) {
+ TransportKnobParams knobParams;
+ try {
+ folly::dynamic params = folly::parseJson(serializedParams);
+ for (const auto& id : params.keys()) {
+ auto paramId = folly::to<uint64_t>(id.asInt());
+ auto val = params[id];
+ switch (val.type()) {
+ case folly::dynamic::Type::BOOL:
+ case folly::dynamic::Type::INT64:
+ knobParams.push_back({paramId, folly::to<uint64_t>(val.asInt())});
+ continue;
+ default:
+ // Quic transport knob param values cannot be of type STRING, ARRAY,
+ // NULLT or OBJECT
+ LOG(ERROR) << "Invalid transport knob param value type";
+ return folly::none;
+ }
+ }
+ } catch (const std::exception& e) {
+ LOG(ERROR) << "fail to parse knobs: " << e.what();
+ return folly::none;
+ }
+
+ std::sort(knobParams.begin(), knobParams.end(), compareTransportKnobParam);
+ return knobParams;
+}
+
+} // namespace quic

ADD
quic/common/TransportKnobs.h
+// Copyright 2004-present Facebook. All Rights Reserved.
+
+#pragma once
+#include <folly/Optional.h>
+#include <string>
+#include <unordered_map>
+#include <vector>
+
+namespace quic {
+
+/**
+ * This is not to be confused with the structure of knob frame, or QUIC's
+ * transport parameter. Knob frame has space, id and a blob. Arbitrary data can
+ * be stored in the blob. TransportKnobParam is a kind of blob that designated
+ * to pass transport-level parameter that's no transferable via existing
+ * methods (e.g. transport parameter).
+ */
+struct TransportKnobParam {
+ uint64_t id;
+ uint64_t val;
+};
+
+using TransportKnobParams = std::vector<TransportKnobParam>;
+
+folly::Optional<TransportKnobParams> parseTransportKnobs(
+ const std::string& serializedParams);
+
+} // namespace quic

ADD
quic/common/test/TransportKnobsTest.cpp
+// (c) Facebook, Inc. and its affiliates. Confidential and proprietary.
+
+#include <quic/common/TransportKnobs.h>
+#include <folly/portability/GTest.h>
+
+using namespace ::testing;
+
+namespace quic {
+namespace test {
+
+struct QuicKnobsParsingTestFixture {
+ std::string serializedKnobs;
+ bool expectError;
+ TransportKnobParams expectParams;
+};
+
+void run(const QuicKnobsParsingTestFixture& fixture) {
+ auto result = parseTransportKnobs(fixture.serializedKnobs);
+ if (fixture.expectError) {
+ EXPECT_FALSE(result.hasValue());
+ } else {
+ EXPECT_EQ(result->size(), fixture.expectParams.size());
+ for (size_t i = 0; i < result->size(); i++) {
+ auto& actualKnob = (*result)[i];
+ auto& expectKnob = fixture.expectParams[i];
+ EXPECT_EQ(actualKnob.id, expectKnob.id) << "Knob " << i;
+ EXPECT_EQ(actualKnob.val, expectKnob.val) << "Knob " << i;
+ }
+ }
+}
+
+TEST(QuicKnobsParsingTest, Simple) {
+ QuicKnobsParsingTestFixture fixture = {
+ "{ \"0\": 1,"
+ " \"1\": 5,"
+ " \"19\": 6,"
+ " \"2\": 3"
+ " }",
+ false,
+ {{0, 1}, {1, 5}, {2, 3}, {19, 6}}};
+ run(fixture);
+}
+
+TEST(QuicKnobsParsingTest, ObjectValue) {
+ QuicKnobsParsingTestFixture fixture = {
+ "{ \"1\": "
+ " {"
+ " \"0\" : 1"
+ " }"
+ "}",
+ true,
+ {}};
+ run(fixture);
+}
+
+TEST(QuicKnobsParsingTest, InvalidJson) {
+ QuicKnobsParsingTestFixture fixture = {
+ "{\"0\": "
+ " \"1\": "
+ " {"
+ " \"0\" : 1"
+ " }"
+ "}",
+ true,
+ {}};
+ run(fixture);
+}
+
+TEST(QuicKnobsParsingTest, Characters) {
+ QuicKnobsParsingTestFixture fixture = {"{ \"o\" : 1 }", true, {}};
+ run(fixture);
+}
+
+TEST(QuicKnobsParsingTest, NegativeNumbers) {
+ QuicKnobsParsingTestFixture fixture = {"{ \"1\" : -1 }", true, {}};
+ run(fixture);
+}
+
+TEST(QuicKnobsParsingTest, StringValue) {
+ QuicKnobsParsingTestFixture fixture = {"{ \"1\" : \"1\" }", true, {}};
+ run(fixture);
+}
+
+TEST(QuicKnobsParsingTest, NonStringKey) {
+ QuicKnobsParsingTestFixture fixture = {"{ 1 : 1 }", true, {}};
+ run(fixture);
+}
+
+TEST(QuicKnobsParsingTest, DoubleKey) {
+ QuicKnobsParsingTestFixture fixture = {"{ \"3.14\" : 1 }", true, {}};
+ run(fixture);
+}
+
+TEST(QuicKnobsParsingTest, DoubleValue) {
+ QuicKnobsParsingTestFixture fixture = {"{ \"10\" : 0.1 }", true, {}};
+ run(fixture);
+}
+
+} // namespace test
+} // namespace quic

MODIFY
quic/server/CMakeLists.txt
quic/server/CMakeLists.txt
@@ -49,6 +49,7 @@ add_dependencies(
mvfst_qlogger
mvfst_state_ack_handler
mvfst_transport
+ mvfst_transport_knobs
)
if(DEFINED CCP_ENABLED)
@@ -74,6 +75,7 @@ target_link_libraries(
mvfst_qlogger
mvfst_state_ack_handler
mvfst_transport
+ mvfst_transport_knobs
${LIBCCP_LIBRARY}
${LIBSTARTCCP_LIBRARY}
)

MODIFY
quic/server/QuicServerTransport.cpp
quic/server/QuicServerTransport.cpp
@@ -563,4 +563,12 @@ QuicServerTransport::getPeerCertificate() const {
return nullptr;
}
+void QuicServerTransport::onTransportKnobs(Buf knobBlob) {
+ std::string serializedKnobs = std::string(
+ reinterpret_cast<const char*>(knobBlob->data()), knobBlob->length());
+ VLOG(4) << "Received transport knobs: " << serializedKnobs;
+ auto params = parseTransportKnobs(serializedKnobs);
+ // Handling not yet implemented
+}
+
} // namespace quic

MODIFY
quic/server/QuicServerTransport.h
quic/server/QuicServerTransport.h
#include <quic/api/QuicTransportBase.h>
#include <quic/api/QuicTransportFunctions.h>
#include <quic/codec/ConnectionIdAlgo.h>
+#include <quic/common/TransportKnobs.h>
#include <quic/congestion_control/CongestionControllerFactory.h>
#include <quic/server/handshake/ServerTransportParametersExtension.h>
#include <quic/server/state/ServerConnectionIdRejector.h>
@@ -132,6 +133,15 @@ class QuicServerTransport
// From ServerHandshake::HandshakeCallback
virtual void onCryptoEventAvailable() noexcept override;
+ void onTransportKnobs(Buf knobBlob) override;
+
+ void handleTransportKnobParams(const TransportKnobParams& params);
+
+ // Made it protected for testing purpose
+ void registerTransportKnobParamHandler(
+ uint64_t paramId,
+ std::function<void(QuicServerConnectionState*, uint64_t)>&& handler);
+
private:
void processPendingData(bool async);
void maybeNotifyTransportReady();

Deserialize transport knob

Summary: Temporarily, we use a simple json-format for transport knob. Currently it's limited to a single packet, but that should be enough for current usage.

Reviewed By: mjoras

Differential Revision: D24584261

fbshipit-source-id: f51ed0fb7560cda2ca447a49d9ad6575c1b2a59f