blob: 357be26f6fba677dc9eeea9b4bf16fe9bfadf749 [file] [log] [blame]
// Copyright (c) 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "quiche/quic/moqt/moqt_parser.h"
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <memory>
#include <string>
#include <vector>
#include "absl/strings/string_view.h"
#include "absl/types/optional.h"
#include "quiche/quic/core/quic_types.h"
#include "quiche/quic/moqt/moqt_messages.h"
#include "quiche/quic/moqt/test_tools/moqt_test_message.h"
#include "quiche/quic/platform/api/quic_test.h"
namespace moqt::test {
struct MoqtParserTestParams {
MoqtParserTestParams(MoqtMessageType message_type,
quic::Perspective perspective, bool uses_web_transport)
: message_type(message_type),
perspective(perspective),
uses_web_transport(uses_web_transport) {}
MoqtMessageType message_type;
quic::Perspective perspective;
bool uses_web_transport;
};
std::vector<MoqtParserTestParams> GetMoqtParserTestParams() {
std::vector<MoqtParserTestParams> params;
std::vector<MoqtMessageType> message_types = {
MoqtMessageType::kObject, MoqtMessageType::kSetup,
MoqtMessageType::kSubscribeRequest, MoqtMessageType::kSubscribeOk,
MoqtMessageType::kSubscribeError, MoqtMessageType::kAnnounce,
MoqtMessageType::kAnnounceOk, MoqtMessageType::kAnnounceError,
MoqtMessageType::kGoAway,
};
std::vector<quic::Perspective> perspectives = {
quic::Perspective::IS_SERVER,
quic::Perspective::IS_CLIENT,
};
std::vector<bool> uses_web_transport_bool = {
false,
true,
};
for (const MoqtMessageType message_type : message_types) {
if (message_type == MoqtMessageType::kSetup) {
for (const quic::Perspective perspective : perspectives) {
for (const bool uses_web_transport : uses_web_transport_bool) {
params.push_back(MoqtParserTestParams(message_type, perspective,
uses_web_transport));
}
}
} else {
// All other types are processed the same for either perspective or
// transport.
params.push_back(MoqtParserTestParams(
message_type, quic::Perspective::IS_SERVER, true));
}
}
return params;
}
std::string ParamNameFormatter(
const testing::TestParamInfo<MoqtParserTestParams>& info) {
return MoqtMessageTypeToString(info.param.message_type) + "_" +
(info.param.perspective == quic::Perspective::IS_SERVER ? "Server"
: "Client") +
"_" + (info.param.uses_web_transport ? "WebTransport" : "QUIC");
}
class MoqtParserTestVisitor : public MoqtParserVisitor {
public:
~MoqtParserTestVisitor() = default;
void OnObjectMessage(const MoqtObject& message, absl::string_view payload,
bool end_of_message) override {
object_payload_ = payload;
end_of_message_ = end_of_message;
messages_received_++;
last_message_ = TestMessageBase::MessageStructuredData(message);
}
void OnSetupMessage(const MoqtSetup& message) override {
end_of_message_ = true;
messages_received_++;
MoqtSetup setup = message;
if (setup.path.has_value()) {
string0_ = std::string(setup.path.value());
setup.path = absl::string_view(string0_);
}
last_message_ = TestMessageBase::MessageStructuredData(setup);
}
void OnSubscribeRequestMessage(const MoqtSubscribeRequest& message) override {
end_of_message_ = true;
messages_received_++;
MoqtSubscribeRequest subscribe_request = message;
string0_ = std::string(subscribe_request.full_track_name);
subscribe_request.full_track_name = absl::string_view(string0_);
if (subscribe_request.authorization_info.has_value()) {
string1_ = std::string(subscribe_request.authorization_info.value());
subscribe_request.authorization_info = absl::string_view(string1_);
}
last_message_ = TestMessageBase::MessageStructuredData(subscribe_request);
}
void OnSubscribeOkMessage(const MoqtSubscribeOk& message) override {
end_of_message_ = true;
messages_received_++;
MoqtSubscribeOk subscribe_ok = message;
string0_ = std::string(subscribe_ok.full_track_name);
subscribe_ok.full_track_name = absl::string_view(string0_);
last_message_ = TestMessageBase::MessageStructuredData(subscribe_ok);
}
void OnSubscribeErrorMessage(const MoqtSubscribeError& message) override {
end_of_message_ = true;
messages_received_++;
MoqtSubscribeError subscribe_error = message;
string0_ = std::string(subscribe_error.full_track_name);
subscribe_error.full_track_name = absl::string_view(string0_);
string1_ = std::string(subscribe_error.reason_phrase);
subscribe_error.reason_phrase = absl::string_view(string1_);
last_message_ = TestMessageBase::MessageStructuredData(subscribe_error);
}
void OnAnnounceMessage(const MoqtAnnounce& message) override {
end_of_message_ = true;
messages_received_++;
MoqtAnnounce announce = message;
string0_ = std::string(announce.track_namespace);
announce.track_namespace = absl::string_view(string0_);
if (announce.authorization_info.has_value()) {
string1_ = std::string(announce.authorization_info.value());
announce.authorization_info = absl::string_view(string1_);
}
last_message_ = TestMessageBase::MessageStructuredData(announce);
}
void OnAnnounceOkMessage(const MoqtAnnounceOk& message) override {
end_of_message_ = true;
messages_received_++;
MoqtAnnounceOk announce_ok = message;
string0_ = std::string(announce_ok.track_namespace);
announce_ok.track_namespace = absl::string_view(string0_);
last_message_ = TestMessageBase::MessageStructuredData(announce_ok);
}
void OnAnnounceErrorMessage(const MoqtAnnounceError& message) override {
end_of_message_ = true;
messages_received_++;
MoqtAnnounceError announce_error = message;
string0_ = std::string(announce_error.track_namespace);
announce_error.track_namespace = absl::string_view(string0_);
string1_ = std::string(announce_error.reason_phrase);
announce_error.reason_phrase = absl::string_view(string1_);
last_message_ = TestMessageBase::MessageStructuredData(announce_error);
}
void OnGoAwayMessage() override {
got_goaway_ = true;
end_of_message_ = true;
messages_received_++;
last_message_ = TestMessageBase::MessageStructuredData();
}
void OnParsingError(absl::string_view reason) override {
QUIC_LOG(INFO) << "Parsing error: " << reason;
parsing_error_ = reason;
}
absl::optional<absl::string_view> object_payload_;
bool end_of_message_ = false;
bool got_goaway_ = false;
absl::optional<absl::string_view> parsing_error_;
uint64_t messages_received_ = 0;
absl::optional<TestMessageBase::MessageStructuredData> last_message_;
// Stored strings for last_message_. The visitor API does not promise the
// memory pointed to by string_views is persistent.
std::string string0_, string1_;
};
class MoqtParserTest
: public quic::test::QuicTestWithParam<MoqtParserTestParams> {
public:
MoqtParserTest()
: message_type_(GetParam().message_type),
is_client_(GetParam().perspective == quic::Perspective::IS_CLIENT),
webtrans_(GetParam().uses_web_transport),
parser_(GetParam().perspective, GetParam().uses_web_transport,
visitor_) {}
std::unique_ptr<TestMessageBase> MakeMessage(MoqtMessageType message_type) {
switch (message_type) {
case MoqtMessageType::kObject:
return std::make_unique<ObjectMessage>();
case MoqtMessageType::kSetup:
return std::make_unique<SetupMessage>(is_client_, webtrans_);
case MoqtMessageType::kSubscribeRequest:
return std::make_unique<SubscribeRequestMessage>();
case MoqtMessageType::kSubscribeOk:
return std::make_unique<SubscribeOkMessage>();
case MoqtMessageType::kSubscribeError:
return std::make_unique<SubscribeErrorMessage>();
case MoqtMessageType::kAnnounce:
return std::make_unique<AnnounceMessage>();
case moqt::MoqtMessageType::kAnnounceOk:
return std::make_unique<AnnounceOkMessage>();
case moqt::MoqtMessageType::kAnnounceError:
return std::make_unique<AnnounceErrorMessage>();
case moqt::MoqtMessageType::kGoAway:
return std::make_unique<GoAwayMessage>();
default:
return nullptr;
}
}
MoqtParserTestVisitor visitor_;
MoqtMessageType message_type_;
bool is_client_;
bool webtrans_;
MoqtParser parser_;
};
INSTANTIATE_TEST_SUITE_P(MoqtParserTests, MoqtParserTest,
testing::ValuesIn(GetMoqtParserTestParams()),
ParamNameFormatter);
TEST_P(MoqtParserTest, OneMessage) {
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
parser_.ProcessData(message->PacketSample(), false);
EXPECT_EQ(visitor_.messages_received_, 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
if (message_type_ == MoqtMessageType::kObject) {
// Check payload message.
EXPECT_TRUE(visitor_.object_payload_.has_value());
EXPECT_EQ(*(visitor_.object_payload_), "foo");
}
}
TEST_P(MoqtParserTest, OneMessageWithLongVarints) {
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
message->ExpandVarints();
parser_.ProcessData(message->PacketSample(), false);
EXPECT_EQ(visitor_.messages_received_, 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
if (message_type_ == MoqtMessageType::kObject) {
// Check payload message.
EXPECT_EQ(visitor_.object_payload_, "foo");
}
EXPECT_FALSE(visitor_.parsing_error_.has_value());
}
TEST_P(MoqtParserTest, MessageNoLengthWithFin) {
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
message->set_message_size(0);
parser_.ProcessData(message->PacketSample(), true);
EXPECT_EQ(visitor_.messages_received_, 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
if (message_type_ == MoqtMessageType::kObject) {
// Check payload message.
EXPECT_TRUE(visitor_.object_payload_.has_value());
EXPECT_EQ(*(visitor_.object_payload_), "foo");
}
EXPECT_FALSE(visitor_.parsing_error_.has_value());
}
TEST_P(MoqtParserTest, MessageNoLengthSeparateFinObjectOrGoAway) {
// OBJECT and GOAWAY can return on a zero-length message even without
// receiving a FIN.
if (message_type_ != MoqtMessageType::kObject &&
message_type_ != MoqtMessageType::kGoAway) {
return;
}
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
message->set_message_size(0);
parser_.ProcessData(message->PacketSample(), false);
EXPECT_EQ(visitor_.messages_received_, 1);
if (message_type_ == MoqtMessageType::kGoAway) {
EXPECT_TRUE(visitor_.got_goaway_);
EXPECT_TRUE(visitor_.end_of_message_);
return;
}
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.object_payload_.has_value());
EXPECT_EQ(*(visitor_.object_payload_), "foo");
EXPECT_FALSE(visitor_.end_of_message_);
parser_.ProcessData(absl::string_view(), true); // send the FIN
EXPECT_EQ(visitor_.messages_received_, 2);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.object_payload_.has_value());
EXPECT_EQ(*(visitor_.object_payload_), "");
EXPECT_TRUE(visitor_.end_of_message_);
EXPECT_FALSE(visitor_.parsing_error_.has_value());
}
TEST_P(MoqtParserTest, MessageNoLengthSeparateFinOtherTypes) {
if (message_type_ == MoqtMessageType::kObject ||
message_type_ == MoqtMessageType::kGoAway) {
return;
}
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
message->set_message_size(0);
parser_.ProcessData(message->PacketSample(), false);
EXPECT_EQ(visitor_.messages_received_, 0);
parser_.ProcessData(absl::string_view(), true); // send the FIN
EXPECT_EQ(visitor_.messages_received_, 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
EXPECT_FALSE(visitor_.parsing_error_.has_value());
}
TEST_P(MoqtParserTest, TwoPartMessage) {
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
// 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;
parser_.ProcessData(message->PacketSample().substr(0, first_data_size),
false);
EXPECT_EQ(visitor_.messages_received_, 0);
parser_.ProcessData(
message->PacketSample().substr(
first_data_size, message->total_message_size() - first_data_size),
false);
EXPECT_EQ(visitor_.messages_received_, 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
if (message_type_ == MoqtMessageType::kObject) {
EXPECT_EQ(visitor_.object_payload_, "foo");
}
EXPECT_FALSE(visitor_.parsing_error_.has_value());
}
// Send the header + some payload, pure payload, then pure payload to end the
// message.
TEST_P(MoqtParserTest, ThreePartObject) {
if (message_type_ != MoqtMessageType::kObject) {
return;
}
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
message->set_message_size(0);
// 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.
parser_.ProcessData(message->PacketSample(), false);
EXPECT_EQ(visitor_.messages_received_, 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_FALSE(visitor_.end_of_message_);
EXPECT_TRUE(visitor_.object_payload_.has_value());
EXPECT_EQ(*(visitor_.object_payload_), "foo");
// second part
parser_.ProcessData("bar", false);
EXPECT_EQ(visitor_.messages_received_, 2);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_FALSE(visitor_.end_of_message_);
EXPECT_TRUE(visitor_.object_payload_.has_value());
EXPECT_EQ(*(visitor_.object_payload_), "bar");
// third part includes FIN
parser_.ProcessData("deadbeef", true);
EXPECT_EQ(visitor_.messages_received_, 3);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
EXPECT_TRUE(visitor_.object_payload_.has_value());
EXPECT_EQ(*(visitor_.object_payload_), "deadbeef");
EXPECT_FALSE(visitor_.parsing_error_.has_value());
}
// Send the part of header, rest of header + payload, plus payload.
TEST_P(MoqtParserTest, ThreePartObjectFirstIncomplete) {
if (message_type_ != MoqtMessageType::kObject) {
return;
}
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
message->set_message_size(0);
// first part
parser_.ProcessData(message->PacketSample().substr(0, 4), false);
EXPECT_EQ(visitor_.messages_received_, 0);
// second part. Add padding to it.
message->set_wire_image_size(100);
parser_.ProcessData(
message->PacketSample().substr(4, message->total_message_size() - 4),
false);
EXPECT_EQ(visitor_.messages_received_, 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_FALSE(visitor_.end_of_message_);
EXPECT_TRUE(visitor_.object_payload_.has_value());
EXPECT_EQ(visitor_.object_payload_->length(), 94);
// third part includes FIN
parser_.ProcessData("bar", true);
EXPECT_EQ(visitor_.messages_received_, 2);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
EXPECT_TRUE(visitor_.object_payload_.has_value());
EXPECT_EQ(*(visitor_.object_payload_), "bar");
EXPECT_FALSE(visitor_.parsing_error_.has_value());
}
TEST_P(MoqtParserTest, OneByteAtATime) {
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
message->set_message_size(0);
constexpr size_t kObjectPrePayloadSize = 6;
for (size_t i = 0; i < message->total_message_size(); ++i) {
parser_.ProcessData(message->PacketSample().substr(i, 1), false);
if (message_type_ == MoqtMessageType::kGoAway &&
i == message->total_message_size() - 1) {
// OnGoAway() is called before FIN.
EXPECT_EQ(visitor_.messages_received_, 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
break;
}
if (message_type_ != MoqtMessageType::kObject ||
i < kObjectPrePayloadSize) {
// OBJECTs will have to buffer for the first 5 bytes (until the varints
// are done). The sixth byte is a bare OBJECT header, so the parser does
// not notify the visitor.
EXPECT_EQ(visitor_.messages_received_, 0);
} else {
// OBJECT payload processing.
EXPECT_EQ(visitor_.messages_received_, i - kObjectPrePayloadSize + 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.object_payload_.has_value());
if (i == 5) {
EXPECT_EQ(visitor_.object_payload_->length(), 0);
} else {
EXPECT_EQ(visitor_.object_payload_->length(), 1);
EXPECT_EQ((*visitor_.object_payload_)[0],
message->PacketSample().substr(i, 1)[0]);
}
}
EXPECT_FALSE(visitor_.end_of_message_);
}
// Send FIN
parser_.ProcessData(absl::string_view(), true);
if (message_type_ == MoqtMessageType::kObject) {
EXPECT_EQ(visitor_.messages_received_,
message->total_message_size() - kObjectPrePayloadSize + 1);
} else {
EXPECT_EQ(visitor_.messages_received_, 1);
}
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
EXPECT_FALSE(visitor_.parsing_error_.has_value());
}
TEST_P(MoqtParserTest, OneByteAtATimeLongerVarints) {
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
message->ExpandVarints();
message->set_message_size(0);
constexpr size_t kObjectPrePayloadSize = 28;
for (size_t i = 0; i < message->total_message_size(); ++i) {
parser_.ProcessData(message->PacketSample().substr(i, 1), false);
if (message_type_ == MoqtMessageType::kGoAway &&
i == message->total_message_size() - 1) {
// OnGoAway() is called before FIN.
EXPECT_EQ(visitor_.messages_received_, 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
break;
}
if (message_type_ != MoqtMessageType::kObject ||
i < kObjectPrePayloadSize) {
// OBJECTs will have to buffer for the first 5 bytes (until the varints
// are done). The sixth byte is a bare OBJECT header, so the parser does
// not notify the visitor.
EXPECT_EQ(visitor_.messages_received_, 0);
} else {
// OBJECT payload processing.
EXPECT_EQ(visitor_.messages_received_, i - kObjectPrePayloadSize + 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.object_payload_.has_value());
if (i == 5) {
EXPECT_EQ(visitor_.object_payload_->length(), 0);
} else {
EXPECT_EQ(visitor_.object_payload_->length(), 1);
EXPECT_EQ((*visitor_.object_payload_)[0],
message->PacketSample().substr(i, 1)[0]);
}
}
EXPECT_FALSE(visitor_.end_of_message_);
}
// Send FIN
parser_.ProcessData(absl::string_view(), true);
if (message_type_ == MoqtMessageType::kObject) {
EXPECT_EQ(visitor_.messages_received_,
message->total_message_size() - kObjectPrePayloadSize + 1);
} else {
EXPECT_EQ(visitor_.messages_received_, 1);
}
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
EXPECT_FALSE(visitor_.parsing_error_.has_value());
}
TEST_P(MoqtParserTest, OneByteAtATimeKnownLength) {
std::unique_ptr<TestMessageBase> message = MakeMessage(message_type_);
constexpr size_t kObjectPrePayloadSize = 6;
// Send all but the last byte
for (size_t i = 0; i < message->total_message_size() - 1; ++i) {
parser_.ProcessData(message->PacketSample().substr(i, 1), false);
if (message_type_ != MoqtMessageType::kObject ||
i < kObjectPrePayloadSize) {
// OBJECTs will have to buffer for the first 5 bytes (until the varints
// are done). The sixth byte is a bare OBJECT header, so the parser does
// not notify the visitor.
EXPECT_EQ(visitor_.messages_received_, 0);
} else {
// OBJECT payload processing.
EXPECT_EQ(visitor_.messages_received_, i - kObjectPrePayloadSize + 1);
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.object_payload_.has_value());
if (i == 5) {
EXPECT_EQ(visitor_.object_payload_->length(), 0);
} else {
EXPECT_EQ(visitor_.object_payload_->length(), 1);
EXPECT_EQ((*visitor_.object_payload_)[0],
message->PacketSample().substr(i, 1)[0]);
}
}
EXPECT_FALSE(visitor_.end_of_message_);
}
// Send last byte
parser_.ProcessData(
message->PacketSample().substr(message->total_message_size() - 1, 1),
false);
if (message_type_ == MoqtMessageType::kObject) {
EXPECT_EQ(visitor_.messages_received_,
message->total_message_size() - kObjectPrePayloadSize);
EXPECT_EQ(visitor_.object_payload_->length(), 1);
EXPECT_EQ((*visitor_.object_payload_)[0],
message->PacketSample().substr(message->total_message_size() - 1,
1)[0]);
} else {
EXPECT_EQ(visitor_.messages_received_, 1);
}
EXPECT_TRUE(message->EqualFieldValues(visitor_.last_message_.value()));
EXPECT_TRUE(visitor_.end_of_message_);
EXPECT_FALSE(visitor_.parsing_error_.has_value());
}
TEST_P(MoqtParserTest, LengthTooShort) {
if (message_type_ == MoqtMessageType::kGoAway ||
message_type_ == MoqtMessageType::kAnnounceOk) {
// GOAWAY already has length zero. ANNOUNCE_OK works for any message length.
return;
}
auto message = MakeMessage(message_type_);
if (message_type_ == MoqtMessageType::kSetup &&
GetParam().perspective == quic::Perspective::IS_CLIENT) {
// Unless varints are longer than necessary, the message is only one byte
// long.
message->ExpandVarints();
}
size_t truncate = (message_type_ == MoqtMessageType::kObject) ? 4 : 1;
message->set_message_size(message->message_size() - truncate);
parser_.ProcessData(message->PacketSample(), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"Not able to parse message given specified length");
}
// Buffered packets are a different code path, so test them separately.
TEST_P(MoqtParserTest, LengthTooShortInBufferedPacket) {
if (message_type_ == MoqtMessageType::kGoAway ||
message_type_ == MoqtMessageType::kAnnounceOk) {
// GOAWAY already has length zero. ANNOUNCE_OK works for any message length.
return;
}
auto message = MakeMessage(message_type_);
if (message_type_ == MoqtMessageType::kSetup &&
GetParam().perspective == quic::Perspective::IS_CLIENT) {
// Unless varints are longer than necessary, the message is only one byte
// long.
message->ExpandVarints();
}
EXPECT_EQ(visitor_.messages_received_, 0);
size_t truncate = (message_type_ == MoqtMessageType::kObject) ? 5 : 2;
message->set_message_size(message->message_size() - truncate + 1);
parser_.ProcessData(
message->PacketSample().substr(0, message->total_message_size() - 1),
false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_FALSE(visitor_.parsing_error_.has_value());
// send the last byte
parser_.ProcessData(
message->PacketSample().substr(message->total_message_size() - 1, 1),
false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"Not able to parse buffered message given specified length");
}
TEST_P(MoqtParserTest, LengthTooLong) {
if (message_type_ == MoqtMessageType::kAnnounceOk ||
message_type_ == MoqtMessageType::kObject ||
message_type_ == MoqtMessageType::kSetup ||
message_type_ == MoqtMessageType::kSubscribeRequest ||
message_type_ == MoqtMessageType::kAnnounce) {
// OBJECT and ANNOUNCE_OK work for any message length.
// SETUP, SUBSCRIBE_REQUEST, and ANNOUNCE have parameters, so an additional
// byte will cause the message to be interpreted as being too short.
return;
}
auto message = MakeMessage(message_type_);
message->set_message_size(message->message_size() + 1);
parser_.ProcessData(message->PacketSample(), false);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(visitor_.messages_received_, 0);
if (message_type_ == MoqtMessageType::kGoAway) {
EXPECT_EQ(*visitor_.parsing_error_, "GOAWAY has data following");
} else {
EXPECT_EQ(*visitor_.parsing_error_, "Specified message length too long");
}
}
TEST_P(MoqtParserTest, LengthExceedsBufferSize) {
if (message_type_ == MoqtMessageType::kObject) {
// OBJECT works for any length.
return;
}
auto message = MakeMessage(message_type_);
message->set_message_size(kMaxMessageHeaderSize + 1);
parser_.ProcessData(message->PacketSample(), false);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(visitor_.messages_received_, 0);
if (message_type_ == MoqtMessageType::kGoAway) {
EXPECT_EQ(*visitor_.parsing_error_, "GOAWAY has data following");
} else {
EXPECT_EQ(*visitor_.parsing_error_, "Message too long");
}
}
// Tests for message-specific error cases.
class MoqtParserErrorTest : public quic::test::QuicTest {
public:
MoqtParserErrorTest() {}
MoqtParserTestVisitor visitor_;
static constexpr bool kWebTrans = true;
static constexpr bool kRawQuic = false;
};
TEST_F(MoqtParserErrorTest, SetupRoleAppearsTwice) {
MoqtParser parser(quic::Perspective::IS_SERVER, kRawQuic, visitor_);
char setup[] = {
0x01, 0x0e, 0x02, 0x01, 0x02, // versions
0x00, 0x01, 0x03, // role = both
0x00, 0x01, 0x03, // role = both
0x01, 0x03, 0x66, 0x6f, 0x6f // path = "foo"
};
parser.ProcessData(absl::string_view(setup, sizeof(setup)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_, "ROLE parameter appears twice in SETUP");
}
TEST_F(MoqtParserErrorTest, SetupRoleFromServer) {
MoqtParser parser(quic::Perspective::IS_CLIENT, kWebTrans, visitor_);
char setup[] = {
0x01, 0x04,
0x01, // version = 1
0x00, 0x01, 0x03, // role = both
};
parser.ProcessData(absl::string_view(setup, sizeof(setup)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_, "ROLE parameter sent by server in SETUP");
}
TEST_F(MoqtParserErrorTest, SetupRoleIsMissing) {
MoqtParser parser(quic::Perspective::IS_SERVER, kRawQuic, visitor_);
char setup[] = {
0x01, 0x08, 0x02, 0x01, 0x02, // versions = 1, 2
0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
};
parser.ProcessData(absl::string_view(setup, sizeof(setup)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"ROLE SETUP parameter missing from Client message");
}
TEST_F(MoqtParserErrorTest, SetupPathFromServer) {
MoqtParser parser(quic::Perspective::IS_CLIENT, kRawQuic, visitor_);
char setup[] = {
0x01, 0x06,
0x01, // version = 1
0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
};
parser.ProcessData(absl::string_view(setup, sizeof(setup)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_, "PATH parameter sent by server in SETUP");
}
TEST_F(MoqtParserErrorTest, SetupPathAppearsTwice) {
MoqtParser parser(quic::Perspective::IS_SERVER, kRawQuic, visitor_);
char setup[] = {
0x01, 0x10, 0x02, 0x01, 0x02, // versions = 1, 2
0x00, 0x01, 0x03, // role = both
0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
};
parser.ProcessData(absl::string_view(setup, sizeof(setup)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_, "PATH parameter appears twice in SETUP");
}
TEST_F(MoqtParserErrorTest, SetupPathOverWebtrans) {
MoqtParser parser(quic::Perspective::IS_SERVER, kWebTrans, visitor_);
char setup[] = {
0x01, 0x0b, 0x02, 0x01, 0x02, // versions = 1, 2
0x00, 0x01, 0x03, // role = both
0x01, 0x03, 0x66, 0x6f, 0x6f, // path = "foo"
};
parser.ProcessData(absl::string_view(setup, sizeof(setup)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"WebTransport connection is using PATH parameter in SETUP");
}
TEST_F(MoqtParserErrorTest, SetupPathMissing) {
MoqtParser parser(quic::Perspective::IS_SERVER, kRawQuic, visitor_);
char setup[] = {
0x01, 0x06, 0x02, 0x01, 0x02, // versions = 1, 2
0x00, 0x01, 0x03, // role = both
};
parser.ProcessData(absl::string_view(setup, sizeof(setup)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"PATH SETUP parameter missing from Client message over QUIC");
}
TEST_F(MoqtParserErrorTest, SetupRoleTooLong) {
MoqtParser parser(quic::Perspective::IS_SERVER, kRawQuic, visitor_);
char setup[] = {
0x01, 0x0e, 0x02, 0x01, 0x02, // versions
// role = both
0x00, 0x09, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x03, 0x01,
0x03, 0x66, 0x6f, 0x6f // path = "foo"
};
parser.ProcessData(absl::string_view(setup, sizeof(setup)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"Cannot parse explicit length integers longer than 8 bytes");
}
TEST_F(MoqtParserErrorTest, SubscribeRequestGroupSequenceTwice) {
MoqtParser parser(quic::Perspective::IS_SERVER, kWebTrans, visitor_);
char subscribe_request[] = {
0x03, 0x12, 0x03, 0x66, 0x6f, 0x6f, // track_name = "foo"
0x00, 0x01, 0x01, // group_sequence = 1
0x00, 0x01, 0x01, // group_sequence = 1
0x01, 0x01, 0x02, // object_sequence = 2
0x02, 0x03, 0x62, 0x61, 0x72, // authorization_info = "bar"
};
parser.ProcessData(
absl::string_view(subscribe_request, sizeof(subscribe_request)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"GROUP_SEQUENCE parameter appears twice in SUBSCRIBE_REQUEST");
}
TEST_F(MoqtParserErrorTest, SubscribeRequestObjectSequenceTwice) {
MoqtParser parser(quic::Perspective::IS_SERVER, kWebTrans, visitor_);
char subscribe_request[] = {
0x03, 0x12, 0x03, 0x66, 0x6f, 0x6f, // track_name = "foo"
0x00, 0x01, 0x01, // group_sequence = 1
0x01, 0x01, 0x02, // object_sequence = 2
0x01, 0x01, 0x02, // object_sequence = 2
0x02, 0x03, 0x62, 0x61, 0x72, // authorization_info = "bar"
};
parser.ProcessData(
absl::string_view(subscribe_request, sizeof(subscribe_request)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"OBJECT_SEQUENCE parameter appears twice in SUBSCRIBE_REQUEST");
}
TEST_F(MoqtParserErrorTest, SubscribeRequestAuthorizationInfoTwice) {
MoqtParser parser(quic::Perspective::IS_SERVER, kWebTrans, visitor_);
char subscribe_request[] = {
0x03, 0x14, 0x03, 0x66, 0x6f, 0x6f, // track_name = "foo"
0x00, 0x01, 0x01, // group_sequence = 1
0x01, 0x01, 0x02, // object_sequence = 2
0x02, 0x03, 0x62, 0x61, 0x72, // authorization_info = "bar"
0x02, 0x03, 0x62, 0x61, 0x72, // authorization_info = "bar"
};
parser.ProcessData(
absl::string_view(subscribe_request, sizeof(subscribe_request)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"AUTHORIZATION_INFO parameter appears twice in SUBSCRIBE_REQUEST");
}
TEST_F(MoqtParserErrorTest, AnnounceGroupSequenceTwice) {
MoqtParser parser(quic::Perspective::IS_SERVER, kWebTrans, visitor_);
char announce[] = {
0x06, 0x0f, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x02, 0x03, 0x62, 0x61, 0x72, // authorization_info = "bar"
0x00, 0x01, 0x01, // group_sequence = 1
0x00, 0x01, 0x01, // group_sequence = 1
};
parser.ProcessData(absl::string_view(announce, sizeof(announce)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"GROUP_SEQUENCE parameter appears twice in ANNOUNCE");
}
TEST_F(MoqtParserErrorTest, AnnounceObjectSequenceTwice) {
MoqtParser parser(quic::Perspective::IS_SERVER, kWebTrans, visitor_);
char announce[] = {
0x06, 0x0e, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x01, 0x01, 0x02, // object_sequence = 2
0x02, 0x03, 0x62, 0x61, 0x72, // authorization_info = "bar"
0x01, 0x01, 0x02, // object_sequence = 2
};
parser.ProcessData(absl::string_view(announce, sizeof(announce)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"OBJECT_SEQUENCE parameter appears twice in ANNOUNCE");
}
TEST_F(MoqtParserErrorTest, AnnounceAuthorizationInfoTwice) {
MoqtParser parser(quic::Perspective::IS_SERVER, kWebTrans, visitor_);
char announce[] = {
0x06, 0x0e, 0x03, 0x66, 0x6f, 0x6f, // track_namespace = "foo"
0x02, 0x03, 0x62, 0x61, 0x72, // authorization_info = "bar"
0x02, 0x03, 0x62, 0x61, 0x72, // authorization_info = "bar"
};
parser.ProcessData(absl::string_view(announce, sizeof(announce)), false);
EXPECT_EQ(visitor_.messages_received_, 0);
EXPECT_TRUE(visitor_.parsing_error_.has_value());
EXPECT_EQ(*visitor_.parsing_error_,
"AUTHORIZATION_INFO parameter appears twice in ANNOUNCE");
}
} // namespace moqt::test