| // 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/load_balancer/load_balancer_encoder.h" |
| |
| #include <cstdint> |
| |
| #include "absl/numeric/int128.h" |
| #include "quiche/quic/platform/api/quic_expect_bug.h" |
| #include "quiche/quic/platform/api/quic_test.h" |
| #include "quiche/quic/test_tools/quic_test_utils.h" |
| |
| namespace quic { |
| |
| namespace test { |
| |
| class LoadBalancerEncoderPeer { |
| public: |
| static void SetNumNoncesLeft(LoadBalancerEncoder &encoder, |
| uint64_t nonces_remaining) { |
| encoder.num_nonces_left_ = absl::uint128(nonces_remaining); |
| } |
| }; |
| |
| namespace { |
| |
| class TestLoadBalancerEncoderVisitor |
| : public LoadBalancerEncoderVisitorInterface { |
| public: |
| ~TestLoadBalancerEncoderVisitor() override {} |
| |
| void OnConfigAdded(const uint8_t config_id) override { |
| num_adds_++; |
| current_config_id_ = config_id; |
| } |
| |
| void OnConfigChanged(const uint8_t old_config_id, |
| const uint8_t new_config_id) override { |
| num_adds_++; |
| num_deletes_++; |
| EXPECT_EQ(old_config_id, current_config_id_); |
| current_config_id_ = new_config_id; |
| } |
| |
| void OnConfigDeleted(const uint8_t config_id) override { |
| EXPECT_EQ(config_id, current_config_id_); |
| current_config_id_.reset(); |
| num_deletes_++; |
| } |
| |
| uint32_t num_adds() const { return num_adds_; } |
| uint32_t num_deletes() const { return num_deletes_; } |
| |
| private: |
| uint32_t num_adds_ = 0, num_deletes_ = 0; |
| absl::optional<uint8_t> current_config_id_ = absl::optional<uint8_t>(); |
| }; |
| |
| // Allows the caller to specify the exact results in 64-bit chunks. |
| class TestRandom : public QuicRandom { |
| public: |
| uint64_t RandUint64() override { |
| if (next_values_.empty()) { |
| return base_; |
| } |
| uint64_t value = next_values_.front(); |
| next_values_.pop(); |
| return value; |
| } |
| |
| void RandBytes(void *data, size_t len) override { |
| size_t written = 0; |
| uint8_t *ptr = static_cast<uint8_t *>(data); |
| while (written < len) { |
| uint64_t result = RandUint64(); |
| size_t to_write = (len - written > sizeof(uint64_t)) ? sizeof(uint64_t) |
| : (len - written); |
| memcpy(ptr + written, &result, to_write); |
| written += to_write; |
| } |
| } |
| |
| void InsecureRandBytes(void *data, size_t len) override { |
| RandBytes(data, len); |
| } |
| |
| uint64_t InsecureRandUint64() override { return RandUint64(); } |
| |
| void AddNextValues(uint64_t hi, uint64_t lo) { |
| next_values_.push(hi); |
| next_values_.push(lo); |
| } |
| |
| private: |
| std::queue<uint64_t> next_values_; |
| uint64_t base_ = 0xDEADBEEFDEADBEEF; |
| }; |
| |
| class LoadBalancerEncoderTest : public QuicTest { |
| public: |
| TestRandom random_; |
| }; |
| |
| // Convenience function to shorten the code. Does not check if |array| is long |
| // enough or |length| is valid for a server ID. |
| LoadBalancerServerId MakeServerId(const uint8_t array[], const uint8_t length) { |
| return *LoadBalancerServerId::Create( |
| absl::Span<const uint8_t>(array, length)); |
| } |
| |
| constexpr char kRawKey[] = {0x8f, 0x95, 0xf0, 0x92, 0x45, 0x76, 0x5f, 0x80, |
| 0x25, 0x69, 0x34, 0xe5, 0x0c, 0x66, 0x20, 0x7f}; |
| constexpr absl::string_view kKey(kRawKey, kLoadBalancerKeyLen); |
| constexpr uint64_t kNonceLow = 0xe5d1c048bf0d08ee; |
| constexpr uint64_t kNonceHigh = 0x9321e7e34dde525d; |
| constexpr uint8_t kServerId[] = {0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f, |
| 0xab, 0x65, 0xba, 0x04, 0xc3, 0x33, 0x0a}; |
| |
| TEST_F(LoadBalancerEncoderTest, BadUnroutableLength) { |
| EXPECT_QUIC_BUG( |
| EXPECT_FALSE( |
| LoadBalancerEncoder::Create(random_, nullptr, false, 0).has_value()), |
| "Invalid unroutable_connection_id_len = 0"); |
| EXPECT_QUIC_BUG( |
| EXPECT_FALSE( |
| LoadBalancerEncoder::Create(random_, nullptr, false, 21).has_value()), |
| "Invalid unroutable_connection_id_len = 21"); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, BadServerIdLength) { |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, true); |
| ASSERT_TRUE(encoder.has_value()); |
| // Expects a 3 byte server ID and got 4. |
| auto config = LoadBalancerConfig::CreateUnencrypted(1, 3, 4); |
| ASSERT_TRUE(config.has_value()); |
| EXPECT_QUIC_BUG( |
| EXPECT_FALSE(encoder->UpdateConfig(*config, MakeServerId(kServerId, 4))), |
| "Server ID length 4 does not match configured value of 3"); |
| EXPECT_FALSE(encoder->IsEncoding()); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, FailToUpdateConfigWithSameId) { |
| TestLoadBalancerEncoderVisitor visitor; |
| auto encoder = LoadBalancerEncoder::Create(random_, &visitor, true); |
| ASSERT_TRUE(encoder.has_value()); |
| auto config = LoadBalancerConfig::CreateUnencrypted(1, 3, 4); |
| ASSERT_TRUE(config.has_value()); |
| EXPECT_TRUE(encoder->UpdateConfig(*config, MakeServerId(kServerId, 3))); |
| EXPECT_EQ(visitor.num_adds(), 1u); |
| EXPECT_QUIC_BUG( |
| EXPECT_FALSE(encoder->UpdateConfig(*config, MakeServerId(kServerId, 3))), |
| "Attempting to change config with same ID"); |
| EXPECT_EQ(visitor.num_adds(), 1u); |
| } |
| |
| struct LoadBalancerEncoderTestCase { |
| LoadBalancerConfig config; |
| QuicConnectionId connection_id; |
| LoadBalancerServerId server_id; |
| }; |
| |
| TEST_F(LoadBalancerEncoderTest, UnencryptedConnectionIdTestVectors) { |
| const struct LoadBalancerEncoderTestCase test_vectors[2] = { |
| { |
| *LoadBalancerConfig::CreateUnencrypted(0, 3, 4), |
| QuicConnectionId({0x07, 0xed, 0x79, 0x3a, 0x80, 0x49, 0x71, 0x8a}), |
| MakeServerId(kServerId, 3), |
| }, |
| { |
| *LoadBalancerConfig::CreateUnencrypted(1, 8, 5), |
| QuicConnectionId({0x4d, 0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, |
| 0x5f, 0xee, 0x15, 0xda, 0x27, 0xc4}), |
| MakeServerId(kServerId, 8), |
| }, |
| }; |
| for (const auto &test : test_vectors) { |
| random_.AddNextValues(kNonceHigh, kNonceLow); |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, true, 8); |
| EXPECT_TRUE(encoder->UpdateConfig(test.config, test.server_id)); |
| absl::uint128 nonces_left = encoder->num_nonces_left(); |
| EXPECT_EQ(encoder->GenerateConnectionId(), test.connection_id); |
| EXPECT_EQ(encoder->num_nonces_left(), nonces_left - 1); |
| } |
| } |
| |
| // Follow example in draft-ietf-quic-load-balancers-15. |
| TEST_F(LoadBalancerEncoderTest, FollowSpecExample) { |
| const uint8_t config_id = 0, server_id_len = 3, nonce_len = 4; |
| const uint8_t raw_server_id[] = { |
| 0x31, |
| 0x44, |
| 0x1a, |
| }; |
| const char raw_key[] = { |
| 0xfd, 0xf7, 0x26, 0xa9, 0x89, 0x3e, 0xc0, 0x5c, |
| 0x06, 0x32, 0xd3, 0x95, 0x66, 0x80, 0xba, 0xf0, |
| }; |
| random_.AddNextValues(0, 0x75c2699c); |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, true, 8); |
| ASSERT_TRUE(encoder.has_value()); |
| auto config = LoadBalancerConfig::Create(config_id, server_id_len, nonce_len, |
| absl::string_view(raw_key)); |
| ASSERT_TRUE(config.has_value()); |
| EXPECT_TRUE(encoder->UpdateConfig( |
| *config, *LoadBalancerServerId::Create(raw_server_id))); |
| EXPECT_TRUE(encoder->IsEncoding()); |
| const char raw_connection_id[] = {0x07, 0x8e, 0x9a, 0x91, |
| 0xf4, 0x94, 0x97, 0x62}; |
| auto expected = |
| QuicConnectionId(raw_connection_id, 1 + server_id_len + nonce_len); |
| EXPECT_EQ(encoder->GenerateConnectionId(), expected); |
| } |
| |
| // Compare test vectors from Appendix B of draft-ietf-quic-load-balancers-15. |
| TEST_F(LoadBalancerEncoderTest, EncoderTestVectors) { |
| // Try (1) the "standard" ConnectionId length of 8 |
| // (2) server_id_len > nonce_len, so there is a fourth decryption pass |
| // (3) the single-pass encryption case |
| // (4) An even total length. |
| const LoadBalancerEncoderTestCase test_vectors[4] = { |
| { |
| *LoadBalancerConfig::Create(0, 3, 4, kKey), |
| QuicConnectionId({0x07, 0x41, 0x26, 0xee, 0x38, 0xbf, 0x54, 0x54}), |
| MakeServerId(kServerId, 3), |
| }, |
| { |
| *LoadBalancerConfig::Create(1, 10, 5, kKey), |
| QuicConnectionId({0x4f, 0xcd, 0x3f, 0x57, 0x2d, 0x4e, 0xef, 0xb0, |
| 0x46, 0xfd, 0xb5, 0x1d, 0x16, 0x4e, 0xfc, 0xcc}), |
| MakeServerId(kServerId, 10), |
| }, |
| { |
| *LoadBalancerConfig::Create(2, 8, 8, kKey), |
| QuicConnectionId({0x90, 0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, |
| 0xb2, 0xb9, 0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, |
| 0xc3}), |
| MakeServerId(kServerId, 8), |
| }, |
| { |
| *LoadBalancerConfig::Create(0, 9, 9, kKey), |
| QuicConnectionId({0x12, 0x12, 0x4d, 0x1e, 0xb8, 0xfb, 0xb2, 0x1e, |
| 0x4a, 0x49, 0x0c, 0xa5, 0x3c, 0xfe, 0x21, 0xd0, |
| 0x4a, 0xe6, 0x3a}), |
| MakeServerId(kServerId, 9), |
| }, |
| }; |
| for (const auto &test : test_vectors) { |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, true, 8); |
| ASSERT_TRUE(encoder.has_value()); |
| random_.AddNextValues(kNonceHigh, kNonceLow); |
| EXPECT_TRUE(encoder->UpdateConfig(test.config, test.server_id)); |
| EXPECT_EQ(encoder->GenerateConnectionId(), test.connection_id); |
| } |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, RunOutOfNonces) { |
| const uint8_t server_id_len = 3; |
| TestLoadBalancerEncoderVisitor visitor; |
| auto encoder = LoadBalancerEncoder::Create(random_, &visitor, true, 8); |
| ASSERT_TRUE(encoder.has_value()); |
| auto config = LoadBalancerConfig::Create(0, server_id_len, 4, kKey); |
| ASSERT_TRUE(config.has_value()); |
| EXPECT_TRUE( |
| encoder->UpdateConfig(*config, MakeServerId(kServerId, server_id_len))); |
| EXPECT_EQ(visitor.num_adds(), 1u); |
| LoadBalancerEncoderPeer::SetNumNoncesLeft(*encoder, 2); |
| EXPECT_EQ(encoder->num_nonces_left(), 2); |
| EXPECT_EQ(encoder->GenerateConnectionId(), |
| QuicConnectionId({0x07, 0x1d, 0x4a, 0xb8, 0xc6, 0x1d, 0xd6, 0x5d})); |
| EXPECT_EQ(encoder->num_nonces_left(), 1); |
| encoder->GenerateConnectionId(); |
| EXPECT_EQ(encoder->IsEncoding(), false); |
| // No retire_calls except for the initial UpdateConfig. |
| EXPECT_EQ(visitor.num_deletes(), 1u); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, UnroutableConnectionId) { |
| random_.AddNextValues(0x83, kNonceHigh); |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, false); |
| ASSERT_TRUE(encoder.has_value()); |
| EXPECT_EQ(encoder->num_nonces_left(), 0); |
| auto connection_id = encoder->GenerateConnectionId(); |
| // The first byte is the config_id (0xc0) xored with (0x83 & 0x3f). |
| // The remaining bytes are random, and therefore match kNonceHigh. |
| QuicConnectionId expected({0xc3, 0x5d, 0x52, 0xde, 0x4d, 0xe3, 0xe7, 0x21}); |
| EXPECT_EQ(expected, connection_id); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, NonDefaultUnroutableConnectionIdLength) { |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, true, 9); |
| ASSERT_TRUE(encoder.has_value()); |
| QuicConnectionId connection_id = encoder->GenerateConnectionId(); |
| EXPECT_EQ(connection_id.length(), 9); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, DeleteConfigWhenNoConfigExists) { |
| TestLoadBalancerEncoderVisitor visitor; |
| auto encoder = LoadBalancerEncoder::Create(random_, &visitor, true); |
| ASSERT_TRUE(encoder.has_value()); |
| encoder->DeleteConfig(); |
| EXPECT_EQ(visitor.num_deletes(), 0u); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, AddConfig) { |
| auto config = LoadBalancerConfig::CreateUnencrypted(0, 3, 4); |
| ASSERT_TRUE(config.has_value()); |
| TestLoadBalancerEncoderVisitor visitor; |
| auto encoder = LoadBalancerEncoder::Create(random_, &visitor, true); |
| EXPECT_TRUE(encoder->UpdateConfig(*config, MakeServerId(kServerId, 3))); |
| EXPECT_EQ(visitor.num_adds(), 1u); |
| absl::uint128 left = encoder->num_nonces_left(); |
| EXPECT_EQ(left, (0x1ull << 32)); |
| EXPECT_TRUE(encoder->IsEncoding()); |
| EXPECT_FALSE(encoder->IsEncrypted()); |
| encoder->GenerateConnectionId(); |
| EXPECT_EQ(encoder->num_nonces_left(), left - 1); |
| EXPECT_EQ(visitor.num_deletes(), 0u); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, UpdateConfig) { |
| auto config = LoadBalancerConfig::CreateUnencrypted(0, 3, 4); |
| ASSERT_TRUE(config.has_value()); |
| TestLoadBalancerEncoderVisitor visitor; |
| auto encoder = LoadBalancerEncoder::Create(random_, &visitor, true); |
| EXPECT_TRUE(encoder->UpdateConfig(*config, MakeServerId(kServerId, 3))); |
| config = LoadBalancerConfig::Create(1, 4, 4, kKey); |
| ASSERT_TRUE(config.has_value()); |
| EXPECT_TRUE(encoder->UpdateConfig(*config, MakeServerId(kServerId, 4))); |
| EXPECT_EQ(visitor.num_adds(), 2u); |
| EXPECT_EQ(visitor.num_deletes(), 1u); |
| EXPECT_TRUE(encoder->IsEncoding()); |
| EXPECT_TRUE(encoder->IsEncrypted()); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, DeleteConfig) { |
| auto config = LoadBalancerConfig::CreateUnencrypted(0, 3, 4); |
| ASSERT_TRUE(config.has_value()); |
| TestLoadBalancerEncoderVisitor visitor; |
| auto encoder = LoadBalancerEncoder::Create(random_, &visitor, true); |
| EXPECT_TRUE(encoder->UpdateConfig(*config, MakeServerId(kServerId, 3))); |
| encoder->DeleteConfig(); |
| EXPECT_EQ(visitor.num_adds(), 1u); |
| EXPECT_EQ(visitor.num_deletes(), 1u); |
| EXPECT_FALSE(encoder->IsEncoding()); |
| EXPECT_FALSE(encoder->IsEncrypted()); |
| EXPECT_EQ(encoder->num_nonces_left(), 0); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, DeleteConfigNoVisitor) { |
| auto config = LoadBalancerConfig::CreateUnencrypted(0, 3, 4); |
| ASSERT_TRUE(config.has_value()); |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, true); |
| EXPECT_TRUE(encoder->UpdateConfig(*config, MakeServerId(kServerId, 3))); |
| encoder->DeleteConfig(); |
| EXPECT_FALSE(encoder->IsEncoding()); |
| EXPECT_FALSE(encoder->IsEncrypted()); |
| EXPECT_EQ(encoder->num_nonces_left(), 0); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, MaybeReplaceConnectionIdReturnsNoChange) { |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, false); |
| ASSERT_TRUE(encoder.has_value()); |
| EXPECT_EQ(encoder->MaybeReplaceConnectionId(TestConnectionId(1), |
| ParsedQuicVersion::Q050()), |
| absl::nullopt); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, MaybeReplaceConnectionIdReturnsChange) { |
| random_.AddNextValues(0x83, kNonceHigh); |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, false); |
| ASSERT_TRUE(encoder.has_value()); |
| // The first byte is the config_id (0xc0) xored with (0x83 & 0x3f). |
| // The remaining bytes are random, and therefore match kNonceHigh. |
| QuicConnectionId expected({0xc3, 0x5d, 0x52, 0xde, 0x4d, 0xe3, 0xe7, 0x21}); |
| EXPECT_EQ(*encoder->MaybeReplaceConnectionId(TestConnectionId(1), |
| ParsedQuicVersion::RFCv1()), |
| expected); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, GenerateNextConnectionIdReturnsNoChange) { |
| auto config = LoadBalancerConfig::CreateUnencrypted(0, 3, 4); |
| ASSERT_TRUE(config.has_value()); |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, true); |
| EXPECT_TRUE(encoder->UpdateConfig(*config, MakeServerId(kServerId, 3))); |
| EXPECT_EQ(encoder->GenerateNextConnectionId(TestConnectionId(1)), |
| absl::nullopt); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, GenerateNextConnectionIdReturnsChange) { |
| random_.AddNextValues(0x83, kNonceHigh); |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, false); |
| ASSERT_TRUE(encoder.has_value()); |
| // The first byte is the config_id (0xc0) xored with (0x83 & 0x3f). |
| // The remaining bytes are random, and therefore match kNonceHigh. |
| QuicConnectionId expected({0xc3, 0x5d, 0x52, 0xde, 0x4d, 0xe3, 0xe7, 0x21}); |
| EXPECT_EQ(*encoder->GenerateNextConnectionId(TestConnectionId(1)), expected); |
| } |
| |
| TEST_F(LoadBalancerEncoderTest, ConnectionIdLengthsEncoded) { |
| // The first byte literally encodes the length. |
| auto len_encoder = LoadBalancerEncoder::Create(random_, nullptr, true); |
| ASSERT_TRUE(len_encoder.has_value()); |
| EXPECT_EQ(len_encoder->ConnectionIdLength(0xc8), 9); |
| EXPECT_EQ(len_encoder->ConnectionIdLength(0x4a), 11); |
| EXPECT_EQ(len_encoder->ConnectionIdLength(0x09), 10); |
| // The length is not self-encoded anymore. |
| auto encoder = LoadBalancerEncoder::Create(random_, nullptr, false); |
| ASSERT_TRUE(encoder.has_value()); |
| EXPECT_EQ(encoder->ConnectionIdLength(0xc8), kQuicDefaultConnectionIdLength); |
| EXPECT_EQ(encoder->ConnectionIdLength(0x4a), kQuicDefaultConnectionIdLength); |
| EXPECT_EQ(encoder->ConnectionIdLength(0x09), kQuicDefaultConnectionIdLength); |
| // Add config ID 0, so that ID now returns a different length. |
| uint8_t config_id = 0; |
| uint8_t server_id_len = 3; |
| uint8_t nonce_len = 6; |
| uint8_t config_0_len = server_id_len + nonce_len + 1; |
| auto config0 = LoadBalancerConfig::CreateUnencrypted(config_id, server_id_len, |
| nonce_len); |
| ASSERT_TRUE(config0.has_value()); |
| EXPECT_TRUE( |
| encoder->UpdateConfig(*config0, MakeServerId(kServerId, server_id_len))); |
| EXPECT_EQ(encoder->ConnectionIdLength(0xc8), kQuicDefaultConnectionIdLength); |
| EXPECT_EQ(encoder->ConnectionIdLength(0x4a), kQuicDefaultConnectionIdLength); |
| EXPECT_EQ(encoder->ConnectionIdLength(0x09), config_0_len); |
| // Replace config ID 0 with 1. There are probably still packets with config |
| // ID 0 arriving, so keep that length in memory. |
| config_id = 1; |
| nonce_len++; |
| uint8_t config_1_len = server_id_len + nonce_len + 1; |
| auto config1 = LoadBalancerConfig::CreateUnencrypted(config_id, server_id_len, |
| nonce_len); |
| ASSERT_TRUE(config1.has_value()); |
| // Old config length still there after replacement |
| EXPECT_TRUE( |
| encoder->UpdateConfig(*config1, MakeServerId(kServerId, server_id_len))); |
| EXPECT_EQ(encoder->ConnectionIdLength(0xc8), kQuicDefaultConnectionIdLength); |
| EXPECT_EQ(encoder->ConnectionIdLength(0x4a), config_1_len); |
| EXPECT_EQ(encoder->ConnectionIdLength(0x09), config_0_len); |
| // Old config length still there after delete |
| encoder->DeleteConfig(); |
| EXPECT_EQ(encoder->ConnectionIdLength(0xc8), kQuicDefaultConnectionIdLength); |
| EXPECT_EQ(encoder->ConnectionIdLength(0x4a), config_1_len); |
| EXPECT_EQ(encoder->ConnectionIdLength(0x09), config_0_len); |
| } |
| |
| } // namespace |
| |
| } // namespace test |
| |
| } // namespace quic |