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
15 #include "pw_multibuf/multibuf.h"
16
17 #include <algorithm>
18 #include <cstring>
19
20 #include "pw_assert/check.h"
21
22 namespace pw::multibuf {
23
Release()24 void MultiBufChunks::Release() noexcept {
25 while (first_ != nullptr) {
26 Chunk* removed = first_;
27 first_ = first_->next_in_buf_;
28 removed->Free();
29 }
30 }
31
size_bytes() const32 size_t MultiBufChunks::size_bytes() const {
33 size_t len = 0;
34 for (const auto& chunk : *this) {
35 len += chunk.size();
36 }
37 return len;
38 }
39
empty() const40 bool MultiBuf::empty() const {
41 return std::all_of(Chunks().begin(), Chunks().end(), [](const Chunk& c) {
42 return c.empty();
43 });
44 }
45
ContiguousSpan() const46 std::optional<ConstByteSpan> MultiBuf::ContiguousSpan() const {
47 ConstByteSpan contiguous;
48 for (const Chunk& chunk : Chunks()) {
49 if (chunk.empty()) {
50 continue;
51 }
52 if (contiguous.empty()) {
53 contiguous = chunk;
54 } else if (contiguous.data() + contiguous.size() == chunk.data()) {
55 contiguous = {contiguous.data(), contiguous.size() + chunk.size()};
56 } else { // Non-empty chunks are not contiguous
57 return std::nullopt;
58 }
59 }
60 // Return either the one non-empty chunk or an empty span.
61 return contiguous;
62 }
63
ClaimPrefix(size_t bytes_to_claim)64 bool MultiBuf::ClaimPrefix(size_t bytes_to_claim) {
65 if (Chunks().empty()) {
66 return false;
67 }
68 return Chunks().front().ClaimPrefix(bytes_to_claim);
69 }
70
ClaimSuffix(size_t bytes_to_claim)71 bool MultiBuf::ClaimSuffix(size_t bytes_to_claim) {
72 if (Chunks().empty()) {
73 return false;
74 }
75 return Chunks().back().ClaimSuffix(bytes_to_claim);
76 }
77
DiscardPrefix(size_t bytes_to_discard)78 void MultiBuf::DiscardPrefix(size_t bytes_to_discard) {
79 PW_DCHECK(bytes_to_discard <= size());
80 while (bytes_to_discard != 0) {
81 if (Chunks().begin()->size() > bytes_to_discard) {
82 Chunks().begin()->DiscardPrefix(bytes_to_discard);
83 return;
84 }
85 OwnedChunk front_chunk = TakeFrontChunk();
86 bytes_to_discard -= front_chunk.size();
87 }
88 }
89
Slice(size_t begin,size_t end)90 void MultiBuf::Slice(size_t begin, size_t end) {
91 PW_DCHECK(end >= begin);
92 DiscardPrefix(begin);
93 Truncate(end - begin);
94 }
95
Truncate(size_t len)96 void MultiBuf::Truncate(size_t len) {
97 if (len == 0) {
98 Release();
99 return;
100 }
101 TruncateAfter(begin() + (len - 1));
102 }
103
TruncateAfter(iterator pos)104 void MultiBuf::TruncateAfter(iterator pos) {
105 PW_DCHECK(pos != end());
106 pos.chunk()->Truncate(pos.byte_index() + 1);
107 Chunk* remainder = pos.chunk()->next_in_buf_;
108 pos.chunk()->next_in_buf_ = nullptr;
109 MultiBuf discard(remainder);
110 }
111
PushPrefix(MultiBuf && front)112 void MultiBuf::PushPrefix(MultiBuf&& front) {
113 front.PushSuffix(std::move(*this));
114 *this = std::move(front);
115 }
116
PushSuffix(MultiBufChunks && tail)117 void MultiBufChunks::PushSuffix(MultiBufChunks&& tail) {
118 if (first_ == nullptr) {
119 first_ = tail.first_;
120 tail.first_ = nullptr;
121 return;
122 }
123 back().next_in_buf_ = tail.first_;
124 tail.first_ = nullptr;
125 }
126
CopyTo(ByteSpan dest,const size_t position) const127 StatusWithSize MultiBuf::CopyTo(ByteSpan dest, const size_t position) const {
128 const_iterator byte_in_chunk = begin() + position;
129
130 MultiBufChunks::const_iterator chunk(byte_in_chunk.chunk());
131 size_t chunk_offset = byte_in_chunk.byte_index();
132
133 size_t bytes_copied = 0;
134 for (; chunk != Chunks().end(); ++chunk) {
135 const size_t chunk_bytes = chunk->size() - chunk_offset;
136 const size_t to_copy = std::min(chunk_bytes, dest.size() - bytes_copied);
137 if (to_copy != 0u) {
138 std::memcpy(
139 dest.data() + bytes_copied, chunk->data() + chunk_offset, to_copy);
140 bytes_copied += to_copy;
141 }
142
143 if (chunk_bytes > to_copy) {
144 return StatusWithSize::ResourceExhausted(bytes_copied);
145 }
146 chunk_offset = 0;
147 }
148
149 return StatusWithSize(bytes_copied); // all chunks were copied
150 }
151
CopyFromAndOptionallyTruncate(ConstByteSpan source,size_t position,bool truncate)152 StatusWithSize MultiBuf::CopyFromAndOptionallyTruncate(ConstByteSpan source,
153 size_t position,
154 bool truncate) {
155 if (source.empty()) {
156 if (truncate) {
157 Truncate(position);
158 }
159 return StatusWithSize(0u);
160 }
161
162 iterator byte_in_chunk = begin() + position;
163 size_t chunk_offset = byte_in_chunk.byte_index();
164
165 size_t bytes_copied = 0;
166 for (MultiBufChunks::iterator chunk(byte_in_chunk.chunk());
167 chunk != Chunks().end();
168 ++chunk) {
169 if (chunk->empty()) {
170 continue;
171 }
172 const size_t to_copy =
173 std::min(chunk->size() - chunk_offset, source.size() - bytes_copied);
174 std::memcpy(
175 chunk->data() + chunk_offset, source.data() + bytes_copied, to_copy);
176 bytes_copied += to_copy;
177
178 if (bytes_copied == source.size()) {
179 if (truncate) {
180 // to_copy is always at least one byte, since source.empty() is checked
181 // above and empty chunks are skipped.
182 TruncateAfter(iterator(&*chunk, chunk_offset + to_copy - 1));
183 }
184 return StatusWithSize(bytes_copied);
185 }
186 chunk_offset = 0;
187 }
188
189 return StatusWithSize::ResourceExhausted(bytes_copied); // ran out of space
190 }
191
TakePrefix(size_t bytes_to_take)192 std::optional<MultiBuf> MultiBuf::TakePrefix(size_t bytes_to_take) {
193 PW_DCHECK(bytes_to_take <= size());
194 MultiBuf front;
195 if (bytes_to_take == 0) {
196 return front;
197 }
198 // Pointer to the last element of `front`, allowing constant-time appending.
199 Chunk* last_front_chunk = nullptr;
200 while (bytes_to_take > Chunks().begin()->size()) {
201 OwnedChunk new_chunk = TakeFrontChunk().Take();
202 Chunk* new_chunk_ptr = &*new_chunk;
203 bytes_to_take -= new_chunk.size();
204 if (last_front_chunk == nullptr) {
205 front.PushFrontChunk(std::move(new_chunk));
206 } else {
207 last_front_chunk->next_in_buf_ = std::move(new_chunk).Take();
208 }
209 last_front_chunk = new_chunk_ptr;
210 }
211 if (bytes_to_take == 0) {
212 return front;
213 }
214 std::optional<OwnedChunk> last_front_bit =
215 Chunks().front().TakePrefix(bytes_to_take);
216 if (last_front_bit.has_value()) {
217 if (last_front_chunk != nullptr) {
218 Chunk* last_front_bit_ptr = std::move(*last_front_bit).Take();
219 last_front_chunk->next_in_buf_ = last_front_bit_ptr;
220 } else {
221 front.PushFrontChunk(std::move(*last_front_bit));
222 }
223 return front;
224 }
225 // The front chunk could not be split, so we must put the front back on.
226 PushPrefix(std::move(front));
227 return std::nullopt;
228 }
229
TakeSuffix(size_t bytes_to_take)230 std::optional<MultiBuf> MultiBuf::TakeSuffix(size_t bytes_to_take) {
231 size_t front_size = size() - bytes_to_take;
232 std::optional<MultiBuf> front_opt = TakePrefix(front_size);
233 if (!front_opt.has_value()) {
234 return std::nullopt;
235 }
236 MultiBuf front_then_back = std::move(*front_opt);
237 std::swap(front_then_back, *this);
238 return front_then_back;
239 }
240
push_front(OwnedChunk && chunk)241 void MultiBufChunks::push_front(OwnedChunk&& chunk) {
242 PW_DCHECK(chunk->next_in_buf_ == nullptr);
243 Chunk* new_chunk = std::move(chunk).Take();
244 Chunk* old_first = first_;
245 new_chunk->next_in_buf_ = old_first;
246 first_ = new_chunk;
247 }
248
push_back(OwnedChunk && chunk)249 void MultiBufChunks::push_back(OwnedChunk&& chunk) {
250 PW_DCHECK(chunk->next_in_buf_ == nullptr);
251 Chunk* new_chunk = std::move(chunk).Take();
252 if (first_ == nullptr) {
253 first_ = new_chunk;
254 return;
255 }
256 Chunk* cur = first_;
257 while (cur->next_in_buf_ != nullptr) {
258 cur = cur->next_in_buf_;
259 }
260 cur->next_in_buf_ = new_chunk;
261 }
262
insert(iterator position,OwnedChunk && chunk)263 MultiBufChunks::iterator MultiBufChunks::insert(iterator position,
264 OwnedChunk&& chunk) {
265 // Note: this also catches the cases where ``first_ == nullptr``
266 PW_DCHECK(chunk->next_in_buf_ == nullptr);
267 if (position == begin()) {
268 push_front(std::move(chunk));
269 return iterator(first_);
270 }
271 Chunk* previous = Previous(position.chunk_);
272 Chunk* old_next = previous->next_in_buf_;
273 Chunk* new_chunk = std::move(chunk).Take();
274 new_chunk->next_in_buf_ = old_next;
275 previous->next_in_buf_ = new_chunk;
276 return iterator(new_chunk);
277 }
278
take_front()279 OwnedChunk MultiBufChunks::take_front() {
280 PW_CHECK(!empty());
281 Chunk* old_first = first_;
282 first_ = old_first->next_in_buf_;
283 old_first->next_in_buf_ = nullptr;
284 return OwnedChunk(old_first);
285 }
286
take(iterator position)287 std::tuple<MultiBufChunks::iterator, OwnedChunk> MultiBufChunks::take(
288 iterator position) {
289 Chunk* chunk = position.chunk_;
290 if (position == begin()) {
291 OwnedChunk old_first = take_front();
292 return std::make_tuple(iterator(first_), std::move(old_first));
293 }
294 Chunk* previous = Previous(chunk);
295 previous->next_in_buf_ = chunk->next_in_buf_;
296 chunk->next_in_buf_ = nullptr;
297 return std::make_tuple(iterator(previous->next_in_buf_), OwnedChunk(chunk));
298 }
299
Previous(Chunk * chunk) const300 Chunk* MultiBufChunks::Previous(Chunk* chunk) const {
301 Chunk* previous = first_;
302 while (previous != nullptr && previous->next_in_buf_ != chunk) {
303 previous = previous->next_in_buf_;
304 }
305 return previous;
306 }
307
operator ++()308 MultiBuf::const_iterator& MultiBuf::const_iterator::operator++() {
309 if (byte_index_ + 1 == chunk_->size()) {
310 chunk_ = chunk_->next_in_buf_;
311 byte_index_ = 0;
312 AdvanceToData();
313 } else {
314 ++byte_index_;
315 }
316 return *this;
317 }
318
operator +=(size_t advance)319 MultiBuf::const_iterator& MultiBuf::const_iterator::operator+=(size_t advance) {
320 if (advance == 0) {
321 return *this;
322 }
323
324 while (chunk_ != nullptr && advance >= (chunk_->size() - byte_index_)) {
325 advance -= (chunk_->size() - byte_index_);
326 chunk_ = chunk_->next_in_buf_;
327 byte_index_ = 0;
328 }
329
330 PW_DCHECK(chunk_ != nullptr || advance == 0u,
331 "Iterated past the end of the MultiBuf");
332 byte_index_ += advance;
333 return *this;
334 }
335
back() const336 const Chunk& MultiBufChunks::back() const {
337 Chunk* current = first_;
338 while (current->next_in_buf_ != nullptr) {
339 current = current->next_in_buf_;
340 }
341 return *current;
342 }
343
344 } // namespace pw::multibuf
345