0

Implement byte transportation for mojo blob service.

Also turn on the BlobStorageBrowserTest for the new mojo implementation
now that transportation mostly works. This required a couple of minor
other changes:
- Adding BlobRegistry to the browser process' manifest
- Turning off some BlobDispatcherHost IsInUseInProcess checks. These
  checks don't really make sense anyway when it will become possible
  to transfer blobs between renderers directly, and this never was a
  security boundary anyway, so removing the check shouldn't hurt.

Bug: 611935
Change-Id: If8bfc2d8351aecc72a3818d0cbb013a55efc6175
Reviewed-on: https://chromium-review.googlesource.com/558192
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Reviewed-by: Daniel Murphy <dmurph@chromium.org>
Commit-Queue: Marijn Kruisselbrink <mek@chromium.org>
Cr-Commit-Position: refs/heads/master@{#487290}
This commit is contained in:
Marijn Kruisselbrink
2017-07-17 23:58:00 +00:00
committed by Commit Bot
parent 7f82c88750
commit 2d263e6d8f
16 changed files with 1656 additions and 25 deletions

@ -7,12 +7,14 @@
#include <algorithm>
#include "base/bind.h"
#include "base/feature_list.h"
#include "base/metrics/histogram_macros.h"
#include "content/browser/bad_message.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/browser/child_process_security_policy_impl.h"
#include "content/browser/fileapi/browser_file_system_helper.h"
#include "content/common/fileapi/webblob_messages.h"
#include "content/public/common/content_features.h"
#include "ipc/ipc_platform_file.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_entry.h"
@ -355,7 +357,12 @@ void BlobDispatcherHost::SendFinalBlobStatus(const std::string& uuid,
}
bool BlobDispatcherHost::IsInUseInHost(const std::string& uuid) {
return base::ContainsKey(blobs_inuse_map_, uuid);
// IsInUseInHost is not a security check, as renderers can arbitrarily start
// using blobs by sending an IncrementRefCount IPC. Furthermore with mojo
// blobs it doesn't make sense anymore to try to decide if a blob is in use in
// a process, so just always return true in that case.
return base::FeatureList::IsEnabled(features::kMojoBlobs) ||
base::ContainsKey(blobs_inuse_map_, uuid);
}
bool BlobDispatcherHost::IsUrlRegisteredInHost(const GURL& blob_url) {

@ -3,11 +3,13 @@
// found in the LICENSE file.
#include "base/run_loop.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/sequenced_worker_pool.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_features.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_utils.h"
@ -103,4 +105,36 @@ IN_PROC_BROWSER_TEST_F(BlobStorageBrowserTest, BlobCombinations) {
RunAllBlockingPoolTasksUntilIdle();
}
class MojoBlobStorageBrowserTest : public BlobStorageBrowserTest {
public:
void SetUp() override {
scoped_feature_list_.InitAndEnableFeature(features::kMojoBlobs);
BlobStorageBrowserTest::SetUp();
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(MojoBlobStorageBrowserTest, BlobCombinations) {
SetBlobLimits();
SimpleTest(GetTestUrl("blob_storage", "blob_creation_and_slicing.html"));
storage::BlobMemoryController* memory_controller = GetMemoryController();
ASSERT_TRUE(memory_controller);
// Our exact usages depend on IPC message ordering & garbage collection.
// Since this is basically random, we just check bounds.
EXPECT_LT(0u, memory_controller->memory_usage());
EXPECT_LT(0ul, memory_controller->disk_usage());
EXPECT_GT(memory_controller->disk_usage(),
static_cast<uint64_t>(memory_controller->memory_usage()));
EXPECT_GT(limits_.max_blob_in_memory_space,
memory_controller->memory_usage());
EXPECT_GT(limits_.effective_max_disk_space, memory_controller->disk_usage());
shell()->Close();
// Make sure we run all file / io tasks.
base::RunLoop().RunUntilIdle();
RunAllBlockingPoolTasksUntilIdle();
}
} // namespace content

@ -45,6 +45,7 @@
"shape_detection::mojom::FaceDetectionProvider",
"resource_coordinator::mojom::CoordinationUnit",
"shape_detection::mojom::TextDetection",
"storage::mojom::BlobRegistry",
"ui::mojom::Gpu"
],
"geolocation_config": [

@ -32,6 +32,8 @@ component("browser") {
"blob/blob_transport_host.h",
"blob/blob_transport_request_builder.cc",
"blob/blob_transport_request_builder.h",
"blob/blob_transport_strategy.cc",
"blob/blob_transport_strategy.h",
"blob/blob_url_request_job.cc",
"blob/blob_url_request_job.h",
"blob/blob_url_request_job_factory.cc",
@ -243,6 +245,7 @@ source_set("unittests") {
"blob/blob_storage_context_unittest.cc",
"blob/blob_storage_registry_unittest.cc",
"blob/blob_transport_request_builder_unittest.cc",
"blob/blob_transport_strategy_unittest.cc",
"database/database_quota_client_unittest.cc",
"database/database_tracker_unittest.cc",
"database/database_util_unittest.cc",
@ -309,6 +312,8 @@ static_library("test_support") {
"test/fileapi_test_file_set.h",
"test/mock_blob_url_request_context.cc",
"test/mock_blob_url_request_context.h",
"test/mock_bytes_provider.cc",
"test/mock_bytes_provider.h",
"test/mock_file_change_observer.cc",
"test/mock_file_change_observer.h",
"test/mock_file_update_observer.cc",
@ -336,6 +341,7 @@ static_library("test_support") {
deps = [
":browser",
"//base/test:test_support",
"//mojo/common",
"//net:test_support",
"//testing/gtest",
"//third_party/leveldatabase",

@ -111,8 +111,19 @@ bool BlobDataBuilder::PopulateFutureData(size_t index,
const char* data,
size_t offset,
size_t length) {
DCHECK_LT(index, items_.size());
DCHECK(data);
char* target = GetFutureDataPointerToPopulate(index, offset, length);
if (!target)
return false;
std::memcpy(target, data, length);
return true;
}
char* BlobDataBuilder::GetFutureDataPointerToPopulate(size_t index,
size_t offset,
size_t length) {
DCHECK_LT(index, items_.size());
DataElement* element = items_[index]->data_element_ptr();
// We lazily allocate our data buffer by waiting until the first
@ -128,16 +139,15 @@ bool BlobDataBuilder::PopulateFutureData(size_t index,
}
if (element->type() != DataElement::TYPE_BYTES) {
DVLOG(1) << "Invalid item type.";
return false;
return nullptr;
}
base::CheckedNumeric<size_t> checked_end = offset;
checked_end += length;
if (!checked_end.IsValid() || checked_end.ValueOrDie() > element->length()) {
DVLOG(1) << "Invalid offset or length.";
return false;
return nullptr;
}
std::memcpy(element->mutable_bytes() + offset, data, length);
return true;
return element->mutable_bytes() + offset;
}
size_t BlobDataBuilder::AppendFutureFile(uint64_t offset,

@ -81,6 +81,16 @@ class STORAGE_EXPORT BlobDataBuilder {
size_t offset,
size_t length);
// Same as PopulateFutureData, but rather than passing in the data to be
// copied, this method returns a pointer where the caller can copy |length|
// bytes of data to.
// Returns nullptr if:
// * The item was not created by using AppendFutureData, or
// * The offset and length are not valid.
char* GetFutureDataPointerToPopulate(size_t index,
size_t offset,
size_t length);
// Adds an item that is flagged for future data population. Use
// 'PopulateFutureFile' to set the file path and expected modification time
// of this file. Returns the index of the item (to be used in

@ -9,9 +9,44 @@
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_impl.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/blob/blob_transport_strategy.h"
namespace storage {
namespace {
using MemoryStrategy = BlobMemoryController::Strategy;
bool CalculateBlobMemorySize(const std::vector<mojom::DataElementPtr>& elements,
size_t* shortcut_bytes,
uint64_t* total_bytes) {
DCHECK(shortcut_bytes);
DCHECK(total_bytes);
base::CheckedNumeric<uint64_t> total_size_checked = 0;
base::CheckedNumeric<size_t> shortcut_size_checked = 0;
for (const auto& e : elements) {
if (e->is_bytes()) {
const auto& bytes = e->get_bytes();
total_size_checked += bytes->length;
if (bytes->embedded_data) {
if (bytes->embedded_data->size() != bytes->length)
return false;
shortcut_size_checked += bytes->length;
}
} else {
continue;
}
if (!total_size_checked.IsValid() || !shortcut_size_checked.IsValid())
return false;
}
*shortcut_bytes = shortcut_size_checked.ValueOrDie();
*total_bytes = total_size_checked.ValueOrDie();
return true;
}
} // namespace
class BlobRegistryImpl::BlobUnderConstruction {
public:
BlobUnderConstruction(BlobRegistryImpl* blob_registry,
@ -33,7 +68,7 @@ class BlobRegistryImpl::BlobUnderConstruction {
// referenced by this new blob. This (and any further methods) could end up
// deleting |this| by removing it from the blobs_under_construction_
// collection in the blob service.
void StartFetchingBlobUUIDs();
void StartTransportation();
~BlobUnderConstruction() {}
@ -48,9 +83,18 @@ class BlobRegistryImpl::BlobUnderConstruction {
// Also deletes |this| by removing it from the blobs_under_construction_ list.
void MarkAsBroken(BlobStatus reason,
const std::string& bad_message_reason = "") {
context()->CancelBuildingBlob(uuid(), reason);
DCHECK(BlobStatusIsError(reason));
DCHECK_EQ(bad_message_reason.empty(), !BlobStatusIsBadIPC(reason));
// The blob might no longer have any references, in which case it may no
// longer exist. If that happens just skip calling cancel.
if (context()->registry().HasEntry(uuid()))
context()->CancelBuildingBlob(uuid(), reason);
if (!bad_message_reason.empty())
std::move(bad_message_callback_).Run(bad_message_reason);
MarkAsFinishedAndDeleteSelf();
}
void MarkAsFinishedAndDeleteSelf() {
blob_registry_->blobs_under_construction_.erase(uuid());
}
@ -75,6 +119,16 @@ class BlobRegistryImpl::BlobUnderConstruction {
// transporting.
void ResolvedAllBlobDependencies();
// Called when memory has been reserved for this blob and transport can begin.
// Could also be called if something caused the blob to become invalid before
// transportation began, in which case we just give up.
void OnReadyForTransport(
BlobStatus status,
std::vector<BlobMemoryController::FileCreationInfo> file_infos);
// Called when all data has been transported, or transport has failed.
void TransportComplete(BlobStatus result);
#if DCHECK_IS_ON()
// Returns true if the DAG made up by this blob and any other blobs that
// are currently being built by BlobRegistryImpl contains any cycles.
@ -99,6 +153,9 @@ class BlobRegistryImpl::BlobUnderConstruction {
// called.
mojo::ReportBadMessageCallback bad_message_callback_;
// Transport strategy to use when transporting data.
std::unique_ptr<BlobTransportStrategy> transport_strategy_;
// List of UUIDs for referenced blobs. Same size as |elements_|. All entries
// for non-blob elements will remain empty strings.
std::vector<std::string> referenced_blob_uuids_;
@ -113,17 +170,11 @@ class BlobRegistryImpl::BlobUnderConstruction {
DISALLOW_COPY_AND_ASSIGN(BlobUnderConstruction);
};
void BlobRegistryImpl::BlobUnderConstruction::StartFetchingBlobUUIDs() {
void BlobRegistryImpl::BlobUnderConstruction::StartTransportation() {
size_t blob_count = 0;
for (size_t i = 0; i < elements_.size(); ++i) {
const auto& element = elements_[i];
if (element->is_blob()) {
if (element->get_blob()->blob.encountered_error()) {
// Will delete |this|.
MarkAsBroken(BlobStatus::ERR_REFERENCED_BLOB_BROKEN);
return;
}
// If connection to blob is broken, something bad happened, so mark this
// new blob as broken, which will delete |this| and keep it from doing
// unneeded extra work.
@ -134,6 +185,10 @@ void BlobRegistryImpl::BlobUnderConstruction::StartFetchingBlobUUIDs() {
element->get_blob()->blob->GetInternalUUID(
base::BindOnce(&BlobUnderConstruction::ReceivedBlobUUID,
weak_ptr_factory_.GetWeakPtr(), blob_count++));
} else if (element->is_bytes()) {
element->get_bytes()->data.set_connection_error_handler(base::BindOnce(
&BlobUnderConstruction::MarkAsBroken, weak_ptr_factory_.GetWeakPtr(),
BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT, ""));
}
}
referenced_blob_uuids_.resize(blob_count);
@ -142,6 +197,32 @@ void BlobRegistryImpl::BlobUnderConstruction::StartFetchingBlobUUIDs() {
// Without it a blob could forever remaing pending if a renderer sends us
// a BlobPtr connected to a (malicious) non-responding implementation.
// Do some basic validation of bytes to transport, and determine memory
// transport strategy to use later.
uint64_t transport_memory_size = 0;
size_t shortcut_size = 0;
if (!CalculateBlobMemorySize(elements_, &shortcut_size,
&transport_memory_size)) {
MarkAsBroken(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
"Invalid byte element sizes in BlobRegistry::Register");
return;
}
const BlobMemoryController& memory_controller =
context()->memory_controller();
MemoryStrategy memory_strategy =
memory_controller.DetermineStrategy(shortcut_size, transport_memory_size);
if (memory_strategy == MemoryStrategy::TOO_LARGE) {
MarkAsBroken(BlobStatus::ERR_OUT_OF_MEMORY);
return;
}
transport_strategy_ = BlobTransportStrategy::Create(
memory_strategy, &builder_,
base::BindOnce(&BlobUnderConstruction::TransportComplete,
weak_ptr_factory_.GetWeakPtr()),
memory_controller.limits());
// If there were no unresolved blobs, immediately proceed to the next step.
// Currently this will only happen if there are no blobs referenced
// whatsoever, but hopefully in the future blob UUIDs will be cached in the
@ -212,10 +293,11 @@ void BlobRegistryImpl::BlobUnderConstruction::ResolvedAllBlobDependencies() {
DCHECK_EQ(resolved_blob_uuid_count_, referenced_blob_uuids_.size());
DCHECK_EQ(ready_dependent_blob_count_, referenced_blob_uuids_.size());
// TODO(mek): Fill BlobDataBuilder with elements_ other than blobs.
auto blob_uuid_it = referenced_blob_uuids_.begin();
for (const auto& element : elements_) {
if (element->is_file()) {
if (element->is_bytes()) {
transport_strategy_->AddBytesElement(element->get_bytes().get());
} else if (element->is_file()) {
const auto& f = element->get_file();
builder_.AppendFile(f->path, f->offset, f->length,
f->expected_modification_time.value_or(base::Time()));
@ -231,14 +313,49 @@ void BlobRegistryImpl::BlobUnderConstruction::ResolvedAllBlobDependencies() {
element->get_blob()->length);
}
}
// OnReadyForTransport can be called synchronously, which can call
// MarkAsFinishedAndDeleteSelf synchronously, so don't access any members
// after this call.
std::unique_ptr<BlobDataHandle> new_handle =
context()->BuildPreregisteredBlob(
builder_, BlobStorageContext::TransportAllowedCallback());
builder_, base::Bind(&BlobUnderConstruction::OnReadyForTransport,
weak_ptr_factory_.GetWeakPtr()));
// TODO(mek): Update BlobImpl with new BlobDataHandle. Although handles
// only differ in their size() attribute, which is currently not used by
// BlobImpl.
DCHECK(!BlobStatusIsPending(new_handle->GetBlobStatus()));
}
void BlobRegistryImpl::BlobUnderConstruction::OnReadyForTransport(
BlobStatus status,
std::vector<BlobMemoryController::FileCreationInfo> file_infos) {
if (!BlobStatusIsPending(status)) {
// Done or error.
MarkAsFinishedAndDeleteSelf();
return;
}
transport_strategy_->BeginTransport(std::move(file_infos));
}
void BlobRegistryImpl::BlobUnderConstruction::TransportComplete(
BlobStatus result) {
// The blob might no longer have any references, in which case it may no
// longer exist. If that happens just skip calling Complete.
// TODO(mek): Stop building sooner if a blob is no longer referenced.
if (context()->registry().HasEntry(uuid())) {
if (result == BlobStatus::DONE)
context()->NotifyTransportComplete(uuid());
else
context()->CancelBuildingBlob(uuid(), result);
}
if (BlobStatusIsBadIPC(result)) {
// BlobTransportStrategy might have already reported a BadMessage on the
// BytesProvider binding, but just to be safe, also report one on the
// BlobRegistry binding itself.
std::move(bad_message_callback_)
.Run("Received invalid data while transporting blob");
}
MarkAsFinishedAndDeleteSelf();
}
#if DCHECK_IS_ON()
@ -331,7 +448,7 @@ void BlobRegistryImpl::Register(mojom::BlobRequest blob,
context_->AddFutureBlob(uuid, content_type, content_disposition);
BlobImpl::Create(std::move(handle), std::move(blob));
blobs_under_construction_[uuid]->StartFetchingBlobUUIDs();
blobs_under_construction_[uuid]->StartTransportation();
std::move(callback).Run();
}

@ -4,13 +4,18 @@
#include "storage/browser/blob/blob_registry_impl.h"
#include <limits>
#include "base/files/scoped_temp_dir.h"
#include "base/rand_util.h"
#include "base/task_scheduler/post_task.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_restrictions.h"
#include "mojo/edk/embedder/embedder.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/blob/blob_data_handle.h"
#include "storage/browser/blob/blob_storage_context.h"
#include "storage/browser/test/mock_bytes_provider.h"
#include "storage/browser/test/mock_special_storage_policy.h"
#include "testing/gtest/include/gtest/gtest.h"
@ -18,6 +23,15 @@ namespace storage {
namespace {
const size_t kTestBlobStorageIPCThresholdBytes = 5;
const size_t kTestBlobStorageMaxSharedMemoryBytes = 20;
const size_t kTestBlobStorageMaxBytesDataItemSize = 13;
const size_t kTestBlobStorageMaxBlobMemorySize = 400;
const uint64_t kTestBlobStorageMaxDiskSpace = 4000;
const uint64_t kTestBlobStorageMinFileSizeBytes = 10;
const uint64_t kTestBlobStorageMaxFileSizeBytes = 100;
class MockBlob : public mojom::Blob {
public:
explicit MockBlob(const std::string& uuid) : uuid_(uuid) {}
@ -51,13 +65,20 @@ class MockDelegate : public BlobRegistryImpl::Delegate {
bool can_read_file_system_file_result = true;
};
void BindBytesProvider(std::unique_ptr<MockBytesProvider> impl,
mojom::BytesProviderRequest request) {
mojo::MakeStrongBinding(std::move(impl), std::move(request));
}
} // namespace
class BlobRegistryImplTest : public testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
context_ = base::MakeUnique<BlobStorageContext>();
context_ = base::MakeUnique<BlobStorageContext>(
data_dir_.GetPath(),
base::CreateTaskRunnerWithTraits({base::MayBlock()}));
auto storage_policy =
base::MakeRefCounted<content::MockSpecialStoragePolicy>();
file_system_context_ = base::MakeRefCounted<storage::FileSystemContext>(
@ -74,11 +95,28 @@ class BlobRegistryImplTest : public testing::Test {
auto delegate = base::MakeUnique<MockDelegate>();
delegate_ptr_ = delegate.get();
registry_impl_->Bind(MakeRequest(&registry_), std::move(delegate));
mojo::edk::SetDefaultProcessErrorCallback(base::Bind(
&BlobRegistryImplTest::OnBadMessage, base::Unretained(this)));
storage::BlobStorageLimits limits;
limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes;
limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes;
limits.max_bytes_data_item_size = kTestBlobStorageMaxBytesDataItemSize;
limits.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize;
limits.desired_max_disk_space = kTestBlobStorageMaxDiskSpace;
limits.effective_max_disk_space = kTestBlobStorageMaxDiskSpace;
limits.min_page_file_size = kTestBlobStorageMinFileSizeBytes;
limits.max_file_size = kTestBlobStorageMaxFileSizeBytes;
context_->mutable_memory_controller()->set_limits_for_testing(limits);
// Disallow IO on the main loop.
base::ThreadRestrictions::SetIOAllowed(false);
}
void TearDown() override {
base::ThreadRestrictions::SetIOAllowed(true);
mojo::edk::SetDefaultProcessErrorCallback(
mojo::edk::ProcessErrorCallback());
}
@ -118,6 +156,35 @@ class BlobRegistryImplTest : public testing::Test {
loop.Run();
}
mojom::BytesProviderPtr CreateBytesProvider(const std::string& bytes) {
if (!bytes_provider_runner_) {
bytes_provider_runner_ = base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::WithBaseSyncPrimitives()});
}
mojom::BytesProviderPtr result;
auto provider = base::MakeUnique<MockBytesProvider>(
std::vector<uint8_t>(bytes.begin(), bytes.end()), &reply_request_count_,
&stream_request_count_, &file_request_count_);
bytes_provider_runner_->PostTask(
FROM_HERE, base::BindOnce(&BindBytesProvider, std::move(provider),
MakeRequest(&result)));
return result;
}
void CreateBytesProvider(const std::string& bytes,
mojom::BytesProviderRequest request) {
if (!bytes_provider_runner_) {
bytes_provider_runner_ = base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::WithBaseSyncPrimitives()});
}
auto provider = base::MakeUnique<MockBytesProvider>(
std::vector<uint8_t>(bytes.begin(), bytes.end()), &reply_request_count_,
&stream_request_count_, &file_request_count_);
bytes_provider_runner_->PostTask(
FROM_HERE, base::BindOnce(&BindBytesProvider, std::move(provider),
std::move(request)));
}
protected:
base::ScopedTempDir data_dir_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
@ -126,6 +193,11 @@ class BlobRegistryImplTest : public testing::Test {
std::unique_ptr<BlobRegistryImpl> registry_impl_;
mojom::BlobRegistryPtr registry_;
MockDelegate* delegate_ptr_;
scoped_refptr<base::SequencedTaskRunner> bytes_provider_runner_;
size_t reply_request_count_ = 0;
size_t stream_request_count_ = 0;
size_t file_request_count_ = 0;
std::vector<std::string> bad_messages_;
};
@ -389,10 +461,10 @@ TEST_F(BlobRegistryImplTest, Register_ValidBlobReferences) {
WaitForBlobCompletion(handle3.get());
EXPECT_FALSE(handle2->IsBroken());
EXPECT_EQ(BlobStatus::DONE, handle2->GetBlobStatus());
ASSERT_EQ(BlobStatus::DONE, handle2->GetBlobStatus());
EXPECT_FALSE(handle3->IsBroken());
EXPECT_EQ(BlobStatus::DONE, handle3->GetBlobStatus());
ASSERT_EQ(BlobStatus::DONE, handle3->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId2);
expected_blob_data.AppendData("hello wo");
@ -441,7 +513,7 @@ TEST_F(BlobRegistryImplTest, Register_ValidFile) {
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
EXPECT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendFile(path, 0, 16, base::Time());
@ -510,7 +582,7 @@ TEST_F(BlobRegistryImplTest, Register_FileSystemFile_Valid) {
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
EXPECT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendFileSystemFile(url, 0, 16, base::Time());
@ -518,4 +590,305 @@ TEST_F(BlobRegistryImplTest, Register_FileSystemFile_Valid) {
EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot());
}
TEST_F(BlobRegistryImplTest, Register_BytesInvalidEmbeddedData) {
const std::string kId = "id";
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBytes(mojom::DataElementBytes::New(
10, std::vector<uint8_t>(5), CreateBytesProvider(""))));
mojom::BlobPtr blob;
EXPECT_FALSE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
handle->GetBlobStatus());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
}
TEST_F(BlobRegistryImplTest, Register_BytesInvalidDataSize) {
const std::string kId = "id";
// Two elements that together are more than uint64_t::max bytes.
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBytes(
mojom::DataElementBytes::New(8, base::nullopt, CreateBytesProvider(""))));
elements.push_back(mojom::DataElement::NewBytes(
mojom::DataElementBytes::New(std::numeric_limits<uint64_t>::max() - 4,
base::nullopt, CreateBytesProvider(""))));
mojom::BlobPtr blob;
EXPECT_FALSE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS,
handle->GetBlobStatus());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
}
TEST_F(BlobRegistryImplTest, Register_BytesOutOfMemory) {
const std::string kId = "id";
// Two elements that together don't fit in the test quota.
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBytes(mojom::DataElementBytes::New(
kTestBlobStorageMaxDiskSpace, base::nullopt, CreateBytesProvider(""))));
elements.push_back(mojom::DataElement::NewBytes(mojom::DataElementBytes::New(
kTestBlobStorageMaxDiskSpace, base::nullopt, CreateBytesProvider(""))));
mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_OUT_OF_MEMORY, handle->GetBlobStatus());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
}
TEST_F(BlobRegistryImplTest, Register_ValidEmbeddedBytes) {
const std::string kId = "id";
const std::string kData = "hello world";
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBytes(mojom::DataElementBytes::New(
kData.size(), std::vector<uint8_t>(kData.begin(), kData.end()),
CreateBytesProvider(kData))));
mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendData(kData);
EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
}
TEST_F(BlobRegistryImplTest, Register_ValidBytesAsReply) {
const std::string kId = "id";
const std::string kData = "hello";
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBytes(mojom::DataElementBytes::New(
kData.size(), base::nullopt, CreateBytesProvider(kData))));
mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendData(kData);
EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot());
EXPECT_EQ(1u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
}
TEST_F(BlobRegistryImplTest, Register_ValidBytesAsStream) {
const std::string kId = "id";
const std::string kData =
base::RandBytesAsString(kTestBlobStorageMaxSharedMemoryBytes * 3 + 13);
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBytes(mojom::DataElementBytes::New(
kData.size(), base::nullopt, CreateBytesProvider(kData))));
mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
size_t offset = 0;
BlobDataBuilder expected_blob_data(kId);
while (offset < kData.size()) {
expected_blob_data.AppendData(
kData.substr(offset, kTestBlobStorageMaxBytesDataItemSize));
offset += kTestBlobStorageMaxBytesDataItemSize;
}
EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(1u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
}
TEST_F(BlobRegistryImplTest, Register_ValidBytesAsFile) {
const std::string kId = "id";
const std::string kData =
base::RandBytesAsString(kTestBlobStorageMaxBlobMemorySize + 42);
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBytes(mojom::DataElementBytes::New(
kData.size(), base::nullopt, CreateBytesProvider(kData))));
mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendData(kData);
size_t expected_file_count =
1 + kData.size() / kTestBlobStorageMaxFileSizeBytes;
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(expected_file_count, file_request_count_);
auto snapshot = handle->CreateSnapshot();
EXPECT_EQ(expected_file_count, snapshot->items().size());
size_t remaining_size = kData.size();
for (const auto& item : snapshot->items()) {
EXPECT_EQ(DataElement::TYPE_FILE, item->type());
EXPECT_EQ(0u, item->offset());
if (remaining_size > kTestBlobStorageMaxFileSizeBytes)
EXPECT_EQ(kTestBlobStorageMaxFileSizeBytes, item->length());
else
EXPECT_EQ(remaining_size, item->length());
remaining_size -= item->length();
}
EXPECT_EQ(0u, remaining_size);
}
TEST_F(BlobRegistryImplTest, Register_BytesProviderClosedPipe) {
const std::string kId = "id";
mojom::BytesProviderPtr bytes_provider;
MakeRequest(&bytes_provider);
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBytes(mojom::DataElementBytes::New(
32, base::nullopt, std::move(bytes_provider))));
mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
WaitForBlobCompletion(handle.get());
EXPECT_TRUE(handle->IsBroken());
EXPECT_EQ(BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT, handle->GetBlobStatus());
}
TEST_F(BlobRegistryImplTest,
Register_DefereferencedWhileBuildingBeforeBreaking) {
const std::string kId = "id";
mojom::BytesProviderPtr bytes_provider;
mojom::BytesProviderRequest request = MakeRequest(&bytes_provider);
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBytes(mojom::DataElementBytes::New(
32, base::nullopt, std::move(bytes_provider))));
mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(context_->GetBlobDataFromUUID(kId)->IsBeingBuilt());
// Now drop all references to the blob.
blob.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(context_->registry().HasEntry(kId));
// Now cause construction to fail, if it would still be going on.
request = nullptr;
base::RunLoop().RunUntilIdle();
}
TEST_F(BlobRegistryImplTest,
Register_DefereferencedWhileBuildingBeforeTransporting) {
const std::string kId = "id";
const std::string kData = "hello world";
mojom::BytesProviderPtr bytes_provider;
mojom::BytesProviderRequest request = MakeRequest(&bytes_provider);
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBytes(mojom::DataElementBytes::New(
kData.size(), base::nullopt, std::move(bytes_provider))));
mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::move(elements)));
EXPECT_TRUE(bad_messages_.empty());
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(context_->GetBlobDataFromUUID(kId)->IsBeingBuilt());
// Now drop all references to the blob.
blob.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(context_->registry().HasEntry(kId));
// Now cause construction to complete, if it would still be going on.
CreateBytesProvider(kData, std::move(request));
scoped_task_environment_.RunUntilIdle();
base::RunLoop().RunUntilIdle();
}
} // namespace storage

@ -149,6 +149,7 @@ class STORAGE_EXPORT BlobStorageContext {
friend class BlobDataHandle;
friend class BlobDataHandle::BlobDataHandleShared;
friend class BlobFlattenerTest;
friend class BlobRegistryImplTest;
friend class BlobSliceTest;
friend class BlobStorageContextTest;

@ -0,0 +1,386 @@
// Copyright 2017 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 "storage/browser/blob/blob_transport_strategy.h"
#include "mojo/public/cpp/system/data_pipe.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/public/interfaces/blobs.mojom.h"
namespace storage {
namespace {
using MemoryStrategy = BlobMemoryController::Strategy;
// Transport strategy when no transport is needed. All Bytes elements should
// have their data embedded already.
class NoneNeededTransportStrategy : public BlobTransportStrategy {
public:
NoneNeededTransportStrategy(BlobDataBuilder* builder,
ResultCallback result_callback)
: BlobTransportStrategy(builder, std::move(result_callback)) {}
void AddBytesElement(mojom::DataElementBytes* bytes) override {
DCHECK(bytes->embedded_data);
DCHECK_EQ(bytes->length, bytes->embedded_data->size());
builder_->AppendData(
reinterpret_cast<const char*>(bytes->embedded_data->data()),
bytes->length);
}
void BeginTransport(
std::vector<BlobMemoryController::FileCreationInfo>) override {
std::move(result_callback_).Run(BlobStatus::DONE);
}
};
// Transport strategy that requests all data as replies.
class ReplyTransportStrategy : public BlobTransportStrategy {
public:
ReplyTransportStrategy(BlobDataBuilder* builder,
ResultCallback result_callback)
: BlobTransportStrategy(builder, std::move(result_callback)) {}
void AddBytesElement(mojom::DataElementBytes* bytes) override {
size_t builder_element_index = builder_->AppendFutureData(bytes->length);
// base::Unretained is safe because |this| is guaranteed (by the contract
// that code using BlobTransportStrategy should adhere to) to outlive the
// BytesProvider.
requests_.push_back(base::BindOnce(
&mojom::BytesProvider::RequestAsReply,
base::Unretained(bytes->data.get()),
base::BindOnce(&ReplyTransportStrategy::OnReply, base::Unretained(this),
builder_element_index, bytes->length)));
}
void BeginTransport(
std::vector<BlobMemoryController::FileCreationInfo>) override {
if (requests_.empty()) {
std::move(result_callback_).Run(BlobStatus::DONE);
return;
}
for (auto& request : requests_)
std::move(request).Run();
}
private:
void OnReply(size_t builder_element_index,
size_t expected_size,
const std::vector<uint8_t>& data) {
if (data.size() != expected_size) {
mojo::ReportBadMessage(
"Invalid data size in reply to BytesProvider::RequestAsReply");
std::move(result_callback_)
.Run(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS);
return;
}
bool populate_result = builder_->PopulateFutureData(
builder_element_index, reinterpret_cast<const char*>(data.data()), 0,
data.size());
DCHECK(populate_result);
if (++num_resolved_requests_ == requests_.size())
std::move(result_callback_).Run(BlobStatus::DONE);
}
std::vector<base::OnceClosure> requests_;
size_t num_resolved_requests_ = 0;
};
// Transport strategy that requests all data as data pipes, one pipe at a time.
class DataPipeTransportStrategy : public BlobTransportStrategy {
public:
DataPipeTransportStrategy(BlobDataBuilder* builder,
ResultCallback result_callback,
const BlobStorageLimits& limits)
: BlobTransportStrategy(builder, std::move(result_callback)),
limits_(limits),
watcher_(FROM_HERE, mojo::SimpleWatcher::ArmingPolicy::AUTOMATIC) {}
void AddBytesElement(mojom::DataElementBytes* bytes) override {
// Split up the data in |max_bytes_data_item_size| sized chunks.
for (uint64_t source_offset = 0; source_offset < bytes->length;
source_offset += limits_.max_bytes_data_item_size) {
size_t builder_element_index =
builder_->AppendFutureData(std::min<uint64_t>(
bytes->length - source_offset, limits_.max_bytes_data_item_size));
if (source_offset == 0) {
requests_.push_back(base::BindOnce(
&DataPipeTransportStrategy::RequestDataPipe, base::Unretained(this),
bytes->data.get(), bytes->length, builder_element_index));
}
}
}
void BeginTransport(
std::vector<BlobMemoryController::FileCreationInfo>) override {
NextRequestOrDone();
}
private:
void NextRequestOrDone() {
if (requests_.empty()) {
std::move(result_callback_).Run(BlobStatus::DONE);
return;
}
auto request = std::move(requests_.front());
requests_.pop_front();
std::move(request).Run();
}
void RequestDataPipe(mojom::BytesProvider* provider,
size_t expected_source_size,
size_t first_builder_element_index) {
// TODO(mek): Determine if the overhead of creating a new SharedMemory
// segment for each BytesProvider is too much. If it is possible solutions
// would include somehow teaching DataPipe to reuse the SharedMemory from a
// previous DataPipe, or simply using a single BytesProvider for all bytes
// elements. http://crbug.com/741159
DCHECK(!consumer_handle_.is_valid());
mojo::ScopedDataPipeProducerHandle producer_handle;
MojoCreateDataPipeOptions options;
options.struct_size = sizeof(MojoCreateDataPipeOptions);
options.flags = MOJO_CREATE_DATA_PIPE_OPTIONS_FLAG_NONE;
options.element_num_bytes = 1;
options.capacity_num_bytes =
std::min(expected_source_size, limits_.max_shared_memory_size);
MojoResult result =
CreateDataPipe(&options, &producer_handle, &consumer_handle_);
if (result != MOJO_RESULT_OK) {
DVLOG(1) << "Unable to create data pipe for blob transfer.";
std::move(result_callback_).Run(BlobStatus::ERR_OUT_OF_MEMORY);
return;
}
current_source_offset_ = 0;
provider->RequestAsStream(std::move(producer_handle));
watcher_.Watch(consumer_handle_.get(), MOJO_HANDLE_SIGNAL_READABLE,
base::Bind(&DataPipeTransportStrategy::OnDataPipeReadable,
base::Unretained(this), expected_source_size,
first_builder_element_index));
}
void OnDataPipeReadable(size_t expected_full_source_size,
size_t first_builder_element_index,
MojoResult result) {
// The index of the element data should currently be written to, relative to
// the first element of this stream (first_builder_element_index).
size_t relative_element_index =
current_source_offset_ / limits_.max_bytes_data_item_size;
// The offset into the current element where data should be written next.
size_t offset_in_builder_element =
current_source_offset_ -
relative_element_index * limits_.max_bytes_data_item_size;
while (true) {
uint32_t num_bytes = 0;
const void* source_buffer;
MojoResult read_result =
mojo::BeginReadDataRaw(consumer_handle_.get(), &source_buffer,
&num_bytes, MOJO_READ_DATA_FLAG_NONE);
if (read_result == MOJO_RESULT_SHOULD_WAIT)
return;
if (read_result != MOJO_RESULT_OK) {
// Data pipe broke before we received all the data.
std::move(result_callback_).Run(BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT);
return;
}
if (current_source_offset_ + num_bytes > expected_full_source_size) {
// Received more bytes then expected.
std::move(result_callback_)
.Run(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS);
return;
}
// Only read as many bytes as can fit in current data element. Any
// remaining bytes will be read on the next iteration of this loop.
num_bytes =
std::min<uint32_t>(num_bytes, limits_.max_bytes_data_item_size -
offset_in_builder_element);
char* output_buffer = builder_->GetFutureDataPointerToPopulate(
first_builder_element_index + relative_element_index,
offset_in_builder_element, num_bytes);
DCHECK(output_buffer);
std::memcpy(output_buffer, source_buffer, num_bytes);
read_result = mojo::EndReadDataRaw(consumer_handle_.get(), num_bytes);
DCHECK_EQ(read_result, MOJO_RESULT_OK);
current_source_offset_ += num_bytes;
if (current_source_offset_ >= expected_full_source_size) {
// Done with this stream, on to the next.
// TODO(mek): Should this wait to see if more data than expected gets
// written, instead of immediately closing the pipe?
watcher_.Cancel();
consumer_handle_.reset();
NextRequestOrDone();
return;
}
offset_in_builder_element += num_bytes;
if (offset_in_builder_element >= limits_.max_bytes_data_item_size) {
offset_in_builder_element = 0;
relative_element_index++;
}
}
}
const BlobStorageLimits& limits_;
std::deque<base::OnceClosure> requests_;
mojo::ScopedDataPipeConsumerHandle consumer_handle_;
mojo::SimpleWatcher watcher_;
// How many bytes have been read and processed so far from the current data
// pipe.
size_t current_source_offset_ = 0;
};
// Transport strategy that requests all data through files.
class FileTransportStrategy : public BlobTransportStrategy {
public:
FileTransportStrategy(BlobDataBuilder* builder,
ResultCallback result_callback,
const BlobStorageLimits& limits)
: BlobTransportStrategy(builder, std::move(result_callback)),
limits_(limits) {}
void AddBytesElement(mojom::DataElementBytes* bytes) override {
uint64_t source_offset = 0;
while (source_offset < bytes->length) {
if (current_file_size_ >= limits_.max_file_size ||
file_requests_.empty()) {
current_file_size_ = 0;
current_file_index_++;
file_requests_.push_back(std::vector<Request>());
}
// Make sure no single file gets too big, but do use up all the available
// space in all but the last file.
uint64_t element_size =
std::min(bytes->length - source_offset,
limits_.max_file_size - current_file_size_);
size_t builder_element_index = builder_->AppendFutureFile(
current_file_size_, element_size, file_requests_.size() - 1);
num_unresolved_requests_++;
file_requests_.back().push_back(Request{bytes->data.get(), source_offset,
element_size,
builder_element_index});
source_offset += element_size;
current_file_size_ += element_size;
}
}
void BeginTransport(
std::vector<BlobMemoryController::FileCreationInfo> file_infos) override {
if (file_requests_.empty()) {
std::move(result_callback_).Run(BlobStatus::DONE);
return;
}
DCHECK_EQ(file_infos.size(), file_requests_.size());
for (size_t file_index = 0; file_index < file_requests_.size();
++file_index) {
const auto& requests = file_requests_[file_index];
uint64_t file_offset = 0;
for (size_t i = 0; i < requests.size(); ++i) {
const auto& request = requests[i];
base::File file = i == requests.size() - 1
? std::move(file_infos[file_index].file)
: file_infos[file_index].file.Duplicate();
// base::Unretained is safe because |this| is guaranteed (by the
// contract that code using BlobTransportStrategy should adhere to) to
// outlive the BytesProvider.
request.provider->RequestAsFile(
request.source_offset, request.source_size, std::move(file),
file_offset,
base::BindOnce(&FileTransportStrategy::OnReply,
base::Unretained(this),
request.builder_element_index,
file_infos[file_index].file_reference));
file_offset += request.source_size;
}
}
}
private:
void OnReply(size_t builder_element_index,
const scoped_refptr<ShareableFileReference>& file_reference,
base::Optional<base::Time> time_file_modified) {
if (!time_file_modified) {
// Writing to the file failed in the renderer.
std::move(result_callback_).Run(BlobStatus::ERR_FILE_WRITE_FAILED);
return;
}
bool populate_result = builder_->PopulateFutureFile(
builder_element_index, file_reference, *time_file_modified);
DCHECK(populate_result);
if (--num_unresolved_requests_ == 0)
std::move(result_callback_).Run(BlobStatus::DONE);
}
const BlobStorageLimits& limits_;
// State used to assign bytes elements to individual files.
// The index of the first file that still has available space.
size_t current_file_index_ = 0;
// How big the current file already is.
uint64_t current_file_size_ = 0;
struct Request {
// The BytesProvider to request this particular bit of data from.
mojom::BytesProvider* provider;
// Offset into the BytesProvider of the data to request.
uint64_t source_offset;
// Size of the bytes to request.
uint64_t source_size;
// Index of the element in the BlobDataBuilder the data should be populated
// into.
size_t builder_element_index;
};
// For each file, a list of requests involving that file.
std::vector<std::vector<Request>> file_requests_;
size_t num_unresolved_requests_ = 0;
};
} // namespace
// static
std::unique_ptr<BlobTransportStrategy> BlobTransportStrategy::Create(
MemoryStrategy strategy,
BlobDataBuilder* builder,
ResultCallback result_callback,
const BlobStorageLimits& limits) {
switch (strategy) {
case MemoryStrategy::NONE_NEEDED:
return base::MakeUnique<NoneNeededTransportStrategy>(
builder, std::move(result_callback));
case MemoryStrategy::IPC:
return base::MakeUnique<ReplyTransportStrategy>(
builder, std::move(result_callback));
case MemoryStrategy::SHARED_MEMORY:
return base::MakeUnique<DataPipeTransportStrategy>(
builder, std::move(result_callback), limits);
case MemoryStrategy::FILE:
return base::MakeUnique<FileTransportStrategy>(
builder, std::move(result_callback), limits);
case MemoryStrategy::TOO_LARGE:
NOTREACHED();
}
NOTREACHED();
return nullptr;
}
BlobTransportStrategy::~BlobTransportStrategy() {}
BlobTransportStrategy::BlobTransportStrategy(BlobDataBuilder* builder,
ResultCallback result_callback)
: builder_(builder), result_callback_(std::move(result_callback)) {}
} // namespace storage

@ -0,0 +1,57 @@
// Copyright 2017 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.
#ifndef STORAGE_BROWSER_BLOB_BLOB_TRANSPORT_STRATEGY_H_
#define STORAGE_BROWSER_BLOB_BLOB_TRANSPORT_STRATEGY_H_
#include "base/callback.h"
#include "storage/browser/blob/blob_memory_controller.h"
#include "storage/browser/storage_browser_export.h"
namespace storage {
class BlobDataBuilder;
namespace mojom {
class DataElementBytes;
}
// This class is responsible for transporting bytes for an under-construction
// blob, using a specified transport strategy. This is used by BlobRegistryImpl
// for the actual transportation of bytes.
class STORAGE_EXPORT BlobTransportStrategy {
public:
using ResultCallback = base::OnceCallback<void(BlobStatus)>;
// Creates a BlobTransportStrategy instance for the specified memory strategy.
// The BlobDataBuilder and BlobStorageLimits must outlive the returned
// BlobTransportStrategy.
static std::unique_ptr<BlobTransportStrategy> Create(
BlobMemoryController::Strategy strategy,
BlobDataBuilder* builder,
ResultCallback result_callback,
const BlobStorageLimits& limits);
virtual ~BlobTransportStrategy();
// Called once for each DataElementBytes in a blob. The |bytes| passed in must
// outlive the BlobTransportStrategy instance.
virtual void AddBytesElement(mojom::DataElementBytes* bytes) = 0;
// Called when quota has been allocated and transportation should begin.
// Implementations will call the |result_callback_| when transportation has
// completed, or failed.
virtual void BeginTransport(
std::vector<BlobMemoryController::FileCreationInfo> file_infos) = 0;
protected:
BlobTransportStrategy(BlobDataBuilder* builder,
ResultCallback result_callback);
BlobDataBuilder* builder_;
ResultCallback result_callback_;
};
} // namespace storage
#endif // STORAGE_BROWSER_BLOB_BLOB_TRANSPORT_STRATEGY_H_

@ -0,0 +1,520 @@
// Copyright 2017 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 "storage/browser/blob/blob_transport_strategy.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/rand_util.h"
#include "base/run_loop.h"
#include "base/task_scheduler/post_task.h"
#include "base/test/scoped_task_environment.h"
#include "base/threading/thread_restrictions.h"
#include "mojo/edk/embedder/embedder.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "storage/browser/blob/blob_data_builder.h"
#include "storage/browser/test/mock_bytes_provider.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace storage {
namespace {
using MemoryStrategy = BlobMemoryController::Strategy;
using FileInfoVector = std::vector<BlobMemoryController::FileCreationInfo>;
const size_t kTestBlobStorageIPCThresholdBytes = 5;
const size_t kTestBlobStorageMaxSharedMemoryBytes = 20;
const size_t kTestBlobStorageMaxBytesDataItemSize = 13;
const size_t kTestBlobStorageMaxBlobMemorySize = 400;
const uint64_t kTestBlobStorageMaxDiskSpace = 4000;
const uint64_t kTestBlobStorageMinFileSizeBytes = 10;
const uint64_t kTestBlobStorageMaxFileSizeBytes = 100;
const char kId[] = "blob-id";
void BindBytesProvider(std::unique_ptr<MockBytesProvider> impl,
mojom::BytesProviderRequest request) {
mojo::MakeStrongBinding(std::move(impl), std::move(request));
}
class BlobTransportStrategyTest : public testing::Test {
public:
void SetUp() override {
ASSERT_TRUE(data_dir_.CreateUniqueTempDir());
bytes_provider_runner_ = base::CreateSequencedTaskRunnerWithTraits(
{base::MayBlock(), base::WithBaseSyncPrimitives()});
mock_time_ = base::Time::Now();
limits_.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes;
limits_.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes;
limits_.max_bytes_data_item_size = kTestBlobStorageMaxBytesDataItemSize;
limits_.max_blob_in_memory_space = kTestBlobStorageMaxBlobMemorySize;
limits_.desired_max_disk_space = kTestBlobStorageMaxDiskSpace;
limits_.effective_max_disk_space = kTestBlobStorageMaxDiskSpace;
limits_.min_page_file_size = kTestBlobStorageMinFileSizeBytes;
limits_.max_file_size = kTestBlobStorageMaxFileSizeBytes;
mojo::edk::SetDefaultProcessErrorCallback(base::Bind(
&BlobTransportStrategyTest::OnBadMessage, base::Unretained(this)));
// Disallow IO on the main loop.
base::ThreadRestrictions::SetIOAllowed(false);
}
void TearDown() override {
base::ThreadRestrictions::SetIOAllowed(true);
mojo::edk::SetDefaultProcessErrorCallback(
mojo::edk::ProcessErrorCallback());
}
void OnBadMessage(const std::string& error) {
bad_messages_.push_back(error);
}
mojom::BytesProviderPtr CreateBytesProvider(const std::string& bytes,
base::Optional<base::Time> time) {
mojom::BytesProviderPtr result;
auto provider = base::MakeUnique<MockBytesProvider>(
std::vector<uint8_t>(bytes.begin(), bytes.end()), &reply_request_count_,
&stream_request_count_, &file_request_count_, time);
bytes_provider_runner_->PostTask(
FROM_HERE, base::BindOnce(&BindBytesProvider, std::move(provider),
MakeRequest(&result)));
return result;
}
protected:
base::ScopedTempDir data_dir_;
base::test::ScopedTaskEnvironment scoped_task_environment_;
scoped_refptr<base::SequencedTaskRunner> bytes_provider_runner_;
base::Time mock_time_;
storage::BlobStorageLimits limits_;
std::vector<std::string> bad_messages_;
size_t reply_request_count_ = 0;
size_t stream_request_count_ = 0;
size_t file_request_count_ = 0;
};
class BasicTests : public BlobTransportStrategyTest,
public testing::WithParamInterface<MemoryStrategy> {};
TEST_P(BasicTests, NoBytes) {
BlobDataBuilder builder(kId);
BlobDataBuilder expected(kId);
base::RunLoop loop;
BlobStatus status = BlobStatus::PENDING_TRANSPORT;
auto strategy = BlobTransportStrategy::Create(
GetParam(), &builder,
base::BindOnce(
[](BlobStatus* result_out, base::OnceClosure closure,
BlobStatus result) {
*result_out = result;
std::move(closure).Run();
},
&status, loop.QuitClosure()),
limits_);
strategy->BeginTransport(FileInfoVector());
loop.Run();
EXPECT_EQ(BlobStatus::DONE, status);
EXPECT_EQ(expected, builder);
EXPECT_EQ(0u,
reply_request_count_ + stream_request_count_ + file_request_count_);
EXPECT_TRUE(bad_messages_.empty());
}
TEST_P(BasicTests, WithBytes) {
BlobDataBuilder builder(kId);
BlobDataBuilder expected(kId);
base::RunLoop loop;
BlobStatus status = BlobStatus::PENDING_TRANSPORT;
auto strategy = BlobTransportStrategy::Create(
GetParam(), &builder,
base::BindOnce(
[](BlobStatus* result_out, base::OnceClosure closure,
BlobStatus result) {
*result_out = result;
std::move(closure).Run();
},
&status, loop.QuitClosure()),
limits_);
std::string data = base::RandBytesAsString(7);
mojom::DataElementBytes bytes1(data.size(),
std::vector<uint8_t>(data.begin(), data.end()),
CreateBytesProvider(data, mock_time_));
strategy->AddBytesElement(&bytes1);
expected.AppendData(data);
data = base::RandBytesAsString(3);
mojom::DataElementBytes bytes2(data.size(),
std::vector<uint8_t>(data.begin(), data.end()),
CreateBytesProvider(data, mock_time_));
strategy->AddBytesElement(&bytes2);
expected.AppendData(data);
data = base::RandBytesAsString(10);
mojom::DataElementBytes bytes3(data.size(), base::nullopt,
CreateBytesProvider(data, mock_time_));
if (GetParam() != MemoryStrategy::NONE_NEEDED) {
strategy->AddBytesElement(&bytes3);
expected.AppendData(data);
}
strategy->BeginTransport(FileInfoVector());
loop.Run();
EXPECT_EQ(BlobStatus::DONE, status);
EXPECT_EQ(expected, builder);
EXPECT_TRUE(bad_messages_.empty());
if (GetParam() == MemoryStrategy::NONE_NEEDED) {
EXPECT_EQ(
0u, reply_request_count_ + stream_request_count_ + file_request_count_);
} else {
EXPECT_EQ(
3u, reply_request_count_ + stream_request_count_ + file_request_count_);
switch (GetParam()) {
case MemoryStrategy::IPC:
EXPECT_EQ(3u, reply_request_count_);
break;
case MemoryStrategy::SHARED_MEMORY:
EXPECT_EQ(3u, stream_request_count_);
break;
default:
NOTREACHED();
}
}
}
INSTANTIATE_TEST_CASE_P(BlobTransportStrategyTest,
BasicTests,
testing::Values(MemoryStrategy::NONE_NEEDED,
MemoryStrategy::IPC,
MemoryStrategy::SHARED_MEMORY));
class BasicErrorTests : public BlobTransportStrategyTest,
public testing::WithParamInterface<MemoryStrategy> {};
TEST_P(BasicErrorTests, NotEnoughBytesInProvider) {
BlobDataBuilder builder(kId);
base::RunLoop loop;
BlobStatus status = BlobStatus::PENDING_TRANSPORT;
auto strategy = BlobTransportStrategy::Create(
GetParam(), &builder,
base::BindOnce(
[](BlobStatus* result_out, base::OnceClosure closure,
BlobStatus result) {
*result_out = result;
std::move(closure).Run();
},
&status, loop.QuitClosure()),
limits_);
std::string data = base::RandBytesAsString(7);
mojom::DataElementBytes bytes(
data.size(), base::nullopt,
CreateBytesProvider(data.substr(0, 4), mock_time_));
strategy->AddBytesElement(&bytes);
strategy->BeginTransport(FileInfoVector());
loop.Run();
EXPECT_TRUE(BlobStatusIsError(status));
EXPECT_EQ(GetParam() == MemoryStrategy::SHARED_MEMORY, bad_messages_.empty());
}
TEST_P(BasicErrorTests, TooManyBytesInProvider) {
BlobDataBuilder builder(kId);
base::RunLoop loop;
BlobStatus status = BlobStatus::PENDING_TRANSPORT;
auto strategy = BlobTransportStrategy::Create(
GetParam(), &builder,
base::BindOnce(
[](BlobStatus* result_out, base::OnceClosure closure,
BlobStatus result) {
*result_out = result;
std::move(closure).Run();
},
&status, loop.QuitClosure()),
limits_);
std::string data = base::RandBytesAsString(4);
mojom::DataElementBytes bytes(
data.size(), base::nullopt,
CreateBytesProvider(data + "foobar", mock_time_));
strategy->AddBytesElement(&bytes);
strategy->BeginTransport(FileInfoVector());
loop.Run();
if (GetParam() == MemoryStrategy::SHARED_MEMORY) {
EXPECT_TRUE(status == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS ||
status == BlobStatus::DONE);
} else {
EXPECT_EQ(BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS, status);
EXPECT_FALSE(bad_messages_.empty());
}
}
INSTANTIATE_TEST_CASE_P(BlobTransportStrategyTest,
BasicErrorTests,
testing::Values(MemoryStrategy::IPC,
MemoryStrategy::SHARED_MEMORY));
TEST_F(BlobTransportStrategyTest, DataStreamChunksData) {
BlobDataBuilder builder(kId);
BlobDataBuilder expected(kId);
base::RunLoop loop;
BlobStatus status = BlobStatus::PENDING_TRANSPORT;
auto strategy = BlobTransportStrategy::Create(
MemoryStrategy::SHARED_MEMORY, &builder,
base::BindOnce(
[](BlobStatus* result_out, base::OnceClosure closure,
BlobStatus result) {
*result_out = result;
std::move(closure).Run();
},
&status, loop.QuitClosure()),
limits_);
std::string data =
base::RandBytesAsString(kTestBlobStorageMaxSharedMemoryBytes * 3 + 13);
mojom::DataElementBytes bytes(data.size(), base::nullopt,
CreateBytesProvider(data, mock_time_));
strategy->AddBytesElement(&bytes);
size_t offset = 0;
while (offset < data.size()) {
expected.AppendData(
data.substr(offset, kTestBlobStorageMaxBytesDataItemSize));
offset += kTestBlobStorageMaxBytesDataItemSize;
}
strategy->BeginTransport(FileInfoVector());
loop.Run();
EXPECT_EQ(BlobStatus::DONE, status);
EXPECT_EQ(expected, builder);
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(1u, stream_request_count_);
EXPECT_EQ(0u, file_request_count_);
}
TEST_F(BlobTransportStrategyTest, Files_NoBytes) {
BlobDataBuilder builder(kId);
BlobDataBuilder expected(kId);
base::RunLoop loop;
BlobStatus status = BlobStatus::PENDING_TRANSPORT;
auto strategy = BlobTransportStrategy::Create(
MemoryStrategy::FILE, &builder,
base::BindOnce(
[](BlobStatus* result_out, base::OnceClosure closure,
BlobStatus result) {
*result_out = result;
std::move(closure).Run();
},
&status, loop.QuitClosure()),
limits_);
strategy->BeginTransport(FileInfoVector());
loop.Run();
EXPECT_EQ(BlobStatus::DONE, status);
EXPECT_EQ(expected, builder);
EXPECT_EQ(0u,
reply_request_count_ + stream_request_count_ + file_request_count_);
EXPECT_TRUE(bad_messages_.empty());
}
TEST_F(BlobTransportStrategyTest, Files_WriteFailed) {
BlobDataBuilder builder(kId);
base::RunLoop loop;
BlobStatus status = BlobStatus::PENDING_TRANSPORT;
auto strategy = BlobTransportStrategy::Create(
MemoryStrategy::FILE, &builder,
base::BindOnce(
[](BlobStatus* result_out, base::OnceClosure closure,
BlobStatus result) {
*result_out = result;
std::move(closure).Run();
},
&status, loop.QuitClosure()),
limits_);
std::string data = base::RandBytesAsString(kTestBlobStorageMaxFileSizeBytes);
mojom::DataElementBytes bytes(data.size(), base::nullopt,
CreateBytesProvider(data, base::nullopt));
strategy->AddBytesElement(&bytes);
FileInfoVector files(1);
{
base::ThreadRestrictions::ScopedAllowIO allow_io;
base::FilePath path;
ASSERT_TRUE(base::CreateTemporaryFileInDir(data_dir_.GetPath(), &path));
files[0].file =
base::File(path, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
files[0].file_deletion_runner = base::ThreadTaskRunnerHandle::Get();
files[0].file_reference = ShareableFileReference::GetOrCreate(
path, ShareableFileReference::DELETE_ON_FINAL_RELEASE,
bytes_provider_runner_.get());
}
strategy->BeginTransport(std::move(files));
loop.Run();
EXPECT_EQ(BlobStatus::ERR_FILE_WRITE_FAILED, status);
}
TEST_F(BlobTransportStrategyTest, Files_ValidBytesOneElement) {
BlobDataBuilder builder(kId);
BlobDataBuilder expected(kId);
base::RunLoop loop;
BlobStatus status = BlobStatus::PENDING_TRANSPORT;
auto strategy = BlobTransportStrategy::Create(
MemoryStrategy::FILE, &builder,
base::BindOnce(
[](BlobStatus* result_out, base::OnceClosure closure,
BlobStatus result) {
*result_out = result;
std::move(closure).Run();
},
&status, loop.QuitClosure()),
limits_);
std::string data =
base::RandBytesAsString(kTestBlobStorageMaxBlobMemorySize + 42);
mojom::DataElementBytes bytes(data.size(), base::nullopt,
CreateBytesProvider(data, mock_time_));
strategy->AddBytesElement(&bytes);
size_t expected_file_count =
1 + data.size() / kTestBlobStorageMaxFileSizeBytes;
FileInfoVector files(expected_file_count);
for (size_t i = 0; i < expected_file_count; ++i) {
base::ThreadRestrictions::ScopedAllowIO allow_io;
base::FilePath path;
ASSERT_TRUE(base::CreateTemporaryFileInDir(data_dir_.GetPath(), &path));
files[i].file =
base::File(path, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
files[i].file_deletion_runner = base::ThreadTaskRunnerHandle::Get();
files[i].file_reference = ShareableFileReference::GetOrCreate(
path, ShareableFileReference::DELETE_ON_FINAL_RELEASE,
bytes_provider_runner_.get());
size_t offset = i * kTestBlobStorageMaxFileSizeBytes;
size_t length = std::min<uint64_t>(kTestBlobStorageMaxFileSizeBytes,
data.size() - offset);
expected.AppendFile(path, 0, length, mock_time_);
}
strategy->BeginTransport(std::move(files));
loop.Run();
EXPECT_EQ(BlobStatus::DONE, status);
EXPECT_EQ(expected, builder);
EXPECT_TRUE(bad_messages_.empty());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(expected_file_count, file_request_count_);
}
TEST_F(BlobTransportStrategyTest, Files_ValidBytesMultipleElements) {
BlobDataBuilder builder(kId);
BlobDataBuilder expected(kId);
base::RunLoop loop;
BlobStatus status = BlobStatus::PENDING_TRANSPORT;
auto strategy = BlobTransportStrategy::Create(
MemoryStrategy::FILE, &builder,
base::BindOnce(
[](BlobStatus* result_out, base::OnceClosure closure,
BlobStatus result) {
*result_out = result;
std::move(closure).Run();
},
&status, loop.QuitClosure()),
limits_);
std::string data =
base::RandBytesAsString(kTestBlobStorageMaxBlobMemorySize / 3);
mojom::DataElementBytes bytes1(data.size(), base::nullopt,
CreateBytesProvider(data, mock_time_));
strategy->AddBytesElement(&bytes1);
mojom::DataElementBytes bytes2(data.size(), base::nullopt,
CreateBytesProvider(data, mock_time_));
strategy->AddBytesElement(&bytes2);
mojom::DataElementBytes bytes3(data.size(), base::nullopt,
CreateBytesProvider(data, mock_time_));
strategy->AddBytesElement(&bytes3);
mojom::DataElementBytes bytes4(data.size(), base::nullopt,
CreateBytesProvider(data, mock_time_));
strategy->AddBytesElement(&bytes4);
size_t expected_file_count =
1 + 4 * data.size() / kTestBlobStorageMaxFileSizeBytes;
FileInfoVector files(expected_file_count);
for (size_t i = 0; i < expected_file_count; ++i) {
base::ThreadRestrictions::ScopedAllowIO allow_io;
base::FilePath path;
ASSERT_TRUE(base::CreateTemporaryFileInDir(data_dir_.GetPath(), &path));
files[i].file =
base::File(path, base::File::FLAG_OPEN | base::File::FLAG_WRITE);
files[i].path = path;
files[i].file_deletion_runner = base::ThreadTaskRunnerHandle::Get();
files[i].file_reference = ShareableFileReference::GetOrCreate(
path, ShareableFileReference::DELETE_ON_FINAL_RELEASE,
bytes_provider_runner_.get());
}
size_t file_offset = 0;
size_t file_index = 0;
size_t expected_request_count = 0;
for (size_t i = 0; i < 4; ++i) {
size_t remaining_size = data.size();
while (remaining_size > 0) {
size_t block_size = std::min<uint64_t>(
kTestBlobStorageMaxFileSizeBytes - file_offset, remaining_size);
expected.AppendFile(files[file_index].path, file_offset, block_size,
mock_time_);
expected_request_count++;
remaining_size -= block_size;
file_offset += block_size;
if (file_offset >= kTestBlobStorageMaxFileSizeBytes) {
file_offset = 0;
file_index++;
}
}
}
strategy->BeginTransport(std::move(files));
loop.Run();
EXPECT_EQ(BlobStatus::DONE, status);
EXPECT_EQ(expected, builder);
EXPECT_TRUE(bad_messages_.empty());
EXPECT_EQ(0u, reply_request_count_);
EXPECT_EQ(0u, stream_request_count_);
EXPECT_EQ(expected_request_count, file_request_count_);
}
} // namespace
} // namespace storage

@ -0,0 +1,58 @@
// Copyright 2017 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 "storage/browser/test/mock_bytes_provider.h"
#include "mojo/common/data_pipe_utils.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace storage {
MockBytesProvider::MockBytesProvider(
std::vector<uint8_t> data,
size_t* reply_request_count,
size_t* stream_request_count,
size_t* file_request_count,
base::Optional<base::Time> file_modification_time)
: data_(std::move(data)),
reply_request_count_(reply_request_count),
stream_request_count_(stream_request_count),
file_request_count_(file_request_count),
file_modification_time_(file_modification_time) {}
MockBytesProvider::~MockBytesProvider() {}
void MockBytesProvider::RequestAsReply(RequestAsReplyCallback callback) {
if (reply_request_count_)
++*reply_request_count_;
std::move(callback).Run(data_);
}
void MockBytesProvider::RequestAsStream(
mojo::ScopedDataPipeProducerHandle pipe) {
if (stream_request_count_)
++*stream_request_count_;
mojo::common::BlockingCopyFromString(
std::string(reinterpret_cast<const char*>(data_.data()), data_.size()),
pipe);
}
void MockBytesProvider::RequestAsFile(uint64_t source_offset,
uint64_t source_size,
base::File file,
uint64_t file_offset,
RequestAsFileCallback callback) {
if (file_request_count_)
++*file_request_count_;
EXPECT_LE(source_offset + source_size, data_.size());
EXPECT_EQ(source_size,
static_cast<uint64_t>(file.Write(
file_offset,
reinterpret_cast<const char*>(data_.data() + source_offset),
source_size)));
EXPECT_TRUE(file.Flush());
std::move(callback).Run(file_modification_time_);
}
} // namespace storage

@ -0,0 +1,44 @@
// Copyright 2017 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.
#ifndef STORAGE_BROWSER_TEST_MOCK_BYTES_PROVIDER_H_
#define STORAGE_BROWSER_TEST_MOCK_BYTES_PROVIDER_H_
#include "storage/public/interfaces/blobs.mojom.h"
namespace storage {
// Mock BytesProvider implementation. RequestAsStream blocks, so make sure to
// bind this implementation to a pipe on a separate sequence from where the
// bytes are consumed.
class MockBytesProvider : public mojom::BytesProvider {
public:
explicit MockBytesProvider(
std::vector<uint8_t> data,
size_t* reply_request_count = nullptr,
size_t* stream_request_count = nullptr,
size_t* file_request_count = nullptr,
base::Optional<base::Time> file_modification_time = base::Time());
~MockBytesProvider() override;
// BytesProvider implementation:
void RequestAsReply(RequestAsReplyCallback callback) override;
void RequestAsStream(mojo::ScopedDataPipeProducerHandle pipe) override;
void RequestAsFile(uint64_t source_offset,
uint64_t source_size,
base::File file,
uint64_t file_offset,
RequestAsFileCallback callback) override;
private:
std::vector<uint8_t> data_;
size_t* reply_request_count_;
size_t* stream_request_count_;
size_t* file_request_count_;
base::Optional<base::Time> file_modification_time_;
};
} // namespace storage
#endif // STORAGE_BROWSER_TEST_MOCK_BYTES_PROVIDER_H_

@ -17,6 +17,7 @@ static_assert(kDefaultMinPageFileSize < kDefaultMaxBlobInMemorySpace,
bool BlobStorageLimits::IsValid() const {
return max_ipc_memory_size < max_shared_memory_size &&
max_ipc_memory_size < max_bytes_data_item_size &&
min_page_file_size < max_file_size &&
min_page_file_size < max_blob_in_memory_space &&
effective_max_disk_space <= desired_max_disk_space;

@ -52,6 +52,12 @@ struct STORAGE_COMMON_EXPORT BlobStorageLimits {
// This is the maximum size of a shared memory handle.
size_t max_shared_memory_size = kDefaultSharedMemorySize;
// This is the maximum size of a bytes BlobDataItem. Only used for mojo
// based blob transportation, as the old IPC/shared memory based
// implementation doesn't support different values for this and
// max_shared_memory_size.
size_t max_bytes_data_item_size = kDefaultSharedMemorySize;
// This is the maximum amount of memory we can use to store blobs.
size_t max_blob_in_memory_space = kDefaultMaxBlobInMemorySpace;
// The ratio applied to |max_blob_in_memory_space| to reduce memory usage