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