blob: cc35e3d9c9c14bd92a56cd30267ad1c86a14ddb2 [file]
// 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_parser.h"
#include <array>
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <limits>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <variant>
#include <vector>
#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/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_priority.h"
#include "quiche/quic/moqt/moqt_session_interface.h"
#include "quiche/quic/moqt/moqt_types.h"
#include "quiche/quic/moqt/test_tools/moqt_framer_utils.h"
#include "quiche/quic/moqt/test_tools/moqt_parser_test_visitor.h"
#include "quiche/quic/moqt/test_tools/moqt_test_message.h"
#include "quiche/quic/platform/api/quic_test.h"
#include "quiche/common/platform/api/quiche_logging.h"
#include "quiche/common/platform/api/quiche_test.h"
#include "quiche/common/quiche_status_utils.h"
#include "quiche/common/test_tools/quiche_test_utils.h"
#include "quiche/web_transport/test_tools/in_memory_stream.h"
namespace moqt::test {
namespace {
using ::quiche::test::IsOkAndHolds;
using ::quiche::test::StatusIs;
using ::testing::AnyOf;
using ::testing::HasSubstr;
constexpr std::array kMessageTypes{
MoqtMessageType::kRequestOk,
MoqtMessageType::kRequestError,
MoqtMessageType::kSubscribe,
MoqtMessageType::kSubscribeOk,
MoqtMessageType::kRequestUpdate,
MoqtMessageType::kPublishDone,
MoqtMessageType::kTrackStatus,
MoqtMessageType::kPublishNamespace,
MoqtMessageType::kNamespace,
MoqtMessageType::kNamespaceDone,
MoqtMessageType::kPublishSkipped,
MoqtMessageType::kGoAway,
MoqtMessageType::kSubscribeNamespace,
MoqtMessageType::kSubscribeTracks,
MoqtMessageType::kFetch,
MoqtMessageType::kFetchOk,
MoqtMessageType::kPublish,
MoqtMessageType::kObjectAck,
MoqtMessageType::kSetup,
};
using GeneralizedMessageType =
std::variant<MoqtMessageType, MoqtDataStreamType>;
} // namespace
struct MoqtParserTestParams {
MoqtParserTestParams(
MoqtMessageType message_type, bool uses_web_transport,
quic::Perspective perspective = quic::Perspective::IS_SERVER)
: message_type(message_type),
uses_web_transport(uses_web_transport),
perspective(perspective) {}
explicit MoqtParserTestParams(MoqtDataStreamType message_type)
: message_type(message_type),
uses_web_transport(true),
perspective(quic::Perspective::IS_SERVER) {}
GeneralizedMessageType message_type;
bool uses_web_transport;
quic::Perspective perspective;
};
std::vector<MoqtParserTestParams> GetMoqtParserTestParams() {
std::vector<MoqtParserTestParams> params;
for (MoqtMessageType message_type : kMessageTypes) {
if (message_type == MoqtMessageType::kSetup) {
for (const bool uses_web_transport : {false, true}) {
for (const quic::Perspective perspective :
{quic::Perspective::IS_CLIENT, quic::Perspective::IS_SERVER}) {
params.push_back(MoqtParserTestParams(
message_type, uses_web_transport, perspective));
}
}
} else if (message_type == MoqtMessageType::kGoAway) {
// GoAwayMessage has a non-empty new_session_uri, which is only valid
// when received by the client.
params.push_back(MoqtParserTestParams(message_type, true,
quic::Perspective::IS_CLIENT));
} else {
// All other types are processed the same for either perspective or
// transport.
params.push_back(MoqtParserTestParams(message_type, true,
quic::Perspective::IS_SERVER));
}
}
for (MoqtDataStreamType type : AllMoqtDataStreamTypes()) {
params.push_back(MoqtParserTestParams(type));
}
return params;
}
std::string TypeFormatter(MoqtMessageType type) {
return MoqtMessageTypeToString(type);
}
std::string TypeFormatter(MoqtDataStreamType type) {
return MoqtDataStreamTypeToString(type);
}
std::string ParamNameFormatter(
const testing::TestParamInfo<MoqtParserTestParams>& info) {
return std::visit([](auto x) { return TypeFormatter(x); },
info.param.message_type) +
"_" + (info.param.uses_web_transport ? "WebTransport" : "QUIC") + "_" +
quic::PerspectiveToString(info.param.perspective);
}
std::optional<MoqtError> ExtractMoqtErrorForStatus(const absl::Status& status) {
if (!absl::IsInvalidArgument(status)) {
return std::nullopt;
}
return GetMoqtErrorForStatus(status).value_or(MoqtError::kProtocolViolation);
}
class MoqtParserTest
: public quic::test::QuicTestWithParam<MoqtParserTestParams> {
public:
MoqtParserTest()
: message_type_(GetParam().message_type),
webtrans_(GetParam().uses_web_transport),
perspective_(GetParam().perspective),
control_stream_(/*stream_id=*/0),
control_parser_(&control_stream_),
message_parser_(kDefaultMoqtVersion, webtrans_, perspective_),
data_stream_(/*stream_id=*/0),
data_parser_(&data_stream_, &data_visitor_) {
// The default object has priority 0x07, so setting this will let the
// parser set the correct value when absent.
data_parser_.set_default_publisher_priority(0x07);
data_parser_.SetGroupOrder(MoqtGroupOrder::kAscending);
}
bool IsDataStream() const {
return std::holds_alternative<MoqtDataStreamType>(message_type_);
}
std::unique_ptr<TestMessageBase> MakeMessage() {
if (IsDataStream()) {
return CreateTestDataStream(std::get<MoqtDataStreamType>(message_type_));
}
return CreateTestMessage(std::get<MoqtMessageType>(message_type_),
webtrans_, FlipPerspective(perspective_));
}
void ProcessData(absl::string_view data, bool fin) {
if (IsDataStream()) {
data_stream_.Receive(data, fin);
data_parser_.ReadAllData();
return;
}
control_stream_.Receive(data, /*fin=*/false);
for (;;) {
absl::StatusOr<MoqtRawControlMessage> message =
control_parser_.ReadNextMessage();
if (!message.ok()) {
if (!absl::IsUnavailable(message.status())) {
control_parsing_error_ = message.status().message();
}
break;
}
absl::Status status =
message_parser_.ParseMessage(*message, [&](auto message) {
control_messages_.push_back(std::move(message));
return absl::OkStatus();
});
if (!status.ok()) {
control_parsing_error_ = status.message();
break;
}
}
}
protected:
size_t messages_received() const {
return IsDataStream() ? data_visitor_.messages_received()
: control_messages_.size();
}
std::optional<TestMessageBase::MessageStructuredData> last_message() const {
if (IsDataStream()) {
return data_visitor_.last_message();
}
if (control_messages_.empty()) {
return std::nullopt;
}
return control_messages_.back();
}
bool end_of_message() const {
return IsDataStream() ? data_visitor_.end_of_message()
: !control_messages_.empty();
}
std::optional<std::string> parsing_error() const {
return IsDataStream() ? data_visitor_.parsing_error()
: control_parsing_error_;
}
std::string object_payload() const {
QUICHE_DCHECK(IsDataStream());
return data_visitor_.object_payload();
}
GeneralizedMessageType message_type_;
bool webtrans_;
quic::Perspective perspective_;
webtransport::test::InMemoryStream control_stream_;
MoqtControlStreamParser control_parser_;
MoqtControlMessageParser message_parser_;
webtransport::test::InMemoryStream data_stream_;
MoqtDataParser data_parser_;
private:
std::vector<TestMessageBase::MessageStructuredData> control_messages_;
std::optional<std::string> control_parsing_error_;
MoqtParserTestVisitor data_visitor_;
};
INSTANTIATE_TEST_SUITE_P(MoqtParserTests, MoqtParserTest,
testing::ValuesIn(GetMoqtParserTestParams()),
ParamNameFormatter);
TEST_P(MoqtParserTest, OneMessage) {
std::unique_ptr<TestMessageBase> message = MakeMessage();
message->MakeObjectEndOfStream();
ProcessData(message->PacketSample(), true);
ASSERT_EQ(messages_received(), 1);
EXPECT_TRUE(message->EqualFieldValues(*last_message()));
EXPECT_TRUE(end_of_message());
if (IsDataStream()) {
EXPECT_EQ(object_payload(), "foo");
}
}
TEST_P(MoqtParserTest, OneMessageWithLongVarints) {
std::unique_ptr<TestMessageBase> message = MakeMessage();
message->ExpandVarints();
ProcessData(message->PacketSample(), false);
EXPECT_EQ(messages_received(), 1);
EXPECT_TRUE(message->EqualFieldValues(*last_message()));
EXPECT_TRUE(end_of_message());
EXPECT_EQ(parsing_error(), std::nullopt);
if (IsDataStream()) {
EXPECT_EQ(object_payload(), "foo");
}
}
TEST_P(MoqtParserTest, TwoPartMessage) {
std::unique_ptr<TestMessageBase> message = MakeMessage();
message->MakeObjectEndOfStream();
// The test Object message has payload for less then half the message length,
// so splitting the message in half will prevent the first half from being
// processed.
size_t first_data_size = message->total_message_size() / 2;
ProcessData(message->PacketSample().substr(0, first_data_size), false);
EXPECT_EQ(messages_received(), 0);
ProcessData(
message->PacketSample().substr(
first_data_size, message->total_message_size() - first_data_size),
true);
EXPECT_EQ(messages_received(), 1);
EXPECT_TRUE(message->EqualFieldValues(*last_message()));
EXPECT_TRUE(end_of_message());
EXPECT_FALSE(parsing_error().has_value());
if (IsDataStream()) {
EXPECT_EQ(object_payload(), "foo");
}
}
TEST_P(MoqtParserTest, OneByteAtATime) {
std::unique_ptr<TestMessageBase> message = MakeMessage();
message->MakeObjectEndOfStream();
for (size_t i = 0; i < message->total_message_size(); ++i) {
EXPECT_EQ(messages_received(), 0);
EXPECT_FALSE(end_of_message());
bool last = i == (message->total_message_size() - 1);
ProcessData(message->PacketSample().substr(i, 1), last);
}
EXPECT_EQ(messages_received(), 1);
EXPECT_TRUE(message->EqualFieldValues(*last_message()));
EXPECT_TRUE(end_of_message());
EXPECT_FALSE(parsing_error().has_value());
if (IsDataStream()) {
EXPECT_EQ(object_payload(), "foo");
}
}
// In OneByteAtATime, the message is received one byte at a time, and
// immediately processed; here, it is received all at once, but the stream
// receive buffer is represented as a sequence of one-byte chunks.
TEST_P(MoqtParserTest, OneByteAtATimePeek) {
control_stream_.set_peek_one_byte_at_a_time(true);
data_stream_.set_peek_one_byte_at_a_time(true);
std::unique_ptr<TestMessageBase> message = MakeMessage();
message->MakeObjectEndOfStream();
ProcessData(message->PacketSample(), true);
ASSERT_EQ(messages_received(), 1);
EXPECT_TRUE(message->EqualFieldValues(*last_message()));
EXPECT_TRUE(end_of_message());
if (IsDataStream()) {
EXPECT_EQ(object_payload(), "foo");
}
}
TEST_P(MoqtParserTest, OneByteAtATimeLongerVarints) {
std::unique_ptr<TestMessageBase> message = MakeMessage();
message->ExpandVarints();
message->MakeObjectEndOfStream();
for (size_t i = 0; i < message->total_message_size(); ++i) {
EXPECT_EQ(messages_received(), 0);
EXPECT_FALSE(end_of_message());
bool last = i == (message->total_message_size() - 1);
ProcessData(message->PacketSample().substr(i, 1), last);
}
EXPECT_EQ(messages_received(), 1);
EXPECT_TRUE(message->EqualFieldValues(*last_message()));
EXPECT_TRUE(end_of_message());
EXPECT_FALSE(parsing_error().has_value());
if (IsDataStream()) {
EXPECT_EQ(object_payload(), "foo");
}
}
TEST_P(MoqtParserTest, TwoBytesAtATime) {
std::unique_ptr<TestMessageBase> message = MakeMessage();
message->MakeObjectEndOfStream();
for (size_t i = 0; i < message->total_message_size(); i += 3) {
EXPECT_EQ(messages_received(), 0);
EXPECT_FALSE(end_of_message());
bool last = (i + 3) >= message->total_message_size();
ProcessData(message->PacketSample().substr(i, 3), last);
}
EXPECT_EQ(messages_received(), 1);
EXPECT_TRUE(message->EqualFieldValues(*last_message()));
EXPECT_TRUE(end_of_message());
EXPECT_FALSE(parsing_error().has_value());
if (IsDataStream()) {
EXPECT_EQ(object_payload(), "foo");
}
}
TEST_P(MoqtParserTest, EarlyFin) {
if (!IsDataStream()) {
return;
}
std::unique_ptr<TestMessageBase> message = MakeMessage();
size_t first_data_size = message->total_message_size() - 1;
ProcessData(message->PacketSample().substr(0, first_data_size), true);
EXPECT_EQ(messages_received(), 0);
EXPECT_THAT(parsing_error(),
AnyOf("FIN after incomplete message",
"FIN received at an unexpected point in the stream"));
}
TEST_P(MoqtParserTest, SeparateEarlyFin) {
if (!IsDataStream()) {
return;
}
std::unique_ptr<TestMessageBase> message = MakeMessage();
size_t first_data_size = message->total_message_size() - 1;
ProcessData(message->PacketSample().substr(0, first_data_size), false);
ProcessData(absl::string_view(), true);
EXPECT_EQ(messages_received(), 0);
EXPECT_THAT(parsing_error(),
AnyOf("FIN after incomplete message",
"FIN received at an unexpected point in the stream"));
}
TEST_P(MoqtParserTest, PayloadLengthTooLong) {
if (IsDataStream()) {
return;
}
MoqtMessageType type = std::get<MoqtMessageType>(message_type_);
if (type == MoqtMessageType::kSubscribeOk ||
type == MoqtMessageType::kFetchOk || type == MoqtMessageType::kPublish) {
// These message types have properties, which use the length field to
// determine the size. It is therefore not processed correctly.
return;
}
std::unique_ptr<TestMessageBase> message = MakeMessage();
message->IncreasePayloadLengthByOne();
ProcessData(message->PacketSample(), false);
EXPECT_EQ(messages_received(), 0);
EXPECT_TRUE(parsing_error().has_value());
}
TEST_P(MoqtParserTest, PayloadLengthTooShort) {
if (IsDataStream()) {
return;
}
std::unique_ptr<TestMessageBase> message = MakeMessage();
message->DecreasePayloadLengthByOne();
if (std::get<MoqtMessageType>(message_type_) == MoqtMessageType::kGoAway) {
// GOAWAY has a variant that is one byte shorter, so decrease by another.
message->DecreasePayloadLengthByOne();
}
ProcessData(message->PacketSample(), false);
EXPECT_EQ(messages_received(), 0);
EXPECT_TRUE(parsing_error().has_value());
}
// Tests for message-specific error cases, and behaviors for a single message
// type.
class MoqtMessageSpecificTest : public quic::test::QuicTest {
public:
MoqtMessageSpecificTest() {}
absl::StatusOr<std::vector<AnyMoqtControlMessage>> ParseAllMessages(
absl::string_view data,
absl::string_view moqt_version = kDefaultMoqtVersion,
bool uses_web_transport = true,
quic::Perspective perspective = quic::Perspective::IS_SERVER) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
stream.Receive(data, /*fin=*/true);
MoqtControlStreamParser stream_parser(&stream);
stream_parser.set_allow_fin(true);
MoqtControlMessageParser message_parser(moqt_version, uses_web_transport,
perspective);
std::vector<AnyMoqtControlMessage> result;
while (!stream_parser.fin_read()) {
absl::StatusOr<MoqtRawControlMessage> raw_message =
stream_parser.ReadNextMessage();
// ParseAllMessages expects a sequence of complete messages.
if (absl::IsUnavailable(raw_message.status())) {
return absl::InvalidArgumentError("Incomplete control message");
}
QUICHE_RETURN_IF_ERROR(raw_message.status());
QUICHE_RETURN_IF_ERROR(
message_parser.ParseMessage(*raw_message, [&](auto message) {
result.push_back(std::move(message));
return absl::OkStatus();
}));
}
return std::move(result);
}
static constexpr bool kWebTrans = true;
static constexpr bool kRawQuic = false;
};
// Send the header + some payload, pure payload, then pure payload to end the
// message.
TEST_F(MoqtMessageSpecificTest, ThreePartObject) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor data_visitor;
MoqtDataParser parser(&stream, &data_visitor);
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(1, 1, true, false, true);
auto message = std::make_unique<StreamHeaderSubgroupMessage>(type);
EXPECT_TRUE(message->SetPayloadLength(14));
message->set_wire_image_size(message->total_message_size() - 11);
stream.Receive(message->PacketSample(), false);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 0);
EXPECT_TRUE(message->EqualFieldValues(*data_visitor.last_message()));
EXPECT_FALSE(data_visitor.end_of_message());
EXPECT_EQ(data_visitor.object_payload(), "foo");
// second part
stream.Receive("bar", false);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 0);
EXPECT_TRUE(message->EqualFieldValues(*data_visitor.last_message()));
EXPECT_FALSE(data_visitor.end_of_message());
EXPECT_EQ(data_visitor.object_payload(), "foobar");
// third part includes FIN
stream.Receive("deadbeef", true);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 1);
EXPECT_TRUE(message->EqualFieldValues(*data_visitor.last_message()));
EXPECT_TRUE(data_visitor.end_of_message());
EXPECT_TRUE(data_visitor.fin_received());
EXPECT_EQ(data_visitor.object_payload(), "foobardeadbeef");
EXPECT_FALSE(data_visitor.parsing_error().has_value());
}
// Send the part of header, rest of header + payload, plus payload.
TEST_F(MoqtMessageSpecificTest, ThreePartObjectFirstIncomplete) {
uint8_t payload_length = 51;
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor data_visitor;
MoqtDataParser parser(&stream, &data_visitor);
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(2, 1, false, false, true);
auto message = std::make_unique<StreamHeaderSubgroupMessage>(type);
EXPECT_TRUE(message->SetPayloadLength(payload_length));
// first part
stream.Receive(message->PacketSample().substr(0, 4), false);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 0);
// second part. Add padding to it.
stream.Receive(
message->PacketSample().substr(4, message->total_message_size() - 7),
false);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 0);
EXPECT_TRUE(message->EqualFieldValues(*data_visitor.last_message()));
EXPECT_FALSE(data_visitor.end_of_message());
EXPECT_EQ(data_visitor.object_payload().length(), payload_length - 3);
// third part includes FIN
stream.Receive("bar", true);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 1);
EXPECT_TRUE(message->EqualFieldValues(*data_visitor.last_message()));
EXPECT_TRUE(data_visitor.end_of_message());
EXPECT_TRUE(data_visitor.fin_received());
EXPECT_EQ(*data_visitor.object_payloads().crbegin(), "bar");
EXPECT_FALSE(data_visitor.parsing_error().has_value());
}
TEST_F(MoqtMessageSpecificTest, ObjectSplitInProperty) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor data_visitor;
MoqtDataParser parser(&stream, &data_visitor);
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(2, 1, false, false, true);
auto message = std::make_unique<StreamHeaderSubgroupMessage>(type);
// first part
stream.Receive(message->PacketSample().substr(0, 10), false);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 0);
// second part
stream.Receive(
message->PacketSample().substr(10, sizeof(message->total_message_size())),
false);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 1);
EXPECT_TRUE(data_visitor.last_message().has_value() &&
message->EqualFieldValues(*data_visitor.last_message()));
EXPECT_TRUE(data_visitor.end_of_message());
}
TEST_F(MoqtMessageSpecificTest, StreamHeaderSubgroupFollowOn) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor data_visitor;
MoqtDataParser parser(&stream, &data_visitor);
// first part
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(0, 1, false, false, true);
auto message1 = std::make_unique<StreamHeaderSubgroupMessage>(type);
stream.Receive(message1->PacketSample(), false);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 1);
EXPECT_TRUE(message1->EqualFieldValues(*data_visitor.last_message()));
EXPECT_TRUE(data_visitor.end_of_message());
EXPECT_EQ(data_visitor.object_payload(), "foo");
EXPECT_FALSE(data_visitor.parsing_error().has_value());
// second part
data_visitor.object_payloads().clear();
auto message2 = std::make_unique<StreamMiddlerSubgroupMessage>(type);
stream.Receive(message2->PacketSample(), false);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 2);
EXPECT_TRUE(message2->EqualFieldValues(*data_visitor.last_message()));
EXPECT_TRUE(data_visitor.end_of_message());
EXPECT_EQ(data_visitor.object_payload(), "bar");
EXPECT_FALSE(data_visitor.parsing_error().has_value());
}
TEST_F(MoqtMessageSpecificTest, StreamHeaderSubgroupFollowOnExpandedVarInts) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor data_visitor;
MoqtDataParser parser(&stream, &data_visitor);
// first part
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(0, 1, false, false, true);
auto message1 = std::make_unique<StreamHeaderSubgroupMessage>(type);
message1->ExpandVarints();
stream.Receive(message1->PacketSample(), false);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 1);
EXPECT_TRUE(message1->EqualFieldValues(*data_visitor.last_message()));
EXPECT_TRUE(data_visitor.end_of_message());
EXPECT_EQ(data_visitor.object_payload(), "foo");
EXPECT_FALSE(data_visitor.parsing_error().has_value());
// second part
data_visitor.object_payloads().clear();
auto message2 = std::make_unique<StreamMiddlerSubgroupMessage>(type);
message2->ExpandVarints();
stream.Receive(message2->PacketSample(), false);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 2);
EXPECT_TRUE(message2->EqualFieldValues(*data_visitor.last_message()));
EXPECT_TRUE(data_visitor.end_of_message());
EXPECT_EQ(data_visitor.object_payload(), "bar");
EXPECT_FALSE(data_visitor.parsing_error().has_value());
}
TEST_F(MoqtMessageSpecificTest, ClientSetupMaxAuthTokenCacheSizeAppearsTwice) {
char setup[] = {
0xaf, 0x00, 0x00, 0x09, 0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
0x03, 0x32, // max_auth_token_cache_size = 50
0x00, 0x32, // max_auth_token_cache_size = 50
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(setup, sizeof(setup)));
EXPECT_THAT(parsed, StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Duplicate Setup Option")));
}
TEST_F(MoqtMessageSpecificTest, ServerSetupAuthorizationTokenTagRegister) {
char setup[] = {
0xaf, 0x00, 0x00, 0x08, 0x03, 0x06,
0x01, 0x10, 0x00, 0x62, 0x61, 0x72, // REGISTER 0x01
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(setup, sizeof(setup)),
kDefaultMoqtVersion, true, quic::Perspective::IS_CLIENT);
// No error even though the registration exceeds the max cache size of 0.
QUICHE_EXPECT_OK(parsed.status());
}
TEST_F(MoqtMessageSpecificTest, SetupPathFromServer) {
char setup[] = {
0xaf, 0x00, 0x00, 0x05, 0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(setup, sizeof(setup)),
kDefaultMoqtVersion, true, quic::Perspective::IS_CLIENT);
ASSERT_THAT(parsed.status(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Setup option parsing error")));
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kInvalidPath);
}
TEST_F(MoqtMessageSpecificTest, SetupAuthorityFromServer) {
char setup[] = {
0xaf, 0x00, 0x00, 0x05, 0x05,
0x03, 0x66, 0x6f, 0x6f, // authority = "foo"
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(setup, sizeof(setup)),
kDefaultMoqtVersion, true, quic::Perspective::IS_CLIENT);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kInvalidAuthority);
}
TEST_F(MoqtMessageSpecificTest, SetupPathAppearsTwice) {
char setup[] = {
0xaf, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
0x00, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
}
TEST_F(MoqtMessageSpecificTest, SetupPathOverWebtrans) {
char setup[] = {
0xaf, 0x00, 0x00, 0x05, 0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kWebTrans);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kInvalidPath);
}
TEST_F(MoqtMessageSpecificTest, SetupAuthorityOverWebtrans) {
char setup[] = {
0xaf, 0x00, 0x00, 0x05, 0x05,
0x03, 0x66, 0x6f, 0x6f, // authority = "foo"
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kWebTrans);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kInvalidAuthority);
}
TEST_F(MoqtMessageSpecificTest, SetupPathMissing) {
char setup[] = {
0xaf,
0x00,
0x00,
0x00, // Empty SETUP message.
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kInvalidPath);
}
TEST_F(MoqtMessageSpecificTest, ClientSetupMalformedPath) {
char setup[] = {
0xaf, 0x00, 0x00, 0x05, 0x01, 0x03, 0x66, 0x5c, 0x6f, // path = "f\o"
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kMalformedPath);
}
TEST_F(MoqtMessageSpecificTest, ClientSetupMalformedAuthority) {
char setup[] = {
0xaf, 0x00, 0x00, 0x0a, 0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
0x04, 0x03, 0x66, 0x5c, 0x6f, // authority = "f\o"
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kMalformedAuthority);
}
TEST_F(MoqtMessageSpecificTest, ServerSetupUnknownOptionIsOk) {
char setup[] = {
0xaf, 0x00, 0x00, 0x0a, 0x1f, 0x03, 0x62, 0x61, 0x72, // 0x1f = "bar"
0x00, 0x03, 0x62, 0x61, 0x72, // 0x1f = "bar"
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kRawQuic,
quic::Perspective::IS_CLIENT);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtSetup message = std::get<MoqtSetup>((*parsed)[0]);
EXPECT_EQ(message.options, SetupOptions());
}
TEST_F(MoqtMessageSpecificTest, SubscribeObjectDeliveryTimeoutTwice) {
char subscribe[] = {
0x03, 0x00, 0x12, 0x01, 0x01,
0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x02, // two params
0x02, 0x67, 0x10, // object_delivery_timeout = 10000
0x00, 0x67, 0x10, // object_delivery_timeout = 10000
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
}
TEST_F(MoqtMessageSpecificTest, SubscribeAuthorizationTokenTagDelete) {
char subscribe[] = {
0x03, 0x00, 0x10, 0x01, 0x01,
0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // one param
0x03, 0x02, 0x00, 0x00 // authorization_token = DELETE 0;
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtSubscribe message = std::get<MoqtSubscribe>((*parsed)[0]);
ASSERT_FALSE(message.parameters.authorization_tokens.empty());
EXPECT_EQ(message.parameters.authorization_tokens[0].alias_type,
AuthTokenAliasType::kDelete);
}
TEST_F(MoqtMessageSpecificTest, SubscribeAuthorizationTokenTagRegister) {
char subscribe[] = {
0x03, 0x00, 0x14, 0x01, 0x01, 0x03, 0x66, 0x6f,
0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // one param
0x03, 0x06, 0x01, 0x10, 0x00, 0x62, 0x61, 0x72, // REGISTER 0x01
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtSubscribe message = std::get<MoqtSubscribe>((*parsed)[0]);
ASSERT_FALSE(message.parameters.authorization_tokens.empty());
EXPECT_EQ(message.parameters.authorization_tokens[0].alias_type,
AuthTokenAliasType::kRegister);
}
TEST_F(MoqtMessageSpecificTest,
SubscribeAuthorizationTokenTagUnknownAliasType) {
char subscribe[] = {
0x03, 0x00, 0x10, 0x01, 0x01,
0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // one param
0x03, 0x02, 0x04, 0x07, // authorization_token type 4
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
}
TEST_F(MoqtMessageSpecificTest,
SubscribeAuthorizationTokenTagUnknownTokenType) {
char subscribe[] = {
0x03, 0x00, 0x12, 0x01, 0x01, 0x03,
0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // one param
0x03, 0x04, 0x03, 0x01, 0x00, 0x00 // authorization_token type 1
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
}
TEST_F(MoqtMessageSpecificTest, SubscribeInvalidForward) {
char subscribe[] = {
0x03, 0x00, 0x0e, 0x01, // id
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 2 parameters
0x10, 0x02 // forward = 2
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
}
TEST_F(MoqtMessageSpecificTest, SubscribeInvalidFilter) {
char subscribe[] = {
0x03, 0x00, 0x0f, 0x01, // id
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 1 parameter
0x21, 0x01, 0x10 // filter_type = 0x10
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
}
TEST_F(MoqtMessageSpecificTest, SubscribeMaxRendezvousTimeout) {
char subscribe[] = {
0x03, 0x00, 0x10, 0x01, // id
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 1 parameter
0x04, 0xc0, 0xea, 0x60, // rendezvous_timeout = 60000
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtSubscribe message = std::get<MoqtSubscribe>((*parsed)[0]);
EXPECT_EQ(message.parameters.rendezvous_timeout, kMaxRendezvousTimeout);
}
TEST_F(MoqtMessageSpecificTest, PublishNamespaceAuthorizationTokenTwice) {
char publish_namespace[] = {
0x06, 0x00, 0x15, 0x02, 0x01, 0x03, 0x66,
0x6f, 0x6f, // track_namespace = "foo"
0x02, // 2 params
0x03, 0x05, 0x03, 0x00, 0x62, 0x61, 0x72, // authorization = "bar"
0x00, 0x05, 0x03, 0x00, 0x62, 0x61, 0x72, // authorization = "bar"
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(publish_namespace, sizeof(publish_namespace)),
kDefaultMoqtVersion, kWebTrans);
EXPECT_TRUE(parsed.ok());
EXPECT_EQ(parsed->size(), 1);
}
TEST_F(MoqtMessageSpecificTest, CannotAccessAfterError1) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
stream.Receive("\xff", /*fin=*/true);
MoqtControlStreamParser parser(&stream);
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kFailedPrecondition));
}
TEST_F(MoqtMessageSpecificTest, CannotAccessAfterError2) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
stream.Receive("\x03\xff\xff");
MoqtControlStreamParser parser(&stream);
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kInvalidArgument));
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kFailedPrecondition));
}
TEST_F(MoqtMessageSpecificTest, FinMidType) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
stream.Receive("\xff", /*fin=*/true);
MoqtControlStreamParser parser(&stream);
parser.set_allow_fin(true);
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kInvalidArgument));
}
TEST_F(MoqtMessageSpecificTest, FinMidLength) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
stream.Receive(absl::string_view("\0\0", 2), /*fin=*/true);
MoqtControlStreamParser parser(&stream);
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kInvalidArgument));
}
TEST_F(MoqtMessageSpecificTest, FinMidControlPayload) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
stream.Receive(absl::string_view("\x00\x00\xff ", 4), /*fin=*/false);
MoqtControlStreamParser parser(&stream);
ASSERT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kUnavailable));
stream.Receive("test", /*fin=*/true);
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("250 bytes left in the current message")));
}
TEST_F(MoqtMessageSpecificTest, FinMidDataPayload) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor data_visitor;
MoqtDataParser parser(&stream, &data_visitor);
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(0, 1, true, false, true);
auto message = std::make_unique<StreamHeaderSubgroupMessage>(type);
stream.Receive(
message->PacketSample().substr(0, message->total_message_size() - 1),
true);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 0);
ASSERT_TRUE(data_visitor.parsing_error().has_value());
EXPECT_THAT(
data_visitor.parsing_error().value(),
AnyOf(HasSubstr("FIN after incomplete message"),
HasSubstr("FIN received at an unexpected point in the stream")));
}
TEST_F(MoqtMessageSpecificTest, FinMidProperty) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor data_visitor;
MoqtDataParser parser(&stream, &data_visitor);
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(0, 1, false, false, true);
auto message = std::make_unique<StreamHeaderSubgroupMessage>(type);
// Read up to the property body and then FIN.
stream.Receive(message->PacketSample().substr(0, 7), true);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 0);
ASSERT_TRUE(data_visitor.parsing_error().has_value());
EXPECT_THAT(
data_visitor.parsing_error().value(),
AnyOf(HasSubstr("FIN after incomplete message"),
HasSubstr("FIN received at an unexpected point in the stream")));
}
TEST_F(MoqtMessageSpecificTest, PartialPayloadThenFin) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor data_visitor;
MoqtDataParser parser(&stream, &data_visitor);
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(1, 1, false, false, true);
auto message = std::make_unique<StreamHeaderSubgroupMessage>(type);
stream.Receive(
message->PacketSample().substr(0, message->total_message_size() - 1),
false);
parser.ReadAllData();
stream.Receive(absl::string_view(), true);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 0);
ASSERT_TRUE(data_visitor.parsing_error().has_value());
EXPECT_THAT(
data_visitor.parsing_error().value(),
AnyOf(HasSubstr("FIN after incomplete message"),
HasSubstr("FIN received at an unexpected point in the stream")));
}
TEST_F(MoqtMessageSpecificTest, FinMidVarint) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor data_visitor;
MoqtDataParser parser(&stream, &data_visitor);
stream.Receive("\x40", true);
parser.ReadAllData();
EXPECT_EQ(data_visitor.messages_received(), 0);
ASSERT_TRUE(data_visitor.parsing_error().has_value());
EXPECT_THAT(
data_visitor.parsing_error().value(),
AnyOf(HasSubstr("FIN after incomplete message"),
HasSubstr("FIN received at an unexpected point in the stream")));
}
TEST_F(MoqtMessageSpecificTest, ControlStreamFinWhenAllowed) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtControlStreamParser parser(&stream);
parser.set_allow_fin(true);
stream.Receive(absl::string_view("\0\0\0", 3), true);
EXPECT_FALSE(parser.fin_read());
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kOk));
EXPECT_TRUE(parser.fin_read());
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kFailedPrecondition));
EXPECT_TRUE(parser.fin_read());
}
TEST_F(MoqtMessageSpecificTest, ControlStreamFinWhenAllowedSeparateFin) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtControlStreamParser parser(&stream);
parser.set_allow_fin(true);
stream.Receive(absl::string_view("\0\0\0", 3), false);
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kOk));
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kUnavailable));
EXPECT_FALSE(parser.fin_read());
stream.Receive(absl::string_view(), true);
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kUnavailable));
EXPECT_TRUE(parser.fin_read());
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kFailedPrecondition));
}
TEST_F(MoqtMessageSpecificTest, ControlStreamFinWhenDisallowed) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtControlStreamParser parser(&stream);
stream.Receive(absl::string_view(), true);
EXPECT_FALSE(parser.fin_read());
EXPECT_THAT(parser.ReadNextMessage().status(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("FIN on a control stream")));
}
TEST_F(MoqtMessageSpecificTest, InvalidObjectStatus) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor data_visitor;
MoqtDataParser parser(&stream, &data_visitor);
char stream_header_subgroup[] = {
0x15, // type field
0x04, 0x05, 0x08, // varints
0x07, // publisher priority
0x06, 0x00, 0x00, 0x0f, // object middler; status = 0x0f
};
stream.Receive(
absl::string_view(stream_header_subgroup, sizeof(stream_header_subgroup)),
false);
parser.ReadAllData();
ASSERT_TRUE(data_visitor.parsing_error().has_value());
EXPECT_THAT(data_visitor.parsing_error().value(),
HasSubstr("Invalid object status provided"));
}
TEST_F(MoqtMessageSpecificTest, Setup2KB) {
char big_message[2 * kMaxMessageHeaderSize];
quic::QuicDataWriter writer(sizeof(big_message), big_message);
writer.WriteMoqVarInt(static_cast<uint64_t>(MoqtMessageType::kSetup));
writer.WriteUInt16(8 + kMaxMessageHeaderSize);
writer.WriteMoqVarInt(0x1); // version
writer.WriteMoqVarInt(0x1); // num_params
writer.WriteMoqVarInt(0xbeef); // unknown param
writer.WriteMoqVarInt(kMaxMessageHeaderSize); // very long parameter
writer.WriteRepeatedByte(0x04, kMaxMessageHeaderSize);
// Send incomplete message
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(big_message, writer.length()),
kDefaultMoqtVersion, true, quic::Perspective::IS_CLIENT);
EXPECT_THAT(
parsed.status(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("control message exceeds the maximum allowed size")));
}
TEST_F(MoqtMessageSpecificTest, UnknownMessageType) {
char message[7];
quic::QuicDataWriter writer(sizeof(message), message);
writer.WriteMoqVarInt(0xbeef); // unknown message type
writer.WriteUInt16(0x1); // length
writer.WriteMoqVarInt(0x1); // payload
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(message, writer.length()));
EXPECT_THAT(parsed.status(),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Unknown control message type 0xbeef")));
}
TEST_F(MoqtMessageSpecificTest, SubscribeNoParameters) {
char subscribe[] = {
0x03, 0x00, 0x0c, 0x01, // request_id = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x00, // 0 parameters
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtSubscribe message = std::get<MoqtSubscribe>((*parsed)[0]);
EXPECT_FALSE(message.parameters.object_delivery_timeout.has_value());
EXPECT_FALSE(message.parameters.subgroup_delivery_timeout.has_value());
EXPECT_FALSE(message.parameters.forward_has_value());
EXPECT_FALSE(message.parameters.subscription_filter.has_value());
EXPECT_FALSE(message.parameters.group_order.has_value());
EXPECT_FALSE(message.parameters.oack_window_size.has_value());
EXPECT_TRUE(message.parameters.authorization_tokens.empty());
EXPECT_FALSE(message.parameters.expires.has_value());
EXPECT_FALSE(message.parameters.subscriber_priority);
EXPECT_FALSE(message.parameters.largest_object.has_value());
EXPECT_FALSE(message.parameters.new_group_request.has_value());
EXPECT_FALSE(message.parameters.track_namespace_prefix.has_value());
}
TEST_F(MoqtMessageSpecificTest, SubscribeUnknownParameter) {
char subscribe[] = {
0x03, 0x00, 0x0f, 0x01, // request_id = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 0 parameters
0x40, 0x60, 0x01, // unknown parameter = 0x60
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
}
TEST_F(MoqtMessageSpecificTest, LargestObject) {
char subscribe[] = {
0x03, 0x00, 0x0f, 0x01, // request_id = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 1 parameter
0x21, 0x01, 0x02, // filter_type = kLargestObject
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtSubscribe message = std::get<MoqtSubscribe>((*parsed)[0]);
ASSERT_TRUE(message.parameters.subscription_filter.has_value());
SubscriptionFilter& filter = *message.parameters.subscription_filter;
EXPECT_TRUE(filter.type() == MoqtFilterType::kLargestObject);
}
TEST_F(MoqtMessageSpecificTest, InvalidDeliveryOrder) {
char subscribe[] = {
0x03, 0x00, 0x0e, 0x01, // id
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 1 parameter
0x22, 0x03, // invalid group order = 3
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
}
TEST_F(MoqtMessageSpecificTest, NextGroupStart) {
char subscribe[] = {
0x03, 0x00, 0x0f, 0x01, // id
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 1 parameter
0x21, 0x01, 0x01, // filter_type = kNextGroupStart
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtSubscribe message = std::get<MoqtSubscribe>((*parsed)[0]);
ASSERT_TRUE(message.parameters.subscription_filter.has_value());
SubscriptionFilter& filter = *message.parameters.subscription_filter;
EXPECT_TRUE(filter.type() == MoqtFilterType::kNextGroupStart);
}
TEST_F(MoqtMessageSpecificTest, AbsoluteRange) {
char subscribe[] = {
0x03, 0x00, 0x12, 0x01, // id
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 1 parameter
0x21, 0x04, 0x04, 0x04, 0x01,
0x03 // filter_type = kAbsoluteRange
// (4,1) to 7
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtSubscribe message = std::get<MoqtSubscribe>((*parsed)[0]);
ASSERT_TRUE(message.parameters.subscription_filter.has_value());
SubscriptionFilter& filter = *message.parameters.subscription_filter;
EXPECT_TRUE(filter.type() == MoqtFilterType::kAbsoluteRange &&
filter.start() == Location(4, 1) && filter.end_group() == 7);
}
TEST_F(MoqtMessageSpecificTest, AbsoluteRangeEndGroupTooLow) {
char subscribe[] = {
0x03, 0x00, 0x12, 0x01, // id
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 1 parameter
0x21, 0x04, 0x04, 0x04, 0x01, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff // filter_type = kAbsoluteRange
// (4,1) to 3
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kKeyValueFormattingError);
}
TEST_F(MoqtMessageSpecificTest, AbsoluteRangeExactlyOneGroup) {
char subscribe[] = {
0x03, 0x00, 0x12, 0x01, // id
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, // 1 parameter
0x21, 0x04, 0x04, 0x04, 0x01,
0x00 // filter_type = kAbsoluteRange
// (4,1) to 4
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtSubscribe message = std::get<MoqtSubscribe>((*parsed)[0]);
EXPECT_EQ(message.parameters.subscription_filter->end_group(), 4);
}
TEST_F(MoqtMessageSpecificTest, RequestUpdateEndGroupTooLow) {
char request_update[] = {
0x02, 0x00, 0x08, 0x02, // request ID
0x01, 0x21, 0x04, 0x04, 0x04, 0x01, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // filter
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(request_update, sizeof(request_update)),
kDefaultMoqtVersion, kRawQuic);
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kKeyValueFormattingError);
}
TEST_F(MoqtMessageSpecificTest, ObjectAckNegativeDelta) {
char object_ack[] = {
0xb1, 0x84, 0x00, 0x04, // type
0x10, 0x20, // group, object
0x80, 0x81, // -0x40 time delta
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(object_ack, sizeof(object_ack)),
kDefaultMoqtVersion, kRawQuic);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtObjectAck message = std::get<MoqtObjectAck>((*parsed)[0]);
EXPECT_EQ(message.group_id, 0x10);
EXPECT_EQ(message.object_id, 0x20);
EXPECT_EQ(message.delta_from_deadline,
quic::QuicTimeDelta::FromMicroseconds(-0x40));
}
TEST_F(MoqtMessageSpecificTest, AllMessagesTogether) {
std::string buffer;
for (MoqtMessageType type : kMessageTypes) {
std::unique_ptr<TestMessageBase> message =
CreateTestMessage(type, kRawQuic, quic::Perspective::IS_SERVER);
buffer += message->PacketSample();
}
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
buffer, kDefaultMoqtVersion, kRawQuic, quic::Perspective::IS_CLIENT);
ASSERT_TRUE(parsed.ok());
}
TEST_F(MoqtMessageSpecificTest, DatagramSuccessful) {
for (MoqtDatagramType datagram_type : AllMoqtDatagramTypes()) {
ObjectDatagramMessage message(datagram_type);
MoqtObject object;
bool use_default_priority;
absl::StatusOr<absl::string_view> payload =
ParseDatagram(message.PacketSample(), object, use_default_priority);
QUICHE_ASSERT_OK(payload);
EXPECT_EQ(use_default_priority, datagram_type.has_default_priority());
if (use_default_priority) {
object.publisher_priority = message.publisher_priority();
}
TestMessageBase::MessageStructuredData object_metadata =
TestMessageBase::MessageStructuredData(object);
EXPECT_TRUE(message.EqualFieldValues(object_metadata));
if (datagram_type.has_status()) {
EXPECT_TRUE(payload->empty());
} else {
EXPECT_EQ(*payload, "foo");
}
}
}
TEST_F(MoqtMessageSpecificTest, DatagramSuccessfulExpandVarints) {
for (MoqtDatagramType datagram_type : AllMoqtDatagramTypes()) {
ObjectDatagramMessage message(datagram_type);
message.ExpandVarints();
MoqtObject object;
bool check_priority;
absl::StatusOr<absl::string_view> payload =
ParseDatagram(message.PacketSample(), object, check_priority);
QUICHE_ASSERT_OK(payload);
EXPECT_EQ(check_priority, datagram_type.has_default_priority());
if (check_priority) {
object.publisher_priority = message.publisher_priority();
}
TestMessageBase::MessageStructuredData object_metadata =
TestMessageBase::MessageStructuredData(object);
EXPECT_TRUE(message.EqualFieldValues(object_metadata));
if (datagram_type.has_status()) {
EXPECT_TRUE(payload->empty());
} else {
EXPECT_EQ(*payload, "foo");
}
}
}
TEST_F(MoqtMessageSpecificTest, WrongMessageInDatagram) {
char payload[] = {0x33, 0x10, 0x20};
MoqtObject object;
bool check_priority;
EXPECT_TRUE(absl::IsInvalidArgument(
ParseDatagram(absl::string_view(payload, sizeof(payload)), object,
check_priority)
.status()));
}
TEST_F(MoqtMessageSpecificTest, TruncatedDatagram) {
ObjectDatagramMessage message(
MoqtDatagramType(false, true, false, false, false));
message.set_wire_image_size(4);
MoqtObject object;
bool check_priority;
EXPECT_TRUE(absl::IsInvalidArgument(
ParseDatagram(message.PacketSample(), object, check_priority).status()));
}
TEST_F(MoqtMessageSpecificTest, VeryTruncatedDatagram) {
char message = 0x40;
MoqtObject object;
bool check_priority;
EXPECT_TRUE(absl::IsInvalidArgument(
ParseDatagram(absl::string_view(&message, 1), object, check_priority)
.status()));
}
TEST_F(MoqtMessageSpecificTest, SubscribeOkInvalidDeliveryOrder) {
SubscribeOkMessage subscribe_ok;
subscribe_ok.SetInvalidDeliveryOrder();
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(subscribe_ok.PacketSample(), kDefaultMoqtVersion,
/*uses_web_transport=*/false);
EXPECT_FALSE(parsed.ok());
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
EXPECT_THAT(parsed.status().message(),
HasSubstr("Invalid SUBSCRIBE_OK track properties"));
}
TEST_F(MoqtMessageSpecificTest, SubscribeOkExpirationIsZero) {
char subscribe_ok[] = {
0x04, 0x00, 0x04, 0x01, // track_alias = 1
0x01, 0x08, 0x00 // expires = 0
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe_ok, sizeof(subscribe_ok)),
kDefaultMoqtVersion, /*uses_web_transport=*/false);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1u);
MoqtSubscribeOk message = std::get<MoqtSubscribeOk>((*parsed)[0]);
EXPECT_EQ(message.parameters.expires, quic::QuicTimeDelta::Infinite());
}
TEST_F(MoqtMessageSpecificTest, FetchWholeGroup) {
FetchMessage fetch;
fetch.SetEndObject(5, std::nullopt);
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(fetch.PacketSample(), kDefaultMoqtVersion,
/*uses_web_transport=*/false);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1u);
MoqtFetch parse_result = std::get<MoqtFetch>((*parsed)[0]);
auto standalone = std::get<StandaloneFetch>(parse_result.fetch);
EXPECT_EQ(standalone.end_location, Location(5, kMaxObjectId));
}
TEST_F(MoqtMessageSpecificTest, FetchInvalidRange) {
FetchMessage fetch;
fetch.SetEndObject(1, 1);
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(fetch.PacketSample(), kDefaultMoqtVersion,
/*uses_web_transport=*/false);
EXPECT_FALSE(parsed.ok());
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
EXPECT_THAT(parsed.status().message(),
HasSubstr("End object comes before start object in FETCH"));
}
TEST_F(MoqtMessageSpecificTest, FetchInvalidRange2) {
FetchMessage fetch;
fetch.SetEndObject(0, std::nullopt);
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(fetch.PacketSample(), kDefaultMoqtVersion,
/*uses_web_transport=*/false);
EXPECT_FALSE(parsed.ok());
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
EXPECT_THAT(parsed.status().message(),
HasSubstr("End object comes before start object in FETCH"));
}
// All messages with TrackNamespace use ReadTrackNamespace. Use
// PUBLISH_NAMESPACE.
TEST_F(MoqtMessageSpecificTest, EmptyNamespace) {
char publish_namespace[7] = {
0x06, 0x00, 0x04, 0x02, // request_id = 2
0x01, 0x00, // one empty namespace element
0x00, // no parameters
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(publish_namespace, sizeof(publish_namespace)),
kDefaultMoqtVersion, /*uses_web_transport=*/false);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1u);
--publish_namespace[2]; // Length is now 3.
--publish_namespace[4]; // 0 elements.
parsed = ParseAllMessages(
absl::string_view(publish_namespace, sizeof(publish_namespace) - 1),
kDefaultMoqtVersion, /*uses_web_transport=*/false);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1u);
EXPECT_TRUE(
std::get<MoqtPublishNamespace>(parsed->at(0)).track_namespace.empty());
}
TEST_F(MoqtMessageSpecificTest, FullTrackNameEmptyNamespace) {
char subscribe[] = {
0x03, 0x00, 0x08, 0x01, // request_id = 1
0x00, // 0 namespace elements
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x00, // 0 parameters
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)),
kDefaultMoqtVersion, kRawQuic);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtSubscribe message = std::get<MoqtSubscribe>((*parsed)[0]);
EXPECT_TRUE(message.full_track_name.track_namespace().empty());
EXPECT_EQ(message.full_track_name.name(), "abcd");
}
TEST_F(MoqtMessageSpecificTest, NamespaceTooLarge) {
char publish_namespace[39] = {
0x06, 0x00, 0x23, 0x02, // type, length = 35, request_id = 2
0x20, // 32 namespace elements. This is the maximum.
};
// 32 empty namespace elements + no parameters.
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(publish_namespace, sizeof(publish_namespace) - 1),
kDefaultMoqtVersion, /*uses_web_transport=*/false);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1u);
++publish_namespace[2]; // Add one element.
++publish_namespace[4];
parsed = ParseAllMessages(
absl::string_view(publish_namespace, sizeof(publish_namespace)),
kDefaultMoqtVersion, /*uses_web_transport=*/false);
EXPECT_FALSE(parsed.ok());
EXPECT_THAT(parsed.status().message(),
HasSubstr("Invalid number of namespace elements"));
}
TEST_F(MoqtMessageSpecificTest, RelativeJoiningFetch) {
RelativeJoiningFetchMessage message;
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(message.PacketSample(), kDefaultMoqtVersion,
/*uses_web_transport=*/false);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1u);
EXPECT_TRUE(std::holds_alternative<MoqtFetch>((*parsed)[0]));
}
TEST_F(MoqtMessageSpecificTest, AbsoluteJoiningFetch) {
AbsoluteJoiningFetchMessage message;
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(message.PacketSample(), kDefaultMoqtVersion,
/*uses_web_transport=*/false);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1u);
EXPECT_TRUE(std::holds_alternative<MoqtFetch>((*parsed)[0]));
}
TEST_F(MoqtMessageSpecificTest, InvalidSubscribeNamespaceOption) {
char subscribe_namespace[] = {
0x11, 0x00, 0x11, 0x01, // request_id = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // namespace = "foo"
0x03, // subscribe_options invalid
0x02, // 2 parameters
0x03, 0x05, 0x03, 0x00, 0x62, 0x61, 0x72, // authorization_tag = "bar"
0x0d, 0x01, // forward = true
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(subscribe_namespace, sizeof(subscribe_namespace)),
kDefaultMoqtVersion, /*uses_web_transport=*/false);
EXPECT_FALSE(parsed.ok());
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
}
TEST_F(MoqtMessageSpecificTest, ParseKeyValuePairListIntegerOverflow) {
char setup[] = {
0xaf, 0x00, 0x00, 0x0b, // kSetup, length = 11
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, // type_diff = max
0x00, // string length = 0
0x01, // type_diff = 1 (overflows)
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(setup, sizeof(setup)), kDefaultMoqtVersion, kRawQuic);
EXPECT_FALSE(parsed.ok());
EXPECT_EQ(ExtractMoqtErrorForStatus(parsed.status()),
MoqtError::kProtocolViolation);
EXPECT_THAT(parsed.status().message(),
HasSubstr("Integer overflow encountered"));
}
class MoqtDataParserStateMachineTest : public quic::test::QuicTest {
protected:
MoqtDataParserStateMachineTest()
: stream_(/*stream_id=*/0), parser_(&stream_, &visitor_) {
parser_.SetGroupOrder(MoqtGroupOrder::kAscending);
}
webtransport::test::InMemoryStream stream_;
MoqtParserTestVisitor visitor_;
MoqtDataParser parser_;
};
TEST_F(MoqtDataParserStateMachineTest, ReadAll) {
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(0, 1, false, false, true);
stream_.Receive(StreamHeaderSubgroupMessage(type).PacketSample());
stream_.Receive(StreamMiddlerSubgroupMessage(type).PacketSample());
parser_.ReadAllData();
ASSERT_EQ(visitor_.messages_received(), 2);
EXPECT_EQ(visitor_.object_payloads()[0], "foo");
EXPECT_EQ(visitor_.object_payloads()[1], "bar");
stream_.Receive("", /*fin=*/true);
parser_.ReadAllData();
EXPECT_EQ(visitor_.parsing_error(), std::nullopt);
EXPECT_TRUE(visitor_.fin_received());
}
TEST_F(MoqtDataParserStateMachineTest, ReadObjects) {
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(0, 1, true, false, true);
stream_.Receive(StreamHeaderSubgroupMessage(type).PacketSample());
stream_.Receive(StreamMiddlerSubgroupMessage(type).PacketSample(),
/*fin=*/true);
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 1);
EXPECT_EQ(visitor_.object_payloads()[0], "foo");
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 2);
EXPECT_EQ(visitor_.object_payloads()[1], "bar");
EXPECT_EQ(visitor_.parsing_error(), std::nullopt);
EXPECT_TRUE(visitor_.fin_received());
}
TEST_F(MoqtDataParserStateMachineTest, ReadTypeThenObjects) {
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(1, 1, false, false, true);
stream_.Receive(StreamHeaderSubgroupMessage(type).PacketSample());
stream_.Receive(StreamMiddlerSubgroupMessage(type).PacketSample(),
/*fin=*/true);
parser_.ReadStreamType();
ASSERT_EQ(visitor_.messages_received(), 0);
EXPECT_TRUE(parser_.stream_type().has_value() &&
parser_.stream_type()->IsSubgroup());
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 1);
EXPECT_EQ(visitor_.object_payloads()[0], "foo");
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 2);
EXPECT_EQ(visitor_.object_payloads()[1], "bar");
EXPECT_EQ(visitor_.parsing_error(), std::nullopt);
EXPECT_TRUE(visitor_.fin_received());
}
TEST_F(MoqtDataParserStateMachineTest, ReadTypeThenObjectsFetch) {
for (MoqtFetchSerialization serialization : AllMoqtFetchSerializations()) {
SCOPED_TRACE(testing::Message() << "flags: " << serialization.value());
MoqtParserTestVisitor visitor;
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtDataParser parser(&stream, &visitor);
parser.SetGroupOrder(MoqtGroupOrder::kAscending);
StreamHeaderFetchMessage header;
StreamMiddlerFetchMessage middler(serialization);
stream.Receive(header.PacketSample());
stream.Receive(middler.PacketSample(), /*fin=*/true);
parser.ReadStreamType();
ASSERT_EQ(visitor.messages_received(), 0);
parser.ReadAtMostOneObject();
ASSERT_EQ(visitor.messages_received(), 1);
EXPECT_TRUE(header.EqualFieldValues(visitor.last_message().value()));
EXPECT_EQ(visitor.object_payloads()[0], "foo");
parser.ReadAtMostOneObject();
ASSERT_EQ(visitor.messages_received(), 2);
EXPECT_TRUE(middler.EqualFieldValues(visitor.last_message().value()));
EXPECT_EQ(visitor.object_payloads()[1], "bar");
EXPECT_EQ(visitor.parsing_error(), std::nullopt);
EXPECT_TRUE(visitor.fin_received());
}
}
TEST_F(MoqtDataParserStateMachineTest, StreamHeaderFetchRefersToPrior) {
char data[] = {0x05, 0x01, 0x00};
for (char value : {0x17, 0x1b}) {
data[2] = value;
MoqtParserTestVisitor visitor;
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtDataParser parser(&stream, &visitor);
parser.SetGroupOrder(MoqtGroupOrder::kAscending);
stream.Receive(absl::string_view(data, sizeof(data)));
parser.ReadStreamType();
parser.ReadAtMostOneObject();
EXPECT_EQ(visitor.parsing_error(),
"Invalid serialization flags for first object");
}
for (char value : {0x0f, 0x1d, 0x1e}) {
data[2] = value;
MoqtParserTestVisitor visitor;
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtDataParser parser(&stream, &visitor);
parser.SetGroupOrder(MoqtGroupOrder::kAscending);
stream.Receive(absl::string_view(data, sizeof(data)));
parser.ReadStreamType();
parser.ReadAtMostOneObject();
EXPECT_EQ(visitor.parsing_error(), "Invalid reference to prior object");
}
}
TEST_F(MoqtDataParserStateMachineTest, DatagramThenPriorSubgroupId) {
char data[] = {0x05, 0x01, 0x5c, 0x05, 0x01, // datagram (5, 1)
0x80, 0x03, 0x61, 0x61, 0x61, // priority, payload
0xff}; // serialization flag to be overwritten
// Iterate through the 2 serializations that refer to the prior subgroup.
for (char value : {0x01, 0x02}) {
data[10] = value;
MoqtParserTestVisitor visitor;
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtDataParser parser(&stream, &visitor);
parser.SetGroupOrder(MoqtGroupOrder::kAscending);
stream.Receive(absl::string_view(data, sizeof(data)));
parser.ReadStreamType();
parser.ReadAtMostOneObject();
parser.ReadAtMostOneObject();
EXPECT_EQ(visitor.parsing_error(),
"reference to subgroup ID of prior datagram");
}
}
TEST_F(MoqtDataParserStateMachineTest, InvalidNonexistentRange) {
char data[] = {0x05, 0x01, 0x80, 0x80};
stream_.Receive(absl::string_view(data, sizeof(data)));
parser_.ReadStreamType();
parser_.ReadAtMostOneObject();
EXPECT_EQ(visitor_.parsing_error(), "Invalid serialization flags");
}
TEST_F(MoqtDataParserStateMachineTest, InvalidNonexistentRangeUnknownRange) {
char data[] = {0x05, 0x01, 0x81, 0x8c};
stream_.Receive(absl::string_view(data, sizeof(data)));
parser_.ReadStreamType();
parser_.ReadAtMostOneObject();
EXPECT_EQ(visitor_.parsing_error(), "Invalid serialization flags");
}
TEST_F(MoqtDataParserStateMachineTest, EndRangeIndicators) {
// Header (group 5, subgroup 8, object 6), Range Indicators, Middler
stream_.Receive(StreamHeaderFetchMessage().PacketSample());
char data[] = {0x80, 0x8c, 0x05, 0x07, // non-existent range -> (11, 7)
0x81, 0x0c, 0x05, 0x09}; // unknown range -> (17, 9)
stream_.Receive(absl::string_view(data, sizeof(data)));
std::optional<MoqtFetchSerialization> serialization =
MoqtFetchSerialization::FromValue(0x01);
// Prior subgroup ID + implicit next object ID.
ASSERT_TRUE(serialization.has_value());
StreamMiddlerFetchMessage middler(*serialization);
stream_.Receive(middler.PacketSample(), /*fin=*/true);
parser_.ReadStreamType();
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 1);
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 2);
EXPECT_EQ(visitor_.last_message()->group_id, 11);
EXPECT_EQ(visitor_.last_message()->object_id, 7);
EXPECT_EQ(visitor_.last_message()->object_status,
MoqtObjectStatus::kEndOfNonExistentRange);
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 3);
EXPECT_EQ(visitor_.last_message()->group_id, 17);
EXPECT_EQ(visitor_.last_message()->object_id, 9);
EXPECT_EQ(visitor_.last_message()->object_status,
MoqtObjectStatus::kEndOfUnknownRange);
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 4);
EXPECT_EQ(visitor_.last_message()->group_id, 17);
EXPECT_EQ(visitor_.last_message()->object_id, 10);
// Subgroup ID and publisher priority from the real object before the range
// indicators are preserved.
EXPECT_EQ(visitor_.last_message()->subgroup_id, 8);
EXPECT_EQ(visitor_.last_message()->publisher_priority, 7);
EXPECT_EQ(visitor_.last_message()->object_status, MoqtObjectStatus::kNormal);
EXPECT_EQ(visitor_.parsing_error(), std::nullopt);
EXPECT_TRUE(visitor_.fin_received());
}
TEST_F(MoqtDataParserStateMachineTest, FetchStartsWithEndOfRange) {
// Stream starts with FETCH header (0x05, request_id = 4), followed by
// kEndOfNonExistentRange (0x8c) at (group 5, object 7) + FIN.
char data[] = {0x05, 0x04, 0x80, 0x8c, 0x05, 0x07};
stream_.Receive(absl::string_view(data, sizeof(data)), /*fin=*/true);
parser_.ReadAllData();
ASSERT_EQ(visitor_.messages_received(), 1);
EXPECT_EQ(visitor_.last_message()->group_id, 5);
EXPECT_EQ(visitor_.last_message()->object_id, 7);
EXPECT_EQ(visitor_.last_message()->object_status,
MoqtObjectStatus::kEndOfNonExistentRange);
EXPECT_EQ(visitor_.parsing_error(), std::nullopt);
EXPECT_TRUE(visitor_.fin_received());
// If a subsequent object after an initial End-of-Range tries to reference a
// prior priority or subgroup ID, it must fail with "Invalid reference to
// prior object".
for (char bad_flags : {0x00, 0x01, 0x02}) {
char stream_bytes[] = {0x05, 0x04, 0x80, 0x8c, 0x05, 0x07, bad_flags};
MoqtParserTestVisitor visitor;
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtDataParser parser(&stream, &visitor);
parser.SetGroupOrder(MoqtGroupOrder::kAscending);
stream.Receive(absl::string_view(stream_bytes, sizeof(stream_bytes)));
parser.ReadAllData();
EXPECT_EQ(visitor.parsing_error(), "Invalid reference to prior object");
}
}
TEST_F(MoqtDataParserStateMachineTest, IntegerOverflowObjectId) {
MoqtDataStreamType type = MoqtDataStreamType::Subgroup(
0, 1, /*no_properties=*/true, /*default_priority=*/false,
/*has_first_object=*/true);
stream_.Receive(StreamHeaderSubgroupMessage(type).PacketSample());
char buffer[32];
quic::QuicDataWriter writer(sizeof(buffer), buffer);
ASSERT_TRUE(writer.WriteMoqVarInt(std::numeric_limits<uint64_t>::max() - 5));
ASSERT_TRUE(
writer.WriteBytes("\x03"
"bar",
4));
stream_.Receive(absl::string_view(buffer, writer.length()));
parser_.ReadAllData();
EXPECT_EQ(visitor_.parsing_error(),
"Integer overflow when parsing object ID");
}
TEST_F(MoqtDataParserStateMachineTest, SubgroupHasFirstObjectTrue) {
MoqtDataStreamType type = MoqtDataStreamType::Subgroup(
0, 1, /*no_properties=*/true, /*default_priority=*/false,
/*has_first_object=*/true);
stream_.Receive(StreamHeaderSubgroupMessage(type).PacketSample());
stream_.Receive(StreamMiddlerSubgroupMessage(type).PacketSample(),
/*fin=*/true);
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 1);
ASSERT_TRUE(visitor_.last_message().has_value());
EXPECT_EQ(visitor_.last_message()->first_object_in_subgroup, true);
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 2);
ASSERT_TRUE(visitor_.last_message().has_value());
EXPECT_EQ(visitor_.last_message()->first_object_in_subgroup, false);
EXPECT_EQ(visitor_.parsing_error(), std::nullopt);
EXPECT_TRUE(visitor_.fin_received());
}
TEST_F(MoqtDataParserStateMachineTest, SubgroupHasFirstObjectFalse) {
MoqtDataStreamType type = MoqtDataStreamType::Subgroup(
0, 1, /*no_properties=*/true, /*default_priority=*/false,
/*has_first_object=*/false);
stream_.Receive(StreamHeaderSubgroupMessage(type).PacketSample());
stream_.Receive(StreamMiddlerSubgroupMessage(type).PacketSample(),
/*fin=*/true);
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 1);
ASSERT_TRUE(visitor_.last_message().has_value());
EXPECT_EQ(visitor_.last_message()->first_object_in_subgroup, false);
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 2);
ASSERT_TRUE(visitor_.last_message().has_value());
EXPECT_EQ(visitor_.last_message()->first_object_in_subgroup, false);
EXPECT_EQ(visitor_.parsing_error(), std::nullopt);
EXPECT_TRUE(visitor_.fin_received());
}
TEST_F(MoqtDataParserStateMachineTest, FetchFirstObjectMissing) {
StreamHeaderFetchMessage header;
stream_.Receive(header.PacketSample());
parser_.ReadStreamType();
ASSERT_EQ(visitor_.messages_received(), 0);
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 1);
ASSERT_TRUE(visitor_.last_message().has_value());
EXPECT_FALSE(visitor_.last_message()->first_object_in_subgroup.has_value());
}
TEST_F(MoqtDataParserStateMachineTest, FetchDescendingGroupOrder) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtParserTestVisitor visitor;
MoqtDataParser parser(&stream, &visitor);
parser.SetGroupOrder(MoqtGroupOrder::kDescending);
// Header has group_id = 5, object_id = 6.
StreamHeaderFetchMessage header;
stream.Receive(header.PacketSample());
// Next object: kHasGroupId | kIsDatagram = 0x48, group delta = 1 -> group =
// 5 - (1 + 1) = 3, omitted object_id -> object_id = 7, payload = "bar".
// Third object: kHasGroupId | kIsDatagram = 0x48, group delta = 3 ->
// underflow (3 - (3 + 1) < 0).
char data[] = {0x48, 0x01, 0x03, 'b', 'a', 'r', 0x48, 0x03};
stream.Receive(absl::string_view(data, sizeof(data)));
parser.ReadStreamType();
parser.ReadAtMostOneObject();
ASSERT_EQ(visitor.messages_received(), 1);
EXPECT_EQ(visitor.last_message()->group_id, 5);
parser.ReadAtMostOneObject();
ASSERT_EQ(visitor.messages_received(), 2);
EXPECT_EQ(visitor.last_message()->group_id, 3);
EXPECT_EQ(visitor.last_message()->object_id, 7);
parser.ReadAtMostOneObject();
EXPECT_EQ(visitor.parsing_error(), "Integer overflow when parsing group ID");
}
TEST_F(MoqtDataParserStateMachineTest,
FetchObjectIdDeltaAfterImplicitIncrement) {
// Header has group_id = 5, object_id = 6.
StreamHeaderFetchMessage header;
stream_.Receive(header.PacketSample());
// Object 2: kIsDatagram = 0x40 (omits group ID and object ID) -> group_id =
// 5, object_id = 7, payload = "bar".
// Object 3: kIsDatagram | kHasObjectId = 0x44, object ID delta = 3 ->
// group_id = 5, object_id = 7 + 3 = 10, payload = "baz".
char data[] = {0x40, 0x03, 'b', 'a', 'r', 0x44, 0x03, 0x03, 'b', 'a', 'z'};
stream_.Receive(absl::string_view(data, sizeof(data)), /*fin=*/true);
parser_.ReadAllData();
ASSERT_EQ(visitor_.messages_received(), 3);
EXPECT_EQ(visitor_.last_message()->group_id, 5);
EXPECT_EQ(visitor_.last_message()->object_id, 10);
EXPECT_EQ(visitor_.parsing_error(), std::nullopt);
}
TEST_F(MoqtDataParserStateMachineTest, FetchZeroObjectIdDelta) {
// Header has group_id = 5, object_id = 6.
StreamHeaderFetchMessage header;
stream_.Receive(header.PacketSample());
// Object 2: kIsDatagram | kHasGroupId | kHasObjectId = 0x4c, group delta = 0,
// explicit object ID = 0 -> valid (group_id = 6, object_id = 0).
// Object 3: kIsDatagram | kHasObjectId = 0x44, object ID delta = 0 ->
// duplicate object ID in same group -> parse error.
char data[] = {0x4c, 0x00, 0x00, 0x03, 'b', 'a', 'r', 0x44, 0x00};
stream_.Receive(absl::string_view(data, sizeof(data)));
parser_.ReadStreamType();
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 1);
parser_.ReadAtMostOneObject();
ASSERT_EQ(visitor_.messages_received(), 2);
EXPECT_EQ(visitor_.last_message()->group_id, 6);
EXPECT_EQ(visitor_.last_message()->object_id, 0);
parser_.ReadAtMostOneObject();
EXPECT_EQ(visitor_.parsing_error(), "Duplicate object ID in FETCH stream");
}
TEST_F(MoqtDataParserStateMachineTest, SubgroupObjectHasStatusAndProperties) {
MoqtDataStreamType type =
MoqtDataStreamType::Subgroup(0, 1, /*no_properties=*/false,
/*default_priority=*/false,
/*has_first_object=*/true);
// StreamHeaderSubgroupMessage with payload_length = 0 followed by status =
// kEndOfGroup (0x03) while properties are non-empty.
StreamHeaderSubgroupMessage header(type);
ASSERT_TRUE(header.SetPayloadLength(0));
stream_.Receive(header.PacketSample());
stream_.Receive("\x03", /*fin=*/true);
parser_.ReadAllData();
EXPECT_EQ(visitor_.parsing_error(), "Object has status and properties");
}
TEST_F(MoqtDataParserStateMachineTest, EndOfGroupFinArrivesLater) {
MoqtDataStreamType type = MoqtDataStreamType::Subgroup(
/*subgroup_id=*/8, /*first_object_id=*/6, /*no_properties=*/false,
/*default_priority=*/false, /*has_first_object=*/true,
/*end_of_group=*/true);
StreamHeaderSubgroupMessage header(type);
stream_.Receive(header.PacketSample(), /*fin=*/false);
parser_.ReadAllData();
ASSERT_EQ(visitor_.messages_received(), 1);
EXPECT_TRUE(visitor_.end_of_message());
EXPECT_FALSE(visitor_.fin_received());
ASSERT_TRUE(visitor_.last_message().has_value());
EXPECT_EQ(visitor_.last_message()->object_status, MoqtObjectStatus::kNormal);
EXPECT_EQ(visitor_.last_message()->object_id, 6);
EXPECT_EQ(visitor_.last_message()->subgroup_id, 8);
EXPECT_FALSE(visitor_.last_message()->properties.empty());
EXPECT_EQ(visitor_.object_payload(), "foo");
// When FIN arrives with no additional object, a new empty kEndOfGroup object
// with object_id = 7, subgroup_id = 8, and no properties is delivered.
stream_.Receive("", /*fin=*/true);
parser_.ReadAllData();
ASSERT_EQ(visitor_.messages_received(), 2);
EXPECT_TRUE(visitor_.end_of_message());
EXPECT_TRUE(visitor_.fin_received());
EXPECT_EQ(visitor_.parsing_error(), std::nullopt);
ASSERT_TRUE(visitor_.last_message().has_value());
EXPECT_EQ(visitor_.last_message()->track_alias, 4);
EXPECT_EQ(visitor_.last_message()->group_id, 5);
EXPECT_EQ(visitor_.last_message()->object_id, 7);
EXPECT_EQ(visitor_.last_message()->subgroup_id, 8);
EXPECT_EQ(visitor_.last_message()->publisher_priority, 7);
EXPECT_EQ(visitor_.last_message()->properties, "");
EXPECT_EQ(visitor_.last_message()->object_status,
MoqtObjectStatus::kEndOfGroup);
EXPECT_EQ(visitor_.last_message()->first_object_in_subgroup, false);
EXPECT_EQ(visitor_.last_message()->payload_length, 0);
EXPECT_EQ(visitor_.object_payloads().back(), "");
}
TEST_F(MoqtDataParserStateMachineTest,
EndOfGroupFollowedByAnotherObjectThenFin) {
MoqtDataStreamType type = MoqtDataStreamType::Subgroup(
/*subgroup_id=*/8, /*first_object_id=*/6, /*no_properties=*/false,
/*default_priority=*/false, /*has_first_object=*/true,
/*end_of_group=*/true);
StreamHeaderSubgroupMessage header(type);
stream_.Receive(header.PacketSample(), /*fin=*/false);
parser_.ReadAllData();
ASSERT_EQ(visitor_.messages_received(), 1);
EXPECT_EQ(visitor_.last_message()->object_status, MoqtObjectStatus::kNormal);
EXPECT_EQ(visitor_.last_message()->object_id, 6);
StreamMiddlerSubgroupMessage middler(type);
stream_.Receive(middler.PacketSample(), /*fin=*/false);
parser_.ReadAllData();
ASSERT_EQ(visitor_.messages_received(), 2);
EXPECT_EQ(visitor_.last_message()->object_status, MoqtObjectStatus::kNormal);
uint64_t second_object_id = visitor_.last_message()->object_id;
stream_.Receive("", /*fin=*/true);
parser_.ReadAllData();
ASSERT_EQ(visitor_.messages_received(), 3);
EXPECT_TRUE(visitor_.fin_received());
EXPECT_EQ(visitor_.parsing_error(), std::nullopt);
EXPECT_EQ(visitor_.last_message()->object_id, second_object_id + 1);
EXPECT_EQ(visitor_.last_message()->subgroup_id, 8);
EXPECT_EQ(visitor_.last_message()->properties, "");
EXPECT_EQ(visitor_.last_message()->payload_length, 0);
EXPECT_EQ(visitor_.last_message()->object_status,
MoqtObjectStatus::kEndOfGroup);
}
TEST_F(MoqtDataParserStateMachineTest, EndOfGroupMaxObjectIdOverflowOnFin) {
MoqtDataStreamType type = MoqtDataStreamType::Subgroup(
/*subgroup_id=*/8, /*first_object_id=*/6, /*no_properties=*/true,
/*default_priority=*/true, /*has_first_object=*/true,
/*end_of_group=*/true);
parser_.set_default_publisher_priority(7);
char buffer[64];
quic::QuicDataWriter writer(sizeof(buffer), buffer);
ASSERT_TRUE(writer.WriteMoqVarInt(type.value()));
ASSERT_TRUE(writer.WriteMoqVarInt(4)); // track_alias
ASSERT_TRUE(writer.WriteMoqVarInt(5)); // group_id
ASSERT_TRUE(writer.WriteMoqVarInt(8)); // subgroup_id
ASSERT_TRUE(writer.WriteMoqVarInt(kMaxObjectId)); // object_id
ASSERT_TRUE(writer.WriteMoqVarInt(3)); // payload_length
ASSERT_TRUE(writer.WriteStringPiece("foo"));
stream_.Receive(absl::string_view(buffer, writer.length()), /*fin=*/false);
parser_.ReadAllData();
ASSERT_EQ(visitor_.messages_received(), 1);
EXPECT_EQ(visitor_.last_message()->object_id, kMaxObjectId);
EXPECT_EQ(visitor_.last_message()->object_status, MoqtObjectStatus::kNormal);
stream_.Receive("", /*fin=*/true);
parser_.ReadAllData();
EXPECT_EQ(visitor_.messages_received(), 1);
EXPECT_TRUE(visitor_.fin_received());
}
TEST_F(MoqtMessageSpecificTest, DatagramHasStatusAndProperties) {
// Type 0x21 (kStatus | kProperties), track_alias = 4, group_id = 5,
// object_id = 6, priority = 7, 1-byte property blob, status = kEndOfTrack
// (0x04).
constexpr absl::string_view kBadDatagram(
"\x21\x04\x05\x06\x07\x02\x02\x00\x04", 9);
MoqtObject object;
bool use_default_priority = false;
EXPECT_THAT(ParseDatagram(kBadDatagram, object, use_default_priority),
StatusIs(absl::StatusCode::kInvalidArgument,
"Datagram has status and properties"));
}
TEST_F(MoqtMessageSpecificTest, StreamTypeParserToControlStream) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtStreamTypeParser type_parser(&stream);
stream.Receive("\x03", false);
absl::StatusOr<uint64_t> type = type_parser.ReadStreamType();
EXPECT_THAT(type,
IsOkAndHolds(static_cast<uint64_t>(MoqtMessageType::kSubscribe)));
EXPECT_NE(type_parser.stream(), nullptr);
MoqtControlStreamParser control_parser(std::move(type_parser));
EXPECT_THAT(
type_parser.ReadStreamType(), // NOLINT(bugprone-use-after-move)
StatusIs(absl::StatusCode::kInternal, HasSubstr("moved-from parser")));
EXPECT_EQ(control_parser.stream(), &stream);
}
TEST_F(MoqtMessageSpecificTest, StreamTypeParserToDataStream) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtStreamTypeParser type_parser(&stream);
stream.Receive("\x10", false);
absl::StatusOr<uint64_t> type = type_parser.ReadStreamType();
EXPECT_THAT(type, IsOkAndHolds(0x10));
EXPECT_THAT(type_parser.ReadStreamType(),
IsOkAndHolds(0x10)); // Second read should read the cached value.
EXPECT_NE(type_parser.stream(), nullptr);
MoqtParserTestVisitor data_visitor;
MoqtDataParser data_parser(std::move(type_parser), &data_visitor);
EXPECT_THAT(
type_parser.ReadStreamType(), // NOLINT(bugprone-use-after-move)
StatusIs(absl::StatusCode::kInternal, HasSubstr("moved-from parser")));
EXPECT_EQ(data_parser.stream_type(), MoqtDataStreamType::FromValue(0x10));
}
TEST_F(MoqtMessageSpecificTest, EmptyStreamTypeParserToDataStream) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtStreamTypeParser type_parser(&stream);
MoqtParserTestVisitor data_visitor;
MoqtDataParser data_parser(std::move(type_parser), &data_visitor);
stream.Receive("\x10", false);
data_parser.ReadStreamType();
EXPECT_EQ(data_parser.stream_type(), MoqtDataStreamType::FromValue(0x10));
}
TEST_F(MoqtMessageSpecificTest, StreamTypeParserFinBeforeType) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtStreamTypeParser type_parser(&stream);
stream.Receive("", true);
absl::StatusOr<uint64_t> type = type_parser.ReadStreamType();
EXPECT_THAT(
type,
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"FIN received before or immediately after the stream type")));
}
TEST_F(MoqtMessageSpecificTest, StreamTypeParserFinInTheMiddleOfType) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtStreamTypeParser type_parser(&stream);
stream.Receive("\xff", true);
absl::StatusOr<uint64_t> type = type_parser.ReadStreamType();
EXPECT_THAT(
type,
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"FIN received before or immediately after the stream type")));
}
TEST_F(MoqtMessageSpecificTest, StreamTypeParserFinAfterType) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtStreamTypeParser type_parser(&stream);
stream.Receive("\x03", true);
absl::StatusOr<uint64_t> type = type_parser.ReadStreamType();
EXPECT_THAT(
type,
StatusIs(
absl::StatusCode::kInvalidArgument,
HasSubstr(
"FIN received before or immediately after the stream type")));
}
TEST_F(MoqtMessageSpecificTest, StreamTypeParserFinForPadding) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtStreamTypeParser type_parser(&stream);
char buffer[5];
quic::QuicDataWriter writer(sizeof(buffer), buffer);
ASSERT_TRUE(writer.WriteMoqVarInt(kPaddingStreamType));
stream.Receive(absl::string_view(writer.data(), writer.length()), true);
absl::StatusOr<uint64_t> type = type_parser.ReadStreamType();
EXPECT_THAT(type, IsOkAndHolds(kPaddingStreamType));
}
TEST_F(MoqtMessageSpecificTest, StreamTypeParserMovedFrom) {
webtransport::test::InMemoryStream stream(/*stream_id=*/0);
MoqtStreamTypeParser type_parser(&stream);
MoqtControlStreamParser control_parser(std::move(type_parser));
EXPECT_THAT(
type_parser.ReadStreamType(), // NOLINT(bugprone-use-after-move)
StatusIs(absl::StatusCode::kInternal, HasSubstr("moved-from parser")));
}
TEST_F(MoqtMessageSpecificTest, ParseGoAwayWithoutRequestId) {
GoAwayMessage goaway_message;
goaway_message.RequestStreamMessage();
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(goaway_message.PacketSample(), kDefaultMoqtVersion,
kRawQuic, quic::Perspective::IS_CLIENT);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
EXPECT_TRUE(
goaway_message.EqualFieldValues(std::get<MoqtGoAway>((*parsed)[0])));
}
TEST_F(MoqtMessageSpecificTest, GoAwayNewSessionUriFromClient) {
GoAwayMessage goaway_message;
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(goaway_message.PacketSample(), kDefaultMoqtVersion,
kWebTrans, quic::Perspective::IS_SERVER);
EXPECT_THAT(parsed,
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("New session URI must be empty from client")));
}
TEST_F(MoqtMessageSpecificTest, GoAwayNewSessionUriTooLong) {
auto make_goaway_packet = [](size_t uri_length) {
std::string uri(uri_length, 'a');
std::string buffer(kMaxMessageHeaderSize, '\0');
quic::QuicDataWriter writer(buffer.size(), buffer.data());
QUICHE_CHECK(
writer.WriteMoqVarInt(static_cast<uint64_t>(MoqtMessageType::kGoAway)));
// Payload is: 2-byte varint length (8192 or 8193) + URI bytes + 1-byte
// timeout.
uint16_t payload_length = 2 + uri.length() + 1;
QUICHE_CHECK(writer.WriteUInt16(payload_length));
QUICHE_CHECK(writer.WriteMoqVarInt(uri.length()));
QUICHE_CHECK(writer.WriteStringPiece(uri));
QUICHE_CHECK(writer.WriteMoqVarInt(0));
buffer.resize(writer.length());
return buffer;
};
// Exactly kMaxNewSessionUriLength succeeds.
std::string valid_packet = make_goaway_packet(kMaxNewSessionUriLength);
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed =
ParseAllMessages(valid_packet, kDefaultMoqtVersion, kWebTrans,
quic::Perspective::IS_CLIENT);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
EXPECT_EQ(std::get<MoqtGoAway>((*parsed)[0]).new_session_uri.length(),
kMaxNewSessionUriLength);
// kMaxNewSessionUriLength + 1 fails with "New session URI too long".
std::string invalid_packet = make_goaway_packet(kMaxNewSessionUriLength + 1);
parsed = ParseAllMessages(invalid_packet, kDefaultMoqtVersion, kWebTrans,
quic::Perspective::IS_CLIENT);
EXPECT_THAT(parsed, StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("New session URI too long")));
}
TEST_F(MoqtMessageSpecificTest, DisallowedParameters) {
char subscribe[] = {
0x03, 0x00, 0x0e, 0x01, // request_id = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, 0x08, 0x01, // expires = 1
};
EXPECT_EQ(
ExtractMoqtErrorForStatus(
ParseAllMessages(absl::string_view(subscribe, sizeof(subscribe)))
.status()),
MoqtError::kProtocolViolation);
char subscribe_ok[] = {
0x04, 0x00, 0x04, 0x01, // track_alias = 1
0x01, 0x10, 0x01, // forward = 1
};
EXPECT_EQ(ExtractMoqtErrorForStatus(
ParseAllMessages(
absl::string_view(subscribe_ok, sizeof(subscribe_ok)))
.status()),
MoqtError::kProtocolViolation);
char publish_namespace[] = {
0x06, 0x00, 0x09, 0x01, // request_id = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x01, 0x10, 0x01, // forward = 1
};
EXPECT_EQ(ExtractMoqtErrorForStatus(
ParseAllMessages(absl::string_view(publish_namespace,
sizeof(publish_namespace)))
.status()),
MoqtError::kProtocolViolation);
char track_status[] = {
0x0d, 0x00, 0x0e, 0x01, // request_id = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x01, 0x10, 0x01, // forward = 1
};
EXPECT_EQ(ExtractMoqtErrorForStatus(
ParseAllMessages(
absl::string_view(track_status, sizeof(track_status)))
.status()),
MoqtError::kProtocolViolation);
char subscribe_namespace[] = {
0x50, 0x00, 0x09, 0x01, // request_id = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x01, 0x10, 0x01, // forward = 1
};
EXPECT_EQ(ExtractMoqtErrorForStatus(
ParseAllMessages(absl::string_view(subscribe_namespace,
sizeof(subscribe_namespace)))
.status()),
MoqtError::kProtocolViolation);
char subscribe_tracks[] = {
0x51, 0x00, 0x09, 0x01, // request_id = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x01, 0x08, 0x01, // expires = 1
};
EXPECT_EQ(ExtractMoqtErrorForStatus(
ParseAllMessages(absl::string_view(subscribe_tracks,
sizeof(subscribe_tracks)))
.status()),
MoqtError::kProtocolViolation);
char fetch[] = {
0x16, 0x00, 0x07, 0x01, // request_id = 1
0x02, 0x02, 0x02, // kRelativeJoining, joining_request_id = 2, 2
0x01, 0x10, 0x01, // forward = 1
};
EXPECT_EQ(
ExtractMoqtErrorForStatus(
ParseAllMessages(absl::string_view(fetch, sizeof(fetch))).status()),
MoqtError::kProtocolViolation);
char fetch_ok[] = {
0x18, 0x00, 0x06,
0x00, // end_of_track = false
0x05, 0x04, // end_location = 5, 3
0x01, 0x10, 0x01, // forward = 1
};
EXPECT_EQ(ExtractMoqtErrorForStatus(
ParseAllMessages(absl::string_view(fetch_ok, sizeof(fetch_ok)))
.status()),
MoqtError::kProtocolViolation);
char publish[] = {
0x1d, 0x00, 0x0f, 0x01, // request_id = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
0x04, // track_alias = 4
0x01, 0x20, 0x01, // subscriber_priority = 1
};
EXPECT_EQ(ExtractMoqtErrorForStatus(
ParseAllMessages(absl::string_view(publish, sizeof(publish)))
.status()),
MoqtError::kProtocolViolation);
}
TEST_F(MoqtMessageSpecificTest, RequestErrorRedirect) {
// Valid redirect from server with connect_uri and full_track_name.
char redirect_from_server[] = {
0x05, 0x00, 0x14,
0x34, // error_code = kRedirect (0x34)
0x00, // retry_interval = 0 (nullopt)
0x03, 0x62, 0x61, 0x72, // reason_phrase = "bar"
0x03, 0x75, 0x72, 0x69, // connect_uri = "uri"
0x01, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x04, 0x61, 0x62, 0x63, 0x64, // track_name = "abcd"
};
absl::StatusOr<std::vector<AnyMoqtControlMessage>> parsed = ParseAllMessages(
absl::string_view(redirect_from_server, sizeof(redirect_from_server)),
kDefaultMoqtVersion, kWebTrans, quic::Perspective::IS_CLIENT);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
MoqtRequestError expected = {
RequestErrorCode::kRedirect,
std::nullopt,
"bar",
Redirect{"uri", FullTrackName("foo", "abcd")},
};
EXPECT_EQ(std::get<MoqtRequestError>((*parsed)[0]), expected);
// Same message from client fails because connect_uri is not empty.
parsed = ParseAllMessages(
absl::string_view(redirect_from_server, sizeof(redirect_from_server)),
kDefaultMoqtVersion, kWebTrans, quic::Perspective::IS_SERVER);
EXPECT_THAT(parsed,
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Connect URI must be empty from client")));
// Valid redirect from client with empty connect_uri and empty FullTrackName.
char redirect_from_client[] = {
0x05, 0x00, 0x09,
0x34, // error_code = kRedirect (0x34)
0x00, // retry_interval = 0
0x03, 0x62, 0x61, 0x72, // reason_phrase = "bar"
0x00, // connect_uri = ""
0x00, 0x00, // track_namespace = {}, track_name = ""
};
parsed = ParseAllMessages(
absl::string_view(redirect_from_client, sizeof(redirect_from_client)),
kDefaultMoqtVersion, kWebTrans, quic::Perspective::IS_SERVER);
ASSERT_TRUE(parsed.ok());
ASSERT_EQ(parsed->size(), 1);
expected.redirect = Redirect{"", FullTrackName()};
EXPECT_EQ(std::get<MoqtRequestError>((*parsed)[0]), expected);
// Redirect missing when error_code == kRedirect.
char missing_redirect[] = {
0x05, 0x00, 0x06,
0x34, // error_code = kRedirect (0x34)
0x00, // retry_interval = 0
0x03, 0x62, 0x61, 0x72, // reason_phrase = "bar"
};
parsed = ParseAllMessages(
absl::string_view(missing_redirect, sizeof(missing_redirect)),
kDefaultMoqtVersion, kWebTrans, quic::Perspective::IS_CLIENT);
EXPECT_THAT(parsed, StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Redirect missing connect URI")));
// Redirect present when error_code != kRedirect.
char unexpected_redirect[] = {
0x05, 0x00, 0x09,
0x11, // error_code = kInvalidRange
0x00, // retry_interval = 0
0x03, 0x62, 0x61, 0x72, // reason_phrase = "bar"
0x00, // connect_uri = ""
0x00, 0x00, // track_namespace = {}, track_name = ""
};
parsed = ParseAllMessages(
absl::string_view(unexpected_redirect, sizeof(unexpected_redirect)),
kDefaultMoqtVersion, kWebTrans, quic::Perspective::IS_CLIENT);
EXPECT_THAT(parsed, StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Control message has excess data")));
}
} // namespace moqt::test