blob: 7e86e0d4222ad2df946b43650f9ab7d3a184a14d [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.
#include "quiche/quic/moqt/moqt_framer.h"
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <optional>
#include <string>
#include <type_traits>
#include <utility>
#include "absl/container/inlined_vector.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "quiche/quic/core/quic_data_writer.h"
#include "quiche/quic/core/quic_time.h"
#include "quiche/quic/moqt/moqt_messages.h"
#include "quiche/quic/moqt/moqt_priority.h"
#include "quiche/quic/platform/api/quic_bug_tracker.h"
#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/quiche_buffer_allocator.h"
#include "quiche/common/quiche_data_writer.h"
#include "quiche/common/simple_buffer_allocator.h"
#include "quiche/common/wire_serialization.h"
namespace moqt {
namespace {
using ::quiche::QuicheBuffer;
using ::quiche::WireBytes;
using ::quiche::WireOptional;
using ::quiche::WireSpan;
using ::quiche::WireStringWithVarInt62Length;
using ::quiche::WireUint8;
using ::quiche::WireVarInt62;
// Encoding for string parameters as described in
// https://moq-wg.github.io/moq-transport/draft-ietf-moq-transport.html#name-parameters
struct StringParameter {
template <typename Enum>
StringParameter(Enum type, absl::string_view data)
: type(static_cast<uint64_t>(type)), data(data) {
static_assert(std::is_enum_v<Enum>);
}
uint64_t type;
absl::string_view data;
};
class WireStringParameter {
public:
using DataType = StringParameter;
explicit WireStringParameter(const StringParameter& parameter)
: parameter_(parameter) {}
size_t GetLengthOnWire() {
return quiche::ComputeLengthOnWire(
WireVarInt62(parameter_.type),
WireStringWithVarInt62Length(parameter_.data));
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
return quiche::SerializeIntoWriter(
writer, WireVarInt62(parameter_.type),
WireStringWithVarInt62Length(parameter_.data));
}
private:
const StringParameter& parameter_;
};
// Encoding for integer parameters as described in
// https://moq-wg.github.io/moq-transport/draft-ietf-moq-transport.html#name-parameters
struct IntParameter {
template <typename Enum, typename Param>
IntParameter(Enum type, Param value)
: type(static_cast<uint64_t>(type)), value(static_cast<uint64_t>(value)) {
static_assert(std::is_enum_v<Enum>);
static_assert(std::is_enum_v<Param> || std::is_unsigned_v<Param>);
}
uint64_t type;
uint64_t value;
};
class WireIntParameter {
public:
using DataType = IntParameter;
explicit WireIntParameter(const IntParameter& parameter)
: parameter_(parameter) {}
size_t GetLengthOnWire() {
return quiche::ComputeLengthOnWire(
WireVarInt62(parameter_.type),
WireVarInt62(NeededVarIntLen(parameter_.value)),
WireVarInt62(parameter_.value));
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
return quiche::SerializeIntoWriter(
writer, WireVarInt62(parameter_.type),
WireVarInt62(NeededVarIntLen(parameter_.value)),
WireVarInt62(parameter_.value));
}
private:
size_t NeededVarIntLen(const uint64_t value) {
return static_cast<size_t>(quic::QuicDataWriter::GetVarInt62Len(value));
}
const IntParameter& parameter_;
};
class WireSubscribeParameterList {
public:
explicit WireSubscribeParameterList(const MoqtSubscribeParameters& list)
: list_(list) {}
size_t GetLengthOnWire() {
auto string_parameters = StringParameters();
auto int_parameters = IntParameters();
return quiche::ComputeLengthOnWire(
WireVarInt62(string_parameters.size() + int_parameters.size()),
WireSpan<WireStringParameter>(string_parameters),
WireSpan<WireIntParameter>(int_parameters));
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
auto string_parameters = StringParameters();
auto int_parameters = IntParameters();
return quiche::SerializeIntoWriter(
writer, WireVarInt62(string_parameters.size() + int_parameters.size()),
WireSpan<WireStringParameter>(string_parameters),
WireSpan<WireIntParameter>(int_parameters));
}
private:
absl::InlinedVector<StringParameter, 1> StringParameters() const {
absl::InlinedVector<StringParameter, 1> result;
if (list_.authorization_info.has_value()) {
result.push_back(
StringParameter(MoqtTrackRequestParameter::kAuthorizationInfo,
*list_.authorization_info));
}
return result;
}
absl::InlinedVector<IntParameter, 3> IntParameters() const {
absl::InlinedVector<IntParameter, 3> result;
if (list_.delivery_timeout.has_value()) {
QUICHE_DCHECK_GE(*list_.delivery_timeout, quic::QuicTimeDelta::Zero());
result.push_back(IntParameter(
MoqtTrackRequestParameter::kDeliveryTimeout,
static_cast<uint64_t>(list_.delivery_timeout->ToMilliseconds())));
}
if (list_.max_cache_duration.has_value()) {
QUICHE_DCHECK_GE(*list_.max_cache_duration, quic::QuicTimeDelta::Zero());
result.push_back(IntParameter(
MoqtTrackRequestParameter::kMaxCacheDuration,
static_cast<uint64_t>(list_.max_cache_duration->ToMilliseconds())));
}
if (list_.object_ack_window.has_value()) {
QUICHE_DCHECK_GE(*list_.object_ack_window, quic::QuicTimeDelta::Zero());
result.push_back(IntParameter(
MoqtTrackRequestParameter::kOackWindowSize,
static_cast<uint64_t>(list_.object_ack_window->ToMicroseconds())));
}
return result;
}
const MoqtSubscribeParameters& list_;
};
class WireFullTrackName {
public:
using DataType = FullTrackName;
// If |includes_name| is true, the last element in the tuple is the track
// name and is therefore not counted in the prefix of the namespace tuple.
WireFullTrackName(const FullTrackName& name, bool includes_name)
: name_(name), includes_name_(includes_name) {}
size_t GetLengthOnWire() {
return quiche::ComputeLengthOnWire(
WireVarInt62(num_elements()),
WireSpan<WireStringWithVarInt62Length, std::string>(name_.tuple()));
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
return quiche::SerializeIntoWriter(
writer, WireVarInt62(num_elements()),
WireSpan<WireStringWithVarInt62Length, std::string>(name_.tuple()));
}
private:
size_t num_elements() const {
return includes_name_ ? (name_.tuple().size() - 1) : name_.tuple().size();
}
const FullTrackName& name_;
const bool includes_name_;
};
// Serializes data into buffer using the default allocator. Invokes QUICHE_BUG
// on failure.
template <typename... Ts>
QuicheBuffer Serialize(Ts... data) {
absl::StatusOr<QuicheBuffer> buffer = quiche::SerializeIntoBuffer(
quiche::SimpleBufferAllocator::Get(), data...);
if (!buffer.ok()) {
QUICHE_BUG(moqt_failed_serialization)
<< "Failed to serialize MoQT frame: " << buffer.status();
return QuicheBuffer();
}
return *std::move(buffer);
}
// Serializes data into buffer using the default allocator. Invokes QUICHE_BUG
// on failure.
template <typename... Ts>
QuicheBuffer SerializeControlMessage(MoqtMessageType type, Ts... data) {
uint64_t message_type = static_cast<uint64_t>(type);
size_t payload_size = quiche::ComputeLengthOnWire(data...);
size_t buffer_size =
payload_size + quiche::ComputeLengthOnWire(WireVarInt62(message_type),
WireVarInt62(payload_size));
if (buffer_size == 0) {
return QuicheBuffer();
}
QuicheBuffer buffer(quiche::SimpleBufferAllocator::Get(), buffer_size);
quiche::QuicheDataWriter writer(buffer.size(), buffer.data());
absl::Status status = SerializeIntoWriter(
writer, WireVarInt62(message_type), WireVarInt62(payload_size), data...);
if (!status.ok() || writer.remaining() != 0) {
QUICHE_BUG(moqt_failed_serialization)
<< "Failed to serialize MoQT frame: " << status;
return QuicheBuffer();
}
return buffer;
}
WireUint8 WireDeliveryOrder(std::optional<MoqtDeliveryOrder> delivery_order) {
if (!delivery_order.has_value()) {
return WireUint8(0x00);
}
switch (*delivery_order) {
case MoqtDeliveryOrder::kAscending:
return WireUint8(0x01);
case MoqtDeliveryOrder::kDescending:
return WireUint8(0x02);
}
QUICHE_NOTREACHED();
return WireUint8(0xff);
}
uint64_t SignedVarintSerializedForm(int64_t value) {
if (value < 0) {
return ((-value) << 1) | 0x01;
}
return value << 1;
}
} // namespace
quiche::QuicheBuffer MoqtFramer::SerializeObjectHeader(
const MoqtObject& message, MoqtDataStreamType message_type,
bool is_first_in_stream) {
if (!ValidateObjectMetadata(message, message_type)) {
QUIC_BUG(quic_bug_serialize_object_header_01)
<< "Object metadata is invalid";
return quiche::QuicheBuffer();
}
if (message_type == MoqtDataStreamType::kObjectDatagram) {
QUIC_BUG(quic_bug_serialize_object_header_02)
<< "Datagrams use SerializeObjectDatagram()";
return quiche::QuicheBuffer();
}
if (!is_first_in_stream) {
switch (message_type) {
case MoqtDataStreamType::kStreamHeaderSubgroup:
return (message.payload_length == 0)
? Serialize(WireVarInt62(message.object_id),
WireVarInt62(message.payload_length),
WireVarInt62(static_cast<uint64_t>(
message.object_status)))
: Serialize(WireVarInt62(message.object_id),
WireVarInt62(message.payload_length));
case MoqtDataStreamType::kStreamHeaderFetch:
return (message.payload_length == 0)
? Serialize(WireVarInt62(message.group_id),
WireVarInt62(*message.subgroup_id),
WireVarInt62(message.object_id),
WireUint8(message.publisher_priority),
WireVarInt62(message.payload_length),
WireVarInt62(static_cast<uint64_t>(
message.object_status)))
: Serialize(WireVarInt62(message.group_id),
WireVarInt62(*message.subgroup_id),
WireVarInt62(message.object_id),
WireUint8(message.publisher_priority),
WireVarInt62(message.payload_length));
default:
QUICHE_NOTREACHED();
return quiche::QuicheBuffer();
}
}
switch (message_type) {
case MoqtDataStreamType::kStreamHeaderSubgroup:
return (message.payload_length == 0)
? Serialize(WireVarInt62(message_type),
WireVarInt62(message.track_alias),
WireVarInt62(message.group_id),
WireVarInt62(*message.subgroup_id),
WireUint8(message.publisher_priority),
WireVarInt62(message.object_id),
WireVarInt62(message.payload_length),
WireVarInt62(message.object_status))
: Serialize(WireVarInt62(message_type),
WireVarInt62(message.track_alias),
WireVarInt62(message.group_id),
WireVarInt62(*message.subgroup_id),
WireUint8(message.publisher_priority),
WireVarInt62(message.object_id),
WireVarInt62(message.payload_length));
case MoqtDataStreamType::kStreamHeaderFetch:
return (message.payload_length == 0)
? Serialize(WireVarInt62(message_type),
WireVarInt62(message.track_alias),
WireVarInt62(message.group_id),
WireVarInt62(*message.subgroup_id),
WireVarInt62(message.object_id),
WireUint8(message.publisher_priority),
WireVarInt62(message.payload_length),
WireVarInt62(message.object_status))
: Serialize(WireVarInt62(message_type),
WireVarInt62(message.track_alias),
WireVarInt62(message.group_id),
WireVarInt62(*message.subgroup_id),
WireVarInt62(message.object_id),
WireUint8(message.publisher_priority),
WireVarInt62(message.payload_length));
default:
QUICHE_NOTREACHED();
return quiche::QuicheBuffer();
}
}
quiche::QuicheBuffer MoqtFramer::SerializeObjectDatagram(
const MoqtObject& message, absl::string_view payload) {
if (!ValidateObjectMetadata(message, MoqtDataStreamType::kObjectDatagram)) {
QUIC_BUG(quic_bug_serialize_object_datagram_01)
<< "Object metadata is invalid";
return quiche::QuicheBuffer();
}
if (message.payload_length != payload.length()) {
QUIC_BUG(quic_bug_serialize_object_datagram_03)
<< "Payload length does not match payload";
return quiche::QuicheBuffer();
}
if (message.payload_length == 0) {
return Serialize(
WireVarInt62(MoqtDataStreamType::kObjectDatagram),
WireVarInt62(message.track_alias), WireVarInt62(message.group_id),
WireVarInt62(message.object_id), WireUint8(message.publisher_priority),
WireVarInt62(message.payload_length),
WireVarInt62(message.object_status));
}
return Serialize(
WireVarInt62(MoqtDataStreamType::kObjectDatagram),
WireVarInt62(message.track_alias), WireVarInt62(message.group_id),
WireVarInt62(message.object_id), WireUint8(message.publisher_priority),
WireVarInt62(message.payload_length), WireBytes(payload));
}
quiche::QuicheBuffer MoqtFramer::SerializeClientSetup(
const MoqtClientSetup& message) {
absl::InlinedVector<IntParameter, 1> int_parameters;
absl::InlinedVector<StringParameter, 1> string_parameters;
if (message.max_subscribe_id.has_value()) {
int_parameters.push_back(IntParameter(MoqtSetupParameter::kMaxSubscribeId,
*message.max_subscribe_id));
}
if (message.supports_object_ack) {
int_parameters.push_back(
IntParameter(MoqtSetupParameter::kSupportObjectAcks, 1u));
}
if (!using_webtrans_ && message.path.has_value()) {
string_parameters.push_back(
StringParameter(MoqtSetupParameter::kPath, *message.path));
}
return SerializeControlMessage(
MoqtMessageType::kClientSetup,
WireVarInt62(message.supported_versions.size()),
WireSpan<WireVarInt62, MoqtVersion>(message.supported_versions),
WireVarInt62(string_parameters.size() + int_parameters.size()),
WireSpan<WireIntParameter>(int_parameters),
WireSpan<WireStringParameter>(string_parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeServerSetup(
const MoqtServerSetup& message) {
absl::InlinedVector<IntParameter, 1> int_parameters;
if (message.max_subscribe_id.has_value()) {
int_parameters.push_back(IntParameter(MoqtSetupParameter::kMaxSubscribeId,
*message.max_subscribe_id));
}
if (message.supports_object_ack) {
int_parameters.push_back(
IntParameter(MoqtSetupParameter::kSupportObjectAcks, 1u));
}
return SerializeControlMessage(MoqtMessageType::kServerSetup,
WireVarInt62(message.selected_version),
WireVarInt62(int_parameters.size()),
WireSpan<WireIntParameter>(int_parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribe(
const MoqtSubscribe& message) {
MoqtFilterType filter_type = GetFilterType(message);
if (filter_type == MoqtFilterType::kNone) {
QUICHE_BUG(MoqtFramer_invalid_subscribe) << "Invalid object range";
return quiche::QuicheBuffer();
}
switch (filter_type) {
case MoqtFilterType::kLatestGroup:
case MoqtFilterType::kLatestObject:
return SerializeControlMessage(
MoqtMessageType::kSubscribe, WireVarInt62(message.subscribe_id),
WireVarInt62(message.track_alias),
WireFullTrackName(message.full_track_name, true),
WireUint8(message.subscriber_priority),
WireDeliveryOrder(message.group_order), WireVarInt62(filter_type),
WireSubscribeParameterList(message.parameters));
case MoqtFilterType::kAbsoluteStart:
return SerializeControlMessage(
MoqtMessageType::kSubscribe, WireVarInt62(message.subscribe_id),
WireVarInt62(message.track_alias),
WireFullTrackName(message.full_track_name, true),
WireUint8(message.subscriber_priority),
WireDeliveryOrder(message.group_order), WireVarInt62(filter_type),
WireVarInt62(*message.start_group),
WireVarInt62(*message.start_object),
WireSubscribeParameterList(message.parameters));
case MoqtFilterType::kAbsoluteRange:
return SerializeControlMessage(
MoqtMessageType::kSubscribe, WireVarInt62(message.subscribe_id),
WireVarInt62(message.track_alias),
WireFullTrackName(message.full_track_name, true),
WireUint8(message.subscriber_priority),
WireDeliveryOrder(message.group_order), WireVarInt62(filter_type),
WireVarInt62(*message.start_group),
WireVarInt62(*message.start_object), WireVarInt62(*message.end_group),
WireVarInt62(message.end_object.has_value() ? *message.end_object + 1
: 0),
WireSubscribeParameterList(message.parameters));
default:
QUICHE_BUG(MoqtFramer_end_group_missing) << "Subscribe framing error.";
return quiche::QuicheBuffer();
}
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeOk(
const MoqtSubscribeOk& message) {
if (message.parameters.authorization_info.has_value()) {
QUICHE_BUG(MoqtFramer_invalid_subscribe_ok)
<< "SUBSCRIBE_OK with delivery timeout";
}
if (message.largest_id.has_value()) {
return SerializeControlMessage(
MoqtMessageType::kSubscribeOk, WireVarInt62(message.subscribe_id),
WireVarInt62(message.expires.ToMilliseconds()),
WireDeliveryOrder(message.group_order), WireUint8(1),
WireVarInt62(message.largest_id->group),
WireVarInt62(message.largest_id->object),
WireSubscribeParameterList(message.parameters));
}
return SerializeControlMessage(
MoqtMessageType::kSubscribeOk, WireVarInt62(message.subscribe_id),
WireVarInt62(message.expires.ToMilliseconds()),
WireDeliveryOrder(message.group_order), WireUint8(0),
WireSubscribeParameterList(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeError(
const MoqtSubscribeError& message) {
return SerializeControlMessage(
MoqtMessageType::kSubscribeError, WireVarInt62(message.subscribe_id),
WireVarInt62(message.error_code),
WireStringWithVarInt62Length(message.reason_phrase),
WireVarInt62(message.track_alias));
}
quiche::QuicheBuffer MoqtFramer::SerializeUnsubscribe(
const MoqtUnsubscribe& message) {
return SerializeControlMessage(MoqtMessageType::kUnsubscribe,
WireVarInt62(message.subscribe_id));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeDone(
const MoqtSubscribeDone& message) {
if (message.final_id.has_value()) {
return SerializeControlMessage(
MoqtMessageType::kSubscribeDone, WireVarInt62(message.subscribe_id),
WireVarInt62(message.status_code),
WireStringWithVarInt62Length(message.reason_phrase), WireUint8(1),
WireVarInt62(message.final_id->group),
WireVarInt62(message.final_id->object));
}
return SerializeControlMessage(
MoqtMessageType::kSubscribeDone, WireVarInt62(message.subscribe_id),
WireVarInt62(message.status_code),
WireStringWithVarInt62Length(message.reason_phrase), WireUint8(0));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeUpdate(
const MoqtSubscribeUpdate& message) {
if (message.parameters.authorization_info.has_value()) {
QUICHE_BUG(MoqtFramer_invalid_subscribe_update)
<< "SUBSCRIBE_UPDATE with authorization info";
}
uint64_t end_group =
message.end_group.has_value() ? *message.end_group + 1 : 0;
uint64_t end_object =
message.end_object.has_value() ? *message.end_object + 1 : 0;
if (end_group == 0 && end_object != 0) {
QUICHE_BUG(MoqtFramer_invalid_subscribe_update) << "Invalid object range";
return quiche::QuicheBuffer();
}
return SerializeControlMessage(
MoqtMessageType::kSubscribeUpdate, WireVarInt62(message.subscribe_id),
WireVarInt62(message.start_group), WireVarInt62(message.start_object),
WireVarInt62(end_group), WireVarInt62(end_object),
WireUint8(message.subscriber_priority),
WireSubscribeParameterList(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeAnnounce(
const MoqtAnnounce& message) {
if (message.parameters.delivery_timeout.has_value()) {
QUICHE_BUG(MoqtFramer_invalid_announce) << "ANNOUNCE with delivery timeout";
}
return SerializeControlMessage(
MoqtMessageType::kAnnounce,
WireFullTrackName(message.track_namespace, false),
WireSubscribeParameterList(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeAnnounceOk(
const MoqtAnnounceOk& message) {
return SerializeControlMessage(
MoqtMessageType::kAnnounceOk,
WireFullTrackName(message.track_namespace, false));
}
quiche::QuicheBuffer MoqtFramer::SerializeAnnounceError(
const MoqtAnnounceError& message) {
return SerializeControlMessage(
MoqtMessageType::kAnnounceError,
WireFullTrackName(message.track_namespace, false),
WireVarInt62(message.error_code),
WireStringWithVarInt62Length(message.reason_phrase));
}
quiche::QuicheBuffer MoqtFramer::SerializeAnnounceCancel(
const MoqtAnnounceCancel& message) {
return SerializeControlMessage(
MoqtMessageType::kAnnounceCancel,
WireFullTrackName(message.track_namespace, false),
WireVarInt62(message.error_code),
WireStringWithVarInt62Length(message.reason_phrase));
}
quiche::QuicheBuffer MoqtFramer::SerializeTrackStatusRequest(
const MoqtTrackStatusRequest& message) {
return SerializeControlMessage(
MoqtMessageType::kTrackStatusRequest,
WireFullTrackName(message.full_track_name, true));
}
quiche::QuicheBuffer MoqtFramer::SerializeUnannounce(
const MoqtUnannounce& message) {
return SerializeControlMessage(
MoqtMessageType::kUnannounce,
WireFullTrackName(message.track_namespace, false));
}
quiche::QuicheBuffer MoqtFramer::SerializeTrackStatus(
const MoqtTrackStatus& message) {
return SerializeControlMessage(
MoqtMessageType::kTrackStatus,
WireFullTrackName(message.full_track_name, true),
WireVarInt62(message.status_code), WireVarInt62(message.last_group),
WireVarInt62(message.last_object));
}
quiche::QuicheBuffer MoqtFramer::SerializeGoAway(const MoqtGoAway& message) {
return SerializeControlMessage(
MoqtMessageType::kGoAway,
WireStringWithVarInt62Length(message.new_session_uri));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeAnnounces(
const MoqtSubscribeAnnounces& message) {
return SerializeControlMessage(
MoqtMessageType::kSubscribeAnnounces,
WireFullTrackName(message.track_namespace, false),
WireSubscribeParameterList(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeAnnouncesOk(
const MoqtSubscribeAnnouncesOk& message) {
return SerializeControlMessage(
MoqtMessageType::kSubscribeAnnouncesOk,
WireFullTrackName(message.track_namespace, false));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeAnnouncesError(
const MoqtSubscribeAnnouncesError& message) {
return SerializeControlMessage(
MoqtMessageType::kSubscribeAnnouncesError,
WireFullTrackName(message.track_namespace, false),
WireVarInt62(message.error_code),
WireStringWithVarInt62Length(message.reason_phrase));
}
quiche::QuicheBuffer MoqtFramer::SerializeUnsubscribeAnnounces(
const MoqtUnsubscribeAnnounces& message) {
return SerializeControlMessage(
MoqtMessageType::kUnsubscribeAnnounces,
WireFullTrackName(message.track_namespace, false));
}
quiche::QuicheBuffer MoqtFramer::SerializeMaxSubscribeId(
const MoqtMaxSubscribeId& message) {
return SerializeControlMessage(MoqtMessageType::kMaxSubscribeId,
WireVarInt62(message.max_subscribe_id));
}
quiche::QuicheBuffer MoqtFramer::SerializeFetch(const MoqtFetch& message) {
if (message.end_group < message.start_object.group ||
(message.end_group == message.start_object.group &&
message.end_object.has_value() &&
*message.end_object < message.start_object.object)) {
QUICHE_BUG(MoqtFramer_invalid_fetch) << "Invalid FETCH object range";
return quiche::QuicheBuffer();
}
return SerializeControlMessage(
MoqtMessageType::kFetch, WireVarInt62(message.subscribe_id),
WireFullTrackName(message.full_track_name, true),
WireUint8(message.subscriber_priority),
WireDeliveryOrder(message.group_order),
WireVarInt62(message.start_object.group),
WireVarInt62(message.start_object.object),
WireVarInt62(message.end_group),
WireVarInt62(message.end_object.has_value() ? *message.end_object + 1
: 0),
WireSubscribeParameterList(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeFetchCancel(
const MoqtFetchCancel& message) {
return SerializeControlMessage(MoqtMessageType::kFetchCancel,
WireVarInt62(message.subscribe_id));
}
quiche::QuicheBuffer MoqtFramer::SerializeFetchOk(const MoqtFetchOk& message) {
return SerializeControlMessage(
MoqtMessageType::kFetchOk, WireVarInt62(message.subscribe_id),
WireDeliveryOrder(message.group_order),
WireVarInt62(message.largest_id.group),
WireVarInt62(message.largest_id.object),
WireSubscribeParameterList(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeFetchError(
const MoqtFetchError& message) {
return SerializeControlMessage(
MoqtMessageType::kFetchError, WireVarInt62(message.subscribe_id),
WireVarInt62(message.error_code),
WireStringWithVarInt62Length(message.reason_phrase));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribesBlocked(
const MoqtSubscribesBlocked& message) {
return SerializeControlMessage(MoqtMessageType::kSubscribesBlocked,
WireVarInt62(message.max_subscribe_id));
}
quiche::QuicheBuffer MoqtFramer::SerializeObjectAck(
const MoqtObjectAck& message) {
return SerializeControlMessage(
MoqtMessageType::kObjectAck, WireVarInt62(message.subscribe_id),
WireVarInt62(message.group_id), WireVarInt62(message.object_id),
WireVarInt62(SignedVarintSerializedForm(
message.delta_from_deadline.ToMicroseconds())));
}
// static
bool MoqtFramer::ValidateObjectMetadata(const MoqtObject& object,
MoqtDataStreamType message_type) {
if (object.object_status != MoqtObjectStatus::kNormal &&
object.payload_length > 0) {
return false;
}
if ((message_type == MoqtDataStreamType::kStreamHeaderSubgroup ||
message_type == MoqtDataStreamType::kStreamHeaderFetch) !=
object.subgroup_id.has_value()) {
return false;
}
return true;
}
} // namespace moqt