blob: 9d44b13a6b7bc3365a6274c7bfdfa555e6b4c5ef [file] [log] [blame]
// Copyright 2018 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 "mojo/public/cpp/base/big_buffer.h"
#include "base/logging.h"
namespace mojo_base {
namespace {
// In the case of shared memory allocation failure, we still attempt to fall
// back onto inline bytes unless the buffer size exceeds a very large threshold,
// given by this constant.
constexpr size_t kMaxFallbackInlineBytes = 127 * 1024 * 1024;
} // namespace
namespace internal {
BigBufferSharedMemoryRegion::BigBufferSharedMemoryRegion() = default;
BigBufferSharedMemoryRegion::BigBufferSharedMemoryRegion(
mojo::ScopedSharedBufferHandle buffer_handle,
size_t size)
: size_(size),
buffer_handle_(std::move(buffer_handle)),
buffer_mapping_(buffer_handle_->Map(size)) {}
BigBufferSharedMemoryRegion::BigBufferSharedMemoryRegion(
BigBufferSharedMemoryRegion&& other) = default;
BigBufferSharedMemoryRegion::~BigBufferSharedMemoryRegion() = default;
BigBufferSharedMemoryRegion& BigBufferSharedMemoryRegion::operator=(
BigBufferSharedMemoryRegion&& other) = default;
mojo::ScopedSharedBufferHandle BigBufferSharedMemoryRegion::TakeBufferHandle() {
DCHECK(buffer_handle_.is_valid());
buffer_mapping_.reset();
return std::move(buffer_handle_);
}
} // namespace internal
namespace {
void TryCreateSharedMemory(
size_t size,
BigBuffer::StorageType* storage_type,
base::Optional<internal::BigBufferSharedMemoryRegion>* shared_memory) {
if (size > BigBuffer::kMaxInlineBytes) {
auto buffer = mojo::SharedBufferHandle::Create(size);
if (buffer.is_valid()) {
internal::BigBufferSharedMemoryRegion shm_region(std::move(buffer), size);
if (shm_region.memory()) {
*storage_type = BigBuffer::StorageType::kSharedMemory;
shared_memory->emplace(std::move(shm_region));
return;
}
}
if (size > kMaxFallbackInlineBytes) {
// The data is too large to even bother with inline fallback, so we
// instead produce an invalid buffer. This will always fail validation on
// the receiving end.
*storage_type = BigBuffer::StorageType::kInvalidBuffer;
return;
}
}
// We can use inline memory.
*storage_type = BigBuffer::StorageType::kBytes;
}
} // namespace
// static
constexpr size_t BigBuffer::kMaxInlineBytes;
BigBuffer::BigBuffer() : storage_type_(StorageType::kBytes), bytes_size_(0) {}
BigBuffer::BigBuffer(BigBuffer&& other)
: storage_type_(other.storage_type_),
bytes_(std::move(other.bytes_)),
bytes_size_(other.bytes_size_),
shared_memory_(std::move(other.shared_memory_)) {
// Make sure |other| looks empty.
other.storage_type_ = StorageType::kInvalidBuffer;
other.bytes_size_ = 0;
}
BigBuffer::BigBuffer(base::span<const uint8_t> data) {
*this = BigBufferView::ToBigBuffer(BigBufferView(data));
}
BigBuffer::BigBuffer(const std::vector<uint8_t>& data)
: BigBuffer(base::make_span(data)) {}
BigBuffer::BigBuffer(internal::BigBufferSharedMemoryRegion shared_memory)
: storage_type_(StorageType::kSharedMemory),
shared_memory_(std::move(shared_memory)) {}
BigBuffer::BigBuffer(size_t size) {
TryCreateSharedMemory(size, &storage_type_, &shared_memory_);
if (storage_type_ == BigBuffer::StorageType::kBytes) {
// Either |size| is small enough or shared memory allocation failed, and
// fallback to inline allocation is feasible.
bytes_ = std::make_unique<uint8_t[]>(size);
bytes_size_ = size;
}
}
BigBuffer::~BigBuffer() = default;
BigBuffer& BigBuffer::operator=(BigBuffer&& other) {
storage_type_ = other.storage_type_;
bytes_ = std::move(other.bytes_);
bytes_size_ = other.bytes_size_;
shared_memory_ = std::move(other.shared_memory_);
// Make sure |other| looks empty.
other.storage_type_ = StorageType::kInvalidBuffer;
other.bytes_size_ = 0;
return *this;
}
uint8_t* BigBuffer::data() {
return const_cast<uint8_t*>(const_cast<const BigBuffer*>(this)->data());
}
const uint8_t* BigBuffer::data() const {
switch (storage_type_) {
case StorageType::kBytes:
return bytes_.get();
case StorageType::kSharedMemory:
DCHECK(shared_memory_->buffer_mapping_);
return static_cast<const uint8_t*>(
const_cast<const void*>(shared_memory_->buffer_mapping_.get()));
case StorageType::kInvalidBuffer:
// We return null here but do not assert unlike the default case. No
// consumer is allowed to dereference this when |size()| is zero anyway.
return nullptr;
default:
NOTREACHED();
return nullptr;
}
}
size_t BigBuffer::size() const {
switch (storage_type_) {
case StorageType::kBytes:
return bytes_size_;
case StorageType::kSharedMemory:
return shared_memory_->size();
case StorageType::kInvalidBuffer:
return 0;
default:
NOTREACHED();
return 0;
}
}
BigBufferView::BigBufferView() = default;
BigBufferView::BigBufferView(BigBufferView&& other) = default;
BigBufferView::BigBufferView(base::span<const uint8_t> bytes) {
TryCreateSharedMemory(bytes.size(), &storage_type_, &shared_memory_);
if (storage_type_ == BigBuffer::StorageType::kSharedMemory) {
DCHECK(shared_memory_->memory());
std::copy(bytes.begin(), bytes.end(),
static_cast<uint8_t*>(shared_memory_->memory()));
return;
}
if (storage_type_ == BigBuffer::StorageType::kBytes) {
// Either the data is small enough or shared memory allocation failed.
// Either way we fall back to directly referencing the input bytes.
bytes_ = bytes;
}
}
BigBufferView::~BigBufferView() = default;
BigBufferView& BigBufferView::operator=(BigBufferView&& other) = default;
void BigBufferView::SetBytes(base::span<const uint8_t> bytes) {
DCHECK(bytes_.empty());
DCHECK(!shared_memory_);
storage_type_ = BigBuffer::StorageType::kBytes;
bytes_ = bytes;
}
void BigBufferView::SetSharedMemory(
internal::BigBufferSharedMemoryRegion shared_memory) {
DCHECK(bytes_.empty());
DCHECK(!shared_memory_);
storage_type_ = BigBuffer::StorageType::kSharedMemory;
shared_memory_ = std::move(shared_memory);
}
base::span<const uint8_t> BigBufferView::data() const {
if (storage_type_ == BigBuffer::StorageType::kBytes) {
return bytes_;
} else if (storage_type_ == BigBuffer::StorageType::kSharedMemory) {
DCHECK(shared_memory_.has_value());
return base::make_span(static_cast<const uint8_t*>(const_cast<const void*>(
shared_memory_->memory())),
shared_memory_->size());
}
return base::span<const uint8_t>();
}
// static
BigBuffer BigBufferView::ToBigBuffer(BigBufferView view) {
BigBuffer buffer;
buffer.storage_type_ = view.storage_type_;
if (view.storage_type_ == BigBuffer::StorageType::kBytes) {
buffer.bytes_ = std::make_unique<uint8_t[]>(view.bytes_.size());
buffer.bytes_size_ = view.bytes_.size();
std::copy(view.bytes_.begin(), view.bytes_.end(), buffer.bytes_.get());
} else if (view.storage_type_ == BigBuffer::StorageType::kSharedMemory) {
buffer.shared_memory_ = std::move(*view.shared_memory_);
}
return buffer;
}
// static
BigBufferView BigBufferView::CreateInvalidForTest() {
BigBufferView view;
view.storage_type_ = BigBuffer::StorageType::kInvalidBuffer;
return view;
}
} // namespace mojo_base