| // 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. |
| |
| #ifndef QUICHE_COMMON_QUICHE_STRING_TUPLE_H_ |
| #define QUICHE_COMMON_QUICHE_STRING_TUPLE_H_ |
| |
| #include <algorithm> |
| #include <cstddef> |
| #include <cstdint> |
| #include <initializer_list> |
| #include <iterator> |
| #include <limits> |
| #include <optional> |
| #include <string> |
| #include <type_traits> |
| #include <vector> |
| |
| #include "absl/base/attributes.h" |
| #include "absl/container/inlined_vector.h" |
| #include "absl/strings/escaping.h" |
| #include "absl/strings/str_cat.h" |
| #include "absl/strings/str_format.h" |
| #include "absl/strings/str_join.h" |
| #include "absl/strings/string_view.h" |
| #include "quiche/common/platform/api/quiche_bug_tracker.h" |
| #include "quiche/common/platform/api/quiche_export.h" |
| |
| namespace quiche { |
| |
| // Flexible container for storing tuples of potentially small strings. Allows |
| // the maximum byte size of the string to be specified at the compile-time; the |
| // specified size can be used to optimize the data layout of the container. |
| // |
| // Note that the limit in question is the limit on the number of string bytes; |
| // as empty elements are allowed, the number of tuple elements can be |
| // potentially unlimited. |
| template <size_t MaxDataSize = std::numeric_limits<size_t>::max(), |
| size_t InlinedSizes = 4> |
| class QUICHE_NO_EXPORT QuicheStringTuple { |
| private: |
| // Use the information about the maximum size of the tuple to pick the |
| // smallest possible offset type. |
| using OffsetT = std::conditional_t< |
| MaxDataSize <= 0xffff, uint16_t, |
| std::conditional_t<MaxDataSize <= 0xffffffff, uint32_t, uint64_t> >; |
| |
| // Basic read-only proxy iterator over the string tuple. Invalidated on any |
| // removal of elements from the tuple. |
| class TupleIterator { |
| public: |
| using value_type = absl::string_view; |
| using difference_type = std::ptrdiff_t; |
| using reference = value_type&; |
| using iterator_category = std::input_iterator_tag; |
| |
| TupleIterator(const TupleIterator&) = default; |
| TupleIterator(TupleIterator&&) = default; |
| TupleIterator& operator=(const TupleIterator&) = default; |
| TupleIterator& operator=(TupleIterator&&) = default; |
| |
| absl::string_view operator*() const { return (*tuple_)[offset_]; } |
| |
| TupleIterator& operator++() { |
| ++offset_; |
| return *this; |
| } |
| TupleIterator operator++(int) { |
| ++offset_; |
| return TupleIterator(tuple_, offset_ - 1); |
| } |
| TupleIterator& operator--() { |
| --offset_; |
| return *this; |
| } |
| TupleIterator operator--(int) { |
| --offset_; |
| return TupleIterator(tuple_, offset_ + 1); |
| } |
| |
| bool operator==(const TupleIterator&) const = default; |
| bool operator!=(const TupleIterator&) const = default; |
| |
| private: |
| friend class QuicheStringTuple; |
| |
| TupleIterator(const QuicheStringTuple* tuple, size_t offset) |
| : tuple_(tuple), offset_(offset) {} |
| |
| const QuicheStringTuple* tuple_; |
| OffsetT offset_; |
| }; |
| |
| public: |
| using value_type = absl::string_view; |
| using reference = value_type&; |
| using const_reference = const value_type&; |
| using iterator = TupleIterator; |
| using const_iterator = TupleIterator; |
| |
| static constexpr size_t kMaxDataSize = MaxDataSize; |
| |
| QuicheStringTuple() = default; |
| QuicheStringTuple(const QuicheStringTuple&) noexcept = default; |
| QuicheStringTuple(QuicheStringTuple&&) noexcept = default; |
| QuicheStringTuple& operator=(const QuicheStringTuple&) noexcept = default; |
| QuicheStringTuple& operator=(QuicheStringTuple&&) noexcept = default; |
| |
| // Convenience constructor meant for constants. Will QUIC_BUG if adding any of |
| // the elements fails. |
| explicit QuicheStringTuple( |
| std::initializer_list<const absl::string_view> items) { |
| ReserveTupleElements(items.size()); |
| for (absl::string_view item : items) { |
| bool success = Add(item); |
| QUICHE_BUG_IF(QuicheStringTuple_initializer_list_too_large, !success) |
| << "Attempted to use initializer list constructor for " |
| "QuicheStringTuple with a string that exceeds the specified " |
| "maximum size"; |
| } |
| } |
| |
| // Adds a new string to the end of the tuple. Returns false if this operation |
| // results in the size limit being exceeded. |
| [[nodiscard]] bool Add(absl::string_view element) { |
| if (element.size() + data_.size() > kMaxDataSize) { |
| return false; |
| } |
| elements_start_offsets_.push_back(data_.size()); |
| data_.append(element.data(), element.size()); |
| return true; |
| } |
| |
| // Removes an element from the end of the tuple, if there are any. |
| bool Pop() { |
| if (empty()) { |
| return false; |
| } |
| data_.resize(elements_start_offsets_.back()); |
| elements_start_offsets_.pop_back(); |
| return true; |
| } |
| |
| // Removes all elements from the tuple. |
| void Clear() { |
| data_.clear(); |
| elements_start_offsets_.clear(); |
| } |
| |
| // Returns the string at the offset `i`, or nullopt if `i` is out of bounds. |
| std::optional<absl::string_view> ValueAt(size_t i) const |
| ABSL_ATTRIBUTE_LIFETIME_BOUND { |
| if (i >= elements_start_offsets_.size()) { |
| return std::nullopt; |
| } |
| if (i == elements_start_offsets_.size() - 1) { |
| return absl::string_view(data_).substr(elements_start_offsets_[i]); |
| } |
| const size_t start = elements_start_offsets_[i]; |
| const size_t end = elements_start_offsets_[i + 1]; |
| return absl::string_view(data_).substr(start, end - start); |
| } |
| |
| // Returns the string at the offset `i`; QUICHE_BUGs if `i` is out of bounds |
| // and returns an empty string. |
| absl::string_view operator[](size_t i) const { |
| auto result = ValueAt(i); |
| if (!result.has_value()) { |
| QUICHE_BUG(QuicheStringTuple_oob_access) |
| << "Tried to access an out-of-bounds element #" << i |
| << " for a string tuple of size " << size(); |
| return absl::string_view(); |
| } |
| return *result; |
| } |
| |
| // Allows pre-allocating memory if the total byte size of the tuple and the |
| // number of elements in it are known in advance. |
| void ReserveDataBytes(size_t n) { data_.reserve(n); } |
| void ReserveTupleElements(size_t n) { elements_start_offsets_.reserve(n); } |
| |
| // Returns true if the specified tuple is a prefix of (or is equal to) this |
| // tuple. |
| bool IsPrefix(const QuicheStringTuple& other) const { |
| return other.size() <= size() && |
| std::equal(iterator(this, 0), iterator(this, other.size()), |
| other.begin()); |
| } |
| |
| bool empty() const { return elements_start_offsets_.empty(); } |
| size_t size() const { return elements_start_offsets_.size(); } |
| size_t TotalBytes() const { return data_.size(); } |
| size_t BytesLeft() const { return kMaxDataSize - data_.size(); } |
| |
| iterator begin() const { return iterator(this, 0); } |
| iterator end() const { return iterator(this, size()); } |
| |
| // Tuples are lexicographical ordered. |
| auto operator<=>(const QuicheStringTuple& other) const { |
| return std::lexicographical_compare_three_way(begin(), end(), other.begin(), |
| other.end()); |
| } |
| bool operator==(const QuicheStringTuple& other) const = default; |
| |
| template <typename H> |
| friend H AbslHashValue(H h, const QuicheStringTuple& tuple) { |
| return H::combine(std::move(h), tuple.data_, tuple.elements_start_offsets_); |
| } |
| |
| // String tuple pretty-printer primarily meant for debugging purposes. |
| template <typename Sink> |
| friend void AbslStringify(Sink& sink, const QuicheStringTuple& tuple) { |
| std::vector<std::string> bits; |
| bits.reserve(tuple.size()); |
| for (absl::string_view element : tuple) { |
| bits.push_back(absl::StrCat("\"", absl::CHexEscape(element), "\"")); |
| } |
| absl::Format(&sink, "{%s}", absl::StrJoin(bits, ", ")); |
| } |
| |
| private: |
| std::string data_; |
| absl::InlinedVector<OffsetT, InlinedSizes> elements_start_offsets_; |
| }; |
| |
| static_assert(std::input_iterator<QuicheStringTuple<>::iterator>); |
| |
| } // namespace quiche |
| |
| #endif // QUICHE_COMMON_QUICHE_STRING_TUPLE_H_ |