xref: /aosp_15_r20/external/pigweed/pw_multibuf/multibuf.cc (revision 61c4878ac05f98d0ceed94b57d316916de578985)
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