• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
19 #include "pw_assert/check.h"
20 
21 namespace pw::multibuf {
22 
Release()23 void MultiBuf::Release() noexcept {
24   while (first_ != nullptr) {
25     Chunk* removed = first_;
26     first_ = first_->next_in_buf_;
27     removed->Free();
28   }
29 }
30 
size() const31 size_t MultiBuf::size() const {
32   size_t len = 0;
33   for (const auto& chunk : Chunks()) {
34     len += chunk.size();
35   }
36   return len;
37 }
38 
empty() const39 bool MultiBuf::empty() const {
40   return std::all_of(Chunks().begin(), Chunks().end(), [](const Chunk& c) {
41     return c.empty();
42   });
43 }
44 
ClaimPrefix(size_t bytes_to_claim)45 bool MultiBuf::ClaimPrefix(size_t bytes_to_claim) {
46   if (first_ == nullptr) {
47     return false;
48   }
49   return first_->ClaimPrefix(bytes_to_claim);
50 }
51 
ClaimSuffix(size_t bytes_to_claim)52 bool MultiBuf::ClaimSuffix(size_t bytes_to_claim) {
53   if (first_ == nullptr) {
54     return false;
55   }
56   return Chunks().back().ClaimSuffix(bytes_to_claim);
57 }
58 
DiscardPrefix(size_t bytes_to_discard)59 void MultiBuf::DiscardPrefix(size_t bytes_to_discard) {
60   PW_DCHECK(bytes_to_discard <= size());
61   while (bytes_to_discard != 0) {
62     if (ChunkBegin()->size() > bytes_to_discard) {
63       ChunkBegin()->DiscardPrefix(bytes_to_discard);
64       return;
65     }
66     OwnedChunk front_chunk = TakeFrontChunk();
67     bytes_to_discard -= front_chunk.size();
68   }
69 }
70 
Slice(size_t begin,size_t end)71 void MultiBuf::Slice(size_t begin, size_t end) {
72   PW_DCHECK(end >= begin);
73   DiscardPrefix(begin);
74   Truncate(end - begin);
75 }
76 
Truncate(size_t len)77 void MultiBuf::Truncate(size_t len) {
78   PW_DCHECK(len <= size());
79   if (len == 0) {
80     Release();
81     return;
82   }
83   Chunk* new_last_chunk = first_;
84   size_t len_from_chunk_start = len;
85   while (len_from_chunk_start > new_last_chunk->size()) {
86     len_from_chunk_start -= new_last_chunk->size();
87     new_last_chunk = new_last_chunk->next_in_buf_;
88   }
89   new_last_chunk->Truncate(len_from_chunk_start);
90   Chunk* remainder = new_last_chunk->next_in_buf_;
91   new_last_chunk->next_in_buf_ = nullptr;
92   MultiBuf discard;
93   discard.first_ = remainder;
94 }
95 
PushPrefix(MultiBuf && front)96 void MultiBuf::PushPrefix(MultiBuf&& front) {
97   front.PushSuffix(std::move(*this));
98   *this = std::move(front);
99 }
100 
PushSuffix(MultiBuf && tail)101 void MultiBuf::PushSuffix(MultiBuf&& tail) {
102   if (first_ == nullptr) {
103     first_ = tail.first_;
104     tail.first_ = nullptr;
105     return;
106   }
107   Chunks().back().next_in_buf_ = tail.first_;
108   tail.first_ = nullptr;
109 }
110 
TakePrefix(size_t bytes_to_take)111 std::optional<MultiBuf> MultiBuf::TakePrefix(size_t bytes_to_take) {
112   PW_DCHECK(bytes_to_take <= size());
113   MultiBuf front;
114   if (bytes_to_take == 0) {
115     return front;
116   }
117   // Pointer to the last element of `front`, allowing constant-time appending.
118   Chunk* last_front_chunk = nullptr;
119   while (bytes_to_take > ChunkBegin()->size()) {
120     OwnedChunk new_chunk = TakeFrontChunk().Take();
121     Chunk* new_chunk_ptr = &*new_chunk;
122     bytes_to_take -= new_chunk.size();
123     if (last_front_chunk == nullptr) {
124       front.PushFrontChunk(std::move(new_chunk));
125     } else {
126       last_front_chunk->next_in_buf_ = std::move(new_chunk).Take();
127     }
128     last_front_chunk = new_chunk_ptr;
129   }
130   if (bytes_to_take == 0) {
131     return front;
132   }
133   std::optional<OwnedChunk> last_front_bit = first_->TakePrefix(bytes_to_take);
134   if (last_front_bit.has_value()) {
135     if (last_front_chunk != nullptr) {
136       Chunk* last_front_bit_ptr = std::move(*last_front_bit).Take();
137       last_front_chunk->next_in_buf_ = last_front_bit_ptr;
138     } else {
139       front.PushFrontChunk(std::move(*last_front_bit));
140     }
141     return front;
142   }
143   // The front chunk could not be split, so we must put the front back on.
144   PushPrefix(std::move(front));
145   return std::nullopt;
146 }
147 
TakeSuffix(size_t bytes_to_take)148 std::optional<MultiBuf> MultiBuf::TakeSuffix(size_t bytes_to_take) {
149   size_t front_size = size() - bytes_to_take;
150   std::optional<MultiBuf> front_opt = TakePrefix(front_size);
151   if (!front_opt.has_value()) {
152     return std::nullopt;
153   }
154   MultiBuf front_then_back = std::move(*front_opt);
155   std::swap(front_then_back, *this);
156   return front_then_back;
157 }
158 
PushFrontChunk(OwnedChunk && chunk)159 void MultiBuf::PushFrontChunk(OwnedChunk&& chunk) {
160   PW_DCHECK(chunk->next_in_buf_ == nullptr);
161   Chunk* new_chunk = std::move(chunk).Take();
162   Chunk* old_first = first_;
163   new_chunk->next_in_buf_ = old_first;
164   first_ = new_chunk;
165 }
166 
PushBackChunk(OwnedChunk && chunk)167 void MultiBuf::PushBackChunk(OwnedChunk&& chunk) {
168   PW_DCHECK(chunk->next_in_buf_ == nullptr);
169   Chunk* new_chunk = std::move(chunk).Take();
170   if (first_ == nullptr) {
171     first_ = new_chunk;
172     return;
173   }
174   Chunk* cur = first_;
175   while (cur->next_in_buf_ != nullptr) {
176     cur = cur->next_in_buf_;
177   }
178   cur->next_in_buf_ = new_chunk;
179 }
180 
InsertChunk(ChunkIterator position,OwnedChunk && chunk)181 MultiBuf::ChunkIterator MultiBuf::InsertChunk(ChunkIterator position,
182                                               OwnedChunk&& chunk) {
183   // Note: this also catches the cases where ``first_ == nullptr``
184   PW_DCHECK(chunk->next_in_buf_ == nullptr);
185   if (position == ChunkBegin()) {
186     PushFrontChunk(std::move(chunk));
187     return ChunkIterator(first_);
188   }
189   Chunk* previous = Previous(position.chunk_);
190   Chunk* old_next = previous->next_in_buf_;
191   Chunk* new_chunk = std::move(chunk).Take();
192   new_chunk->next_in_buf_ = old_next;
193   previous->next_in_buf_ = new_chunk;
194   return ChunkIterator(new_chunk);
195 }
196 
TakeFrontChunk()197 OwnedChunk MultiBuf::TakeFrontChunk() {
198   Chunk* old_first = first_;
199   first_ = old_first->next_in_buf_;
200   old_first->next_in_buf_ = nullptr;
201   return OwnedChunk(old_first);
202 }
203 
TakeChunk(ChunkIterator position)204 std::tuple<MultiBuf::ChunkIterator, OwnedChunk> MultiBuf::TakeChunk(
205     ChunkIterator position) {
206   Chunk* chunk = position.chunk_;
207   if (position == ChunkBegin()) {
208     OwnedChunk old_first = TakeFrontChunk();
209     return std::make_tuple(ChunkIterator(first_), std::move(old_first));
210   }
211   Chunk* previous = Previous(chunk);
212   previous->next_in_buf_ = chunk->next_in_buf_;
213   chunk->next_in_buf_ = nullptr;
214   return std::make_tuple(ChunkIterator(previous->next_in_buf_),
215                          OwnedChunk(chunk));
216 }
217 
Previous(Chunk * chunk) const218 Chunk* MultiBuf::Previous(Chunk* chunk) const {
219   Chunk* previous = first_;
220   while (previous != nullptr && previous->next_in_buf_ != chunk) {
221     previous = previous->next_in_buf_;
222   }
223   return previous;
224 }
225 
operator ++()226 MultiBuf::const_iterator& MultiBuf::const_iterator::operator++() {
227   if (byte_index_ + 1 == chunk_->size()) {
228     chunk_ = chunk_->next_in_buf_;
229     byte_index_ = 0;
230     AdvanceToData();
231   } else {
232     ++byte_index_;
233   }
234   return *this;
235 }
236 
operator +=(size_t advance)237 MultiBuf::const_iterator& MultiBuf::const_iterator::operator+=(size_t advance) {
238   if (advance == 0) {
239     return *this;
240   }
241   while (chunk_ != nullptr && advance >= (chunk_->size() - byte_index_)) {
242     advance -= (chunk_->size() - byte_index_);
243     chunk_ = chunk_->next_in_buf_;
244     byte_index_ = 0;
245   }
246   byte_index_ += advance;
247   return *this;
248 }
249 
back()250 Chunk& MultiBuf::ChunkIterable::back() {
251   return const_cast<Chunk&>(std::as_const(*this).back());
252 }
253 
back() const254 const Chunk& MultiBuf::ChunkIterable::back() const {
255   Chunk* current = first_;
256   while (current->next_in_buf_ != nullptr) {
257     current = current->next_in_buf_;
258   }
259   return *current;
260 }
261 
size() const262 size_t MultiBuf::ChunkIterable::size() const {
263   Chunk* current = first_;
264   size_t i = 0;
265   while (current != nullptr) {
266     ++i;
267     current = current->next_in_buf_;
268   }
269   return i;
270 }
271 
272 }  // namespace pw::multibuf
273