| // Copyright (c) 2010 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <functional> |
| |
| #include "base/logging.h" |
| #include "net/base/io_buffer.h" |
| #include "remoting/base/compound_buffer.h" |
| |
| namespace remoting { |
| |
| CompoundBuffer::DataChunk::DataChunk( |
| net::IOBuffer* buffer_value, const char* start_value, int size_value) |
| : buffer(buffer_value), |
| start(start_value), |
| size(size_value) { |
| } |
| |
| CompoundBuffer::DataChunk::~DataChunk() {} |
| |
| CompoundBuffer::CompoundBuffer() |
| : total_bytes_(0), |
| locked_(false) { |
| } |
| |
| CompoundBuffer::~CompoundBuffer() { |
| } |
| |
| void CompoundBuffer::Clear() { |
| CHECK(!locked_); |
| chunks_.clear(); |
| total_bytes_ = 0; |
| } |
| |
| void CompoundBuffer::Append(net::IOBuffer* buffer, |
| const char* start, int size) { |
| // A weak check that the |start| is within |buffer|. |
| DCHECK_GE(start, buffer->data()); |
| DCHECK_GT(size, 0); |
| |
| CHECK(!locked_); |
| |
| chunks_.push_back(DataChunk(buffer, start, size)); |
| total_bytes_ += size; |
| } |
| |
| void CompoundBuffer::Append(net::IOBuffer* buffer, int size) { |
| Append(buffer, buffer->data(), size); |
| } |
| |
| void CompoundBuffer::Append(const CompoundBuffer& buffer) { |
| for (DataChunkList::const_iterator it = buffer.chunks_.begin(); |
| it != buffer.chunks_.end(); ++it) { |
| Append(it->buffer.get(), it->start, it->size); |
| } |
| } |
| |
| void CompoundBuffer::Prepend(net::IOBuffer* buffer, |
| const char* start, int size) { |
| // A weak check that the |start| is within |buffer|. |
| DCHECK_GE(start, buffer->data()); |
| DCHECK_GT(size, 0); |
| |
| CHECK(!locked_); |
| |
| chunks_.push_front(DataChunk(buffer, start, size)); |
| total_bytes_ += size; |
| } |
| |
| void CompoundBuffer::Prepend(net::IOBuffer* buffer, int size) { |
| Prepend(buffer, buffer->data(), size); |
| } |
| |
| void CompoundBuffer::Prepend(const CompoundBuffer& buffer) { |
| for (DataChunkList::const_iterator it = buffer.chunks_.begin(); |
| it != buffer.chunks_.end(); ++it) { |
| Prepend(it->buffer.get(), it->start, it->size); |
| } |
| } |
| void CompoundBuffer::AppendCopyOf(const char* data, int size) { |
| net::IOBuffer* buffer = new net::IOBuffer(size); |
| memcpy(buffer->data(), data, size); |
| Append(buffer, size); |
| } |
| |
| void CompoundBuffer::PrependCopyOf(const char* data, int size) { |
| net::IOBuffer* buffer = new net::IOBuffer(size); |
| memcpy(buffer->data(), data, size); |
| Prepend(buffer, size); |
| } |
| |
| void CompoundBuffer::CropFront(int bytes) { |
| CHECK(!locked_); |
| |
| if (total_bytes_ <= bytes) { |
| Clear(); |
| return; |
| } |
| |
| total_bytes_ -= bytes; |
| while (!chunks_.empty() && chunks_.front().size <= bytes) { |
| bytes -= chunks_.front().size; |
| chunks_.pop_front(); |
| } |
| if (!chunks_.empty() && bytes > 0) { |
| chunks_.front().start += bytes; |
| chunks_.front().size -= bytes; |
| DCHECK_GT(chunks_.front().size, 0); |
| bytes = 0; |
| } |
| DCHECK_EQ(bytes, 0); |
| } |
| |
| void CompoundBuffer::CropBack(int bytes) { |
| CHECK(!locked_); |
| |
| if (total_bytes_ <= bytes) { |
| Clear(); |
| return; |
| } |
| |
| total_bytes_ -= bytes; |
| while (!chunks_.empty() && chunks_.back().size <= bytes) { |
| bytes -= chunks_.back().size; |
| chunks_.pop_back(); |
| } |
| if (!chunks_.empty() && bytes > 0) { |
| chunks_.back().size -= bytes; |
| DCHECK_GT(chunks_.back().size, 0); |
| bytes = 0; |
| } |
| DCHECK_EQ(bytes, 0); |
| } |
| |
| void CompoundBuffer::Lock() { |
| locked_ = true; |
| } |
| |
| net::IOBufferWithSize* CompoundBuffer::ToIOBufferWithSize() const { |
| net::IOBufferWithSize* result = new net::IOBufferWithSize(total_bytes_); |
| CopyTo(result->data(), total_bytes_); |
| return result; |
| } |
| |
| void CompoundBuffer::CopyTo(char* data, int size) const { |
| char* pos = data; |
| for (DataChunkList::const_iterator it = chunks_.begin(); |
| it != chunks_.end(); ++it) { |
| CHECK_LE(pos + it->size, data + size); |
| memcpy(pos, it->start, it->size); |
| pos += it->size; |
| } |
| } |
| |
| void CompoundBuffer::CopyFrom(const CompoundBuffer& source, |
| int start, int end) { |
| // Check that 0 <= |start| <= |end| <= |total_bytes_|. |
| DCHECK_LE(0, start); |
| DCHECK_LE(start, end); |
| DCHECK_LE(end, source.total_bytes()); |
| |
| Clear(); |
| |
| if (end == start) { |
| return; |
| } |
| |
| // Iterate over chunks in the |source| and add those that we need. |
| int pos = 0; |
| for (DataChunkList::const_iterator it = source.chunks_.begin(); |
| it != source.chunks_.end(); ++it) { |
| |
| // Add data from the current chunk only if it is in the specified interval. |
| if (pos + it->size > start && pos < end) { |
| int relative_start = std::max(0, start - pos); |
| int relative_end = std::min(it->size, end - pos); |
| DCHECK_LE(0, relative_start); |
| DCHECK_LT(relative_start, relative_end); |
| DCHECK_LE(relative_end, it->size); |
| Append(it->buffer.get(), it->start + relative_start, |
| relative_end - relative_start); |
| } |
| |
| pos += it->size; |
| if (pos >= end) { |
| // We've got all the data we need. |
| break; |
| } |
| } |
| |
| DCHECK_EQ(total_bytes_, end - start); |
| } |
| |
| CompoundBufferInputStream::CompoundBufferInputStream( |
| const CompoundBuffer* buffer) |
| : buffer_(buffer), |
| current_chunk_(0), |
| current_chunk_position_(0), |
| position_(0), |
| last_returned_size_(0) { |
| DCHECK(buffer_->locked()); |
| } |
| |
| CompoundBufferInputStream::~CompoundBufferInputStream() { |
| } |
| |
| bool CompoundBufferInputStream::Next(const void** data, int* size) { |
| if (current_chunk_ < buffer_->chunks_.size()) { |
| // Reply with the number of bytes remaining in the current buffer. |
| const CompoundBuffer::DataChunk& chunk = buffer_->chunks_[current_chunk_]; |
| int read_size = chunk.size - current_chunk_position_; |
| *data = chunk.start + current_chunk_position_; |
| *size = read_size; |
| |
| // Adjust position. |
| ++current_chunk_; |
| current_chunk_position_ = 0; |
| position_ += read_size; |
| |
| last_returned_size_ = read_size; |
| return true; |
| } |
| |
| DCHECK_EQ(position_, buffer_->total_bytes()); |
| |
| // We've reached the end of the stream. So reset |last_returned_size_| |
| // to zero to prevent any backup request. |
| // This is the same as in ArrayInputStream. |
| // See google/protobuf/io/zero_copy_stream_impl_lite.cc. |
| last_returned_size_ = 0; |
| return false; |
| } |
| |
| void CompoundBufferInputStream::BackUp(int count) { |
| DCHECK_LE(count, last_returned_size_); |
| DCHECK_GT(current_chunk_, 0u); |
| |
| // Rewind one buffer and rewind data offset by |count| bytes. |
| --current_chunk_; |
| const CompoundBuffer::DataChunk& chunk = buffer_->chunks_[current_chunk_]; |
| current_chunk_position_ = chunk.size - count; |
| position_ -= count; |
| DCHECK_GE(position_, 0); |
| |
| // Prevent additional backups. |
| last_returned_size_ = 0; |
| } |
| |
| bool CompoundBufferInputStream::Skip(int count) { |
| DCHECK_GE(count, 0); |
| last_returned_size_ = 0; |
| |
| while (count > 0 && current_chunk_ < buffer_->chunks_.size()) { |
| const CompoundBuffer::DataChunk& chunk = buffer_->chunks_[current_chunk_]; |
| int read = std::min(count, chunk.size - current_chunk_position_); |
| |
| // Advance the current buffer offset and position. |
| current_chunk_position_ += read; |
| position_ += read; |
| count -= read; |
| |
| // If the current buffer is fully read, then advance to the next buffer. |
| if (current_chunk_position_ == chunk.size) { |
| ++current_chunk_; |
| current_chunk_position_ = 0; |
| } |
| } |
| |
| return count == 0; |
| } |
| |
| int64 CompoundBufferInputStream::ByteCount() const { |
| return position_; |
| } |
| |
| } // namespace remoting |