Skip to content

Commit 8f36373

Browse files
vasilvvcopybara-github
authored andcommitted
Add a dedicated string tuple type to QUICHE.
I encountered some pain points in the current MOQT namespace API, started refactoring, and ended up writing this. PiperOrigin-RevId: 859709614
1 parent f9b7295 commit 8f36373

File tree

5 files changed

+393
-0
lines changed

5 files changed

+393
-0
lines changed

build/source_list.bzl

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ quiche_core_hdrs = [
6060
"common/quiche_socket_address.h",
6161
"common/quiche_status_utils.h",
6262
"common/quiche_stream.h",
63+
"common/quiche_string_tuple.h",
6364
"common/quiche_text_utils.h",
6465
"common/quiche_weak_ptr.h",
6566
"common/simple_buffer_allocator.h",
@@ -1150,6 +1151,7 @@ quiche_tests_srcs = [
11501151
"common/quiche_simple_arena_test.cc",
11511152
"common/quiche_socket_address_test.cc",
11521153
"common/quiche_status_utils_test.cc",
1154+
"common/quiche_string_tuple_test.cc",
11531155
"common/quiche_text_utils_test.cc",
11541156
"common/quiche_weak_ptr_test.cc",
11551157
"common/simple_buffer_allocator_test.cc",

build/source_list.gni

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ quiche_core_hdrs = [
6060
"src/quiche/common/quiche_socket_address.h",
6161
"src/quiche/common/quiche_status_utils.h",
6262
"src/quiche/common/quiche_stream.h",
63+
"src/quiche/common/quiche_string_tuple.h",
6364
"src/quiche/common/quiche_text_utils.h",
6465
"src/quiche/common/quiche_weak_ptr.h",
6566
"src/quiche/common/simple_buffer_allocator.h",
@@ -1151,6 +1152,7 @@ quiche_tests_srcs = [
11511152
"src/quiche/common/quiche_simple_arena_test.cc",
11521153
"src/quiche/common/quiche_socket_address_test.cc",
11531154
"src/quiche/common/quiche_status_utils_test.cc",
1155+
"src/quiche/common/quiche_string_tuple_test.cc",
11541156
"src/quiche/common/quiche_text_utils_test.cc",
11551157
"src/quiche/common/quiche_weak_ptr_test.cc",
11561158
"src/quiche/common/simple_buffer_allocator_test.cc",

build/source_list.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@
5959
"quiche/common/quiche_socket_address.h",
6060
"quiche/common/quiche_status_utils.h",
6161
"quiche/common/quiche_stream.h",
62+
"quiche/common/quiche_string_tuple.h",
6263
"quiche/common/quiche_text_utils.h",
6364
"quiche/common/quiche_weak_ptr.h",
6465
"quiche/common/simple_buffer_allocator.h",
@@ -1150,6 +1151,7 @@
11501151
"quiche/common/quiche_simple_arena_test.cc",
11511152
"quiche/common/quiche_socket_address_test.cc",
11521153
"quiche/common/quiche_status_utils_test.cc",
1154+
"quiche/common/quiche_string_tuple_test.cc",
11531155
"quiche/common/quiche_text_utils_test.cc",
11541156
"quiche/common/quiche_weak_ptr_test.cc",
11551157
"quiche/common/simple_buffer_allocator_test.cc",
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
// Copyright 2026 The Chromium Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
#ifndef QUICHE_COMMON_QUICHE_STRING_TUPLE_H_
6+
#define QUICHE_COMMON_QUICHE_STRING_TUPLE_H_
7+
8+
#include <algorithm>
9+
#include <cstddef>
10+
#include <cstdint>
11+
#include <initializer_list>
12+
#include <iterator>
13+
#include <limits>
14+
#include <optional>
15+
#include <string>
16+
#include <type_traits>
17+
#include <vector>
18+
19+
#include "absl/base/attributes.h"
20+
#include "absl/container/inlined_vector.h"
21+
#include "absl/strings/escaping.h"
22+
#include "absl/strings/str_cat.h"
23+
#include "absl/strings/str_format.h"
24+
#include "absl/strings/str_join.h"
25+
#include "absl/strings/string_view.h"
26+
#include "quiche/common/platform/api/quiche_bug_tracker.h"
27+
#include "quiche/common/platform/api/quiche_export.h"
28+
29+
namespace quiche {
30+
31+
// Flexible container for storing tuples of potentially small strings. Allows
32+
// the maximum byte size of the string to be specified at the compile-time; the
33+
// specified size can be used to optimize the data layout of the container.
34+
//
35+
// Note that the limit in question is the limit on the number of string bytes;
36+
// as empty elements are allowed, the number of tuple elements can be
37+
// potentially unlimited.
38+
template <size_t MaxDataSize = std::numeric_limits<size_t>::max(),
39+
size_t InlinedSizes = 4>
40+
class QUICHE_NO_EXPORT QuicheStringTuple {
41+
private:
42+
// Use the information about the maximum size of the tuple to pick the
43+
// smallest possible offset type.
44+
using OffsetT = std::conditional_t<
45+
MaxDataSize <= 0xffff, uint16_t,
46+
std::conditional_t<MaxDataSize <= 0xffffffff, uint32_t, uint64_t> >;
47+
48+
// Basic read-only proxy iterator over the string tuple. Invalidated on any
49+
// removal of elements from the tuple.
50+
class TupleIterator {
51+
public:
52+
using value_type = absl::string_view;
53+
using difference_type = std::ptrdiff_t;
54+
using reference = value_type&;
55+
using iterator_category = std::input_iterator_tag;
56+
57+
TupleIterator(const TupleIterator&) = default;
58+
TupleIterator(TupleIterator&&) = default;
59+
TupleIterator& operator=(const TupleIterator&) = default;
60+
TupleIterator& operator=(TupleIterator&&) = default;
61+
62+
absl::string_view operator*() const { return (*tuple_)[offset_]; }
63+
64+
TupleIterator& operator++() {
65+
++offset_;
66+
return *this;
67+
}
68+
TupleIterator operator++(int) {
69+
++offset_;
70+
return TupleIterator(tuple_, offset_ - 1);
71+
}
72+
TupleIterator& operator--() {
73+
--offset_;
74+
return *this;
75+
}
76+
TupleIterator operator--(int) {
77+
--offset_;
78+
return TupleIterator(tuple_, offset_ + 1);
79+
}
80+
81+
bool operator==(const TupleIterator&) const = default;
82+
bool operator!=(const TupleIterator&) const = default;
83+
84+
private:
85+
friend class QuicheStringTuple;
86+
87+
TupleIterator(const QuicheStringTuple* tuple, size_t offset)
88+
: tuple_(tuple), offset_(offset) {}
89+
90+
const QuicheStringTuple* tuple_;
91+
OffsetT offset_;
92+
};
93+
94+
public:
95+
using value_type = absl::string_view;
96+
using reference = value_type&;
97+
using const_reference = const value_type&;
98+
using iterator = TupleIterator;
99+
using const_iterator = TupleIterator;
100+
101+
static constexpr size_t kMaxDataSize = MaxDataSize;
102+
103+
QuicheStringTuple() = default;
104+
QuicheStringTuple(const QuicheStringTuple&) noexcept = default;
105+
QuicheStringTuple(QuicheStringTuple&&) noexcept = default;
106+
QuicheStringTuple& operator=(const QuicheStringTuple&) noexcept = default;
107+
QuicheStringTuple& operator=(QuicheStringTuple&&) noexcept = default;
108+
109+
// Convenience constructor meant for constants. Will QUIC_BUG if adding any of
110+
// the elements fails.
111+
explicit QuicheStringTuple(
112+
std::initializer_list<const absl::string_view> items) {
113+
ReserveTupleElements(items.size());
114+
for (absl::string_view item : items) {
115+
bool success = Add(item);
116+
QUICHE_BUG_IF(QuicheStringTuple_initializer_list_too_large, !success)
117+
<< "Attempted to use initializer list constructor for "
118+
"QuicheStringTuple with a string that exceeds the specified "
119+
"maximum size";
120+
}
121+
}
122+
123+
// Adds a new string to the end of the tuple. Returns false if this operation
124+
// results in the size limit being exceeded.
125+
[[nodiscard]] bool Add(absl::string_view element) {
126+
if (element.size() + data_.size() > kMaxDataSize) {
127+
return false;
128+
}
129+
elements_start_offsets_.push_back(data_.size());
130+
data_.append(element.data(), element.size());
131+
return true;
132+
}
133+
134+
// Removes an element from the end of the tuple, if there are any.
135+
bool Pop() {
136+
if (empty()) {
137+
return false;
138+
}
139+
data_.resize(elements_start_offsets_.back());
140+
elements_start_offsets_.pop_back();
141+
return true;
142+
}
143+
144+
// Removes all elements from the tuple.
145+
void Clear() {
146+
data_.clear();
147+
elements_start_offsets_.clear();
148+
}
149+
150+
// Returns the string at the offset `i`, or nullopt if `i` is out of bounds.
151+
std::optional<absl::string_view> ValueAt(size_t i) const
152+
ABSL_ATTRIBUTE_LIFETIME_BOUND {
153+
if (i >= elements_start_offsets_.size()) {
154+
return std::nullopt;
155+
}
156+
if (i == elements_start_offsets_.size() - 1) {
157+
return absl::string_view(data_).substr(elements_start_offsets_[i]);
158+
}
159+
const size_t start = elements_start_offsets_[i];
160+
const size_t end = elements_start_offsets_[i + 1];
161+
return absl::string_view(data_).substr(start, end - start);
162+
}
163+
164+
// Returns the string at the offset `i`; QUICHE_BUGs if `i` is out of bounds
165+
// and returns an empty string.
166+
absl::string_view operator[](size_t i) const {
167+
auto result = ValueAt(i);
168+
if (!result.has_value()) {
169+
QUICHE_BUG(QuicheStringTuple_oob_access)
170+
<< "Tried to access an out-of-bounds element #" << i
171+
<< " for a string tuple of size " << size();
172+
return absl::string_view();
173+
}
174+
return *result;
175+
}
176+
177+
// Allows pre-allocating memory if the total byte size of the tuple and the
178+
// number of elements in it are known in advance.
179+
void ReserveDataBytes(size_t n) { data_.reserve(n); }
180+
void ReserveTupleElements(size_t n) { elements_start_offsets_.reserve(n); }
181+
182+
// Returns true if the specified tuple is a prefix of (or is equal to) this
183+
// tuple.
184+
bool IsPrefix(const QuicheStringTuple& other) const {
185+
return other.size() <= size() &&
186+
std::equal(iterator(this, 0), iterator(this, other.size()),
187+
other.begin());
188+
}
189+
190+
bool empty() const { return elements_start_offsets_.empty(); }
191+
size_t size() const { return elements_start_offsets_.size(); }
192+
size_t TotalBytes() const { return data_.size(); }
193+
size_t BytesLeft() const { return kMaxDataSize - data_.size(); }
194+
195+
iterator begin() const { return iterator(this, 0); }
196+
iterator end() const { return iterator(this, size()); }
197+
198+
// Tuples are lexicographical ordered.
199+
auto operator<=>(const QuicheStringTuple& other) const {
200+
return std::lexicographical_compare_three_way(begin(), end(), other.begin(),
201+
other.end());
202+
}
203+
bool operator==(const QuicheStringTuple& other) const = default;
204+
205+
template <typename H>
206+
friend H AbslHashValue(H h, const QuicheStringTuple& tuple) {
207+
return H::combine(std::move(h), tuple.data_, tuple.elements_start_offsets_);
208+
}
209+
210+
// String tuple pretty-printer primarily meant for debugging purposes.
211+
template <typename Sink>
212+
friend void AbslStringify(Sink& sink, const QuicheStringTuple& tuple) {
213+
std::vector<std::string> bits;
214+
bits.reserve(tuple.size());
215+
for (absl::string_view element : tuple) {
216+
bits.push_back(absl::StrCat("\"", absl::CHexEscape(element), "\""));
217+
}
218+
absl::Format(&sink, "{%s}", absl::StrJoin(bits, ", "));
219+
}
220+
221+
private:
222+
std::string data_;
223+
absl::InlinedVector<OffsetT, InlinedSizes> elements_start_offsets_;
224+
};
225+
226+
static_assert(std::input_iterator<QuicheStringTuple<>::iterator>);
227+
228+
} // namespace quiche
229+
230+
#endif // QUICHE_COMMON_QUICHE_STRING_TUPLE_H_

0 commit comments

Comments
 (0)