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:

committed by
Commit Bot

parent
7f82c88750
commit
2d263e6d8f
@ -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(®istry_), 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;
|
||||
|
||||
|
386
storage/browser/blob/blob_transport_strategy.cc
Normal file
386
storage/browser/blob/blob_transport_strategy.cc
Normal file
@ -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
|
57
storage/browser/blob/blob_transport_strategy.h
Normal file
57
storage/browser/blob/blob_transport_strategy.h
Normal file
@ -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_
|
520
storage/browser/blob/blob_transport_strategy_unittest.cc
Normal file
520
storage/browser/blob/blob_transport_strategy_unittest.cc
Normal file
@ -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
|
58
storage/browser/test/mock_bytes_provider.cc
Normal file
58
storage/browser/test/mock_bytes_provider.cc
Normal file
@ -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
|
44
storage/browser/test/mock_bytes_provider.h
Normal file
44
storage/browser/test/mock_bytes_provider.h
Normal file
@ -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
|
||||
|
Reference in New Issue
Block a user