Implement MOQ/EBML varint parsing and serialization. MOQ varints will be useful when we move to draft-17, EBML varints may turn out to be helpful for MSF-related tooling. PiperOrigin-RevId: 881671907
diff --git a/build/source_list.bzl b/build/source_list.bzl index 58ed467..434977b 100644 --- a/build/source_list.bzl +++ b/build/source_list.bzl
@@ -20,6 +20,7 @@ "common/lifetime_tracking.h", "common/masque/connect_ip_datagram_payload.h", "common/masque/connect_udp_datagram_payload.h", + "common/moq_varint.h", "common/platform/api/quiche_bug_tracker.h", "common/platform/api/quiche_client_stats.h", "common/platform/api/quiche_containers.h", @@ -428,6 +429,7 @@ "common/internet_checksum.cc", "common/masque/connect_ip_datagram_payload.cc", "common/masque/connect_udp_datagram_payload.cc", + "common/moq_varint.cc", "common/platform/api/quiche_hostname_utils.cc", "common/quiche_buffer_allocator.cc", "common/quiche_crypto_logging.cc", @@ -1129,6 +1131,7 @@ "common/lifetime_tracking_test.cc", "common/masque/connect_ip_datagram_payload_test.cc", "common/masque/connect_udp_datagram_payload_test.cc", + "common/moq_varint_test.cc", "common/platform/api/quiche_client_stats_test.cc", "common/platform/api/quiche_file_utils_test.cc", "common/platform/api/quiche_hostname_utils_test.cc",
diff --git a/build/source_list.gni b/build/source_list.gni index 42458e6..9b85f15 100644 --- a/build/source_list.gni +++ b/build/source_list.gni
@@ -20,6 +20,7 @@ "src/quiche/common/lifetime_tracking.h", "src/quiche/common/masque/connect_ip_datagram_payload.h", "src/quiche/common/masque/connect_udp_datagram_payload.h", + "src/quiche/common/moq_varint.h", "src/quiche/common/platform/api/quiche_bug_tracker.h", "src/quiche/common/platform/api/quiche_client_stats.h", "src/quiche/common/platform/api/quiche_containers.h", @@ -428,6 +429,7 @@ "src/quiche/common/internet_checksum.cc", "src/quiche/common/masque/connect_ip_datagram_payload.cc", "src/quiche/common/masque/connect_udp_datagram_payload.cc", + "src/quiche/common/moq_varint.cc", "src/quiche/common/platform/api/quiche_hostname_utils.cc", "src/quiche/common/quiche_buffer_allocator.cc", "src/quiche/common/quiche_crypto_logging.cc", @@ -1130,6 +1132,7 @@ "src/quiche/common/lifetime_tracking_test.cc", "src/quiche/common/masque/connect_ip_datagram_payload_test.cc", "src/quiche/common/masque/connect_udp_datagram_payload_test.cc", + "src/quiche/common/moq_varint_test.cc", "src/quiche/common/platform/api/quiche_client_stats_test.cc", "src/quiche/common/platform/api/quiche_file_utils_test.cc", "src/quiche/common/platform/api/quiche_hostname_utils_test.cc",
diff --git a/build/source_list.json b/build/source_list.json index 86a6e76..c30464e 100644 --- a/build/source_list.json +++ b/build/source_list.json
@@ -19,6 +19,7 @@ "quiche/common/lifetime_tracking.h", "quiche/common/masque/connect_ip_datagram_payload.h", "quiche/common/masque/connect_udp_datagram_payload.h", + "quiche/common/moq_varint.h", "quiche/common/platform/api/quiche_bug_tracker.h", "quiche/common/platform/api/quiche_client_stats.h", "quiche/common/platform/api/quiche_containers.h", @@ -427,6 +428,7 @@ "quiche/common/internet_checksum.cc", "quiche/common/masque/connect_ip_datagram_payload.cc", "quiche/common/masque/connect_udp_datagram_payload.cc", + "quiche/common/moq_varint.cc", "quiche/common/platform/api/quiche_hostname_utils.cc", "quiche/common/quiche_buffer_allocator.cc", "quiche/common/quiche_crypto_logging.cc", @@ -1129,6 +1131,7 @@ "quiche/common/lifetime_tracking_test.cc", "quiche/common/masque/connect_ip_datagram_payload_test.cc", "quiche/common/masque/connect_udp_datagram_payload_test.cc", + "quiche/common/moq_varint_test.cc", "quiche/common/platform/api/quiche_client_stats_test.cc", "quiche/common/platform/api/quiche_file_utils_test.cc", "quiche/common/platform/api/quiche_hostname_utils_test.cc",
diff --git a/quiche/common/moq_varint.cc b/quiche/common/moq_varint.cc new file mode 100644 index 0000000..dbc6bc9 --- /dev/null +++ b/quiche/common/moq_varint.cc
@@ -0,0 +1,162 @@ +// 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 <algorithm> +#include <cstddef> +#include <cstdint> +#include <cstring> +#include <optional> + +#include "absl/base/casts.h" +#include "absl/numeric/bits.h" +#include "absl/strings/string_view.h" +#include "quiche/common/platform/api/quiche_logging.h" +#include "quiche/common/quiche_data_reader.h" +#include "quiche/common/quiche_data_writer.h" +#include "quiche/common/quiche_endian.h" + +namespace quiche { +namespace { + +constexpr size_t kTheForbiddenMoqVarintLength = 7; +constexpr uint64_t kMaxEbmlVarintValue = (UINT64_C(1) << 56) - 2; + +// The two varint dialects are similar, the notable differences are: +// - MOQ varints use a sequence of 1s for encoding length, EBML uses 0s. +// - MOQ does not allow varints of length 7. +// - EBML does not allow varints of length 9, meaning only 2^56 values are +// encodable. +enum class VarintDialect { kMoq, kEbml }; + +template <VarintDialect dialect> +size_t GetVarintSizeForFirstByte(char first_byte_raw) { + uint8_t first_byte = absl::bit_cast<uint8_t>(first_byte_raw); + if constexpr (dialect == VarintDialect::kEbml) { + first_byte = ~first_byte; + } + return absl::countl_one(first_byte) + 1; +} + +template <VarintDialect dialect> +std::optional<uint64_t> ReadVarint(QuicheDataReader& reader) { + if (reader.BytesRemaining() == 0) { + return std::nullopt; + } + const size_t length = GetVarintSizeForFirstByte<dialect>(reader.PeekByte()); + if (reader.BytesRemaining() < length) { + return std::nullopt; + } + if (dialect == VarintDialect::kMoq && + length == kTheForbiddenMoqVarintLength) { + return std::nullopt; + } + if (dialect == VarintDialect::kEbml && length > 8) { + return std::nullopt; + } + QUICHE_DCHECK_LE(length, 9u); + + absl::string_view data = reader.PeekRemainingPayload(); + uint64_t result = 0; + if (length == 9) { + memcpy(&result, data.data() + 1, 8); + } else { + char* result_start = reinterpret_cast<char*>(&result) + (8 - length); + memcpy(result_start, data.data(), length); + const uint8_t first_byte_mask = (1 << (8 - length)) - 1; + *result_start &= first_byte_mask; + } + result = QuicheEndian::NetToHost64(result); + reader.Seek(length); + return result; +} + +template <VarintDialect dialect> +size_t GetVarintLengthForValue(uint64_t value) { + if (dialect == VarintDialect::kEbml) { + // Avoid writing "all ones" sequences, as those have magic semantics in some + // contexts (RFC 8794, Section 6.2). + ++value; + } + // This is equivalent to `ceil(bit_width(value) / 7)`. + size_t length = (6 + absl::bit_width(value)) / 7; + if (dialect == VarintDialect::kMoq && + length == kTheForbiddenMoqVarintLength) { + ++length; + } + return std::clamp<size_t>(length, 1, 9); +} + +template <VarintDialect dialect> +[[nodiscard]] bool WriteVarint(QuicheDataWriter& writer, uint64_t value, + size_t length) { + if (dialect == VarintDialect::kEbml && value > kMaxEbmlVarintValue) { + return false; + } + QUICHE_DCHECK_GE(length, 1u); + QUICHE_DCHECK_LE(length, 9u); + + if (length == 9) { + QUICHE_DCHECK(dialect == VarintDialect::kMoq); + return writer.WriteUInt8(0xff) && writer.WriteUInt64(value); + } + + char buffer[8]; + value = QuicheEndian::HostToNet64(value); + const char* input_start = + reinterpret_cast<const char*>(&value) + (8 - length); + memcpy(buffer, input_start, length); + const uint8_t first_byte_data_mask = (1 << (8 - length)) - 1; + const uint8_t first_byte_length_mask = ~first_byte_data_mask; + uint8_t first_byte_length = 1 << (8 - length); + if constexpr (dialect == VarintDialect::kMoq) { + first_byte_length = ~first_byte_length; + } + buffer[0] = (buffer[0] & first_byte_data_mask) | + (first_byte_length & first_byte_length_mask); + return writer.WriteBytes(buffer, length); +} + +} // namespace + +size_t GetMoqVarintLengthForFirstByte(char first_byte) { + return GetVarintSizeForFirstByte<VarintDialect::kMoq>(first_byte); +} +size_t GetEbmlVarintLengthForFirstByte(char first_byte) { + return GetVarintSizeForFirstByte<VarintDialect::kEbml>(first_byte); +} + +size_t GetMoqVarintLengthForValue(uint64_t value) { + return GetVarintLengthForValue<VarintDialect::kMoq>(value); +} +std::optional<size_t> GetEbmlVarintLengthForValue(uint64_t value) { + if (value > kMaxEbmlVarintValue) { + return std::nullopt; + } + return GetVarintLengthForValue<VarintDialect::kEbml>(value); +} + +std::optional<uint64_t> ReadMoqVarint(QuicheDataReader& reader) { + return ReadVarint<VarintDialect::kMoq>(reader); +} +std::optional<uint64_t> ReadEbmlVarint(QuicheDataReader& reader) { + return ReadVarint<VarintDialect::kEbml>(reader); +} + +[[nodiscard]] bool WriteMoqVarint(QuicheDataWriter& writer, uint64_t value) { + const size_t length = GetVarintLengthForValue<VarintDialect::kMoq>(value); + return WriteVarint<VarintDialect::kMoq>(writer, value, length); +} +[[nodiscard]] bool WriteEbmlVarint(QuicheDataWriter& writer, uint64_t value) { + const size_t length = GetVarintLengthForValue<VarintDialect::kEbml>(value); + return WriteVarint<VarintDialect::kEbml>(writer, value, length); +} +[[nodiscard]] bool WriteMoqVarintWithCustomLength(QuicheDataWriter& writer, + uint64_t value, + size_t length) { + return WriteVarint<VarintDialect::kMoq>(writer, value, length); +} + +} // namespace quiche
diff --git a/quiche/common/moq_varint.h b/quiche/common/moq_varint.h new file mode 100644 index 0000000..114f155 --- /dev/null +++ b/quiche/common/moq_varint.h
@@ -0,0 +1,57 @@ +// 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. + +// This file contains parsing and serialization logic for two closely related +// variable-length integer formats: +// - MOQT varint (draft-ietf-moq-transport-17, Section 1.4.1) +// - EBML varint (RFC 8794, Section 4). +// +// Both have the property that the first byte is sufficient to determine the +// length of the varint. +// +// Note that EBML defines magic varint values for indefinite lengths; those are +// not handled here when reading, but are avoided when writing. + +#ifndef QUICHE_COMMON_MOQ_VARINT_H_ +#define QUICHE_COMMON_MOQ_VARINT_H_ + +#include <cstddef> +#include <cstdint> +#include <optional> + +#include "quiche/common/quiche_data_reader.h" +#include "quiche/common/quiche_data_writer.h" + +namespace quiche { + +// From the first byte of the varint, determine its total length (the first byte +// is included in that length). +size_t GetMoqVarintLengthForFirstByte(char first_byte); +size_t GetEbmlVarintLengthForFirstByte(char first_byte); + +// From a value, determine the length of the minimal encoding of that value as a +// varint. +size_t GetMoqVarintLengthForValue(uint64_t value); +std::optional<size_t> GetEbmlVarintLengthForValue(uint64_t value); + +// Reads the varint from `reader`. If successful, the varint is removed from +// `data`, and the parsed value is returned. If unsuccessful, `data` is +// unchanged and std::nullopt is returned. +std::optional<uint64_t> ReadMoqVarint(QuicheDataReader& reader); +std::optional<uint64_t> ReadEbmlVarint(QuicheDataReader& reader); + +// Writes the varint into `writer`. Returns true on success. +[[nodiscard]] bool WriteMoqVarint(QuicheDataWriter& writer, uint64_t value); +[[nodiscard]] bool WriteEbmlVarint(QuicheDataWriter& writer, uint64_t value); + +// Writes the varint with the specified length instead of internally computed. +// Primarily meant to be used for testing. If the `value` is too large to fit +// into a varint of length `length`, the result is undefined. +[[nodiscard]] bool WriteMoqVarintWithCustomLength(QuicheDataWriter& writer, + uint64_t value, + size_t length); + +} // namespace quiche + +#endif // QUICHE_COMMON_MOQ_VARINT_H_
diff --git a/quiche/common/moq_varint_test.cc b/quiche/common/moq_varint_test.cc new file mode 100644 index 0000000..8059666 --- /dev/null +++ b/quiche/common/moq_varint_test.cc
@@ -0,0 +1,149 @@ +// 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-17(bis), 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>("fefa318fa8e3ca11"), 70423237261249041); + EXPECT_EQ(CompleteRead<ReadMoqVarint>("ffffffffffffffffff"), + 18446744073709551615u); + + // The forbidden MOQ varint length. + EXPECT_EQ(CompleteRead<ReadMoqVarint>("fcffffffffffff"), std::nullopt); + 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), "fe00ffffffffffff"); + + // 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
diff --git a/quiche/common/quiche_data_reader.cc b/quiche/common/quiche_data_reader.cc index 8f5bf8e..9d900ad 100644 --- a/quiche/common/quiche_data_reader.cc +++ b/quiche/common/quiche_data_reader.cc
@@ -5,12 +5,15 @@ #include "quiche/common/quiche_data_reader.h" #include <algorithm> +#include <cstdint> #include <cstring> +#include <optional> #include <string> #include "absl/strings/numbers.h" #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "quiche/common/moq_varint.h" #include "quiche/common/platform/api/quiche_bug_tracker.h" #include "quiche/common/platform/api/quiche_logging.h" #include "quiche/common/quiche_endian.h" @@ -247,6 +250,37 @@ return success; } +std::optional<size_t> QuicheDataReader::PeekMoqVarIntLength() { + if (PeekRemainingPayload().empty()) { + return std::nullopt; + } + return GetMoqVarintLengthForFirstByte(PeekByte()); +} + +bool QuicheDataReader::ReadMoqVarInt(uint64_t* result) { + std::optional<uint64_t> value = ReadMoqVarint(*this); + if (!value.has_value()) { + return false; + } + *result = *value; + return true; +} + +bool QuicheDataReader::ReadStringPieceMoqVarInt(absl::string_view* result) { + uint64_t result_length; + if (!ReadMoqVarInt(&result_length)) { + return false; + } + return ReadStringPiece(result, result_length); +} + +bool QuicheDataReader::ReadStringMoqVarInt(std::string& result) { + absl::string_view result_view; + bool success = ReadStringPieceMoqVarInt(&result_view); + result = std::string(result_view); + return success; +} + absl::string_view QuicheDataReader::ReadRemainingPayload() { absl::string_view payload = PeekRemainingPayload(); pos_ = len_;
diff --git a/quiche/common/quiche_data_reader.h b/quiche/common/quiche_data_reader.h index 3694d34..a71432e 100644 --- a/quiche/common/quiche_data_reader.h +++ b/quiche/common/quiche_data_reader.h
@@ -8,6 +8,7 @@ #include <cstddef> #include <cstdint> #include <limits> +#include <optional> #include <string> #include "absl/strings/string_view.h" @@ -122,6 +123,13 @@ // the number and subsequent string, true otherwise. bool ReadStringVarInt62(std::string& result); + // The equivalent of the functions above that uses 64-bit MOQ varints + // instead of 62-bit RFC 9000 ones. + std::optional<size_t> PeekMoqVarIntLength(); + bool ReadMoqVarInt(uint64_t* result); + bool ReadStringPieceMoqVarInt(absl::string_view* result); + bool ReadStringMoqVarInt(std::string& result); + // Returns the remaining payload as a absl::string_view. // // NOTE: Does not copy but rather references strings in the underlying buffer.
diff --git a/quiche/common/quiche_data_reader_test.cc b/quiche/common/quiche_data_reader_test.cc index 16378d8..b294f34 100644 --- a/quiche/common/quiche_data_reader_test.cc +++ b/quiche/common/quiche_data_reader_test.cc
@@ -5,6 +5,7 @@ #include "quiche/common/quiche_data_reader.h" #include <cstdint> +#include <string> #include "absl/strings/string_view.h" #include "quiche/common/platform/api/quiche_test.h" @@ -194,4 +195,22 @@ EXPECT_EQ(reader.ReadAtMost(1000), ""); } +TEST(QuicheDataReaderTest, MoqVarint) { + constexpr absl::string_view kData = "\xbb\xbd"; + QuicheDataReader reader(kData); + uint64_t out = 0; + ASSERT_TRUE(reader.ReadMoqVarInt(&out)); + EXPECT_EQ(out, 15293u); + EXPECT_TRUE(reader.IsDoneReading()); +} + +TEST(QuicheDataReaderTest, MoqVarintString) { + constexpr absl::string_view kData = "\x04test"; + QuicheDataReader reader(kData); + std::string out; + ASSERT_TRUE(reader.ReadStringMoqVarInt(out)); + EXPECT_EQ(out, "test"); + EXPECT_TRUE(reader.IsDoneReading()); +} + } // namespace quiche
diff --git a/quiche/common/quiche_data_writer.cc b/quiche/common/quiche_data_writer.cc index bf49f59..c439e72 100644 --- a/quiche/common/quiche_data_writer.cc +++ b/quiche/common/quiche_data_writer.cc
@@ -5,11 +5,13 @@ #include "quiche/common/quiche_data_writer.h" #include <algorithm> +#include <cstdint> #include <limits> #include <string> #include "absl/strings/str_cat.h" #include "absl/strings/string_view.h" +#include "quiche/common/moq_varint.h" #include "quiche/common/platform/api/quiche_bug_tracker.h" #include "quiche/common/quiche_endian.h" @@ -220,6 +222,10 @@ return false; } +bool QuicheDataWriter::WriteMoqVarInt(uint64_t value) { + return WriteMoqVarint(*this, value); +} + bool QuicheDataWriter::WriteStringPieceVarInt62( const absl::string_view& string_piece) { if (!WriteVarInt62(string_piece.size())) { @@ -233,6 +239,12 @@ return true; } +bool QuicheDataWriter::WriteStringPieceMoqVarInt( + absl::string_view string_piece) { + return WriteMoqVarInt(string_piece.size()) && + WriteBytes(string_piece.data(), string_piece.size()); +} + // static QuicheVariableLengthIntegerLength QuicheDataWriter::GetVarInt62Len( uint64_t value) {
diff --git a/quiche/common/quiche_data_writer.h b/quiche/common/quiche_data_writer.h index 31b9210..94f8480 100644 --- a/quiche/common/quiche_data_writer.h +++ b/quiche/common/quiche_data_writer.h
@@ -92,6 +92,9 @@ // in the buffer. Requires that the endianness is NETWORK_BYTE_ORDER. bool WriteVarInt62(uint64_t value); + // Writes a 64-bit unsigned integer encoded using the MOQT varint syntax. + bool WriteMoqVarInt(uint64_t value); + // Same as WriteVarInt62(uint64_t), but forces an encoding size to write to. // This is not as optimized as WriteVarInt62(uint64_t). Returns false if the // value does not fit in the specified write_length or if there is no room in @@ -104,6 +107,9 @@ // NETWORK_BYTE_ORDER. bool WriteStringPieceVarInt62(const absl::string_view& string_piece); + // Same as above but with MOQ-style varints. + bool WriteStringPieceMoqVarInt(absl::string_view string_piece); + // Utility function to return the number of bytes needed to encode // the given value using IETF VarInt62 encoding. Returns the number // of bytes required to encode the given integer or 0 if the value
diff --git a/quiche/common/quiche_data_writer_test.cc b/quiche/common/quiche_data_writer_test.cc index 745e1ef..866e9db 100644 --- a/quiche/common/quiche_data_writer_test.cc +++ b/quiche/common/quiche_data_writer_test.cc
@@ -4,6 +4,7 @@ #include "quiche/common/quiche_data_writer.h" +#include <array> #include <cstdint> #include <cstring> #include <string> @@ -21,6 +22,8 @@ namespace test { namespace { +using ::testing::ElementsAre; + char* AsChars(unsigned char* data) { return reinterpret_cast<char*>(data); } struct TestParams { @@ -832,6 +835,14 @@ previously_read_payload3.length(), buffer, sizeof(buffer)); } +TEST_P(QuicheDataWriterTest, MoqVarint) { + std::array<char, 3> buffer; + QuicheDataWriter writer(buffer.size(), buffer.data()); + ASSERT_TRUE(writer.WriteStringPieceMoqVarInt("")); + ASSERT_TRUE(writer.WriteStringPieceMoqVarInt(" ")); + EXPECT_THAT(buffer, ElementsAre(0x00, 0x01, 0x20)); +} + } // namespace } // namespace test } // namespace quiche