| // Copyright (c) 2026 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/moqt/moqt_key_value_pair.h" |
| |
| #include <cstdint> |
| #include <optional> |
| |
| #include "absl/strings/string_view.h" |
| #include "quiche/quic/core/quic_time.h" |
| #include "quiche/quic/moqt/moqt_error.h" |
| #include "quiche/quic/moqt/moqt_priority.h" |
| #include "quiche/quic/platform/api/quic_test.h" |
| |
| namespace moqt::test { |
| |
| class LocationTest : public quic::test::QuicTest {}; |
| |
| TEST_F(LocationTest, LocationTests) { |
| Location location; |
| EXPECT_EQ(location, Location(0, 0)); |
| EXPECT_EQ(location.Next(), Location(0, 1)); |
| EXPECT_LT(Location(4, 20), Location(5, 0)); |
| EXPECT_LT(Location(4, 0), Location(4, 1)); |
| } |
| |
| class AuthTokenTest : public quic::test::QuicTest {}; |
| |
| TEST_F(AuthTokenTest, Delete) { |
| AuthToken token(1, AuthTokenAliasType::kDelete); |
| EXPECT_EQ(token.alias_type, AuthTokenAliasType::kDelete); |
| EXPECT_EQ(token.alias, 1); |
| EXPECT_FALSE(token.type.has_value()); |
| EXPECT_FALSE(token.value.has_value()); |
| } |
| |
| TEST_F(AuthTokenTest, Register) { |
| AuthToken token(1, AuthTokenType::kOutOfBand, "token"); |
| EXPECT_EQ(token.alias_type, AuthTokenAliasType::kRegister); |
| EXPECT_EQ(token.alias, 1); |
| EXPECT_EQ(token.type, AuthTokenType::kOutOfBand); |
| EXPECT_EQ(token.value, "token"); |
| } |
| |
| TEST_F(AuthTokenTest, UseAlias) { |
| AuthToken token(1, AuthTokenAliasType::kUseAlias); |
| EXPECT_EQ(token.alias_type, AuthTokenAliasType::kUseAlias); |
| EXPECT_EQ(token.alias, 1); |
| EXPECT_FALSE(token.type.has_value()); |
| EXPECT_FALSE(token.value.has_value()); |
| } |
| |
| TEST_F(AuthTokenTest, UseValue) { |
| AuthToken token(AuthTokenType::kOutOfBand, "token"); |
| EXPECT_EQ(token.alias_type, AuthTokenAliasType::kUseValue); |
| EXPECT_EQ(token.type, AuthTokenType::kOutOfBand); |
| EXPECT_EQ(token.value, "token"); |
| } |
| |
| class SubscriptionFilterTest : public quic::test::QuicTest {}; |
| |
| TEST_F(SubscriptionFilterTest, NextGroupStart) { |
| SubscriptionFilter filter(MoqtFilterType::kNextGroupStart); |
| EXPECT_EQ(filter.type(), (MoqtFilterType::kNextGroupStart)); |
| EXPECT_FALSE(filter.WindowKnown()); |
| filter.OnLargestObject(Location(3, 6)); |
| EXPECT_TRUE(filter.WindowKnown()); |
| EXPECT_EQ(filter.type(), (MoqtFilterType::kAbsoluteStart)); |
| EXPECT_TRUE(filter.InWindow(Location(4, 0))); |
| EXPECT_FALSE(filter.InWindow(Location(3, 7))); |
| EXPECT_TRUE(filter.InWindow(4)); |
| EXPECT_FALSE(filter.InWindow(3)); |
| } |
| |
| TEST_F(SubscriptionFilterTest, LargestObject) { |
| SubscriptionFilter filter(MoqtFilterType::kLargestObject); |
| EXPECT_EQ(filter.type(), (MoqtFilterType::kLargestObject)); |
| EXPECT_FALSE(filter.WindowKnown()); |
| filter.OnLargestObject(Location(3, 6)); |
| EXPECT_EQ(filter.type(), (MoqtFilterType::kAbsoluteStart)); |
| EXPECT_TRUE(filter.WindowKnown()); |
| EXPECT_TRUE(filter.InWindow(Location(4, 0))); |
| EXPECT_TRUE(filter.InWindow(Location(3, 7))); |
| EXPECT_FALSE(filter.InWindow(Location(3, 6))); |
| EXPECT_TRUE(filter.InWindow(4)); |
| EXPECT_TRUE(filter.InWindow(3)); |
| EXPECT_FALSE(filter.InWindow(2)); |
| } |
| |
| TEST_F(SubscriptionFilterTest, LargestObjectNoObjectsYet) { |
| SubscriptionFilter filter(MoqtFilterType::kLargestObject); |
| EXPECT_EQ(filter.type(), (MoqtFilterType::kLargestObject)); |
| EXPECT_FALSE(filter.WindowKnown()); |
| filter.OnLargestObject(std::nullopt); |
| EXPECT_EQ(filter.type(), (MoqtFilterType::kAbsoluteStart)); |
| EXPECT_TRUE(filter.WindowKnown()); |
| EXPECT_TRUE(filter.InWindow(Location(0, 0))); |
| EXPECT_TRUE(filter.InWindow(0)); |
| } |
| |
| TEST_F(SubscriptionFilterTest, AbsoluteStart) { |
| SubscriptionFilter filter(Location(3, 6)); |
| EXPECT_EQ(filter.type(), (MoqtFilterType::kAbsoluteStart)); |
| EXPECT_TRUE(filter.WindowKnown()); |
| EXPECT_TRUE(filter.InWindow(Location(4, 0))); |
| EXPECT_TRUE(filter.InWindow(Location(3, 6))); |
| EXPECT_FALSE(filter.InWindow(Location(3, 5))); |
| EXPECT_TRUE(filter.InWindow(4)); |
| EXPECT_TRUE(filter.InWindow(3)); |
| EXPECT_FALSE(filter.InWindow(2)); |
| } |
| |
| TEST_F(SubscriptionFilterTest, AbsoluteRange) { |
| SubscriptionFilter filter(Location(3, 6), 5); |
| EXPECT_EQ(filter.type(), (MoqtFilterType::kAbsoluteRange)); |
| EXPECT_TRUE(filter.WindowKnown()); |
| EXPECT_EQ(filter.start(), Location(3, 6)); |
| EXPECT_EQ(filter.end_group(), 5); |
| EXPECT_TRUE(filter.InWindow(Location(4, 0))); |
| EXPECT_TRUE(filter.InWindow(Location(3, 6))); |
| EXPECT_TRUE(filter.InWindow(Location(5, kMaxObjectId))); |
| EXPECT_FALSE(filter.InWindow(Location(3, 5))); |
| EXPECT_FALSE(filter.InWindow(Location(6, 0))); |
| EXPECT_TRUE(filter.InWindow(5)); |
| EXPECT_TRUE(filter.InWindow(3)); |
| EXPECT_FALSE(filter.InWindow(2)); |
| EXPECT_FALSE(filter.InWindow(6)); |
| } |
| |
| class MessageParametersTest : public quic::test::QuicTest {}; |
| |
| TEST_F(MessageParametersTest, FromKeyValuePairList) { |
| KeyValuePairList list; |
| list.insert(static_cast<uint64_t>(MessageParameter::kDeliveryTimeout), 1ULL); |
| list.insert(static_cast<uint64_t>(MessageParameter::kForward), 0ULL); |
| list.insert(static_cast<uint64_t>(MessageParameter::kOackWindowSize), |
| 12345678ULL); |
| MessageParameters parameters; |
| parameters.FromKeyValuePairList(list); |
| EXPECT_EQ(parameters.delivery_timeout, |
| quic::QuicTimeDelta::FromMilliseconds(1)); |
| EXPECT_FALSE(parameters.forward()); |
| EXPECT_EQ(parameters.oack_window_size, |
| quic::QuicTimeDelta::FromMicroseconds(12345678)); |
| } |
| |
| TEST_F(MessageParametersTest, IllegalKeyValuePairs) { |
| KeyValuePairList list; |
| MessageParameters parameters; |
| list.insert(static_cast<uint64_t>(MessageParameter::kDeliveryTimeout), 0ULL); |
| EXPECT_EQ(parameters.FromKeyValuePairList(list), |
| MoqtError::kProtocolViolation); |
| list.clear(); |
| list.insert(static_cast<uint64_t>(MessageParameter::kForward), 2ULL); |
| EXPECT_EQ(parameters.FromKeyValuePairList(list), |
| MoqtError::kProtocolViolation); |
| list.clear(); |
| list.insert(static_cast<uint64_t>(MessageParameter::kSubscriberPriority), |
| 256ULL); |
| EXPECT_EQ(parameters.FromKeyValuePairList(list), |
| MoqtError::kProtocolViolation); |
| list.clear(); |
| list.insert(static_cast<uint64_t>(MessageParameter::kGroupOrder), 0ULL); |
| EXPECT_EQ(parameters.FromKeyValuePairList(list), |
| MoqtError::kProtocolViolation); |
| list.clear(); |
| list.insert(static_cast<uint64_t>(MessageParameter::kGroupOrder), 3ULL); |
| EXPECT_EQ(parameters.FromKeyValuePairList(list), |
| MoqtError::kProtocolViolation); |
| // Unknown MessageParameter. |
| list.clear(); |
| list.insert(0x12345678, 12345678ULL); |
| EXPECT_EQ(parameters.FromKeyValuePairList(list), |
| MoqtError::kProtocolViolation); |
| } |
| |
| TEST_F(MessageParametersTest, DuplicateParameters) { |
| for (MessageParameter param : |
| {MessageParameter::kDeliveryTimeout, |
| // Auth token can be repeated. |
| MessageParameter::kExpires, MessageParameter::kLargestObject, |
| MessageParameter::kForward, MessageParameter::kSubscriberPriority, |
| MessageParameter::kSubscriptionFilter, MessageParameter::kGroupOrder, |
| MessageParameter::kNewGroupRequest, |
| MessageParameter::kOackWindowSize}) { |
| KeyValuePairList list; |
| MessageParameters parameters; |
| switch (param) { |
| case MessageParameter::kLargestObject: { |
| char largest_object[] = {0x00, 0x01}; |
| list.insert(static_cast<uint64_t>(param), |
| absl::string_view(largest_object, 2)); |
| largest_object[1] = 0x02; |
| list.insert(static_cast<uint64_t>(param), |
| absl::string_view(largest_object, 2)); |
| break; |
| } |
| case MessageParameter::kForward: { |
| list.insert(static_cast<uint64_t>(param), 0ULL); |
| list.insert(static_cast<uint64_t>(param), 1ULL); |
| break; |
| } |
| case MessageParameter::kSubscriberPriority: { |
| list.insert(static_cast<uint64_t>(param), 127ULL); |
| list.insert(static_cast<uint64_t>(param), 128ULL); |
| break; |
| } |
| case MessageParameter::kSubscriptionFilter: { |
| char filter[] = {0x01}; // kNextGroupStart |
| list.insert(static_cast<uint64_t>(param), absl::string_view(filter, 1)); |
| filter[0] = 0x02; // kLargestObject |
| list.insert(static_cast<uint64_t>(param), absl::string_view(filter, 1)); |
| break; |
| } |
| case MessageParameter::kGroupOrder: { |
| list.insert(static_cast<uint64_t>(param), 1ULL); |
| list.insert(static_cast<uint64_t>(param), 2ULL); |
| break; |
| } |
| default: { |
| list.insert(static_cast<uint64_t>(param), 1024ULL); |
| list.insert(static_cast<uint64_t>(param), 2048ULL); |
| break; |
| } |
| } |
| EXPECT_EQ(parameters.FromKeyValuePairList(list), |
| MoqtError::kProtocolViolation); |
| } |
| } |
| |
| class TrackExtensionsTest : public quic::test::QuicTest {}; |
| |
| TEST_F(TrackExtensionsTest, DefaultConstructor) { |
| TrackExtensions extensions; |
| EXPECT_TRUE(extensions.Validate()); |
| EXPECT_EQ(extensions.delivery_timeout(), kDefaultDeliveryTimeout); |
| EXPECT_EQ(extensions.max_cache_duration(), kDefaultMaxCacheDuration); |
| EXPECT_EQ(extensions.default_publisher_priority(), kDefaultPublisherPriority); |
| EXPECT_EQ(extensions.default_publisher_group_order(), kDefaultGroupOrder); |
| EXPECT_EQ(extensions.dynamic_groups(), kDefaultDynamicGroups); |
| EXPECT_TRUE(extensions.immutable_extensions().empty()); |
| } |
| |
| TEST_F(TrackExtensionsTest, AllExtensions) { |
| TrackExtensions extensions(quic::QuicTimeDelta::FromMilliseconds(1), |
| quic::QuicTimeDelta::FromMilliseconds(2), |
| MoqtPriority(10), MoqtDeliveryOrder::kDescending, |
| true, "extensions"); |
| EXPECT_TRUE(extensions.Validate()); |
| EXPECT_EQ(extensions.delivery_timeout(), |
| quic::QuicTimeDelta::FromMilliseconds(1)); |
| EXPECT_EQ(extensions.max_cache_duration(), |
| quic::QuicTimeDelta::FromMilliseconds(2)); |
| EXPECT_EQ(extensions.default_publisher_priority(), MoqtPriority(10)); |
| EXPECT_EQ(extensions.default_publisher_group_order(), |
| MoqtDeliveryOrder::kDescending); |
| EXPECT_TRUE(extensions.dynamic_groups()); |
| EXPECT_EQ(extensions.immutable_extensions(), "extensions"); |
| } |
| |
| TEST_F(TrackExtensionsTest, ExplicitDefaults) { |
| TrackExtensions extensions(kDefaultDeliveryTimeout, kDefaultMaxCacheDuration, |
| kDefaultPublisherPriority, kDefaultGroupOrder, |
| kDefaultDynamicGroups, ""); |
| EXPECT_TRUE(extensions.Validate()); |
| EXPECT_EQ(extensions.size(), 0); |
| EXPECT_EQ(extensions.delivery_timeout(), kDefaultDeliveryTimeout); |
| EXPECT_EQ(extensions.max_cache_duration(), kDefaultMaxCacheDuration); |
| EXPECT_EQ(extensions.default_publisher_priority(), kDefaultPublisherPriority); |
| EXPECT_EQ(extensions.default_publisher_group_order(), kDefaultGroupOrder); |
| EXPECT_EQ(extensions.dynamic_groups(), kDefaultDynamicGroups); |
| EXPECT_TRUE(extensions.immutable_extensions().empty()); |
| } |
| |
| TEST_F(TrackExtensionsTest, Validate) { |
| TrackExtensions extensions; |
| // Unknown extension. |
| extensions.insert(0x42, 15ULL); |
| extensions.insert(0x42, 25ULL); |
| EXPECT_TRUE(extensions.Validate()); |
| |
| extensions.insert(static_cast<uint64_t>(ExtensionHeader::kDeliveryTimeout), |
| 5ULL); |
| extensions.insert(static_cast<uint64_t>(ExtensionHeader::kDeliveryTimeout), |
| 6ULL); |
| EXPECT_FALSE(extensions.Validate()); |
| |
| extensions.clear(); |
| extensions.insert(static_cast<uint64_t>(ExtensionHeader::kMaxCacheDuration), |
| 5ULL); |
| extensions.insert(static_cast<uint64_t>(ExtensionHeader::kMaxCacheDuration), |
| 6ULL); |
| EXPECT_FALSE(extensions.Validate()); |
| |
| extensions.clear(); |
| extensions.insert( |
| static_cast<uint64_t>(ExtensionHeader::kDefaultPublisherPriority), |
| 256ULL); |
| EXPECT_FALSE(extensions.Validate()); |
| extensions.clear(); |
| extensions.insert( |
| static_cast<uint64_t>(ExtensionHeader::kDefaultPublisherPriority), 0ULL); |
| extensions.insert( |
| static_cast<uint64_t>(ExtensionHeader::kDefaultPublisherPriority), 1ULL); |
| EXPECT_FALSE(extensions.Validate()); |
| |
| extensions.clear(); |
| extensions.insert( |
| static_cast<uint64_t>(ExtensionHeader::kDefaultPublisherGroupOrder), |
| 0ULL); |
| EXPECT_FALSE(extensions.Validate()); |
| extensions.clear(); |
| extensions.insert( |
| static_cast<uint64_t>(ExtensionHeader::kDefaultPublisherGroupOrder), |
| 3ULL); |
| EXPECT_FALSE(extensions.Validate()); |
| extensions.clear(); |
| extensions.insert(static_cast<uint64_t>(ExtensionHeader::kDynamicGroups), |
| 2ULL); |
| extensions.insert(static_cast<uint64_t>(ExtensionHeader::kDynamicGroups), |
| 1ULL); |
| EXPECT_FALSE(extensions.Validate()); |
| |
| extensions.clear(); |
| extensions.insert(static_cast<uint64_t>(ExtensionHeader::kDynamicGroups), |
| 2ULL); |
| EXPECT_FALSE(extensions.Validate()); |
| extensions.clear(); |
| extensions.insert(static_cast<uint64_t>(ExtensionHeader::kDynamicGroups), |
| 0ULL); |
| extensions.insert(static_cast<uint64_t>(ExtensionHeader::kDynamicGroups), |
| 1ULL); |
| EXPECT_FALSE(extensions.Validate()); |
| |
| extensions.clear(); |
| extensions.insert( |
| static_cast<uint64_t>(ExtensionHeader::kImmutableExtensions), "foo"); |
| extensions.insert( |
| static_cast<uint64_t>(ExtensionHeader::kImmutableExtensions), "bar"); |
| EXPECT_FALSE(extensions.Validate()); |
| } |
| |
| } // namespace moqt::test |