| // Copyright (c) 2023 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. |
| |
| // Structured data for message types in draft-ietf-moq-transport-02. |
| |
| #ifndef QUICHE_QUIC_MOQT_MOQT_MESSAGES_H_ |
| #define QUICHE_QUIC_MOQT_MOQT_MESSAGES_H_ |
| |
| #include <cstddef> |
| #include <cstdint> |
| #include <initializer_list> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "absl/container/btree_map.h" |
| #include "absl/container/inlined_vector.h" |
| #include "absl/status/status.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/string_view.h" |
| #include "absl/types/span.h" |
| #include "quiche/quic/core/quic_time.h" |
| #include "quiche/quic/core/quic_types.h" |
| #include "quiche/quic/core/quic_versions.h" |
| #include "quiche/quic/moqt/moqt_priority.h" |
| #include "quiche/common/platform/api/quiche_bug_tracker.h" |
| #include "quiche/common/platform/api/quiche_export.h" |
| #include "quiche/common/quiche_callbacks.h" |
| #include "quiche/web_transport/web_transport.h" |
| |
| namespace moqt { |
| |
| inline constexpr quic::ParsedQuicVersionVector GetMoqtSupportedQuicVersions() { |
| return quic::ParsedQuicVersionVector{quic::ParsedQuicVersion::RFCv1()}; |
| } |
| |
| enum class MoqtVersion : uint64_t { |
| kDraft11 = 0xff00000b, |
| kUnrecognizedVersionForTests = 0xfe0000ff, |
| }; |
| |
| inline constexpr MoqtVersion kDefaultMoqtVersion = MoqtVersion::kDraft11; |
| inline constexpr uint64_t kDefaultInitialMaxRequestId = 100; |
| // TODO(martinduke): Implement an auth token cache. |
| inline constexpr uint64_t kDefaultMaxAuthTokenCacheSize = 0; |
| inline constexpr uint64_t kMinNamespaceElements = 1; |
| inline constexpr uint64_t kMaxNamespaceElements = 32; |
| inline constexpr size_t kMaxFullTrackNameSize = 1024; |
| |
| struct QUICHE_EXPORT MoqtSessionParameters { |
| // TODO: support multiple versions. |
| MoqtSessionParameters() = default; |
| explicit MoqtSessionParameters(quic::Perspective perspective) |
| : perspective(perspective), using_webtrans(true) {} |
| MoqtSessionParameters(quic::Perspective perspective, std::string path) |
| : perspective(perspective), |
| using_webtrans(false), |
| path(std::move(path)) {} |
| MoqtSessionParameters(quic::Perspective perspective, std::string path, |
| uint64_t max_request_id) |
| : perspective(perspective), |
| using_webtrans(true), |
| path(std::move(path)), |
| max_request_id(max_request_id) {} |
| MoqtSessionParameters(quic::Perspective perspective, uint64_t max_request_id) |
| : perspective(perspective), max_request_id(max_request_id) {} |
| bool operator==(const MoqtSessionParameters& other) { |
| return version == other.version && |
| deliver_partial_objects == other.deliver_partial_objects && |
| perspective == other.perspective && |
| using_webtrans == other.using_webtrans && path == other.path && |
| max_request_id == other.max_request_id && |
| max_auth_token_cache_size == other.max_auth_token_cache_size && |
| support_object_acks == other.support_object_acks; |
| } |
| |
| MoqtVersion version = kDefaultMoqtVersion; |
| bool deliver_partial_objects = false; |
| quic::Perspective perspective = quic::Perspective::IS_SERVER; |
| bool using_webtrans = true; |
| std::string path = ""; |
| uint64_t max_request_id = kDefaultInitialMaxRequestId; |
| uint64_t max_auth_token_cache_size = kDefaultMaxAuthTokenCacheSize; |
| bool support_object_acks = false; |
| }; |
| |
| // The maximum length of a message, excluding any OBJECT payload. This prevents |
| // DoS attack via forcing the parser to buffer a large message (OBJECT payloads |
| // are not buffered by the parser). |
| inline constexpr size_t kMaxMessageHeaderSize = 2048; |
| |
| enum class QUICHE_EXPORT MoqtDataStreamType : uint64_t { |
| kStreamHeaderSubgroup = 0x04, |
| kStreamHeaderFetch = 0x05, |
| |
| // Currently QUICHE-specific. All data on a kPadding stream is ignored. |
| kPadding = 0x26d3, |
| }; |
| |
| enum class QUICHE_EXPORT MoqtDatagramType : uint64_t { |
| kObject = 0x01, |
| kObjectStatus = 0x02, |
| }; |
| |
| enum class QUICHE_EXPORT MoqtMessageType : uint64_t { |
| kSubscribeUpdate = 0x02, |
| kSubscribe = 0x03, |
| kSubscribeOk = 0x04, |
| kSubscribeError = 0x05, |
| kAnnounce = 0x06, |
| kAnnounceOk = 0x7, |
| kAnnounceError = 0x08, |
| kUnannounce = 0x09, |
| kUnsubscribe = 0x0a, |
| kSubscribeDone = 0x0b, |
| kAnnounceCancel = 0x0c, |
| kTrackStatusRequest = 0x0d, |
| kTrackStatus = 0x0e, |
| kGoAway = 0x10, |
| kSubscribeAnnounces = 0x11, |
| kSubscribeAnnouncesOk = 0x12, |
| kSubscribeAnnouncesError = 0x13, |
| kUnsubscribeAnnounces = 0x14, |
| kMaxRequestId = 0x15, |
| kFetch = 0x16, |
| kFetchCancel = 0x17, |
| kFetchOk = 0x18, |
| kFetchError = 0x19, |
| kRequestsBlocked = 0x1a, |
| kClientSetup = 0x20, |
| kServerSetup = 0x21, |
| |
| // QUICHE-specific extensions. |
| |
| // kObjectAck (OACK for short) is a frame used by the receiver indicating that |
| // it has received and processed the specified object. |
| kObjectAck = 0x3184, |
| }; |
| |
| enum class QUICHE_EXPORT MoqtError : uint64_t { |
| kNoError = 0x0, |
| kInternalError = 0x1, |
| kUnauthorized = 0x2, |
| kProtocolViolation = 0x3, |
| kInvalidRequestId = 0x4, |
| kDuplicateTrackAlias = 0x5, |
| kKeyValueFormattingError = 0x6, |
| kTooManyRequests = 0x7, |
| kInvalidPath = 0x8, |
| kMalformedPath = 0x9, |
| kGoawayTimeout = 0x10, |
| kControlMessageTimeout = 0x11, |
| kDataStreamTimeout = 0x12, |
| kAuthTokenCacheOverflow = 0x13, |
| kDuplicateAuthTokenAlias = 0x14, |
| kVersionNegotiationFailed = 0x15, |
| }; |
| |
| // Error codes used by MoQT to reset streams. |
| // TODO: update with spec-defined error codes once those are available, see |
| // <https://github.com/moq-wg/moq-transport/issues/481>. |
| inline constexpr webtransport::StreamErrorCode kResetCodeUnknown = 0x00; |
| inline constexpr webtransport::StreamErrorCode kResetCodeSubscriptionGone = |
| 0x01; |
| inline constexpr webtransport::StreamErrorCode kResetCodeTimedOut = 0x02; |
| |
| enum class QUICHE_EXPORT SetupParameter : uint64_t { |
| kPath = 0x1, |
| kMaxRequestId = 0x2, |
| kMaxAuthTokenCacheSize = 0x4, |
| |
| // QUICHE-specific extensions. |
| // Indicates support for OACK messages. |
| kSupportObjectAcks = 0xbbf1438, |
| }; |
| |
| enum class QUICHE_EXPORT VersionSpecificParameter : uint64_t { |
| kAuthorizationToken = 0x1, |
| kDeliveryTimeout = 0x2, |
| kMaxCacheDuration = 0x4, |
| |
| // QUICHE-specific extensions. |
| kOackWindowSize = 0xbbf1438, |
| }; |
| |
| 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(AuthTokenType token_type, absl::string_view token) |
| : type(token_type), token(token) {} |
| bool operator==(const AuthToken& other) const { |
| return type == other.type && token == other.token; |
| } |
| AuthTokenType type; |
| std::string token; |
| }; |
| |
| 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_token.emplace_back(token_type, token); |
| }; |
| VersionSpecificParameters(quic::QuicTimeDelta delivery_timeout, |
| AuthTokenType token_type, absl::string_view token) |
| : delivery_timeout(delivery_timeout) { |
| authorization_token.emplace_back(token_type, token); |
| } |
| |
| // TODO(martinduke): Turn auth_token into structured data. |
| std::vector<AuthToken> authorization_token; |
| 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 { |
| return authorization_token == other.authorization_token && |
| delivery_timeout == other.delivery_timeout && |
| max_cache_duration == other.max_cache_duration && |
| oack_window_size == other.oack_window_size; |
| } |
| }; |
| |
| // Used for SUBSCRIBE_ERROR, ANNOUNCE_ERROR, ANNOUNCE_CANCEL, |
| // SUBSCRIBE_ANNOUNCES_ERROR, and FETCH_ERROR. |
| enum class QUICHE_EXPORT RequestErrorCode : uint64_t { |
| kInternalError = 0x0, |
| kUnauthorized = 0x1, |
| kTimeout = 0x2, |
| kNotSupported = 0x3, |
| kTrackDoesNotExist = 0x4, // SUBSCRIBE_ERROR and FETCH_ERROR only. |
| kUninterested = 0x4, // ANNOUNCE_ERROR and ANNOUNCE_CANCEL only. |
| kNamespacePrefixUnknown = 0x4, // SUBSCRIBE_ANNOUNCES_ERROR only. |
| kInvalidRange = 0x5, // SUBSCRIBE_ERROR and FETCH_ERROR only. |
| kNamespacePrefixOverlap = 0x5, // SUBSCRIBE_ANNOUNCES_ERROR only. |
| kRetryTrackAlias = 0x6, // SUBSCRIBE_ERROR only. |
| kNoObjects = 0x6, // FETCH_ERROR only. |
| kInvalidJoiningSubscribeId = 0x7, // FETCH_ERROR only. |
| kMalformedAuthToken = 0x10, |
| kUnknownAuthTokenAlias = 0x11, |
| kExpiredAuthToken = 0x12, |
| }; |
| |
| struct MoqtSubscribeErrorReason { |
| RequestErrorCode error_code; |
| std::string reason_phrase; |
| }; |
| using MoqtAnnounceErrorReason = MoqtSubscribeErrorReason; |
| |
| class TrackNamespace { |
| public: |
| explicit TrackNamespace(absl::Span<const absl::string_view> elements); |
| explicit TrackNamespace( |
| std::initializer_list<const absl::string_view> elements) |
| : TrackNamespace(absl::Span<const absl::string_view>( |
| std::data(elements), std::size(elements))) {} |
| explicit TrackNamespace(absl::string_view ns) : TrackNamespace({ns}) {} |
| TrackNamespace() : TrackNamespace({}) {} |
| |
| bool IsValid() const { |
| return !tuple_.empty() && tuple_.size() <= kMaxNamespaceElements && |
| length_ <= kMaxFullTrackNameSize; |
| } |
| bool InNamespace(const TrackNamespace& other) const; |
| // Check if adding an element will exceed limits, without triggering a |
| // bug. Useful for the parser, which has to be robust to malformed data. |
| bool CanAddElement(absl::string_view element) { |
| return (tuple_.size() < kMaxNamespaceElements && |
| length_ + element.length() <= kMaxFullTrackNameSize); |
| } |
| void AddElement(absl::string_view element); |
| std::string ToString() const; |
| // Returns the number of elements in the tuple. |
| size_t number_of_elements() const { return tuple_.size(); } |
| // Returns the sum of the lengths of all elements in the tuple. |
| size_t total_length() const { return length_; } |
| bool operator==(const TrackNamespace& other) const; |
| bool operator<(const TrackNamespace& other) const; |
| const std::vector<std::string>& tuple() const { return tuple_; } |
| |
| template <typename H> |
| friend H AbslHashValue(H h, const TrackNamespace& m) { |
| return H::combine(std::move(h), m.tuple_); |
| } |
| template <typename Sink> |
| friend void AbslStringify(Sink& sink, const TrackNamespace& track_namespace) { |
| sink.Append(track_namespace.ToString()); |
| } |
| |
| private: |
| std::vector<std::string> tuple_; |
| size_t length_ = 0; // size in bytes. |
| }; |
| |
| class FullTrackName { |
| public: |
| FullTrackName(absl::string_view ns, absl::string_view name) |
| : namespace_(ns), name_(name) { |
| QUICHE_BUG_IF(Moqt_full_track_name_too_large_01, !IsValid()) |
| << "Constructing a Full Track Name that is too large."; |
| } |
| FullTrackName(TrackNamespace ns, absl::string_view name) |
| : namespace_(ns), name_(name) { |
| QUICHE_BUG_IF(Moqt_full_track_name_too_large_02, !IsValid()) |
| << "Constructing a Full Track Name that is too large."; |
| } |
| FullTrackName() = default; |
| |
| bool IsValid() const { |
| return namespace_.IsValid() && length() <= kMaxFullTrackNameSize; |
| } |
| const TrackNamespace& track_namespace() const { return namespace_; } |
| TrackNamespace& track_namespace() { return namespace_; } |
| absl::string_view name() const { return name_; } |
| void AddElement(absl::string_view element) { |
| return namespace_.AddElement(element); |
| } |
| std::string ToString() const { |
| return absl::StrCat(namespace_.ToString(), "::", name_); |
| } |
| // Check if the name will exceed limits, without triggering a bug. Useful for |
| // the parser, which has to be robust to malformed data. |
| bool CanAddName(absl::string_view name) { |
| return (namespace_.total_length() + name.length() <= kMaxFullTrackNameSize); |
| } |
| void set_name(absl::string_view name); |
| size_t length() const { return namespace_.total_length() + name_.length(); } |
| bool operator==(const FullTrackName& other) const { |
| return name_ == other.name_ && namespace_ == other.namespace_; |
| } |
| bool operator<(const FullTrackName& other) const { |
| return namespace_ < other.namespace_ || |
| (namespace_ == other.namespace_ && name_ < other.name_); |
| } |
| template <typename H> |
| friend H AbslHashValue(H h, const FullTrackName& m) { |
| return H::combine(std::move(h), m.namespace_.tuple(), m.name_); |
| } |
| template <typename Sink> |
| friend void AbslStringify(Sink& sink, const FullTrackName& full_track_name) { |
| sink.Append(full_track_name.ToString()); |
| } |
| |
| private: |
| TrackNamespace namespace_; |
| std::string name_ = ""; |
| }; |
| |
| // These are absolute sequence numbers. |
| struct Location { |
| uint64_t group; |
| uint64_t subgroup; |
| uint64_t object; |
| Location() : Location(0, 0) {} |
| // There is a lot of code from before subgroups. Assume there's one subgroup |
| // with ID 0 per group. |
| Location(uint64_t group, uint64_t object) : Location(group, 0, object) {} |
| Location(uint64_t group, uint64_t subgroup, uint64_t object) |
| : group(group), subgroup(subgroup), object(object) {} |
| bool operator==(const Location& other) const { |
| return group == other.group && object == other.object; |
| } |
| // These are temporal ordering comparisons, so subgroup ID doesn't matter. |
| bool operator<(const Location& other) const { |
| return group < other.group || |
| (group == other.group && object < other.object); |
| } |
| bool operator<=(const Location& other) const { |
| return (group < other.group || |
| (group == other.group && object <= other.object)); |
| } |
| bool operator>(const Location& other) const { return !(*this <= other); } |
| Location& operator=(Location other) { |
| group = other.group; |
| subgroup = other.subgroup; |
| object = other.object; |
| return *this; |
| } |
| Location next() const { return Location{group, subgroup, 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); |
| } |
| }; |
| |
| struct SubgroupPriority { |
| uint8_t publisher_priority = 0xf0; |
| uint64_t subgroup_id = 0; |
| |
| bool operator==(const SubgroupPriority& other) const { |
| return publisher_priority == other.publisher_priority && |
| subgroup_id == other.subgroup_id; |
| } |
| bool operator<(const SubgroupPriority& other) const { |
| return publisher_priority < other.publisher_priority || |
| (publisher_priority == other.publisher_priority && |
| subgroup_id < other.subgroup_id); |
| } |
| bool operator<=(const SubgroupPriority& other) const { |
| return (publisher_priority < other.publisher_priority || |
| (publisher_priority == other.publisher_priority && |
| subgroup_id <= other.subgroup_id)); |
| } |
| }; |
| |
| template <typename H> |
| H AbslHashValue(H h, const Location& m) { |
| return H::combine(std::move(h), m.group, m.object); |
| } |
| |
| // 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, |
| // although it does accept various uint64_t-based enums to reduce the burden of |
| // casting on the caller. |
| class KeyValuePairList { |
| public: |
| KeyValuePairList() = default; |
| size_t size() const { return integer_map_.size() + string_map_.size(); } |
| void insert(VersionSpecificParameter key, uint64_t value) { |
| insert(static_cast<uint64_t>(key), value); |
| } |
| void insert(SetupParameter key, uint64_t value) { |
| insert(static_cast<uint64_t>(key), value); |
| } |
| void insert(VersionSpecificParameter key, absl::string_view value) { |
| insert(static_cast<uint64_t>(key), value); |
| } |
| void insert(SetupParameter key, absl::string_view value) { |
| insert(static_cast<uint64_t>(key), value); |
| } |
| void insert(uint64_t key, absl::string_view value); |
| void insert(uint64_t key, uint64_t value); |
| size_t count(VersionSpecificParameter key) const { |
| return count(static_cast<uint64_t>(key)); |
| } |
| size_t count(SetupParameter key) const { |
| return count(static_cast<uint64_t>(key)); |
| } |
| bool contains(VersionSpecificParameter key) const { |
| return contains(static_cast<uint64_t>(key)); |
| } |
| bool contains(SetupParameter key) const { |
| return contains(static_cast<uint64_t>(key)); |
| } |
| // If either of these callbacks returns false, ForEach will return early. |
| using IntCallback = quiche::UnretainedCallback<bool(uint64_t, uint64_t)>; |
| using StringCallback = |
| quiche::UnretainedCallback<bool(uint64_t, absl::string_view)>; |
| // Iterates through the whole list, and executes int_callback for each integer |
| // value and string_callback for each string value. |
| bool ForEach(IntCallback int_callback, StringCallback string_callback) const { |
| for (const auto& [key, value] : integer_map_) { |
| if (!int_callback(key, value)) { |
| return false; |
| } |
| } |
| for (const auto& [key, value] : string_map_) { |
| if (!string_callback(key, value)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| std::vector<uint64_t> GetIntegers(VersionSpecificParameter key) const { |
| return GetIntegers(static_cast<uint64_t>(key)); |
| } |
| std::vector<uint64_t> GetIntegers(SetupParameter key) const { |
| return GetIntegers(static_cast<uint64_t>(key)); |
| } |
| std::vector<absl::string_view> GetStrings( |
| VersionSpecificParameter key) const { |
| return GetStrings(static_cast<uint64_t>(key)); |
| } |
| std::vector<absl::string_view> GetStrings(SetupParameter key) const { |
| return GetStrings(static_cast<uint64_t>(key)); |
| } |
| void clear() { |
| integer_map_.clear(); |
| string_map_.clear(); |
| } |
| |
| private: |
| size_t count(uint64_t key) const; |
| bool contains(uint64_t key) const; |
| std::vector<uint64_t> GetIntegers(uint64_t key) const; |
| std::vector<absl::string_view> GetStrings(uint64_t key) const; |
| absl::btree_multimap<uint64_t, uint64_t> integer_map_; |
| absl::btree_multimap<uint64_t, std::string> string_map_; |
| }; |
| |
| // TODO(martinduke): Collapse both Setup messages into MoqtSessionParameters. |
| struct QUICHE_EXPORT MoqtClientSetup { |
| std::vector<MoqtVersion> supported_versions; |
| MoqtSessionParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtServerSetup { |
| MoqtVersion selected_version; |
| MoqtSessionParameters parameters; |
| }; |
| |
| // These codes do not appear on the wire. |
| enum class QUICHE_EXPORT MoqtForwardingPreference { |
| kSubgroup, |
| kDatagram, |
| }; |
| |
| enum class QUICHE_EXPORT MoqtObjectStatus : uint64_t { |
| kNormal = 0x0, |
| kObjectDoesNotExist = 0x1, |
| kGroupDoesNotExist = 0x2, |
| kEndOfGroup = 0x3, |
| kEndOfTrackAndGroup = 0x4, |
| kEndOfTrack = 0x5, |
| kInvalidObjectStatus = 0x6, |
| }; |
| |
| MoqtObjectStatus IntegerToObjectStatus(uint64_t integer); |
| |
| // The data contained in every Object message, although the message type |
| // implies some of the values. |
| struct QUICHE_EXPORT MoqtObject { |
| uint64_t track_alias; // For FETCH, this is the subscribe ID. |
| uint64_t group_id; |
| uint64_t object_id; |
| MoqtPriority publisher_priority; |
| std::string extension_headers; // Raw, unparsed extension headers. |
| MoqtObjectStatus object_status; |
| std::optional<uint64_t> subgroup_id; |
| uint64_t payload_length; |
| }; |
| |
| enum class QUICHE_EXPORT MoqtFilterType : uint64_t { |
| kNone = 0x0, |
| kNextGroupStart = 0x1, |
| kLatestObject = 0x2, |
| kAbsoluteStart = 0x3, |
| kAbsoluteRange = 0x4, |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribe { |
| uint64_t request_id; |
| uint64_t track_alias; |
| FullTrackName full_track_name; |
| MoqtPriority subscriber_priority; |
| std::optional<MoqtDeliveryOrder> group_order; |
| bool forward; |
| MoqtFilterType filter_type; |
| std::optional<Location> start; |
| std::optional<uint64_t> end_group; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeOk { |
| uint64_t request_id; |
| // The message uses ms, but expires is in us. |
| quic::QuicTimeDelta expires = quic::QuicTimeDelta::FromMilliseconds(0); |
| MoqtDeliveryOrder group_order; |
| // If ContextExists on the wire is zero, largest_id has no value. |
| std::optional<Location> largest_location; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeError { |
| uint64_t request_id; |
| RequestErrorCode error_code; |
| std::string reason_phrase; |
| uint64_t track_alias; |
| }; |
| |
| struct QUICHE_EXPORT MoqtUnsubscribe { |
| uint64_t subscribe_id; |
| }; |
| |
| enum class QUICHE_EXPORT SubscribeDoneCode : uint64_t { |
| kInternalError = 0x0, |
| kUnauthorized = 0x1, |
| kTrackEnded = 0x2, |
| kSubscriptionEnded = 0x3, |
| kGoingAway = 0x4, |
| kExpired = 0x5, |
| kTooFarBehind = 0x6, |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeDone { |
| uint64_t subscribe_id; |
| SubscribeDoneCode status_code; |
| uint64_t stream_count; |
| std::string reason_phrase; |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeUpdate { |
| uint64_t request_id; |
| Location start; |
| std::optional<uint64_t> end_group; |
| MoqtPriority subscriber_priority; |
| bool forward; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtAnnounce { |
| TrackNamespace track_namespace; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtAnnounceOk { |
| TrackNamespace track_namespace; |
| }; |
| |
| struct QUICHE_EXPORT MoqtAnnounceError { |
| TrackNamespace track_namespace; |
| RequestErrorCode error_code; |
| std::string reason_phrase; |
| }; |
| |
| struct QUICHE_EXPORT MoqtUnannounce { |
| TrackNamespace track_namespace; |
| }; |
| |
| enum class QUICHE_EXPORT MoqtTrackStatusCode : uint64_t { |
| kInProgress = 0x0, |
| kDoesNotExist = 0x1, |
| kNotYetBegun = 0x2, |
| kFinished = 0x3, |
| kStatusNotAvailable = 0x4, |
| }; |
| |
| inline bool DoesTrackStatusImplyHavingData(MoqtTrackStatusCode code) { |
| switch (code) { |
| case MoqtTrackStatusCode::kInProgress: |
| case MoqtTrackStatusCode::kFinished: |
| return true; |
| case MoqtTrackStatusCode::kDoesNotExist: |
| case MoqtTrackStatusCode::kNotYetBegun: |
| case MoqtTrackStatusCode::kStatusNotAvailable: |
| return false; |
| } |
| return false; |
| } |
| |
| struct QUICHE_EXPORT MoqtTrackStatus { |
| FullTrackName full_track_name; |
| MoqtTrackStatusCode status_code; |
| uint64_t last_group; |
| uint64_t last_object; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtAnnounceCancel { |
| TrackNamespace track_namespace; |
| RequestErrorCode error_code; |
| std::string reason_phrase; |
| }; |
| |
| struct QUICHE_EXPORT MoqtTrackStatusRequest { |
| FullTrackName full_track_name; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtGoAway { |
| std::string new_session_uri; |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeAnnounces { |
| TrackNamespace track_namespace; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeAnnouncesOk { |
| TrackNamespace track_namespace; |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeAnnouncesError { |
| TrackNamespace track_namespace; |
| RequestErrorCode error_code; |
| std::string reason_phrase; |
| }; |
| |
| struct QUICHE_EXPORT MoqtUnsubscribeAnnounces { |
| TrackNamespace track_namespace; |
| }; |
| |
| struct QUICHE_EXPORT MoqtMaxRequestId { |
| uint64_t max_request_id; |
| }; |
| |
| enum class QUICHE_EXPORT FetchType : uint64_t { |
| kStandalone = 0x1, |
| kJoining = 0x2, |
| }; |
| |
| struct JoiningFetch { |
| JoiningFetch(uint64_t joining_subscribe_id, uint64_t preceding_group_offset) |
| : joining_subscribe_id(joining_subscribe_id), |
| preceding_group_offset(preceding_group_offset) {} |
| uint64_t joining_subscribe_id; |
| uint64_t preceding_group_offset; |
| }; |
| |
| struct QUICHE_EXPORT MoqtFetch { |
| uint64_t fetch_id; |
| MoqtPriority subscriber_priority; |
| std::optional<MoqtDeliveryOrder> group_order; |
| // If joining_fetch has a value, then the parser will not populate the name |
| // and ranges. The session will populate them instead. |
| std::optional<JoiningFetch> joining_fetch; |
| FullTrackName full_track_name; |
| Location start_object; // subgroup is ignored |
| uint64_t end_group; |
| std::optional<uint64_t> end_object; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtFetchCancel { |
| uint64_t subscribe_id; |
| }; |
| |
| struct QUICHE_EXPORT MoqtFetchOk { |
| uint64_t subscribe_id; |
| MoqtDeliveryOrder group_order; |
| Location largest_id; // subgroup is ignored |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtFetchError { |
| uint64_t subscribe_id; |
| RequestErrorCode error_code; |
| std::string reason_phrase; |
| }; |
| |
| struct QUICHE_EXPORT MoqtRequestsBlocked { |
| uint64_t max_request_id; |
| }; |
| |
| // All of the four values in this message are encoded as varints. |
| // `delta_from_deadline` is encoded as an absolute value, with the lowest bit |
| // indicating the sign (0 if positive). |
| struct QUICHE_EXPORT MoqtObjectAck { |
| uint64_t subscribe_id; |
| uint64_t group_id; |
| uint64_t object_id; |
| // Positive if the object has been received before the deadline. |
| quic::QuicTimeDelta delta_from_deadline = quic::QuicTimeDelta::Zero(); |
| }; |
| |
| RequestErrorCode StatusToRequestErrorCode(absl::Status status); |
| absl::StatusCode RequestErrorCodeToStatusCode(RequestErrorCode error_code); |
| absl::Status RequestErrorCodeToStatus(RequestErrorCode error_code, |
| absl::string_view reason_phrase); |
| |
| // Returns an error if the parameters are malformed or otherwise violate the |
| // spec. |perspective| is the consumer of the message, not the sender. |
| MoqtError ValidateSetupParameters(const KeyValuePairList& parameters, |
| bool webtrans, quic::Perspective perspective); |
| // Returns false if the parameters contain a protocol violation, or a |
| // parameter cannot be in |message type|. Does not validate the internal |
| // structure of Authorization Token values. |
| bool ValidateVersionSpecificParameters(const KeyValuePairList& parameters, |
| MoqtMessageType message_type); |
| |
| std::string MoqtMessageTypeToString(MoqtMessageType message_type); |
| std::string MoqtDataStreamTypeToString(MoqtDataStreamType type); |
| std::string MoqtDatagramTypeToString(MoqtDatagramType type); |
| |
| std::string MoqtForwardingPreferenceToString( |
| MoqtForwardingPreference preference); |
| |
| absl::Status MoqtStreamErrorToStatus(webtransport::StreamErrorCode error_code, |
| absl::string_view reason_phrase); |
| |
| } // namespace moqt |
| |
| #endif // QUICHE_QUIC_MOQT_MOQT_MESSAGES_H_ |