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