blob: fd9aeef7236def0fb1c89cd5e7eaa014c7f09248 [file] [log] [blame]
// 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 <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/platform/api/quic_bug_tracker.h"
#include "quiche/common/platform/api/quiche_bug_tracker.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_;
};
// 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);
}
} // namespace
quiche::QuicheBuffer MoqtFramer::SerializeObjectHeader(
const MoqtObject& message, bool is_first_in_stream) {
if (!message.payload_length.has_value() &&
!(message.forwarding_preference == MoqtForwardingPreference::kObject ||
message.forwarding_preference == MoqtForwardingPreference::kDatagram)) {
QUIC_BUG(quic_bug_serialize_object_input_01)
<< "Track or Group forwarding preference requires knowing the object "
"length in advance";
return quiche::QuicheBuffer();
}
if (message.object_status != MoqtObjectStatus::kNormal &&
message.payload_length.has_value() && *message.payload_length > 0) {
QUIC_BUG(quic_bug_serialize_object_input_03)
<< "Object status must be kNormal if payload is non-empty";
return quiche::QuicheBuffer();
}
if (!is_first_in_stream) {
switch (message.forwarding_preference) {
case MoqtForwardingPreference::kTrack:
return (*message.payload_length == 0)
? Serialize(WireVarInt62(message.group_id),
WireVarInt62(message.object_id),
WireVarInt62(*message.payload_length),
WireVarInt62(message.object_status))
: Serialize(WireVarInt62(message.group_id),
WireVarInt62(message.object_id),
WireVarInt62(*message.payload_length));
case MoqtForwardingPreference::kGroup:
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));
default:
QUIC_BUG(quic_bug_serialize_object_input_02)
<< "Object or Datagram forwarding_preference must be first in "
"stream";
return quiche::QuicheBuffer();
}
}
MoqtMessageType message_type =
GetMessageTypeForForwardingPreference(message.forwarding_preference);
switch (message.forwarding_preference) {
case MoqtForwardingPreference::kTrack:
return (*message.payload_length == 0)
? Serialize(WireVarInt62(message_type),
WireVarInt62(message.subscribe_id),
WireVarInt62(message.track_alias),
WireVarInt62(message.object_send_order),
WireVarInt62(message.group_id),
WireVarInt62(message.object_id),
WireVarInt62(*message.payload_length),
WireVarInt62(message.object_status))
: Serialize(WireVarInt62(message_type),
WireVarInt62(message.subscribe_id),
WireVarInt62(message.track_alias),
WireVarInt62(message.object_send_order),
WireVarInt62(message.group_id),
WireVarInt62(message.object_id),
WireVarInt62(*message.payload_length));
case MoqtForwardingPreference::kGroup:
return (*message.payload_length == 0)
? Serialize(WireVarInt62(message_type),
WireVarInt62(message.subscribe_id),
WireVarInt62(message.track_alias),
WireVarInt62(message.group_id),
WireVarInt62(message.object_send_order),
WireVarInt62(message.object_id),
WireVarInt62(*message.payload_length),
WireVarInt62(message.object_status))
: Serialize(WireVarInt62(message_type),
WireVarInt62(message.subscribe_id),
WireVarInt62(message.track_alias),
WireVarInt62(message.group_id),
WireVarInt62(message.object_send_order),
WireVarInt62(message.object_id),
WireVarInt62(*message.payload_length));
case MoqtForwardingPreference::kObject:
case MoqtForwardingPreference::kDatagram:
return Serialize(
WireVarInt62(message_type), WireVarInt62(message.subscribe_id),
WireVarInt62(message.track_alias), WireVarInt62(message.group_id),
WireVarInt62(message.object_id),
WireVarInt62(message.object_send_order),
WireVarInt62(message.object_status));
}
}
quiche::QuicheBuffer MoqtFramer::SerializeObjectDatagram(
const MoqtObject& message, absl::string_view payload) {
if (message.object_status != MoqtObjectStatus::kNormal && !payload.empty()) {
QUIC_BUG(quic_bug_serialize_object_datagram_01)
<< "Object status must be kNormal if payload is non-empty";
return quiche::QuicheBuffer();
}
return Serialize(
WireVarInt62(MoqtMessageType::kObjectDatagram),
WireVarInt62(message.subscribe_id), WireVarInt62(message.track_alias),
WireVarInt62(message.group_id), WireVarInt62(message.object_id),
WireVarInt62(message.object_send_order),
WireVarInt62(static_cast<uint64_t>(message.object_status)),
WireBytes(payload));
}
quiche::QuicheBuffer MoqtFramer::SerializeClientSetup(
const MoqtClientSetup& message) {
absl::InlinedVector<IntParameter, 1> int_parameters;
absl::InlinedVector<StringParameter, 1> string_parameters;
if (message.role.has_value()) {
int_parameters.push_back(
IntParameter(MoqtSetupParameter::kRole, *message.role));
}
if (!using_webtrans_ && message.path.has_value()) {
string_parameters.push_back(
StringParameter(MoqtSetupParameter::kPath, *message.path));
}
return Serialize(
WireVarInt62(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.role.has_value()) {
int_parameters.push_back(
IntParameter(MoqtSetupParameter::kRole, *message.role));
}
return Serialize(WireVarInt62(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();
}
absl::InlinedVector<StringParameter, 1> string_params;
if (message.authorization_info.has_value()) {
string_params.push_back(
StringParameter(MoqtTrackRequestParameter::kAuthorizationInfo,
*message.authorization_info));
}
switch (filter_type) {
case MoqtFilterType::kLatestGroup:
case MoqtFilterType::kLatestObject:
return Serialize(
WireVarInt62(MoqtMessageType::kSubscribe),
WireVarInt62(message.subscribe_id), WireVarInt62(message.track_alias),
WireStringWithVarInt62Length(message.track_namespace),
WireStringWithVarInt62Length(message.track_name),
WireVarInt62(filter_type), WireVarInt62(string_params.size()),
WireSpan<WireStringParameter>(string_params));
case MoqtFilterType::kAbsoluteStart:
return Serialize(
WireVarInt62(MoqtMessageType::kSubscribe),
WireVarInt62(message.subscribe_id), WireVarInt62(message.track_alias),
WireStringWithVarInt62Length(message.track_namespace),
WireStringWithVarInt62Length(message.track_name),
WireVarInt62(filter_type), WireVarInt62(*message.start_group),
WireVarInt62(*message.start_object),
WireVarInt62(string_params.size()),
WireSpan<WireStringParameter>(string_params));
case MoqtFilterType::kAbsoluteRange:
return Serialize(
WireVarInt62(MoqtMessageType::kSubscribe),
WireVarInt62(message.subscribe_id), WireVarInt62(message.track_alias),
WireStringWithVarInt62Length(message.track_namespace),
WireStringWithVarInt62Length(message.track_name),
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),
WireVarInt62(string_params.size()),
WireSpan<WireStringParameter>(string_params));
default:
QUICHE_BUG(MoqtFramer_end_group_missing) << "Subscribe framing error.";
return quiche::QuicheBuffer();
}
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeOk(
const MoqtSubscribeOk& message) {
if (message.largest_id.has_value()) {
return Serialize(WireVarInt62(MoqtMessageType::kSubscribeOk),
WireVarInt62(message.subscribe_id),
WireVarInt62(message.expires.ToMilliseconds()),
WireUint8(1), WireVarInt62(message.largest_id->group),
WireVarInt62(message.largest_id->object));
}
return Serialize(WireVarInt62(MoqtMessageType::kSubscribeOk),
WireVarInt62(message.subscribe_id),
WireVarInt62(message.expires.ToMilliseconds()),
WireUint8(0));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeError(
const MoqtSubscribeError& message) {
return Serialize(WireVarInt62(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 Serialize(WireVarInt62(MoqtMessageType::kUnsubscribe),
WireVarInt62(message.subscribe_id));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeDone(
const MoqtSubscribeDone& message) {
if (message.final_id.has_value()) {
return Serialize(WireVarInt62(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 Serialize(
WireVarInt62(MoqtMessageType::kSubscribeDone),
WireVarInt62(message.subscribe_id), WireVarInt62(message.status_code),
WireStringWithVarInt62Length(message.reason_phrase), WireUint8(0));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeUpdate(
const MoqtSubscribeUpdate& message) {
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();
}
absl::InlinedVector<StringParameter, 1> string_params;
if (message.authorization_info.has_value()) {
string_params.push_back(
StringParameter(MoqtTrackRequestParameter::kAuthorizationInfo,
*message.authorization_info));
}
return Serialize(
WireVarInt62(MoqtMessageType::kSubscribeUpdate),
WireVarInt62(message.subscribe_id), WireVarInt62(message.start_group),
WireVarInt62(message.start_object), WireVarInt62(end_group),
WireVarInt62(end_object), WireSpan<WireStringParameter>(string_params));
}
quiche::QuicheBuffer MoqtFramer::SerializeAnnounce(
const MoqtAnnounce& message) {
absl::InlinedVector<StringParameter, 1> string_params;
if (message.authorization_info.has_value()) {
string_params.push_back(
StringParameter(MoqtTrackRequestParameter::kAuthorizationInfo,
*message.authorization_info));
}
return Serialize(
WireVarInt62(static_cast<uint64_t>(MoqtMessageType::kAnnounce)),
WireStringWithVarInt62Length(message.track_namespace),
WireVarInt62(string_params.size()),
WireSpan<WireStringParameter>(string_params));
}
quiche::QuicheBuffer MoqtFramer::SerializeAnnounceOk(
const MoqtAnnounceOk& message) {
return Serialize(WireVarInt62(MoqtMessageType::kAnnounceOk),
WireStringWithVarInt62Length(message.track_namespace));
}
quiche::QuicheBuffer MoqtFramer::SerializeAnnounceError(
const MoqtAnnounceError& message) {
return Serialize(WireVarInt62(MoqtMessageType::kAnnounceError),
WireStringWithVarInt62Length(message.track_namespace),
WireVarInt62(message.error_code),
WireStringWithVarInt62Length(message.reason_phrase));
}
quiche::QuicheBuffer MoqtFramer::SerializeAnnounceCancel(
const MoqtAnnounceCancel& message) {
return Serialize(WireVarInt62(MoqtMessageType::kAnnounceCancel),
WireStringWithVarInt62Length(message.track_namespace));
}
quiche::QuicheBuffer MoqtFramer::SerializeTrackStatusRequest(
const MoqtTrackStatusRequest& message) {
return Serialize(WireVarInt62(MoqtMessageType::kTrackStatusRequest),
WireStringWithVarInt62Length(message.track_namespace),
WireStringWithVarInt62Length(message.track_name));
}
quiche::QuicheBuffer MoqtFramer::SerializeUnannounce(
const MoqtUnannounce& message) {
return Serialize(WireVarInt62(MoqtMessageType::kUnannounce),
WireStringWithVarInt62Length(message.track_namespace));
}
quiche::QuicheBuffer MoqtFramer::SerializeTrackStatus(
const MoqtTrackStatus& message) {
return Serialize(WireVarInt62(MoqtMessageType::kTrackStatus),
WireStringWithVarInt62Length(message.track_namespace),
WireStringWithVarInt62Length(message.track_name),
WireVarInt62(message.status_code),
WireVarInt62(message.last_group),
WireVarInt62(message.last_object));
}
quiche::QuicheBuffer MoqtFramer::SerializeGoAway(const MoqtGoAway& message) {
return Serialize(WireVarInt62(MoqtMessageType::kGoAway),
WireStringWithVarInt62Length(message.new_session_uri));
}
} // namespace moqt