100.00% Lines (88/88)
100.00% Functions (19/19)
| TLA | Baseline | Branch | ||||||
|---|---|---|---|---|---|---|---|---|
| Line | Hits | Code | Line | Hits | Code | |||
| 1 | + | // | ||||||
| 2 | + | // Copyright (c) 2026 Steve Gerbino | ||||||
| 3 | + | // | ||||||
| 4 | + | // Distributed under the Boost Software License, Version 1.0. (See accompanying | ||||||
| 5 | + | // file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | ||||||
| 6 | + | // | ||||||
| 7 | + | // Official repository: https://github.com/cppalliance/capy | ||||||
| 8 | + | // | ||||||
| 9 | + | |||||||
| 10 | + | #ifndef BOOST_CAPY_BUFFERS_BUFFERS_ITERATOR_HPP | ||||||
| 11 | + | #define BOOST_CAPY_BUFFERS_BUFFERS_ITERATOR_HPP | ||||||
| 12 | + | |||||||
| 13 | + | #include <boost/capy/detail/config.hpp> | ||||||
| 14 | + | #include <boost/capy/buffers.hpp> | ||||||
| 15 | + | |||||||
| 16 | + | #include <compare> | ||||||
| 17 | + | #include <cstddef> | ||||||
| 18 | + | #include <iterator> | ||||||
| 19 | + | #include <ranges> | ||||||
| 20 | + | #include <type_traits> | ||||||
| 21 | + | #include <utility> | ||||||
| 22 | + | |||||||
| 23 | + | namespace boost { | ||||||
| 24 | + | namespace capy { | ||||||
| 25 | + | |||||||
| 26 | + | namespace detail { | ||||||
| 27 | + | |||||||
| 28 | + | // Walking back from the end needs an end iterator, not a sentinel | ||||||
| 29 | + | template<class T> | ||||||
| 30 | + | concept common_buffer_sequence = | ||||||
| 31 | + | ConstBufferSequence<T> && | ||||||
| 32 | + | (ConstBuffer<T> || std::ranges::common_range<T const>); | ||||||
| 33 | + | |||||||
| 34 | + | } // detail | ||||||
| 35 | + | |||||||
| 36 | + | /** A random access iterator over the bytes in a buffer sequence. | ||||||
| 37 | + | |||||||
| 38 | + | A buffer sequence stores its bytes in several separate regions. | ||||||
| 39 | + | This iterator presents them as one run of bytes, so standard | ||||||
| 40 | + | algorithms such as `std::search` can scan across the boundaries | ||||||
| 41 | + | between buffers without first copying the data into contiguous | ||||||
| 42 | + | storage. Empty buffers in the sequence are skipped. | ||||||
| 43 | + | |||||||
| 44 | + | The iterator dereferences to `ByteType&` for a | ||||||
| 45 | + | @ref MutableBufferSequence and to `ByteType const&` otherwise. | ||||||
| 46 | + | |||||||
| 47 | + | Moving by `n` bytes walks the underlying buffers, so `operator+=`, | ||||||
| 48 | + | `operator-=`, `operator+`, `operator-` with an integer, and | ||||||
| 49 | + | `operator[]` are linear in the number of buffers crossed. Comparison | ||||||
| 50 | + | and the difference of two iterators are constant time. | ||||||
| 51 | + | |||||||
| 52 | + | @par Preconditions | ||||||
| 53 | + | The buffer sequence and the memory it refers to outlive every | ||||||
| 54 | + | iterator obtained from it. Iterators from different sequences are | ||||||
| 55 | + | not comparable. | ||||||
| 56 | + | |||||||
| 57 | + | @par Example | ||||||
| 58 | + | @code | ||||||
| 59 | + | std::array<capy::const_buffer, 2> bufs{ | ||||||
| 60 | + | capy::make_buffer(std::string_view("GET / HTTP/1.1\r")), | ||||||
| 61 | + | capy::make_buffer(std::string_view("\n\r\nbody")) }; | ||||||
| 62 | + | |||||||
| 63 | + | // Find the end of the header, which spans both buffers | ||||||
| 64 | + | char const crlfcrlf[] = "\r\n\r\n"; | ||||||
| 65 | + | auto it = std::search( | ||||||
| 66 | + | capy::buffers_begin(bufs), capy::buffers_end(bufs), | ||||||
| 67 | + | crlfcrlf, crlfcrlf + 4); | ||||||
| 68 | + | std::size_t header_size = it - capy::buffers_begin(bufs) + 4; | ||||||
| 69 | + | @endcode | ||||||
| 70 | + | |||||||
| 71 | + | @tparam BufferSequence The buffer sequence type. It must be a single | ||||||
| 72 | + | buffer, or a range of buffers whose begin and end have the same type. | ||||||
| 73 | + | |||||||
| 74 | + | @tparam ByteType The byte type presented by the iterator. Its size | ||||||
| 75 | + | must be 1. | ||||||
| 76 | + | |||||||
| 77 | + | @see @ref buffers_begin, @ref buffers_end, | ||||||
| 78 | + | @ref buffer_sequence_begin | ||||||
| 79 | + | */ | ||||||
| 80 | + | template<class BufferSequence, class ByteType = char> | ||||||
| 81 | + | requires detail::common_buffer_sequence<BufferSequence> | ||||||
| 82 | + | class buffers_iterator | ||||||
| 83 | + | { | ||||||
| 84 | + | static_assert(sizeof(ByteType) == 1, "ByteType must be a byte"); | ||||||
| 85 | + | |||||||
| 86 | + | using sequence_iterator = decltype( | ||||||
| 87 | + | buffer_sequence_begin(std::declval<BufferSequence const&>())); | ||||||
| 88 | + | using buffer_type = capy::buffer_type<BufferSequence>; | ||||||
| 89 | + | using byte_type = std::conditional_t< | ||||||
| 90 | + | MutableBufferSequence<BufferSequence>, | ||||||
| 91 | + | ByteType, | ||||||
| 92 | + | ByteType const>; | ||||||
| 93 | + | |||||||
| 94 | + | buffer_type current_buffer_{}; | ||||||
| 95 | + | std::size_t current_buffer_position_ = 0; | ||||||
| 96 | + | sequence_iterator current_{}; | ||||||
| 97 | + | sequence_iterator end_{}; | ||||||
| 98 | + | std::size_t position_ = 0; | ||||||
| 99 | + | |||||||
| 100 | + | // Move to the next non-empty buffer, or to the end of the sequence | ||||||
| 101 | + | void | ||||||
| HITGNC | 102 | + | 202 | skip_forward() noexcept | ||||
| 103 | + | { | ||||||
| HITGNC | 104 | + | 363 | while(current_ != end_) | ||||
| 105 | + | { | ||||||
| HITGNC | 106 | + | 334 | current_buffer_ = buffer_type(*current_); | ||||
| HITGNC | 107 | + | 334 | if(current_buffer_.size() > 0) | ||||
| HITGNC | 108 | + | 173 | return; | ||||
| HITGNC | 109 | + | 161 | ++current_; | ||||
| 110 | + | } | ||||||
| HITGNC | 111 | + | 29 | current_buffer_ = {}; | ||||
| 112 | + | } | ||||||
| 113 | + | |||||||
| 114 | + | // Move to the previous non-empty buffer, positioned past its last byte | ||||||
| 115 | + | void | ||||||
| HITGNC | 116 | + | 155 | skip_backward() noexcept | ||||
| 117 | + | { | ||||||
| 118 | + | do | ||||||
| 119 | + | { | ||||||
| HITGNC | 120 | + | 277 | --current_; | ||||
| HITGNC | 121 | + | 277 | current_buffer_ = buffer_type(*current_); | ||||
| 122 | + | } | ||||||
| HITGNC | 123 | + | 277 | while(current_buffer_.size() == 0); | ||||
| HITGNC | 124 | + | 155 | current_buffer_position_ = current_buffer_.size(); | ||||
| HITGNC | 125 | + | 155 | } | ||||
| 126 | + | |||||||
| 127 | + | void | ||||||
| HITGNC | 128 | + | 318 | advance(std::ptrdiff_t n) noexcept | ||||
| 129 | + | { | ||||||
| HITGNC | 130 | + | 318 | if(n >= 0) | ||||
| 131 | + | { | ||||||
| HITGNC | 132 | + | 177 | auto left = static_cast<std::size_t>(n); | ||||
| HITGNC | 133 | + | 349 | while(left > 0) | ||||
| 134 | + | { | ||||||
| HITGNC | 135 | + | 269 | std::size_t const avail = | ||||
| HITGNC | 136 | + | 269 | current_buffer_.size() - current_buffer_position_; | ||||
| HITGNC | 137 | + | 269 | if(left < avail) | ||||
| 138 | + | { | ||||||
| HITGNC | 139 | + | 97 | current_buffer_position_ += left; | ||||
| HITGNC | 140 | + | 97 | position_ += left; | ||||
| HITGNC | 141 | + | 97 | return; | ||||
| 142 | + | } | ||||||
| HITGNC | 143 | + | 172 | left -= avail; | ||||
| HITGNC | 144 | + | 172 | position_ += avail; | ||||
| HITGNC | 145 | + | 172 | ++current_; | ||||
| HITGNC | 146 | + | 172 | current_buffer_position_ = 0; | ||||
| HITGNC | 147 | + | 172 | skip_forward(); | ||||
| 148 | + | } | ||||||
| 149 | + | } | ||||||
| 150 | + | else | ||||||
| 151 | + | { | ||||||
| HITGNC | 152 | + | 141 | auto left = static_cast<std::size_t>(-n); | ||||
| HITGNC | 153 | + | 296 | while(left > 0) | ||||
| 154 | + | { | ||||||
| HITGNC | 155 | + | 296 | if(left <= current_buffer_position_) | ||||
| 156 | + | { | ||||||
| HITGNC | 157 | + | 141 | current_buffer_position_ -= left; | ||||
| HITGNC | 158 | + | 141 | position_ -= left; | ||||
| HITGNC | 159 | + | 141 | return; | ||||
| 160 | + | } | ||||||
| HITGNC | 161 | + | 155 | left -= current_buffer_position_; | ||||
| HITGNC | 162 | + | 155 | position_ -= current_buffer_position_; | ||||
| HITGNC | 163 | + | 155 | skip_backward(); | ||||
| 164 | + | } | ||||||
| 165 | + | } | ||||||
| 166 | + | } | ||||||
| 167 | + | |||||||
| 168 | + | public: | ||||||
| 169 | + | /// The signed type of the distance between two iterators. | ||||||
| 170 | + | using difference_type = std::ptrdiff_t; | ||||||
| 171 | + | |||||||
| 172 | + | /// The type of the bytes the iterator visits. | ||||||
| 173 | + | using value_type = ByteType; | ||||||
| 174 | + | |||||||
| 175 | + | /// The type returned by `operator->`. | ||||||
| 176 | + | using pointer = byte_type*; | ||||||
| 177 | + | |||||||
| 178 | + | /// The type returned by `operator*`. | ||||||
| 179 | + | using reference = byte_type&; | ||||||
| 180 | + | |||||||
| 181 | + | /// The iterator category. | ||||||
| 182 | + | using iterator_category = std::random_access_iterator_tag; | ||||||
| 183 | + | |||||||
| 184 | + | /// Construct a singular iterator. | ||||||
| HITGNC | 185 | + | 14 | buffers_iterator() = default; | ||||
| 186 | + | |||||||
| 187 | + | /** Return an iterator to the first byte of a buffer sequence. | ||||||
| 188 | + | |||||||
| 189 | + | @param bs The buffer sequence. It must outlive the iterator. | ||||||
| 190 | + | |||||||
| 191 | + | @return An iterator to the first byte, or the end iterator if | ||||||
| 192 | + | the sequence holds no bytes. | ||||||
| 193 | + | */ | ||||||
| 194 | + | static | ||||||
| 195 | + | buffers_iterator | ||||||
| HITGNC | 196 | + | 18 | begin(BufferSequence const& bs) noexcept | ||||
| 197 | + | { | ||||||
| HITGNC | 198 | + | 18 | buffers_iterator it; | ||||
| HITGNC | 199 | + | 18 | it.current_ = buffer_sequence_begin(bs); | ||||
| HITGNC | 200 | + | 18 | it.end_ = buffer_sequence_end(bs); | ||||
| HITGNC | 201 | + | 18 | it.skip_forward(); | ||||
| HITGNC | 202 | + | 18 | return it; | ||||
| 203 | + | } | ||||||
| 204 | + | |||||||
| 205 | + | /** Return an iterator past the last byte of a buffer sequence. | ||||||
| 206 | + | |||||||
| 207 | + | @param bs The buffer sequence. It must outlive the iterator. | ||||||
| 208 | + | |||||||
| 209 | + | @return The end iterator. | ||||||
| 210 | + | */ | ||||||
| 211 | + | static | ||||||
| 212 | + | buffers_iterator | ||||||
| HITGNC | 213 | + | 24 | end(BufferSequence const& bs) noexcept | ||||
| 214 | + | { | ||||||
| HITGNC | 215 | + | 24 | buffers_iterator it; | ||||
| HITGNC | 216 | + | 24 | it.current_ = buffer_sequence_end(bs); | ||||
| HITGNC | 217 | + | 24 | it.end_ = it.current_; | ||||
| HITGNC | 218 | + | 24 | it.position_ = buffer_size(bs); | ||||
| HITGNC | 219 | + | 24 | return it; | ||||
| 220 | + | } | ||||||
| 221 | + | |||||||
| 222 | + | /** Return a reference to the current byte. | ||||||
| 223 | + | |||||||
| 224 | + | @par Preconditions | ||||||
| 225 | + | The iterator is not the end iterator. | ||||||
| 226 | + | */ | ||||||
| 227 | + | reference | ||||||
| HITGNC | 228 | + | 125 | operator*() const noexcept | ||||
| 229 | + | { | ||||||
| 230 | + | return static_cast<pointer>( | ||||||
| HITGNC | 231 | + | 125 | current_buffer_.data())[current_buffer_position_]; | ||||
| 232 | + | } | ||||||
| 233 | + | |||||||
| 234 | + | /** Return a pointer to the current byte. | ||||||
| 235 | + | |||||||
| 236 | + | @par Preconditions | ||||||
| 237 | + | The iterator is not the end iterator. | ||||||
| 238 | + | */ | ||||||
| 239 | + | pointer | ||||||
| 240 | + | operator->() const noexcept | ||||||
| 241 | + | { | ||||||
| 242 | + | return &**this; | ||||||
| 243 | + | } | ||||||
| 244 | + | |||||||
| 245 | + | /** Return a reference to the byte `n` positions away. | ||||||
| 246 | + | |||||||
| 247 | + | @par Complexity | ||||||
| 248 | + | Linear in the number of buffers crossed. | ||||||
| 249 | + | |||||||
| 250 | + | @param n The offset from this iterator, in bytes. | ||||||
| 251 | + | */ | ||||||
| 252 | + | reference | ||||||
| HITGNC | 253 | + | 33 | operator[](difference_type n) const noexcept | ||||
| 254 | + | { | ||||||
| HITGNC | 255 | + | 33 | return *(*this + n); | ||||
| 256 | + | } | ||||||
| 257 | + | |||||||
| 258 | + | /// Advance to the next byte. | ||||||
| 259 | + | buffers_iterator& | ||||||
| HITGNC | 260 | + | 59 | operator++() noexcept | ||||
| 261 | + | { | ||||||
| HITGNC | 262 | + | 59 | ++position_; | ||||
| HITGNC | 263 | + | 59 | if(++current_buffer_position_ < current_buffer_.size()) | ||||
| HITGNC | 264 | + | 47 | return *this; | ||||
| HITGNC | 265 | + | 12 | ++current_; | ||||
| HITGNC | 266 | + | 12 | current_buffer_position_ = 0; | ||||
| HITGNC | 267 | + | 12 | skip_forward(); | ||||
| HITGNC | 268 | + | 12 | return *this; | ||||
| 269 | + | } | ||||||
| 270 | + | |||||||
| 271 | + | /// Advance to the next byte, returning the previous position. | ||||||
| 272 | + | buffers_iterator | ||||||
| 273 | + | operator++(int) noexcept | ||||||
| 274 | + | { | ||||||
| 275 | + | auto tmp = *this; | ||||||
| 276 | + | ++*this; | ||||||
| 277 | + | return tmp; | ||||||
| 278 | + | } | ||||||
| 279 | + | |||||||
| 280 | + | /// Move back to the previous byte. | ||||||
| 281 | + | buffers_iterator& | ||||||
| HITGNC | 282 | + | 20 | operator--() noexcept | ||||
| 283 | + | { | ||||||
| HITGNC | 284 | + | 20 | advance(-1); | ||||
| HITGNC | 285 | + | 20 | return *this; | ||||
| 286 | + | } | ||||||
| 287 | + | |||||||
| 288 | + | /// Move back to the previous byte, returning the previous position. | ||||||
| 289 | + | buffers_iterator | ||||||
| 290 | + | operator--(int) noexcept | ||||||
| 291 | + | { | ||||||
| 292 | + | auto tmp = *this; | ||||||
| 293 | + | --*this; | ||||||
| 294 | + | return tmp; | ||||||
| 295 | + | } | ||||||
| 296 | + | |||||||
| 297 | + | /** Move by `n` bytes. | ||||||
| 298 | + | |||||||
| 299 | + | @par Complexity | ||||||
| 300 | + | Linear in the number of buffers crossed. | ||||||
| 301 | + | |||||||
| 302 | + | @param n The number of bytes to move; negative moves backward. | ||||||
| 303 | + | |||||||
| 304 | + | @return A reference to `*this`. | ||||||
| 305 | + | */ | ||||||
| 306 | + | buffers_iterator& | ||||||
| HITGNC | 307 | + | 166 | operator+=(difference_type n) noexcept | ||||
| 308 | + | { | ||||||
| HITGNC | 309 | + | 166 | advance(n); | ||||
| HITGNC | 310 | + | 166 | return *this; | ||||
| 311 | + | } | ||||||
| 312 | + | |||||||
| 313 | + | /** Move back by `n` bytes. | ||||||
| 314 | + | |||||||
| 315 | + | @par Complexity | ||||||
| 316 | + | Linear in the number of buffers crossed. | ||||||
| 317 | + | |||||||
| 318 | + | @param n The number of bytes to move back. | ||||||
| 319 | + | |||||||
| 320 | + | @return A reference to `*this`. | ||||||
| 321 | + | */ | ||||||
| 322 | + | buffers_iterator& | ||||||
| HITGNC | 323 | + | 132 | operator-=(difference_type n) noexcept | ||||
| 324 | + | { | ||||||
| HITGNC | 325 | + | 132 | advance(-n); | ||||
| HITGNC | 326 | + | 132 | return *this; | ||||
| 327 | + | } | ||||||
| 328 | + | |||||||
| 329 | + | /// Return an iterator `n` bytes after `it`. | ||||||
| 330 | + | friend | ||||||
| 331 | + | buffers_iterator | ||||||
| HITGNC | 332 | + | 166 | operator+(buffers_iterator it, difference_type n) noexcept | ||||
| 333 | + | { | ||||||
| HITGNC | 334 | + | 166 | it += n; | ||||
| HITGNC | 335 | + | 166 | return it; | ||||
| 336 | + | } | ||||||
| 337 | + | |||||||
| 338 | + | /// Return an iterator `n` bytes after `it`. | ||||||
| 339 | + | friend | ||||||
| 340 | + | buffers_iterator | ||||||
| 341 | + | operator+(difference_type n, buffers_iterator it) noexcept | ||||||
| 342 | + | { | ||||||
| 343 | + | it += n; | ||||||
| 344 | + | return it; | ||||||
| 345 | + | } | ||||||
| 346 | + | |||||||
| 347 | + | /// Return an iterator `n` bytes before `it`. | ||||||
| 348 | + | friend | ||||||
| 349 | + | buffers_iterator | ||||||
| HITGNC | 350 | + | 11 | operator-(buffers_iterator it, difference_type n) noexcept | ||||
| 351 | + | { | ||||||
| HITGNC | 352 | + | 11 | it -= n; | ||||
| HITGNC | 353 | + | 11 | return it; | ||||
| 354 | + | } | ||||||
| 355 | + | |||||||
| 356 | + | /// Return the number of bytes from `b` to `a`. | ||||||
| 357 | + | friend | ||||||
| 358 | + | difference_type | ||||||
| HITGNC | 359 | + | 264 | operator-( | ||||
| 360 | + | buffers_iterator const& a, | ||||||
| 361 | + | buffers_iterator const& b) noexcept | ||||||
| 362 | + | { | ||||||
| HITGNC | 363 | + | 264 | return static_cast<difference_type>(a.position_) - | ||||
| HITGNC | 364 | + | 264 | static_cast<difference_type>(b.position_); | ||||
| 365 | + | } | ||||||
| 366 | + | |||||||
| 367 | + | /// Test whether two iterators refer to the same byte. | ||||||
| 368 | + | friend | ||||||
| 369 | + | bool | ||||||
| HITGNC | 370 | + | 312 | operator==( | ||||
| 371 | + | buffers_iterator const& a, | ||||||
| 372 | + | buffers_iterator const& b) noexcept | ||||||
| 373 | + | { | ||||||
| HITGNC | 374 | + | 312 | return a.position_ == b.position_; | ||||
| 375 | + | } | ||||||
| 376 | + | |||||||
| 377 | + | /// Order two iterators by byte position. | ||||||
| 378 | + | friend | ||||||
| 379 | + | std::strong_ordering | ||||||
| HITGNC | 380 | + | 121 | operator<=>( | ||||
| 381 | + | buffers_iterator const& a, | ||||||
| 382 | + | buffers_iterator const& b) noexcept | ||||||
| 383 | + | { | ||||||
| HITGNC | 384 | + | 121 | return a.position_ <=> b.position_; | ||||
| 385 | + | } | ||||||
| 386 | + | }; | ||||||
| 387 | + | |||||||
| 388 | + | /** Return an iterator to the first byte of a buffer sequence. | ||||||
| 389 | + | |||||||
| 390 | + | @param bs The buffer sequence. It must outlive the iterator. | ||||||
| 391 | + | |||||||
| 392 | + | @return `buffers_iterator<BufferSequence>::begin(bs)`. | ||||||
| 393 | + | |||||||
| 394 | + | @see @ref buffers_iterator, @ref buffers_end | ||||||
| 395 | + | */ | ||||||
| 396 | + | template<detail::common_buffer_sequence BufferSequence> | ||||||
| 397 | + | buffers_iterator<BufferSequence> | ||||||
| HITGNC | 398 | + | 17 | buffers_begin(BufferSequence const& bs) noexcept | ||||
| 399 | + | { | ||||||
| HITGNC | 400 | + | 17 | return buffers_iterator<BufferSequence>::begin(bs); | ||||
| 401 | + | } | ||||||
| 402 | + | |||||||
| 403 | + | /// Rejects a temporary sequence, since the iterator would dangle. | ||||||
| 404 | + | template<detail::common_buffer_sequence BufferSequence> | ||||||
| 405 | + | buffers_iterator<BufferSequence> | ||||||
| 406 | + | buffers_begin(BufferSequence const&& bs) = delete; | ||||||
| 407 | + | |||||||
| 408 | + | /** Return an iterator past the last byte of a buffer sequence. | ||||||
| 409 | + | |||||||
| 410 | + | @param bs The buffer sequence. It must outlive the iterator. | ||||||
| 411 | + | |||||||
| 412 | + | @return `buffers_iterator<BufferSequence>::end(bs)`. | ||||||
| 413 | + | |||||||
| 414 | + | @see @ref buffers_iterator, @ref buffers_begin | ||||||
| 415 | + | */ | ||||||
| 416 | + | template<detail::common_buffer_sequence BufferSequence> | ||||||
| 417 | + | buffers_iterator<BufferSequence> | ||||||
| HITGNC | 418 | + | 24 | buffers_end(BufferSequence const& bs) noexcept | ||||
| 419 | + | { | ||||||
| HITGNC | 420 | + | 24 | return buffers_iterator<BufferSequence>::end(bs); | ||||
| 421 | + | } | ||||||
| 422 | + | |||||||
| 423 | + | /// Rejects a temporary sequence, since the iterator would dangle. | ||||||
| 424 | + | template<detail::common_buffer_sequence BufferSequence> | ||||||
| 425 | + | buffers_iterator<BufferSequence> | ||||||
| 426 | + | buffers_end(BufferSequence const&& bs) = delete; | ||||||
| 427 | + | |||||||
| 428 | + | } // capy | ||||||
| 429 | + | } // boost | ||||||
| 430 | + | |||||||
| 431 | + | #endif | ||||||