Roll to MoQT draft-14, with core wire image changes.
Datagram header compression has been reworked.
Follow-on objects in Subgroup streams have an an object ID delta instead of an object ID.
TBD: new SETUP parameters, Malformed track definition, cosmetic renaming.
PiperOrigin-RevId: 803497308
diff --git a/quiche/quic/moqt/moqt_framer.cc b/quiche/quic/moqt/moqt_framer.cc
index c70164c..7ba6ab5 100644
--- a/quiche/quic/moqt/moqt_framer.cc
+++ b/quiche/quic/moqt/moqt_framer.cc
@@ -270,12 +270,13 @@
quiche::QuicheBuffer MoqtFramer::SerializeObjectHeader(
const MoqtObject& message, MoqtDataStreamType message_type,
- bool is_first_in_stream) {
+ std::optional<uint64_t> previous_object_in_stream) {
if (!ValidateObjectMetadata(message, /*is_datagram=*/false)) {
QUICHE_BUG(QUICHE_BUG_serialize_object_header_01)
<< "Object metadata is invalid";
return quiche::QuicheBuffer();
}
+ bool is_first_in_stream = !previous_object_in_stream.has_value();
// Not all fields will be written to the wire. Keep optional ones in
// std::optional so that they can be excluded.
// Three fields are always optional.
@@ -300,11 +301,23 @@
WireVarInt62(message.payload_length),
WireOptional<WireVarInt62>(object_status));
}
+ if (previous_object_in_stream.has_value() &&
+ message.object_id <= *previous_object_in_stream) {
+ QUICHE_BUG(QUICHE_BUG_serialize_object_header_02)
+ << "Object ID is not increasing";
+ return quiche::QuicheBuffer();
+ }
// Subgroup headers have more optional fields.
QUICHE_CHECK(message_type.IsSubgroup());
std::optional<uint64_t> group_id =
- is_first_in_stream ? std::optional<uint64_t>(message.group_id)
- : std::nullopt;
+ previous_object_in_stream.has_value()
+ ? std::nullopt
+ : std::optional<uint64_t>(message.group_id);
+ uint64_t object_id = message.object_id;
+ if (!is_first_in_stream) {
+ // The value is actually an object ID delta, not the absolute object ID.
+ object_id -= (*previous_object_in_stream + 1);
+ }
std::optional<uint64_t> subgroup_id =
(is_first_in_stream && message_type.IsSubgroupPresent())
? std::optional<uint64_t>(message.subgroup_id)
@@ -321,8 +334,7 @@
WireOptional<WireVarInt62>(track_alias),
WireOptional<WireVarInt62>(group_id),
WireOptional<WireVarInt62>(subgroup_id),
- WireOptional<WireUint8>(publisher_priority),
- WireVarInt62(message.object_id),
+ WireOptional<WireUint8>(publisher_priority), WireVarInt62(object_id),
WireOptional<WireStringWithVarInt62Length>(extension_headers),
WireVarInt62(message.payload_length),
WireOptional<WireVarInt62>(object_status));
@@ -341,9 +353,12 @@
return quiche::QuicheBuffer();
}
MoqtDatagramType datagram_type(
- /*has_status=*/payload.empty(), !message.extension_headers.empty(),
- !payload.empty() &&
- message.object_status == MoqtObjectStatus::kEndOfGroup);
+ !payload.empty(), !message.extension_headers.empty(),
+ message.object_status == MoqtObjectStatus::kEndOfGroup,
+ 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<absl::string_view> extensions =
datagram_type.has_extension()
? std::optional<absl::string_view>(message.extension_headers)
@@ -357,7 +372,7 @@
: std::optional<absl::string_view>(payload);
return Serialize(
WireVarInt62(datagram_type.value()), WireVarInt62(message.track_alias),
- WireVarInt62(message.group_id), WireVarInt62(message.object_id),
+ WireVarInt62(message.group_id), WireOptional<WireVarInt62>(object_id),
WireUint8(message.publisher_priority),
WireOptional<WireStringWithVarInt62Length>(extensions),
WireOptional<WireVarInt62>(object_status),
diff --git a/quiche/quic/moqt/moqt_framer.h b/quiche/quic/moqt/moqt_framer.h
index 650d75e..d1a6294 100644
--- a/quiche/quic/moqt/moqt_framer.h
+++ b/quiche/quic/moqt/moqt_framer.h
@@ -5,6 +5,9 @@
#ifndef QUICHE_QUIC_MOQT_MOQT_FRAMER_H_
#define QUICHE_QUIC_MOQT_MOQT_FRAMER_H_
+#include <cstdint>
+#include <optional>
+
#include "absl/strings/string_view.h"
#include "quiche/quic/moqt/moqt_messages.h"
#include "quiche/common/platform/api/quiche_export.h"
@@ -28,11 +31,12 @@
// Serialize functions. Takes structured data and serializes it into a
// QuicheBuffer for delivery to the stream.
- // Serializes the header for an object, including the appropriate stream
- // header if `is_first_in_stream` is set to true.
- quiche::QuicheBuffer SerializeObjectHeader(const MoqtObject& message,
- MoqtDataStreamType message_type,
- bool is_first_in_stream);
+ // Serializes the header for an object, |previous_object_in_stream| is nullopt
+ // if this is the first object in the stream, the object ID of the previous
+ // one otherwise.
+ quiche::QuicheBuffer SerializeObjectHeader(
+ const MoqtObject& message, MoqtDataStreamType message_type,
+ std::optional<uint64_t> previous_object_in_stream);
// Serializes both OBJECT and OBJECT_STATUS datagrams.
quiche::QuicheBuffer SerializeObjectDatagram(const MoqtObject& message,
absl::string_view payload);
diff --git a/quiche/quic/moqt/moqt_framer_test.cc b/quiche/quic/moqt/moqt_framer_test.cc
index 2da7139..5503c54 100644
--- a/quiche/quic/moqt/moqt_framer_test.cc
+++ b/quiche/quic/moqt/moqt_framer_test.cc
@@ -86,15 +86,20 @@
(info.param.uses_web_transport ? "WebTransport" : "QUIC");
}
+// If |change_in_object_id| is 0, it's the first object in the stream.
quiche::QuicheBuffer SerializeObject(MoqtFramer& framer,
const MoqtObject& message,
absl::string_view payload,
MoqtDataStreamType stream_type,
- bool is_first_in_stream) {
+ uint64_t change_in_object_id) {
MoqtObject adjusted_message = message;
adjusted_message.payload_length = payload.size();
+ QUICHE_DCHECK(message.object_id > change_in_object_id);
quiche::QuicheBuffer header = framer.SerializeObjectHeader(
- adjusted_message, stream_type, is_first_in_stream);
+ adjusted_message, stream_type,
+ change_in_object_id == 0
+ ? std::nullopt
+ : std::optional<uint64_t>(message.object_id - change_in_object_id));
if (header.empty()) {
return quiche::QuicheBuffer();
}
@@ -284,16 +289,15 @@
TEST_F(MoqtFramerSimpleTest, GroupMiddler) {
MoqtDataStreamType type = MoqtDataStreamType::Subgroup(1, 1, true);
auto header = std::make_unique<StreamHeaderSubgroupMessage>(type);
- auto buffer1 =
- SerializeObject(framer_, std::get<MoqtObject>(header->structured_data()),
- "foo", type, true);
+ auto buffer1 = SerializeObject(
+ framer_, std::get<MoqtObject>(header->structured_data()), "foo", type, 0);
EXPECT_EQ(buffer1.size(), header->total_message_size());
EXPECT_EQ(buffer1.AsStringView(), header->PacketSample());
auto middler = std::make_unique<StreamMiddlerSubgroupMessage>(type);
auto buffer2 =
SerializeObject(framer_, std::get<MoqtObject>(middler->structured_data()),
- "bar", type, false);
+ "bar", type, /*change_in_object_id=*/3);
EXPECT_EQ(buffer2.size(), middler->total_message_size());
EXPECT_EQ(buffer2.AsStringView(), middler->PacketSample());
}
@@ -302,14 +306,14 @@
auto header = std::make_unique<StreamHeaderFetchMessage>();
auto buffer1 =
SerializeObject(framer_, std::get<MoqtObject>(header->structured_data()),
- "foo", MoqtDataStreamType::Fetch(), true);
+ "foo", MoqtDataStreamType::Fetch(), 0);
EXPECT_EQ(buffer1.size(), header->total_message_size());
EXPECT_EQ(buffer1.AsStringView(), header->PacketSample());
auto middler = std::make_unique<StreamMiddlerFetchMessage>();
auto buffer2 =
SerializeObject(framer_, std::get<MoqtObject>(middler->structured_data()),
- "bar", MoqtDataStreamType::Fetch(), false);
+ "bar", MoqtDataStreamType::Fetch(), 3);
EXPECT_EQ(buffer2.size(), middler->total_message_size());
EXPECT_EQ(buffer2.AsStringView(), middler->PacketSample());
}
@@ -368,65 +372,15 @@
EXPECT_TRUE(buffer.empty());
}
-TEST_F(MoqtFramerSimpleTest, Datagram) {
- auto datagram = std::make_unique<ObjectDatagramMessage>(
- MoqtDatagramType(/*has_status=*/false, /*has_extension=*/true,
- /*end_of_group=*/false));
- MoqtObject object = {
- /*track_alias=*/4,
- /*group_id=*/5,
- /*object_id=*/6,
- /*publisher_priority=*/7,
- std::string(kDefaultExtensionBlob),
- /*object_status=*/MoqtObjectStatus::kNormal,
- /*subgroup_id=*/6,
- /*payload_length=*/3,
- };
- std::string payload = "foo";
- quiche::QuicheBuffer buffer;
- buffer = framer_.SerializeObjectDatagram(object, payload);
- EXPECT_EQ(buffer.size(), datagram->total_message_size());
- EXPECT_EQ(buffer.AsStringView(), datagram->PacketSample());
-}
-
-TEST_F(MoqtFramerSimpleTest, DatagramStatus) {
- auto datagram = std::make_unique<ObjectDatagramMessage>(
- MoqtDatagramType(true, true, false));
- MoqtObject object = {
- /*track_alias=*/4,
- /*group_id=*/5,
- /*object_id=*/6,
- /*publisher_priority=*/7,
- std::string(kDefaultExtensionBlob),
- /*object_status=*/MoqtObjectStatus::kObjectDoesNotExist,
- /*subgroup_id=*/6,
- /*payload_length=*/0,
- };
- quiche::QuicheBuffer buffer;
- buffer = framer_.SerializeObjectDatagram(object, "");
- EXPECT_EQ(buffer.size(), datagram->total_message_size());
- EXPECT_EQ(buffer.AsStringView(), datagram->PacketSample());
-}
-
-TEST_F(MoqtFramerSimpleTest, DatagramEndOfGroup) {
- auto datagram = std::make_unique<ObjectDatagramMessage>(
- MoqtDatagramType(/*has_status=*/false, /*has_extension=*/true,
- /*end_of_group=*/true));
- MoqtObject object = {
- /*track_alias=*/4,
- /*group_id=*/5,
- /*object_id=*/6,
- /*publisher_priority=*/7,
- std::string(kDefaultExtensionBlob),
- /*object_status=*/MoqtObjectStatus::kEndOfGroup,
- /*subgroup_id=*/6,
- /*payload_length=*/3,
- };
- std::string payload = "foo";
- quiche::QuicheBuffer buffer;
- buffer = framer_.SerializeObjectDatagram(object, payload);
- EXPECT_EQ(buffer.size(), datagram->total_message_size());
- EXPECT_EQ(buffer.AsStringView(), datagram->PacketSample());
+TEST_F(MoqtFramerSimpleTest, AllDatagramTypes) {
+ for (MoqtDatagramType type : AllMoqtDatagramTypes()) {
+ ObjectDatagramMessage message(type);
+ MoqtObject object = std::get<MoqtObject>(message.structured_data());
+ quiche::QuicheBuffer buffer =
+ framer_.SerializeObjectDatagram(object, type.has_status() ? "" : "foo");
+ EXPECT_EQ(buffer.size(), message.total_message_size());
+ EXPECT_EQ(buffer.AsStringView(), message.PacketSample());
+ }
}
TEST_F(MoqtFramerSimpleTest, AllSubscribeInputs) {
diff --git a/quiche/quic/moqt/moqt_messages.h b/quiche/quic/moqt/moqt_messages.h
index c545c0a..3d94c44 100644
--- a/quiche/quic/moqt/moqt_messages.h
+++ b/quiche/quic/moqt/moqt_messages.h
@@ -41,11 +41,11 @@
}
enum class MoqtVersion : uint64_t {
- kDraft13 = 0xff00000d,
+ kDraft14 = 0xff00000e,
kUnrecognizedVersionForTests = 0xfe0000ff,
};
-inline constexpr MoqtVersion kDefaultMoqtVersion = MoqtVersion::kDraft13;
+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;
@@ -193,32 +193,46 @@
class QUICHE_EXPORT MoqtDatagramType {
public:
- MoqtDatagramType(bool has_status, bool has_extension, bool end_of_group)
+ // 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) {
- if (has_extension) {
+ // 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 (has_status) {
+ if (zero_object_id) {
value_ |= 0x04;
}
- if (value_ > 0x5) {
- QUICHE_BUG(Moqt_invalid_datagram_type)
- << "Invalid datagram type: " << value_;
- // Clear the end of group bit.
- value_ &= 0x5;
- return;
+ if (!payload) {
+ value_ |= 0x20;
}
}
static std::optional<MoqtDatagramType> FromValue(uint64_t value) {
- if (value <= 5) {
+ if (value <= 7 || value == 0x20 || value == 0x21) {
return MoqtDatagramType(value);
}
return std::nullopt;
}
- bool has_status() const { return value_ & 0x04; }
+ 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_; }
diff --git a/quiche/quic/moqt/moqt_messages_test.cc b/quiche/quic/moqt/moqt_messages_test.cc
index c799137..beb131d 100644
--- a/quiche/quic/moqt/moqt_messages_test.cc
+++ b/quiche/quic/moqt/moqt_messages_test.cc
@@ -96,21 +96,24 @@
}
TEST(MoqtMessagesTest, MoqtDatagramType) {
- for (bool has_status : {false, true}) {
- for (bool has_extension : {false, true}) {
+ for (bool payload : {false, true}) {
+ for (bool extension : {false, true}) {
for (bool end_of_group : {false, true}) {
- if (has_status && end_of_group) {
- EXPECT_QUICHE_BUG(
- MoqtDatagramType(has_status, has_extension, end_of_group),
- "Invalid datagram type");
- continue;
+ for (bool zero_object_id : {false, true}) {
+ MoqtDatagramType type(payload, extension, end_of_group,
+ zero_object_id);
+ EXPECT_EQ(type.has_status(),
+ !payload && (!end_of_group || !zero_object_id));
+ EXPECT_EQ(type.has_extension(), extension);
+ EXPECT_EQ(type.end_of_group(),
+ end_of_group && (payload || zero_object_id));
+ EXPECT_EQ(type.has_object_id(),
+ !zero_object_id || (!payload && !end_of_group));
+ // The constructor should always produce a valid value.
+ std::optional<MoqtDatagramType> from_value =
+ MoqtDatagramType::FromValue(type.value());
+ EXPECT_TRUE(from_value.has_value() && type == *from_value);
}
- MoqtDatagramType type(has_status, has_extension, end_of_group);
- EXPECT_EQ(type.has_status(), has_status);
- EXPECT_EQ(type.has_extension(), has_extension);
- std::optional<MoqtDatagramType> from_value =
- MoqtDatagramType::FromValue(type.value());
- EXPECT_TRUE(from_value.has_value() && type == *from_value);
}
}
}
diff --git a/quiche/quic/moqt/moqt_parser.cc b/quiche/quic/moqt/moqt_parser.cc
index 6af3e37..847292f 100644
--- a/quiche/quic/moqt/moqt_parser.cc
+++ b/quiche/quic/moqt/moqt_parser.cc
@@ -1269,12 +1269,10 @@
object_metadata = MoqtObject();
if (!reader.ReadVarInt62(&type_raw) ||
!reader.ReadVarInt62(&object_metadata.track_alias) ||
- !reader.ReadVarInt62(&object_metadata.group_id) ||
- !reader.ReadVarInt62(&object_metadata.object_id) ||
- !reader.ReadUInt8(&object_metadata.publisher_priority)) {
+ !reader.ReadVarInt62(&object_metadata.group_id)) {
return std::nullopt;
}
- object_metadata.subgroup_id = object_metadata.object_id;
+
std::optional<MoqtDatagramType> datagram_type =
MoqtDatagramType::FromValue(type_raw);
if (!datagram_type.has_value()) {
@@ -1290,6 +1288,17 @@
} else {
object_metadata.object_status = MoqtObjectStatus::kNormal;
}
+ if (datagram_type->has_object_id()) {
+ if (!reader.ReadVarInt62(&object_metadata.object_id)) {
+ return std::nullopt;
+ }
+ } else {
+ object_metadata.object_id = 0;
+ }
+ object_metadata.subgroup_id = object_metadata.object_id;
+ if (!reader.ReadUInt8(&object_metadata.publisher_priority)) {
+ return std::nullopt;
+ }
if (datagram_type->has_extension()) {
if (!reader.ReadStringPieceVarInt62(&extensions)) {
return std::nullopt;
@@ -1481,7 +1490,13 @@
case kObjectId: {
std::optional<uint64_t> value_read = ReadVarInt62NoFin();
if (value_read.has_value()) {
- metadata_.object_id = *value_read;
+ if (type_.has_value() && type_->IsSubgroup() &&
+ last_object_id_.has_value()) {
+ metadata_.object_id = *value_read + *last_object_id_ + 1;
+ } else {
+ metadata_.object_id = *value_read;
+ }
+ last_object_id_ = metadata_.object_id;
AdvanceParserState();
}
return;
diff --git a/quiche/quic/moqt/moqt_parser.h b/quiche/quic/moqt/moqt_parser.h
index aa4d367..9c287ba 100644
--- a/quiche/quic/moqt/moqt_parser.h
+++ b/quiche/quic/moqt/moqt_parser.h
@@ -11,6 +11,7 @@
#include <cstddef>
#include <cstdint>
#include <optional>
+#include <string>
#include <vector>
#include "absl/strings/string_view.h"
@@ -284,6 +285,7 @@
std::optional<MoqtDataStreamType> type_ = std::nullopt;
NextInput next_input_ = kStreamType;
MoqtObject metadata_;
+ std::optional<uint64_t> last_object_id_;
size_t payload_length_remaining_ = 0;
size_t num_objects_read_ = 0;
diff --git a/quiche/quic/moqt/moqt_parser_test.cc b/quiche/quic/moqt/moqt_parser_test.cc
index 24e5657..039776f 100644
--- a/quiche/quic/moqt/moqt_parser_test.cc
+++ b/quiche/quic/moqt/moqt_parser_test.cc
@@ -1277,7 +1277,7 @@
}
TEST_F(MoqtMessageSpecificTest, DatagramSuccessful) {
- for (MoqtDatagramType datagram_type : kMoqtDatagramTypes) {
+ for (MoqtDatagramType datagram_type : AllMoqtDatagramTypes()) {
ObjectDatagramMessage message(datagram_type);
MoqtObject object;
std::optional<absl::string_view> payload =
@@ -1295,7 +1295,7 @@
}
TEST_F(MoqtMessageSpecificTest, DatagramSuccessfulExpandVarints) {
- for (MoqtDatagramType datagram_type : kMoqtDatagramTypes) {
+ for (MoqtDatagramType datagram_type : AllMoqtDatagramTypes()) {
ObjectDatagramMessage message(datagram_type);
message.ExpandVarints();
MoqtObject object;
@@ -1323,7 +1323,7 @@
}
TEST_F(MoqtMessageSpecificTest, TruncatedDatagram) {
- ObjectDatagramMessage message(MoqtDatagramType(false, true, false));
+ ObjectDatagramMessage message(MoqtDatagramType(false, true, false, false));
message.set_wire_image_size(4);
MoqtObject object;
std::optional<absl::string_view> payload =
diff --git a/quiche/quic/moqt/moqt_session.cc b/quiche/quic/moqt/moqt_session.cc
index 9ccb1db..7005f76 100644
--- a/quiche/quic/moqt/moqt_session.cc
+++ b/quiche/quic/moqt/moqt_session.cc
@@ -641,7 +641,9 @@
if (fetch->session_->WriteObjectToStream(
stream_, fetch->request_id(), object.metadata,
std::move(object.payload), MoqtDataStreamType::Fetch(),
- !stream_header_written_,
+ // last Object ID doesn't matter for FETCH, just use zero.
+ stream_header_written_ ? std::optional<uint64_t>(0)
+ : std::nullopt,
/*fin=*/false)) {
stream_header_written_ = true;
}
@@ -2393,14 +2395,14 @@
}
if (!session_->WriteObjectToStream(
stream_, *subscription.track_alias(), object->metadata,
- std::move(object->payload), stream_type_, !stream_header_written_,
+ std::move(object->payload), stream_type_, last_object_id_,
object->fin_after_this)) {
// WriteObjectToStream() closes the connection on error, meaning that
// there is no need to process the stream any further.
return;
}
++next_object_;
- stream_header_written_ = true;
+ last_object_id_ = object->metadata.location.object;
subscription.OnObjectSent(object->metadata.location);
if (object->fin_after_this && !delivery_timeout.IsInfinite() &&
@@ -2435,7 +2437,8 @@
const PublishedObjectMetadata& metadata,
quiche::QuicheMemSlice payload,
MoqtDataStreamType type,
- bool is_first_on_stream, bool fin) {
+ std::optional<uint64_t> last_id,
+ bool fin) {
QUICHE_DCHECK(stream->CanWrite());
MoqtObject header;
header.track_alias = id;
@@ -2447,7 +2450,7 @@
header.payload_length = payload.length();
quiche::QuicheBuffer serialized_header =
- framer_.SerializeObjectHeader(header, type, is_first_on_stream);
+ framer_.SerializeObjectHeader(header, type, last_id);
// TODO(vasilvv): add a version of WebTransport write API that accepts
// memslices so that we can avoid a copy here.
std::array write_vector = {
diff --git a/quiche/quic/moqt/moqt_session.h b/quiche/quic/moqt/moqt_session.h
index 15d1d62..140de3f 100644
--- a/quiche/quic/moqt/moqt_session.h
+++ b/quiche/quic/moqt/moqt_session.h
@@ -563,7 +563,9 @@
// exact ID of the next object in the stream because the next object could
// be in a different subgroup or simply be skipped.
uint64_t next_object_;
- bool stream_header_written_ = false;
+ // Used in subgroup streams to compute the object ID diff. If nullopt, the
+ // stream header has not been written yet.
+ std::optional<uint64_t> last_object_id_;
// If this data stream is for SUBSCRIBE, reset it if an object has been
// excessively delayed per Section 7.1.1.2.
std::unique_ptr<quic::QuicAlarm> delivery_timeout_alarm_;
@@ -755,8 +757,8 @@
bool WriteObjectToStream(webtransport::Stream* stream, uint64_t id,
const PublishedObjectMetadata& metadata,
quiche::QuicheMemSlice payload,
- MoqtDataStreamType type, bool is_first_on_stream,
- bool fin);
+ MoqtDataStreamType type,
+ std::optional<uint64_t> last_id, bool fin);
void CancelFetch(uint64_t request_id);
diff --git a/quiche/quic/moqt/moqt_session_test.cc b/quiche/quic/moqt/moqt_session_test.cc
index e57c1f4..56b3a73 100644
--- a/quiche/quic/moqt/moqt_session_test.cc
+++ b/quiche/quic/moqt/moqt_session_test.cc
@@ -193,7 +193,8 @@
object,
MoqtDataStreamType::Subgroup(object.subgroup_id, object.object_id,
false),
- visitor == nullptr);
+ (visitor == nullptr) ? std::nullopt
+ : std::optional<uint64_t>(object.object_id - 1));
size_t data_read = 0;
if (visitor == nullptr) { // It's the first object in the stream
EXPECT_CALL(session, AcceptIncomingUnidirectionalStream())
@@ -1651,7 +1652,7 @@
// Reopen the window.
correct_message = false;
// object id, extensions, payload length, status.
- const std::string kExpectedMessage2 = {0x01, 0x00, 0x00, 0x03};
+ const std::string kExpectedMessage2 = {0x00, 0x00, 0x00, 0x03};
EXPECT_CALL(mock_stream_, CanWrite()).WillRepeatedly([&] { return true; });
EXPECT_CALL(*track, GetCachedObject(5, 0, 1)).WillRepeatedly([&] {
return PublishedObject{PublishedObjectMetadata{
@@ -1963,7 +1964,7 @@
// Publish in window.
bool correct_message = false;
uint8_t kExpectedMessage[] = {
- 0x00, 0x02, 0x05, 0x00, 0x80, 0x64, 0x65,
+ 0x04, 0x02, 0x05, 0x80, 0x64, 0x65,
0x61, 0x64, 0x62, 0x65, 0x65, 0x66, // "deadbeef"
};
EXPECT_CALL(mock_session_, SendOrQueueDatagram(_))
@@ -2724,8 +2725,8 @@
/*payload_length=*/3,
};
MoqtFramer framer(quiche::SimpleBufferAllocator::Get(), true);
- quiche::QuicheBuffer header =
- framer.SerializeObjectHeader(object, MoqtDataStreamType::Fetch(), true);
+ quiche::QuicheBuffer header = framer.SerializeObjectHeader(
+ object, MoqtDataStreamType::Fetch(), std::nullopt);
// Open stream, deliver two objects before FETCH_OK. Neither should be read.
webtransport::test::InMemoryStream data_stream(kIncomingUniStreamId);
@@ -2942,7 +2943,8 @@
for (int i = 0; i < 4; ++i) {
object.object_id = i;
headers.push(framer_.SerializeObjectHeader(
- object, MoqtDataStreamType::Fetch(), i == 0));
+ object, MoqtDataStreamType::Fetch(),
+ i == 0 ? std::nullopt : std::optional<uint64_t>(i - 1)));
payloads.push("foo");
}
@@ -3013,7 +3015,8 @@
for (int i = 0; i < 4; ++i) {
object.object_id = i;
headers.push(framer_.SerializeObjectHeader(
- object, MoqtDataStreamType::Fetch(), i == 0));
+ object, MoqtDataStreamType::Fetch(),
+ i == 0 ? std::nullopt : std::optional<uint64_t>(i - 1)));
payloads.push("foo");
}
@@ -3102,8 +3105,8 @@
/*payload_length=*/6,
};
MoqtFramer framer_(quiche::SimpleBufferAllocator::Get(), true);
- quiche::QuicheBuffer header =
- framer_.SerializeObjectHeader(object, MoqtDataStreamType::Fetch(), true);
+ quiche::QuicheBuffer header = framer_.SerializeObjectHeader(
+ object, MoqtDataStreamType::Fetch(), std::nullopt);
stream.Receive(header.AsStringView(), false);
EXPECT_FALSE(task->HasObject());
EXPECT_FALSE(object_ready);
diff --git a/quiche/quic/moqt/test_tools/moqt_test_message.h b/quiche/quic/moqt/test_tools/moqt_test_message.h
index bfe3d95..ad433a1 100644
--- a/quiche/quic/moqt/test_tools/moqt_test_message.h
+++ b/quiche/quic/moqt/test_tools/moqt_test_message.h
@@ -32,12 +32,20 @@
inline constexpr absl::string_view kDefaultExtensionBlob(
"\x00\x0c\x01\x03\x66\x6f\x6f", 7);
-const MoqtDatagramType kMoqtDatagramTypes[] = {
- MoqtDatagramType(false, false, true), MoqtDatagramType(false, false, false),
- MoqtDatagramType(false, true, true), MoqtDatagramType(false, true, false),
- MoqtDatagramType(true, false, false), MoqtDatagramType(true, true, false),
- // Cannot have status and end_of_group both be true.
-};
+inline std::vector<MoqtDatagramType> AllMoqtDatagramTypes() {
+ std::vector<MoqtDatagramType> types;
+ for (bool payload : {false, true}) {
+ for (bool extension : {false, true}) {
+ for (bool end_of_group : {false, true}) {
+ for (bool zero_object_id : {false, true}) {
+ types.push_back(MoqtDatagramType(payload, extension, end_of_group,
+ zero_object_id));
+ }
+ }
+ }
+ }
+ return types;
+}
inline std::vector<MoqtDataStreamType> AllMoqtDataStreamTypes() {
std::vector<MoqtDataStreamType> types;
@@ -243,7 +251,6 @@
public:
ObjectDatagramMessage(MoqtDatagramType datagram_type)
: ObjectMessage(), datagram_type_(datagram_type) {
- object_.subgroup_id = object_.object_id;
// Update ObjectMessage::object_ to match the datagram type.
if (datagram_type.has_status()) {
object_.object_status = MoqtObjectStatus::kObjectDoesNotExist;
@@ -254,15 +261,18 @@
: MoqtObjectStatus::kNormal;
object_.payload_length = 3;
}
- if (datagram_type.has_extension()) {
- object_.extension_headers = std::string(kDefaultExtensionBlob);
- } else {
- object_.extension_headers = "";
- }
+ object_.extension_headers =
+ datagram_type.has_extension() ? std::string(kDefaultExtensionBlob) : "";
+ object_.object_id = datagram_type.has_object_id() ? 6 : 0;
+ object_.subgroup_id = object_.object_id;
quic::QuicDataWriter writer(sizeof(raw_packet_),
reinterpret_cast<char*>(raw_packet_));
EXPECT_TRUE(writer.WriteVarInt62(datagram_type.value()));
- EXPECT_TRUE(writer.WriteStringPiece(kRawVarints));
+ EXPECT_TRUE(writer.WriteStringPiece(kRawAliasGroup));
+ if (datagram_type.has_object_id()) {
+ EXPECT_TRUE(writer.WriteStringPiece(kRawObject));
+ }
+ EXPECT_TRUE(writer.WriteStringPiece(kRawPriority));
if (datagram_type.has_extension()) {
EXPECT_TRUE(writer.WriteStringPiece(kRawExtensions));
}
@@ -277,25 +287,26 @@
}
void ExpandVarints() override {
- if (datagram_type_.has_extension()) {
- if (datagram_type_.has_status()) {
- ExpandVarintsImpl("vvvv-v-------v", false);
- } else {
- ExpandVarintsImpl("vvvv-v----------", false);
- }
- } else {
- if (datagram_type_.has_status()) {
- ExpandVarintsImpl("vvvv-v", false);
- } else {
- ExpandVarintsImpl("vvvv----", false);
- }
+ std::string varints = "vvv";
+ if (datagram_type_.has_object_id()) {
+ varints += "v";
}
+ varints += "-"; // priority
+ if (datagram_type_.has_extension()) {
+ varints += "v-------";
+ }
+ if (datagram_type_.has_status()) {
+ varints += "v";
+ }
+ ExpandVarintsImpl(varints, false);
}
private:
uint8_t raw_packet_[17];
MoqtDatagramType datagram_type_;
- static constexpr absl::string_view kRawVarints = "\x04\x05\x06\x07";
+ static constexpr absl::string_view kRawAliasGroup = "\x04\x05";
+ static constexpr absl::string_view kRawObject = "\x06";
+ static constexpr absl::string_view kRawPriority = "\x07";
static constexpr absl::string_view kRawExtensions{
"\x07\x00\x0c\x01\x03\x66\x6f\x6f", 8}; // see kDefaultExtensionBlob
static constexpr absl::string_view kRawPayload = "foo";
@@ -396,7 +407,7 @@
}
object_.object_id = 9;
quic::QuicDataWriter writer(sizeof(raw_packet_), raw_packet_);
- EXPECT_TRUE(writer.WriteVarInt62(object_.object_id));
+ EXPECT_TRUE(writer.WriteVarInt62(2)); // Object ID delta - 1
if (type.AreExtensionHeadersPresent()) {
EXPECT_TRUE(
writer.WriteBytes(kRawExtensions.data(), kRawExtensions.length()));