xref: /aosp_15_r20/external/pigweed/pw_multibuf/public/pw_multibuf/multibuf.h (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1 // Copyright 2023 The Pigweed Authors
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License"); you may not
4 // use this file except in compliance with the License. You may obtain a copy of
5 // the License at
6 //
7 //     https://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software
10 // distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 // WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 // License for the specific language governing permissions and limitations under
13 // the License.
14 #pragma once
15 
16 #include <iterator>
17 #include <tuple>
18 
19 #include "pw_multibuf/chunk.h"
20 #include "pw_preprocessor/compiler.h"
21 #include "pw_status/status_with_size.h"
22 
23 namespace pw::multibuf {
24 
25 class MultiBuf;
26 
27 /// A `Chunk`-oriented view of a `MultiBuf`.
28 class MultiBufChunks {
29  public:
30   using element_type = Chunk;
31   using value_type = Chunk;
32   using pointer = Chunk*;
33   using reference = Chunk&;
34   using const_pointer = const Chunk*;
35   using difference_type = std::ptrdiff_t;
36   using const_reference = const Chunk&;
37   using size_type = std::size_t;
38 
39   /// A `std::forward_iterator` over the `Chunk`s of a `MultiBuf`.
40   class iterator {
41    public:
42     using value_type = Chunk;
43     using difference_type = std::ptrdiff_t;
44     using reference = Chunk&;
45     using pointer = Chunk*;
46     using iterator_category = std::forward_iterator_tag;
47 
48     constexpr iterator() = default;
49 
50     constexpr reference operator*() const { return *chunk_; }
51     constexpr pointer operator->() const { return chunk_; }
52 
53     constexpr iterator& operator++() {
54       chunk_ = chunk_->next_in_buf_;
55       return *this;
56     }
57 
58     constexpr iterator operator++(int) {
59       iterator tmp = *this;
60       ++(*this);
61       return tmp;
62     }
63 
64     constexpr bool operator==(const iterator& other) const {
65       return chunk_ == other.chunk_;
66     }
67 
68     constexpr bool operator!=(const iterator& other) const {
69       return chunk_ != other.chunk_;
70     }
71 
72    private:
73     friend class MultiBufChunks;
74     friend class MultiBuf;
75 
iterator(Chunk * chunk)76     constexpr iterator(Chunk* chunk) : chunk_(chunk) {}
end()77     static constexpr iterator end() { return iterator(nullptr); }
78     Chunk* chunk_ = nullptr;
79   };
80 
81   /// A const `std::forward_iterator` over the `Chunk`s of a `MultiBuf`.
82   class const_iterator {
83    public:
84     using value_type = const Chunk;
85     using difference_type = std::ptrdiff_t;
86     using reference = const Chunk&;
87     using pointer = const Chunk*;
88     using iterator_category = std::forward_iterator_tag;
89 
90     constexpr const_iterator() = default;
91 
92     constexpr reference operator*() const { return *chunk_; }
93     constexpr pointer operator->() const { return chunk_; }
94 
95     constexpr const_iterator& operator++() {
96       chunk_ = chunk_->next_in_buf_;
97       return *this;
98     }
99 
100     constexpr const_iterator operator++(int) {
101       const_iterator tmp = *this;
102       ++(*this);
103       return tmp;
104     }
105 
106     constexpr bool operator==(const const_iterator& other) const {
107       return chunk_ == other.chunk_;
108     }
109 
110     constexpr bool operator!=(const const_iterator& other) const {
111       return chunk_ != other.chunk_;
112     }
113 
114    private:
115     friend class MultiBufChunks;
116     friend class MultiBuf;
117 
const_iterator(const Chunk * chunk)118     constexpr const_iterator(const Chunk* chunk) : chunk_(chunk) {}
end()119     static constexpr const_iterator end() { return const_iterator(nullptr); }
120     const Chunk* chunk_ = nullptr;
121   };
122 
123   MultiBufChunks(const MultiBufChunks&) = delete;
124   MultiBufChunks& operator=(const MultiBufChunks&) = delete;
125 
126   /// Returns a reference to the first chunk.
127   ///
128   /// The behavior of this method is undefined when `size() == 0`.
front()129   constexpr Chunk& front() { return *first_; }
front()130   constexpr const Chunk& front() const { return *first_; }
131 
132   /// Returns a reference to the final chunk.
133   ///
134   /// The behavior of this method is undefined when `size() == 0`.
135   ///
136   /// NOTE: this method is `O(size())`.
back()137   Chunk& back() { return const_cast<Chunk&>(std::as_const(*this).back()); }
138   const Chunk& back() const;
139 
begin()140   constexpr iterator begin() { return iterator(first_); }
begin()141   constexpr const_iterator begin() const { return cbegin(); }
cbegin()142   constexpr const_iterator cbegin() const { return const_iterator(first_); }
143 
end()144   constexpr iterator end() { return iterator::end(); }
end()145   constexpr const_iterator end() const { return cend(); }
cend()146   constexpr const_iterator cend() const { return const_iterator::end(); }
147 
148   /// Returns the number of `Chunk`s in this `MultiBuf`, including empty chunks.
size()149   size_t size() const {
150     return static_cast<size_t>(std::distance(begin(), end()));
151   }
152 
153   /// Returns the total number of bytes in all `Chunk`s.
154   size_t size_bytes() const;
155 
156   /// Returns whether the `MultiBuf` contains any chunks (`size() == 0`).
empty()157   [[nodiscard]] bool empty() const { return first_ == nullptr; }
158 
159   /// Pushes `Chunk` onto the front of the `MultiBuf`.
160   ///
161   /// This operation does not move any data and is `O(1)`.
162   void push_front(OwnedChunk&& chunk);
163 
164   /// Pushes `Chunk` onto the end of the `MultiBuf`.
165   ///
166   /// This operation does not move any data and is `O(Chunks().size())`.
167   void push_back(OwnedChunk&& chunk);
168 
169   /// Removes the first `Chunk`.
170   ///
171   /// This operation does not move any data and is `O(1)`.
172   OwnedChunk take_front();
173 
174   /// Inserts `chunk` into the specified position in the `MultiBuf`. The `Chunk`
175   /// at `position` will be after the new chunk.
176   ///
177   /// This operation does not move any data and is `O(Chunks().size())`.
178   ///
179   /// Returns an iterator pointing to the newly-inserted `Chunk`.
180   //
181   // Implementation note: `Chunks().size()` should be remain relatively small,
182   // but this could be made `O(1)` in the future by adding a `prev` pointer to
183   // the `iterator`.
184   iterator insert(iterator position, OwnedChunk&& chunk);
185 
186   /// Removes and returns `Chunk` from the specified position.
187   ///
188   /// This operation does not move any data and is `O(Chunks().size())`.
189   ///
190   /// Returns an iterator pointing to the `Chunk` after the removed `Chunk`, or
191   /// `Chunks().end()` if this was the last `Chunk` in the `MultiBuf`.
192   //
193   // Implementation note: `Chunks().size()` should be remain relatively small,
194   // but this could be made `O(1)` in the future by adding a `prev` pointer to
195   // the `iterator`.
196   std::tuple<iterator, OwnedChunk> take(iterator position);
197 
198  protected:
MultiBufChunks(Chunk * first_chunk)199   explicit constexpr MultiBufChunks(Chunk* first_chunk) : first_(first_chunk) {}
200 
201   /// This destructor will acquire a mutex and is not IRQ safe.
~MultiBufChunks()202   ~MultiBufChunks() { Release(); }
203 
204   // Disable maybe-uninitialized: this check fails erroneously on Windows GCC.
205   PW_MODIFY_DIAGNOSTICS_PUSH();
206   PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wmaybe-uninitialized");
MultiBufChunks(MultiBufChunks && other)207   constexpr MultiBufChunks(MultiBufChunks&& other) noexcept
208       : first_(other.first_) {
209     other.first_ = nullptr;
210   }
211   PW_MODIFY_DIAGNOSTICS_POP();
212 
213   MultiBufChunks& operator=(MultiBufChunks&& other) noexcept {
214     Release();
215     first_ = other.first_;
216     other.first_ = nullptr;
217     return *this;
218   }
219 
220   // Releases all chunks in the `MultiBuf`.
221   void Release() noexcept;
222 
223   void PushSuffix(MultiBufChunks&& tail);
224 
225   /// Returns the `Chunk` preceding `chunk` in this `MultiBuf`.
226   ///
227   /// Requires that this `MultiBuf` is not empty, and that `chunk` is either in
228   /// `MultiBuf` or is `nullptr`, in which case the last `Chunk` in `MultiBuf`
229   /// will be returned.
230   ///
231   /// This operation is `O(Chunks().size())`.
232   Chunk* Previous(Chunk* chunk) const;
233 
234  private:
235   Chunk* first_;
236 };
237 
238 /// A byte buffer optimized for zero-copy data transfer.
239 ///
240 /// A `MultiBuf` consists of multiple `Chunk`s of data.
241 ///
242 /// `MultiBuf` inherits privately from `MultiBufChunks`. This allows one class
243 /// to provide either a byte-oriented or a `Chunk`-oriented interface, and keeps
244 /// those interfaces separate.
245 class MultiBuf : private MultiBufChunks {
246  public:
247   class iterator;
248   class const_iterator;
249 
MultiBuf()250   constexpr MultiBuf() : MultiBufChunks(nullptr) {}
251 
FromChunk(OwnedChunk && chunk)252   static MultiBuf FromChunk(OwnedChunk&& chunk) {
253     return MultiBuf(std::move(chunk).Take());
254   }
255 
256   MultiBuf(const MultiBuf&) = delete;
257   MultiBuf& operator=(const MultiBuf&) = delete;
258 
259   constexpr MultiBuf(MultiBuf&& other) noexcept = default;
260 
261   MultiBuf& operator=(MultiBuf&& other) noexcept = default;
262 
263   /// Decrements the reference count on the underlying chunks of data and
264   /// empties this `MultiBuf` so that `size() == 0`.
265   ///
266   /// Does not modify the underlying data, but may cause it to be deallocated.
267   ///
268   /// This method is equivalent to `{ MultiBuf _unused = std::move(multibuf); }`
269   ///
270   /// This method will acquire a mutex and is not IRQ safe.
Release()271   void Release() noexcept { MultiBufChunks::Release(); }
272 
273   /// This destructor will acquire a mutex and is not IRQ safe.
274   ~MultiBuf() = default;
275 
276   /// Returns the number of bytes in this container.
277   ///
278   /// This method's complexity is `O(Chunks().size())`.
size()279   [[nodiscard]] size_t size() const { return MultiBufChunks::size_bytes(); }
280 
281   /// Returns whether the `MultiBuf` contains any bytes (`size() == 0`).
282   ///
283   /// This method's complexity is `O(Chunks().size())`, but will be more
284   /// efficient than `size() == 0` in most cases.
285   [[nodiscard]] bool empty() const;
286 
287   /// Returns if the `MultiBuf` is contiguous. A `MultiBuf` is contiguous if it
288   /// is comprised of either:
289   ///
290   /// - one non-empty chunk,
291   /// - only empty chunks, or
292   /// - no chunks at all.
IsContiguous()293   [[nodiscard]] bool IsContiguous() const {
294     return ContiguousSpan().has_value();
295   }
296 
297   /// If the `MultiBuf` is contiguous, returns it as a span. The span will be
298   /// empty if the `MultiBuf` is empty.
299   ///
300   /// A `MultiBuf` is contiguous if it is comprised of either:
301   ///
302   /// - one non-empty chunk,
303   /// - only empty chunks, or
304   /// - no chunks at all.
ContiguousSpan()305   std::optional<ByteSpan> ContiguousSpan() {
306     auto result = std::as_const(*this).ContiguousSpan();
307     if (result.has_value()) {
308       return span(const_cast<std::byte*>(result->data()), result->size());
309     }
310     return std::nullopt;
311   }
312   std::optional<ConstByteSpan> ContiguousSpan() const;
313 
314   /// Returns an iterator pointing to the first byte of this `MultiBuf`.
begin()315   iterator begin() { return iterator(Chunks().begin().chunk_); }
316   /// Returns a const iterator pointing to the first byte of this `MultiBuf`.
begin()317   const_iterator begin() const {
318     return const_iterator(Chunks().begin().chunk_);
319   }
320   /// Returns a const iterator pointing to the first byte of this `MultiBuf`.
cbegin()321   const_iterator cbegin() const {
322     return const_iterator(Chunks().begin().chunk_);
323   }
324 
325   /// Returns an iterator pointing to the end of this `MultiBuf`.
end()326   iterator end() { return iterator::end(); }
327   /// Returns a const iterator pointing to the end of this `MultiBuf`.
end()328   const_iterator end() const { return const_iterator::end(); }
329   /// Returns a const iterator pointing to the end of this `MultiBuf`.
cend()330   const_iterator cend() const { return const_iterator::end(); }
331 
332   /// Attempts to add `bytes_to_claim` to the front of this buffer by advancing
333   /// its range backwards in memory. Returns `true` if the operation succeeded.
334   ///
335   /// This will only succeed if the first `Chunk` in this buffer points to a
336   /// section of a region that has unreferenced bytes preceding it. See also
337   /// `Chunk::ClaimPrefix`.
338   ///
339   /// This method will acquire a mutex and is not IRQ safe.
340   [[nodiscard]] bool ClaimPrefix(size_t bytes_to_claim);
341 
342   /// Attempts to add `bytes_to_claim` to the front of this buffer by advancing
343   /// its range forwards in memory. Returns `true` if the operation succeeded.
344   ///
345   /// This will only succeed if the last `Chunk` in this buffer points to a
346   /// section of a region that has unreferenced bytes following it. See also
347   /// `Chunk::ClaimSuffix`.
348   ///
349   /// This method will acquire a mutex and is not IRQ safe.
350   [[nodiscard]] bool ClaimSuffix(size_t bytes_to_claim);
351 
352   /// Shrinks this handle to refer to the data beginning at offset
353   /// `bytes_to_discard`.
354   ///
355   /// Does not modify the underlying data. The discarded memory continues to be
356   /// held by the underlying region as long as any `Chunk`s exist within it.
357   /// This allows the memory to be later reclaimed using `ClaimPrefix`.
358   ///
359   /// This method will acquire a mutex and is not IRQ safe.
360   void DiscardPrefix(size_t bytes_to_discard);
361 
362   /// Shrinks this handle to refer to data in the range `begin..<end`.
363   ///
364   /// Does not modify the underlying data. The discarded memory continues to be
365   /// held by the underlying region as long as any `Chunk`s exist within it.
366   /// This allows the memory to be later reclaimed using `ClaimPrefix` or
367   /// `ClaimSuffix`.
368   ///
369   /// This method will acquire a mutex and is not IRQ safe.
370   void Slice(size_t begin, size_t end);
371 
372   /// Shrinks this handle to refer to only the first `len` bytes.
373   ///
374   /// Does not modify the underlying data. The discarded memory continues to be
375   /// held by the underlying region as long as any `Chunk`s exist within it.
376   /// This allows the memory to be later reclaimed using `ClaimSuffix`.
377   ///
378   /// This method will acquire a mutex and is not IRQ safe.
379   void Truncate(size_t len);
380 
381   /// Truncates the `MultiBuf` after the current iterator. All bytes following
382   /// the iterator are removed.
383   ///
384   /// Does not modify the underlying data.
385   ///
386   /// This method will acquire a mutex and is not IRQ safe.
387   void TruncateAfter(iterator pos);
388 
389   /// Attempts to shrink this handle to refer to the data beginning at offset
390   /// `bytes_to_take`, returning the first `bytes_to_take` bytes as a new
391   /// `MultiBuf`.
392   ///
393   /// If the inner call to `AllocateChunkClass` fails, this function will return
394   /// `std::nullopt` and this handle's span will not change.
395   ///
396   /// This method will acquire a mutex and is not IRQ safe.
397   std::optional<MultiBuf> TakePrefix(size_t bytes_to_take);
398 
399   /// Attempts to shrink this handle to refer only the first `len -
400   /// bytes_to_take` bytes, returning the last `bytes_to_take` bytes as a new
401   /// `MultiBuf`.
402   ///
403   /// If the inner call to `AllocateChunkClass` fails, this function will return
404   /// `std::nullopt` and this handle's span will not change.
405   ///
406   /// This method will acquire a mutex and is not IRQ safe.
407   std::optional<MultiBuf> TakeSuffix(size_t bytes_to_take);
408 
409   /// Pushes `front` onto the front of this `MultiBuf`.
410   ///
411   /// This operation does not move any data and is `O(front.Chunks().size())`.
412   void PushPrefix(MultiBuf&& front);
413 
414   /// Pushes `tail` onto the end of this `MultiBuf`.
415   ///
416   /// This operation does not move any data and is `O(Chunks().size())`.
PushSuffix(MultiBuf && tail)417   void PushSuffix(MultiBuf&& tail) {
418     return MultiBufChunks::PushSuffix(std::move(tail.Chunks()));
419   }
420 
421   /// Copies bytes from the multibuf into the provided buffer.
422   ///
423   /// @param[out] dest Destination into which to copy data from the `MultiBuf`.
424   /// @param[in] position Offset in the `MultiBuf` from which to start.
425   ///
426   /// @returns @rst
427   ///
428   /// .. pw-status-codes::
429   ///
430   ///    OK: All bytes were copied into the destination. The
431   ///    :cpp:class:`pw::StatusWithSize` includes the number of bytes copied,
432   ///    which is the size of the :cpp:class:`MultiBuf`.
433   ///
434   ///    RESOURCE_EXHAUSTED: Some bytes were copied, but the
435   ///    :cpp:class:`MultiBuf` was larger than the destination buffer. The
436   ///    :cpp:class:`pw::StatusWithSize` includes the number of bytes copied.
437   ///
438   /// @endrst
439   StatusWithSize CopyTo(ByteSpan dest, size_t position = 0) const;
440 
441   /// Copies bytes from the provided buffer into the multibuf.
442   ///
443   /// @param[in] source Data to copy into the `MultiBuf`.
444   /// @param[in] position Offset in the `MultiBuf` from which to start.
445   ///
446   /// @returns @rst
447   ///
448   /// .. pw-status-codes::
449   ///
450   ///    OK: All bytes were copied. The :cpp:class:`pw::StatusWithSize` includes
451   ///    the number of bytes copied, which is the size of the `MultiBuf`.
452   ///
453   ///    RESOURCE_EXHAUSTED: Some bytes were copied, but the source was larger
454   ///    than the destination. The :cpp:class:`pw::StatusWithSize` includes the
455   ///    number of bytes copied.
456   ///
457   /// @endrst
458   StatusWithSize CopyFrom(ConstByteSpan source, size_t position = 0) {
459     return CopyFromAndOptionallyTruncate(source, position, /*truncate=*/false);
460   }
461 
462   /// Copies bytes from the provided buffer into this `MultiBuf` and truncates
463   /// it to the end of the copied data. This is a more efficient version of:
464   /// @code{.cpp}
465   ///
466   ///   if (multibuf.CopyFrom(destination).ok()) {
467   ///     multibuf.Truncate(destination.size());
468   ///   }
469   ///
470   /// @endcode
471   ///
472   /// @param[in] source Data to copy into the `MultiBuf`.
473   /// @param[in] position Offset in the `MultiBuf` from which to start.
474   ///
475   /// @returns @rst
476   ///
477   /// .. pw-status-codes::
478   ///
479   ///    OK: All bytes were copied and the :cpp:class:`MultiBuf` was truncated.
480   ///    The :cpp:class:`pw::StatusWithSize` includes the new
481   ///    :cpp:func:`MultiBuf::size`.
482   ///
483   ///    RESOURCE_EXHAUSTED: Some bytes were copied, but the source buffer was
484   ///    larger than the :cpp:class:`MultiBuf`. The returned
485   ///    :cpp:class:`pw::StatusWithSize` includes the number of bytes copied,
486   ///    which is the size of the :cpp:class:`MultiBuf`.
487   ///
488   /// @endrst
489   StatusWithSize CopyFromAndTruncate(ConstByteSpan source,
490                                      size_t position = 0) {
491     return CopyFromAndOptionallyTruncate(source, position, /*truncate=*/true);
492   }
493 
494   ///////////////////////////////////////////////////////////////////
495   //--------------------- Chunk manipulation ----------------------//
496   ///////////////////////////////////////////////////////////////////
497 
498   /// @copydoc MultiBufChunks::push_front
PushFrontChunk(OwnedChunk && chunk)499   void PushFrontChunk(OwnedChunk&& chunk) {
500     MultiBufChunks::push_front(std::move(chunk));
501   }
502 
503   /// @copydoc MultiBufChunks::push_back
PushBackChunk(OwnedChunk && chunk)504   void PushBackChunk(OwnedChunk&& chunk) {
505     MultiBufChunks::push_back(std::move(chunk));
506   }
507 
508   /// @copydoc MultiBufChunks::take_front
TakeFrontChunk()509   OwnedChunk TakeFrontChunk() { return MultiBufChunks::take_front(); }
510 
511   /// @copydoc MultiBufChunks::insert
InsertChunk(MultiBufChunks::iterator position,OwnedChunk && chunk)512   MultiBufChunks::iterator InsertChunk(MultiBufChunks::iterator position,
513                                        OwnedChunk&& chunk) {
514     return MultiBufChunks::insert(position, std::move(chunk));
515   }
516 
517   /// @copydoc MultiBufChunks::take
TakeChunk(MultiBufChunks::iterator position)518   std::tuple<MultiBufChunks::iterator, OwnedChunk> TakeChunk(
519       MultiBufChunks::iterator position) {
520     return MultiBufChunks::take(position);
521   }
522 
523   /// Returns a `Chunk`-oriented view of this `MultiBuf`.
Chunks()524   constexpr MultiBufChunks& Chunks() { return *this; }
525 
526   /// Returns a `const Chunk`-oriented view of this `MultiBuf`.
Chunks()527   constexpr const MultiBufChunks& Chunks() const { return *this; }
528 
529   /// Returns a `const Chunk`-oriented view of this `MultiBuf`.
ConstChunks()530   constexpr const MultiBufChunks& ConstChunks() const { return *this; }
531 
532   ///////////////////////////////////////////////////////////////////
533   //--------------------- Iterator details ------------------------//
534   ///////////////////////////////////////////////////////////////////
535 
536   using element_type = std::byte;
537   using value_type = std::byte;
538   using pointer = std::byte*;
539   using const_pointer = const std::byte*;
540   using reference = std::byte&;
541   using const_reference = const std::byte&;
542   using difference_type = std::ptrdiff_t;
543   using size_type = std::size_t;
544 
545   /// A const `std::forward_iterator` over the bytes of a `MultiBuf`.
546   class const_iterator {
547    public:
548     using value_type = std::byte;
549     using difference_type = std::ptrdiff_t;
550     using reference = const std::byte&;
551     using pointer = const std::byte*;
552     using iterator_category = std::forward_iterator_tag;
553 
const_iterator()554     constexpr const_iterator() : chunk_(nullptr), byte_index_(0) {}
555 
556     reference operator*() const { return (*chunk_)[byte_index_]; }
557     pointer operator->() const { return &(*chunk_)[byte_index_]; }
558 
559     const_iterator& operator++();
560     const_iterator operator++(int) {
561       const_iterator tmp = *this;
562       ++(*this);
563       return tmp;
564     }
565     const_iterator operator+(size_t rhs) const {
566       const_iterator tmp = *this;
567       tmp += rhs;
568       return tmp;
569     }
570     const_iterator& operator+=(size_t advance);
571 
572     constexpr bool operator==(const const_iterator& other) const {
573       return chunk_ == other.chunk_ && byte_index_ == other.byte_index_;
574     }
575 
576     constexpr bool operator!=(const const_iterator& other) const {
577       return !(*this == other);
578     }
579 
580     /// Returns the current `Chunk` pointed to by this `iterator`.
chunk()581     constexpr const Chunk* chunk() const { return chunk_; }
582 
583     /// Returns the index of the byte pointed to by this `iterator` within the
584     /// current `Chunk`.
byte_index()585     constexpr size_t byte_index() const { return byte_index_; }
586 
587    private:
588     friend class MultiBuf;
589 
590     explicit constexpr const_iterator(const Chunk* chunk, size_t byte_index = 0)
chunk_(chunk)591         : chunk_(chunk), byte_index_(byte_index) {
592       AdvanceToData();
593     }
594 
end()595     static const_iterator end() { return const_iterator(nullptr); }
596 
AdvanceToData()597     constexpr void AdvanceToData() {
598       while (chunk_ != nullptr && chunk_->empty()) {
599         chunk_ = chunk_->next_in_buf_;
600       }
601     }
602 
603     const Chunk* chunk_;
604     size_t byte_index_;
605   };
606 
607   /// An `std::forward_iterator` over the bytes of a `MultiBuf`.
608   class iterator {
609    public:
610     using value_type = std::byte;
611     using difference_type = std::ptrdiff_t;
612     using reference = std::byte&;
613     using pointer = std::byte*;
614     using iterator_category = std::forward_iterator_tag;
615 
616     constexpr iterator() = default;
617 
618     reference operator*() const { return const_cast<std::byte&>(*const_iter_); }
619     pointer operator->() const { return const_cast<std::byte*>(&*const_iter_); }
620 
621     iterator& operator++() {
622       const_iter_++;
623       return *this;
624     }
625     iterator operator++(int) {
626       iterator tmp = *this;
627       ++(*this);
628       return tmp;
629     }
630     iterator operator+(size_t rhs) const {
631       iterator tmp = *this;
632       tmp += rhs;
633       return tmp;
634     }
635     iterator& operator+=(size_t rhs) {
636       const_iter_ += rhs;
637       return *this;
638     }
639     constexpr bool operator==(const iterator& other) const {
640       return const_iter_ == other.const_iter_;
641     }
642     constexpr bool operator!=(const iterator& other) const {
643       return const_iter_ != other.const_iter_;
644     }
645 
646     /// Returns the current `Chunk` pointed to by this `iterator`.
chunk()647     constexpr Chunk* chunk() const {
648       return const_cast<Chunk*>(const_iter_.chunk());
649     }
650 
651     /// Returns the index of the byte pointed to by this `iterator` within the
652     /// current `Chunk`.
byte_index()653     constexpr size_t byte_index() const { return const_iter_.byte_index(); }
654 
655    private:
656     friend class MultiBuf;
657 
658     explicit constexpr iterator(Chunk* chunk, size_t byte_index = 0)
const_iter_(chunk,byte_index)659         : const_iter_(chunk, byte_index) {}
660 
end()661     static iterator end() { return iterator(nullptr); }
662 
663     const_iterator const_iter_;
664   };
665 
666  private:
MultiBuf(Chunk * first_chunk)667   explicit constexpr MultiBuf(Chunk* first_chunk)
668       : MultiBufChunks(first_chunk) {}
669 
670   StatusWithSize CopyFromAndOptionallyTruncate(ConstByteSpan source,
671                                                size_t position,
672                                                bool truncate);
673 };
674 
675 }  // namespace pw::multibuf
676