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