blob: bf4c45ea4d517facc6c1ab77f68dcb12200bdf9d [file] [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 <algorithm>
#include <cstddef>
#include <cstdint>
#include <cstdlib>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include "absl/container/fixed_array.h"
#include "absl/functional/overload.h"
#include "absl/status/status.h"
#include "absl/status/statusor.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/moqt/moqt_error.h"
#include "quiche/quic/moqt/moqt_key_value_pair.h"
#include "quiche/quic/moqt/moqt_messages.h"
#include "quiche/quic/moqt/moqt_names.h"
#include "quiche/quic/moqt/moqt_object.h"
#include "quiche/quic/moqt/moqt_priority.h"
#include "quiche/quic/moqt/moqt_types.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/quiche_status_utils.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::WireMoqVarInt;
using ::quiche::WireOptional;
using ::quiche::WireSpan;
using ::quiche::WireStringWithMoqVarIntLength;
using ::quiche::WireUint8;
class WireKeyVarIntPair {
public:
explicit WireKeyVarIntPair(uint64_t key, uint64_t value)
: key_(key), value_(value) {}
size_t GetLengthOnWire() {
return quiche::ComputeLengthOnWire(WireMoqVarInt(key_),
WireMoqVarInt(value_));
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
return quiche::SerializeIntoWriter(writer, WireMoqVarInt(key_),
WireMoqVarInt(value_));
}
private:
const uint64_t key_;
const uint64_t value_;
};
class WireKeyStringPair {
public:
explicit WireKeyStringPair(uint64_t key, absl::string_view value)
: key_(key), value_(value) {}
size_t GetLengthOnWire() {
return quiche::ComputeLengthOnWire(WireMoqVarInt(key_),
WireStringWithMoqVarIntLength(value_));
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
return quiche::SerializeIntoWriter(writer, WireMoqVarInt(key_),
WireStringWithMoqVarIntLength(value_));
}
private:
const uint64_t key_;
const absl::string_view value_;
};
class WireKeyValuePairList {
public:
explicit WireKeyValuePairList(const KeyValuePairList& list) : list_(list) {}
size_t GetLengthOnWire() {
size_t total = 0;
uint64_t last_key = 0;
list_.ForEach([&](uint64_t key,
std::variant<uint64_t, absl::string_view> value) {
total += std::visit(
absl::Overload{
[&](uint64_t val) {
return WireKeyVarIntPair(key - last_key, val).GetLengthOnWire();
},
[&](absl::string_view val) {
return WireKeyStringPair(key - last_key, val).GetLengthOnWire();
}},
value);
last_key = key;
return true;
});
return total;
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
uint64_t last_key = 0;
list_.ForEach(
[&](uint64_t key, std::variant<uint64_t, absl::string_view> value) {
absl::Status status = std::visit(
absl::Overload{[&](uint64_t val) {
return WireKeyVarIntPair(key - last_key, val)
.SerializeIntoWriter(writer);
},
[&](absl::string_view val) {
return WireKeyStringPair(key - last_key, val)
.SerializeIntoWriter(writer);
}},
value);
last_key = key;
return quiche::IsWriterStatusOk(status);
});
return absl::OkStatus();
}
private:
const KeyValuePairList& list_;
};
class WireLocation {
public:
explicit WireLocation(const Location& location) : location_(location) {}
size_t GetLengthOnWire() {
return quiche::ComputeLengthOnWire(WireMoqVarInt(location_.group),
WireMoqVarInt(location_.object));
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
return quiche::SerializeIntoWriter(writer, WireMoqVarInt(location_.group),
WireMoqVarInt(location_.object));
}
private:
const Location& location_;
};
class WireAuthToken {
public:
explicit WireAuthToken(const AuthToken& token) : token_(token) {}
size_t GetLengthOnWire() {
return quiche::ComputeLengthOnWire(
WireMoqVarInt(token_.alias_type),
WireOptional<WireMoqVarInt>(token_.alias),
WireOptional<WireMoqVarInt>(token_.type),
WireOptional<WireBytes>(token_.value));
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
return quiche::SerializeIntoWriter(
writer, WireMoqVarInt(token_.alias_type),
WireOptional<WireMoqVarInt>(token_.alias),
WireOptional<WireMoqVarInt>(token_.type),
WireOptional<WireBytes>(token_.value));
}
private:
const AuthToken& token_;
};
class WireSubscriptionFilter {
public:
explicit WireSubscriptionFilter(const SubscriptionFilter& filter)
: filter_(filter) {}
size_t GetLengthOnWire() {
switch (filter_.type()) {
case MoqtFilterType::kNextGroupStart:
case MoqtFilterType::kLargestObject:
return quiche::ComputeLengthOnWire(WireMoqVarInt(filter_.type()));
case MoqtFilterType::kAbsoluteStart:
return quiche::ComputeLengthOnWire(WireMoqVarInt(filter_.type()),
WireLocation(filter_.start()));
case MoqtFilterType::kAbsoluteRange:
return quiche::ComputeLengthOnWire(
WireMoqVarInt(filter_.type()), WireLocation(filter_.start()),
WireMoqVarInt(filter_.end_group() - filter_.start().group));
}
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
switch (filter_.type()) {
case MoqtFilterType::kNextGroupStart:
case MoqtFilterType::kLargestObject:
return quiche::SerializeIntoWriter(writer,
WireMoqVarInt(filter_.type()));
case MoqtFilterType::kAbsoluteStart:
return quiche::SerializeIntoWriter(writer,
WireMoqVarInt(filter_.type()),
WireLocation(filter_.start()));
case MoqtFilterType::kAbsoluteRange:
return quiche::SerializeIntoWriter(
writer, WireMoqVarInt(filter_.type()),
WireLocation(filter_.start()),
WireMoqVarInt(filter_.end_group() - filter_.start().group));
}
}
private:
const SubscriptionFilter& filter_;
};
class WireTrackNamespace {
public:
WireTrackNamespace(const TrackNamespace& name) : namespace_(name) {}
size_t GetLengthOnWire() {
absl::FixedArray<absl::string_view> tuple(namespace_.tuple().begin(),
namespace_.tuple().end());
return quiche::ComputeLengthOnWire(
WireMoqVarInt(namespace_.number_of_elements()),
WireSpan<WireStringWithMoqVarIntLength, absl::string_view>(
absl::MakeSpan(tuple)));
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
absl::FixedArray<absl::string_view> tuple(namespace_.tuple().begin(),
namespace_.tuple().end());
return quiche::SerializeIntoWriter(
writer, WireMoqVarInt(namespace_.number_of_elements()),
WireSpan<WireStringWithMoqVarIntLength, absl::string_view>(
absl::MakeSpan(tuple)));
}
private:
const TrackNamespace& namespace_;
};
uint64_t TimeDeltaToMilliseconds(const quic::QuicTimeDelta& time_delta) {
if (time_delta == quic::QuicTimeDelta::Infinite()) {
return 0ULL;
}
return std::max(time_delta.ToMilliseconds(), int64_t{1});
}
class WireMessageParameters {
public:
explicit WireMessageParameters(const MessageParameters& parameters)
: parameters_(parameters), num_parameters_(0) {
if (parameters_.object_delivery_timeout.has_value()) {
++num_parameters_;
}
num_parameters_ += parameters_.authorization_tokens.size();
if (parameters_.rendezvous_timeout.has_value()) {
++num_parameters_;
}
if (parameters_.subgroup_delivery_timeout.has_value()) {
++num_parameters_;
}
if (parameters_.expires.has_value()) {
++num_parameters_;
}
if (parameters_.largest_object.has_value()) {
++num_parameters_;
}
if (parameters_.fill_timeout.has_value()) {
++num_parameters_;
}
if (parameters_.forward_has_value()) {
++num_parameters_;
}
if (parameters_.subscriber_priority.has_value()) {
++num_parameters_;
}
if (parameters_.subscription_filter.has_value()) {
++num_parameters_;
}
if (parameters_.group_order.has_value()) {
++num_parameters_;
}
if (parameters_.new_group_request.has_value()) {
++num_parameters_;
}
if (parameters_.track_namespace_prefix.has_value()) {
++num_parameters_;
}
if (parameters_.oack_window_size.has_value()) {
++num_parameters_;
}
}
size_t GetLengthOnWire() {
size_t length = WireMoqVarInt(num_parameters_).GetLengthOnWire();
uint64_t last_key = 0;
auto key_delta = [&](MessageParameter key) {
uint64_t delta = static_cast<uint64_t>(key) - last_key;
last_key = static_cast<uint64_t>(key);
return delta;
};
if (parameters_.object_delivery_timeout.has_value()) {
length += quiche::ComputeLengthOnWire(WireKeyVarIntPair(
key_delta(MessageParameter::kObjectDeliveryTimeout),
TimeDeltaToMilliseconds(*parameters_.object_delivery_timeout)));
}
for (const AuthToken& token : parameters_.authorization_tokens) {
WireAuthToken wire_token(token);
length += quiche::ComputeLengthOnWire(
WireMoqVarInt(key_delta(MessageParameter::kAuthorizationToken)),
WireMoqVarInt(wire_token.GetLengthOnWire()), wire_token);
}
if (parameters_.rendezvous_timeout.has_value()) {
length +=
WireKeyVarIntPair(key_delta(MessageParameter::kRendezvousTimeout),
parameters_.rendezvous_timeout->ToMilliseconds())
.GetLengthOnWire();
}
if (parameters_.subgroup_delivery_timeout.has_value()) {
length +=
WireKeyVarIntPair(
key_delta(MessageParameter::kSubgroupDeliveryTimeout),
TimeDeltaToMilliseconds(*parameters_.subgroup_delivery_timeout))
.GetLengthOnWire();
}
if (parameters_.expires.has_value()) {
length += WireKeyVarIntPair(key_delta(MessageParameter::kExpires),
TimeDeltaToMilliseconds(*parameters_.expires))
.GetLengthOnWire();
}
if (parameters_.largest_object.has_value()) {
length += quiche::ComputeLengthOnWire(
WireMoqVarInt(key_delta(MessageParameter::kLargestObject)),
WireLocation(*parameters_.largest_object));
}
if (parameters_.fill_timeout.has_value()) {
length += WireKeyVarIntPair(key_delta(MessageParameter::kFillTimeout),
parameters_.fill_timeout->ToMilliseconds())
.GetLengthOnWire();
}
if (parameters_.forward_has_value()) {
length += quiche::ComputeLengthOnWire(
WireMoqVarInt(key_delta(MessageParameter::kForward)),
WireUint8(parameters_.forward() ? 1ULL : 0ULL));
}
if (parameters_.subscriber_priority.has_value()) {
length += quiche::ComputeLengthOnWire(
WireMoqVarInt(key_delta(MessageParameter::kSubscriberPriority)),
WireUint8(*parameters_.subscriber_priority));
}
if (parameters_.subscription_filter.has_value()) {
WireSubscriptionFilter filter(*parameters_.subscription_filter);
length += quiche::ComputeLengthOnWire(
WireMoqVarInt(key_delta(MessageParameter::kSubscriptionFilter)),
WireMoqVarInt(filter.GetLengthOnWire()), filter);
}
if (parameters_.group_order.has_value()) {
length += quiche::ComputeLengthOnWire(
WireMoqVarInt(key_delta(MessageParameter::kGroupOrder)),
WireUint8(static_cast<uint8_t>(*parameters_.group_order)));
}
if (parameters_.new_group_request.has_value()) {
length += WireKeyVarIntPair(key_delta(MessageParameter::kNewGroupRequest),
*parameters_.new_group_request)
.GetLengthOnWire();
}
if (parameters_.track_namespace_prefix.has_value()) {
length += quiche::ComputeLengthOnWire(
WireMoqVarInt(key_delta(MessageParameter::kTrackNamespacePrefix)),
WireTrackNamespace(*parameters_.track_namespace_prefix));
}
if (parameters_.oack_window_size.has_value()) {
length +=
WireKeyVarIntPair(key_delta(MessageParameter::kOackWindowSize),
parameters_.oack_window_size->ToMicroseconds())
.GetLengthOnWire();
}
return length;
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
QUICHE_RETURN_IF_ERROR(
quiche::SerializeIntoWriter(writer, WireMoqVarInt(num_parameters_)));
uint64_t last_key = 0;
auto key_delta = [&](MessageParameter key) {
uint64_t delta = static_cast<uint64_t>(key) - last_key;
last_key = static_cast<uint64_t>(key);
return delta;
};
if (parameters_.object_delivery_timeout.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer,
WireKeyVarIntPair(
key_delta(MessageParameter::kObjectDeliveryTimeout),
TimeDeltaToMilliseconds(*parameters_.object_delivery_timeout))));
}
for (const AuthToken& token : parameters_.authorization_tokens) {
WireAuthToken wire_token(token);
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer,
WireMoqVarInt(key_delta(MessageParameter::kAuthorizationToken)),
WireMoqVarInt(wire_token.GetLengthOnWire()), wire_token));
}
if (parameters_.rendezvous_timeout.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer,
WireKeyVarIntPair(key_delta(MessageParameter::kRendezvousTimeout),
parameters_.rendezvous_timeout->ToMilliseconds())));
}
if (parameters_.subgroup_delivery_timeout.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer, WireKeyVarIntPair(
key_delta(MessageParameter::kSubgroupDeliveryTimeout),
TimeDeltaToMilliseconds(
*parameters_.subgroup_delivery_timeout))));
}
if (parameters_.expires.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer,
WireKeyVarIntPair(key_delta(MessageParameter::kExpires),
TimeDeltaToMilliseconds(*parameters_.expires))));
}
if (parameters_.largest_object.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer, WireMoqVarInt(key_delta(MessageParameter::kLargestObject)),
WireLocation(*parameters_.largest_object)));
}
if (parameters_.fill_timeout.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer,
WireKeyVarIntPair(key_delta(MessageParameter::kFillTimeout),
parameters_.fill_timeout->ToMilliseconds())));
}
if (parameters_.forward_has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer, WireMoqVarInt(key_delta(MessageParameter::kForward)),
WireUint8(parameters_.forward() ? 1ULL : 0ULL)));
}
if (parameters_.subscriber_priority.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer,
WireMoqVarInt(key_delta(MessageParameter::kSubscriberPriority)),
WireUint8(*parameters_.subscriber_priority)));
}
if (parameters_.subscription_filter.has_value()) {
WireSubscriptionFilter filter(*parameters_.subscription_filter);
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer,
WireMoqVarInt(key_delta(MessageParameter::kSubscriptionFilter)),
WireMoqVarInt(filter.GetLengthOnWire()), filter));
}
if (parameters_.group_order.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer, WireMoqVarInt(key_delta(MessageParameter::kGroupOrder)),
WireUint8(static_cast<uint8_t>(*parameters_.group_order))));
}
if (parameters_.new_group_request.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer,
WireKeyVarIntPair(key_delta(MessageParameter::kNewGroupRequest),
*parameters_.new_group_request)));
}
if (parameters_.track_namespace_prefix.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer,
WireMoqVarInt(key_delta(MessageParameter::kTrackNamespacePrefix)),
WireTrackNamespace(*parameters_.track_namespace_prefix)));
}
if (parameters_.oack_window_size.has_value()) {
QUICHE_RETURN_IF_ERROR(quiche::SerializeIntoWriter(
writer,
WireKeyVarIntPair(key_delta(MessageParameter::kOackWindowSize),
parameters_.oack_window_size->ToMicroseconds())));
}
return absl::OkStatus();
}
private:
const MessageParameters& parameters_;
uint64_t num_parameters_;
};
class WireFullTrackName {
public:
WireFullTrackName(const FullTrackName& name) : name_(name) {}
size_t GetLengthOnWire() {
return quiche::ComputeLengthOnWire(
WireTrackNamespace(name_.track_namespace()),
WireStringWithMoqVarIntLength(name_.name()));
}
absl::Status SerializeIntoWriter(quiche::QuicheDataWriter& writer) {
return quiche::SerializeIntoWriter(
writer, WireTrackNamespace(name_.track_namespace()),
WireStringWithMoqVarIntLength(name_.name()));
}
private:
const FullTrackName& 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 = sizeof(uint16_t) + payload_size +
quiche::ComputeLengthOnWire(WireMoqVarInt(message_type));
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, WireMoqVarInt(message_type),
quiche::WireUint16(payload_size), data...);
if (!status.ok() || writer.remaining() != 0) {
QUICHE_BUG(moqt_failed_serialization)
<< "Failed to serialize MoQT frame: " << status;
return QuicheBuffer();
}
return buffer;
}
[[maybe_unused]] WireUint8 WireDeliveryOrder(
std::optional<MoqtGroupOrder> group_order) {
if (!group_order.has_value()) {
return WireUint8(0x00);
}
switch (*group_order) {
case MoqtGroupOrder::kAscending:
return WireUint8(0x01);
case MoqtGroupOrder::kDescending:
return WireUint8(0x02);
}
QUICHE_NOTREACHED();
return WireUint8(0xff);
}
WireUint8 WireBoolean(bool value) { return WireUint8(value ? 0x01 : 0x00); }
uint64_t SignedVarintSerializedForm(int64_t value) {
if (value < 0) {
return ((-value) << 1) | 0x01;
}
return value << 1;
}
quiche::QuicheBuffer SerializeAuthToken(const AuthToken& token) {
return Serialize(WireMoqVarInt(token.alias_type),
WireOptional<WireMoqVarInt>(token.alias),
WireOptional<WireMoqVarInt>(token.type),
WireOptional<WireBytes>(token.value));
}
} // namespace
KeyValuePairList SetupOptions::ToKeyValuePairList() const {
KeyValuePairList out;
if (max_auth_token_cache_size.has_value()) {
out.insert(static_cast<uint64_t>(SetupOption::kMaxAuthTokenCacheSize),
*max_auth_token_cache_size);
}
if (path.has_value()) {
out.insert(static_cast<uint64_t>(SetupOption::kPath), *path);
}
for (const AuthToken& token : authorization_tokens) {
out.insert(static_cast<uint64_t>(SetupOption::kAuthorizationToken),
SerializeAuthToken(token).AsStringView());
}
if (authority.has_value()) {
out.insert(static_cast<uint64_t>(SetupOption::kAuthority), *authority);
}
if (moqt_implementation.has_value()) {
out.insert(static_cast<uint64_t>(SetupOption::kMoqtImplementation),
*moqt_implementation);
}
if (support_object_acks.has_value()) {
out.insert(static_cast<uint64_t>(SetupOption::kSupportObjectAcks),
*support_object_acks ? 1ULL : 0ULL);
}
return out;
}
quiche::QuicheBuffer MoqtFramer::SerializeObjectHeader(
const MoqtObject& message, MoqtDataStreamType message_type,
std::optional<PublishedObjectMetadata>& previous_object_in_stream) {
if (!ValidateObjectMetadata(message, message_type.EndOfGroupInStream(),
message_type.IsFetch())) {
QUICHE_BUG(QUICHE_BUG_serialize_object_header_01)
<< "Object metadata is invalid";
return quiche::QuicheBuffer();
}
// Many fields are optional because the stream type or Fetch serialization
// omits them.
std::optional<uint64_t> stream_type;
std::optional<uint64_t> track_id; // Track alias or FETCH ID.
std::optional<uint64_t> group_id;
std::optional<uint64_t> subgroup_id;
std::optional<uint64_t> object_id;
std::optional<uint8_t> publisher_priority;
std::optional<absl::string_view> properties;
bool is_first_in_stream = !previous_object_in_stream.has_value();
if (is_first_in_stream) {
stream_type = message_type.value();
track_id = message.track_alias;
}
if (message_type.IsFetch()) {
MoqtFetchSerialization serialization;
std::optional<uint64_t> payload_length;
if (is_first_in_stream) {
serialization = MoqtFetchSerialization(message);
} else {
serialization =
MoqtFetchSerialization(message, *previous_object_in_stream);
}
if (serialization.has_group_id()) {
if (is_first_in_stream) {
group_id = message.group_id;
} else {
group_id =
(message.group_id > previous_object_in_stream->location.group)
? (message.group_id -
previous_object_in_stream->location.group - 1)
: (previous_object_in_stream->location.group -
message.group_id - 1);
}
}
if (serialization.has_subgroup_id()) {
subgroup_id = message.subgroup_id;
}
if (serialization.has_object_id()) {
if (is_first_in_stream || serialization.has_group_id()) {
object_id = message.object_id;
} else {
object_id =
message.object_id - previous_object_in_stream->location.object;
}
}
if (serialization.has_priority()) {
publisher_priority = message.publisher_priority;
}
if (serialization.has_properties()) {
properties = message.properties;
}
if (!serialization.end_of_non_existent_range() &&
!serialization.end_of_unknown_range()) {
payload_length = message.payload_length;
}
return Serialize(WireOptional<WireMoqVarInt>(stream_type),
WireOptional<WireMoqVarInt>(track_id),
WireMoqVarInt(serialization.value()),
WireOptional<WireMoqVarInt>(group_id),
WireOptional<WireMoqVarInt>(subgroup_id),
WireOptional<WireMoqVarInt>(object_id),
WireOptional<WireUint8>(publisher_priority),
WireOptional<WireStringWithMoqVarIntLength>(properties),
WireOptional<WireMoqVarInt>(payload_length));
}
// Subgroup stream.
if (!message.subgroup_id.has_value()) {
QUICHE_BUG(QUICHE_BUG_serialize_object_header_02)
<< "Subgroup ID is missing";
return quiche::QuicheBuffer();
}
if (is_first_in_stream) {
group_id = message.group_id;
if (message_type.IsSubgroupPresent()) {
subgroup_id = message.subgroup_id;
}
if (!message_type.HasDefaultPriority()) {
publisher_priority = message.publisher_priority;
}
}
object_id = message.object_id;
if (!is_first_in_stream) {
*object_id -= (previous_object_in_stream->location.object + 1);
}
if (message_type.ArePropertiesPresent()) {
properties = message.properties;
}
std::optional<uint64_t> object_status;
if (message.payload_length == 0) {
object_status = static_cast<uint64_t>(message.object_status);
}
return Serialize(WireOptional<WireMoqVarInt>(stream_type),
WireOptional<WireMoqVarInt>(track_id),
WireOptional<WireMoqVarInt>(group_id),
WireOptional<WireMoqVarInt>(subgroup_id),
WireOptional<WireUint8>(publisher_priority),
WireMoqVarInt(*object_id),
WireOptional<WireStringWithMoqVarIntLength>(properties),
WireMoqVarInt(message.payload_length),
WireOptional<WireMoqVarInt>(object_status));
}
quiche::QuicheBuffer MoqtFramer::SerializeObjectDatagram(
const MoqtObject& message, absl::string_view payload,
MoqtPriority default_priority) {
// Any datagram can support an alternate end-of-group encoding, and it is
// never a FETCH.
if (!ValidateObjectMetadata(message, true, false) ||
message.subgroup_id.has_value()) {
QUICHE_BUG(QUICHE_BUG_serialize_object_datagram_01)
<< "Object metadata is invalid";
return quiche::QuicheBuffer();
}
if (message.payload_length != payload.length()) {
QUICHE_BUG(QUICHE_BUG_serialize_object_datagram_03)
<< "Payload length does not match payload";
return quiche::QuicheBuffer();
}
MoqtDatagramType datagram_type(
!payload.empty(), !message.properties.empty(),
message.object_status == MoqtObjectStatus::kEndOfGroup,
message.publisher_priority == default_priority, message.object_id == 0);
std::optional<uint64_t> object_id =
datagram_type.has_object_id() ? std::optional<uint64_t>(message.object_id)
: std::nullopt;
std::optional<uint8_t> publisher_priority =
datagram_type.has_default_priority()
? std::nullopt
: std::optional<uint8_t>(message.publisher_priority);
std::optional<absl::string_view> properties =
datagram_type.has_properties()
? std::optional<absl::string_view>(message.properties)
: std::nullopt;
std::optional<uint64_t> object_status =
datagram_type.has_status()
? std::optional<uint64_t>(
static_cast<uint64_t>(message.object_status))
: std::nullopt;
std::optional<absl::string_view> raw_payload =
payload.empty() ? std::nullopt
: std::optional<absl::string_view>(payload);
return Serialize(
WireMoqVarInt(datagram_type.value()), WireMoqVarInt(message.track_alias),
WireMoqVarInt(message.group_id), WireOptional<WireMoqVarInt>(object_id),
WireOptional<WireUint8>(publisher_priority),
WireOptional<WireStringWithMoqVarIntLength>(properties),
WireOptional<WireMoqVarInt>(object_status),
WireOptional<WireBytes>(raw_payload));
}
quiche::QuicheBuffer MoqtFramer::SerializeSetup(const MoqtSetup& message) {
KeyValuePairList options;
if (!FillAndValidateSetupOptions(message.options, options)) {
return quiche::QuicheBuffer();
}
return SerializeControlMessage(MoqtMessageType::kSetup,
WireKeyValuePairList(options));
}
quiche::QuicheBuffer MoqtFramer::SerializeRequestOk(
const MoqtRequestOk& message) {
return SerializeControlMessage(MoqtMessageType::kRequestOk,
WireMessageParameters(message.parameters),
WireKeyValuePairList(message.properties));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribe(
const MoqtSubscribe& message) {
return SerializeControlMessage(MoqtMessageType::kSubscribe,
WireMoqVarInt(message.request_id),
WireFullTrackName(message.full_track_name),
WireMessageParameters(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeOk(
const MoqtSubscribeOk& message) {
if (!message.properties.Validate()) {
QUICHE_BUG(QUICHE_BUG_serialize_subscribe_ok_01)
<< "Subscribe OK properties are ill-formed";
return quiche::QuicheBuffer();
}
return SerializeControlMessage(MoqtMessageType::kSubscribeOk,
WireMoqVarInt(message.track_alias),
WireMessageParameters(message.parameters),
WireKeyValuePairList(message.properties));
}
quiche::QuicheBuffer MoqtFramer::SerializeRequestError(
const MoqtRequestError& message) {
if ((message.error_code == RequestErrorCode::kRedirect) !=
message.redirect.has_value()) {
QUICHE_BUG(QUICHE_BUG_serialize_request_error_01)
<< "Redirect presence must match kRedirect error code";
return quiche::QuicheBuffer();
}
if (message.redirect.has_value()) {
if (perspective_ == quic::Perspective::IS_CLIENT &&
!message.redirect->connect_uri.empty()) {
QUICHE_BUG(QUICHE_BUG_serialize_request_error_02)
<< "Connect URI must be empty from client";
return quiche::QuicheBuffer();
}
return SerializeControlMessage(
MoqtMessageType::kRequestError, WireMoqVarInt(message.error_code),
WireMoqVarInt(message.retry_interval.has_value()
? message.retry_interval->ToMilliseconds() + 1
: 0),
WireStringWithMoqVarIntLength(message.reason_phrase),
WireStringWithMoqVarIntLength(message.redirect->connect_uri),
WireFullTrackName(message.redirect->full_track_name));
}
return SerializeControlMessage(
MoqtMessageType::kRequestError, WireMoqVarInt(message.error_code),
WireMoqVarInt(message.retry_interval.has_value()
? message.retry_interval->ToMilliseconds() + 1
: 0),
WireStringWithMoqVarIntLength(message.reason_phrase));
}
quiche::QuicheBuffer MoqtFramer::SerializePublishDone(
const MoqtPublishDone& message) {
return SerializeControlMessage(
MoqtMessageType::kPublishDone, WireMoqVarInt(message.status_code),
WireMoqVarInt(message.stream_count),
WireStringWithMoqVarIntLength(message.error_reason));
}
quiche::QuicheBuffer MoqtFramer::SerializeRequestUpdate(
const MoqtRequestUpdate& message) {
return SerializeControlMessage(MoqtMessageType::kRequestUpdate,
WireMoqVarInt(message.request_id),
WireMessageParameters(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializePublishNamespace(
const MoqtPublishNamespace& message) {
return SerializeControlMessage(MoqtMessageType::kPublishNamespace,
WireMoqVarInt(message.request_id),
WireTrackNamespace(message.track_namespace),
WireMessageParameters(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeNamespace(
const MoqtNamespace& message) {
return SerializeControlMessage(
MoqtMessageType::kNamespace,
WireTrackNamespace(message.track_namespace_suffix));
}
quiche::QuicheBuffer MoqtFramer::SerializeNamespaceDone(
const MoqtNamespaceDone& message) {
return SerializeControlMessage(
MoqtMessageType::kNamespaceDone,
WireTrackNamespace(message.track_namespace_suffix));
}
quiche::QuicheBuffer MoqtFramer::SerializePublishSkipped(
const MoqtPublishSkipped& message) {
return SerializeControlMessage(MoqtMessageType::kPublishSkipped,
WireFullTrackName(message.name));
}
quiche::QuicheBuffer MoqtFramer::SerializeTrackStatus(
const MoqtTrackStatus& message) {
return SerializeControlMessage(MoqtMessageType::kTrackStatus,
WireMoqVarInt(message.request_id),
WireFullTrackName(message.full_track_name),
WireMessageParameters(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeGoAway(const MoqtGoAway& message) {
if (perspective_ == quic::Perspective::IS_CLIENT &&
!message.new_session_uri.empty()) {
QUICHE_BUG(QUICHE_BUG_serialize_go_away_01)
<< "New session URI must be empty from client";
return quiche::QuicheBuffer();
}
if (message.new_session_uri.length() > kMaxNewSessionUriLength) {
QUICHE_BUG(QUICHE_BUG_serialize_go_away_02)
<< "New session URI is too long";
return quiche::QuicheBuffer();
}
return SerializeControlMessage(
MoqtMessageType::kGoAway,
WireStringWithMoqVarIntLength(message.new_session_uri),
WireMoqVarInt(message.timeout.ToMilliseconds()),
WireOptional<WireMoqVarInt>(message.request_id));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeNamespace(
const MoqtSubscribeNamespace& message) {
return SerializeControlMessage(
MoqtMessageType::kSubscribeNamespace, WireMoqVarInt(message.request_id),
WireTrackNamespace(message.track_namespace_prefix),
WireMessageParameters(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeSubscribeTracks(
const MoqtSubscribeTracks& message) {
return SerializeControlMessage(
MoqtMessageType::kSubscribeTracks, WireMoqVarInt(message.request_id),
WireTrackNamespace(message.track_namespace_prefix),
WireMessageParameters(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeFetch(const MoqtFetch& message) {
if (std::holds_alternative<StandaloneFetch>(message.fetch)) {
const StandaloneFetch& standalone_fetch =
std::get<StandaloneFetch>(message.fetch);
if (standalone_fetch.end_location < standalone_fetch.start_location) {
QUICHE_BUG(MoqtFramer_invalid_fetch) << "Invalid FETCH object range";
return quiche::QuicheBuffer();
}
}
if (std::holds_alternative<StandaloneFetch>(message.fetch)) {
const StandaloneFetch& standalone_fetch =
std::get<StandaloneFetch>(message.fetch);
return SerializeControlMessage(
MoqtMessageType::kFetch, WireMoqVarInt(message.request_id),
WireMoqVarInt(FetchType::kStandalone),
WireFullTrackName(standalone_fetch.full_track_name),
WireMoqVarInt(standalone_fetch.start_location.group),
WireMoqVarInt(standalone_fetch.start_location.object),
WireMoqVarInt(standalone_fetch.end_location.group),
WireMoqVarInt(standalone_fetch.end_location.object == kMaxObjectId
? 0
: standalone_fetch.end_location.object + 1),
WireMessageParameters(message.parameters));
}
uint64_t request_id, joining_start;
if (std::holds_alternative<JoiningFetchRelative>(message.fetch)) {
const JoiningFetchRelative& joining_fetch =
std::get<JoiningFetchRelative>(message.fetch);
request_id = joining_fetch.joining_request_id;
joining_start = joining_fetch.joining_start;
} else {
const JoiningFetchAbsolute& joining_fetch =
std::get<JoiningFetchAbsolute>(message.fetch);
request_id = joining_fetch.joining_request_id;
joining_start = joining_fetch.joining_start;
}
return SerializeControlMessage(
MoqtMessageType::kFetch, WireMoqVarInt(message.request_id),
WireMoqVarInt(message.fetch.index() + 1), WireMoqVarInt(request_id),
WireMoqVarInt(joining_start), WireMessageParameters(message.parameters));
}
quiche::QuicheBuffer MoqtFramer::SerializeFetchOk(const MoqtFetchOk& message) {
return SerializeControlMessage(
MoqtMessageType::kFetchOk, WireBoolean(message.end_of_track),
WireMoqVarInt(message.end_location.group),
WireMoqVarInt(message.end_location.object == kMaxObjectId
? 0
: (message.end_location.object + 1)),
WireMessageParameters(message.parameters),
WireKeyValuePairList(message.properties));
}
quiche::QuicheBuffer MoqtFramer::SerializePublish(const MoqtPublish& message) {
return SerializeControlMessage(MoqtMessageType::kPublish,
WireMoqVarInt(message.request_id),
WireFullTrackName(message.full_track_name),
WireMoqVarInt(message.track_alias),
WireMessageParameters(message.parameters),
WireKeyValuePairList(message.properties));
}
quiche::QuicheBuffer MoqtFramer::SerializeObjectAck(
const MoqtObjectAck& message) {
return SerializeControlMessage(
MoqtMessageType::kObjectAck, WireMoqVarInt(message.group_id),
WireMoqVarInt(message.object_id),
WireMoqVarInt(SignedVarintSerializedForm(
message.delta_from_deadline.ToMicroseconds())));
}
bool MoqtFramer::FillAndValidateSetupOptions(const SetupOptions& options,
KeyValuePairList& out) {
if (SetupOptionsAllowedByMessage(options, perspective_, using_webtrans_) !=
MoqtError::kNoError) {
QUICHE_BUG(QUICHE_BUG_invalid_setup_options)
<< "Invalid setup options for "
<< MoqtMessageTypeToString(MoqtMessageType::kSetup);
return false;
}
out = options.ToKeyValuePairList();
return true;
}
// static
bool MoqtFramer::ValidateObjectMetadata(const MoqtObject& object,
bool alternate_end_of_group_encoding,
bool is_fetch) {
switch (object.object_status) {
case MoqtObjectStatus::kNormal:
return true;
case MoqtObjectStatus::kEndOfGroup:
return alternate_end_of_group_encoding ||
(object.payload_length == 0 && object.properties.empty());
case MoqtObjectStatus::kEndOfTrack:
return object.payload_length == 0 && object.properties.empty();
case MoqtObjectStatus::kEndOfNonExistentRange:
case MoqtObjectStatus::kEndOfUnknownRange:
return is_fetch && object.payload_length == 0 &&
object.properties.empty();
default:
QUICHE_NOTREACHED();
return false;
}
}
} // namespace moqt