blob: 09d89492e7bfe92533da8a59fc73a96e61bd0119 [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.
#ifndef QUICHE_QUIC_MOQT_MOQT_KEY_VALUE_PAIR_H_
#define QUICHE_QUIC_MOQT_MOQT_KEY_VALUE_PAIR_H_
#include <cstddef>
#include <cstdint>
#include <optional>
#include <string>
#include <variant>
#include <vector>
#include "absl/container/btree_map.h"
#include "absl/status/status.h"
#include "absl/strings/str_cat.h"
#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_export.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/quiche_callbacks.h"
namespace moqt {
// Encodes a list of key-value pairs common to both parameters and properties.
// If the key is odd, it is a length-prefixed string (which may encode further
// item-specific structure). If the key is even, it is a varint.
// This class does not interpret the semantic meaning of the keys and values.
// Keys must be ordered.
class QUICHE_EXPORT KeyValuePairList {
public:
KeyValuePairList() = default;
size_t size() const { return map_.size(); }
void insert(uint64_t key, std::variant<uint64_t, absl::string_view> value);
size_t count(uint64_t key) const { return map_.count(key); }
bool contains(uint64_t key) const { return map_.contains(key); }
using ValueCallback = quiche::UnretainedCallback<bool(
uint64_t, std::variant<uint64_t, absl::string_view>)>;
// Iterates through the whole list in increasing numerical order of key, and
// executes |callback| for each element.
// Returns false if |callback| returns false for any element.
bool ForEach(ValueCallback callback) const;
using ValueVector = std::vector<std::variant<uint64_t, absl::string_view>>;
ValueVector Get(uint64_t key) const;
void clear() { map_.clear(); }
bool operator==(const KeyValuePairList& other) const = default;
KeyValuePairList& operator=(const KeyValuePairList& other) = default;
protected:
// Returns the first key in the range [min_key, max_key], or nullopt if
// there is no such key.
std::optional<uint64_t> first_key_in_range(uint64_t min_key,
uint64_t max_key) const {
auto it = map_.lower_bound(min_key);
if (it == map_.end() || it->first > max_key) {
return std::nullopt;
}
return it->first;
}
private:
absl::btree_multimap<uint64_t, std::variant<uint64_t, std::string>> map_;
};
enum AuthTokenType : uint64_t {
kOutOfBand = 0x0,
kMaxAuthTokenType = 0x0,
};
enum AuthTokenAliasType : uint64_t {
kDelete = 0x0,
kRegister = 0x1,
kUseAlias = 0x2,
kUseValue = 0x3,
kMaxValue = 0x3,
};
struct AuthToken {
AuthToken(uint64_t alias, AuthTokenAliasType alias_type)
: alias_type(alias_type), alias(alias) {
QUICHE_DCHECK(alias_type == AuthTokenAliasType::kDelete ||
alias_type == AuthTokenAliasType::kUseAlias);
}
AuthToken(uint64_t alias, AuthTokenType type, absl::string_view value)
: alias_type(AuthTokenAliasType::kRegister),
alias(alias),
type(type),
value(value) {}
AuthToken(AuthTokenType type, absl::string_view value)
: alias_type(AuthTokenAliasType::kUseValue), type(type), value(value) {}
bool operator==(const AuthToken& other) const = default;
AuthTokenAliasType alias_type;
std::optional<uint64_t> alias;
std::optional<AuthTokenType> type;
std::optional<std::string> value;
};
enum class QUICHE_EXPORT MoqtFilterType : uint64_t {
kNextGroupStart = 0x1,
kLargestObject = 0x2,
kAbsoluteStart = 0x3,
kAbsoluteRange = 0x4,
};
class QUICHE_EXPORT SubscriptionFilter {
public:
// Constructors prevent illegal constructions.
explicit SubscriptionFilter(MoqtFilterType type) : type_(type) {
QUICHE_DCHECK(type == MoqtFilterType::kNextGroupStart ||
type == MoqtFilterType::kLargestObject);
}
explicit SubscriptionFilter(Location start)
: type_(MoqtFilterType::kAbsoluteStart), start_(start) {}
SubscriptionFilter(Location start, uint64_t end_group)
: type_(MoqtFilterType::kAbsoluteRange),
start_(start),
end_group_(end_group) {
QUICHE_DCHECK(end_group >= start.group);
}
MoqtFilterType type() const { return type_; }
Location start() const { return start_; }
uint64_t end_group() const { return end_group_; }
// If true, the filter does not depend on knowing LargestObject, or we
// already know LargestObject. A Subscriber cannot have an unknown window,
// because it can't process an object without getting the track alias from
// the SubscribeOk.
bool WindowKnown() const {
return type_ == MoqtFilterType::kAbsoluteStart ||
type_ == MoqtFilterType::kAbsoluteRange;
}
// Publishers should not call InWindow() if !WindowKnown(), as they should
// not forward objects without knowing the window.
bool InWindow(Location location) const {
return location >= start_ && location.group <= end_group_;
}
bool InWindow(uint64_t group) const {
return group >= start_.group && group <= end_group_;
}
// Update the filter when LargestObject is learned. If the type is
// LargestObject or NextGroupStart, changes the type to AbsoluteStart.
void OnLargestObject(const std::optional<Location>& largest_object);
SubscriptionFilter& operator=(const SubscriptionFilter& other) = default;
bool operator==(const SubscriptionFilter& other) const = default;
private:
MoqtFilterType type_;
// These could be std::optional, but it would just add to the class size.
Location start_ = Location(0, 0);
uint64_t end_group_ = kMaxGroupId;
};
// Setup parameters.
// TODO(martinduke): Implement an auth token cache.
inline constexpr uint64_t kDefaultMaxAuthTokenCacheSize = 0;
inline constexpr bool kDefaultSupportObjectAcks = false;
enum class QUICHE_EXPORT SetupParameter : uint64_t {
kPath = 0x1,
kAuthorizationToken = 0x3,
kMaxAuthTokenCacheSize = 0x4,
kAuthority = 0x5,
kMoqtImplementation = 0x7,
// QUICHE-specific extensions.
// Indicates support for OACK messages.
kSupportObjectAcks = 0xbbf1438,
};
// TODO(martinduke): Refactor this to be more like TrackProperties.
struct QUICHE_EXPORT SetupParameters {
SetupParameters() = default;
// Constructors for tests.
SetupParameters(absl::string_view path, absl::string_view authority)
: path(path), authority(authority) {}
std::optional<std::string> path;
// TODO(martinduke): Turn authorization_token into structured data.
std::vector<AuthToken> authorization_tokens;
std::optional<uint64_t> max_auth_token_cache_size;
std::optional<std::string> authority;
std::optional<std::string> moqt_implementation;
std::optional<bool> support_object_acks;
bool operator==(const SetupParameters& other) const = default;
// Defined in moqt_framer.cc.
KeyValuePairList ToKeyValuePairList() const;
// Defined in moqt_parser.cc.
// If the class is not initialized with the default constructor, it is likely
// to return an error if a non-default field duplicates what is in |list|.
absl::Status FromKeyValuePairList(const KeyValuePairList& list);
};
enum class MessageParameter : uint64_t {
kDeliveryTimeout = 0x02,
kAuthorizationToken = 0x03,
kExpires = 0x08,
kLargestObject = 0x09,
kForward = 0x10,
kSubscriberPriority = 0x20,
kSubscriptionFilter = 0x21,
kGroupOrder = 0x22,
kNewGroupRequest = 0x32,
kOackWindowSize = 0xbbF1438,
};
constexpr quic::QuicTimeDelta kDefaultDeliveryTimeout =
quic::QuicTimeDelta::Infinite();
constexpr quic::QuicTimeDelta kDefaultExpires = quic::QuicTimeDelta::Infinite();
constexpr bool kDefaultForward = true;
// TODO(martinduke): Refactor this to be more like TrackProperties.
struct MessageParameters {
MessageParameters() = default;
MessageParameters(const MessageParameters&) = default;
// Constructors for subscription filters with Auth tokens.
MessageParameters(const MoqtFilterType& filter_type) {
subscription_filter.emplace(filter_type);
}
MessageParameters(const Location& location) {
subscription_filter.emplace(location);
}
// If |other| has a value in a particular field, replace the current value
// with it. Otherwise, leave unchanged.
void Update(const MessageParameters& other);
std::optional<quic::QuicTimeDelta> delivery_timeout;
std::vector<AuthToken> authorization_tokens;
std::optional<quic::QuicTimeDelta> expires;
std::optional<Location> largest_object;
bool forward() const { return forward_.value_or(kDefaultForward); }
void set_forward(bool forward) { forward_ = forward; }
bool forward_has_value() const { return forward_.has_value(); }
std::optional<MoqtPriority> subscriber_priority;
std::optional<SubscriptionFilter> subscription_filter;
std::optional<MoqtDeliveryOrder> group_order;
std::optional<uint64_t> new_group_request;
// QUICHE-specific parameters.
std::optional<quic::QuicTimeDelta> oack_window_size;
bool operator==(const MessageParameters& other) const = default;
// Defined in moqt_framer.cc.
KeyValuePairList ToKeyValuePairList() const;
// Defined in moqt_parser.cc.
// If the class is not initialized with the default constructor, it is likely
// to return an error if a non-default field duplicates what is in |list|.
absl::Status FromKeyValuePairList(const KeyValuePairList& list);
private:
// "if (forward)" is bug-prone because it returns forward_.has_value(). Make
// it private and use public accessors instead.
std::optional<bool> forward_;
};
enum class PropertyType : uint64_t {
kDeliveryTimeout = 0x02,
kMaxCacheDuration = 0x04,
kImmutableProperties = 0x0b,
kDefaultPublisherPriority = 0x0e,
kDefaultPublisherGroupOrder = 0x22,
kDynamicGroups = 0x30,
kPriorGroupIdGap = 0x3c,
kPriorObjectIdGap = 0x3e,
};
inline constexpr quic::QuicTimeDelta kDefaultMaxCacheDuration =
quic::QuicTimeDelta::Infinite();
inline constexpr bool kDefaultImmutableProperties = false;
inline constexpr MoqtDeliveryOrder kDefaultGroupOrder =
MoqtDeliveryOrder::kAscending;
inline constexpr bool kDefaultDynamicGroups = false;
inline constexpr uint64_t kMinMandatoryTrackProperty = 0x4000;
inline constexpr uint64_t kMaxMandatoryTrackProperty = 0x7FFF;
class TrackProperties : public KeyValuePairList {
public:
TrackProperties() = default;
TrackProperties(const TrackProperties&) = default;
// Constructor for Original publishers to create their track properties.
TrackProperties(std::optional<quic::QuicTimeDelta> delivery_timeout,
std::optional<quic::QuicTimeDelta> max_cache_duration,
std::optional<MoqtPriority> publisher_priority,
std::optional<MoqtDeliveryOrder> group_order,
std::optional<bool> dynamic_groups,
std::optional<absl::string_view> immutable_properties);
// If present and well-formed, returns the value of the property. Returns the
// default value if missing or ill-formed.
quic::QuicTimeDelta delivery_timeout() const;
quic::QuicTimeDelta max_cache_duration() const;
absl::string_view immutable_properties() const;
MoqtPriority default_publisher_priority() const;
MoqtDeliveryOrder default_publisher_group_order() const;
bool dynamic_groups() const;
bool empty() const { return size() == 0; }
// Returns false if the property list contains illegal values or illegally
// duplicated properties.
bool Validate() const;
// Returns OK if there are no unknown mandatory properties. Otherwise, returns
// an error.
absl::Status CheckForUnknownMandatoryProperty() const {
std::optional<uint64_t> key = first_key_in_range(
kMinMandatoryTrackProperty, kMaxMandatoryTrackProperty);
return !key.has_value()
? absl::OkStatus()
: absl::FailedPreconditionError(absl::StrCat(
"Unknown mandatory property: 0x", absl::Hex(*key)));
}
bool operator==(const TrackProperties& other) const = default;
TrackProperties& operator=(const TrackProperties& other) = default;
private:
// Returns the value of the property if there is exactly one, otherwise
// returns std::nullopt. Must not be called on odd property types.
std::optional<uint64_t> GetValueIfExactlyOne(PropertyType header) const;
// Verifies that there is no more that one instance of an property, and if
// present, that the value is acceptable.
bool ValidateInner(PropertyType header, std::optional<uint64_t> min_value,
std::optional<uint64_t> max_value) const;
};
} // namespace moqt
#endif // QUICHE_QUIC_MOQT_MOQT_KEY_VALUE_PAIR_H_