QUIC-LB Encoder Class. After rethinking the design of this, ServerId and Config no longer have const elements.
PiperOrigin-RevId: 438877892
diff --git a/quic/load_balancer/load_balancer_config.h b/quic/load_balancer/load_balancer_config.h
index ac9967d..8e952ce 100644
--- a/quic/load_balancer/load_balancer_config.h
+++ b/quic/load_balancer/load_balancer_config.h
@@ -69,18 +69,18 @@
LoadBalancerConfig(uint8_t config_id, uint8_t server_id_len,
uint8_t nonce_len, absl::string_view key);
- const uint8_t config_id_;
- const uint8_t server_id_len_;
- const uint8_t nonce_len_;
+ uint8_t config_id_;
+ uint8_t server_id_len_;
+ uint8_t nonce_len_;
// All Connection ID encryption and decryption uses the AES_encrypt function
// at root, so there is a single key for all of it. This is empty if the
// config is not encrypted.
- const absl::optional<AES_KEY> key_;
+ absl::optional<AES_KEY> key_;
// The one exception is that when total_len == 16, connection ID decryption
// uses AES_decrypt. The bytes that comprise the key are the same, but
// AES_decrypt requires an AES_KEY that is initialized differently. In all
// other cases, block_decrypt_key_ is empty.
- const absl::optional<AES_KEY> block_decrypt_key_;
+ absl::optional<AES_KEY> block_decrypt_key_;
};
} // namespace quic
diff --git a/quic/load_balancer/load_balancer_encoder.cc b/quic/load_balancer/load_balancer_encoder.cc
new file mode 100644
index 0000000..c5f0d03
--- /dev/null
+++ b/quic/load_balancer/load_balancer_encoder.cc
@@ -0,0 +1,178 @@
+// 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 "quic/load_balancer/load_balancer_encoder.h"
+
+#include "absl/numeric/int128.h"
+#include "quic/core/quic_connection_id.h"
+#include "quic/core/quic_data_reader.h"
+#include "quic/core/quic_data_writer.h"
+#include "quic/core/quic_packet_number.h"
+#include "quic/core/quic_types.h"
+#include "quic/core/quic_utils.h"
+#include "quic/load_balancer/load_balancer_config.h"
+#include "quic/platform/api/quic_bug_tracker.h"
+
+namespace quic {
+
+namespace {
+
+// Returns the number of nonces given a certain |nonce_len|.
+absl::uint128 NumberOfNonces(uint8_t nonce_len) {
+ return (static_cast<absl::uint128>(1) << (nonce_len * 8));
+}
+
+// Writes the |size| least significant bytes from |in| to |out| in host byte
+// order. Returns false if |out| does not have enough space.
+bool WriteUint128(const absl::uint128 in, uint8_t size, QuicDataWriter &out) {
+ if (out.remaining() < size) {
+ QUIC_BUG(quic_bug_435375038_05)
+ << "Call to WriteUint128() does not have enough space in |out|";
+ return false;
+ }
+ uint64_t num64 = absl::Uint128Low64(in);
+ if (size <= sizeof(num64)) {
+ out.WriteBytes(&num64, size);
+ } else {
+ out.WriteBytes(&num64, sizeof(num64));
+ num64 = absl::Uint128High64(in);
+ out.WriteBytes(&num64, size - sizeof(num64));
+ }
+ return true;
+}
+
+} // namespace
+
+constexpr uint8_t kLoadBalancerLengthMask = 0x3f;
+constexpr uint8_t kLoadBalancerUnroutableConfigId = 0xc0;
+
+absl::optional<LoadBalancerEncoder> LoadBalancerEncoder::Create(
+ QuicRandom &random, LoadBalancerEncoderVisitorInterface *const visitor,
+ const bool len_self_encoded, const uint8_t unroutable_connection_id_len) {
+ if (unroutable_connection_id_len == 0 ||
+ unroutable_connection_id_len >
+ kQuicMaxConnectionIdWithLengthPrefixLength) {
+ QUIC_BUG(quic_bug_435375038_01)
+ << "Invalid unroutable_connection_id_len = "
+ << static_cast<int>(unroutable_connection_id_len);
+ return absl::optional<LoadBalancerEncoder>();
+ }
+ return LoadBalancerEncoder(random, visitor, len_self_encoded,
+ unroutable_connection_id_len);
+}
+
+bool LoadBalancerEncoder::UpdateConfig(const LoadBalancerConfig &config,
+ const LoadBalancerServerId server_id) {
+ if (config_.has_value() && config_->config_id() == config.config_id()) {
+ QUIC_BUG(quic_bug_435375038_02)
+ << "Attempting to change config with same ID";
+ return false;
+ }
+ if (server_id.length() != config.server_id_len()) {
+ QUIC_BUG(quic_bug_435375038_03)
+ << "Server ID length " << static_cast<int>(server_id.length())
+ << " does not match configured value of "
+ << static_cast<int>(config.server_id_len());
+ return false;
+ }
+ if (visitor_ != nullptr) {
+ if (config_.has_value()) {
+ visitor_->OnConfigChanged(config_->config_id(), config.config_id());
+ } else {
+ visitor_->OnConfigAdded(config.config_id());
+ }
+ }
+ config_ = config;
+ server_id_ = server_id;
+
+ seed_ = absl::MakeUint128(random_.RandUint64(), random_.RandUint64()) %
+ NumberOfNonces(config.nonce_len());
+ num_nonces_left_ = NumberOfNonces(config.nonce_len());
+ return true;
+}
+
+void LoadBalancerEncoder::DeleteConfig() {
+ if (visitor_ != nullptr && config_.has_value()) {
+ visitor_->OnConfigDeleted(config_->config_id());
+ }
+ config_.reset();
+ server_id_.reset();
+ num_nonces_left_ = 0;
+}
+
+QuicConnectionId LoadBalancerEncoder::GenerateConnectionId() {
+ uint8_t length = (config_.has_value()) ? (config_->total_len() + 1)
+ : unroutable_connection_id_len_;
+ uint8_t config_id = config_.has_value() ? (config_->config_id() << 6)
+ : kLoadBalancerUnroutableConfigId;
+ if (config_.has_value() != server_id_.has_value()) {
+ QUIC_BUG(quic_bug_435375038_04)
+ << "Existence of config and server_id are out of sync";
+ return QuicConnectionId();
+ }
+ uint8_t first_byte;
+ // first byte
+ if (len_self_encoded_) {
+ first_byte = config_id | (length - 1);
+ } else {
+ random_.RandBytes(static_cast<void *>(&first_byte), 1);
+ first_byte = config_id | (first_byte & kLoadBalancerLengthMask);
+ }
+ if (config_id == kLoadBalancerUnroutableConfigId) {
+ return MakeUnroutableConnectionId(first_byte);
+ }
+ QuicConnectionId id;
+ id.set_length(length);
+ QuicDataWriter writer(length, id.mutable_data(), quiche::HOST_BYTE_ORDER);
+ writer.WriteUInt8(first_byte);
+ absl::uint128 next_nonce =
+ (seed_ + num_nonces_left_--) % NumberOfNonces(config_->nonce_len());
+ writer.WriteBytes(server_id_->data().data(), server_id_->length());
+ if (!WriteUint128(next_nonce, config_->nonce_len(), writer)) {
+ return QuicConnectionId();
+ }
+ uint8_t *block_start = reinterpret_cast<uint8_t *>(writer.data() + 1);
+ if (!config_->IsEncrypted()) {
+ // Fill the nonce field with a hash of the Connection ID to avoid the nonce
+ // visibly increasing by one. This would allow observers to correlate
+ // connection IDs as being sequential and likely from the same connection,
+ // not just the same server.
+ absl::uint128 nonce_hash =
+ QuicUtils::FNV1a_128_Hash(absl::string_view(writer.data(), length));
+ QuicDataWriter rewriter(config_->nonce_len(),
+ id.mutable_data() + config_->server_id_len() + 1,
+ quiche::HOST_BYTE_ORDER);
+ if (!WriteUint128(nonce_hash, config_->nonce_len(), rewriter)) {
+ return QuicConnectionId();
+ }
+ } else if (config_->total_len() == kLoadBalancerBlockSize) {
+ // Use one encryption pass.
+ if (!config_->BlockEncrypt(block_start, block_start)) {
+ QUIC_LOG(ERROR) << "Block encryption failed";
+ return QuicConnectionId();
+ }
+ } else {
+ for (uint8_t i = 1; i <= 4; i++) {
+ if (!config_->EncryptionPass(block_start, i)) {
+ QUIC_LOG(ERROR) << "Block encryption failed";
+ return QuicConnectionId();
+ }
+ }
+ }
+ if (num_nonces_left_ == 0) {
+ DeleteConfig();
+ }
+ return id;
+}
+
+QuicConnectionId LoadBalancerEncoder::MakeUnroutableConnectionId(
+ uint8_t first_byte) {
+ QuicConnectionId id;
+ id.set_length(unroutable_connection_id_len_);
+ id.mutable_data()[0] = first_byte;
+ random_.RandBytes(&id.mutable_data()[1], unroutable_connection_id_len_ - 1);
+ return id;
+}
+
+} // namespace quic
diff --git a/quic/load_balancer/load_balancer_encoder.h b/quic/load_balancer/load_balancer_encoder.h
new file mode 100644
index 0000000..e0b4ab7
--- /dev/null
+++ b/quic/load_balancer/load_balancer_encoder.h
@@ -0,0 +1,123 @@
+// 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.
+
+#ifndef QUICHE_QUIC_LOAD_BALANCER_ENCODER_H_
+#define QUICHE_QUIC_LOAD_BALANCER_ENCODER_H_
+
+#include "quic/core/crypto/quic_random.h"
+#include "quic/load_balancer/load_balancer_config.h"
+#include "quic/load_balancer/load_balancer_server_id.h"
+
+namespace quic {
+
+namespace test {
+class LoadBalancerEncoderPeer;
+}
+
+// Default length of a 4-tuple connection ID.
+inline constexpr uint8_t kLoadBalancerUnroutableLen = 8;
+
+// Interface which receives notifications when the current config is updated.
+class QUIC_EXPORT_PRIVATE LoadBalancerEncoderVisitorInterface {
+ public:
+ virtual ~LoadBalancerEncoderVisitorInterface() {}
+
+ // Called when a config is added where none existed.
+ //
+ // Connections that support address migration should retire unroutable
+ // connection IDs and replace them with routable ones using the new config,
+ // while avoiding sending a sudden storm of packets containing
+ // RETIRE_CONNECTION_ID and NEW_CONNECTION_ID frames.
+ virtual void OnConfigAdded(const uint8_t config_id) = 0;
+ // Called when the config is changed.
+ //
+ // Existing routable connection IDs should be retired before the decoder stops
+ // supporting that config. The timing of this event is deployment-dependent
+ // and might be tied to the arrival of a new config at the encoder.
+ virtual void OnConfigChanged(const uint8_t old_config_id,
+ const uint8_t new_config_id) = 0;
+ // Called when a config is deleted. The encoder will generate unroutable
+ // connection IDs from now on.
+ //
+ // New connections will not be able to support address migration until a new
+ // config arrives. Existing connections can retain connection IDs that use the
+ // deleted config, which will only become unroutable once the decoder also
+ // deletes it. The time of that deletion is deployment-dependent and might be
+ // tied to the arrival of a new config at the encoder.
+ virtual void OnConfigDeleted(const uint8_t config_id) = 0;
+};
+
+// Manages QUIC-LB configurations to properly encode a given server ID in a
+// QUIC Connection ID.
+class QUIC_EXPORT_PRIVATE LoadBalancerEncoder {
+ public:
+ // Returns a newly created encoder with no active config, if
+ // |unroutable_connection_id_length| is valid. |visitor| specifies an optional
+ // interface to receive callbacks when connection IDs need to be retired.
+ // If |encode_length| is true, then the first byte of any generated
+ // connection ids will encode the length. Otherwise, those bits will be
+ // random. |unroutable_connection_id_length| specifies the length of
+ // connection IDs to be generated when there is no active config. It must not
+ // be 0 and must not be larger than the RFC9000 maximum of 20.
+ static absl::optional<LoadBalancerEncoder> Create(
+ QuicRandom& random, LoadBalancerEncoderVisitorInterface* const visitor,
+ const bool len_self_encoded,
+ const uint8_t unroutable_connection_id_len = kLoadBalancerUnroutableLen);
+
+ // Attempts to replace the current config and server_id with |config| and
+ // |server_id|. If the length |server_id| does not match the server_id_length
+ // of |config| or the ID of |config| matches the ID of the current config,
+ // returns false and leaves the current config unchanged. Otherwise, returns
+ // true. When the encoder runs out of nonces, it will delete the config and
+ // begin generating unroutable connection IDs.
+ bool UpdateConfig(const LoadBalancerConfig& config,
+ const LoadBalancerServerId server_id);
+
+ // Delete the current config and generate unroutable connection IDs from now
+ // on.
+ void DeleteConfig();
+
+ // Returns the number of additional connections IDs that can be generated with
+ // the current config, or 0 if there is no current config.
+ absl::uint128 num_nonces_left() const { return num_nonces_left_; }
+
+ // Returns true if there is an active configuration.
+ bool IsEncoding() const { return config_.has_value(); }
+ // Returns true if there is an active configuration that uses encryption.
+ bool IsEncrypted() const {
+ return config_.has_value() && config_->IsEncrypted();
+ }
+
+ // If there's an active config, generates a connection ID using it. If not,
+ // generates an unroutable connection_id. If there's an error, returns a zero-
+ // length Connection ID.
+ QuicConnectionId GenerateConnectionId();
+
+ private:
+ friend class test::LoadBalancerEncoderPeer;
+
+ LoadBalancerEncoder(QuicRandom& random,
+ LoadBalancerEncoderVisitorInterface* const visitor,
+ const bool len_self_encoded,
+ const uint8_t unroutable_connection_id_len)
+ : random_(random),
+ len_self_encoded_(len_self_encoded),
+ visitor_(visitor),
+ unroutable_connection_id_len_(unroutable_connection_id_len) {}
+
+ QuicConnectionId MakeUnroutableConnectionId(uint8_t first_byte);
+
+ QuicRandom& random_;
+ const bool len_self_encoded_;
+ LoadBalancerEncoderVisitorInterface* const visitor_;
+ const uint8_t unroutable_connection_id_len_;
+
+ absl::optional<LoadBalancerConfig> config_;
+ absl::uint128 seed_, num_nonces_left_ = 0;
+ absl::optional<LoadBalancerServerId> server_id_;
+};
+
+} // namespace quic
+
+#endif // QUICHE_QUIC_LOAD_BALANCER_ENCODER_H_
diff --git a/quic/load_balancer/load_balancer_encoder_test.cc b/quic/load_balancer/load_balancer_encoder_test.cc
new file mode 100644
index 0000000..d2817f0
--- /dev/null
+++ b/quic/load_balancer/load_balancer_encoder_test.cc
@@ -0,0 +1,355 @@
+// 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 "quic/load_balancer/load_balancer_encoder.h"
+
+#include <cstdint>
+
+#include "absl/numeric/int128.h"
+#include "quic/platform/api/quic_expect_bug.h"
+#include "quic/platform/api/quic_test.h"
+#include "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);
+ EXPECT_TRUE(encoder.has_value());
+ // Expects a 3 byte server ID and got 4.
+ auto config = LoadBalancerConfig::CreateUnencrypted(1, 3, 4);
+ EXPECT_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);
+ EXPECT_TRUE(encoder.has_value());
+ EXPECT_TRUE(encoder.has_value());
+ auto config = LoadBalancerConfig::CreateUnencrypted(1, 3, 4);
+ EXPECT_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);
+}
+
+TEST_F(LoadBalancerEncoderTest, UnencryptedConnectionIdTestVectors) {
+ const uint8_t server_id_lens[] = {3, 8};
+ const uint8_t nonce_lens[] = {4, 5};
+ static_assert(sizeof(server_id_lens) == sizeof(nonce_lens));
+ const std::vector<QuicConnectionId> expected_connection_ids{
+ QuicConnectionId({0x07, 0xed, 0x79, 0x3a, 0x80, 0x49, 0x71, 0x8a}),
+ QuicConnectionId({0x4d, 0xed, 0x79, 0x3a, 0x51, 0xd4, 0x9b, 0x8f, 0x5f,
+ 0xee, 0x15, 0xda, 0x27, 0xc4}),
+ };
+ EXPECT_EQ(sizeof(server_id_lens), expected_connection_ids.size());
+ for (uint8_t i = 0; i < sizeof(server_id_lens); i++) {
+ uint8_t config_id = i % 3;
+ random_.AddNextValues(kNonceHigh, kNonceLow);
+ auto encoder = LoadBalancerEncoder::Create(random_, nullptr, true, 8);
+ EXPECT_TRUE(encoder.has_value());
+ auto config = LoadBalancerConfig::CreateUnencrypted(
+ config_id, server_id_lens[i], nonce_lens[i]);
+ EXPECT_TRUE(config.has_value());
+ EXPECT_TRUE(encoder->UpdateConfig(
+ *config, MakeServerId(kServerId, server_id_lens[i])));
+ absl::uint128 nonces_left = encoder->num_nonces_left();
+ EXPECT_EQ(encoder->GenerateConnectionId(), expected_connection_ids[i]);
+ EXPECT_EQ(encoder->num_nonces_left(), nonces_left - 1);
+ }
+}
+
+// Follow example in draft-ietf-quic-load-balancers-12.
+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);
+ EXPECT_TRUE(encoder.has_value());
+ auto config = LoadBalancerConfig::Create(config_id, server_id_len, nonce_len,
+ absl::string_view(raw_key));
+ EXPECT_TRUE(config.has_value());
+ EXPECT_TRUE(encoder->UpdateConfig(
+ *config, *LoadBalancerServerId::Create(raw_server_id)));
+ EXPECT_TRUE(encoder->IsEncoding());
+ const char raw_connection_id[] = {0x07, 0xe2, 0x3c, 0xb4,
+ 0x2b, 0xba, 0x1e, 0xe0};
+ 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-12.
+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.
+ uint8_t server_id_lens[] = {3, 10, 8, 9};
+ uint8_t nonce_lens[] = {4, 5, 8, 9};
+ static_assert(sizeof(server_id_lens) == sizeof(nonce_lens));
+ const std::vector<QuicConnectionId> expected_connection_ids{
+ QuicConnectionId({0x07, 0xfb, 0xfe, 0x05, 0xf7, 0x31, 0xb4, 0x25}),
+ QuicConnectionId({0x4f, 0x01, 0x09, 0x56, 0xfb, 0x5c, 0x1d, 0x4d, 0x86,
+ 0xe0, 0x10, 0x18, 0x3e, 0x0b, 0x7d, 0x1e}),
+ QuicConnectionId({0x90, 0x4d, 0xd2, 0xd0, 0x5a, 0x7b, 0x0d, 0xe9, 0xb2,
+ 0xb9, 0x90, 0x7a, 0xfb, 0x5e, 0xcf, 0x8c, 0xc3}),
+ QuicConnectionId({0x12, 0x7a, 0x28, 0x5a, 0x09, 0xf8, 0x52, 0x80, 0xf4,
+ 0xfd, 0x6a, 0xbb, 0x43, 0x4a, 0x71, 0x59, 0xe4, 0xd3,
+ 0xeb}),
+ };
+ EXPECT_EQ(sizeof(server_id_lens), expected_connection_ids.size());
+ for (uint8_t i = 0; i < sizeof(server_id_lens); i++) {
+ uint8_t config_id = i % 3;
+ auto encoder = LoadBalancerEncoder::Create(random_, nullptr, true, 8);
+ EXPECT_TRUE(encoder.has_value());
+ auto config = LoadBalancerConfig::Create(config_id, server_id_lens[i],
+ nonce_lens[i], kKey);
+ EXPECT_TRUE(config.has_value());
+ random_.AddNextValues(kNonceHigh, kNonceLow);
+ EXPECT_TRUE(encoder->UpdateConfig(
+ *config, MakeServerId(kServerId, server_id_lens[i])));
+ EXPECT_EQ(encoder->GenerateConnectionId(), expected_connection_ids[i]);
+ }
+}
+
+TEST_F(LoadBalancerEncoderTest, RunOutOfNonces) {
+ const uint8_t config_id = 0, server_id_len = 3, nonce_len = 4;
+ TestLoadBalancerEncoderVisitor visitor;
+ auto encoder = LoadBalancerEncoder::Create(random_, &visitor, true, 8);
+ EXPECT_TRUE(encoder.has_value());
+ auto config =
+ LoadBalancerConfig::Create(config_id, server_id_len, nonce_len, kKey);
+ EXPECT_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, 0xba, 0x7f, 0x38, 0x2f, 0x5c, 0x22, 0x1d}));
+ 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);
+ EXPECT_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);
+ EXPECT_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);
+ EXPECT_TRUE(encoder.has_value());
+ encoder->DeleteConfig();
+ EXPECT_EQ(visitor.num_deletes(), 0u);
+}
+
+TEST_F(LoadBalancerEncoderTest, AddConfig) {
+ auto config = LoadBalancerConfig::CreateUnencrypted(0, 3, 4);
+ EXPECT_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);
+ EXPECT_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);
+ EXPECT_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);
+ EXPECT_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);
+ EXPECT_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);
+}
+
+} // namespace
+
+} // namespace test
+
+} // namespace quic
diff --git a/quic/load_balancer/load_balancer_server_id.h b/quic/load_balancer/load_balancer_server_id.h
index fe4c5df..3c4eb86 100644
--- a/quic/load_balancer/load_balancer_server_id.h
+++ b/quic/load_balancer/load_balancer_server_id.h
@@ -56,8 +56,8 @@
// The constructor is private because it can't validate the input.
LoadBalancerServerId(const absl::Span<const uint8_t> data);
- const std::array<uint8_t, kLoadBalancerMaxServerIdLen> data_;
- const uint8_t length_;
+ std::array<uint8_t, kLoadBalancerMaxServerIdLen> data_;
+ uint8_t length_;
};
} // namespace quic