blob: 9e4370086774bd63ccbf97142bbde0ecf917b2d2 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "services/network/web_bundle/web_bundle_chunked_buffer.h"
#include <algorithm>
#include "base/check.h"
#include "base/memory/ptr_util.h"
#include "base/not_fatal_until.h"
#include "base/numerics/checked_math.h"
namespace network {
namespace {
class ChunkedBufferDataSource : public mojo::DataPipeProducer::DataSource {
public:
ChunkedBufferDataSource(std::unique_ptr<const WebBundleChunkedBuffer> 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<char> buffer) override {
ReadResult result;
if (offset >= length_) {
result.result = MOJO_RESULT_OUT_OF_RANGE;
return result;
}
uint64_t read_start = offset_ + offset;
uint64_t len = std::min<uint64_t>(length_ - offset, buffer.size());
result.bytes_read = buffer_->ReadData(
read_start,
base::as_writable_bytes(buffer).first(base::checked_cast<size_t>(len)));
return result;
}
private:
const std::unique_ptr<const WebBundleChunkedBuffer> buffer_;
const uint64_t offset_;
const uint64_t length_;
};
} // namespace
WebBundleChunkedBuffer::Chunk::Chunk(
uint64_t start_pos,
scoped_refptr<const base::RefCountedBytes> bytes)
: start_pos_(start_pos), bytes_(std::move(bytes)) {
DCHECK(bytes_);
DCHECK(bytes_->size() != 0);
CHECK(base::CheckAdd<uint64_t>(start_pos_, bytes_->size()).IsValid());
}
WebBundleChunkedBuffer::Chunk::~Chunk() = default;
WebBundleChunkedBuffer::Chunk::Chunk(const WebBundleChunkedBuffer::Chunk&) =
default;
WebBundleChunkedBuffer::Chunk::Chunk(WebBundleChunkedBuffer::Chunk&&) = default;
WebBundleChunkedBuffer::WebBundleChunkedBuffer() = default;
WebBundleChunkedBuffer::WebBundleChunkedBuffer(ChunkVector chunks)
: chunks_(std::move(chunks)) {}
WebBundleChunkedBuffer::~WebBundleChunkedBuffer() = default;
void WebBundleChunkedBuffer::Append(base::span<const uint8_t> data) {
if (data.empty()) {
return;
}
auto bytes = base::MakeRefCounted<base::RefCountedBytes>(data);
chunks_.emplace_back(end_pos(), std::move(bytes));
}
bool WebBundleChunkedBuffer::ContainsAll(uint64_t offset,
uint64_t length) const {
DCHECK(base::CheckAdd<uint64_t>(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<mojo::DataPipeProducer::DataSource>
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<ChunkedBufferDataSource>(
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<const WebBundleChunkedBuffer>
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,
base::span<uint8_t> out) const {
uint64_t length = GetAvailableLength(offset, out.size());
if (length == 0) {
return 0;
}
ChunkVector::const_iterator it = FindChunk(offset);
uint64_t written = 0;
while (length > written && it != chunks_.end()) {
auto it_span = base::span(*it);
uint64_t offset_in_chunk = offset + written - it->start_pos();
uint64_t length_in_chunk =
std::min(it->size() - offset_in_chunk, length - written);
size_t checked_offset = base::checked_cast<size_t>(offset_in_chunk);
size_t checked_length = base::checked_cast<size_t>(length_in_chunk);
out.copy_prefix_from(it_span.subspan(checked_offset, checked_length));
out = out.subspan(checked_length);
written += checked_length;
++it;
}
return written;
}
} // namespace network