Server ID object for QUIC-LB. Wraps an array of uint8_t with validation to make sure the length is correct. This will be used by other classes. PiperOrigin-RevId: 434513538
diff --git a/quic/load_balancer/load_balancer_server_id.cc b/quic/load_balancer/load_balancer_server_id.cc new file mode 100644 index 0000000..12f1135 --- /dev/null +++ b/quic/load_balancer/load_balancer_server_id.cc
@@ -0,0 +1,45 @@ +// 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_server_id.h" + +#include "absl/strings/escaping.h" +#include "absl/types/span.h" +#include "quic/core/quic_types.h" +#include "quic/platform/api/quic_bug_tracker.h" + +namespace quic { + +namespace { + +// Helper to allow setting the const array during initialization. +std::array<uint8_t, kLoadBalancerMaxServerIdLen> MakeArray( + const absl::Span<const uint8_t> data, const uint8_t length) { + std::array<uint8_t, kLoadBalancerMaxServerIdLen> array; + memcpy(array.data(), data.data(), length); + return array; +} + +} // namespace + +absl::optional<LoadBalancerServerId> LoadBalancerServerId::Create( + const absl::Span<const uint8_t> data) { + if (data.length() == 0 || data.length() > kLoadBalancerMaxServerIdLen) { + QUIC_BUG(quic_bug_433312504_01) + << "Attempted to create LoadBalancerServerId with length " + << data.length(); + return absl::optional<LoadBalancerServerId>(); + } + return LoadBalancerServerId(data); +} + +std::string LoadBalancerServerId::ToString() const { + return absl::BytesToHexString( + absl::string_view(reinterpret_cast<const char*>(data_.data()), length_)); +} + +LoadBalancerServerId::LoadBalancerServerId(const absl::Span<const uint8_t> data) + : data_(MakeArray(data, data.length())), length_(data.length()) {} + +} // namespace quic
diff --git a/quic/load_balancer/load_balancer_server_id.h b/quic/load_balancer/load_balancer_server_id.h new file mode 100644 index 0000000..e729437 --- /dev/null +++ b/quic/load_balancer/load_balancer_server_id.h
@@ -0,0 +1,65 @@ +// 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_SERVER_ID_H_ +#define QUICHE_QUIC_LOAD_BALANCER_SERVER_ID_H_ + +#include <array> + +#include "quic/core/quic_types.h" +#include "quic/platform/api/quic_export.h" + +namespace quic { + +// The maximum number of bytes in a LoadBalancerServerId. +inline constexpr uint8_t kLoadBalancerMaxServerIdLen = 15; + +// LoadBalancerServerId is the globally understood identifier for a given pool +// member. It is unique to any given QUIC-LB configuration. See +// draft-ietf-quic-load-balancers-12. +// Note: this has nothing to do with QuicServerID. It's an unfortunate collision +// between an internal term for the destination identifiers for a particular +// deployment (QuicServerID) and the object of a load balancing decision +// (LoadBalancerServerId). +class QUIC_EXPORT_PRIVATE LoadBalancerServerId { + public: + // Copies all the bytes from |data| into a new LoadBalancerServerId. + static absl::optional<LoadBalancerServerId> Create( + const absl::Span<const uint8_t> data); + + // Server IDs are opaque bytes, but defining these operators allows us to sort + // them into a tree and define ranges. + bool operator<(const LoadBalancerServerId& other) const { + return data() < other.data(); + } + bool operator==(const LoadBalancerServerId& other) const { + return data() == other.data(); + } + + // Hash function to allow use as a key in unordered maps. + template <typename H> + friend H AbslHashValue(H h, const LoadBalancerServerId& server_id) { + return H::combine_contiguous(std::move(h), server_id.data().data(), + server_id.length()); + } + + absl::Span<const uint8_t> data() const { + return absl::MakeConstSpan(data_.data(), length_); + } + inline uint8_t length() const { return length_; } + + // Returns the server ID in hex format. + std::string ToString() const; + + private: + // 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_; +}; + +} // namespace quic + +#endif // QUICHE_QUIC_LOAD_BALANCER_SERVER_ID_H_
diff --git a/quic/load_balancer/load_balancer_server_id_test.cc b/quic/load_balancer/load_balancer_server_id_test.cc new file mode 100644 index 0000000..8160830 --- /dev/null +++ b/quic/load_balancer/load_balancer_server_id_test.cc
@@ -0,0 +1,111 @@ +// 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_server_id.h" + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" + +#include "absl/hash/hash_testing.h" + +#pragma clang diagnostic pop + +#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 { + +namespace { + +class LoadBalancerServerIdTest : public QuicTest {}; + +constexpr uint8_t kRawServerId[] = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, + 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f}; + +TEST_F(LoadBalancerServerIdTest, CreateReturnsNullIfTooLong) { + EXPECT_QUIC_BUG(EXPECT_FALSE(LoadBalancerServerId::Create( + absl::Span<const uint8_t>(kRawServerId, 16)) + .has_value()), + "Attempted to create LoadBalancerServerId with length 16"); + EXPECT_QUIC_BUG( + EXPECT_FALSE(LoadBalancerServerId::Create(absl::Span<const uint8_t>()) + .has_value()), + "Attempted to create LoadBalancerServerId with length 0"); +} + +TEST_F(LoadBalancerServerIdTest, CompareIdenticalExceptLength) { + auto server_id = + LoadBalancerServerId::Create(absl::Span<const uint8_t>(kRawServerId, 15)); + ASSERT_TRUE(server_id.has_value()); + EXPECT_EQ(server_id->length(), 15); + auto shorter_server_id = + LoadBalancerServerId::Create(absl::Span<const uint8_t>(kRawServerId, 5)); + ASSERT_TRUE(shorter_server_id.has_value()); + EXPECT_EQ(shorter_server_id->length(), 5); + // Shorter comes before longer if all bits match + EXPECT_TRUE(shorter_server_id < server_id); + EXPECT_FALSE(server_id < shorter_server_id); + // Different lengths are never equal. + EXPECT_FALSE(shorter_server_id == server_id); +} + +TEST_F(LoadBalancerServerIdTest, AccessorFunctions) { + auto server_id = + LoadBalancerServerId::Create(absl::Span<const uint8_t>(kRawServerId, 5)); + EXPECT_TRUE(server_id.has_value()); + EXPECT_EQ(server_id->length(), 5); + EXPECT_EQ(memcmp(server_id->data().data(), kRawServerId, 5), 0); + EXPECT_EQ(server_id->ToString(), "0001020304"); +} + +TEST_F(LoadBalancerServerIdTest, CompareDifferentServerIds) { + auto server_id = + LoadBalancerServerId::Create(absl::Span<const uint8_t>(kRawServerId, 5)); + ASSERT_TRUE(server_id.has_value()); + auto reverse = LoadBalancerServerId::Create({0x0f, 0x0e, 0x0d, 0x0c, 0x0b}); + ASSERT_TRUE(reverse.has_value()); + EXPECT_TRUE(server_id < reverse); + auto long_server_id = + LoadBalancerServerId::Create(absl::Span<const uint8_t>(kRawServerId, 15)); + EXPECT_TRUE(long_server_id < reverse); +} + +TEST_F(LoadBalancerServerIdTest, EqualityOperators) { + auto server_id = + LoadBalancerServerId::Create(absl::Span<const uint8_t>(kRawServerId, 15)); + ASSERT_TRUE(server_id.has_value()); + auto shorter_server_id = + LoadBalancerServerId::Create(absl::Span<const uint8_t>(kRawServerId, 5)); + ASSERT_TRUE(shorter_server_id.has_value()); + EXPECT_FALSE(server_id == shorter_server_id); + auto server_id2 = server_id; + EXPECT_TRUE(server_id == server_id2); +} + +TEST_F(LoadBalancerServerIdTest, SupportsHash) { + auto server_id = + LoadBalancerServerId::Create(absl::Span<const uint8_t>(kRawServerId, 15)); + ASSERT_TRUE(server_id.has_value()); + auto shorter_server_id = + LoadBalancerServerId::Create(absl::Span<const uint8_t>(kRawServerId, 5)); + ASSERT_TRUE(shorter_server_id.has_value()); + auto different_server_id = + LoadBalancerServerId::Create({0x0f, 0x0e, 0x0d, 0x0c, 0x0b}); + ASSERT_TRUE(different_server_id.has_value()); + EXPECT_TRUE(absl::VerifyTypeImplementsAbslHashCorrectly({ + *server_id, + *shorter_server_id, + *different_server_id, + })); +} + +} // namespace + +} // namespace test + +} // namespace quic