blob: e31518bd93bd54d587667f656d5247fca5ba8d9d [file] [log] [blame]
// 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_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"
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) {}
void Clone(mojom::BlobRequest request) override {
mojo::MakeStrongBinding(base::MakeUnique<MockBlob>(uuid_),
std::move(request));
}
void GetInternalUUID(GetInternalUUIDCallback callback) override {
std::move(callback).Run(uuid_);
}
private:
std::string uuid_;
};
class MockDelegate : public BlobRegistryImpl::Delegate {
public:
MockDelegate() {}
~MockDelegate() override {}
bool CanReadFile(const base::FilePath& file) override {
return can_read_file_result;
}
bool CanReadFileSystemFile(const FileSystemURL& url) override {
return can_read_file_system_file_result;
}
bool can_read_file_result = true;
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>(
data_dir_.GetPath(),
base::CreateTaskRunnerWithTraits({base::MayBlock()}));
auto storage_policy =
base::MakeRefCounted<content::MockSpecialStoragePolicy>();
file_system_context_ = base::MakeRefCounted<storage::FileSystemContext>(
base::ThreadTaskRunnerHandle::Get().get(),
base::ThreadTaskRunnerHandle::Get().get(),
nullptr /* external_mount_points */, storage_policy.get(),
nullptr /* quota_manager_proxy */,
std::vector<std::unique_ptr<FileSystemBackend>>(),
std::vector<URLRequestAutoMountHandler>(), data_dir_.GetPath(),
FileSystemOptions(FileSystemOptions::PROFILE_MODE_INCOGNITO,
std::vector<std::string>(), nullptr));
registry_impl_ = base::MakeUnique<BlobRegistryImpl>(context_.get(),
file_system_context_);
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());
}
std::unique_ptr<BlobDataHandle> CreateBlobFromString(
const std::string& uuid,
const std::string& contents) {
BlobDataBuilder builder(uuid);
builder.set_content_type("text/plain");
builder.AppendData(contents);
return context_->AddFinishedBlob(builder);
}
std::string UUIDFromBlob(mojom::Blob* blob) {
base::RunLoop loop;
std::string received_uuid;
blob->GetInternalUUID(base::Bind(
[](base::Closure quit_closure, std::string* uuid_out,
const std::string& uuid) {
*uuid_out = uuid;
quit_closure.Run();
},
loop.QuitClosure(), &received_uuid));
loop.Run();
return received_uuid;
}
void OnBadMessage(const std::string& error) {
bad_messages_.push_back(error);
}
void WaitForBlobCompletion(BlobDataHandle* blob_handle) {
base::RunLoop loop;
blob_handle->RunOnConstructionComplete(base::Bind(
[](const base::Closure& closure, BlobStatus status) { closure.Run(); },
loop.QuitClosure()));
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_;
std::unique_ptr<BlobStorageContext> context_;
scoped_refptr<storage::FileSystemContext> file_system_context_;
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_;
};
TEST_F(BlobRegistryImplTest, GetBlobFromUUID) {
const std::string kId = "id";
std::unique_ptr<BlobDataHandle> handle =
CreateBlobFromString(kId, "hello world");
{
mojom::BlobPtr blob;
registry_->GetBlobFromUUID(MakeRequest(&blob), kId);
EXPECT_EQ(kId, UUIDFromBlob(blob.get()));
EXPECT_FALSE(blob.encountered_error());
}
{
mojom::BlobPtr blob;
registry_->GetBlobFromUUID(MakeRequest(&blob), "invalid id");
blob.FlushForTesting();
EXPECT_TRUE(blob.encountered_error());
}
}
TEST_F(BlobRegistryImplTest, GetBlobFromEmptyUUID) {
mojom::BlobPtr blob;
registry_->GetBlobFromUUID(MakeRequest(&blob), "");
blob.FlushForTesting();
EXPECT_EQ(1u, bad_messages_.size());
EXPECT_TRUE(blob.encountered_error());
}
TEST_F(BlobRegistryImplTest, Register_EmptyUUID) {
mojom::BlobPtr blob;
EXPECT_FALSE(registry_->Register(MakeRequest(&blob), "", "", "",
std::vector<mojom::DataElementPtr>()));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
blob.FlushForTesting();
EXPECT_TRUE(blob.encountered_error());
}
TEST_F(BlobRegistryImplTest, Register_ExistingUUID) {
const std::string kId = "id";
std::unique_ptr<BlobDataHandle> handle =
CreateBlobFromString(kId, "hello world");
mojom::BlobPtr blob;
EXPECT_FALSE(registry_->Register(MakeRequest(&blob), kId, "", "",
std::vector<mojom::DataElementPtr>()));
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
blob.FlushForTesting();
EXPECT_TRUE(blob.encountered_error());
}
TEST_F(BlobRegistryImplTest, Register_EmptyBlob) {
const std::string kId = "id";
const std::string kContentType = "content/type";
const std::string kContentDisposition = "disposition";
mojom::BlobPtr blob;
EXPECT_TRUE(registry_->Register(MakeRequest(&blob), kId, kContentType,
kContentDisposition,
std::vector<mojom::DataElementPtr>()));
EXPECT_TRUE(bad_messages_.empty());
EXPECT_EQ(kId, UUIDFromBlob(blob.get()));
EXPECT_TRUE(context_->registry().HasEntry(kId));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
EXPECT_EQ(kContentType, handle->content_type());
EXPECT_EQ(kContentDisposition, handle->content_disposition());
EXPECT_EQ(0u, handle->size());
WaitForBlobCompletion(handle.get());
EXPECT_FALSE(handle->IsBroken());
EXPECT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
}
TEST_F(BlobRegistryImplTest, Register_ReferencedBlobClosedPipe) {
const std::string kId = "id";
std::vector<mojom::DataElementPtr> elements;
mojom::BlobPtr referenced_blob;
MakeRequest(&referenced_blob);
elements.push_back(mojom::DataElement::NewBlob(
mojom::DataElementBlob::New(std::move(referenced_blob), 0, 16)));
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_REFERENCED_BLOB_BROKEN, handle->GetBlobStatus());
}
TEST_F(BlobRegistryImplTest, Register_SelfReference) {
const std::string kId = "id";
mojom::BlobPtr blob;
mojom::BlobRequest blob_request = MakeRequest(&blob);
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewBlob(
mojom::DataElementBlob::New(std::move(blob), 0, 16)));
EXPECT_TRUE(registry_->Register(std::move(blob_request), 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_INVALID_CONSTRUCTION_ARGUMENTS,
handle->GetBlobStatus());
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
}
TEST_F(BlobRegistryImplTest, Register_CircularReference) {
const std::string kId1 = "id1";
const std::string kId2 = "id2";
const std::string kId3 = "id3";
mojom::BlobPtr blob1, blob2, blob3;
mojom::BlobRequest blob_request1 = MakeRequest(&blob1);
mojom::BlobRequest blob_request2 = MakeRequest(&blob2);
mojom::BlobRequest blob_request3 = MakeRequest(&blob3);
std::vector<mojom::DataElementPtr> elements1;
elements1.push_back(mojom::DataElement::NewBlob(
mojom::DataElementBlob::New(std::move(blob1), 0, 16)));
std::vector<mojom::DataElementPtr> elements2;
elements2.push_back(mojom::DataElement::NewBlob(
mojom::DataElementBlob::New(std::move(blob2), 0, 16)));
std::vector<mojom::DataElementPtr> elements3;
elements3.push_back(mojom::DataElement::NewBlob(
mojom::DataElementBlob::New(std::move(blob3), 0, 16)));
EXPECT_TRUE(registry_->Register(std::move(blob_request1), kId1, "", "",
std::move(elements2)));
EXPECT_TRUE(registry_->Register(std::move(blob_request2), kId2, "", "",
std::move(elements3)));
EXPECT_TRUE(registry_->Register(std::move(blob_request3), kId3, "", "",
std::move(elements1)));
EXPECT_TRUE(bad_messages_.empty());
#if DCHECK_IS_ON()
// Without DCHECKs on this will just hang forever.
std::unique_ptr<BlobDataHandle> handle1 = context_->GetBlobDataFromUUID(kId1);
std::unique_ptr<BlobDataHandle> handle2 = context_->GetBlobDataFromUUID(kId2);
std::unique_ptr<BlobDataHandle> handle3 = context_->GetBlobDataFromUUID(kId3);
WaitForBlobCompletion(handle1.get());
WaitForBlobCompletion(handle2.get());
WaitForBlobCompletion(handle3.get());
EXPECT_TRUE(handle1->IsBroken());
EXPECT_TRUE(handle2->IsBroken());
EXPECT_TRUE(handle3->IsBroken());
BlobStatus status1 = handle1->GetBlobStatus();
BlobStatus status2 = handle2->GetBlobStatus();
BlobStatus status3 = handle3->GetBlobStatus();
EXPECT_TRUE(status1 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS ||
status1 == BlobStatus::ERR_REFERENCED_BLOB_BROKEN);
EXPECT_TRUE(status2 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS ||
status2 == BlobStatus::ERR_REFERENCED_BLOB_BROKEN);
EXPECT_TRUE(status3 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS ||
status3 == BlobStatus::ERR_REFERENCED_BLOB_BROKEN);
EXPECT_EQ((status1 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS) +
(status2 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS) +
(status3 == BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS),
1);
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
#endif
}
TEST_F(BlobRegistryImplTest, Register_NonExistentBlob) {
const std::string kId = "id";
std::vector<mojom::DataElementPtr> elements;
mojom::BlobPtr referenced_blob;
mojo::MakeStrongBinding(base::MakeUnique<MockBlob>("mock blob"),
MakeRequest(&referenced_blob));
elements.push_back(mojom::DataElement::NewBlob(
mojom::DataElementBlob::New(std::move(referenced_blob), 0, 16)));
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_INVALID_CONSTRUCTION_ARGUMENTS,
handle->GetBlobStatus());
EXPECT_EQ(1u, bad_messages_.size());
registry_.FlushForTesting();
EXPECT_TRUE(registry_.encountered_error());
}
TEST_F(BlobRegistryImplTest, Register_ValidBlobReferences) {
const std::string kId1 = "id1";
std::unique_ptr<BlobDataHandle> handle =
CreateBlobFromString(kId1, "hello world");
mojom::BlobPtr blob1;
mojo::MakeStrongBinding(base::MakeUnique<MockBlob>(kId1),
MakeRequest(&blob1));
const std::string kId2 = "id2";
mojom::BlobPtr blob2;
mojom::BlobRequest blob_request2 = MakeRequest(&blob2);
std::vector<mojom::DataElementPtr> elements1;
elements1.push_back(mojom::DataElement::NewBlob(
mojom::DataElementBlob::New(std::move(blob1), 0, 8)));
std::vector<mojom::DataElementPtr> elements2;
elements2.push_back(mojom::DataElement::NewBlob(
mojom::DataElementBlob::New(std::move(blob2), 0, 8)));
mojom::BlobPtr final_blob;
const std::string kId3 = "id3";
EXPECT_TRUE(registry_->Register(MakeRequest(&final_blob), kId3, "", "",
std::move(elements2)));
EXPECT_TRUE(registry_->Register(std::move(blob_request2), kId2, "", "",
std::move(elements1)));
// kId3 references kId2, kId2 reference kId1, kId1 is a simple string.
std::unique_ptr<BlobDataHandle> handle2 = context_->GetBlobDataFromUUID(kId2);
std::unique_ptr<BlobDataHandle> handle3 = context_->GetBlobDataFromUUID(kId3);
WaitForBlobCompletion(handle2.get());
WaitForBlobCompletion(handle3.get());
EXPECT_FALSE(handle2->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle2->GetBlobStatus());
EXPECT_FALSE(handle3->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle3->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId2);
expected_blob_data.AppendData("hello wo");
EXPECT_EQ(expected_blob_data, *handle2->CreateSnapshot());
EXPECT_EQ(expected_blob_data, *handle3->CreateSnapshot());
}
TEST_F(BlobRegistryImplTest, Register_UnreadableFile) {
delegate_ptr_->can_read_file_result = false;
const std::string kId = "id";
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewFile(mojom::DataElementFile::New(
base::FilePath(FILE_PATH_LITERAL("foobar")), 0, 16, base::nullopt)));
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_FILE_WRITE_FAILED, handle->GetBlobStatus());
}
TEST_F(BlobRegistryImplTest, Register_ValidFile) {
delegate_ptr_->can_read_file_result = true;
const std::string kId = "id";
const base::FilePath path(FILE_PATH_LITERAL("foobar"));
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewFile(
mojom::DataElementFile::New(path, 0, 16, base::nullopt)));
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_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendFile(path, 0, 16, base::Time());
EXPECT_EQ(expected_blob_data, *handle->CreateSnapshot());
}
TEST_F(BlobRegistryImplTest, Register_FileSystemFile_InvalidScheme) {
const std::string kId = "id";
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewFileFilesystem(
mojom::DataElementFilesystemURL::New(GURL("http://foobar.com/"), 0, 16,
base::nullopt)));
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_FILE_WRITE_FAILED, handle->GetBlobStatus());
}
TEST_F(BlobRegistryImplTest, Register_FileSystemFile_UnreadablFile) {
delegate_ptr_->can_read_file_system_file_result = false;
const std::string kId = "id";
const GURL url("filesystem:http://example.com/temporary/myfile.png");
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewFileFilesystem(
mojom::DataElementFilesystemURL::New(url, 0, 16, base::nullopt)));
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_FILE_WRITE_FAILED, handle->GetBlobStatus());
}
TEST_F(BlobRegistryImplTest, Register_FileSystemFile_Valid) {
delegate_ptr_->can_read_file_system_file_result = true;
const std::string kId = "id";
const GURL url("filesystem:http://example.com/temporary/myfile.png");
std::vector<mojom::DataElementPtr> elements;
elements.push_back(mojom::DataElement::NewFileFilesystem(
mojom::DataElementFilesystemURL::New(url, 0, 16, base::nullopt)));
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_FALSE(handle->IsBroken());
ASSERT_EQ(BlobStatus::DONE, handle->GetBlobStatus());
BlobDataBuilder expected_blob_data(kId);
expected_blob_data.AppendFileSystemFile(url, 0, 16, base::Time());
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