blob: 151077970cab95cf3b70bd77edb28cc66c202a15 [file] [log] [blame]
// Copyright 2023 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_multibuf/multibuf.h"
#include <algorithm>
#include "pw_assert/check.h"
namespace pw::multibuf {
void MultiBuf::Release() noexcept {
while (first_ != nullptr) {
Chunk* removed = first_;
first_ = first_->next_in_buf_;
removed->Free();
}
}
size_t MultiBuf::size() const {
size_t len = 0;
for (const auto& chunk : Chunks()) {
len += chunk.size();
}
return len;
}
bool MultiBuf::empty() const {
return std::all_of(Chunks().begin(), Chunks().end(), [](const Chunk& c) {
return c.empty();
});
}
bool MultiBuf::ClaimPrefix(size_t bytes_to_claim) {
if (first_ == nullptr) {
return false;
}
return first_->ClaimPrefix(bytes_to_claim);
}
bool MultiBuf::ClaimSuffix(size_t bytes_to_claim) {
if (first_ == nullptr) {
return false;
}
return Chunks().back().ClaimSuffix(bytes_to_claim);
}
void MultiBuf::DiscardPrefix(size_t bytes_to_discard) {
PW_DCHECK(bytes_to_discard <= size());
while (bytes_to_discard != 0) {
if (ChunkBegin()->size() > bytes_to_discard) {
ChunkBegin()->DiscardPrefix(bytes_to_discard);
return;
}
OwnedChunk front_chunk = TakeFrontChunk();
bytes_to_discard -= front_chunk.size();
}
}
void MultiBuf::Slice(size_t begin, size_t end) {
PW_DCHECK(end >= begin);
DiscardPrefix(begin);
Truncate(end - begin);
}
void MultiBuf::Truncate(size_t len) {
PW_DCHECK(len <= size());
if (len == 0) {
Release();
}
Chunk* new_last_chunk = first_;
size_t len_from_chunk_start = len;
while (len_from_chunk_start > new_last_chunk->size()) {
len_from_chunk_start -= new_last_chunk->size();
new_last_chunk = new_last_chunk->next_in_buf_;
}
new_last_chunk->Truncate(len_from_chunk_start);
Chunk* remainder = new_last_chunk->next_in_buf_;
new_last_chunk->next_in_buf_ = nullptr;
MultiBuf discard;
discard.first_ = remainder;
}
void MultiBuf::PushPrefix(MultiBuf&& front) {
front.PushSuffix(std::move(*this));
*this = std::move(front);
}
void MultiBuf::PushSuffix(MultiBuf&& tail) {
if (first_ == nullptr) {
first_ = tail.first_;
tail.first_ = nullptr;
return;
}
Chunks().back().next_in_buf_ = tail.first_;
tail.first_ = nullptr;
}
std::optional<MultiBuf> MultiBuf::TakePrefix(size_t bytes_to_take) {
PW_DCHECK(bytes_to_take <= size());
MultiBuf front;
if (bytes_to_take == 0) {
return front;
}
// Pointer to the last element of `front`, allowing constant-time appending.
Chunk* last_front_chunk = nullptr;
while (bytes_to_take > ChunkBegin()->size()) {
OwnedChunk new_chunk = TakeFrontChunk().Take();
Chunk* new_chunk_ptr = &*new_chunk;
bytes_to_take -= new_chunk.size();
if (last_front_chunk == nullptr) {
front.PushFrontChunk(std::move(new_chunk));
} else {
last_front_chunk->next_in_buf_ = std::move(new_chunk).Take();
}
last_front_chunk = new_chunk_ptr;
}
if (bytes_to_take == 0) {
return front;
}
std::optional<OwnedChunk> last_front_bit = first_->TakePrefix(bytes_to_take);
if (last_front_bit.has_value()) {
if (last_front_chunk != nullptr) {
Chunk* last_front_bit_ptr = std::move(*last_front_bit).Take();
last_front_chunk->next_in_buf_ = last_front_bit_ptr;
} else {
front.PushFrontChunk(std::move(*last_front_bit));
}
return front;
}
// The front chunk could not be split, so we must put the front back on.
PushPrefix(std::move(front));
return std::nullopt;
}
std::optional<MultiBuf> MultiBuf::TakeSuffix(size_t bytes_to_take) {
size_t front_size = size() - bytes_to_take;
std::optional<MultiBuf> front_opt = TakePrefix(front_size);
if (!front_opt.has_value()) {
return std::nullopt;
}
MultiBuf front_then_back = std::move(*front_opt);
std::swap(front_then_back, *this);
return front_then_back;
}
void MultiBuf::PushFrontChunk(OwnedChunk&& chunk) {
PW_DCHECK(chunk->next_in_buf_ == nullptr);
Chunk* new_chunk = std::move(chunk).Take();
Chunk* old_first = first_;
new_chunk->next_in_buf_ = old_first;
first_ = new_chunk;
}
void MultiBuf::PushBackChunk(OwnedChunk&& chunk) {
PW_DCHECK(chunk->next_in_buf_ == nullptr);
Chunk* new_chunk = std::move(chunk).Take();
if (first_ == nullptr) {
first_ = new_chunk;
return;
}
Chunk* cur = first_;
while (cur->next_in_buf_ != nullptr) {
cur = cur->next_in_buf_;
}
cur->next_in_buf_ = new_chunk;
}
MultiBuf::ChunkIterator MultiBuf::InsertChunk(ChunkIterator position,
OwnedChunk&& chunk) {
// Note: this also catches the cases where ``first_ == nullptr``
PW_DCHECK(chunk->next_in_buf_ == nullptr);
if (position == ChunkBegin()) {
PushFrontChunk(std::move(chunk));
return ChunkIterator(first_);
}
Chunk* previous = Previous(position.chunk_);
Chunk* old_next = previous->next_in_buf_;
Chunk* new_chunk = std::move(chunk).Take();
new_chunk->next_in_buf_ = old_next;
previous->next_in_buf_ = new_chunk;
return ChunkIterator(new_chunk);
}
OwnedChunk MultiBuf::TakeFrontChunk() {
Chunk* old_first = first_;
first_ = old_first->next_in_buf_;
old_first->next_in_buf_ = nullptr;
return OwnedChunk(old_first);
}
std::tuple<MultiBuf::ChunkIterator, OwnedChunk> MultiBuf::TakeChunk(
ChunkIterator position) {
Chunk* chunk = position.chunk_;
if (position == ChunkBegin()) {
OwnedChunk old_first = TakeFrontChunk();
return std::make_tuple(ChunkIterator(first_), std::move(old_first));
}
Chunk* previous = Previous(chunk);
previous->next_in_buf_ = chunk->next_in_buf_;
chunk->next_in_buf_ = nullptr;
return std::make_tuple(ChunkIterator(previous->next_in_buf_),
OwnedChunk(chunk));
}
Chunk* MultiBuf::Previous(Chunk* chunk) const {
Chunk* previous = first_;
while (previous != nullptr && previous->next_in_buf_ != chunk) {
previous = previous->next_in_buf_;
}
return previous;
}
MultiBuf::const_iterator& MultiBuf::const_iterator::operator++() {
if (byte_index_ + 1 == chunk_->size()) {
chunk_ = chunk_->next_in_buf_;
byte_index_ = 0;
AdvanceToData();
} else {
++byte_index_;
}
return *this;
}
MultiBuf::const_iterator& MultiBuf::const_iterator::operator+=(size_t advance) {
if (advance == 0) {
return *this;
}
while (chunk_ != nullptr && advance >= (chunk_->size() - byte_index_)) {
advance -= (chunk_->size() - byte_index_);
chunk_ = chunk_->next_in_buf_;
byte_index_ = 0;
}
byte_index_ += advance;
return *this;
}
void MultiBuf::const_iterator::AdvanceToData() {
while (chunk_ != nullptr && chunk_->empty()) {
chunk_ = chunk_->next_in_buf_;
}
}
Chunk& MultiBuf::ChunkIterable::back() {
return const_cast<Chunk&>(std::as_const(*this).back());
}
const Chunk& MultiBuf::ChunkIterable::back() const {
Chunk* current = first_;
while (current->next_in_buf_ != nullptr) {
current = current->next_in_buf_;
}
return *current;
}
size_t MultiBuf::ChunkIterable::size() const {
Chunk* current = first_;
size_t i = 0;
while (current != nullptr) {
++i;
current = current->next_in_buf_;
}
return i;
}
} // namespace pw::multibuf