blob: 1e171cff5b5c502b87cf290e420a2743fce2a213 [file] [edit]
// Copyright 2024 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_names.h"
#include <string>
#include <utility>
#include <vector>
#include "absl/hash/hash.h"
#include "absl/status/status.h"
#include "absl/status/statusor.h"
#include "absl/strings/string_view.h"
#include "quiche/common/platform/api/quiche_expect_bug.h"
#include "quiche/common/platform/api/quiche_fuzztest.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"
namespace moqt::test {
namespace {
using ::quiche::test::IsOkAndHolds;
using ::quiche::test::StatusIs;
using ::testing::ElementsAre;
using ::testing::HasSubstr;
TEST(MoqtNamesTest, TrackNamespaceConstructors) {
TrackNamespace name1({"foo", "bar"});
MoqtStringTuple list({"foo", "bar"});
absl::StatusOr<TrackNamespace> name2 =
TrackNamespace::Create(std::move(list));
QUICHE_ASSERT_OK(name2);
ASSERT_EQ(name1, *name2);
EXPECT_EQ(absl::HashOf(name1), absl::HashOf(*name2));
}
TEST(MoqtNamesTest, TrackNamespaceOrder) {
TrackNamespace name1({"a", "b"});
TrackNamespace name2({"a", "b", "c"});
TrackNamespace name3({"b", "a"});
EXPECT_LT(name1, name2);
EXPECT_LT(name2, name3);
EXPECT_LT(name1, name3);
}
TEST(MoqtNamesTest, TrackNamespaceInNamespace) {
TrackNamespace name1({"a", "b"});
TrackNamespace name2({"a", "b", "c"});
TrackNamespace name3({"d", "b"});
EXPECT_TRUE(name2.InNamespace(name1));
EXPECT_FALSE(name1.InNamespace(name2));
EXPECT_TRUE(name1.InNamespace(name1));
EXPECT_FALSE(name2.InNamespace(name3));
}
TEST(MoqtNamesTest, TrackNamespacePushPop) {
TrackNamespace name({"a"});
TrackNamespace original = name;
EXPECT_TRUE(name.AddElement("b"));
EXPECT_TRUE(name.InNamespace(original));
EXPECT_FALSE(original.InNamespace(name));
EXPECT_TRUE(name.PopElement());
EXPECT_EQ(name, original);
EXPECT_TRUE(name.PopElement());
EXPECT_EQ(name.number_of_elements(), 0);
EXPECT_FALSE(name.PopElement());
}
TEST(MoqtNamesTest, TrackNamespaceToString) {
TrackNamespace name1({"a", "b"});
EXPECT_EQ(name1.ToString(), "a-b");
TrackNamespace name2({"\xff\x01", "\x61"});
EXPECT_EQ(name2.ToString(), ".ff.01-a");
TrackNamespace name3({"a_b", "c_d?"});
EXPECT_EQ(name3.ToString(), "a_b-c_d.3f");
}
TEST(MoqtNamesTest, FullTrackNameToString) {
FullTrackName name1(TrackNamespace{"a", "b"}, "c");
EXPECT_EQ(name1.ToString(), "a-b--c");
}
TEST(MoqtNamesTest, TrackNamespaceSuffixes) {
using quiche::test::IsOkAndHolds;
TrackNamespace name1({"a", "b"});
TrackNamespace name2({"c", "d"});
TrackNamespace name3({"a", "b", "c", "d"});
EXPECT_THAT(name1.AddSuffix(name2), IsOkAndHolds(name3));
EXPECT_THAT(name3.ExtractSuffix(name1), IsOkAndHolds(name2));
EXPECT_THAT(name1.AddSuffix(TrackNamespace()), IsOkAndHolds(name1));
EXPECT_THAT(TrackNamespace().AddSuffix(name1), IsOkAndHolds(name1));
EXPECT_THAT(name1.ExtractSuffix(TrackNamespace()), IsOkAndHolds(name1));
EXPECT_THAT(name1.ExtractSuffix(name1), IsOkAndHolds(TrackNamespace()));
TrackNamespace name4({"c", "b"});
EXPECT_EQ(name1.ExtractSuffix(name4).status(),
absl::InvalidArgumentError("Prefix is not in namespace"));
}
TEST(MoqtNamesTest, TooManyNamespaceElements) {
// 32 elements work.
absl::StatusOr<TrackNamespace> name1 = TrackNamespace::Create(MoqtStringTuple(
{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "aa", "bb", "cc", "dd", "ee", "ff"}));
QUICHE_ASSERT_OK(name1);
EXPECT_FALSE(name1->AddElement("a"));
EXPECT_EQ(name1->number_of_elements(), kMaxNamespaceElements);
// 33 elements fail.
absl::StatusOr<TrackNamespace> name2 = TrackNamespace::Create(MoqtStringTuple(
{"a", "b", "c", "d", "e", "f", "g", "h", "i", "j", "k",
"l", "m", "n", "o", "p", "q", "r", "s", "t", "u", "v",
"w", "x", "y", "z", "aa", "bb", "cc", "dd", "ee", "ff", "gg"}));
EXPECT_THAT(name2.status(), StatusIs(absl::StatusCode::kOutOfRange,
HasSubstr("33 elements")));
}
TEST(MoqtNamesTest, FullTrackNameTooLong) {
char raw_name[kMaxFullTrackNameSize + 1];
absl::string_view track_namespace(raw_name, kMaxFullTrackNameSize);
// Adding an element takes it over the length limit.
TrackNamespace max_length_namespace({track_namespace});
EXPECT_FALSE(max_length_namespace.AddElement("f"));
// Constructing a FullTrackName where the name brings it over the length
// limit.
EXPECT_QUICHE_BUG(FullTrackName(max_length_namespace.tuple()[0], "f"),
"Constructing a Full Track Name that is too large.");
// The namespace is too long by itself..
absl::string_view big_namespace(raw_name, kMaxFullTrackNameSize + 1);
EXPECT_QUICHE_BUG(TrackNamespace({big_namespace}),
"TrackNamspace constructor");
}
absl::StatusOr<MoqtStringTuple> ParseNamespace(absl::string_view input) {
absl::StatusOr<TrackNamespace> ns = TrackNamespace::Parse(input);
QUICHE_RETURN_IF_ERROR(ns.status());
return ns->tuple();
}
TEST(MoqtNamesTest, ParseNamespace) {
EXPECT_THAT(ParseNamespace(""), IsOkAndHolds(ElementsAre()));
EXPECT_THAT(ParseNamespace("foo"), IsOkAndHolds(ElementsAre("foo")));
EXPECT_THAT(ParseNamespace("foo-bar"),
IsOkAndHolds(ElementsAre("foo", "bar")));
EXPECT_THAT(ParseNamespace("foo-bar--test"),
IsOkAndHolds(ElementsAre("foo", "bar", "", "test")));
EXPECT_THAT(ParseNamespace("foo-.ff"),
IsOkAndHolds(ElementsAre("foo", "\xff")));
EXPECT_THAT(ParseNamespace("foo-.f"),
StatusIs(absl::StatusCode::kInvalidArgument,
"Incomplete escape sequence"));
EXPECT_THAT(
ParseNamespace("foo-.zz"),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("Invalid hex")));
EXPECT_THAT(
ParseNamespace("foo-.FF"),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("Invalid hex")));
EXPECT_THAT(
ParseNamespace("foo-.0z"),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("Invalid hex")));
EXPECT_THAT(ParseNamespace("foo-.61"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Hex-encoding a safe character")));
EXPECT_THAT(
ParseNamespace("................"),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("Invalid hex")));
EXPECT_THAT(ParseNamespace("foo-\xff"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("Invalid character 0xff")));
std::string long_string(2 * kMaxFullTrackNameSize, 'a');
EXPECT_THAT(
ParseNamespace(long_string),
StatusIs(absl::StatusCode::kOutOfRange, "Maximum tuple size exceeded"));
}
TEST(MoqtNamesTest, ParseFullTrackName) {
EXPECT_THAT(FullTrackName::Parse("foo-bar--test"),
IsOkAndHolds(FullTrackName({"foo", "bar"}, "test")));
EXPECT_THAT(FullTrackName::Parse("foo--bar--test"),
IsOkAndHolds(FullTrackName({"foo", "", "bar"}, "test")));
EXPECT_THAT(FullTrackName::Parse("--test"),
IsOkAndHolds(FullTrackName({}, "test")));
EXPECT_THAT(
FullTrackName::Parse("a-b-c-d-e-f"),
StatusIs(absl::StatusCode::kInvalidArgument, HasSubstr("must use --")));
EXPECT_THAT(FullTrackName::Parse("foo-bar"),
StatusIs(absl::StatusCode::kInvalidArgument,
HasSubstr("missing elements")));
}
void FuzzParseNamespace(absl::string_view encoded_namespace) {
(void)TrackNamespace::Parse(encoded_namespace);
}
void FuzzParseFullTrackName(absl::string_view encoded_track_name) {
(void)FullTrackName::Parse(encoded_track_name);
}
void SerializeAndParseNamespace(const std::vector<std::string>& elements) {
if (elements.empty() || elements[0].empty()) {
return;
}
TrackNamespace ns;
for (const std::string& element : elements) {
if (!ns.AddElement(element)) {
return;
}
}
absl::StatusOr<TrackNamespace> round_trip_result =
TrackNamespace::Parse(ns.ToString());
QUICHE_ASSERT_OK(round_trip_result.status());
EXPECT_EQ(ns, *round_trip_result);
}
void ParseAndSerializeNamespace(absl::string_view encoded_namespace) {
absl::StatusOr<TrackNamespace> ns = TrackNamespace::Parse(encoded_namespace);
if (!ns.ok()) {
return;
}
EXPECT_EQ(ns->ToString(), encoded_namespace);
}
FUZZ_TEST(MoqtNamesTest, FuzzParseNamespace);
FUZZ_TEST(MoqtNamesTest, FuzzParseFullTrackName);
FUZZ_TEST(MoqtNamesTest, SerializeAndParseNamespace);
FUZZ_TEST(MoqtNamesTest, ParseAndSerializeNamespace);
} // namespace
} // namespace moqt::test