blob: 20e383be67fc163d440c81d5dddd4d57cdc4eb13 [file] [log] [blame]
// 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 "absl/types/span.h"
#include "quiche/common/platform/api/quiche_bug_tracker.h"
#include "quiche/common/platform/api/quiche_export.h"
#include "quiche/common/vectorized_io_utils.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;
}
AddUnchecked(element);
return true;
}
// Appends another tuple to the end of this one. Returns false if this
// operation results in the size limit being exceeded.
[[nodiscard]] bool Append(const QuicheStringTuple& suffix) {
if (data_.size() + suffix.data_.size() > kMaxDataSize) {
return false;
}
OffsetT current_offset = data_.size();
elements_start_offsets_.reserve(elements_start_offsets_.size() +
suffix.elements_start_offsets_.size());
for (const absl::string_view element : suffix) {
elements_start_offsets_.push_back(current_offset);
current_offset += element.size();
}
data_.append(suffix.data_);
return true;
}
// Appends a span of string_views to the end of the tuple. Returns false if
// this operation results in the size limit being exceeded.
[[nodiscard]] bool Append(absl::Span<const absl::string_view> span) {
size_t total_size = TotalStringViewSpanSize(span);
if (data_.size() + total_size > kMaxDataSize) {
return false;
}
elements_start_offsets_.reserve(elements_start_offsets_.size() +
span.size());
data_.reserve(data_.size() + total_size);
for (absl::string_view element : span) {
AddUnchecked(element);
}
return true;
}
// If `prefix` is a prefix of this current tuple, the prefix is removed.
[[nodiscard]] bool ConsumePrefix(const QuicheStringTuple& prefix) {
if (!IsPrefix(prefix)) {
return false;
}
data_ = data_.substr(prefix.data_.size());
const OffsetT prefix_offset = prefix.data_.size();
const size_t prefix_element_count = prefix.elements_start_offsets_.size();
for (size_t i = prefix_element_count; i < elements_start_offsets_.size();
++i) {
elements_start_offsets_[i - prefix_element_count] =
elements_start_offsets_[i] - prefix_offset;
}
elements_start_offsets_.resize(elements_start_offsets_.size() -
prefix_element_count);
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(); }
absl::string_view front() const { return (*this)[0]; }
absl::string_view back() const { return (*this)[size() - 1]; }
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:
void AddUnchecked(absl::string_view element) {
elements_start_offsets_.push_back(data_.size());
data_.append(element.data(), element.size());
}
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_