• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <tuple>
17 
18 #include "pw_multibuf/chunk.h"
19 #include "pw_preprocessor/compiler.h"
20 
21 namespace pw::multibuf {
22 
23 /// A buffer optimized for zero-copy data transfer.
24 ///
25 /// A ``MultiBuf`` consists of multiple ``Chunk`` s of data.
26 class MultiBuf {
27  public:
28   class iterator;
29   class const_iterator;
30   class ChunkIterator;
31   class ConstChunkIterator;
32   class ChunkIterable;
33 
MultiBuf()34   constexpr MultiBuf() : first_(nullptr) {}
FromChunk(OwnedChunk && chunk)35   static MultiBuf FromChunk(OwnedChunk&& chunk) {
36     MultiBuf buf;
37     buf.first_ = std::move(chunk).Take();
38     return buf;
39   }
40   MultiBuf(const MultiBuf&) = delete;
41   MultiBuf& operator=(const MultiBuf&) = delete;
42 
43   // Disable maybe-uninitialized: this check fails erroniously on Windows GCC.
44   PW_MODIFY_DIAGNOSTICS_PUSH();
45   PW_MODIFY_DIAGNOSTIC_GCC(ignored, "-Wmaybe-uninitialized");
MultiBuf(MultiBuf && other)46   constexpr MultiBuf(MultiBuf&& other) noexcept : first_(other.first_) {
47     other.first_ = nullptr;
48   }
49   PW_MODIFY_DIAGNOSTICS_POP();
50 
51   MultiBuf& operator=(MultiBuf&& other) noexcept {
52     Release();
53     first_ = other.first_;
54     other.first_ = nullptr;
55     return *this;
56   }
57 
58   /// Decrements the reference count on the underlying chunks of data and
59   /// empties this ``MultiBuf`` so that ``size() == 0``.
60   ///
61   /// Does not modify the underlying data, but may cause it to be deallocated
62   ///
63   /// This method is equivalent to ``{ MultiBuf _unused = std::move(multibuf);
64   /// }``
65   ///
66   /// This method will acquire a mutex and is not IRQ safe.
67   void Release() noexcept;
68 
69   /// This destructor will acquire a mutex and is not IRQ safe.
~MultiBuf()70   ~MultiBuf() { Release(); }
71 
72   /// Returns the number of bytes in this container.
73   ///
74   /// This method's complexity is ``O(Chunks().size())``.
75   [[nodiscard]] size_t size() const;
76 
77   /// Returns whether the container is empty (`size() == 0`).
78   ///
79   /// This method's complexity is ``O(Chunks().size())``, but will be more
80   /// efficient than `size() == 0` in most cases.
81   [[nodiscard]] bool empty() const;
82 
83   /// Returns an iterator pointing to the first byte of this ``MultiBuf`.
begin()84   iterator begin() { return iterator(first_); }
85   /// Returns a const iterator pointing to the first byte of this ``MultiBuf`.
begin()86   const_iterator begin() const { return const_iterator(first_); }
87   /// Returns a const iterator pointing to the first byte of this ``MultiBuf`.
cbegin()88   const_iterator cbegin() const { return const_iterator(first_); }
89 
90   /// Returns an iterator pointing to the end of this ``MultiBuf``.
end()91   iterator end() { return iterator::end(); }
92   /// Returns a const iterator pointing to the end of this ``MultiBuf``.
end()93   const_iterator end() const { return const_iterator::end(); }
94   /// Returns a const iterator pointing to the end of this ``MultiBuf``.
cend()95   const_iterator cend() const { return const_iterator::end(); }
96 
97   /// Attempts to add ``bytes_to_claim`` to the front of this buffer by
98   /// advancing its range backwards in memory. Returns ``true`` if the operation
99   /// succeeded.
100   ///
101   /// This will only succeed if the first ``Chunk`` in this buffer points to a
102   /// section of a region that has unreferenced bytes preceeding it. See also
103   /// ``Chunk::ClaimPrefix``.
104   ///
105   /// This method will acquire a mutex and is not IRQ safe.
106   [[nodiscard]] bool ClaimPrefix(size_t bytes_to_claim);
107 
108   /// Attempts to add ``bytes_to_claim`` to the front of this buffer by
109   /// advancing its range forwards in memory. Returns ``true`` if the operation
110   /// succeeded.
111   ///
112   /// This will only succeed if the last ``Chunk`` in this buffer points to a
113   /// section of a region that has unreferenced bytes following it. See also
114   /// ``Chunk::ClaimSuffix``.
115   ///
116   /// This method will acquire a mutex and is not IRQ safe.
117   [[nodiscard]] bool ClaimSuffix(size_t bytes_to_claim);
118 
119   /// Shrinks this handle to refer to the data beginning at offset
120   /// ``bytes_to_discard``.
121   ///
122   /// Does not modify the underlying data.
123   ///
124   /// This method will acquire a mutex and is not IRQ safe.
125   void DiscardPrefix(size_t bytes_to_discard);
126 
127   /// Shrinks this handle to refer to data in the range ``begin..<end``.
128   ///
129   /// Does not modify the underlying data.
130   ///
131   /// This method will acquire a mutex and is not IRQ safe.
132   void Slice(size_t begin, size_t end);
133 
134   /// Shrinks this handle to refer to only the first ``len`` bytes.
135   ///
136   /// Does not modify the underlying data.
137   ///
138   /// This method will acquire a mutex and is not IRQ safe.
139   void Truncate(size_t len);
140 
141   /// Attempts to shrink this handle to refer to the data beginning at
142   /// offset ``bytes_to_take``, returning the first ``bytes_to_take`` bytes as
143   /// a new ``MultiBuf``.
144   ///
145   /// If the inner call to ``AllocateChunkClass`` fails, this function
146   /// will return ``std::nullopt` and this handle's span will not change.
147   ///
148   /// This method will acquire a mutex and is not IRQ safe.
149   std::optional<MultiBuf> TakePrefix(size_t bytes_to_take);
150 
151   /// Attempts to shrink this handle to refer only the first
152   /// ``len - bytes_to_take`` bytes, returning the last ``bytes_to_take``
153   /// bytes as a new ``MultiBuf``.
154   ///
155   /// If the inner call to ``AllocateChunkClass`` fails, this function
156   /// will return ``std::nullopt`` and this handle's span will not change.
157   ///
158   /// This method will acquire a mutex and is not IRQ safe.
159   std::optional<MultiBuf> TakeSuffix(size_t bytes_to_take);
160 
161   /// Pushes ``front`` onto the front of this ``MultiBuf``.
162   ///
163   /// This operation does not move any data and is ``O(front.Chunks().size())``.
164   void PushPrefix(MultiBuf&& front);
165 
166   /// Pushes ``tail`` onto the end of this ``MultiBuf``.
167   ///
168   /// This operation does not move any data and is ``O(Chunks().size())``.
169   void PushSuffix(MultiBuf&& tail);
170 
171   ///////////////////////////////////////////////////////////////////
172   //--------------------- Chunk manipulation ----------------------//
173   ///////////////////////////////////////////////////////////////////
174 
175   /// Pushes ``Chunk`` onto the front of the ``MultiBuf``.
176   ///
177   /// This operation does not move any data and is ``O(1)``.
178   void PushFrontChunk(OwnedChunk&& chunk);
179 
180   /// Pushes ``Chunk`` onto the end of the ``MultiBuf``.
181   ///
182   /// This operation does not move any data and is ``O(Chunks().size())``.
183   void PushBackChunk(OwnedChunk&& chunk);
184 
185   /// Removes the first ``Chunk``.
186   ///
187   /// This operation does not move any data and is ``O(1)``.
188   OwnedChunk TakeFrontChunk();
189 
190   /// Inserts ``chunk`` into the specified position in the ``MultiBuf``.
191   ///
192   /// This operation does not move any data and is ``O(Chunks().size())``.
193   ///
194   /// Returns an iterator pointing to the newly-inserted ``Chunk``.
195   //
196   // Implementation note: ``Chunks().size()`` should be remain relatively
197   // small, but this could be made ``O(1)`` in the future by adding a ``prev``
198   // pointer to the ``ChunkIterator``.
199   ChunkIterator InsertChunk(ChunkIterator position, OwnedChunk&& chunk);
200 
201   /// Removes a ``Chunk`` from the specified position.
202   ///
203   /// This operation does not move any data and is ``O(Chunks().size())``.
204   ///
205   /// Returns an iterator pointing to the ``Chunk`` after the removed
206   /// ``Chunk``, or ``Chunks().end()`` if this was the last ``Chunk`` in the
207   /// ``MultiBuf``.
208   //
209   // Implementation note: ``Chunks().size()`` should be remain relatively
210   // small, but this could be made ``O(1)`` in the future by adding a ``prev``
211   // pointer to the ``ChunkIterator``.
212   std::tuple<ChunkIterator, OwnedChunk> TakeChunk(ChunkIterator position);
213 
214   /// Returns an iterable container which yields the ``Chunk``s in this
215   /// ``MultiBuf``.
Chunks()216   constexpr ChunkIterable Chunks() { return ChunkIterable(first_); }
217 
218   /// Returns an iterable container which yields the ``const Chunk``s in
219   /// this ``MultiBuf``.
Chunks()220   constexpr const ChunkIterable Chunks() const { return ChunkIterable(first_); }
221 
222   /// Returns an iterator pointing to the first ``Chunk`` in this ``MultiBuf``.
ChunkBegin()223   constexpr ChunkIterator ChunkBegin() { return ChunkIterator(first_); }
224   /// Returns an iterator pointing to the end of the ``Chunk``s in this
225   /// ``MultiBuf``.
ChunkEnd()226   constexpr ChunkIterator ChunkEnd() { return ChunkIterator::end(); }
227   /// Returns a const iterator pointing to the first ``Chunk`` in this
228   /// ``MultiBuf``.
ConstChunkBegin()229   constexpr ConstChunkIterator ConstChunkBegin() {
230     return ConstChunkIterator(first_);
231   }
232   /// Returns a const iterator pointing to the end of the ``Chunk``s in this
233   /// ``MultiBuf``.
ConstChunkEnd()234   constexpr ConstChunkIterator ConstChunkEnd() {
235     return ConstChunkIterator::end();
236   }
237 
238   ///////////////////////////////////////////////////////////////////
239   //--------------------- Iterator details ------------------------//
240   ///////////////////////////////////////////////////////////////////
241 
242   using element_type = std::byte;
243   using value_type = std::byte;
244   using pointer = std::byte*;
245   using const_pointer = const std::byte*;
246   using reference = std::byte&;
247   using const_reference = const std::byte&;
248   using difference_type = std::ptrdiff_t;
249   using size_type = std::size_t;
250 
251   /// A const ``std::forward_iterator`` over the bytes of a ``MultiBuf``.
252   class const_iterator {
253    public:
254     using value_type = std::byte;
255     using difference_type = std::ptrdiff_t;
256     using reference = const std::byte&;
257     using pointer = const std::byte*;
258     using iterator_category = std::forward_iterator_tag;
259 
const_iterator()260     constexpr const_iterator() : chunk_(nullptr), byte_index_(0) {}
261 
262     reference operator*() const { return (*chunk_)[byte_index_]; }
263     pointer operator->() const { return &(*chunk_)[byte_index_]; }
264 
265     const_iterator& operator++();
266     const_iterator operator++(int) {
267       const_iterator tmp = *this;
268       ++(*this);
269       return tmp;
270     }
271     const_iterator operator+(size_t rhs) const {
272       const_iterator tmp = *this;
273       tmp += rhs;
274       return tmp;
275     }
276     const_iterator& operator+=(size_t advance);
277 
278     constexpr bool operator==(const const_iterator& other) const {
279       return chunk_ == other.chunk_ && byte_index_ == other.byte_index_;
280     }
281 
282     constexpr bool operator!=(const const_iterator& other) const {
283       return !(*this == other);
284     }
285 
286     /// Returns the current ``Chunk`` pointed to by this `iterator`.
chunk()287     constexpr const Chunk* chunk() const { return chunk_; }
288 
289     /// Returns the index of the byte pointed to by this `iterator` within the
290     /// current ``Chunk``.
byte_index()291     constexpr size_t byte_index() const { return byte_index_; }
292 
293    private:
294     friend class MultiBuf;
295 
const_iterator(const Chunk * chunk)296     explicit constexpr const_iterator(const Chunk* chunk)
297         : chunk_(chunk), byte_index_(0) {
298       AdvanceToData();
299     }
300 
end()301     static const_iterator end() { return const_iterator(nullptr); }
302 
AdvanceToData()303     constexpr void AdvanceToData() {
304       while (chunk_ != nullptr && chunk_->empty()) {
305         chunk_ = chunk_->next_in_buf_;
306       }
307     }
308 
309     const Chunk* chunk_;
310     size_t byte_index_;
311   };
312 
313   /// An ``std::forward_iterator`` over the bytes of a ``MultiBuf``.
314   class iterator {
315    public:
316     using value_type = std::byte;
317     using difference_type = std::ptrdiff_t;
318     using reference = std::byte&;
319     using pointer = std::byte*;
320     using iterator_category = std::forward_iterator_tag;
321 
322     constexpr iterator() = default;
323 
324     reference operator*() const { return const_cast<std::byte&>(*const_iter_); }
325     pointer operator->() const { return const_cast<std::byte*>(&*const_iter_); }
326 
327     iterator& operator++() {
328       const_iter_++;
329       return *this;
330     }
331     iterator operator++(int) {
332       iterator tmp = *this;
333       ++(*this);
334       return tmp;
335     }
336     iterator operator+(size_t rhs) const {
337       iterator tmp = *this;
338       tmp += rhs;
339       return tmp;
340     }
341     iterator& operator+=(size_t rhs) {
342       const_iter_ += rhs;
343       return *this;
344     }
345     constexpr bool operator==(const iterator& other) const {
346       return const_iter_ == other.const_iter_;
347     }
348     constexpr bool operator!=(const iterator& other) const {
349       return const_iter_ != other.const_iter_;
350     }
351 
352     /// Returns the current ``Chunk`` pointed to by this `iterator`.
chunk()353     constexpr Chunk* chunk() const {
354       return const_cast<Chunk*>(const_iter_.chunk());
355     }
356 
357     /// Returns the index of the byte pointed to by this `iterator` within the
358     /// current ``Chunk``.
byte_index()359     constexpr size_t byte_index() const { return const_iter_.byte_index(); }
360 
361    private:
362     friend class MultiBuf;
363 
iterator(Chunk * chunk)364     explicit constexpr iterator(Chunk* chunk) : const_iter_(chunk) {}
365 
end()366     static iterator end() { return iterator(nullptr); }
367 
368     const_iterator const_iter_;
369   };
370 
371   /// An iterable containing the ``Chunk`` s of a ``MultiBuf``.
372   class ChunkIterable {
373    public:
374     using element_type = Chunk;
375     using value_type = Chunk;
376     using pointer = Chunk*;
377     using reference = Chunk&;
378     using const_pointer = const Chunk*;
379     using difference_type = std::ptrdiff_t;
380     using const_reference = const Chunk&;
381     using size_type = std::size_t;
382 
383     /// Returns a reference to the first chunk.
384     ///
385     /// The behavior of this method is undefined when ``size() == 0``.
front()386     Chunk& front() { return *first_; }
front()387     const Chunk& front() const { return *first_; }
388 
389     /// Returns a reference to the final chunk.
390     ///
391     /// The behavior of this method is undefined when ``size() == 0``.
392     ///
393     /// NOTE: this method is ``O(size())``.
394     Chunk& back();
395     const Chunk& back() const;
396 
begin()397     constexpr ChunkIterator begin() { return ChunkIterator(first_); }
begin()398     constexpr ConstChunkIterator begin() const { return cbegin(); }
cbegin()399     constexpr ConstChunkIterator cbegin() const {
400       return ConstChunkIterator(first_);
401     }
end()402     constexpr ChunkIterator end() { return ChunkIterator::end(); }
end()403     constexpr ConstChunkIterator end() const { return cend(); }
cend()404     constexpr ConstChunkIterator cend() const {
405       return ConstChunkIterator::end();
406     }
407 
408     /// Returns the number of ``Chunk``s in this iterable.
409     size_t size() const;
410 
411    private:
412     Chunk* first_ = nullptr;
ChunkIterable(Chunk * chunk)413     constexpr ChunkIterable(Chunk* chunk) : first_(chunk) {}
414     friend class MultiBuf;
415   };
416 
417   /// A ``std::forward_iterator`` over the ``Chunk``s of a ``MultiBuf``.
418   class ChunkIterator {
419    public:
420     using value_type = Chunk;
421     using difference_type = std::ptrdiff_t;
422     using reference = Chunk&;
423     using pointer = Chunk*;
424     using iterator_category = std::forward_iterator_tag;
425 
426     constexpr ChunkIterator() = default;
427 
428     constexpr reference operator*() const { return *chunk_; }
429     constexpr pointer operator->() const { return chunk_; }
430 
431     constexpr ChunkIterator& operator++() {
432       chunk_ = chunk_->next_in_buf_;
433       return *this;
434     }
435 
436     constexpr ChunkIterator operator++(int) {
437       ChunkIterator tmp = *this;
438       ++(*this);
439       return tmp;
440     }
441 
442     constexpr bool operator==(const ChunkIterator& other) const {
443       return chunk_ == other.chunk_;
444     }
445 
446     constexpr bool operator!=(const ChunkIterator& other) const {
447       return chunk_ != other.chunk_;
448     }
449 
chunk()450     constexpr Chunk* chunk() const { return chunk_; }
451 
452    private:
ChunkIterator(Chunk * chunk)453     constexpr ChunkIterator(Chunk* chunk) : chunk_(chunk) {}
end()454     static constexpr ChunkIterator end() { return ChunkIterator(nullptr); }
455     Chunk* chunk_ = nullptr;
456     friend class MultiBuf;
457     friend class ChunkIterable;
458   };
459 
460   /// A const ``std::forward_iterator`` over the ``Chunk``s of a ``MultiBuf``.
461   class ConstChunkIterator {
462    public:
463     using value_type = const Chunk;
464     using difference_type = std::ptrdiff_t;
465     using reference = const Chunk&;
466     using pointer = const Chunk*;
467     using iterator_category = std::forward_iterator_tag;
468 
469     constexpr ConstChunkIterator() = default;
470 
471     constexpr reference operator*() const { return *chunk_; }
472     constexpr pointer operator->() const { return chunk_; }
473 
474     constexpr ConstChunkIterator& operator++() {
475       chunk_ = chunk_->next_in_buf_;
476       return *this;
477     }
478 
479     constexpr ConstChunkIterator operator++(int) {
480       ConstChunkIterator tmp = *this;
481       ++(*this);
482       return tmp;
483     }
484 
485     constexpr bool operator==(const ConstChunkIterator& other) const {
486       return chunk_ == other.chunk_;
487     }
488 
489     constexpr bool operator!=(const ConstChunkIterator& other) const {
490       return chunk_ != other.chunk_;
491     }
492 
chunk()493     constexpr const Chunk* chunk() const { return chunk_; }
494 
495    private:
ConstChunkIterator(const Chunk * chunk)496     constexpr ConstChunkIterator(const Chunk* chunk) : chunk_(chunk) {}
end()497     static constexpr ConstChunkIterator end() {
498       return ConstChunkIterator(nullptr);
499     }
500     const Chunk* chunk_ = nullptr;
501     friend class MultiBuf;
502     friend class ChunkIterable;
503   };
504 
505  private:
506   /// Returns the ``Chunk`` preceding ``chunk`` in this ``MultiBuf``.
507   ///
508   /// Requires that this ``MultiBuf`` is not empty, and that ``chunk``
509   /// is either in ``MultiBuf`` or is ``nullptr``, in which case the last
510   /// ``Chunk`` in ``MultiBuf`` will be returned.
511   ///
512   /// This operation is ``O(Chunks().size())``.
513   Chunk* Previous(Chunk* chunk) const;
514 
515   Chunk* first_;
516 };
517 
518 }  // namespace pw::multibuf
519