blob: a77d408ca8ffa34dd4ebea3e28b407520fe0cf5a [file] [log] [blame]
// Copyright 2015 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 <stddef.h>
#include <stdint.h>
#include <algorithm>
#include "base/numerics/safe_math.h"
#include "storage/browser/blob/blob_async_transport_request_builder.h"
#include "storage/common/blob_storage/blob_storage_constants.h"
namespace storage {
namespace {
bool IsBytes(DataElement::Type type) {
return type == DataElement::TYPE_BYTES ||
type == DataElement::TYPE_BYTES_DESCRIPTION;
}
// This is the general template that each strategy below implements. See the
// ForEachWithSegment method for a description of how these are called.
// class BlobSegmentVisitor {
// public:
// typedef ___ SizeType;
// void VisitBytesSegment(size_t element_index, uint64_t element_offset,
// size_t segment_index, uint64_t segment_offset,
// uint64_t size);
// void VisitNonBytesSegment(const DataElement& element, size_t element_idx);
// void Done();
// };
// This class handles the logic of how transported memory is going to be
// represented as storage in the browser. The main idea is that all the memory
// is now packed into file chunks, and the browser items will just reference
// the file with offsets and sizes.
class FileStorageStrategy {
public:
FileStorageStrategy(
std::vector<BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest>*
requests,
BlobDataBuilder* builder)
: requests(requests), builder(builder), current_item_index(0) {}
~FileStorageStrategy() {}
void VisitBytesSegment(size_t element_index,
uint64_t element_offset,
size_t segment_index,
uint64_t segment_offset,
uint64_t size) {
BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest request;
request.browser_item_index = current_item_index;
request.browser_item_offset = 0;
request.message.request_number = requests->size();
request.message.transport_strategy = IPCBlobItemRequestStrategy::FILE;
request.message.renderer_item_index = element_index;
request.message.renderer_item_offset = element_offset;
request.message.size = size;
request.message.handle_index = segment_index;
request.message.handle_offset = segment_offset;
requests->push_back(request);
builder->AppendFutureFile(segment_offset, size);
current_item_index++;
}
void VisitNonBytesSegment(const DataElement& element, size_t element_index) {
builder->AppendIPCDataElement(element);
current_item_index++;
}
void Done() {}
std::vector<BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest>*
requests;
BlobDataBuilder* builder;
size_t current_item_index;
};
// This class handles the logic of storing memory that is transported as
// consolidated shared memory.
class SharedMemoryStorageStrategy {
public:
SharedMemoryStorageStrategy(
size_t max_segment_size,
std::vector<BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest>*
requests,
BlobDataBuilder* builder)
: requests(requests),
max_segment_size(max_segment_size),
builder(builder),
current_item_size(0),
current_item_index(0) {}
~SharedMemoryStorageStrategy() {}
void VisitBytesSegment(size_t element_index,
uint64_t element_offset,
size_t segment_index,
uint64_t segment_offset,
uint64_t size) {
if (current_item_size + size > max_segment_size) {
builder->AppendFutureData(current_item_size);
current_item_index++;
current_item_size = 0;
}
BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest request;
request.browser_item_index = current_item_index;
request.browser_item_offset = current_item_size;
request.message.request_number = requests->size();
request.message.transport_strategy =
IPCBlobItemRequestStrategy::SHARED_MEMORY;
request.message.renderer_item_index = element_index;
request.message.renderer_item_offset = element_offset;
request.message.size = size;
request.message.handle_index = segment_index;
request.message.handle_offset = segment_offset;
requests->push_back(request);
current_item_size += size;
}
void VisitNonBytesSegment(const DataElement& element, size_t element_index) {
if (current_item_size != 0) {
builder->AppendFutureData(current_item_size);
current_item_index++;
}
builder->AppendIPCDataElement(element);
current_item_index++;
current_item_size = 0;
}
void Done() {
if (current_item_size != 0) {
builder->AppendFutureData(current_item_size);
}
}
std::vector<BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest>*
requests;
size_t max_segment_size;
BlobDataBuilder* builder;
size_t current_item_size;
uint64_t current_item_index;
};
// This iterates of the data elements and segments the 'bytes' data into
// the smallest number of segments given the max_segment_size.
// The callback describes either:
// * A non-memory item
// * A partition of a bytes element which will be populated into a given
// segment and segment offset.
// More specifically, we split each |element| into one or more |segments| of a
// max_size, invokes the strategy to determine the request to make for each
// |segment| produced. A |segment| can also span multiple |elements|.
// Assumptions: All memory items are consolidated. As in, there are no two
// 'bytes' items next to eachother.
template <typename Visitor>
void ForEachWithSegment(const std::vector<DataElement>& elements,
uint64_t max_segment_size,
Visitor* visitor) {
DCHECK_GT(max_segment_size, 0ull);
size_t segment_index = 0;
uint64_t segment_offset = 0;
size_t elements_length = elements.size();
for (size_t element_index = 0; element_index < elements_length;
++element_index) {
const auto& element = elements.at(element_index);
DataElement::Type type = element.type();
if (!IsBytes(type)) {
visitor->VisitNonBytesSegment(element, element_index);
continue;
}
uint64_t element_memory_left = element.length();
uint64_t element_offset = 0;
while (element_memory_left > 0) {
if (segment_offset == max_segment_size) {
++segment_index;
segment_offset = 0;
}
uint64_t memory_writing =
std::min(max_segment_size - segment_offset, element_memory_left);
visitor->VisitBytesSegment(element_index, element_offset, segment_index,
segment_offset, memory_writing);
element_memory_left -= memory_writing;
segment_offset += memory_writing;
element_offset += memory_writing;
}
}
visitor->Done();
}
} // namespace
BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest::
RendererMemoryItemRequest()
: browser_item_index(0), browser_item_offset(0) {}
BlobAsyncTransportRequestBuilder::BlobAsyncTransportRequestBuilder()
: total_bytes_size_(0) {}
BlobAsyncTransportRequestBuilder::~BlobAsyncTransportRequestBuilder() {}
// Initializes the transport strategy for file requests.
void BlobAsyncTransportRequestBuilder::InitializeForFileRequests(
size_t max_file_size,
uint64_t blob_total_size,
const std::vector<DataElement>& elements,
BlobDataBuilder* builder) {
DCHECK(requests_.empty());
total_bytes_size_ = blob_total_size;
ComputeHandleSizes(total_bytes_size_, max_file_size, &file_sizes_);
FileStorageStrategy strategy(&requests_, builder);
ForEachWithSegment(elements, static_cast<uint64_t>(max_file_size), &strategy);
}
void BlobAsyncTransportRequestBuilder::InitializeForSharedMemoryRequests(
size_t max_shared_memory_size,
uint64_t blob_total_size,
const std::vector<DataElement>& elements,
BlobDataBuilder* builder) {
DCHECK(requests_.empty());
DCHECK(blob_total_size <= std::numeric_limits<size_t>::max());
total_bytes_size_ = blob_total_size;
ComputeHandleSizes(total_bytes_size_, max_shared_memory_size,
&shared_memory_sizes_);
SharedMemoryStorageStrategy strategy(max_shared_memory_size, &requests_,
builder);
ForEachWithSegment(elements, static_cast<uint64_t>(max_shared_memory_size),
&strategy);
}
void BlobAsyncTransportRequestBuilder::InitializeForIPCRequests(
size_t max_ipc_memory_size,
uint64_t blob_total_size,
const std::vector<DataElement>& elements,
BlobDataBuilder* builder) {
DCHECK(requests_.empty());
// We don't segment anything, and just request the memory items directly
// in IPC.
size_t items_length = elements.size();
total_bytes_size_ = blob_total_size;
for (size_t i = 0; i < items_length; i++) {
const auto& info = elements.at(i);
if (!IsBytes(info.type())) {
builder->AppendIPCDataElement(info);
continue;
}
BlobAsyncTransportRequestBuilder::RendererMemoryItemRequest request;
request.browser_item_index = i;
request.browser_item_offset = 0;
request.message.request_number = requests_.size();
request.message.transport_strategy = IPCBlobItemRequestStrategy::IPC;
request.message.renderer_item_index = i;
request.message.renderer_item_offset = 0;
request.message.size = info.length();
requests_.push_back(request);
builder->AppendFutureData(info.length());
}
}
/* static */
bool BlobAsyncTransportRequestBuilder::ShouldBeShortcut(
const std::vector<DataElement>& elements,
size_t memory_available) {
base::CheckedNumeric<size_t> shortcut_bytes = 0;
for (const auto& element : elements) {
DataElement::Type type = element.type();
if (type == DataElement::TYPE_BYTES_DESCRIPTION) {
return false;
}
if (type == DataElement::TYPE_BYTES) {
shortcut_bytes += element.length();
if (!shortcut_bytes.IsValid()) {
return false;
}
}
}
return shortcut_bytes.ValueOrDie() <= memory_available;
}
/* static */
void BlobAsyncTransportRequestBuilder::ComputeHandleSizes(
uint64_t total_memory_size,
size_t max_segment_size,
std::vector<size_t>* segment_sizes) {
size_t total_max_segments =
static_cast<size_t>(total_memory_size / max_segment_size);
bool has_extra_segment = (total_memory_size % max_segment_size) > 0;
segment_sizes->reserve(total_max_segments + (has_extra_segment ? 1 : 0));
segment_sizes->insert(segment_sizes->begin(), total_max_segments,
max_segment_size);
if (has_extra_segment) {
segment_sizes->push_back(total_memory_size % max_segment_size);
}
}
} // namespace storage