Coverage Report

Created: 2026-03-09 06:24

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/proc/self/cwd/pw_multibuf/v2/generic_multibuf.cc
Line
Count
Source
1
// Copyright 2025 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
15
#include <cstring>
16
#include <utility>
17
18
#include "pw_assert/check.h"
19
#include "pw_containers/algorithm.h"
20
#include "pw_multibuf/v2/internal/byte_iterator.h"
21
#include "pw_multibuf/v2/multibuf.h"
22
#include "pw_status/try.h"
23
24
namespace pw::multibuf::v2::internal {
25
26
template <typename Out>
27
0
static constexpr Out CheckedCast(size_t val) {
28
0
  size_t max = size_t(std::numeric_limits<Out>::max());
29
0
  PW_CHECK_UINT_LE(val, max);
30
0
  return static_cast<Out>(val);
31
0
}
Unexecuted instantiation: generic_multibuf.cc:unsigned short pw::multibuf::v2::internal::CheckedCast<unsigned short>(unsigned long)
Unexecuted instantiation: generic_multibuf.cc:long pw::multibuf::v2::internal::CheckedCast<long>(unsigned long)
32
33
0
GenericMultiBuf& GenericMultiBuf::operator=(GenericMultiBuf&& other) {
34
0
  deque_ = std::move(other.deque_);
35
0
  entries_per_chunk_ =
36
0
      std::exchange(other.entries_per_chunk_, Entry::kMinEntriesPerChunk);
37
0
  observer_ = std::exchange(other.observer_, nullptr);
38
0
  return *this;
39
0
}
40
41
bool GenericMultiBuf::TryReserveForInsert(const_iterator pos,
42
0
                                          const GenericMultiBuf& mb) {
43
0
  size_type entries_per_chunk = entries_per_chunk_;
44
0
  while (entries_per_chunk_ < mb.entries_per_chunk_) {
45
0
    if (!AddLayer(0)) {
46
0
      break;
47
0
    }
48
0
  }
49
0
  if (pos.offset_ == 0 || !IsOwned(pos.chunk_) ||
50
0
      TryConvertToShared(pos.chunk_)) {
51
0
    if (entries_per_chunk_ >= mb.entries_per_chunk_ &&
52
0
        TryReserveEntries(entries_per_chunk_ * mb.num_chunks(),
53
0
                          pos.offset_ != 0)) {
54
0
      return true;
55
0
    }
56
0
  }
57
0
  while (entries_per_chunk_ > entries_per_chunk) {
58
0
    PopLayer();
59
0
  }
60
0
  return false;
61
0
}
62
63
0
bool GenericMultiBuf::TryReserveForInsert(const_iterator pos) {
64
0
  return (pos.offset_ == 0 || !IsOwned(pos.chunk_) ||
65
0
          TryConvertToShared(pos.chunk_)) &&
66
0
         TryReserveEntries(entries_per_chunk_, pos.offset_ != 0);
67
0
}
68
69
auto GenericMultiBuf::Insert(const_iterator pos, GenericMultiBuf&& mb)
70
0
    -> iterator {
71
  // Make room for the other object's entries.
72
0
  PW_CHECK(TryReserveForInsert(pos, mb));
73
0
  size_type chunk = InsertChunks(pos, mb.num_chunks());
74
75
  // Merge the entries into this object.
76
0
  size_t size = 0;
77
0
  size_type index = chunk * entries_per_chunk_;
78
0
  while (!mb.empty()) {
79
0
    size_type i = 0;
80
0
    size_type offset = mb.GetOffset(0);
81
0
    size_type length = mb.GetLength(0);
82
0
    for (; i < mb.entries_per_chunk_; ++i) {
83
0
      deque_[index + i] = mb.deque_.front();
84
0
      mb.deque_.pop_front();
85
0
    }
86
87
    // If this object is deeper than `mb`, pad it with extra entries.
88
0
    for (; i < entries_per_chunk_; ++i) {
89
0
      deque_[index + i].view = {
90
0
          .offset = offset,
91
0
          .sealed = false,
92
0
          .length = length,
93
0
          .boundary = true,
94
0
      };
95
0
    }
96
0
    size += size_t{length};
97
0
    index += entries_per_chunk_;
98
0
  }
99
0
  if (mb.observer_ != nullptr) {
100
0
    mb.observer_->Notify(Observer::Event::kBytesRemoved, size);
101
0
  }
102
0
  if (observer_ != nullptr) {
103
0
    observer_->Notify(Observer::Event::kBytesAdded, size);
104
0
  }
105
0
  return MakeIterator(chunk);
106
0
}
107
108
auto GenericMultiBuf::Insert(const_iterator pos, ConstByteSpan bytes)
109
0
    -> iterator {
110
0
  PW_CHECK(TryReserveForInsert(pos));
111
0
  [[maybe_unused]] auto [iter, unused] = Insert(pos, bytes, 0, bytes.size());
112
0
  return iter;
113
0
}
114
115
auto GenericMultiBuf::Insert(const_iterator pos,
116
0
                             UniquePtr<const std::byte[]>&& owned) -> iterator {
117
0
  ConstByteSpan bytes(owned.get(), owned.size());
118
0
  PW_CHECK(TryReserveForInsert(pos));
119
0
  auto [iter, chunk] = Insert(pos, bytes, 0, bytes.size());
120
0
  if (owned == nullptr) {
121
0
    return iter;
122
0
  }
123
0
  deque_[memory_context_index(chunk)].deallocator = owned.deallocator();
124
0
  deque_[base_view_index(chunk)].base_view.owned = true;
125
0
  owned.Release();
126
0
  return iter;
127
0
}
128
129
auto GenericMultiBuf::Insert(const_iterator pos,
130
                             const SharedPtr<const std::byte[]>& shared,
131
                             size_t offset,
132
0
                             size_t length) -> iterator {
133
0
  ConstByteSpan bytes(shared.get(), shared.size());
134
0
  PW_CHECK(TryReserveForInsert(pos));
135
0
  auto [iter, chunk] = Insert(pos, bytes, offset, length);
136
0
  if (shared == nullptr) {
137
0
    return iter;
138
0
  }
139
140
  // Extract the shared pointer's control block using a restricted method.
141
0
  const auto& handle = ControlBlockHandle::GetInstance_DO_NOT_USE();
142
0
  ControlBlock* control_block = shared.GetControlBlock(handle);
143
144
0
  deque_[memory_context_index(chunk)].control_block = control_block;
145
0
  deque_[base_view_index(chunk)].base_view.shared = true;
146
0
  control_block->IncrementShared();
147
0
  return iter;
148
0
}
149
150
Result<GenericMultiBuf> GenericMultiBuf::Remove(const_iterator pos,
151
0
                                                size_t size) {
152
0
  PW_CHECK(IsRemovable(pos, size));
153
0
  GenericMultiBuf out(deque_.get_allocator());
154
0
  if (!TryReserveForRemove(pos, size, &out)) {
155
0
    return Status::ResourceExhausted();
156
0
  }
157
0
  MoveRange(pos, size, out);
158
0
  if (observer_ != nullptr) {
159
0
    observer_->Notify(Observer::Event::kBytesRemoved, size);
160
0
  }
161
0
  return Result<GenericMultiBuf>(std::move(out));
162
0
}
163
164
0
Result<GenericMultiBuf> GenericMultiBuf::PopFrontFragment() {
165
0
  PW_CHECK(!empty());
166
0
  size_t size = 0;
167
0
  for (size_type chunk = 0; chunk < num_chunks(); ++chunk) {
168
0
    size_type length = GetLength(chunk);
169
0
    if (length == 0) {
170
0
      continue;
171
0
    }
172
0
    size += size_t{length};
173
0
    if (IsBoundary(chunk)) {
174
0
      break;
175
0
    }
176
0
  }
177
0
  return Remove(begin(), size);
178
0
}
179
180
Result<GenericMultiBuf::const_iterator> GenericMultiBuf::Discard(
181
0
    const_iterator pos, size_t size) {
182
0
  if (!TryReserveForRemove(pos, size, nullptr)) {
183
0
    return Status::ResourceExhausted();
184
0
  }
185
0
  difference_type out_offset = pos - begin();
186
0
  ClearRange(pos, size);
187
0
  if (observer_ != nullptr) {
188
0
    observer_->Notify(Observer::Event::kBytesRemoved, size);
189
0
  }
190
0
  return cbegin() + out_offset;
191
0
}
192
193
0
bool GenericMultiBuf::IsReleasable(const_iterator pos) const {
194
0
  PW_CHECK(pos != cend());
195
0
  return IsOwned(pos.chunk_);
196
0
}
197
198
0
UniquePtr<std::byte[]> GenericMultiBuf::Release(const_iterator pos) {
199
0
  PW_CHECK(IsReleasable(pos));
200
0
  ByteSpan bytes = GetView(pos.chunk_, 1);
201
0
  Deallocator& deallocator = GetDeallocator(pos.chunk_);
202
0
  EraseRange(pos - pos.offset_, size_t{GetLength(pos.chunk_)});
203
0
  if (observer_ != nullptr) {
204
0
    observer_->Notify(Observer::Event::kBytesRemoved, bytes.size());
205
0
  }
206
0
  return UniquePtr<std::byte[]>(bytes.data(), bytes.size(), deallocator);
207
0
}
208
209
0
bool GenericMultiBuf::IsShareable(const_iterator pos) const {
210
0
  PW_CHECK(pos != cend());
211
0
  return IsShared(pos.chunk_);
212
0
}
213
214
0
SharedPtr<std::byte[]> GenericMultiBuf::Share(const_iterator pos) const {
215
0
  PW_CHECK(IsShareable(pos));
216
0
  ControlBlock& control_block = GetControlBlock(pos.chunk_);
217
0
  control_block.IncrementShared();
218
0
  return SharedPtr<std::byte[]>(GetData(pos.chunk_), &control_block);
219
0
}
220
221
0
size_t GenericMultiBuf::CopyFrom(ConstByteSpan src, size_t offset) {
222
0
  size_t total = 0;
223
0
  for (size_type chunk = 0; chunk < num_chunks(); ++chunk) {
224
0
    if (src.empty()) {
225
0
      break;
226
0
    }
227
0
    ByteSpan view = GetView(chunk);
228
0
    if (offset < view.size()) {
229
0
      size_t size = std::min(view.size() - offset, src.size());
230
0
      std::memcpy(view.data() + offset, src.data(), size);
231
0
      src = src.subspan(size);
232
0
      offset = 0;
233
0
      total += size;
234
0
    } else {
235
0
      offset -= view.size();
236
0
    }
237
0
  }
238
0
  return total;
239
0
}
240
241
0
ConstByteSpan GenericMultiBuf::Get(ByteSpan copy, size_t offset) const {
242
0
  ByteSpan buffer;
243
0
  std::optional<size_type> start;
244
0
  for (size_type chunk = 0; chunk < num_chunks(); ++chunk) {
245
0
    ByteSpan view = GetView(chunk);
246
0
    if (buffer.empty() && offset >= view.size()) {
247
      // Still looking for start of data.
248
0
      offset -= view.size();
249
0
    } else if (buffer.empty()) {
250
      // Found the start of data.
251
0
      buffer = view.subspan(offset);
252
0
      start = chunk;
253
0
    } else if (buffer.data() + buffer.size() == view.data()) {
254
      // Current view is contiguous with previous; append.
255
0
      buffer = ByteSpan(buffer.data(), buffer.size() + view.size());
256
0
    } else {
257
      // Span is discontiguous and needs to be copied.
258
0
      size_t copied = CopyToImpl(copy, offset, start.value());
259
0
      return copy.subspan(0, copied);
260
0
    }
261
0
  }
262
  // Requested span is contiguous and can be directly passed to the visitor.
263
0
  return buffer.size() <= copy.size() ? buffer : buffer.subspan(0, copy.size());
264
0
}
265
266
0
void GenericMultiBuf::Clear() {
267
0
  while (num_layers() > 1) {
268
0
    UnsealTopLayer();
269
0
    PopLayer();
270
0
  }
271
0
  size_t num_bytes = size();
272
0
  ClearRange(begin(), num_bytes);
273
0
  if (observer_ != nullptr) {
274
0
    observer_->Notify(Observer::Event::kBytesRemoved, num_bytes);
275
0
    observer_ = nullptr;
276
0
  }
277
0
}
278
279
0
void GenericMultiBuf::ShrinkToFit() { deque_.shrink_to_fit(); }
280
281
0
bool GenericMultiBuf::TryReserveLayers(size_t num_layers, size_t num_chunks) {
282
0
  if (num_layers == 0 || num_chunks == 0) {
283
0
    return true;
284
0
  }
285
0
  size_type num_entries = 0;
286
0
  PW_CHECK(CheckedIncrement(num_layers, Entry::kMinEntriesPerChunk - 1));
287
0
  PW_CHECK(CheckedMul(num_layers, num_chunks, num_entries));
288
0
  if (num_entries <= deque_.size()) {
289
0
    return true;
290
0
  }
291
0
  return TryReserveEntries(num_entries - deque_.size());
292
0
}
293
294
0
bool GenericMultiBuf::AddLayer(size_t offset, size_t length) {
295
  // Given entries with layers A and B, to which we want to add layer C:
296
  //     A1 B1 A2 B2 A3 B3 A4 B4
297
  // 1). Add `shift` empty buffers:
298
  //     A1 B1 A2 B2 A3 B3 A4 B4 -- -- -- --
299
0
  size_type shift = num_chunks();
300
0
  if (!TryReserveEntries(shift)) {
301
0
    return false;
302
0
  }
303
0
  ++entries_per_chunk_;
304
0
  for (size_t i = 0; i < shift; ++i) {
305
0
    deque_.push_back({.data = nullptr});
306
0
  }
307
308
  // 2). Shift the existing layers over. This is expensive, but slicing usually
309
  //     happens with `shift == 1`:
310
  //     A1 B1 -- A2 B2 -- A3 B3 -- A4 B4 --
311
0
  for (size_type i = deque_.size(); i != 0; --i) {
312
0
    if (i % entries_per_chunk_ == 0) {
313
0
      --shift;
314
0
      deque_[i - 1].view = {
315
0
          .offset = 0,
316
0
          .sealed = false,
317
0
          .length = 0,
318
0
          .boundary = false,
319
0
      };
320
0
    } else {
321
0
      deque_[i - 1] = deque_[i - 1 - shift];
322
0
    }
323
0
  }
324
325
  // 3). Fill in the new layer C with subspans of layer B:
326
  //     A1 B1 C1 A2 B2 C2 A3 B3 C3 A4 B4 C4
327
0
  size_type off = CheckedCast<size_type>(offset);
328
0
  size_type len = length == dynamic_extent ? 0 : CheckedCast<size_type>(length);
329
0
  size_type lower_layer = num_layers() - 1;
330
0
  size_type num_fragments = 0;
331
0
  Entry* last = nullptr;
332
0
  for (size_type chunk = 0; chunk < num_chunks(); ++chunk) {
333
0
    size_type lower_off = GetOffset(chunk, lower_layer);
334
0
    size_type lower_len = GetLength(chunk, lower_layer);
335
336
0
    if (lower_len != 0 && IsBoundary(chunk, lower_layer)) {
337
0
      ++num_fragments;
338
0
    }
339
340
    // Skip over entries until we reach `offset`.
341
0
    Entry& entry = deque_[top_view_index(chunk)];
342
0
    if (off >= lower_len) {
343
0
      off -= lower_len;
344
0
      entry.view.offset = 0;
345
0
      entry.view.length = 0;
346
0
      continue;
347
0
    }
348
0
    entry.view.offset = lower_off + off;
349
0
    lower_len -= off;
350
351
    // This is similar to `entry.view.length = std::min(lower_len, len);`, but
352
    // with extra cases to correctly handle `dynamic_extent` and boundaries.
353
0
    if (lower_len == 0) {
354
0
      entry.view.length = 0;
355
0
    } else if (length == dynamic_extent) {
356
0
      entry.view.length = lower_len;
357
0
      last = &entry;
358
0
    } else if (len == 0) {
359
0
      entry.view.length = 0;
360
0
    } else if (len <= lower_len) {
361
0
      entry.view.length = len;
362
0
      entry.view.boundary = true;
363
0
      len = 0;
364
0
    } else {
365
0
      entry.view.length = lower_len;
366
0
      len -= lower_len;
367
0
    }
368
0
    off = 0;
369
0
  }
370
0
  PW_CHECK_UINT_EQ(len, 0, "Requested layer exceeds available data");
371
372
0
  if (last != nullptr) {
373
0
    last->view.boundary = true;
374
0
  }
375
0
  if (observer_ != nullptr) {
376
0
    observer_->Notify(Observer::Event::kLayerAdded, num_fragments);
377
0
  }
378
0
  return true;
379
0
}
380
381
0
void GenericMultiBuf::SealTopLayer() {
382
0
  PW_CHECK_UINT_GT(num_layers(), 1);
383
0
  for (size_type chunk = 0; chunk < num_chunks(); ++chunk) {
384
0
    deque_[top_view_index(chunk)].view.sealed = true;
385
0
  }
386
0
}
387
388
0
void GenericMultiBuf::UnsealTopLayer() {
389
0
  PW_CHECK_UINT_GT(num_layers(), 1);
390
0
  for (size_type chunk = 0; chunk < num_chunks(); ++chunk) {
391
0
    deque_[top_view_index(chunk)].view.sealed = false;
392
0
  }
393
0
}
394
395
0
void GenericMultiBuf::TruncateTopLayer(size_t length) {
396
0
  PW_CHECK_UINT_GT(num_layers(), 1);
397
398
0
  size_type len = CheckedCast<size_type>(length);
399
0
  for (size_type chunk = 0; chunk < num_chunks(); ++chunk) {
400
0
    PW_CHECK(!IsSealed(chunk),
401
0
             "MultiBuf::TruncateTopLayer() was called on a sealed layer; call "
402
0
             "UnsealTopLayer first");
403
0
    size_type chunk_len = GetLength(chunk);
404
0
    if (len >= chunk_len) {
405
0
      len -= chunk_len;
406
0
      continue;
407
0
    }
408
0
    deque_[top_view_index(chunk)].view.length = len;
409
0
    len = 0;
410
0
  }
411
0
  PW_CHECK_UINT_EQ(len,
412
0
                   0,
413
0
                   "MultiBuf::TruncateTopLayer() was called with a length "
414
0
                   "longer than the MultiBuf");
415
0
}
416
417
0
void GenericMultiBuf::PopLayer() {
418
0
  PW_CHECK_UINT_GT(num_layers(), 1);
419
420
  // Given entries with layers A, B, and C, to remove layer C:
421
  //     A1 B1 C1 A2 B2 C2 A3 B3 C3 A4 B4 C4
422
  // 1). Check that the layer is not sealed.
423
0
  size_t num_fragments = 0;
424
0
  for (size_type chunk = 0; chunk < num_chunks(); ++chunk) {
425
0
    PW_CHECK(!IsSealed(chunk),
426
0
             "MultiBuf::PopLayer() was called on a sealed layer; call "
427
0
             "UnsealTopLayer first");
428
0
    if (GetLength(chunk) != 0 && IsBoundary(chunk)) {
429
0
      ++num_fragments;
430
0
    }
431
0
  }
432
433
  // 2). Compress lower layers backward.
434
  //     -- -- -- -- A1 B1 A2 B2 A3 B3 A4 B4
435
0
  size_type shift = 0;
436
0
  size_type discard = deque_.size() / entries_per_chunk_;
437
0
  size_type keep = deque_.size() - discard;
438
0
  --entries_per_chunk_;
439
0
  for (size_type i = 1; i <= keep; ++i) {
440
0
    size_type j = deque_.size() - i;
441
0
    if ((i - 1) % entries_per_chunk_ == 0) {
442
0
      ++shift;
443
0
    }
444
0
    deque_[j] = deque_[j - shift];
445
0
    if ((j - discard) % entries_per_chunk_ != entries_per_chunk_ - 1) {
446
0
      continue;
447
0
    }
448
0
  }
449
450
  // 3). Discard the first elements
451
  //     A1 B1 A2 B2 A3 B3 A4 B4
452
0
  for (size_type i = 0; i < discard; ++i) {
453
0
    deque_.pop_front();
454
0
  }
455
0
  if (observer_ != nullptr) {
456
0
    observer_->Notify(Observer::Event::kLayerRemoved, num_fragments);
457
0
  }
458
0
}
459
460
// Implementation methods
461
462
0
size_t GenericMultiBuf::CheckRange(size_t offset, size_t length, size_t size) {
463
0
  PW_CHECK_UINT_LE(size, Entry::kMaxSize);
464
0
  PW_CHECK_UINT_LE(offset, size);
465
0
  if (length == dynamic_extent) {
466
0
    return size - offset;
467
0
  }
468
0
  PW_CHECK_UINT_LE(length, size - offset);
469
0
  return length;
470
0
}
471
472
0
GenericMultiBuf::size_type GenericMultiBuf::NumFragments() const {
473
0
  size_type num_fragments = 0;
474
0
  for (size_type chunk = 0; chunk < num_chunks(); ++chunk) {
475
0
    if (GetLength(chunk) != 0 && IsBoundary(chunk)) {
476
0
      ++num_fragments;
477
0
    }
478
0
  }
479
0
  return num_fragments;
480
0
}
481
482
0
bool GenericMultiBuf::TryConvertToShared(size_type chunk) {
483
0
  Deallocator& deallocator = GetDeallocator(chunk);
484
0
  std::byte* data = GetData(chunk);
485
0
  Entry::BaseView& base_view = deque_[base_view_index(chunk)].base_view;
486
487
  // Create a new control block using a restricted method.
488
0
  const auto& handle = ControlBlockHandle::GetInstance_DO_NOT_USE();
489
0
  auto* control_block =
490
0
      ControlBlock::Create(handle, &deallocator, data, base_view.length);
491
492
0
  if (control_block == nullptr) {
493
0
    return false;
494
0
  }
495
0
  deque_[memory_context_index(chunk)].control_block = control_block;
496
0
  base_view.owned = false;
497
0
  base_view.shared = true;
498
0
  return true;
499
0
}
500
501
0
bool GenericMultiBuf::TryReserveEntries(size_type num_entries, bool split) {
502
0
  if (split) {
503
0
    PW_CHECK(CheckedAdd(num_entries, entries_per_chunk_, num_entries));
504
0
  }
505
0
  PW_CHECK(CheckedAdd(num_entries, deque_.size(), num_entries));
506
0
  return deque_.try_reserve_exact(num_entries);
507
0
}
508
509
GenericMultiBuf::size_type GenericMultiBuf::InsertChunks(const_iterator pos,
510
0
                                                         size_type num_chunks) {
511
0
  size_type chunk = pos.chunk_;
512
0
  size_type offset = pos.offset_;
513
0
  if (offset != 0) {
514
0
    num_chunks++;
515
0
  } else if (chunk < this->num_chunks()) {
516
    // Insert before any empty chunks at `pos`.
517
0
    while (chunk != 0 && GetLength(chunk - 1) == 0) {
518
0
      --chunk;
519
0
    }
520
0
  }
521
0
  size_type num_entries = num_chunks * entries_per_chunk_;
522
0
  PW_CHECK(TryReserveEntries(num_entries, offset != 0));
523
0
  Entry entry;
524
0
  entry.data = nullptr;
525
0
  for (size_type i = 0; i < num_entries; ++i) {
526
0
    deque_.push_back(entry);
527
0
  }
528
0
  size_type index = chunk * entries_per_chunk_;
529
0
  for (size_type i = deque_.size() - 1; i >= index + num_entries; --i) {
530
0
    deque_[i] = deque_[i - num_entries];
531
0
  }
532
533
0
  if (offset == 0) {
534
    // New chunk falls between existing chunks.
535
0
    return chunk;
536
0
  }
537
  // New chunk within an existing chunk, which must be split.
538
0
  SplitAfter(chunk, offset, deque_, chunk + num_chunks);
539
0
  SplitBefore(chunk, offset);
540
0
  return chunk + 1;
541
0
}
542
543
auto GenericMultiBuf::Insert(const_iterator pos,
544
                             ConstByteSpan bytes,
545
                             size_t offset,
546
0
                             size_t length) -> std::tuple<iterator, size_type> {
547
0
  size_type chunk = InsertChunks(pos, 1);
548
0
  deque_[memory_context_index(chunk)].deallocator = nullptr;
549
0
  deque_[data_index(chunk)].data = const_cast<std::byte*>(bytes.data());
550
0
  auto off = CheckedCast<size_type>(offset);
551
0
  if (length == dynamic_extent) {
552
0
    length = bytes.size() - offset;
553
0
  }
554
0
  auto len = CheckedCast<size_type>(length);
555
0
  deque_[base_view_index(chunk)].base_view = {
556
0
      .offset = off,
557
0
      .owned = false,
558
0
      .length = len,
559
0
      .shared = false,
560
0
  };
561
0
  for (size_type layer = 2; layer <= num_layers(); ++layer) {
562
0
    deque_[view_index(chunk, layer)].view = {
563
0
        .offset = off,
564
0
        .sealed = false,
565
0
        .length = len,
566
0
        .boundary = true,
567
0
    };
568
0
  }
569
0
  if (observer_ != nullptr) {
570
0
    observer_->Notify(Observer::Event::kBytesAdded, length);
571
0
  }
572
0
  return std::make_tuple(MakeIterator(chunk), chunk);
573
0
}
574
575
void GenericMultiBuf::SplitBase(size_type chunk,
576
                                Deque& out_deque,
577
0
                                size_type out_chunk) {
578
0
  if (&deque_ == &out_deque && chunk == out_chunk) {
579
0
    return;
580
0
  }
581
0
  PW_CHECK(!IsOwned(chunk));
582
0
  size_type index = chunk * entries_per_chunk_;
583
0
  size_type out_index = out_chunk * entries_per_chunk_;
584
0
  for (size_type i = 0; i < entries_per_chunk_; ++i) {
585
0
    out_deque[out_index + i] = deque_[index + i];
586
0
  }
587
0
  if (IsShared(chunk)) {
588
0
    GetControlBlock(chunk).IncrementShared();
589
0
  }
590
0
}
591
592
void GenericMultiBuf::SplitBefore(size_type chunk,
593
                                  size_type split,
594
                                  Deque& out_deque,
595
0
                                  size_type out_chunk) {
596
0
  SplitBase(chunk, out_deque, out_chunk);
597
0
  split += GetOffset(chunk);
598
0
  Entry::BaseView src_base_view = deque_[base_view_index(chunk)].base_view;
599
0
  Entry::BaseView& dst_base_view =
600
0
      out_deque[base_view_index(out_chunk)].base_view;
601
0
  dst_base_view.offset = src_base_view.offset;
602
0
  dst_base_view.length = split - src_base_view.offset;
603
0
  for (size_type layer = 2; layer <= num_layers(); ++layer) {
604
0
    Entry::View src_view = deque_[view_index(chunk, layer)].view;
605
0
    Entry::View& dst_view = out_deque[view_index(out_chunk, layer)].view;
606
0
    dst_view.offset = src_view.offset;
607
0
    dst_view.length = split - src_view.offset;
608
0
  }
609
0
}
610
611
0
void GenericMultiBuf::SplitBefore(size_type chunk, size_type split) {
612
0
  SplitBefore(chunk, split, deque_, chunk);
613
0
}
614
615
void GenericMultiBuf::SplitAfter(size_type chunk,
616
                                 size_type split,
617
                                 Deque& out_deque,
618
0
                                 size_type out_chunk) {
619
0
  SplitBase(chunk, out_deque, out_chunk);
620
0
  split += GetOffset(chunk);
621
0
  Entry::BaseView src_base_view = deque_[base_view_index(chunk)].base_view;
622
0
  Entry::BaseView& dst_base_view =
623
0
      out_deque[base_view_index(out_chunk)].base_view;
624
0
  dst_base_view.offset = split;
625
0
  dst_base_view.length = src_base_view.offset + src_base_view.length - split;
626
0
  for (size_type layer = 2; layer <= num_layers(); ++layer) {
627
0
    Entry::View src_view = deque_[view_index(chunk, layer)].view;
628
0
    Entry::View& dst_view = out_deque[view_index(out_chunk, layer)].view;
629
0
    dst_view.offset = split;
630
0
    dst_view.length = src_view.offset + src_view.length - split;
631
0
  }
632
0
}
633
634
0
void GenericMultiBuf::SplitAfter(size_type chunk, size_type split) {
635
0
  SplitAfter(chunk, split, deque_, chunk);
636
0
}
637
638
bool GenericMultiBuf::TryReserveForRemove(const_iterator pos,
639
                                          size_t size,
640
0
                                          GenericMultiBuf* out) {
641
0
  PW_CHECK_UINT_NE(size, 0u);
642
0
  auto end = pos + CheckedCast<difference_type>(size);
643
0
  size_type shift = end.chunk_ - pos.chunk_;
644
645
  // If removing part of an owned chunk, make it shared.
646
0
  if (pos.offset_ != 0 && IsOwned(pos.chunk_) &&
647
0
      !TryConvertToShared(pos.chunk_)) {
648
0
    return false;
649
0
  }
650
651
  // Removing a sub-chunk.
652
0
  if (shift == 0 && pos.offset_ != 0) {
653
0
    return (out == nullptr || out->TryReserveEntries(entries_per_chunk_)) &&
654
0
           TryReserveEntries(0, /*split=*/true);
655
0
  }
656
657
  // If removing part of an owned chunk, make it shared.
658
0
  if (end.offset_ != 0 && IsOwned(end.chunk_) &&
659
0
      !TryConvertToShared(end.chunk_)) {
660
0
    return false;
661
0
  }
662
663
  // Discarding entries, no room needed.
664
0
  if (out == nullptr) {
665
0
    return true;
666
0
  }
667
668
  // Make room in `out`.
669
0
  if (end.offset_ != 0) {
670
0
    ++shift;
671
0
  }
672
0
  return out->TryReserveEntries(shift * entries_per_chunk_);
673
0
}
674
675
void GenericMultiBuf::MoveRange(const_iterator pos,
676
                                size_t size,
677
0
                                GenericMultiBuf& out) {
678
0
  size_type chunk = pos.chunk_;
679
0
  size_type offset = pos.offset_;
680
0
  auto end = pos + CheckedCast<difference_type>(size);
681
0
  out.entries_per_chunk_ = entries_per_chunk_;
682
683
  // Determine how many entries needs to be moved.
684
0
  size_type shift = end.chunk_ - chunk;
685
686
  // Are we removing the prefix of a single chunk?
687
0
  if (shift == 0 && offset == 0) {
688
0
    out.InsertChunks(begin(), 1);
689
0
    SplitBefore(chunk, end.offset_, out.deque_, 0);
690
0
    EraseRange(pos, size);
691
0
    return;
692
0
  }
693
694
  // Are we removing a sub-chunk? If so, split the chunk in two.
695
0
  if (shift == 0) {
696
0
    out.InsertChunks(begin(), 1);
697
0
    SplitBefore(end.chunk_, end.offset_, out.deque_, 0);
698
0
    out.SplitAfter(0, offset);
699
0
    EraseRange(pos, size);
700
0
    return;
701
0
  }
702
703
  // Otherwise, start by copying entries to the new deque, if provided.
704
0
  size_type out_chunk = 0;
705
0
  size_type reserve = end.offset_ == 0 ? shift : shift + 1;
706
0
  out.InsertChunks(cend(), reserve);
707
708
  // Move the suffix of the first chunk.
709
0
  if (offset != 0) {
710
0
    SplitAfter(chunk, offset, out.deque_, out_chunk);
711
0
    --shift;
712
0
    ++chunk;
713
0
    ++out_chunk;
714
0
  }
715
716
  // Move the complete chunks.
717
0
  size_type index = chunk * entries_per_chunk_;
718
0
  size_type end_index = end.chunk_ * entries_per_chunk_;
719
0
  size_type out_index = out_chunk * entries_per_chunk_;
720
0
  pw::copy(deque_.begin() + index,
721
0
           deque_.begin() + end_index,
722
0
           out.deque_.begin() + out_index);
723
0
  chunk += shift;
724
0
  out_chunk += shift;
725
726
  // Copy the prefix of the last chunk.
727
0
  if (end.offset_ != 0) {
728
0
    SplitBefore(end.chunk_, end.offset_, out.deque_, out_chunk);
729
0
  }
730
0
  EraseRange(pos, size);
731
0
}
732
733
0
void GenericMultiBuf::ClearRange(const_iterator pos, size_t size) {
734
0
  size_type chunk = pos.chunk_;
735
0
  size_type offset = pos.offset_;
736
0
  auto end = pos + CheckedCast<difference_type>(size);
737
0
  if (offset != 0) {
738
0
    ++chunk;
739
0
  }
740
0
  for (; chunk < end.chunk_; ++chunk) {
741
0
    std::byte* data = GetData(chunk);
742
0
    if (IsOwned(chunk)) {
743
0
      Deallocator& deallocator = GetDeallocator(chunk);
744
0
      deallocator.Deallocate(data);
745
0
      continue;
746
0
    }
747
0
    if (!IsShared(chunk)) {
748
0
      continue;
749
0
    }
750
    // To avoid races with other shared or weak pointers to the data, put the
751
    // data pointer back into a SharedPtr and let it go out scope.
752
0
    ControlBlock& control_block = GetControlBlock(chunk);
753
0
    SharedPtr<std::byte[]> shared(data, &control_block);
754
0
  }
755
0
  EraseRange(pos, size);
756
0
}
757
758
0
void GenericMultiBuf::EraseRange(const_iterator pos, size_t size) {
759
0
  size_type chunk = pos.chunk_;
760
0
  size_type offset = pos.offset_;
761
0
  auto end = pos + CheckedCast<difference_type>(size);
762
763
  // Are we removing a sub-chunk? If so, split the chunk in two.
764
0
  if (chunk == end.chunk_ && offset != 0) {
765
0
    size_type new_chunk = InsertChunks(pos, 0);
766
0
    SplitAfter(new_chunk, end.offset_ - offset);
767
0
    return;
768
0
  }
769
770
  // Discard suffix of first chunk.
771
0
  if (offset != 0) {
772
0
    SplitBefore(chunk, offset);
773
0
    ++chunk;
774
0
  }
775
776
  // Discard prefix of last chunk.
777
0
  if (end.offset_ != 0) {
778
0
    SplitAfter(end.chunk_, end.offset_);
779
0
  }
780
781
  // Discard complete chunks.
782
0
  if (chunk < end.chunk_) {
783
0
    deque_.erase(deque_.begin() + (chunk * entries_per_chunk_),
784
0
                 deque_.begin() + (end.chunk_ * entries_per_chunk_));
785
0
  }
786
0
}
787
788
size_t GenericMultiBuf::CopyToImpl(ByteSpan dst,
789
                                   size_t offset,
790
0
                                   size_type start) const {
791
0
  size_t total = 0;
792
0
  for (size_type chunk = start; chunk < num_chunks(); ++chunk) {
793
0
    if (dst.empty()) {
794
0
      break;
795
0
    }
796
0
    ConstByteSpan view = GetView(chunk);
797
0
    if (offset < view.size()) {
798
0
      size_t size = std::min(view.size() - offset, dst.size());
799
0
      std::memcpy(dst.data(), view.data() + offset, size);
800
0
      dst = dst.subspan(size);
801
0
      offset = 0;
802
0
      total += size;
803
0
    } else {
804
0
      offset -= view.size();
805
0
    }
806
0
  }
807
0
  return total;
808
0
}
809
810
0
bool GenericMultiBuf::IsTopLayerSealed() const {
811
0
  for (size_type chunk = 0; chunk < num_chunks(); ++chunk) {
812
0
    if (IsSealed(chunk)) {
813
0
      return true;
814
0
    }
815
0
  }
816
0
  return false;
817
0
}
818
819
}  // namespace pw::multibuf::v2::internal