blob: d4722fc06614f33d047601b8064cc55f95d475c4 [file] [log] [blame] [edit]
// 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/inlined_vector.h"
#include "absl/status/status.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/web_transport/web_transport.h"
namespace moqt {
inline constexpr quic::ParsedQuicVersionVector GetMoqtSupportedQuicVersions() {
return quic::ParsedQuicVersionVector{quic::ParsedQuicVersion::RFCv1()};
}
enum class MoqtVersion : uint64_t {
kDraft07 = 0xff000007,
kUnrecognizedVersionForTests = 0xfe0000ff,
};
inline constexpr MoqtVersion kDefaultMoqtVersion = MoqtVersion::kDraft07;
inline constexpr uint64_t kDefaultInitialMaxSubscribeId = 100;
inline constexpr uint64_t kMinNamespaceElements = 1;
inline constexpr uint64_t kMaxNamespaceElements = 32;
struct QUICHE_EXPORT MoqtSessionParameters {
// TODO: support multiple versions.
// TODO: support roles other than PubSub.
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)) {}
MoqtVersion version = kDefaultMoqtVersion;
quic::Perspective perspective;
bool using_webtrans;
std::string path;
uint64_t max_subscribe_id = kDefaultInitialMaxSubscribeId;
bool deliver_partial_objects = false;
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 {
kObjectDatagram = 0x01,
kStreamHeaderSubgroup = 0x04,
kStreamHeaderFetch = 0x05,
// Currently QUICHE-specific. All data on a kPadding stream is ignored.
kPadding = 0x26d3,
};
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,
kMaxSubscribeId = 0x15,
kFetch = 0x16,
kFetchCancel = 0x17,
kFetchOk = 0x18,
kFetchError = 0x19,
kSubscribesBlocked = 0x1a,
kClientSetup = 0x40,
kServerSetup = 0x41,
// 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,
kDuplicateTrackAlias = 0x4,
kParameterLengthMismatch = 0x5,
kTooManySubscribes = 0x6,
kGoawayTimeout = 0x10,
kControlMessageTimeout = 0x11,
kDataStreamTimeout = 0x12,
};
// 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 MoqtSetupParameter : uint64_t {
kRole = 0x0,
kPath = 0x1,
kMaxSubscribeId = 0x2,
// QUICHE-specific extensions.
// Indicates support for OACK messages.
kSupportObjectAcks = 0xbbf1439,
};
enum class QUICHE_EXPORT MoqtTrackRequestParameter : uint64_t {
kAuthorizationInfo = 0x2,
kDeliveryTimeout = 0x3,
kMaxCacheDuration = 0x4,
// QUICHE-specific extensions.
kOackWindowSize = 0xbbf1439,
};
// TODO: those are non-standard; add standard error codes once those exist, see
// <https://github.com/moq-wg/moq-transport/issues/393>.
enum class MoqtAnnounceErrorCode : uint64_t {
kInternalError = 0,
kAnnounceNotSupported = 1,
kNotASubscribedNamespace = 2,
};
enum class QUICHE_EXPORT SubscribeErrorCode : uint64_t {
kInternalError = 0x0,
kInvalidRange = 0x1,
kRetryTrackAlias = 0x2,
kTrackDoesNotExist = 0x3,
kUnauthorized = 0x4,
kTimeout = 0x5,
};
struct MoqtSubscribeErrorReason {
SubscribeErrorCode error_code;
std::string reason_phrase;
};
struct MoqtAnnounceErrorReason {
MoqtAnnounceErrorCode error_code;
std::string reason_phrase;
};
// Full track name represents a tuple of name elements. All higher order
// elements MUST be present, but lower-order ones (like the name) can be
// omitted.
class FullTrackName {
public:
explicit FullTrackName(absl::Span<const absl::string_view> elements);
explicit FullTrackName(
std::initializer_list<const absl::string_view> elements)
: FullTrackName(absl::Span<const absl::string_view>(
std::data(elements), std::size(elements))) {
QUICHE_BUG_IF(Moqt_namespace_too_large_02,
elements.size() > (kMaxNamespaceElements + 1))
<< "Constructing a namespace that is too large.";
}
explicit FullTrackName(absl::string_view ns, absl::string_view name)
: FullTrackName({ns, name}) {}
FullTrackName() : FullTrackName({}) {}
std::string ToString() const;
void AddElement(absl::string_view element) {
QUICHE_BUG_IF(Moqt_namespace_too_large_01,
tuple_.size() > (kMaxNamespaceElements + 1))
<< "Constructing a namespace that is too large.";
tuple_.push_back(std::string(element));
}
// Remove the last element to convert a name to a namespace.
void NameToNamespace() { tuple_.pop_back(); }
// returns true is |this| is a subdomain of |other|.
bool InNamespace(const FullTrackName& other) const {
if (tuple_.size() < other.tuple_.size()) {
return false;
}
for (int i = 0; i < other.tuple_.size(); ++i) {
if (tuple_[i] != other.tuple_[i]) {
return false;
}
}
return true;
}
absl::Span<const std::string> tuple() const {
return absl::MakeConstSpan(tuple_);
}
bool operator==(const FullTrackName& other) const;
bool operator<(const FullTrackName& other) const;
template <typename H>
friend H AbslHashValue(H h, const FullTrackName& m) {
return H::combine(std::move(h), m.tuple_);
}
template <typename Sink>
friend void AbslStringify(Sink& sink, const FullTrackName& track_name) {
sink.Append(track_name.ToString());
}
bool empty() const { return tuple_.empty(); }
private:
absl::InlinedVector<std::string, 2> tuple_;
};
// These are absolute sequence numbers.
struct FullSequence {
uint64_t group;
uint64_t subgroup;
uint64_t object;
FullSequence() : FullSequence(0, 0) {}
// There is a lot of code from before subgroups. Assume there's one subgroup
// with ID 0 per group.
FullSequence(uint64_t group, uint64_t object)
: FullSequence(group, 0, object) {}
FullSequence(uint64_t group, uint64_t subgroup, uint64_t object)
: group(group), subgroup(subgroup), object(object) {}
bool operator==(const FullSequence& other) const {
return group == other.group && object == other.object;
}
// These are temporal ordering comparisons, so subgroup ID doesn't matter.
bool operator<(const FullSequence& other) const {
return group < other.group ||
(group == other.group && object < other.object);
}
bool operator<=(const FullSequence& other) const {
return (group < other.group ||
(group == other.group && object <= other.object));
}
bool operator>(const FullSequence& other) const { return !(*this <= other); }
FullSequence& operator=(FullSequence other) {
group = other.group;
subgroup = other.subgroup;
object = other.object;
return *this;
}
FullSequence next() const {
return FullSequence{group, subgroup, object + 1};
}
template <typename H>
friend H AbslHashValue(H h, const FullSequence& m);
template <typename Sink>
friend void AbslStringify(Sink& sink, const FullSequence& 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 FullSequence& m) {
return H::combine(std::move(h), m.group, m.object);
}
struct QUICHE_EXPORT MoqtClientSetup {
std::vector<MoqtVersion> supported_versions;
std::optional<std::string> path;
std::optional<uint64_t> max_subscribe_id;
bool supports_object_ack = false;
};
struct QUICHE_EXPORT MoqtServerSetup {
MoqtVersion selected_version;
std::optional<uint64_t> max_subscribe_id;
bool supports_object_ack = false;
};
// 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,
kEndOfTrack = 0x4,
kEndOfSubgroup = 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;
MoqtObjectStatus object_status;
std::optional<uint64_t> subgroup_id;
uint64_t payload_length;
};
enum class QUICHE_EXPORT MoqtFilterType : uint64_t {
kNone = 0x0,
kLatestGroup = 0x1,
kLatestObject = 0x2,
kAbsoluteStart = 0x3,
kAbsoluteRange = 0x4,
};
struct QUICHE_EXPORT MoqtSubscribeParameters {
std::optional<std::string> authorization_info;
std::optional<quic::QuicTimeDelta> delivery_timeout;
std::optional<quic::QuicTimeDelta> max_cache_duration;
// If present, indicates that OBJECT_ACK messages will be sent in response to
// the objects on the stream. The actual value is informational, and it
// communicates how many frames the subscriber is willing to buffer, in
// microseconds.
std::optional<quic::QuicTimeDelta> object_ack_window;
bool operator==(const MoqtSubscribeParameters& other) const {
return authorization_info == other.authorization_info &&
delivery_timeout == other.delivery_timeout &&
max_cache_duration == other.max_cache_duration &&
object_ack_window == other.object_ack_window;
}
};
struct QUICHE_EXPORT MoqtSubscribe {
uint64_t subscribe_id;
uint64_t track_alias;
FullTrackName full_track_name;
MoqtPriority subscriber_priority;
std::optional<MoqtDeliveryOrder> group_order;
// The combinations of these that have values indicate the filter type.
// SG: Start Group; SO: Start Object; EG: End Group; EO: End Object;
// (none): KLatestObject
// SO: kLatestGroup (must be zero)
// SG, SO: kAbsoluteStart
// SG, SO, EG, EO: kAbsoluteRange
// SG, SO, EG: kAbsoluteRange (request whole last group)
// All other combinations are invalid.
std::optional<uint64_t> start_group;
std::optional<uint64_t> start_object;
std::optional<uint64_t> end_group;
std::optional<uint64_t> end_object;
// If the mode is kNone, the these are std::nullopt.
MoqtSubscribeParameters parameters;
};
// Deduce the filter type from the combination of group and object IDs. Returns
// kNone if the state of the subscribe is invalid.
MoqtFilterType GetFilterType(const MoqtSubscribe& message);
struct QUICHE_EXPORT MoqtSubscribeOk {
uint64_t subscribe_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<FullSequence> largest_id;
MoqtSubscribeParameters parameters;
};
struct QUICHE_EXPORT MoqtSubscribeError {
uint64_t subscribe_id;
SubscribeErrorCode 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 {
kUnsubscribed = 0x0,
kInternalError = 0x1,
kUnauthorized = 0x2,
kTrackEnded = 0x3,
kSubscriptionEnded = 0x4,
kGoingAway = 0x5,
kExpired = 0x6,
};
struct QUICHE_EXPORT MoqtSubscribeDone {
uint64_t subscribe_id;
SubscribeDoneCode status_code;
std::string reason_phrase;
std::optional<FullSequence> final_id;
};
struct QUICHE_EXPORT MoqtSubscribeUpdate {
uint64_t subscribe_id;
uint64_t start_group;
uint64_t start_object;
std::optional<uint64_t> end_group;
std::optional<uint64_t> end_object;
MoqtPriority subscriber_priority;
MoqtSubscribeParameters parameters;
};
struct QUICHE_EXPORT MoqtAnnounce {
FullTrackName track_namespace;
MoqtSubscribeParameters parameters;
};
struct QUICHE_EXPORT MoqtAnnounceOk {
FullTrackName track_namespace;
};
struct QUICHE_EXPORT MoqtAnnounceError {
FullTrackName track_namespace;
MoqtAnnounceErrorCode error_code;
std::string reason_phrase;
};
struct QUICHE_EXPORT MoqtUnannounce {
FullTrackName 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;
};
struct QUICHE_EXPORT MoqtAnnounceCancel {
FullTrackName track_namespace;
MoqtAnnounceErrorCode error_code;
std::string reason_phrase;
};
struct QUICHE_EXPORT MoqtTrackStatusRequest {
FullTrackName full_track_name;
};
struct QUICHE_EXPORT MoqtGoAway {
std::string new_session_uri;
};
struct QUICHE_EXPORT MoqtSubscribeAnnounces {
FullTrackName track_namespace;
MoqtSubscribeParameters parameters;
};
struct QUICHE_EXPORT MoqtSubscribeAnnouncesOk {
FullTrackName track_namespace;
};
struct QUICHE_EXPORT MoqtSubscribeAnnouncesError {
FullTrackName track_namespace;
SubscribeErrorCode error_code;
std::string reason_phrase;
};
struct QUICHE_EXPORT MoqtUnsubscribeAnnounces {
FullTrackName track_namespace;
};
struct QUICHE_EXPORT MoqtMaxSubscribeId {
uint64_t max_subscribe_id;
};
struct QUICHE_EXPORT MoqtFetch {
uint64_t subscribe_id;
FullTrackName full_track_name;
MoqtPriority subscriber_priority;
std::optional<MoqtDeliveryOrder> group_order;
FullSequence start_object; // subgroup is ignored
uint64_t end_group;
std::optional<uint64_t> end_object;
MoqtSubscribeParameters parameters;
};
struct QUICHE_EXPORT MoqtFetchCancel {
uint64_t subscribe_id;
};
struct QUICHE_EXPORT MoqtFetchOk {
uint64_t subscribe_id;
MoqtDeliveryOrder group_order;
FullSequence largest_id; // subgroup is ignored
MoqtSubscribeParameters parameters;
};
struct QUICHE_EXPORT MoqtFetchError {
uint64_t subscribe_id;
SubscribeErrorCode error_code;
std::string reason_phrase;
};
struct QUICHE_EXPORT MoqtSubscribesBlocked {
uint64_t max_subscribe_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();
};
std::string MoqtMessageTypeToString(MoqtMessageType message_type);
std::string MoqtDataStreamTypeToString(MoqtDataStreamType type);
std::string MoqtForwardingPreferenceToString(
MoqtForwardingPreference preference);
MoqtForwardingPreference GetForwardingPreference(MoqtDataStreamType type);
MoqtDataStreamType GetMessageTypeForForwardingPreference(
MoqtForwardingPreference preference);
absl::Status MoqtStreamErrorToStatus(webtransport::StreamErrorCode error_code,
absl::string_view reason_phrase);
} // namespace moqt
#endif // QUICHE_QUIC_MOQT_MOQT_MESSAGES_H_