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