| // Copyright 2026 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/common/moq_varint.h" | |
| #include <array> | |
| #include <cstdint> | |
| #include <limits> | |
| #include <optional> | |
| #include <string> | |
| #include "absl/strings/escaping.h" | |
| #include "absl/strings/string_view.h" | |
| #include "quiche/common/platform/api/quiche_fuzztest.h" | |
| #include "quiche/common/platform/api/quiche_test.h" | |
| #include "quiche/common/quiche_data_reader.h" | |
| #include "quiche/common/quiche_data_writer.h" | |
| namespace quiche { | |
| namespace { | |
| using ::testing::ElementsAre; | |
| using ReadFunction = std::optional<uint64_t> (*)(QuicheDataReader&); | |
| template <ReadFunction reader> | |
| std::optional<uint64_t> CompleteRead(absl::string_view hex_varint) { | |
| std::string raw_data; | |
| if (!absl::HexStringToBytes(hex_varint, &raw_data)) { | |
| return std::nullopt; | |
| } | |
| QuicheDataReader view(raw_data); | |
| std::optional<uint64_t> result = reader(view); | |
| if (!view.IsDoneReading()) { | |
| return std::nullopt; | |
| } | |
| return result; | |
| } | |
| TEST(MoqVarintTest, VarintParsing) { | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>(""), std::nullopt); | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>("00"), 0); | |
| EXPECT_EQ(CompleteRead<ReadEbmlVarint>("80"), 0); | |
| EXPECT_EQ(CompleteRead<ReadEbmlVarint>("000000000000000000"), std::nullopt); | |
| // Examples from RFC 8794, Section 4.4. | |
| EXPECT_EQ(CompleteRead<ReadEbmlVarint>("82"), 2); | |
| EXPECT_EQ(CompleteRead<ReadEbmlVarint>("4002"), 2); | |
| EXPECT_EQ(CompleteRead<ReadEbmlVarint>("200002"), 2); | |
| EXPECT_EQ(CompleteRead<ReadEbmlVarint>("10000002"), 2); | |
| // Examples from draft-ietf-moq-transport-18, Section 1.4.1. | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>("25"), 37); | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>("8025"), 37); | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>("bbbd"), 15293); | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>("ed7f3e7d"), 226442877); | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>("faa1a0e403d8"), 2893212287960); | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>("fc8998abc66bc0"), 151288809941952); | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>("fefa318fa8e3ca11"), 70423237261249041); | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>("ffffffffffffffffff"), | |
| 18446744073709551615u); | |
| // The (previously) forbidden MOQ varint length. | |
| EXPECT_EQ(CompleteRead<ReadMoqVarint>("fcffffffffffff"), 0xffffffffffff); | |
| EXPECT_EQ(CompleteRead<ReadEbmlVarint>("02ffffffffffff"), 0xffffffffffff); | |
| } | |
| void FuzzMoqVarintParser(absl::string_view s) { | |
| QuicheDataReader reader(s); | |
| ReadMoqVarint(reader); | |
| } | |
| void FuzzEbmlVarintParser(absl::string_view s) { | |
| QuicheDataReader reader(s); | |
| ReadEbmlVarint(reader); | |
| } | |
| FUZZ_TEST(MoqVarintFuzzTest, FuzzMoqVarintParser); | |
| FUZZ_TEST(MoqVarintFuzzTest, FuzzEbmlVarintParser); | |
| using WriteFunction = bool (*)(QuicheDataWriter&, uint64_t); | |
| template <WriteFunction writer> | |
| std::optional<std::string> Write(uint64_t value) { | |
| char buffer[9]; | |
| QuicheDataWriter output(sizeof(buffer), buffer); | |
| bool success = writer(output, value); | |
| if (!success) { | |
| return std::nullopt; | |
| } | |
| return absl::BytesToHexString( | |
| absl::string_view(buffer, sizeof(buffer) - output.remaining())); | |
| } | |
| TEST(MoqVarintTest, VarintSerialization) { | |
| EXPECT_EQ(Write<WriteMoqVarint>(0), "00"); | |
| EXPECT_EQ(Write<WriteMoqVarint>(std::numeric_limits<uint64_t>::max()), | |
| "ffffffffffffffffff"); | |
| EXPECT_EQ(Write<WriteMoqVarint>(UINT64_C(0xffffffffffffff00)), | |
| "ffffffffffffffff00"); | |
| EXPECT_EQ(Write<WriteEbmlVarint>(0), "80"); | |
| EXPECT_EQ(Write<WriteEbmlVarint>(127), "407f"); | |
| EXPECT_EQ(Write<WriteEbmlVarint>(UINT64_C(1) << 56), std::nullopt); | |
| EXPECT_EQ(Write<WriteEbmlVarint>(0xffffffffffff), "02ffffffffffff"); | |
| EXPECT_EQ(Write<WriteMoqVarint>(0xffffffffffff), "fcffffffffffff"); | |
| // Examples from draft-ietf-moq-transport-17(bis), Section 1.4.1. | |
| EXPECT_EQ(Write<WriteMoqVarint>(37), "25"); | |
| EXPECT_EQ(Write<WriteMoqVarint>(15293), "bbbd"); | |
| EXPECT_EQ(Write<WriteMoqVarint>(226442877), "ed7f3e7d"); | |
| EXPECT_EQ(Write<WriteMoqVarint>(2893212287960), "faa1a0e403d8"); | |
| EXPECT_EQ(Write<WriteMoqVarint>(70423237261249041), "fefa318fa8e3ca11"); | |
| EXPECT_EQ(Write<WriteMoqVarint>(18446744073709551615u), "ffffffffffffffffff"); | |
| } | |
| TEST(MoqVarintTest, WriteMoqVarintWithCustomLength) { | |
| std::array<char, 9> buffer; | |
| quiche::QuicheDataWriter writer(buffer.size(), buffer.data()); | |
| ASSERT_TRUE(WriteMoqVarintWithCustomLength(writer, 0xabcd, 9)); | |
| EXPECT_THAT(buffer, ElementsAre(0xff, 0, 0, 0, 0, 0, 0, 0xab, 0xcd)); | |
| } | |
| TEST(MoqVarintTest, VarintSerializationInsufficientBuffer) { | |
| const uint64_t kValue = (UINT64_C(1) << 56) - 1; | |
| char buffer[9]; | |
| for (int size = 0; size < 8; ++size) { | |
| QuicheDataWriter writer(size, buffer); | |
| EXPECT_FALSE(WriteMoqVarint(writer, kValue)) << size; | |
| EXPECT_FALSE(WriteEbmlVarint(writer, kValue)) << size; | |
| } | |
| } | |
| void MoqRoundTrip(uint64_t value) { | |
| std::optional<std::string> encoded = Write<WriteMoqVarint>(value); | |
| ASSERT_TRUE(encoded.has_value()); | |
| EXPECT_EQ(encoded->size() / 2, GetMoqVarintLengthForValue(value)); | |
| std::optional<uint64_t> decoded = CompleteRead<ReadMoqVarint>(*encoded); | |
| EXPECT_EQ(value, decoded) << *encoded; | |
| } | |
| void EbmlRoundTrip(uint64_t value) { | |
| std::optional<std::string> encoded = Write<WriteEbmlVarint>(value); | |
| if (!encoded.has_value()) { | |
| return; | |
| } | |
| EXPECT_EQ(encoded->size() / 2, GetEbmlVarintLengthForValue(value)); | |
| std::optional<uint64_t> decoded = CompleteRead<ReadEbmlVarint>(*encoded); | |
| EXPECT_EQ(value, decoded); | |
| } | |
| FUZZ_TEST(MoqVarintFuzzTest, MoqRoundTrip); | |
| FUZZ_TEST(MoqVarintFuzzTest, EbmlRoundTrip); | |
| } // namespace | |
| } // namespace quiche |