blob: a7fe5c72d42c7db8991377fe8d14518f3a8820b0 [file] [log] [blame]
// 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/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/common/platform/api/quiche_export.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/quiche_callbacks.h"
#include "quiche/common/quiche_data_writer.h"
namespace moqt {
// Encodes a list of key-value pairs common to both parameters and extensions.
// 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;
private:
absl::btree_multimap<uint64_t, std::variant<uint64_t, std::string>> map_;
};
inline constexpr uint64_t kMaxGroupId = quiche::kVarInt62MaxValue;
inline constexpr uint64_t kMaxObjectId = quiche::kVarInt62MaxValue;
// Location as defined in
// https://moq-wg.github.io/moq-transport/draft-ietf-moq-transport.html#location-structure
struct Location {
uint64_t group = 0;
uint64_t object = 0;
Location() = default;
Location(uint64_t group, uint64_t object) : group(group), object(object) {}
// Location order as described in
// https://moq-wg.github.io/moq-transport/draft-ietf-moq-transport.html#location-structure
auto operator<=>(const Location&) const = default;
Location Next() const {
if (object == kMaxObjectId) {
if (group == kMaxObjectId) {
return Location(0, 0);
}
return Location(group + 1, 0);
}
return Location(group, object + 1);
}
template <typename H>
friend H AbslHashValue(H h, const Location& m);
template <typename Sink>
friend void AbslStringify(Sink& sink, const Location& sequence) {
absl::Format(&sink, "(%d; %d)", sequence.group, sequence.object);
}
};
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.
inline constexpr uint64_t kDefaultMaxRequestId = 0;
// 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,
kMaxRequestId = 0x2,
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 TrackExtensions.
struct QUICHE_EXPORT SetupParameters {
SetupParameters() = default;
// Constructors for tests.
SetupParameters(absl::string_view path, absl::string_view authority,
uint64_t max_request_id)
: path(path), max_request_id(max_request_id), authority(authority) {}
SetupParameters(uint64_t max_request_id) : max_request_id(max_request_id) {}
std::optional<std::string> path;
std::optional<uint64_t> max_request_id;
// 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|.
MoqtError 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;
constexpr uint8_t kDefaultSubscriberPriority = 128;
// TODO(martinduke): Refactor this to be more like TrackExtensions.
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);
}
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|.
MoqtError 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 ExtensionHeader : uint64_t {
kDeliveryTimeout = 0x02,
kMaxCacheDuration = 0x04,
kImmutableExtensions = 0x0b,
kDefaultPublisherPriority = 0x0e,
kDefaultPublisherGroupOrder = 0x22,
kDynamicGroups = 0x30,
kPriorGroupIdGap = 0x3c,
kPriorObjectIdGap = 0x3e,
};
inline constexpr quic::QuicTimeDelta kDefaultMaxCacheDuration =
quic::QuicTimeDelta::Infinite();
inline constexpr bool kDefaultImmutableExtensions = false;
inline constexpr MoqtPriority kDefaultPublisherPriority = 128;
inline constexpr MoqtDeliveryOrder kDefaultGroupOrder =
MoqtDeliveryOrder::kAscending;
inline constexpr bool kDefaultDynamicGroups = false;
class TrackExtensions : public KeyValuePairList {
public:
TrackExtensions() = default;
TrackExtensions(const TrackExtensions&) = default;
// Constructor for Original publishers to create their extensions.
TrackExtensions(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_extensions);
// If present and well-formed, returns the value of the extension. Returns the
// default value if missing or ill-formed.
quic::QuicTimeDelta delivery_timeout() const;
quic::QuicTimeDelta max_cache_duration() const;
absl::string_view immutable_extensions() const;
MoqtPriority default_publisher_priority() const;
MoqtDeliveryOrder default_publisher_group_order() const;
bool dynamic_groups() const;
// Returns false if the extension list contains illegal values or illegally
// duplicated extensions.
bool Validate() const;
bool operator==(const TrackExtensions& other) const = default;
TrackExtensions& operator=(const TrackExtensions& other) = default;
private:
// Returns the value of the extension if there is exactly one, otherwise
// returns std::nullopt. Must not be called on odd extension types.
std::optional<uint64_t> GetValueIfExactlyOne(ExtensionHeader header) const;
// Verifies that there is no more that one instance of an extension, and if
// present, that the value is acceptable.
bool ValidateInner(ExtensionHeader header, std::optional<uint64_t> min_value,
std::optional<uint64_t> max_value) const;
};
// Version specific parameters.
// TODO(martinduke): Replace with MessageParameters and delete when all
// messages are migrated.
enum class QUICHE_EXPORT VersionSpecificParameter : uint64_t {
kDeliveryTimeout = 0x2,
kAuthorizationToken = 0x3,
kMaxCacheDuration = 0x4,
// QUICHE-specific extensions.
kOackWindowSize = 0xbbf1438,
};
struct VersionSpecificParameters {
VersionSpecificParameters() = default;
// Likely parameter combinations.
VersionSpecificParameters(quic::QuicTimeDelta delivery_timeout,
quic::QuicTimeDelta max_cache_duration)
: delivery_timeout(delivery_timeout),
max_cache_duration(max_cache_duration) {}
VersionSpecificParameters(AuthTokenType token_type, absl::string_view token) {
authorization_tokens.emplace_back(token_type, token);
}
VersionSpecificParameters(quic::QuicTimeDelta delivery_timeout,
AuthTokenType token_type, absl::string_view token)
: delivery_timeout(delivery_timeout) {
authorization_tokens.emplace_back(token_type, token);
}
std::vector<AuthToken> authorization_tokens;
quic::QuicTimeDelta delivery_timeout = quic::QuicTimeDelta::Infinite();
quic::QuicTimeDelta max_cache_duration = quic::QuicTimeDelta::Infinite();
std::optional<quic::QuicTimeDelta> oack_window_size;
bool operator==(const VersionSpecificParameters& other) const = default;
KeyValuePairList ToKeyValuePairList() const;
MoqtError FromKeyValuePairList(const KeyValuePairList& list);
};
// TODO(martinduke): Extension Headers (MOQT draft-16 Sec 11)
} // namespace moqt
#endif // QUICHE_QUIC_MOQT_MOQT_KEY_VALUE_PAIR_H_