|  | #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 |