blob: f941042d3a5d21b499c79099c234816f225760dc [file] [log] [blame]
#include "quiche/http2/adapter/chunked_buffer.h"
#include <algorithm>
namespace http2 {
namespace adapter {
namespace {
constexpr size_t kKilobyte = 1024;
size_t RoundUpToNearestKilobyte(size_t n) {
// The way to think of this bit math is: it fills in all of the least
// significant bits less than 1024, then adds one. This guarantees that all of
// those bits end up as 0, hence rounding up to a multiple of 1024.
return ((n - 1) | (kKilobyte - 1)) + 1;
}
} // namespace
void ChunkedBuffer::Append(absl::string_view data) {
// Appends the data by copying it.
const size_t to_copy = std::min(TailBytesFree(), data.size());
if (to_copy > 0) {
chunks_.back().AppendSuffix(data.substr(0, to_copy));
data.remove_prefix(to_copy);
}
EnsureTailBytesFree(data.size());
chunks_.back().AppendSuffix(data);
}
void ChunkedBuffer::Append(std::unique_ptr<char[]> data, size_t size) {
if (TailBytesFree() >= size) {
// Copies the data into the existing last chunk, since it will fit.
Chunk& c = chunks_.back();
c.AppendSuffix(absl::string_view(data.get(), size));
return;
}
while (!chunks_.empty() && chunks_.front().Empty()) {
chunks_.pop_front();
}
// Appends the memory to the end of the deque, since it won't fit in an
// existing chunk.
absl::string_view v = {data.get(), size};
chunks_.push_back({std::move(data), size, v});
}
absl::string_view ChunkedBuffer::GetPrefix() const {
if (chunks_.empty()) {
return "";
}
return chunks_.front().live;
}
std::vector<absl::string_view> ChunkedBuffer::Read() const {
std::vector<absl::string_view> result;
result.reserve(chunks_.size());
for (const Chunk& c : chunks_) {
result.push_back(c.live);
}
return result;
}
void ChunkedBuffer::RemovePrefix(size_t n) {
while (!Empty() && n > 0) {
Chunk& c = chunks_.front();
const size_t to_remove = std::min(n, c.live.size());
c.RemovePrefix(to_remove);
n -= to_remove;
if (c.Empty()) {
TrimFirstChunk();
}
}
}
bool ChunkedBuffer::Empty() const {
return chunks_.empty() ||
(chunks_.size() == 1 && chunks_.front().live.empty());
}
void ChunkedBuffer::Chunk::RemovePrefix(size_t n) {
QUICHE_DCHECK_GE(live.size(), n);
live.remove_prefix(n);
}
void ChunkedBuffer::Chunk::AppendSuffix(absl::string_view to_append) {
QUICHE_DCHECK_GE(TailBytesFree(), to_append.size());
if (live.empty()) {
std::copy(to_append.begin(), to_append.end(), data.get());
// Live needs to be initialized, since it points to nullptr.
live = absl::string_view(data.get(), to_append.size());
} else {
std::copy(to_append.begin(), to_append.end(),
const_cast<char*>(live.data()) + live.size());
// Live can be extended, since it already points to valid data.
live = absl::string_view(live.data(), live.size() + to_append.size());
}
}
size_t ChunkedBuffer::TailBytesFree() const {
if (chunks_.empty()) {
return 0;
}
return chunks_.back().TailBytesFree();
}
void ChunkedBuffer::EnsureTailBytesFree(size_t n) {
if (TailBytesFree() >= n) {
return;
}
const size_t to_allocate = RoundUpToNearestKilobyte(n);
auto data = std::unique_ptr<char[]>(new char[to_allocate]);
chunks_.push_back({std::move(data), to_allocate, ""});
}
void ChunkedBuffer::TrimFirstChunk() {
// Leave the first chunk, if it's the only one and already the default size.
if (chunks_.empty() ||
(chunks_.size() == 1 && chunks_.front().size == kDefaultChunkSize)) {
return;
}
chunks_.pop_front();
}
} // namespace adapter
} // namespace http2