| #include "quiche/http2/adapter/chunked_buffer.h" |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| 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 |