blob: 5cf47dfa96e00abcdbdb0aa6f1047d56ab6e5c45 [file] [log] [blame]
// Copyright 2020 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 "chrome/browser/policy/messaging_layer/storage/storage.h"
#include <cstdint>
#include <tuple>
#include <utility>
#include "base/files/scoped_temp_dir.h"
#include "base/optional.h"
#include "base/sequenced_task_runner.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/scoped_feature_list.h"
#include "base/test/task_environment.h"
#include "chrome/browser/policy/messaging_layer/encryption/decryption.h"
#include "chrome/browser/policy/messaging_layer/encryption/encryption.h"
#include "chrome/browser/policy/messaging_layer/encryption/test_encryption_module.h"
#include "chrome/browser/policy/messaging_layer/storage/storage_configuration.h"
#include "chrome/browser/policy/messaging_layer/util/status.h"
#include "chrome/browser/policy/messaging_layer/util/status_macros.h"
#include "chrome/browser/policy/messaging_layer/util/statusor.h"
#include "components/policy/proto/record.pb.h"
#include "components/policy/proto/record_constants.pb.h"
#include "crypto/sha2.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/boringssl/src/include/openssl/curve25519.h"
using ::testing::_;
using ::testing::Between;
using ::testing::Eq;
using ::testing::Invoke;
using ::testing::NotNull;
using ::testing::Property;
using ::testing::Return;
using ::testing::Sequence;
using ::testing::StrEq;
using ::testing::WithArg;
namespace reporting {
namespace {
// Usage (in tests only):
//
// TestEvent<ResType> e;
// ... Do some async work passing e.cb() as a completion callback of
// base::OnceCallback<void(ResType* res)> type which also may perform some
// other action specified by |done| callback provided by the caller.
// ... = e.result(); // Will wait for e.cb() to be called and return the
// collected result.
//
template <typename ResType>
class TestEvent {
public:
TestEvent() : run_loop_(std::make_unique<base::RunLoop>()) {}
~TestEvent() { EXPECT_FALSE(run_loop_->running()) << "Not responded"; }
TestEvent(const TestEvent& other) = delete;
TestEvent& operator=(const TestEvent& other) = delete;
ResType result() {
run_loop_->Run();
return std::forward<ResType>(result_);
}
// Completion callback to hand over to the processing method.
base::OnceCallback<void(ResType res)> cb() {
return base::BindOnce(
[](base::RunLoop* run_loop, ResType* result, ResType res) {
*result = std::forward<ResType>(res);
run_loop->Quit();
},
base::Unretained(run_loop_.get()), base::Unretained(&result_));
}
private:
std::unique_ptr<base::RunLoop> run_loop_;
ResType result_;
};
// Context of single decryption. Self-destructs upon completion or failure.
class SingleDecryptionContext {
public:
SingleDecryptionContext(
const EncryptedRecord& encrypted_record,
scoped_refptr<Decryptor> decryptor,
base::OnceCallback<void(StatusOr<base::StringPiece>)> response)
: encrypted_record_(encrypted_record),
decryptor_(decryptor),
response_(std::move(response)) {}
SingleDecryptionContext(const SingleDecryptionContext& other) = delete;
SingleDecryptionContext& operator=(const SingleDecryptionContext& other) =
delete;
~SingleDecryptionContext() {
DCHECK(!response_) << "Self-destruct without prior response";
}
void Start() {
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&SingleDecryptionContext::RetrieveMatchingPrivateKey,
base::Unretained(this)));
}
private:
void Respond(StatusOr<base::StringPiece> result) {
std::move(response_).Run(result);
delete this;
}
void RetrieveMatchingPrivateKey() {
// Retrieve private key that matches public key hash.
decryptor_->RetrieveMatchingPrivateKey(
encrypted_record_.encryption_info().public_key_id(),
base::BindOnce(
[](SingleDecryptionContext* self,
StatusOr<std::string> private_key_result) {
if (!private_key_result.ok()) {
self->Respond(private_key_result.status());
return;
}
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&SingleDecryptionContext::DecryptSharedSecret,
base::Unretained(self),
private_key_result.ValueOrDie()));
},
base::Unretained(this)));
}
void DecryptSharedSecret(base::StringPiece private_key) {
// Decrypt shared secret from private key and peer public key.
auto shared_secret_result = decryptor_->DecryptSecret(
private_key, encrypted_record_.encryption_info().encryption_key());
if (!shared_secret_result.ok()) {
Respond(shared_secret_result.status());
return;
}
base::ThreadPool::PostTask(
FROM_HERE, base::BindOnce(&SingleDecryptionContext::OpenRecord,
base::Unretained(this),
shared_secret_result.ValueOrDie()));
}
void OpenRecord(base::StringPiece shared_secret) {
decryptor_->OpenRecord(
shared_secret,
base::BindOnce(
[](SingleDecryptionContext* self,
StatusOr<Decryptor::Handle*> handle_result) {
if (!handle_result.ok()) {
self->Respond(handle_result.status());
return;
}
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&SingleDecryptionContext::AddToRecord,
base::Unretained(self),
base::Unretained(handle_result.ValueOrDie())));
},
base::Unretained(this)));
}
void AddToRecord(Decryptor::Handle* handle) {
handle->AddToRecord(
encrypted_record_.encrypted_wrapped_record(),
base::BindOnce(
[](SingleDecryptionContext* self, Decryptor::Handle* handle,
Status status) {
if (!status.ok()) {
self->Respond(status);
return;
}
base::ThreadPool::PostTask(
FROM_HERE,
base::BindOnce(&SingleDecryptionContext::CloseRecord,
base::Unretained(self),
base::Unretained(handle)));
},
base::Unretained(this), base::Unretained(handle)));
}
void CloseRecord(Decryptor::Handle* handle) {
handle->CloseRecord(base::BindOnce(
[](SingleDecryptionContext* self,
StatusOr<base::StringPiece> decryption_result) {
self->Respond(decryption_result);
},
base::Unretained(this)));
}
private:
const EncryptedRecord encrypted_record_;
const scoped_refptr<Decryptor> decryptor_;
base::OnceCallback<void(StatusOr<base::StringPiece>)> response_;
};
class MockUploadClient : public Storage::UploaderInterface {
public:
// Mapping of <generation id, sequencing id> to matching record digest.
// Whenever a record is uploaded and includes last record digest, this map
// should have that digest already recorded. Only the first record in a
// generation is uploaded without last record digest.
using LastRecordDigestMap = std::map<std::tuple<Priority,
uint64_t /*generation id*/,
uint64_t /*sequencing id*/>,
std::string /*digest*/>;
explicit MockUploadClient(
LastRecordDigestMap* last_record_digest_map,
scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner,
scoped_refptr<Decryptor> decryptor)
: last_record_digest_map_(last_record_digest_map),
sequenced_task_runner_(sequenced_task_runner),
decryptor_(decryptor) {}
void ProcessRecord(EncryptedRecord encrypted_record,
base::OnceCallback<void(bool)> processed_cb) override {
const auto& sequencing_information =
encrypted_record.sequencing_information();
if (!encrypted_record.has_encryption_info()) {
// Wrapped record is not encrypted.
WrappedRecord wrapped_record;
ASSERT_TRUE(wrapped_record.ParseFromString(
encrypted_record.encrypted_wrapped_record()));
ScheduleVerifyRecord(sequencing_information, std::move(wrapped_record),
std::move(processed_cb));
return;
}
// Decrypt encrypted_record.
(new SingleDecryptionContext(
encrypted_record, decryptor_,
base::BindOnce(
[](SequencingInformation sequencing_information,
base::OnceCallback<void(bool)> processed_cb,
MockUploadClient* client, StatusOr<base::StringPiece> result) {
ASSERT_OK(result.status());
WrappedRecord wrapped_record;
ASSERT_TRUE(wrapped_record.ParseFromArray(
result.ValueOrDie().data(), result.ValueOrDie().size()));
// Verify wrapped record once decrypted.
client->ScheduleVerifyRecord(sequencing_information,
std::move(wrapped_record),
std::move(processed_cb));
},
sequencing_information, std::move(processed_cb),
base::Unretained(this))))
->Start();
}
void ProcessGap(SequencingInformation start,
uint64_t count,
base::OnceCallback<void(bool)> processed_cb) override {
LOG(FATAL) << "Gap not implemented yet";
}
void Completed(bool need_encryption_key, Status status) override {
UploadComplete(need_encryption_key, status);
}
MOCK_METHOD(bool,
UploadRecord,
(Priority, uint64_t, base::StringPiece),
(const));
MOCK_METHOD(bool, UploadRecordFailure, (Status), (const));
MOCK_METHOD(void, UploadComplete, (bool, Status), (const));
// Helper class for setting up mock client expectations of a successful
// completion.
class SetUp {
public:
SetUp(Priority priority, MockUploadClient* client)
: priority_(priority), client_(client) {}
~SetUp() {
EXPECT_CALL(*client_, UploadRecordFailure(_))
.Times(0)
.InSequence(client_->test_upload_sequence_);
EXPECT_CALL(*client_, UploadComplete(/*need_encryption_key=*/false,
Eq(Status::StatusOK())))
.Times(1)
.InSequence(client_->test_upload_sequence_);
}
SetUp& Required(uint64_t sequencing_id, base::StringPiece value) {
EXPECT_CALL(*client_, UploadRecord(Eq(priority_), Eq(sequencing_id),
StrEq(std::string(value))))
.InSequence(client_->test_upload_sequence_)
.WillOnce(Return(true));
return *this;
}
SetUp& Possible(uint64_t sequencing_id, base::StringPiece value) {
EXPECT_CALL(*client_, UploadRecord(Eq(priority_), Eq(sequencing_id),
StrEq(std::string(value))))
.Times(Between(0, 1))
.InSequence(client_->test_upload_sequence_)
.WillRepeatedly(Return(true));
return *this;
}
private:
Priority priority_;
MockUploadClient* const client_;
};
// Helper class for setting up mock client expectations on empty queue.
class SetEmpty {
public:
SetEmpty(Priority priority, MockUploadClient* client)
: priority_(priority), client_(client) {}
~SetEmpty() {
EXPECT_CALL(*client_, UploadRecord(Eq(priority_), _, _)).Times(0);
EXPECT_CALL(*client_, UploadRecordFailure(_)).Times(0);
EXPECT_CALL(*client_, UploadComplete(/*need_encryption_key=*/false,
Property(&Status::error_code,
Eq(error::OUT_OF_RANGE))))
.Times(1);
}
private:
Priority priority_;
MockUploadClient* const client_;
};
// Helper class for setting up mock client expectations for key delivery.
class SetKeyDelivery {
public:
SetKeyDelivery(Priority priority, MockUploadClient* client)
: priority_(priority), client_(client) {}
~SetKeyDelivery() {
EXPECT_CALL(*client_, UploadRecord(Eq(priority_), _, _)).Times(0);
EXPECT_CALL(*client_, UploadRecordFailure(_)).Times(0);
EXPECT_CALL(*client_, UploadComplete(/*need_encryption_key=*/true,
Eq(Status::StatusOK())))
.Times(1);
}
private:
Priority priority_;
MockUploadClient* const client_;
};
private:
void ScheduleVerifyRecord(SequencingInformation sequencing_information,
WrappedRecord wrapped_record,
base::OnceCallback<void(bool)> processed_cb) {
sequenced_task_runner_->PostTask(
FROM_HERE,
base::BindOnce(&MockUploadClient::VerifyRecord, base::Unretained(this),
sequencing_information, std::move(wrapped_record),
std::move(processed_cb)));
}
void VerifyRecord(SequencingInformation sequencing_information,
WrappedRecord wrapped_record,
base::OnceCallback<void(bool)> processed_cb) {
// Verify generation match.
if (generation_id_.has_value() &&
generation_id_.value() != sequencing_information.generation_id()) {
std::move(processed_cb)
.Run(UploadRecordFailure(Status(
error::DATA_LOSS,
base::StrCat({"Generation id mismatch, expected=",
base::NumberToString(generation_id_.value()),
" actual=",
base::NumberToString(
sequencing_information.generation_id())}))));
return;
}
if (!generation_id_.has_value()) {
generation_id_ = sequencing_information.generation_id();
}
// Verify digest and its match.
// Last record digest is not verified yet, since duplicate records are
// accepted in this test.
{
std::string serialized_record;
wrapped_record.record().SerializeToString(&serialized_record);
const auto record_digest = crypto::SHA256HashString(serialized_record);
DCHECK_EQ(record_digest.size(), crypto::kSHA256Length);
if (record_digest != wrapped_record.record_digest()) {
std::move(processed_cb)
.Run(UploadRecordFailure(
Status(error::DATA_LOSS, "Record digest mismatch")));
return;
}
if (wrapped_record.has_last_record_digest()) {
auto it = last_record_digest_map_->find(
std::make_tuple(sequencing_information.priority(),
sequencing_information.sequencing_id() - 1,
sequencing_information.generation_id()));
if (it == last_record_digest_map_->end() ||
it->second != wrapped_record.last_record_digest()) {
std::move(processed_cb)
.Run(UploadRecordFailure(
Status(error::DATA_LOSS, "Last record digest mismatch")));
return;
}
}
last_record_digest_map_->emplace(
std::make_tuple(sequencing_information.priority(),
sequencing_information.sequencing_id(),
sequencing_information.generation_id()),
record_digest);
}
std::move(processed_cb)
.Run(UploadRecord(sequencing_information.priority(),
sequencing_information.sequencing_id(),
wrapped_record.record().data()));
}
base::Optional<uint64_t> generation_id_;
LastRecordDigestMap* const last_record_digest_map_;
scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_;
const scoped_refptr<Decryptor> decryptor_;
Sequence test_upload_sequence_;
};
class StorageTest
: public ::testing::TestWithParam<::testing::tuple<bool, size_t>> {
protected:
void SetUp() override {
ASSERT_TRUE(location_.CreateUniqueTempDir());
// Encryption is disabled by default.
ASSERT_FALSE(EncryptionModule::is_enabled());
if (::testing::get<0>(GetParam())) {
// Enable encryption.
scoped_feature_list_.InitFromCommandLine(
{EncryptionModule::kEncryptedReporting}, {});
// Create decryption module.
auto decryptor_result = Decryptor::Create();
ASSERT_OK(decryptor_result.status()) << decryptor_result.status();
decryptor_ = std::move(decryptor_result.ValueOrDie());
// First creation of Storage would need key delivered.
expect_to_need_key_ = true;
}
}
void TearDown() override { ResetTestStorage(); }
StatusOr<scoped_refptr<Storage>> CreateTestStorage(
const StorageOptions& options,
scoped_refptr<EncryptionModule> encryption_module) {
if (expect_to_need_key_) {
// Set uploader expectations for any queue; expect no records and need
// key. Make sure no uploads happen, and key is requested.
EXPECT_CALL(set_mock_uploader_expectations_, Call(_, NotNull()))
.WillOnce(Invoke(
[](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetKeyDelivery(priority, mock_upload_client);
}));
}
// Initialize Storage with no key.
TestEvent<StatusOr<scoped_refptr<Storage>>> e;
Storage::Create(options,
base::BindRepeating(&StorageTest::BuildMockUploader,
base::Unretained(this)),
encryption_module, e.cb());
ASSIGN_OR_RETURN(auto storage, e.result());
if (expect_to_need_key_) {
// Provision the storage with a key.
// Key delivery must have been requested above.
GenerateAndDeliverKey(storage.get());
}
return storage;
}
void CreateTestStorageOrDie(
const StorageOptions& options,
scoped_refptr<EncryptionModule> encryption_module =
base::MakeRefCounted<EncryptionModule>()) {
ASSERT_FALSE(storage_) << "StorageTest already assigned";
StatusOr<scoped_refptr<Storage>> storage_result =
CreateTestStorage(options, encryption_module);
ASSERT_OK(storage_result)
<< "Failed to create StorageTest, error=" << storage_result.status();
storage_ = std::move(storage_result.ValueOrDie());
}
void ResetTestStorage() {
task_environment_.RunUntilIdle();
storage_.reset();
expect_to_need_key_ = false;
}
StorageOptions BuildTestStorageOptions() const {
return StorageOptions()
.set_directory(base::FilePath(location_.GetPath()))
.set_single_file_size(::testing::get<1>(GetParam()));
}
StatusOr<std::unique_ptr<Storage::UploaderInterface>> BuildMockUploader(
Priority priority) {
auto uploader = std::make_unique<MockUploadClient>(
&last_record_digest_map_, sequenced_task_runner_, decryptor_);
set_mock_uploader_expectations_.Call(priority, uploader.get());
return uploader;
}
Status WriteString(Priority priority, base::StringPiece data) {
EXPECT_TRUE(storage_) << "Storage not created yet";
TestEvent<Status> w;
Record record;
record.set_data(std::string(data));
record.set_destination(UPLOAD_EVENTS);
record.set_dm_token("DM TOKEN");
storage_->Write(priority, std::move(record), w.cb());
return w.result();
}
void WriteStringOrDie(Priority priority, base::StringPiece data) {
const Status write_result = WriteString(priority, data);
ASSERT_OK(write_result) << write_result;
}
void ConfirmOrDie(Priority priority, std::uint64_t sequencing_id) {
TestEvent<Status> c;
storage_->Confirm(priority, sequencing_id, c.cb());
const Status c_result = c.result();
ASSERT_OK(c_result) << c_result;
}
void GenerateAndDeliverKey(Storage* storage) {
ASSERT_TRUE(decryptor_) << "Decryptor not created";
// Generate new pair of private key and public value.
uint8_t public_value[X25519_PUBLIC_VALUE_LEN];
uint8_t private_key[X25519_PRIVATE_KEY_LEN];
X25519_keypair(public_value, private_key);
TestEvent<StatusOr<Encryptor::PublicKeyId>> prepare_key_pair;
decryptor_->RecordKeyPair(
std::string(reinterpret_cast<const char*>(private_key),
X25519_PRIVATE_KEY_LEN),
std::string(reinterpret_cast<const char*>(public_value),
X25519_PUBLIC_VALUE_LEN),
prepare_key_pair.cb());
auto prepare_key_result = prepare_key_pair.result();
ASSERT_OK(prepare_key_result.status());
Encryptor::PublicKeyId new_public_key_id = prepare_key_result.ValueOrDie();
// Deliver public key to storage.
SignedEncryptionInfo signed_encryption_key;
signed_encryption_key.set_public_asymmetric_key(std::string(
reinterpret_cast<const char*>(public_value), X25519_PUBLIC_VALUE_LEN));
signed_encryption_key.set_public_key_id(new_public_key_id);
// TODO(b/170054326): Add key signature.
storage->UpdateEncryptionKey(signed_encryption_key);
}
base::test::TaskEnvironment task_environment_{
base::test::TaskEnvironment::TimeSource::MOCK_TIME};
base::test::ScopedFeatureList scoped_feature_list_;
base::ScopedTempDir location_;
scoped_refptr<Decryptor> decryptor_;
scoped_refptr<Storage> storage_;
bool expect_to_need_key_{false};
// Test-wide global mapping of <generation id, sequencing id> to record
// digest. Serves all MockUploadClients created by test fixture.
MockUploadClient::LastRecordDigestMap last_record_digest_map_;
// Guard Access to last_record_digest_map_
scoped_refptr<base::SequencedTaskRunner> sequenced_task_runner_{
base::ThreadPool::CreateSequencedTaskRunner(base::TaskTraits())};
::testing::MockFunction<void(Priority, MockUploadClient*)>
set_mock_uploader_expectations_;
};
constexpr std::array<const char*, 3> data = {"Rec1111", "Rec222", "Rec33"};
constexpr std::array<const char*, 3> more_data = {"More1111", "More222",
"More33"};
TEST_P(StorageTest, WriteIntoNewStorageAndReopen) {
CreateTestStorageOrDie(BuildTestStorageOptions());
EXPECT_CALL(set_mock_uploader_expectations_, Call(_, NotNull())).Times(0);
WriteStringOrDie(FAST_BATCH, data[0]);
WriteStringOrDie(FAST_BATCH, data[1]);
WriteStringOrDie(FAST_BATCH, data[2]);
ResetTestStorage();
CreateTestStorageOrDie(BuildTestStorageOptions());
}
TEST_P(StorageTest, WriteIntoNewStorageReopenAndWriteMore) {
CreateTestStorageOrDie(BuildTestStorageOptions());
EXPECT_CALL(set_mock_uploader_expectations_, Call(_, NotNull())).Times(0);
WriteStringOrDie(FAST_BATCH, data[0]);
WriteStringOrDie(FAST_BATCH, data[1]);
WriteStringOrDie(FAST_BATCH, data[2]);
ResetTestStorage();
CreateTestStorageOrDie(BuildTestStorageOptions());
WriteStringOrDie(FAST_BATCH, more_data[0]);
WriteStringOrDie(FAST_BATCH, more_data[1]);
WriteStringOrDie(FAST_BATCH, more_data[2]);
}
TEST_P(StorageTest, WriteIntoNewStorageAndUpload) {
CreateTestStorageOrDie(BuildTestStorageOptions());
WriteStringOrDie(FAST_BATCH, data[0]);
WriteStringOrDie(FAST_BATCH, data[1]);
WriteStringOrDie(FAST_BATCH, data[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(FAST_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(0, data[0])
.Required(1, data[1])
.Required(2, data[2]);
}));
// Trigger upload.
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageTest, WriteIntoNewStorageReopenWriteMoreAndUpload) {
CreateTestStorageOrDie(BuildTestStorageOptions());
WriteStringOrDie(FAST_BATCH, data[0]);
WriteStringOrDie(FAST_BATCH, data[1]);
WriteStringOrDie(FAST_BATCH, data[2]);
ResetTestStorage();
CreateTestStorageOrDie(BuildTestStorageOptions());
WriteStringOrDie(FAST_BATCH, more_data[0]);
WriteStringOrDie(FAST_BATCH, more_data[1]);
WriteStringOrDie(FAST_BATCH, more_data[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(FAST_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(0, data[0])
.Required(1, data[1])
.Required(2, data[2])
.Required(3, more_data[0])
.Required(4, more_data[1])
.Required(5, more_data[2]);
}));
// Trigger upload.
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageTest, WriteIntoNewStorageAndFlush) {
CreateTestStorageOrDie(BuildTestStorageOptions());
WriteStringOrDie(MANUAL_BATCH, data[0]);
WriteStringOrDie(MANUAL_BATCH, data[1]);
WriteStringOrDie(MANUAL_BATCH, data[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_,
Call(Eq(MANUAL_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(0, data[0])
.Required(1, data[1])
.Required(2, data[2]);
}));
// Trigger upload.
EXPECT_OK(storage_->Flush(MANUAL_BATCH));
}
TEST_P(StorageTest, WriteIntoNewStorageReopenWriteMoreAndFlush) {
CreateTestStorageOrDie(BuildTestStorageOptions());
WriteStringOrDie(MANUAL_BATCH, data[0]);
WriteStringOrDie(MANUAL_BATCH, data[1]);
WriteStringOrDie(MANUAL_BATCH, data[2]);
ResetTestStorage();
CreateTestStorageOrDie(BuildTestStorageOptions());
WriteStringOrDie(MANUAL_BATCH, more_data[0]);
WriteStringOrDie(MANUAL_BATCH, more_data[1]);
WriteStringOrDie(MANUAL_BATCH, more_data[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_,
Call(Eq(MANUAL_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(0, data[0])
.Required(1, data[1])
.Required(2, data[2])
.Required(3, more_data[0])
.Required(4, more_data[1])
.Required(5, more_data[2]);
}));
// Trigger upload.
EXPECT_OK(storage_->Flush(MANUAL_BATCH));
}
TEST_P(StorageTest, WriteAndRepeatedlyUploadWithConfirmations) {
CreateTestStorageOrDie(BuildTestStorageOptions());
WriteStringOrDie(FAST_BATCH, data[0]);
WriteStringOrDie(FAST_BATCH, data[1]);
WriteStringOrDie(FAST_BATCH, data[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(FAST_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(0, data[0])
.Required(1, data[1])
.Required(2, data[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #0 and forward time again, removing data #0
ConfirmOrDie(FAST_BATCH, /*sequencing_id=*/0);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(FAST_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(1, data[1])
.Required(2, data[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #1 and forward time again, removing data #1
ConfirmOrDie(FAST_BATCH, /*sequencing_id=*/1);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(FAST_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(2, data[2]);
}));
// Forward time to trigger upload
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Add more records and verify that #2 and new records are returned.
WriteStringOrDie(FAST_BATCH, more_data[0]);
WriteStringOrDie(FAST_BATCH, more_data[1]);
WriteStringOrDie(FAST_BATCH, more_data[2]);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(FAST_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(2, data[2])
.Required(3, more_data[0])
.Required(4, more_data[1])
.Required(5, more_data[2]);
}));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
// Confirm #2 and forward time again, removing data #2
ConfirmOrDie(FAST_BATCH, /*sequencing_id=*/2);
// Set uploader expectations.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(FAST_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(3, more_data[0])
.Required(4, more_data[1])
.Required(5, more_data[2]);
}));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(1));
}
TEST_P(StorageTest, WriteAndRepeatedlyImmediateUpload) {
CreateTestStorageOrDie(BuildTestStorageOptions());
// Upload is initiated asynchronously, so it may happen after the next
// record is also written. Because of that we set expectations for the
// records after the current one as |Possible|.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(0, data[0])
.Possible(1, data[1])
.Possible(2, data[2]);
}));
WriteStringOrDie(IMMEDIATE,
data[0]); // Immediately uploads and verifies.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(0, data[0])
.Required(1, data[1])
.Possible(2, data[2]);
}));
WriteStringOrDie(IMMEDIATE,
data[1]); // Immediately uploads and verifies.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(0, data[0])
.Required(1, data[1])
.Required(2, data[2]);
}));
WriteStringOrDie(IMMEDIATE,
data[2]); // Immediately uploads and verifies.
}
TEST_P(StorageTest, WriteAndRepeatedlyImmediateUploadWithConfirmations) {
CreateTestStorageOrDie(BuildTestStorageOptions());
// Upload is initiated asynchronously, so it may happen after the next
// record is also written. Because of the Confirmation below, we set
// expectations for the records that may be eliminated by Confirmation as
// |Possible|.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Possible(0, data[0])
.Possible(1, data[1])
.Possible(2, data[2]);
}));
WriteStringOrDie(IMMEDIATE, data[0]);
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Possible(0, data[0])
.Possible(1, data[1])
.Possible(2, data[2]);
}));
WriteStringOrDie(IMMEDIATE, data[1]);
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Possible(0, data[0])
.Possible(1, data[1])
.Required(2, data[2]);
}));
WriteStringOrDie(IMMEDIATE, data[2]);
// Confirm #1, removing data #0 and #1
ConfirmOrDie(IMMEDIATE, /*sequencing_id=*/1);
// Add more records and verify that #2 and new records are returned.
// Upload is initiated asynchronously, so it may happen after the next
// record is also written. Because of that we set expectations for the
// records after the current one as |Possible|.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(2, data[2])
.Required(3, more_data[0])
.Possible(4, more_data[1])
.Possible(5, more_data[2]);
}));
WriteStringOrDie(IMMEDIATE, more_data[0]);
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(2, data[2])
.Required(3, more_data[0])
.Required(4, more_data[1])
.Possible(5, more_data[2]);
}));
WriteStringOrDie(IMMEDIATE, more_data[1]);
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(2, data[2])
.Required(3, more_data[0])
.Required(4, more_data[1])
.Required(5, more_data[2]);
}));
WriteStringOrDie(IMMEDIATE, more_data[2]);
}
TEST_P(StorageTest, WriteAndRepeatedlyUploadMultipleQueues) {
CreateTestStorageOrDie(BuildTestStorageOptions());
// Upload is initiated asynchronously, so it may happen after the next
// record is also written. Because of the Confirmation below, we set
// expectations for the records that may be eliminated by Confirmation as
// |Possible|.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Possible(0, data[0])
.Possible(1, data[1])
.Possible(2, data[2]);
}));
WriteStringOrDie(IMMEDIATE, data[0]);
WriteStringOrDie(SLOW_BATCH, more_data[0]);
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Possible(0, data[0])
.Possible(1, data[1])
.Possible(2, data[2]);
}));
WriteStringOrDie(IMMEDIATE, data[1]);
WriteStringOrDie(SLOW_BATCH, more_data[1]);
// Set uploader expectations for SLOW_BATCH.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(FAST_BATCH), NotNull()))
.WillRepeatedly(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetEmpty(priority, mock_upload_client);
}));
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(SLOW_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Required(0, more_data[0])
.Required(1, more_data[1]);
}));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(20));
// Confirm #0 SLOW_BATCH, removing data #0
ConfirmOrDie(SLOW_BATCH, /*sequencing_id=*/0);
// Confirm #1 IMMEDIATE, removing data #0 and #1
ConfirmOrDie(IMMEDIATE, /*sequencing_id=*/1);
// Add more data
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(IMMEDIATE), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(priority, mock_upload_client)
.Possible(1, data[1])
.Required(2, data[2]);
}));
WriteStringOrDie(IMMEDIATE, data[2]);
WriteStringOrDie(SLOW_BATCH, more_data[2]);
// Set uploader expectations for SLOW_BATCH.
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(FAST_BATCH), NotNull()))
.WillRepeatedly(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetEmpty(priority, mock_upload_client);
}));
EXPECT_CALL(set_mock_uploader_expectations_, Call(Eq(SLOW_BATCH), NotNull()))
.WillOnce(
Invoke([](Priority priority, MockUploadClient* mock_upload_client) {
MockUploadClient::SetUp(SLOW_BATCH, mock_upload_client)
.Required(1, more_data[1])
.Required(2, more_data[2]);
}));
task_environment_.FastForwardBy(base::TimeDelta::FromSeconds(20));
}
TEST_P(StorageTest, WriteEncryptFailure) {
auto test_encryption_module =
base::MakeRefCounted<test::TestEncryptionModule>();
CreateTestStorageOrDie(BuildTestStorageOptions(), test_encryption_module);
EXPECT_CALL(*test_encryption_module, EncryptRecord(_, _))
.WillOnce(WithArg<1>(
Invoke([](base::OnceCallback<void(StatusOr<EncryptedRecord>)> cb) {
std::move(cb).Run(Status(error::UNKNOWN, "Failing for tests"));
})));
const Status result = WriteString(FAST_BATCH, "TEST_MESSAGE");
EXPECT_FALSE(result.ok());
EXPECT_EQ(result.error_code(), error::UNKNOWN);
}
INSTANTIATE_TEST_SUITE_P(
VaryingFileSize,
StorageTest,
::testing::Combine(::testing::Bool() /* true - encryption enabled */,
::testing::Values(128 * 1024LL * 1024LL,
256 /* two records in file */,
1 /* single record in file */)));
} // namespace
} // namespace reporting