blob: 3abeaf0c835791f573dad15efb52b8d033f39c24 [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 <string>
#include <variant>
#include <vector>
#include "absl/strings/string_view.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/moqt/moqt_priority.h"
#include "quiche/quic/moqt/moqt_types.h"
#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_logging.h"
namespace moqt {
void KeyValuePairList::insert(uint64_t key,
std::variant<uint64_t, absl::string_view> value) {
QUICHE_DCHECK(
(key % 2 == 1 && std::holds_alternative<absl::string_view>(value)) ||
(key % 2 == 0 && std::holds_alternative<uint64_t>(value)));
if (std::holds_alternative<absl::string_view>(value)) {
map_.insert({key, std::string(std::get<absl::string_view>(value))});
} else {
map_.insert({key, std::get<uint64_t>(value)});
}
}
bool KeyValuePairList::ForEach(ValueCallback callback) const {
for (const auto& [key, value] : map_) {
if (!std::visit([&](const auto& val) { return callback(key, val); },
value)) {
return false;
}
}
return true;
}
KeyValuePairList::ValueVector KeyValuePairList::Get(uint64_t key) const {
std::vector<std::variant<uint64_t, absl::string_view>> values;
auto entries = map_.equal_range(key);
for (auto it = entries.first; it != entries.second; ++it) {
std::visit([&](const auto& value) { values.push_back(value); }, it->second);
}
return values;
}
void SubscriptionFilter::OnLargestObject(
const std::optional<Location>& largest_object) {
switch (type_) {
case MoqtFilterType::kAbsoluteStart:
case MoqtFilterType::kAbsoluteRange:
break;
case MoqtFilterType::kNextGroupStart:
if (largest_object.has_value()) {
start_ = Location(largest_object->group + 1, 0);
} else {
start_ = Location(0, 0);
}
break;
case MoqtFilterType::kLargestObject:
if (largest_object.has_value()) {
start_ = largest_object->Next();
} else {
start_ = Location(0, 0);
}
break;
}
}
void MessageParameters::Update(const MessageParameters& other) {
if (other.object_delivery_timeout.has_value()) {
object_delivery_timeout = other.object_delivery_timeout;
}
if (!other.authorization_tokens.empty()) {
authorization_tokens = other.authorization_tokens;
}
if (other.subgroup_delivery_timeout.has_value()) {
subgroup_delivery_timeout = other.subgroup_delivery_timeout;
}
if (other.expires.has_value()) {
expires = other.expires;
}
if (other.largest_object.has_value()) {
largest_object = other.largest_object;
}
if (other.forward_.has_value()) {
forward_ = other.forward_;
}
if (other.subscriber_priority.has_value()) {
subscriber_priority = other.subscriber_priority;
}
if (other.subscription_filter.has_value()) {
subscription_filter = other.subscription_filter;
}
// Group order cannot be updated.
if (other.new_group_request.has_value()) {
new_group_request = other.new_group_request;
}
if (other.track_namespace_prefix.has_value()) {
track_namespace_prefix = other.track_namespace_prefix;
}
if (other.oack_window_size.has_value()) {
oack_window_size = other.oack_window_size;
}
}
TrackProperties::TrackProperties(
std::optional<quic::QuicTimeDelta> object_delivery_timeout,
std::optional<quic::QuicTimeDelta> max_cache_duration,
std::optional<quic::QuicTimeDelta> subgroup_delivery_timeout,
std::optional<MoqtPriority> publisher_priority,
std::optional<MoqtGroupOrder> group_order,
std::optional<bool> dynamic_groups,
std::optional<absl::string_view> immutable_properties) {
if (object_delivery_timeout.has_value() &&
*object_delivery_timeout != kDefaultDeliveryTimeout &&
*object_delivery_timeout != quic::QuicTimeDelta::Zero()) {
insert(static_cast<uint64_t>(PropertyType::kObjectDeliveryTimeout),
static_cast<uint64_t>(object_delivery_timeout->ToMilliseconds()));
}
if (max_cache_duration.has_value() &&
*max_cache_duration != kDefaultMaxCacheDuration) {
insert(static_cast<uint64_t>(PropertyType::kMaxCacheDuration),
static_cast<uint64_t>(max_cache_duration->ToMilliseconds()));
}
if (subgroup_delivery_timeout.has_value() &&
*subgroup_delivery_timeout != kDefaultDeliveryTimeout &&
*subgroup_delivery_timeout != quic::QuicTimeDelta::Zero()) {
insert(static_cast<uint64_t>(PropertyType::kSubgroupDeliveryTimeout),
static_cast<uint64_t>(subgroup_delivery_timeout->ToMilliseconds()));
}
if (immutable_properties.has_value() && !immutable_properties->empty()) {
insert(static_cast<uint64_t>(PropertyType::kImmutableProperties),
*immutable_properties);
}
if (publisher_priority.has_value() &&
*publisher_priority != kDefaultPublisherPriority) {
insert(static_cast<uint64_t>(PropertyType::kDefaultPublisherPriority),
static_cast<uint64_t>(*publisher_priority));
}
if (group_order.has_value() && *group_order != kDefaultGroupOrder) {
insert(static_cast<uint64_t>(PropertyType::kDefaultPublisherGroupOrder),
static_cast<uint64_t>(*group_order));
}
if (dynamic_groups.has_value() && *dynamic_groups != kDefaultDynamicGroups) {
insert(static_cast<uint64_t>(PropertyType::kDynamicGroups),
*dynamic_groups ? 1ULL : 0ULL);
}
}
quic::QuicTimeDelta TrackProperties::object_delivery_timeout() const {
std::optional<uint64_t> value =
GetValueIfExactlyOne(PropertyType::kObjectDeliveryTimeout);
return (value.has_value() && *value > 0)
? quic::QuicTimeDelta::FromMilliseconds(*value)
: kDefaultDeliveryTimeout;
}
quic::QuicTimeDelta TrackProperties::max_cache_duration() const {
std::optional<uint64_t> value =
GetValueIfExactlyOne(PropertyType::kMaxCacheDuration);
return value.has_value() ? quic::QuicTimeDelta::FromMilliseconds(*value)
: kDefaultMaxCacheDuration;
}
quic::QuicTimeDelta TrackProperties::subgroup_delivery_timeout() const {
std::optional<uint64_t> value =
GetValueIfExactlyOne(PropertyType::kSubgroupDeliveryTimeout);
return (value.has_value() && *value > 0)
? quic::QuicTimeDelta::FromMilliseconds(*value)
: kDefaultDeliveryTimeout;
}
absl::string_view TrackProperties::immutable_properties() const {
ValueVector values =
Get(static_cast<uint64_t>(PropertyType::kImmutableProperties));
return (values.size() != 1 ||
!std::holds_alternative<absl::string_view>(values[0]))
? ""
: std::get<absl::string_view>(values[0]);
}
MoqtPriority TrackProperties::default_publisher_priority() const {
std::optional<uint64_t> value =
GetValueIfExactlyOne(PropertyType::kDefaultPublisherPriority);
return (!value.has_value() || *value > kMaxPriority)
? kDefaultPublisherPriority
: static_cast<MoqtPriority>(*value);
}
MoqtGroupOrder TrackProperties::default_publisher_group_order() const {
std::optional<uint64_t> value =
GetValueIfExactlyOne(PropertyType::kDefaultPublisherGroupOrder);
return (!value.has_value() || *value > kMaxMoqtGroupOrder ||
*value < kMinMoqtGroupOrder)
? kDefaultGroupOrder
: static_cast<MoqtGroupOrder>(*value);
}
bool TrackProperties::dynamic_groups() const {
std::optional<uint64_t> value =
GetValueIfExactlyOne(PropertyType::kDynamicGroups);
return (!value.has_value() || *value > 1) ? kDefaultDynamicGroups
: (*value == 1);
}
bool TrackProperties::Validate() const {
// TODO(martinduke): If immutable properties include an immutable properties
// property, the track is malformed.
return (ValidateInner(PropertyType::kObjectDeliveryTimeout, std::nullopt,
std::nullopt) &&
ValidateInner(PropertyType::kMaxCacheDuration, std::nullopt,
std::nullopt) &&
ValidateInner(PropertyType::kSubgroupDeliveryTimeout, std::nullopt,
std::nullopt) &&
ValidateInner(PropertyType::kDefaultPublisherPriority, std::nullopt,
kMaxPriority) &&
ValidateInner(PropertyType::kDefaultPublisherGroupOrder,
kMinMoqtGroupOrder, kMaxMoqtGroupOrder) &&
ValidateInner(PropertyType::kDynamicGroups, 0, 1) &&
count(static_cast<uint64_t>(PropertyType::kImmutableProperties)) <=
1);
}
// private
std::optional<uint64_t> TrackProperties::GetValueIfExactlyOne(
PropertyType header) const {
QUICHE_BUG_IF(moqt_bug_must_be_even, (static_cast<uint64_t>(header) % 2) == 1)
<< "MoqtKeyValuePair extracting integer from odd key.";
ValueVector values = Get(static_cast<uint64_t>(header));
if (values.size() != 1 || !std::holds_alternative<uint64_t>(values[0])) {
return std::nullopt;
}
return std::get<uint64_t>(values[0]);
}
bool TrackProperties::ValidateInner(PropertyType header,
std::optional<uint64_t> min,
std::optional<uint64_t> max) const {
ValueVector values = Get(static_cast<uint64_t>(header));
switch (values.size()) {
case 0:
return true;
case 1:
if (!std::holds_alternative<uint64_t>(values[0])) {
return false;
}
return (!min.has_value() || std::get<uint64_t>(values[0]) >= *min) &&
(!max.has_value() || std::get<uint64_t>(values[0]) <= *max);
default:
return false;
}
}
} // namespace moqt