| // 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 <algorithm> |
| #include <cstddef> |
| #include <cstdint> |
| #include <initializer_list> |
| #include <optional> |
| #include <string> |
| #include <utility> |
| #include <variant> |
| #include <vector> |
| |
| #include "absl/container/btree_map.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/platform/api/quiche_logging.h" |
| #include "quiche/common/quiche_callbacks.h" |
| #include "quiche/common/quiche_data_writer.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 { |
| kDraft14 = 0xff00000e, |
| kUnrecognizedVersionForTests = 0xfe0000ff, |
| }; |
| |
| inline constexpr MoqtVersion kDefaultMoqtVersion = MoqtVersion::kDraft14; |
| 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; |
| inline constexpr uint64_t kMaxObjectId = quiche::kVarInt62MaxValue; |
| |
| 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 = default; |
| |
| AuthTokenType type; |
| std::string token; |
| }; |
| |
| 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) const = default; |
| |
| 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; |
| // TODO(martinduke): Turn authorization_token into structured data. |
| std::vector<AuthToken> authorization_token; |
| }; |
| |
| // 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; |
| |
| class QUICHE_EXPORT MoqtDataStreamType { |
| public: |
| static constexpr uint64_t kFetch = 0x05; |
| static constexpr uint64_t kPadding = 0x26d3; |
| static constexpr uint64_t kSubgroupFlag = 0x10; |
| static constexpr uint64_t kExtensionFlag = 0x01; |
| static constexpr uint64_t kEndOfGroupFlag = 0x08; |
| // These two cannot simultaneously be true; |
| static constexpr uint64_t kFirstObjectIdFlag = 0x02; |
| static constexpr uint64_t kSubgroupIdFlag = 0x04; |
| |
| // Factory functions. |
| static std::optional<MoqtDataStreamType> FromValue(uint64_t value) { |
| MoqtDataStreamType stream_type(value); |
| if (stream_type.IsFetch() || stream_type.IsPadding() || |
| (!((value & kSubgroupIdFlag) && (value & kFirstObjectIdFlag)) && |
| stream_type.IsSubgroup())) { |
| return stream_type; |
| } |
| return std::nullopt; |
| } |
| static MoqtDataStreamType Fetch() { return MoqtDataStreamType(kFetch); } |
| static MoqtDataStreamType Padding() { return MoqtDataStreamType(kPadding); } |
| static MoqtDataStreamType Subgroup(uint64_t subgroup_id, |
| uint64_t first_object_id, |
| bool no_extension_headers, |
| bool end_of_group = false) { |
| uint64_t value = kSubgroupFlag; |
| if (!no_extension_headers) { |
| value |= kExtensionFlag; |
| } |
| if (end_of_group) { |
| value |= kEndOfGroupFlag; |
| } |
| if (subgroup_id == 0) { |
| return MoqtDataStreamType(value); |
| } |
| if (subgroup_id == first_object_id) { |
| value |= kFirstObjectIdFlag; |
| } else { |
| value |= kSubgroupIdFlag; |
| } |
| return MoqtDataStreamType(value); |
| } |
| MoqtDataStreamType(const MoqtDataStreamType& other) = default; |
| bool IsFetch() const { return value_ == kFetch; } |
| bool IsPadding() const { return value_ == kPadding; } |
| bool IsSubgroup() const { |
| QUICHE_CHECK( |
| !((value_ & kSubgroupIdFlag) && (value_ & kFirstObjectIdFlag))); |
| return (value_ & kSubgroupFlag) && (value_ & ~0x1f) == 0; |
| } |
| bool IsSubgroupPresent() const { |
| return IsSubgroup() && (value_ & kSubgroupIdFlag); |
| } |
| bool SubgroupIsZero() const { |
| return IsSubgroup() && !(value_ & kSubgroupIdFlag) && |
| !(value_ & kFirstObjectIdFlag); |
| } |
| bool SubgroupIsFirstObjectId() const { |
| return IsSubgroup() && (value_ & kFirstObjectIdFlag); |
| } |
| bool AreExtensionHeadersPresent() const { |
| return IsSubgroup() && (value_ & kExtensionFlag); |
| } |
| bool EndOfGroupInStream() const { |
| return IsSubgroup() && (value_ & kEndOfGroupFlag); |
| } |
| |
| uint64_t value() const { return value_; } |
| bool operator==(const MoqtDataStreamType& other) const = default; |
| |
| private: |
| explicit MoqtDataStreamType(uint64_t value) : value_(value) {} |
| const uint64_t value_; |
| }; |
| |
| class QUICHE_EXPORT MoqtDatagramType { |
| public: |
| // The arguments here are properties of the object. The constructor creates |
| // the appropriate type given those properties and the spec restrictions. |
| MoqtDatagramType(bool payload, bool extension, bool end_of_group, |
| bool zero_object_id) |
| : value_(0) { |
| // Avoid illegal types. Status cannot coexist with the zero-object-id flag |
| // or the end-of-group flag. |
| if (!payload && !end_of_group) { |
| // The only way to express non-normal, non-end-of-group with no payload is |
| // with an explicit status, so we cannot utilize object ID compression. |
| zero_object_id = false; |
| } else if (zero_object_id) { |
| // zero-object-id saves a byte; no-payload does not. |
| payload = true; |
| } else if (!payload) { |
| // If it's an empty end-of-group object, use the explict status because |
| // it's more readable. |
| end_of_group = false; |
| } |
| if (extension) { |
| value_ |= 0x01; |
| } |
| if (end_of_group) { |
| value_ |= 0x02; |
| } |
| if (zero_object_id) { |
| value_ |= 0x04; |
| } |
| if (!payload) { |
| value_ |= 0x20; |
| } |
| } |
| static std::optional<MoqtDatagramType> FromValue(uint64_t value) { |
| if (value <= 7 || value == 0x20 || value == 0x21) { |
| return MoqtDatagramType(value); |
| } |
| return std::nullopt; |
| } |
| bool has_status() const { return value_ & 0x20; } |
| bool has_object_id() const { return !(value_ & 0x04); } |
| bool end_of_group() const { return value_ & 0x02; } |
| bool has_extension() const { return value_ & 0x01; } |
| uint64_t value() const { return value_; } |
| |
| bool operator==(const MoqtDatagramType& other) const = default; |
| |
| private: |
| uint64_t value_; |
| explicit MoqtDatagramType(uint64_t value) : value_(value) {} |
| }; |
| |
| 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, |
| kTrackStatus = 0x0d, |
| kTrackStatusOk = 0x0e, |
| kTrackStatusError = 0x0f, |
| kGoAway = 0x10, |
| kSubscribeNamespace = 0x11, |
| kSubscribeNamespaceOk = 0x12, |
| kSubscribeNamespaceError = 0x13, |
| kUnsubscribeNamespace = 0x14, |
| kMaxRequestId = 0x15, |
| kFetch = 0x16, |
| kFetchCancel = 0x17, |
| kFetchOk = 0x18, |
| kFetchError = 0x19, |
| kRequestsBlocked = 0x1a, |
| kPublish = 0x1d, |
| kPublishOk = 0x1e, |
| kPublishError = 0x1f, |
| 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, |
| kMalformedAuthToken = 0x16, |
| kUnknownAuthTokenAlias = 0x17, |
| kExpiredAuthToken = 0x18, |
| kInvalidAuthority = 0x19, |
| kMalformedAuthority = 0x1a, |
| }; |
| |
| // Error codes used by MoQT to reset streams. |
| inline constexpr webtransport::StreamErrorCode kResetCodeUnknown = 0x00; |
| inline constexpr webtransport::StreamErrorCode kResetCodeCanceled = 0x01; |
| inline constexpr webtransport::StreamErrorCode kResetCodeDeliveryTimeout = 0x02; |
| inline constexpr webtransport::StreamErrorCode kResetCodeSessionClosed = 0x03; |
| // TODO(martinduke): This is not in the spec, but is needed. The number might |
| // change. |
| inline constexpr webtransport::StreamErrorCode kResetCodeMalformedTrack = 0x04; |
| |
| enum class QUICHE_EXPORT SetupParameter : uint64_t { |
| kPath = 0x1, |
| kMaxRequestId = 0x2, |
| kAuthorizationToken = 0x3, |
| kMaxAuthTokenCacheSize = 0x4, |
| |
| // QUICHE-specific extensions. |
| // Indicates support for OACK messages. |
| kSupportObjectAcks = 0xbbf1438, |
| }; |
| |
| 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_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 = default; |
| }; |
| |
| // Used for SUBSCRIBE_ERROR, ANNOUNCE_ERROR, ANNOUNCE_CANCEL, |
| // SUBSCRIBE_NAMESPACE_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_NAMESPACE_ERROR only. |
| kInvalidRange = 0x5, // SUBSCRIBE_ERROR and FETCH_ERROR only. |
| kNamespacePrefixOverlap = 0x5, // SUBSCRIBE_NAMESPACE_ERROR only. |
| kNoObjects = 0x6, // FETCH_ERROR only. |
| kInvalidJoiningRequestId = 0x7, // FETCH_ERROR only. |
| kUnknownStatusInRange = 0x8, // FETCH_ERROR only. |
| kMalformedTrack = 0x9, |
| kMalformedAuthToken = 0x10, |
| 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_; } |
| |
| auto operator<=>(const TrackNamespace& other) const { |
| return std::lexicographical_compare_three_way( |
| tuple_.cbegin(), tuple_.cend(), other.tuple_.cbegin(), |
| other.tuple_.cend()); |
| } |
| bool operator==(const TrackNamespace&) const = default; |
| |
| 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(); } |
| |
| auto operator<=>(const FullTrackName&) const = default; |
| 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_ = ""; |
| }; |
| |
| // 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); |
| } |
| }; |
| |
| struct SubgroupPriority { |
| uint8_t publisher_priority = 0xf0; |
| uint64_t subgroup_id = 0; |
| |
| auto operator<=>(const SubgroupPriority&) const = default; |
| }; |
| |
| 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, |
| kEndOfGroup = 0x3, |
| kEndOfTrack = 0x4, |
| kInvalidObjectStatus = 0x5, |
| }; |
| |
| 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; |
| 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; |
| 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; |
| uint64_t track_alias; |
| // 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; |
| }; |
| |
| struct QUICHE_EXPORT MoqtUnsubscribe { |
| uint64_t request_id; |
| }; |
| |
| enum class QUICHE_EXPORT SubscribeDoneCode : uint64_t { |
| kInternalError = 0x0, |
| kUnauthorized = 0x1, |
| kTrackEnded = 0x2, |
| kSubscriptionEnded = 0x3, |
| kGoingAway = 0x4, |
| kExpired = 0x5, |
| kTooFarBehind = 0x6, |
| kMalformedTrack = 0x7, |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeDone { |
| uint64_t request_id; |
| SubscribeDoneCode status_code; |
| uint64_t stream_count; |
| std::string error_reason; |
| }; |
| |
| 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 { |
| uint64_t request_id; |
| TrackNamespace track_namespace; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtAnnounceOk { |
| uint64_t request_id; |
| }; |
| |
| struct QUICHE_EXPORT MoqtAnnounceError { |
| uint64_t request_id; |
| RequestErrorCode error_code; |
| std::string error_reason; |
| }; |
| |
| struct QUICHE_EXPORT MoqtUnannounce { |
| TrackNamespace track_namespace; |
| }; |
| |
| struct QUICHE_EXPORT MoqtAnnounceCancel { |
| TrackNamespace track_namespace; |
| RequestErrorCode error_code; |
| std::string error_reason; |
| }; |
| |
| struct QUICHE_EXPORT MoqtTrackStatus : public MoqtSubscribe { |
| MoqtTrackStatus() = default; |
| MoqtTrackStatus(MoqtSubscribe subscribe) : MoqtSubscribe(subscribe) {} |
| }; |
| |
| struct QUICHE_EXPORT MoqtTrackStatusOk : public MoqtSubscribeOk { |
| MoqtTrackStatusOk() = default; |
| MoqtTrackStatusOk(MoqtSubscribeOk subscribe_ok) |
| : MoqtSubscribeOk(subscribe_ok) {} |
| }; |
| |
| struct QUICHE_EXPORT MoqtTrackStatusError : public MoqtSubscribeError { |
| MoqtTrackStatusError() = default; |
| MoqtTrackStatusError(MoqtSubscribeError subscribe_error) |
| : MoqtSubscribeError(subscribe_error) {} |
| }; |
| |
| struct QUICHE_EXPORT MoqtGoAway { |
| std::string new_session_uri; |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeNamespace { |
| uint64_t request_id; |
| TrackNamespace track_namespace; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeNamespaceOk { |
| uint64_t request_id; |
| }; |
| |
| struct QUICHE_EXPORT MoqtSubscribeNamespaceError { |
| uint64_t request_id; |
| RequestErrorCode error_code; |
| std::string error_reason; |
| }; |
| |
| struct QUICHE_EXPORT MoqtUnsubscribeNamespace { |
| TrackNamespace track_namespace; |
| }; |
| |
| struct QUICHE_EXPORT MoqtMaxRequestId { |
| uint64_t max_request_id; |
| }; |
| |
| enum class QUICHE_EXPORT FetchType : uint64_t { |
| kStandalone = 0x1, |
| kRelativeJoining = 0x2, |
| kAbsoluteJoining = 0x3, |
| }; |
| |
| struct StandaloneFetch { |
| StandaloneFetch() = default; |
| StandaloneFetch(FullTrackName full_track_name, Location start_location, |
| Location end_location) |
| : full_track_name(full_track_name), |
| start_location(start_location), |
| end_location(end_location) {} |
| FullTrackName full_track_name; |
| Location start_location; |
| Location end_location; |
| bool operator==(const StandaloneFetch& other) const { |
| return full_track_name == other.full_track_name && |
| start_location == other.start_location && |
| end_location == other.end_location; |
| } |
| bool operator!=(const StandaloneFetch& other) const { |
| return !(*this == other); |
| } |
| }; |
| |
| struct JoiningFetchRelative { |
| JoiningFetchRelative(uint64_t joining_request_id, uint64_t joining_start) |
| : joining_request_id(joining_request_id), joining_start(joining_start) {} |
| uint64_t joining_request_id; |
| uint64_t joining_start; |
| bool operator==(const JoiningFetchRelative& other) const { |
| return joining_request_id == other.joining_request_id && |
| joining_start == other.joining_start; |
| } |
| bool operator!=(const JoiningFetchRelative& other) const { |
| return !(*this == other); |
| } |
| }; |
| |
| struct JoiningFetchAbsolute { |
| JoiningFetchAbsolute(uint64_t joining_request_id, uint64_t joining_start) |
| : joining_request_id(joining_request_id), joining_start(joining_start) {} |
| uint64_t joining_request_id; |
| uint64_t joining_start; |
| bool operator==(const JoiningFetchAbsolute& other) const { |
| return joining_request_id == other.joining_request_id && |
| joining_start == other.joining_start; |
| } |
| bool operator!=(const JoiningFetchAbsolute& other) const { |
| return !(*this == other); |
| } |
| }; |
| |
| struct QUICHE_EXPORT MoqtFetch { |
| uint64_t request_id; |
| MoqtPriority subscriber_priority; |
| std::optional<MoqtDeliveryOrder> group_order; |
| std::variant<StandaloneFetch, JoiningFetchRelative, JoiningFetchAbsolute> |
| fetch; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtFetchOk { |
| uint64_t request_id; |
| MoqtDeliveryOrder group_order; |
| bool end_of_track; |
| Location end_location; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtFetchError { |
| uint64_t request_id; |
| RequestErrorCode error_code; |
| std::string error_reason; |
| }; |
| |
| struct QUICHE_EXPORT MoqtFetchCancel { |
| uint64_t request_id; |
| }; |
| |
| struct QUICHE_EXPORT MoqtRequestsBlocked { |
| uint64_t max_request_id; |
| }; |
| |
| struct QUICHE_EXPORT MoqtPublish { |
| uint64_t request_id; |
| FullTrackName full_track_name; |
| uint64_t track_alias; |
| MoqtDeliveryOrder group_order; |
| std::optional<Location> largest_location; |
| bool forward; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtPublishOk { |
| uint64_t request_id; |
| bool forward; |
| MoqtPriority subscriber_priority; |
| MoqtDeliveryOrder group_order; |
| MoqtFilterType filter_type; |
| std::optional<Location> start; |
| std::optional<uint64_t> end_group; |
| VersionSpecificParameters parameters; |
| }; |
| |
| struct QUICHE_EXPORT MoqtPublishError { |
| uint64_t request_id; |
| RequestErrorCode error_code; |
| std::string error_reason; |
| }; |
| |
| // 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_ |