Enable getting and setting custom QUIC transport parameters.

This is primarily for WebTransport (https://tools.ietf.org/html/draft-vvv-webtransport-quic-00#section-3.2), but may be useful for other purposes in the future.

gfe-relnote: n/a (v99 only)
PiperOrigin-RevId: 268811939
Change-Id: Icf4ba1657ffbca367d72110be86047052bd5dcdf
diff --git a/quic/core/crypto/transport_parameters.cc b/quic/core/crypto/transport_parameters.cc
index 8e4fa40..27b1f98 100644
--- a/quic/core/crypto/transport_parameters.cc
+++ b/quic/core/crypto/transport_parameters.cc
@@ -6,6 +6,7 @@
 
 #include <cstdint>
 #include <cstring>
+#include <forward_list>
 
 #include "third_party/boringssl/src/include/openssl/bytestring.h"
 #include "net/third_party/quiche/src/quic/core/crypto/crypto_framer.h"
@@ -282,6 +283,10 @@
     rv += " " + TransportParameterIdToString(kGoogleQuicParam);
   }
   rv += "]";
+  for (const auto& kv : custom_parameters) {
+    rv += " 0x" + QuicTextUtils::Hex(static_cast<uint32_t>(kv.first));
+    rv += "=" + QuicTextUtils::HexEncode(kv.second);
+  }
   return rv;
 }
 
@@ -534,6 +539,23 @@
     }
   }
 
+  auto custom_parameters = std::make_unique<CBB[]>(in.custom_parameters.size());
+  int i = 0;
+  for (const auto& kv : in.custom_parameters) {
+    CBB* custom_parameter = &custom_parameters[i++];
+    QUIC_BUG_IF(kv.first < 0xff00) << "custom_parameters should not be used "
+                                      "for non-private use parameters";
+    if (!CBB_add_u16(&params, kv.first) ||
+        !CBB_add_u16_length_prefixed(&params, custom_parameter) ||
+        !CBB_add_bytes(custom_parameter,
+                       reinterpret_cast<const uint8_t*>(kv.second.data()),
+                       kv.second.size())) {
+      QUIC_BUG << "Failed to write custom parameter "
+               << static_cast<int>(kv.first);
+      return false;
+    }
+  }
+
   if (!CBB_flush(cbb.get())) {
     QUIC_BUG << "Failed to flush CBB for " << in;
     return false;
@@ -744,6 +766,10 @@
           }
         }
       } break;
+      default:
+        out->custom_parameters[param_id] = std::string(
+            reinterpret_cast<const char*>(CBS_data(&value)), CBS_len(&value));
+        break;
     }
     if (!parse_success) {
       return false;
diff --git a/quic/core/crypto/transport_parameters.h b/quic/core/crypto/transport_parameters.h
index 95a01da..22dc252 100644
--- a/quic/core/crypto/transport_parameters.h
+++ b/quic/core/crypto/transport_parameters.h
@@ -14,6 +14,7 @@
 #include "net/third_party/quiche/src/quic/core/quic_data_writer.h"
 #include "net/third_party/quiche/src/quic/core/quic_types.h"
 #include "net/third_party/quiche/src/quic/core/quic_versions.h"
+#include "net/third_party/quiche/src/quic/platform/api/quic_containers.h"
 
 namespace quic {
 
@@ -24,6 +25,8 @@
 struct QUIC_EXPORT_PRIVATE TransportParameters {
   // The identifier used to differentiate transport parameters.
   enum TransportParameterId : uint16_t;
+  // A map used to specify custom parameters.
+  using ParameterMap = QuicUnorderedMap<TransportParameterId, std::string>;
   // Represents an individual QUIC transport parameter that only encodes a
   // variable length integer. Can only be created inside the constructor for
   // TransportParameters.
@@ -174,6 +177,9 @@
   // the specification.
   bool AreValid() const;
 
+  // Custom parameters that may be specific to application protocol.
+  ParameterMap custom_parameters;
+
   // Allows easily logging transport parameters.
   std::string ToString() const;
   friend QUIC_EXPORT_PRIVATE std::ostream& operator<<(
diff --git a/quic/core/crypto/transport_parameters_test.cc b/quic/core/crypto/transport_parameters_test.cc
index e782c38..ed75f45 100644
--- a/quic/core/crypto/transport_parameters_test.cc
+++ b/quic/core/crypto/transport_parameters_test.cc
@@ -17,6 +17,10 @@
 namespace quic {
 namespace test {
 namespace {
+
+using testing::Pair;
+using testing::UnorderedElementsAre;
+
 const ParsedQuicVersion kVersion(PROTOCOL_TLS1_3, QUIC_VERSION_99);
 const QuicVersionLabel kFakeVersionLabel = 0x01234567;
 const QuicVersionLabel kFakeVersionLabel2 = 0x89ABCDEF;
@@ -47,6 +51,12 @@
     kFakePreferredStatelessResetTokenData,
     kFakePreferredStatelessResetTokenData +
         sizeof(kFakeStatelessResetTokenData));
+const auto kCustomParameter1 =
+    static_cast<TransportParameters::TransportParameterId>(0xffcd);
+const char* kCustomParameter1Value = "foo";
+const auto kCustomParameter2 =
+    static_cast<TransportParameters::TransportParameterId>(0xff34);
+const char* kCustomParameter2Value = "bar";
 
 QuicSocketAddress CreateFakeV4SocketAddress() {
   QuicIpAddress ipv4_address;
@@ -101,6 +111,8 @@
   orig_params.disable_migration = kFakeDisableMigration;
   orig_params.active_connection_id_limit.set_value(
       kFakeActiveConnectionIdLimit);
+  orig_params.custom_parameters[kCustomParameter1] = kCustomParameter1Value;
+  orig_params.custom_parameters[kCustomParameter2] = kCustomParameter2Value;
 
   std::vector<uint8_t> serialized;
   ASSERT_TRUE(SerializeTransportParameters(kVersion, orig_params, &serialized));
@@ -134,6 +146,10 @@
   EXPECT_EQ(kFakeDisableMigration, new_params.disable_migration);
   EXPECT_EQ(kFakeActiveConnectionIdLimit,
             new_params.active_connection_id_limit.value());
+  EXPECT_THAT(
+      new_params.custom_parameters,
+      UnorderedElementsAre(Pair(kCustomParameter1, kCustomParameter1Value),
+                           Pair(kCustomParameter2, kCustomParameter2Value)));
 }
 
 TEST_F(TransportParametersTest, RoundTripServer) {
@@ -205,6 +221,7 @@
             new_params.preferred_address->stateless_reset_token);
   EXPECT_EQ(kFakeActiveConnectionIdLimit,
             new_params.active_connection_id_limit.value());
+  EXPECT_EQ(0u, new_params.custom_parameters.size());
 }
 
 TEST_F(TransportParametersTest, IsValid) {
diff --git a/quic/core/http/end_to_end_test.cc b/quic/core/http/end_to_end_test.cc
index 1033b26..5845f78 100644
--- a/quic/core/http/end_to_end_test.cc
+++ b/quic/core/http/end_to_end_test.cc
@@ -4172,6 +4172,24 @@
       static_cast<QuicSpdySession*>(GetServerSession())->max_allowed_push_id());
 }
 
+TEST_P(EndToEndTest, CustomTransportParameters) {
+  if (GetParam().negotiated_version.handshake_protocol != PROTOCOL_TLS1_3) {
+    Initialize();
+    return;
+  }
+
+  constexpr auto kCustomParameter =
+      static_cast<TransportParameters::TransportParameterId>(0xff34);
+  client_config_.custom_transport_parameters_to_send()[kCustomParameter] =
+      "test";
+  ASSERT_TRUE(Initialize());
+
+  EXPECT_TRUE(client_->client()->WaitForCryptoHandshakeConfirmed());
+  EXPECT_EQ(server_config_.received_custom_transport_parameters().at(
+                kCustomParameter),
+            "test");
+}
+
 }  // namespace
 }  // namespace test
 }  // namespace quic
diff --git a/quic/core/quic_config.cc b/quic/core/quic_config.cc
index 6400b0e..e48d21a 100644
--- a/quic/core/quic_config.cc
+++ b/quic/core/quic_config.cc
@@ -978,6 +978,7 @@
   initial_round_trip_time_us_.ToHandshakeMessage(
       params->google_quic_params.get());
   connection_options_.ToHandshakeMessage(params->google_quic_params.get());
+  params->custom_parameters = custom_transport_parameters_to_send_;
 
   return true;
 }
@@ -1085,6 +1086,8 @@
     }
   }
 
+  received_custom_transport_parameters_ = params.custom_parameters;
+
   *error_details = "";
   return QUIC_NO_ERROR;
 }
diff --git a/quic/core/quic_config.h b/quic/core/quic_config.h
index b9ebcc1..25522b3 100644
--- a/quic/core/quic_config.h
+++ b/quic/core/quic_config.h
@@ -479,6 +479,14 @@
                                            HelloType hello_type,
                                            std::string* error_details);
 
+  TransportParameters::ParameterMap& custom_transport_parameters_to_send() {
+    return custom_transport_parameters_to_send_;
+  }
+  const TransportParameters::ParameterMap&
+  received_custom_transport_parameters() const {
+    return received_custom_transport_parameters_;
+  }
+
  private:
   friend class test::QuicConfigPeer;
 
@@ -557,6 +565,11 @@
   // deserializing the frame); the received exponent is the value the peer uses
   // to serialize frames and this node uses to deserialize them.
   QuicFixedUint32 ack_delay_exponent_;
+
+  // Custom transport parameters that can be sent and received in the TLS
+  // handshake.
+  TransportParameters::ParameterMap custom_transport_parameters_to_send_;
+  TransportParameters::ParameterMap received_custom_transport_parameters_;
 };
 
 }  // namespace quic