// Copyright 2021 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 "components/web_package/web_bundle_chunked_buffer.h" #include #include "base/check.h" #include "base/memory/ptr_util.h" #include "base/numerics/checked_math.h" namespace web_package { namespace { class ChunkedBufferDataSource : public mojo::DataPipeProducer::DataSource { public: ChunkedBufferDataSource(std::unique_ptr buffer, uint64_t offset, uint64_t length) : buffer_(std::move(buffer)), offset_(offset), length_(length) { DCHECK(buffer_); } ~ChunkedBufferDataSource() override = default; // Disallow copy and assign. ChunkedBufferDataSource(const ChunkedBufferDataSource&) = delete; ChunkedBufferDataSource& operator=(const ChunkedBufferDataSource&) = delete; uint64_t GetLength() const override { return length_; } ReadResult Read(uint64_t offset, base::span buffer) override { ReadResult result; if (offset >= length_) { result.result = MOJO_RESULT_OUT_OF_RANGE; return result; } uint64_t read_start = offset_ + offset; uint64_t out_size = buffer.size(); uint64_t len = std::min(length_ - offset, out_size); buffer_->ReadData(read_start, len, reinterpret_cast(buffer.data())); result.bytes_read = len; return result; } private: const std::unique_ptr buffer_; const uint64_t offset_; const uint64_t length_; }; } // namespace WebBundleChunkedBuffer::Chunk::Chunk( uint64_t start_pos, scoped_refptr bytes) : start_pos_(start_pos), bytes_(std::move(bytes)) { DCHECK(bytes_); DCHECK(bytes_->size() != 0); DCHECK(base::CheckAdd(start_pos_, bytes_->size()).IsValid()); } WebBundleChunkedBuffer::Chunk::~Chunk() = default; WebBundleChunkedBuffer::Chunk::Chunk(const WebBundleChunkedBuffer::Chunk&) = default; WebBundleChunkedBuffer::Chunk::Chunk(WebBundleChunkedBuffer::Chunk&&) = default; uint64_t WebBundleChunkedBuffer::Chunk::start_pos() const { return start_pos_; } uint64_t WebBundleChunkedBuffer::Chunk::end_pos() const { return start_pos_ + bytes_->size(); } uint64_t WebBundleChunkedBuffer::Chunk::size() const { return bytes_->size(); } const uint8_t* WebBundleChunkedBuffer::Chunk::data() const { return bytes_->data().data(); } WebBundleChunkedBuffer::WebBundleChunkedBuffer() = default; WebBundleChunkedBuffer::WebBundleChunkedBuffer(ChunkVector chunks) : chunks_(std::move(chunks)) {} WebBundleChunkedBuffer::~WebBundleChunkedBuffer() = default; void WebBundleChunkedBuffer::Append(const uint8_t* data, size_t num_bytes) { DCHECK(base::CheckAdd(end_pos(), num_bytes).IsValid()); if (num_bytes == 0) return; auto bytes = base::MakeRefCounted(data, num_bytes); chunks_.push_back(Chunk(end_pos(), std::move(bytes))); } bool WebBundleChunkedBuffer::ContainsAll(uint64_t offset, uint64_t length) const { DCHECK(base::CheckAdd(offset, length).IsValid()); if (length == 0) return true; if (offset < start_pos()) return false; if (offset + length > end_pos()) return false; return true; } std::unique_ptr WebBundleChunkedBuffer::CreateDataSource(uint64_t offset, uint64_t max_length) const { uint64_t length = GetAvailableLength(offset, max_length); if (length == 0) return nullptr; return std::make_unique( CreatePartialBuffer(offset, length), offset, length); } uint64_t WebBundleChunkedBuffer::size() const { DCHECK_GE(end_pos(), start_pos()); return end_pos() - start_pos(); } WebBundleChunkedBuffer::ChunkVector::const_iterator WebBundleChunkedBuffer::FindChunk(uint64_t pos) const { if (empty()) return chunks_.end(); // |pos| ls before everything if (pos < chunks_.begin()->start_pos()) return chunks_.end(); // As an optimization, check the last region first if (chunks_.back().start_pos() <= pos) { if (chunks_.back().end_pos() <= pos) return chunks_.end(); return chunks_.end() - 1; } // Binary search return std::partition_point( chunks_.begin(), chunks_.end(), [pos](const Chunk& chunk) { return chunk.end_pos() <= pos; }); } std::unique_ptr WebBundleChunkedBuffer::CreatePartialBuffer(uint64_t offset, uint64_t length) const { DCHECK(ContainsAll(offset, length)); ChunkVector::const_iterator it = FindChunk(offset); CHECK(it != chunks_.end()); ChunkVector new_chunks; while (it != chunks_.end() && it->start_pos() < offset + length) { new_chunks.push_back(*it); ++it; } return base::WrapUnique(new WebBundleChunkedBuffer(std::move(new_chunks))); } bool WebBundleChunkedBuffer::empty() const { return chunks_.empty(); } uint64_t WebBundleChunkedBuffer::start_pos() const { if (empty()) return 0; return chunks_.front().start_pos(); } uint64_t WebBundleChunkedBuffer::end_pos() const { if (empty()) return 0; return chunks_.back().end_pos(); } uint64_t WebBundleChunkedBuffer::GetAvailableLength(uint64_t offset, uint64_t max_length) const { if (offset < start_pos()) return 0; if (end_pos() <= offset) return 0; return std::min(max_length, end_pos() - offset); } uint64_t WebBundleChunkedBuffer::ReadData(uint64_t offset, uint64_t max_length, uint8_t* out) const { uint64_t length = GetAvailableLength(offset, max_length); if (length == 0) return 0; ChunkVector::const_iterator it = FindChunk(offset); DCHECK(it != chunks_.end()); uint64_t written = 0; while (length > written) { DCHECK(it != chunks_.end()); uint64_t offset_in_chunk = offset + written - it->start_pos(); uint64_t length_in_chunk = std::min(it->size() - offset_in_chunk, length - written); memcpy(out + written, it->data() + offset_in_chunk, length_in_chunk); written += length_in_chunk; ++it; } return written; } } // namespace web_package