Extract Quiche default connection ID generation algorithms into a library. When integrated into quiche, similar functions can be removed.
PiperOrigin-RevId: 465353115
diff --git a/build/source_list.bzl b/build/source_list.bzl
index 43d246d..7c1b37c 100644
--- a/build/source_list.bzl
+++ b/build/source_list.bzl
@@ -186,6 +186,7 @@
"quic/core/crypto/tls_server_connection.h",
"quic/core/crypto/transport_parameters.h",
"quic/core/crypto/web_transport_fingerprint_proof_verifier.h",
+ "quic/core/deterministic_connection_id_generator.h",
"quic/core/frames/quic_ack_frame.h",
"quic/core/frames/quic_ack_frequency_frame.h",
"quic/core/frames/quic_blocked_frame.h",
@@ -527,6 +528,7 @@
"quic/core/crypto/tls_server_connection.cc",
"quic/core/crypto/transport_parameters.cc",
"quic/core/crypto/web_transport_fingerprint_proof_verifier.cc",
+ "quic/core/deterministic_connection_id_generator.cc",
"quic/core/frames/quic_ack_frame.cc",
"quic/core/frames/quic_ack_frequency_frame.cc",
"quic/core/frames/quic_blocked_frame.cc",
@@ -1153,6 +1155,7 @@
"quic/core/crypto/quic_random_test.cc",
"quic/core/crypto/transport_parameters_test.cc",
"quic/core/crypto/web_transport_fingerprint_proof_verifier_test.cc",
+ "quic/core/deterministic_connection_id_generator_test.cc",
"quic/core/frames/quic_frames_test.cc",
"quic/core/http/capsule_test.cc",
"quic/core/http/http_decoder_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni
index ba93bf9..76219c9 100644
--- a/build/source_list.gni
+++ b/build/source_list.gni
@@ -186,6 +186,7 @@
"src/quiche/quic/core/crypto/tls_server_connection.h",
"src/quiche/quic/core/crypto/transport_parameters.h",
"src/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.h",
+ "src/quiche/quic/core/deterministic_connection_id_generator.h",
"src/quiche/quic/core/frames/quic_ack_frame.h",
"src/quiche/quic/core/frames/quic_ack_frequency_frame.h",
"src/quiche/quic/core/frames/quic_blocked_frame.h",
@@ -527,6 +528,7 @@
"src/quiche/quic/core/crypto/tls_server_connection.cc",
"src/quiche/quic/core/crypto/transport_parameters.cc",
"src/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.cc",
+ "src/quiche/quic/core/deterministic_connection_id_generator.cc",
"src/quiche/quic/core/frames/quic_ack_frame.cc",
"src/quiche/quic/core/frames/quic_ack_frequency_frame.cc",
"src/quiche/quic/core/frames/quic_blocked_frame.cc",
@@ -1153,6 +1155,7 @@
"src/quiche/quic/core/crypto/quic_random_test.cc",
"src/quiche/quic/core/crypto/transport_parameters_test.cc",
"src/quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier_test.cc",
+ "src/quiche/quic/core/deterministic_connection_id_generator_test.cc",
"src/quiche/quic/core/frames/quic_frames_test.cc",
"src/quiche/quic/core/http/capsule_test.cc",
"src/quiche/quic/core/http/http_decoder_test.cc",
diff --git a/build/source_list.json b/build/source_list.json
index e3b983c..8e32230 100644
--- a/build/source_list.json
+++ b/build/source_list.json
@@ -185,6 +185,7 @@
"quiche/quic/core/crypto/tls_server_connection.h",
"quiche/quic/core/crypto/transport_parameters.h",
"quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.h",
+ "quiche/quic/core/deterministic_connection_id_generator.h",
"quiche/quic/core/frames/quic_ack_frame.h",
"quiche/quic/core/frames/quic_ack_frequency_frame.h",
"quiche/quic/core/frames/quic_blocked_frame.h",
@@ -526,6 +527,7 @@
"quiche/quic/core/crypto/tls_server_connection.cc",
"quiche/quic/core/crypto/transport_parameters.cc",
"quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier.cc",
+ "quiche/quic/core/deterministic_connection_id_generator.cc",
"quiche/quic/core/frames/quic_ack_frame.cc",
"quiche/quic/core/frames/quic_ack_frequency_frame.cc",
"quiche/quic/core/frames/quic_blocked_frame.cc",
@@ -1152,6 +1154,7 @@
"quiche/quic/core/crypto/quic_random_test.cc",
"quiche/quic/core/crypto/transport_parameters_test.cc",
"quiche/quic/core/crypto/web_transport_fingerprint_proof_verifier_test.cc",
+ "quiche/quic/core/deterministic_connection_id_generator_test.cc",
"quiche/quic/core/frames/quic_frames_test.cc",
"quiche/quic/core/http/capsule_test.cc",
"quiche/quic/core/http/http_decoder_test.cc",
diff --git a/quiche/quic/core/deterministic_connection_id_generator.cc b/quiche/quic/core/deterministic_connection_id_generator.cc
new file mode 100644
index 0000000..fd86dc7
--- /dev/null
+++ b/quiche/quic/core/deterministic_connection_id_generator.cc
@@ -0,0 +1,73 @@
+// Copyright (c) 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/deterministic_connection_id_generator.h"
+
+#include "quiche/quic/core/quic_utils.h"
+#include "quiche/quic/platform/api/quic_bug_tracker.h"
+#include "quiche/quic/platform/api/quic_logging.h"
+
+namespace quic {
+
+DeterministicConnectionIdGenerator::DeterministicConnectionIdGenerator(
+ uint8_t expected_connection_id_length)
+ : expected_connection_id_length_(expected_connection_id_length) {
+ if (expected_connection_id_length_ >
+ kQuicMaxConnectionIdWithLengthPrefixLength) {
+ QUIC_BUG(quic_bug_465151159_01)
+ << "Issuing connection IDs longer than allowed in RFC9000";
+ }
+}
+
+absl::optional<QuicConnectionId>
+DeterministicConnectionIdGenerator::GenerateNextConnectionId(
+ const QuicConnectionId& original) {
+ if (expected_connection_id_length_ == 0) {
+ return EmptyQuicConnectionId();
+ }
+ const uint64_t connection_id_hash64 = QuicUtils::FNV1a_64_Hash(
+ absl::string_view(original.data(), original.length()));
+ if (expected_connection_id_length_ <= sizeof(uint64_t)) {
+ return QuicConnectionId(
+ reinterpret_cast<const char*>(&connection_id_hash64),
+ expected_connection_id_length_);
+ }
+ char new_connection_id_data[255] = {};
+ const absl::uint128 connection_id_hash128 = QuicUtils::FNV1a_128_Hash(
+ absl::string_view(original.data(), original.length()));
+ static_assert(sizeof(connection_id_hash64) + sizeof(connection_id_hash128) <=
+ sizeof(new_connection_id_data),
+ "bad size");
+ memcpy(new_connection_id_data, &connection_id_hash64,
+ sizeof(connection_id_hash64));
+ // TODO(martinduke): We don't have any test coverage of the line below. In
+ // particular, if the memcpy somehow misses a byte, a test could check if one
+ // byte position in generated connection IDs is always the same.
+ memcpy(new_connection_id_data + sizeof(connection_id_hash64),
+ &connection_id_hash128, sizeof(connection_id_hash128));
+ return QuicConnectionId(new_connection_id_data,
+ expected_connection_id_length_);
+}
+
+absl::optional<QuicConnectionId>
+DeterministicConnectionIdGenerator::MaybeReplaceConnectionId(
+ const QuicConnectionId& original, const ParsedQuicVersion& version) {
+ if (original.length() == expected_connection_id_length_) {
+ return absl::optional<QuicConnectionId>();
+ }
+ QUICHE_DCHECK(version.AllowsVariableLengthConnectionIds());
+ absl::optional<QuicConnectionId> new_connection_id =
+ GenerateNextConnectionId(original);
+ // Verify that ReplaceShortServerConnectionId is deterministic.
+ QUICHE_DCHECK(new_connection_id.has_value());
+ QUICHE_DCHECK_EQ(
+ *new_connection_id,
+ static_cast<QuicConnectionId>(*GenerateNextConnectionId(original)));
+ QUICHE_DCHECK_EQ(expected_connection_id_length_, new_connection_id->length());
+ QUIC_DLOG(INFO) << "Replacing incoming connection ID " << original << " with "
+ << new_connection_id.value();
+ return new_connection_id;
+}
+
+} // namespace quic
diff --git a/quiche/quic/core/deterministic_connection_id_generator.h b/quiche/quic/core/deterministic_connection_id_generator.h
new file mode 100644
index 0000000..cd27811
--- /dev/null
+++ b/quiche/quic/core/deterministic_connection_id_generator.h
@@ -0,0 +1,37 @@
+// Copyright (c) 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+// A Connection ID generator that generates deterministic connection IDs for
+// QUIC servers.
+
+#ifndef QUICHE_QUIC_CORE_CONNECTION_ID_GENERATOR_DETERMINISTIC_H_
+#define QUICHE_QUIC_CORE_CONNECTION_ID_GENERATOR_DETERMINISTIC_H_
+
+#include "quiche/quic/core/connection_id_generator.h"
+
+namespace quic {
+
+// Generates connection IDs deterministically from the provided original
+// connection ID.
+class QUIC_EXPORT_PRIVATE DeterministicConnectionIdGenerator
+ : public ConnectionIdGeneratorInterface {
+ public:
+ DeterministicConnectionIdGenerator(uint8_t expected_connection_id_length);
+
+ // Hashes |original| to create a new connection ID.
+ absl::optional<QuicConnectionId> GenerateNextConnectionId(
+ const QuicConnectionId& original) override;
+ // Replace the connection ID if and only if |original| is not of the expected
+ // length.
+ absl::optional<QuicConnectionId> MaybeReplaceConnectionId(
+ const QuicConnectionId& original,
+ const ParsedQuicVersion& version) override;
+
+ private:
+ const uint8_t expected_connection_id_length_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_CORE__CONNECTION_ID_GENERATOR_DETERMINISTIC_H_
diff --git a/quiche/quic/core/deterministic_connection_id_generator_test.cc b/quiche/quic/core/deterministic_connection_id_generator_test.cc
new file mode 100644
index 0000000..67016b5
--- /dev/null
+++ b/quiche/quic/core/deterministic_connection_id_generator_test.cc
@@ -0,0 +1,122 @@
+// Copyright (c) 2022 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include "quiche/quic/core/deterministic_connection_id_generator.h"
+
+#include <optional>
+
+#include "quiche/quic/platform/api/quic_test.h"
+#include "quiche/quic/test_tools/quic_test_utils.h"
+
+namespace quic {
+namespace test {
+
+struct TestParams {
+ TestParams(int connection_id_length)
+ : connection_id_length_(connection_id_length) {}
+ TestParams() : TestParams(kQuicDefaultConnectionIdLength) {}
+
+ friend std::ostream& operator<<(std::ostream& os, const TestParams& p) {
+ os << "{ connection ID length: " << p.connection_id_length_ << " }";
+ return os;
+ }
+
+ int connection_id_length_;
+};
+
+// Constructs various test permutations.
+std::vector<struct TestParams> GetTestParams() {
+ std::vector<struct TestParams> params;
+ std::vector<int> connection_id_lengths{7, 8, 9, 16, 20};
+ for (int connection_id_length : connection_id_lengths) {
+ params.push_back(TestParams(connection_id_length));
+ }
+ return params;
+}
+
+class DeterministicConnectionIdGeneratorTest
+ : public QuicTestWithParam<TestParams> {
+ public:
+ DeterministicConnectionIdGeneratorTest()
+ : connection_id_length_(GetParam().connection_id_length_),
+ generator_(DeterministicConnectionIdGenerator(connection_id_length_)),
+ version_(ParsedQuicVersion::RFCv1()) {}
+
+ protected:
+ int connection_id_length_;
+ DeterministicConnectionIdGenerator generator_;
+ ParsedQuicVersion version_;
+};
+
+INSTANTIATE_TEST_SUITE_P(DeterministicConnectionIdGeneratorTests,
+ DeterministicConnectionIdGeneratorTest,
+ ::testing::ValuesIn(GetTestParams()));
+
+TEST_P(DeterministicConnectionIdGeneratorTest,
+ NextConnectionIdIsDeterministic) {
+ // Verify that two equal connection IDs get the same replacement.
+ QuicConnectionId connection_id64a = TestConnectionId(33);
+ QuicConnectionId connection_id64b = TestConnectionId(33);
+ EXPECT_EQ(connection_id64a, connection_id64b);
+ EXPECT_EQ(*generator_.GenerateNextConnectionId(connection_id64a),
+ *generator_.GenerateNextConnectionId(connection_id64b));
+ QuicConnectionId connection_id72a = TestConnectionIdNineBytesLong(42);
+ QuicConnectionId connection_id72b = TestConnectionIdNineBytesLong(42);
+ EXPECT_EQ(connection_id72a, connection_id72b);
+ EXPECT_EQ(*generator_.GenerateNextConnectionId(connection_id72a),
+ *generator_.GenerateNextConnectionId(connection_id72b));
+}
+
+TEST_P(DeterministicConnectionIdGeneratorTest,
+ NextConnectionIdLengthIsCorrect) {
+ // Verify that all generated IDs are of the correct length.
+ const char connection_id_bytes[255] = {};
+ for (uint8_t i = 0; i < sizeof(connection_id_bytes) - 1; ++i) {
+ QuicConnectionId connection_id(connection_id_bytes, i);
+ absl::optional<QuicConnectionId> replacement_connection_id =
+ generator_.GenerateNextConnectionId(connection_id);
+ ASSERT_TRUE(replacement_connection_id.has_value());
+ EXPECT_EQ(connection_id_length_, replacement_connection_id->length());
+ }
+}
+
+TEST_P(DeterministicConnectionIdGeneratorTest, NextConnectionIdHasEntropy) {
+ // Make sure all these test connection IDs have different replacements.
+ for (uint64_t i = 0; i < 256; ++i) {
+ QuicConnectionId connection_id_i = TestConnectionId(i);
+ absl::optional<QuicConnectionId> new_i =
+ generator_.GenerateNextConnectionId(connection_id_i);
+ ASSERT_TRUE(new_i.has_value());
+ EXPECT_NE(connection_id_i, *new_i);
+ for (uint64_t j = i + 1; j <= 256; ++j) {
+ QuicConnectionId connection_id_j = TestConnectionId(j);
+ EXPECT_NE(connection_id_i, connection_id_j);
+ absl::optional<QuicConnectionId> new_j =
+ generator_.GenerateNextConnectionId(connection_id_j);
+ ASSERT_TRUE(new_j.has_value());
+ EXPECT_NE(*new_i, *new_j);
+ }
+ }
+}
+
+TEST_P(DeterministicConnectionIdGeneratorTest,
+ OnlyReplaceConnectionIdWithWrongLength) {
+ const char connection_id_input[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06,
+ 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d,
+ 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14};
+ for (int i = 0; i < kQuicMaxConnectionIdWithLengthPrefixLength; i++) {
+ QuicConnectionId input = QuicConnectionId(connection_id_input, i);
+ absl::optional<QuicConnectionId> output =
+ generator_.MaybeReplaceConnectionId(input, version_);
+ if (i == connection_id_length_) {
+ EXPECT_FALSE(output.has_value());
+ } else {
+ ASSERT_TRUE(output.has_value());
+ EXPECT_EQ(*output, generator_.GenerateNextConnectionId(input));
+ }
+ }
+}
+
+} // namespace test
+} // namespace quic