blob: f5ae770266b633c07287a32676cdb4375df7f23b [file] [log] [blame]
// Copyright 2016 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 "content/browser/blob_storage/blob_dispatcher_host.h"
#include <memory>
#include <tuple>
#include <vector>
#include "base/command_line.h"
#include "base/memory/shared_memory.h"
#include "base/run_loop.h"
#include "base/strings/string_number_conversions.h"
#include "content/browser/blob_storage/chrome_blob_storage_context.h"
#include "content/common/fileapi/webblob_messages.h"
#include "content/public/common/content_switches.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_browser_thread_bundle.h"
#include "content/public/test/test_file_system_context.h"
#include "ipc/ipc_sender.h"
#include "ipc/ipc_test_sink.h"
#include "ipc/message_filter.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/common/blob_storage/blob_item_bytes_request.h"
#include "storage/common/blob_storage/blob_item_bytes_response.h"
#include "storage/common/data_element.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using storage::BlobDataBuilder;
using storage::BlobDataHandle;
using storage::BlobItemBytesRequest;
using storage::BlobItemBytesResponse;
using storage::BlobStatus;
using storage::BlobStorageContext;
using storage::DataElement;
using RequestMemoryCallback = storage::BlobTransportHost::RequestMemoryCallback;
namespace content {
namespace {
const char kContentType[] = "text/plain";
const char kContentDisposition[] = "content_disposition";
const char kData[] = "data!!";
const size_t kDataSize = 6;
const size_t kTestBlobStorageIPCThresholdBytes = 20;
const size_t kTestBlobStorageMaxSharedMemoryBytes = 50;
const size_t kTestBlobStorageMaxBlobMemorySize = 400;
const uint64_t kTestBlobStorageMaxDiskSpace = 1000;
const uint64_t kTestBlobStorageMinFileSizeBytes = 10;
const uint64_t kTestBlobStorageMaxFileSizeBytes = 100;
void ConstructionCompletePopulator(bool* success_pointer,
BlobStatus* reason_pointer,
BlobStatus reason) {
*reason_pointer = reason;
*success_pointer = reason == BlobStatus::DONE;
}
// TODO(dmurph): Create file test that verifies security policy.
class TestableBlobDispatcherHost : public BlobDispatcherHost {
public:
TestableBlobDispatcherHost(ChromeBlobStorageContext* blob_storage_context,
storage::FileSystemContext* file_system_context,
IPC::TestSink* sink)
: BlobDispatcherHost(0 /* process_id */,
make_scoped_refptr(blob_storage_context),
make_scoped_refptr(file_system_context)),
sink_(sink) {}
bool Send(IPC::Message* message) override { return sink_->Send(message); }
void ShutdownForBadMessage() override { shutdown_for_bad_message_ = true; }
bool shutdown_for_bad_message_ = false;
protected:
~TestableBlobDispatcherHost() override {}
private:
friend class base::RefCountedThreadSafe<TestableBlobDispatcherHost>;
IPC::TestSink* sink_;
};
} // namespace
class BlobDispatcherHostTest : public testing::Test {
protected:
BlobDispatcherHostTest()
: chrome_blob_storage_context_(
ChromeBlobStorageContext::GetFor(&browser_context_)) {
file_system_context_ =
CreateFileSystemContextForTesting(NULL, base::FilePath());
host_ = new TestableBlobDispatcherHost(chrome_blob_storage_context_,
file_system_context_.get(), &sink_);
}
~BlobDispatcherHostTest() override {}
void SetUp() override {
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (!command_line->HasSwitch(switches::kDisableKillAfterBadIPC)) {
command_line->AppendSwitch(switches::kDisableKillAfterBadIPC);
}
// We run the run loop to initialize the chrome blob storage context.
base::RunLoop().RunUntilIdle();
context_ = chrome_blob_storage_context_->context();
ASSERT_TRUE(context_);
storage::BlobStorageLimits limits;
limits.max_ipc_memory_size = kTestBlobStorageIPCThresholdBytes;
limits.max_shared_memory_size = kTestBlobStorageMaxSharedMemoryBytes;
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);
}
void ExpectBlobNotExist(const std::string& id) {
EXPECT_FALSE(context_->registry().HasEntry(id));
EXPECT_FALSE(host_->IsInUseInHost(id));
EXPECT_FALSE(IsBeingBuiltInHost(id));
}
void AsyncShortcutBlobTransfer(const std::string& id) {
sink_.ClearMessages();
ExpectBlobNotExist(id);
DataElement element;
element.SetToBytes(kData, kDataSize);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(id, std::string(kContentType),
std::string(kContentDisposition), elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
ExpectDone(id);
sink_.ClearMessages();
}
void AsyncBlobTransfer(const std::string& id) {
sink_.ClearMessages();
ExpectBlobNotExist(id);
DataElement element;
element.SetToBytesDescription(kDataSize);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(id, std::string(kContentType),
std::string(kContentDisposition), elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
// Expect our request.
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
ExpectRequest(id, expected_requests);
sink_.ClearMessages();
// Send results;
BlobItemBytesResponse response(0);
std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
std::vector<BlobItemBytesResponse> responses = {response};
host_->OnMemoryItemResponse(id, responses);
ExpectDone(id);
sink_.ClearMessages();
}
void ExpectAndResetBadMessage() {
EXPECT_TRUE(host_->shutdown_for_bad_message_);
host_->shutdown_for_bad_message_ = false;
}
void ExpectHandleEqualsData(BlobDataHandle* handle,
const std::vector<DataElement>& data) {
std::unique_ptr<storage::BlobDataSnapshot> snapshot =
handle->CreateSnapshot();
EXPECT_FALSE(handle->IsBeingBuilt());
for (size_t i = 0; i < data.size(); i++) {
const DataElement& expected = data[i];
const DataElement& actual = snapshot->items()[i]->data_element();
EXPECT_EQ(expected, actual);
}
EXPECT_EQ(data.size(), snapshot->items().size());
}
void ExpectRequest(
const std::string& expected_uuid,
const std::vector<BlobItemBytesRequest>& expected_requests) {
EXPECT_FALSE(
sink_.GetFirstMessageMatching(BlobStorageMsg_SendBlobStatus::ID));
const IPC::Message* message =
sink_.GetUniqueMessageMatching(BlobStorageMsg_RequestMemoryItem::ID);
ASSERT_TRUE(message);
std::tuple<std::string, std::vector<storage::BlobItemBytesRequest>,
std::vector<base::SharedMemoryHandle>,
std::vector<IPC::PlatformFileForTransit>>
args;
BlobStorageMsg_RequestMemoryItem::Read(message, &args);
EXPECT_EQ(expected_uuid, std::get<0>(args));
std::vector<BlobItemBytesRequest> requests = std::get<1>(args);
ASSERT_EQ(requests.size(), expected_requests.size());
for (size_t i = 0; i < expected_requests.size(); ++i) {
EXPECT_EQ(expected_requests[i], requests[i]);
}
}
void ExpectRequestWithSharedMemoryHandles(
const std::string& expected_uuid,
const std::vector<BlobItemBytesRequest>& expected_requests,
std::vector<base::SharedMemoryHandle>* shared_memory_handles) {
EXPECT_FALSE(
sink_.GetFirstMessageMatching(BlobStorageMsg_SendBlobStatus::ID));
const IPC::Message* message =
sink_.GetUniqueMessageMatching(BlobStorageMsg_RequestMemoryItem::ID);
ASSERT_TRUE(message);
std::tuple<std::string, std::vector<storage::BlobItemBytesRequest>,
std::vector<base::SharedMemoryHandle>,
std::vector<IPC::PlatformFileForTransit>>
args;
BlobStorageMsg_RequestMemoryItem::Read(message, &args);
EXPECT_EQ(expected_uuid, std::get<0>(args));
std::vector<BlobItemBytesRequest> requests = std::get<1>(args);
ASSERT_EQ(requests.size(), expected_requests.size());
for (size_t i = 0; i < expected_requests.size(); ++i) {
EXPECT_EQ(expected_requests[i], requests[i]);
}
*shared_memory_handles = std::move(std::get<2>(args));
}
void ExpectCancel(const std::string& expected_uuid, BlobStatus code) {
EXPECT_FALSE(
sink_.GetFirstMessageMatching(BlobStorageMsg_RequestMemoryItem::ID));
const IPC::Message* message =
sink_.GetUniqueMessageMatching(BlobStorageMsg_SendBlobStatus::ID);
ASSERT_TRUE(message);
std::tuple<std::string, BlobStatus> args;
BlobStorageMsg_SendBlobStatus::Read(message, &args);
EXPECT_EQ(expected_uuid, std::get<0>(args));
EXPECT_EQ(code, std::get<1>(args));
}
void ExpectDone(const std::string& expected_uuid) {
EXPECT_FALSE(
sink_.GetFirstMessageMatching(BlobStorageMsg_RequestMemoryItem::ID));
const IPC::Message* message =
sink_.GetUniqueMessageMatching(BlobStorageMsg_SendBlobStatus::ID);
ASSERT_TRUE(message);
std::tuple<std::string, BlobStatus> args;
BlobStorageMsg_SendBlobStatus::Read(message, &args);
EXPECT_EQ(expected_uuid, std::get<0>(args));
EXPECT_EQ(BlobStatus::DONE, std::get<1>(args));
}
bool IsBeingBuiltInHost(const std::string& uuid) {
return host_->transport_host_.IsBeingBuilt(uuid);
}
bool IsBeingBuiltInContext(const std::string& uuid) {
return BlobStatusIsPending(context_->GetBlobStatus(uuid));
}
IPC::TestSink sink_;
TestBrowserThreadBundle browser_thread_bundle_;
TestBrowserContext browser_context_;
ChromeBlobStorageContext* chrome_blob_storage_context_;
BlobStorageContext* context_ = nullptr;
scoped_refptr<storage::FileSystemContext> file_system_context_;
scoped_refptr<TestableBlobDispatcherHost> host_;
};
TEST_F(BlobDispatcherHostTest, EmptyUUIDs) {
host_->OnRegisterBlob("", "", "", std::vector<DataElement>());
ExpectAndResetBadMessage();
host_->OnMemoryItemResponse("", std::vector<BlobItemBytesResponse>());
ExpectAndResetBadMessage();
host_->OnCancelBuildingBlob("",
BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS);
ExpectAndResetBadMessage();
}
TEST_F(BlobDispatcherHostTest, Shortcut) {
const std::string kId = "uuid1";
AsyncShortcutBlobTransfer(kId);
EXPECT_TRUE(context_->registry().HasEntry(kId));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
EXPECT_TRUE(handle);
DataElement expected;
expected.SetToBytes(kData, kDataSize);
std::vector<DataElement> elements = {expected};
ExpectHandleEqualsData(handle.get(), elements);
}
TEST_F(BlobDispatcherHostTest, RegularTransfer) {
const std::string kId = "uuid1";
AsyncBlobTransfer(kId);
EXPECT_TRUE(context_->registry().HasEntry(kId));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
EXPECT_TRUE(handle);
DataElement expected;
expected.SetToBytes(kData, kDataSize);
std::vector<DataElement> elements = {expected};
ExpectHandleEqualsData(handle.get(), elements);
}
TEST_F(BlobDispatcherHostTest, MultipleTransfers) {
const std::string kId = "uuid";
const int kNumIters = 10;
sink_.ClearMessages();
for (int i = 0; i < kNumIters; i++) {
std::string id = kId;
id += ('0' + i);
ExpectBlobNotExist(id);
DataElement element;
element.SetToBytesDescription(kDataSize);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(id, std::string(kContentType),
std::string(kContentDisposition), elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
// Expect our request.
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
ExpectRequest(id, expected_requests);
sink_.ClearMessages();
}
for (int i = 0; i < kNumIters; i++) {
std::string id = kId;
id += ('0' + i);
// Send results;
BlobItemBytesResponse response(0);
std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
std::vector<BlobItemBytesResponse> responses = {response};
host_->OnMemoryItemResponse(id, responses);
ExpectDone(id);
sink_.ClearMessages();
}
}
TEST_F(BlobDispatcherHostTest, SharedMemoryTransfer) {
const std::string kId = "uuid1";
const size_t kLargeSize = kTestBlobStorageMaxSharedMemoryBytes * 2;
std::vector<base::SharedMemoryHandle> shared_memory_handles;
ExpectBlobNotExist(kId);
DataElement element;
element.SetToBytesDescription(kLargeSize);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
// Grab the handle.
std::unique_ptr<BlobDataHandle> blob_data_handle =
context_->GetBlobDataFromUUID(kId);
bool built = false;
BlobStatus error_code = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
blob_data_handle->RunOnConstructionComplete(
base::Bind(&ConstructionCompletePopulator, &built, &error_code));
EXPECT_FALSE(built);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
// Expect our first request.
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateSharedMemoryRequest(
0 /* request_number */, 0 /* renderer_item_index */,
0 /* renderer_item_offset */,
static_cast<uint64_t>(
kTestBlobStorageMaxSharedMemoryBytes) /* size */,
0 /* handle_index */, 0 /* handle_offset */)};
ExpectRequestWithSharedMemoryHandles(kId, expected_requests,
&shared_memory_handles);
sink_.ClearMessages();
// Populate the shared memory.
EXPECT_EQ(1u, shared_memory_handles.size());
EXPECT_TRUE(base::SharedMemory::IsHandleValid(shared_memory_handles[0]));
{
base::SharedMemory memory(
base::SharedMemory::DuplicateHandle(shared_memory_handles[0]), false);
memory.Map(kTestBlobStorageMaxSharedMemoryBytes);
std::memset(memory.memory(), 'X', kTestBlobStorageMaxSharedMemoryBytes);
memory.Close();
}
// Send the confirmation.
std::vector<BlobItemBytesResponse> responses = {BlobItemBytesResponse(0)};
host_->OnMemoryItemResponse(kId, responses);
// Expect our second request.
expected_requests = {BlobItemBytesRequest::CreateSharedMemoryRequest(
1 /* request_number */, 0 /* renderer_item_index */,
static_cast<uint64_t>(
kTestBlobStorageMaxSharedMemoryBytes) /* renderer_item_offset */,
static_cast<uint64_t>(kTestBlobStorageMaxSharedMemoryBytes) /* size */,
0 /* handle_index */, 0 /* handle_offset */)};
ExpectRequestWithSharedMemoryHandles(kId, expected_requests,
&shared_memory_handles);
sink_.ClearMessages();
// Populate the shared memory.
EXPECT_EQ(1u, shared_memory_handles.size());
EXPECT_TRUE(base::SharedMemory::IsHandleValid(shared_memory_handles[0]));
{
base::SharedMemory memory(
base::SharedMemory::DuplicateHandle(shared_memory_handles[0]), false);
memory.Map(kTestBlobStorageMaxSharedMemoryBytes);
std::memset(memory.memory(), 'Z', kTestBlobStorageMaxSharedMemoryBytes);
memory.Close();
}
// Send the confirmation.
responses = {BlobItemBytesResponse(1)};
host_->OnMemoryItemResponse(kId, responses);
ExpectDone(kId);
sink_.ClearMessages();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(built) << "Error code: " << static_cast<int>(error_code);
EXPECT_TRUE(context_->registry().HasEntry(kId));
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
EXPECT_TRUE(handle);
DataElement expected;
expected.SetToAllocatedBytes(kLargeSize / 2);
std::memset(expected.mutable_bytes(), 'X', kLargeSize / 2);
elements = {expected};
std::memset(expected.mutable_bytes(), 'Z', kLargeSize / 2);
elements.push_back(expected);
ExpectHandleEqualsData(handle.get(), elements);
}
TEST_F(BlobDispatcherHostTest, OnCancelBuildingBlob) {
const std::string kId("id");
// We ignore blobs that are unknown, as it could have been cancelled earlier
// and the renderer didn't know about it yet.
host_->OnCancelBuildingBlob(kId,
BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
// Start building blob.
DataElement element;
element.SetToBytesDescription(kDataSize);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
// It should have requested memory here.
EXPECT_FALSE(host_->shutdown_for_bad_message_);
sink_.ClearMessages();
// Cancel in middle of construction.
host_->OnCancelBuildingBlob(kId, BlobStatus::ERR_OUT_OF_MEMORY);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(host_->IsInUseInHost(kId));
EXPECT_FALSE(IsBeingBuiltInHost(kId));
// Check that's it's broken.
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
EXPECT_TRUE(handle->IsBroken());
handle.reset();
base::RunLoop().RunUntilIdle();
// Get rid of it in the host.
host_->OnDecrementBlobRefCount(kId);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
ExpectBlobNotExist(kId);
// Create blob again to verify we don't have any old construction state lying
// around.
AsyncBlobTransfer(kId);
// Check data.
handle = context_->GetBlobDataFromUUID(kId);
EXPECT_TRUE(handle);
DataElement expected;
expected.SetToBytes(kData, kDataSize);
std::vector<DataElement> expecteds = {expected};
ExpectHandleEqualsData(handle.get(), expecteds);
// Verify we can't cancel after the fact.
host_->OnCancelBuildingBlob(kId, BlobStatus::ERR_OUT_OF_MEMORY);
ExpectAndResetBadMessage();
}
TEST_F(BlobDispatcherHostTest, BlobDataWithHostDeletion) {
// Build up a basic blob.
const std::string kId("id");
AsyncShortcutBlobTransfer(kId);
std::unique_ptr<BlobDataHandle> handle = context_->GetBlobDataFromUUID(kId);
EXPECT_TRUE(handle);
// Kill the host.
host_ = nullptr;
base::RunLoop().RunUntilIdle();
// Should still be there due to the handle.
std::unique_ptr<BlobDataHandle> another_handle =
context_->GetBlobDataFromUUID(kId);
EXPECT_TRUE(another_handle);
// Should disappear after dropping both handles.
handle.reset();
another_handle.reset();
base::RunLoop().RunUntilIdle();
handle = context_->GetBlobDataFromUUID(kId);
EXPECT_FALSE(handle);
}
TEST_F(BlobDispatcherHostTest, BlobReferenceWhileConstructing) {
const std::string kId("id");
// Start building blob.
DataElement element;
element.SetToBytesDescription(kDataSize);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
// Grab the handle.
std::unique_ptr<BlobDataHandle> blob_data_handle =
context_->GetBlobDataFromUUID(kId);
EXPECT_TRUE(blob_data_handle);
EXPECT_TRUE(blob_data_handle->IsBeingBuilt());
bool built = false;
BlobStatus error_code = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
blob_data_handle->RunOnConstructionComplete(
base::Bind(&ConstructionCompletePopulator, &built, &error_code));
sink_.ClearMessages();
// Send data.
BlobItemBytesResponse response(0);
std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
std::vector<BlobItemBytesResponse> responses = {response};
sink_.ClearMessages();
host_->OnMemoryItemResponse(kId, responses);
ExpectDone(kId);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(built) << "Error code: " << static_cast<int>(error_code);
}
TEST_F(BlobDispatcherHostTest, BlobReferenceWhileConstructingCancelled) {
const std::string kId("id");
// Start building blob.
DataElement element;
element.SetToBytesDescription(kDataSize);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
// Grab the handle.
std::unique_ptr<BlobDataHandle> blob_data_handle =
context_->GetBlobDataFromUUID(kId);
EXPECT_TRUE(blob_data_handle);
EXPECT_TRUE(blob_data_handle->IsBeingBuilt());
bool built = true;
BlobStatus error_code = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
blob_data_handle->RunOnConstructionComplete(
base::Bind(&ConstructionCompletePopulator, &built, &error_code));
// Cancel in middle of construction.
host_->OnCancelBuildingBlob(kId, BlobStatus::ERR_OUT_OF_MEMORY);
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(host_->IsInUseInHost(kId));
EXPECT_FALSE(IsBeingBuiltInHost(kId));
EXPECT_TRUE(blob_data_handle->IsBroken());
EXPECT_FALSE(built);
EXPECT_EQ(BlobStatus::ERR_OUT_OF_MEMORY, error_code);
error_code = BlobStatus::ERR_OUT_OF_MEMORY;
built = true;
blob_data_handle->RunOnConstructionComplete(
base::Bind(&ConstructionCompletePopulator, &built, &error_code));
EXPECT_FALSE(built);
EXPECT_EQ(BlobStatus::ERR_OUT_OF_MEMORY, error_code);
// Remove it.
blob_data_handle.reset();
base::RunLoop().RunUntilIdle();
EXPECT_TRUE(context_->registry().HasEntry(kId));
host_->OnDecrementBlobRefCount(kId);
ExpectBlobNotExist(kId);
}
TEST_F(BlobDispatcherHostTest, DecrementRefAfterOnStart) {
const std::string kId("id");
// Decrement the refcount while building, after we call OnStartBuildlingBlob.
DataElement element;
element.SetToBytesDescription(kDataSize);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
ExpectRequest(kId, expected_requests);
sink_.ClearMessages();
EXPECT_TRUE(context_->registry().HasEntry(kId));
host_->OnDecrementBlobRefCount(kId);
EXPECT_FALSE(context_->registry().HasEntry(kId));
EXPECT_FALSE(IsBeingBuiltInHost(kId));
ExpectCancel(kId, BlobStatus::ERR_BLOB_DEREFERENCED_WHILE_BUILDING);
sink_.ClearMessages();
// Do the same, but this time grab a handle to keep it alive.
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
ExpectRequest(kId, expected_requests);
sink_.ClearMessages();
EXPECT_TRUE(context_->registry().HasEntry(kId));
// Grab the handle before decrementing.
std::unique_ptr<BlobDataHandle> blob_data_handle =
context_->GetBlobDataFromUUID(kId);
host_->OnDecrementBlobRefCount(kId);
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(IsBeingBuiltInHost(kId));
EXPECT_EQ(0u, sink_.message_count());
// We finish the blob, and verify that we send 'Done' back to the renderer.
BlobItemBytesResponse response(0);
std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
std::vector<BlobItemBytesResponse> responses = {response};
host_->OnMemoryItemResponse(kId, responses);
ExpectDone(kId);
sink_.ClearMessages();
// Check that it's still around.
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_FALSE(IsBeingBuiltInHost(kId));
// Get rid of the handle, and verify it's gone.
blob_data_handle.reset();
base::RunLoop().RunUntilIdle();
// Check that it's no longer around.
EXPECT_FALSE(context_->registry().HasEntry(kId));
EXPECT_FALSE(IsBeingBuiltInHost(kId));
}
TEST_F(BlobDispatcherHostTest, DecrementRefAfterOnStartWithHandle) {
const std::string kId("id");
// Decrement the refcount while building, after we call
// OnStartBuildlingBlob, except we have another handle.
DataElement element;
element.SetToBytesDescription(kDataSize);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
std::unique_ptr<BlobDataHandle> blob_data_handle =
context_->GetBlobDataFromUUID(kId);
EXPECT_TRUE(blob_data_handle->IsBeingBuilt());
bool built = true;
BlobStatus error_code = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
blob_data_handle->RunOnConstructionComplete(
base::Bind(&ConstructionCompletePopulator, &built, &error_code));
// Check that we got the expected request.
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
ExpectRequest(kId, expected_requests);
sink_.ClearMessages();
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(IsBeingBuiltInHost(kId));
// Decrement, simulating where the ref goes out of scope in renderer.
host_->OnDecrementBlobRefCount(kId);
// We still have the blob as it's not done.
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_TRUE(blob_data_handle->IsBeingBuilt());
EXPECT_TRUE(IsBeingBuiltInHost(kId));
// Cancel to clean up.
host_->OnCancelBuildingBlob(kId, BlobStatus::ERR_OUT_OF_MEMORY);
// Run loop to propagate the handle decrement in the host.
base::RunLoop().RunUntilIdle();
// We still have the entry because of our earlier handle.
EXPECT_TRUE(context_->registry().HasEntry(kId));
EXPECT_FALSE(IsBeingBuiltInHost(kId));
EXPECT_FALSE(built);
EXPECT_EQ(BlobStatus::ERR_OUT_OF_MEMORY, error_code);
sink_.ClearMessages();
// Should disappear after dropping the handle.
EXPECT_TRUE(blob_data_handle->IsBroken());
blob_data_handle.reset();
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(context_->registry().HasEntry(kId));
}
TEST_F(BlobDispatcherHostTest, HostDisconnectAfterOnStart) {
const std::string kId("id");
// Host deleted after OnStartBuilding.
DataElement element;
element.SetToBytesDescription(kDataSize);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
ExpectRequest(kId, expected_requests);
sink_.ClearMessages();
host_ = nullptr;
// We need to run the message loop because of the handle in the async builder.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(context_->registry().HasEntry(kId));
}
TEST_F(BlobDispatcherHostTest, HostDisconnectAfterOnMemoryResponse) {
const std::string kId("id");
// Host deleted after OnMemoryItemResponse.
DataElement element;
element.SetToBytesDescription(kDataSize);
std::vector<DataElement> elements = {element, element};
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
// Create list of two items.
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize),
BlobItemBytesRequest::CreateIPCRequest(1, 1, 0, kDataSize)};
ExpectRequest(kId, expected_requests);
sink_.ClearMessages();
// Send just one response so the blob isn't 'done' yet.
BlobItemBytesResponse response(0);
std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
std::vector<BlobItemBytesResponse> responses = {response};
host_->OnMemoryItemResponse(kId, responses);
EXPECT_EQ(0u, sink_.message_count());
host_ = nullptr;
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(context_->registry().HasEntry(kId));
}
TEST_F(BlobDispatcherHostTest, CreateBlobWithBrokenReference) {
const std::string kBrokenId("id1");
const std::string kReferencingId("id2");
// First, let's test a circular reference.
const std::string kCircularId("id1");
DataElement element;
element.SetToBlob(kCircularId);
std::vector<DataElement> elements = {element};
host_->OnRegisterBlob(kCircularId, std::string(kContentType),
std::string(kContentDisposition), elements);
ExpectAndResetBadMessage();
// Remove the blob.
host_->OnDecrementBlobRefCount(kCircularId);
sink_.ClearMessages();
// Next, test a blob that references a broken blob.
element.SetToBytesDescription(kDataSize);
elements = {element};
host_->OnRegisterBlob(kBrokenId, std::string(kContentType),
std::string(kContentDisposition), elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
sink_.ClearMessages();
host_->OnCancelBuildingBlob(kBrokenId, BlobStatus::ERR_OUT_OF_MEMORY);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
sink_.ClearMessages();
EXPECT_TRUE(context_->GetBlobDataFromUUID(kBrokenId)->IsBroken());
// Create referencing blob. We should be broken right away, but also ignore
// the subsequent OnStart message.
element.SetToBytesDescription(kDataSize);
elements = {element};
element.SetToBlob(kBrokenId);
elements.push_back(element);
host_->OnRegisterBlob(kReferencingId, std::string(kContentType),
std::string(kContentDisposition), elements);
EXPECT_TRUE(context_->GetBlobDataFromUUID(kReferencingId)->IsBroken());
EXPECT_FALSE(IsBeingBuiltInHost(kReferencingId));
EXPECT_TRUE(context_->registry().HasEntry(kReferencingId));
ExpectCancel(kReferencingId, BlobStatus::ERR_REFERENCED_BLOB_BROKEN);
sink_.ClearMessages();
}
TEST_F(BlobDispatcherHostTest, DeferenceBlobOnDifferentHost) {
const std::string kId("id");
// Data elements for our transfer & checking messages.
DataElement element;
element.SetToBytesDescription(kDataSize);
std::vector<DataElement> elements = {element};
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
BlobItemBytesResponse response(0);
std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
std::vector<BlobItemBytesResponse> responses = {response};
scoped_refptr<TestableBlobDispatcherHost> host2(
new TestableBlobDispatcherHost(chrome_blob_storage_context_,
file_system_context_.get(), &sink_));
// Delete host with another host having a referencing, then dereference on
// second host. Verify we're still building it on first host, and then
// verify that a building message from the renderer will kill it.
// Test OnStartBuilding after double dereference.
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
ExpectRequest(kId, expected_requests);
sink_.ClearMessages();
host2->OnIncrementBlobRefCount(kId);
host_->OnDecrementBlobRefCount(kId);
EXPECT_FALSE(host_->IsInUseInHost(kId));
host2->OnDecrementBlobRefCount(kId);
// Blob is gone as we've been decremented from both hosts.
EXPECT_FALSE(context_->registry().HasEntry(kId));
// Send the memory. When the host sees the entry doesn't exist, it should
// cancel and clean up.
EXPECT_TRUE(host_->transport_host_.IsBeingBuilt(kId));
host_->OnMemoryItemResponse(kId, responses);
// We should be cleaned up.
EXPECT_FALSE(context_->registry().HasEntry(kId));
EXPECT_FALSE(host_->transport_host_.IsBeingBuilt(kId));
ExpectCancel(kId, BlobStatus::ERR_BLOB_DEREFERENCED_WHILE_BUILDING);
sink_.ClearMessages();
// Same, but now for OnCancel.
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
host2->OnIncrementBlobRefCount(kId);
host_->OnDecrementBlobRefCount(kId);
EXPECT_FALSE(host_->IsInUseInHost(kId));
ExpectRequest(kId, expected_requests);
sink_.ClearMessages();
host2->OnDecrementBlobRefCount(kId);
// Blob is gone as we've been decremented from both hosts.
EXPECT_FALSE(context_->registry().HasEntry(kId));
EXPECT_TRUE(host_->transport_host_.IsBeingBuilt(kId));
host_->OnCancelBuildingBlob(kId, BlobStatus::ERR_OUT_OF_MEMORY);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
EXPECT_FALSE(context_->registry().HasEntry(kId));
// We should be cleaned up.
EXPECT_FALSE(host_->transport_host_.IsBeingBuilt(kId));
ExpectCancel(kId, BlobStatus::ERR_BLOB_DEREFERENCED_WHILE_BUILDING);
}
TEST_F(BlobDispatcherHostTest, BuildingReferenceChain) {
const std::string kId("id");
const std::string kSameHostReferencingId("id2");
const std::string kDifferentHostReferencingId("id3");
// Data elements for our transfer & checking messages.
DataElement element;
element.SetToBytesDescription(kDataSize);
DataElement referencing_element;
referencing_element.SetToBlob(kId);
std::vector<DataElement> elements = {element};
std::vector<DataElement> referencing_elements = {referencing_element};
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
BlobItemBytesResponse response(0);
std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
std::vector<BlobItemBytesResponse> responses = {response};
scoped_refptr<TestableBlobDispatcherHost> host2(
new TestableBlobDispatcherHost(chrome_blob_storage_context_,
file_system_context_.get(), &sink_));
// We want to have a blob referencing another blob that is building, both on
// the same host and a different host. We should successfully build all blobs
// after the referenced blob is finished.
// First we start the referenced blob.
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
ExpectRequest(kId, expected_requests);
sink_.ClearMessages();
EXPECT_TRUE(host_->IsInUseInHost(kId));
EXPECT_TRUE(IsBeingBuiltInContext(kId));
// Next we start the referencing blobs in both the same and different host.
host_->OnRegisterBlob(kSameHostReferencingId, std::string(kContentType),
std::string(kContentDisposition), referencing_elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
ExpectDone(kSameHostReferencingId);
sink_.ClearMessages();
EXPECT_FALSE(
context_->GetBlobDataFromUUID(kSameHostReferencingId)->IsBroken());
EXPECT_FALSE(host_->transport_host_.IsBeingBuilt(kSameHostReferencingId));
EXPECT_TRUE(IsBeingBuiltInContext(kSameHostReferencingId));
// Now the other host.
host2->OnRegisterBlob(kDifferentHostReferencingId, std::string(kContentType),
std::string(kContentDisposition), referencing_elements);
EXPECT_FALSE(host2->shutdown_for_bad_message_);
ExpectDone(kDifferentHostReferencingId);
sink_.ClearMessages();
EXPECT_FALSE(
context_->GetBlobDataFromUUID(kDifferentHostReferencingId)->IsBroken());
EXPECT_FALSE(
host2->transport_host_.IsBeingBuilt(kDifferentHostReferencingId));
EXPECT_TRUE(IsBeingBuiltInContext(kDifferentHostReferencingId));
// Now we finish the first blob, and we expect all blobs to finish.
host_->OnMemoryItemResponse(kId, responses);
ExpectDone(kId);
// We need to run the message loop to propagate the construction callbacks.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(
host2->transport_host_.IsBeingBuilt(kDifferentHostReferencingId));
EXPECT_FALSE(host_->transport_host_.IsBeingBuilt(kSameHostReferencingId));
EXPECT_FALSE(
context_->GetBlobDataFromUUID(kSameHostReferencingId)->IsBroken());
EXPECT_FALSE(
context_->GetBlobDataFromUUID(kDifferentHostReferencingId)->IsBroken());
sink_.ClearMessages();
// Finally check that our data is correct in the child elements.
std::unique_ptr<BlobDataHandle> handle =
context_->GetBlobDataFromUUID(kDifferentHostReferencingId);
DataElement expected;
expected.SetToBytes(kData, kDataSize);
std::vector<DataElement> expecteds = {expected};
ExpectHandleEqualsData(handle.get(), expecteds);
}
TEST_F(BlobDispatcherHostTest, BuildingReferenceChainWithCancel) {
const std::string kId("id");
const std::string kSameHostReferencingId("id2");
const std::string kDifferentHostReferencingId("id3");
// Data elements for our transfer & checking messages.
DataElement element;
element.SetToBytesDescription(kDataSize);
DataElement referencing_element;
referencing_element.SetToBlob(kId);
std::vector<DataElement> elements = {element};
std::vector<DataElement> referencing_elements = {referencing_element};
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
scoped_refptr<TestableBlobDispatcherHost> host2(
new TestableBlobDispatcherHost(chrome_blob_storage_context_,
file_system_context_.get(), &sink_));
// We want to have a blob referencing another blob that is building, both on
// the same host and a different host. We should successfully build all blobs
// after the referenced blob is finished.
// First we start the referenced blob.
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
ExpectRequest(kId, expected_requests);
sink_.ClearMessages();
EXPECT_TRUE(host_->IsInUseInHost(kId));
EXPECT_TRUE(IsBeingBuiltInContext(kId));
// Next we start the referencing blobs in both the same and different host.
host_->OnRegisterBlob(kSameHostReferencingId, std::string(kContentType),
std::string(kContentDisposition), referencing_elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
ExpectDone(kSameHostReferencingId);
sink_.ClearMessages();
EXPECT_FALSE(
context_->GetBlobDataFromUUID(kSameHostReferencingId)->IsBroken());
EXPECT_FALSE(host_->transport_host_.IsBeingBuilt(kSameHostReferencingId));
EXPECT_TRUE(IsBeingBuiltInContext(kSameHostReferencingId));
// Now the other host.
host2->OnRegisterBlob(kDifferentHostReferencingId, std::string(kContentType),
std::string(kContentDisposition), referencing_elements);
EXPECT_FALSE(host2->shutdown_for_bad_message_);
ExpectDone(kDifferentHostReferencingId);
sink_.ClearMessages();
EXPECT_FALSE(
context_->GetBlobDataFromUUID(kDifferentHostReferencingId)->IsBroken());
EXPECT_FALSE(
host2->transport_host_.IsBeingBuilt(kDifferentHostReferencingId));
EXPECT_TRUE(IsBeingBuiltInContext(kDifferentHostReferencingId));
bool built = false;
BlobStatus error_code = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
context_->GetBlobDataFromUUID(kDifferentHostReferencingId)
->RunOnConstructionComplete(
base::Bind(&ConstructionCompletePopulator, &built, &error_code));
// Now we cancel the first blob, and we expect all blobs to cancel.
host_->OnCancelBuildingBlob(kId, BlobStatus::ERR_OUT_OF_MEMORY);
// We need to run the message loop to propagate the construction callbacks.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(
host2->transport_host_.IsBeingBuilt(kDifferentHostReferencingId));
EXPECT_FALSE(host_->transport_host_.IsBeingBuilt(kSameHostReferencingId));
EXPECT_TRUE(
context_->GetBlobDataFromUUID(kSameHostReferencingId)->IsBroken());
EXPECT_TRUE(
context_->GetBlobDataFromUUID(kDifferentHostReferencingId)->IsBroken());
EXPECT_FALSE(built);
EXPECT_EQ(BlobStatus::ERR_REFERENCED_BLOB_BROKEN, error_code);
sink_.ClearMessages();
}
TEST_F(BlobDispatcherHostTest, BuildingReferenceChainWithSourceDeath) {
const std::string kId("id");
const std::string kSameHostReferencingId("id2");
const std::string kDifferentHostReferencingId("id3");
// Data elements for our transfer & checking messages.
DataElement element;
element.SetToBytesDescription(kDataSize);
DataElement referencing_element;
referencing_element.SetToBlob(kId);
std::vector<DataElement> elements = {element};
std::vector<DataElement> referencing_elements = {referencing_element};
std::vector<BlobItemBytesRequest> expected_requests = {
BlobItemBytesRequest::CreateIPCRequest(0, 0, 0, kDataSize)};
BlobItemBytesResponse response(0);
std::memcpy(response.allocate_mutable_data(kDataSize), kData, kDataSize);
std::vector<BlobItemBytesResponse> responses = {response};
scoped_refptr<TestableBlobDispatcherHost> host2(
new TestableBlobDispatcherHost(chrome_blob_storage_context_,
file_system_context_.get(), &sink_));
// We want to have a blob referencing another blob that is building, both on
// the same host and a different host. We should successfully build all blobs
// after the referenced blob is finished.
// First we start the referenced blob.
host_->OnRegisterBlob(kId, std::string(kContentType),
std::string(kContentDisposition), elements);
ExpectRequest(kId, expected_requests);
sink_.ClearMessages();
EXPECT_TRUE(host_->IsInUseInHost(kId));
EXPECT_TRUE(IsBeingBuiltInContext(kId));
// Next we start the referencing blobs in both the same and different host.
host_->OnRegisterBlob(kSameHostReferencingId, std::string(kContentType),
std::string(kContentDisposition), referencing_elements);
EXPECT_FALSE(host_->shutdown_for_bad_message_);
ExpectDone(kSameHostReferencingId);
sink_.ClearMessages();
// Now the other host.
host2->OnRegisterBlob(kDifferentHostReferencingId, std::string(kContentType),
std::string(kContentDisposition), referencing_elements);
EXPECT_FALSE(host2->shutdown_for_bad_message_);
ExpectDone(kDifferentHostReferencingId);
sink_.ClearMessages();
// Grab handles & add listeners.
bool built = true;
BlobStatus error_code = BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
std::unique_ptr<BlobDataHandle> blob_handle =
context_->GetBlobDataFromUUID(kId);
blob_handle->RunOnConstructionComplete(
base::Bind(&ConstructionCompletePopulator, &built, &error_code));
bool same_host_built = true;
BlobStatus same_host_error_code =
BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
std::unique_ptr<BlobDataHandle> same_host_blob_handle =
context_->GetBlobDataFromUUID(kSameHostReferencingId);
same_host_blob_handle->RunOnConstructionComplete(base::Bind(
&ConstructionCompletePopulator, &same_host_built, &same_host_error_code));
bool other_host_built = true;
BlobStatus other_host_error_code =
BlobStatus::ERR_INVALID_CONSTRUCTION_ARGUMENTS;
std::unique_ptr<BlobDataHandle> other_host_blob_handle =
context_->GetBlobDataFromUUID(kDifferentHostReferencingId);
other_host_blob_handle->RunOnConstructionComplete(
base::Bind(&ConstructionCompletePopulator, &other_host_built,
&other_host_error_code));
// Now we kill the host.
host_ = nullptr;
// We need to run the message loop to propagate the construction callbacks.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(
host2->transport_host_.IsBeingBuilt(kDifferentHostReferencingId));
EXPECT_TRUE(
context_->GetBlobDataFromUUID(kSameHostReferencingId)->IsBroken());
EXPECT_TRUE(
context_->GetBlobDataFromUUID(kDifferentHostReferencingId)->IsBroken());
// Check our callbacks
EXPECT_FALSE(built);
EXPECT_EQ(BlobStatus::ERR_SOURCE_DIED_IN_TRANSIT, error_code);
EXPECT_FALSE(same_host_built);
EXPECT_EQ(BlobStatus::ERR_REFERENCED_BLOB_BROKEN, same_host_error_code);
EXPECT_FALSE(other_host_built);
EXPECT_EQ(BlobStatus::ERR_REFERENCED_BLOB_BROKEN, other_host_error_code);
sink_.ClearMessages();
}
} // namespace content