blob: 5bc84952653fab3263dcf9749896d7f6e25cca24 [file]
// 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/status/status.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/moqt/moqt_names.h"
#include "quiche/quic/moqt/moqt_priority.h"
#include "quiche/quic/moqt/moqt_types.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/common/test_tools/quiche_test_utils.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::kNextGroupStart));
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::kLargestObject));
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::kLargestObject));
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, Update) {
MessageParameters p1;
p1.object_delivery_timeout = quic::QuicTimeDelta::FromMilliseconds(10);
p1.expires = quic::QuicTimeDelta::FromMilliseconds(100);
p1.set_forward(false);
p1.subscriber_priority = 100;
p1.new_group_request = 1;
p1.track_namespace_prefix = TrackNamespace({"foo"});
MessageParameters p2;
p2.object_delivery_timeout = quic::QuicTimeDelta::FromMilliseconds(20);
p2.authorization_tokens.push_back(
AuthToken(AuthTokenType::kOutOfBand, "token"));
p2.set_forward(true);
p2.group_order = MoqtGroupOrder::kDescending;
p2.track_namespace_prefix = TrackNamespace({"bar"});
p1.Update(p2);
EXPECT_EQ(p1.object_delivery_timeout,
quic::QuicTimeDelta::FromMilliseconds(20));
EXPECT_EQ(p1.expires, quic::QuicTimeDelta::FromMilliseconds(100));
ASSERT_EQ(p1.authorization_tokens.size(), 1);
EXPECT_EQ(p1.authorization_tokens[0],
AuthToken(AuthTokenType::kOutOfBand, "token"));
EXPECT_TRUE(p1.forward());
EXPECT_EQ(p1.subscriber_priority, 100);
EXPECT_EQ(p1.group_order, std::nullopt);
EXPECT_EQ(p1.new_group_request, 1);
EXPECT_EQ(p1.track_namespace_prefix, TrackNamespace({"bar"}));
}
class TrackPropertiesTest : public quic::test::QuicTest {};
TEST_F(TrackPropertiesTest, DefaultConstructor) {
TrackProperties properties;
EXPECT_TRUE(properties.Validate());
EXPECT_EQ(properties.object_delivery_timeout(), kDefaultDeliveryTimeout);
EXPECT_EQ(properties.max_cache_duration(), kDefaultMaxCacheDuration);
EXPECT_EQ(properties.default_publisher_priority(), kDefaultPublisherPriority);
EXPECT_EQ(properties.default_publisher_group_order(), kDefaultGroupOrder);
EXPECT_EQ(properties.dynamic_groups(), kDefaultDynamicGroups);
EXPECT_TRUE(properties.immutable_properties().empty());
}
TEST_F(TrackPropertiesTest, AllProperties) {
TrackProperties properties(quic::QuicTimeDelta::FromMilliseconds(1),
quic::QuicTimeDelta::FromMilliseconds(2),
quic::QuicTimeDelta::FromMilliseconds(3),
MoqtPriority(10), MoqtGroupOrder::kDescending,
true, "properties");
EXPECT_TRUE(properties.Validate());
EXPECT_EQ(properties.object_delivery_timeout(),
quic::QuicTimeDelta::FromMilliseconds(1));
EXPECT_EQ(properties.max_cache_duration(),
quic::QuicTimeDelta::FromMilliseconds(2));
EXPECT_EQ(properties.subgroup_delivery_timeout(),
quic::QuicTimeDelta::FromMilliseconds(3));
EXPECT_EQ(properties.default_publisher_priority(), MoqtPriority(10));
EXPECT_EQ(properties.default_publisher_group_order(),
MoqtGroupOrder::kDescending);
EXPECT_TRUE(properties.dynamic_groups());
EXPECT_EQ(properties.immutable_properties(), "properties");
}
TEST_F(TrackPropertiesTest, ExplicitDefaults) {
TrackProperties properties(kDefaultDeliveryTimeout, kDefaultMaxCacheDuration,
kDefaultDeliveryTimeout, kDefaultPublisherPriority,
kDefaultGroupOrder, kDefaultDynamicGroups, "");
EXPECT_TRUE(properties.Validate());
EXPECT_EQ(properties.size(), 0);
EXPECT_EQ(properties.object_delivery_timeout(), kDefaultDeliveryTimeout);
EXPECT_EQ(properties.max_cache_duration(), kDefaultMaxCacheDuration);
EXPECT_EQ(properties.subgroup_delivery_timeout(), kDefaultDeliveryTimeout);
EXPECT_EQ(properties.default_publisher_priority(), kDefaultPublisherPriority);
EXPECT_EQ(properties.default_publisher_group_order(), kDefaultGroupOrder);
EXPECT_EQ(properties.dynamic_groups(), kDefaultDynamicGroups);
EXPECT_TRUE(properties.immutable_properties().empty());
}
TEST_F(TrackPropertiesTest, Validate) {
TrackProperties properties;
// Unknown property.
properties.insert(0x42, 15ULL);
properties.insert(0x42, 25ULL);
EXPECT_TRUE(properties.Validate());
properties.insert(static_cast<uint64_t>(PropertyType::kObjectDeliveryTimeout),
5ULL);
properties.insert(static_cast<uint64_t>(PropertyType::kObjectDeliveryTimeout),
6ULL);
EXPECT_FALSE(properties.Validate());
properties.clear();
properties.insert(static_cast<uint64_t>(PropertyType::kMaxCacheDuration),
5ULL);
properties.insert(static_cast<uint64_t>(PropertyType::kMaxCacheDuration),
6ULL);
EXPECT_FALSE(properties.Validate());
properties.clear();
properties.insert(
static_cast<uint64_t>(PropertyType::kSubgroupDeliveryTimeout), 5ULL);
properties.insert(
static_cast<uint64_t>(PropertyType::kSubgroupDeliveryTimeout), 6ULL);
EXPECT_FALSE(properties.Validate());
properties.clear();
properties.insert(
static_cast<uint64_t>(PropertyType::kDefaultPublisherPriority), 256ULL);
EXPECT_FALSE(properties.Validate());
properties.clear();
properties.insert(
static_cast<uint64_t>(PropertyType::kDefaultPublisherPriority), 0ULL);
properties.insert(
static_cast<uint64_t>(PropertyType::kDefaultPublisherPriority), 1ULL);
EXPECT_FALSE(properties.Validate());
properties.clear();
properties.insert(
static_cast<uint64_t>(PropertyType::kDefaultPublisherGroupOrder), 0ULL);
EXPECT_FALSE(properties.Validate());
properties.clear();
properties.insert(
static_cast<uint64_t>(PropertyType::kDefaultPublisherGroupOrder), 3ULL);
EXPECT_FALSE(properties.Validate());
properties.clear();
properties.insert(static_cast<uint64_t>(PropertyType::kDynamicGroups), 2ULL);
properties.insert(static_cast<uint64_t>(PropertyType::kDynamicGroups), 1ULL);
EXPECT_FALSE(properties.Validate());
properties.clear();
properties.insert(static_cast<uint64_t>(PropertyType::kDynamicGroups), 2ULL);
EXPECT_FALSE(properties.Validate());
properties.clear();
properties.insert(static_cast<uint64_t>(PropertyType::kDynamicGroups), 0ULL);
properties.insert(static_cast<uint64_t>(PropertyType::kDynamicGroups), 1ULL);
EXPECT_FALSE(properties.Validate());
properties.clear();
properties.insert(static_cast<uint64_t>(PropertyType::kImmutableProperties),
"foo");
properties.insert(static_cast<uint64_t>(PropertyType::kImmutableProperties),
"bar");
EXPECT_FALSE(properties.Validate());
}
TEST_F(TrackPropertiesTest, CheckForUnknownMandatoryProperty) {
TrackProperties properties;
QUICHE_EXPECT_OK(properties.CheckForUnknownMandatoryProperty());
// Non-mandatory property types (< 0x4000).
properties.insert(static_cast<uint64_t>(PropertyType::kObjectDeliveryTimeout),
5ULL);
properties.insert(0x3FFE, 1ULL);
properties.insert(0x3FFF, "odd_optional");
QUICHE_EXPECT_OK(properties.CheckForUnknownMandatoryProperty());
// Non-mandatory property types (> 0x7FFF).
properties.insert(0x8000, 2ULL);
properties.insert(0x8001, "above_range");
properties.insert(0x10000, 3ULL);
QUICHE_EXPECT_OK(properties.CheckForUnknownMandatoryProperty());
// Min boundary: 0x4000.
TrackProperties min_mandatory;
min_mandatory.insert(kMinMandatoryTrackProperty, 0ULL);
EXPECT_TRUE(
IsFailedPrecondition(min_mandatory.CheckForUnknownMandatoryProperty()));
EXPECT_EQ(min_mandatory.CheckForUnknownMandatoryProperty().message(),
"Unknown mandatory property: 0x4000");
// Max boundary: 0x7FFF.
TrackProperties max_mandatory;
max_mandatory.insert(kMaxMandatoryTrackProperty, "mandatory_string");
EXPECT_TRUE(
IsFailedPrecondition(max_mandatory.CheckForUnknownMandatoryProperty()));
EXPECT_EQ(max_mandatory.CheckForUnknownMandatoryProperty().message(),
"Unknown mandatory property: 0x7fff");
// In between.
TrackProperties mid_mandatory;
mid_mandatory.insert(0x5000, 42ULL);
EXPECT_TRUE(
IsFailedPrecondition(mid_mandatory.CheckForUnknownMandatoryProperty()));
EXPECT_EQ(mid_mandatory.CheckForUnknownMandatoryProperty().message(),
"Unknown mandatory property: 0x5000");
}
} // namespace moqt::test