blob: 313635849320a499719b5c0e69f9f09f06334bc5 [file] [log] [blame]
// Copyright 2019 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/sharing_message/sharing_fcm_sender.h"
#include <memory>
#include "base/base64.h"
#include "base/callback_list.h"
#include "base/memory/ptr_util.h"
#include "base/memory/raw_ptr.h"
#include "base/test/mock_callback.h"
#include "components/gcm_driver/crypto/gcm_encryption_result.h"
#include "components/gcm_driver/fake_gcm_driver.h"
#include "components/sharing_message/features.h"
#include "components/sharing_message/proto/sharing_message.pb.h"
#include "components/sharing_message/sharing_constants.h"
#include "components/sharing_message/sharing_message_bridge.h"
#include "components/sharing_message/sharing_sync_preference.h"
#include "components/sharing_message/sharing_utils.h"
#include "components/sync/model/data_type_controller_delegate.h"
#include "components/sync/test/test_sync_service.h"
#include "components/sync_device_info/device_info.h"
#include "components/sync_device_info/fake_device_info_sync_service.h"
#include "components/sync_device_info/fake_local_device_info_provider.h"
#include "components/sync_preferences/testing_pref_service_syncable.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
using testing::_;
const char kSenderIdFcmToken[] = "sender_id_fcm_token";
const char kSenderIdP256dh[] = "sender_id_p256dh";
const char kSenderIdAuthSecret[] = "sender_id_auth_secret";
const int kTtlSeconds = 10;
const char kServerConfiguration[] = "test_server_configuration";
const char kServerP256dh[] = "test_server_p256_dh";
const char kServerAuthSecret[] = "test_server_auth_secret";
class FakeGCMDriver : public gcm::FakeGCMDriver {
public:
FakeGCMDriver() = default;
FakeGCMDriver(const FakeGCMDriver&) = delete;
FakeGCMDriver& operator=(const FakeGCMDriver&) = delete;
~FakeGCMDriver() override = default;
void EncryptMessage(const std::string& app_id,
const std::string& authorized_entity,
const std::string& p256dh,
const std::string& auth_secret,
const std::string& message,
EncryptMessageCallback callback) override {
app_id_ = app_id;
authorized_entity_ = authorized_entity;
p256dh_ = p256dh;
auth_secret_ = auth_secret;
std::move(callback).Run(gcm::GCMEncryptionResult::ENCRYPTED_DRAFT_08,
message);
}
const std::string& app_id() { return app_id_; }
const std::string& authorized_entity() { return authorized_entity_; }
const std::string& p256dh() { return p256dh_; }
const std::string& auth_secret() { return auth_secret_; }
private:
std::string app_id_, authorized_entity_, p256dh_, auth_secret_;
};
class FakeSharingMessageBridge : public SharingMessageBridge {
public:
FakeSharingMessageBridge() = default;
FakeSharingMessageBridge(const FakeSharingMessageBridge&) = delete;
FakeSharingMessageBridge& operator=(const FakeSharingMessageBridge&) = delete;
~FakeSharingMessageBridge() override = default;
// SharingMessageBridge:
void SendSharingMessage(
std::unique_ptr<sync_pb::SharingMessageSpecifics> specifics,
CommitFinishedCallback on_commit_callback) override {
specifics_ = std::move(*specifics);
sync_pb::SharingMessageCommitError commit_error;
commit_error.set_error_code(error_code_);
std::move(on_commit_callback).Run(commit_error);
}
// SharingMessageBridge:
base::WeakPtr<syncer::DataTypeControllerDelegate> GetControllerDelegate()
override {
return nullptr;
}
const std::optional<sync_pb::SharingMessageSpecifics>& specifics() {
return specifics_;
}
void set_error_code(
const sync_pb::SharingMessageCommitError::ErrorCode& error_code) {
error_code_ = error_code;
}
private:
std::optional<sync_pb::SharingMessageSpecifics> specifics_;
sync_pb::SharingMessageCommitError::ErrorCode error_code_ =
sync_pb::SharingMessageCommitError::NONE;
};
class SharingFCMSenderTest : public testing::Test {
public:
void OnMessageSent(SharingSendMessageResult* result_out,
std::optional<std::string>* message_id_out,
SharingChannelType* channel_type_out,
SharingSendMessageResult result,
std::optional<std::string> message_id,
SharingChannelType channel_type) {
*result_out = result;
*message_id_out = std::move(message_id);
*channel_type_out = channel_type;
}
protected:
SharingFCMSenderTest()
: sync_prefs_(&prefs_, &fake_device_info_sync_service_),
sharing_fcm_sender_(
&fake_sharing_message_bridge_,
&sync_prefs_,
&fake_gcm_driver_,
fake_device_info_sync_service_.GetDeviceInfoTracker(),
&fake_local_device_info_provider_,
&test_sync_service_,
mock_sync_flare_.Get()) {
SharingSyncPreference::RegisterProfilePrefs(prefs_.registry());
}
sync_preferences::TestingPrefServiceSyncable prefs_;
FakeSharingMessageBridge fake_sharing_message_bridge_;
syncer::FakeDeviceInfoSyncService fake_device_info_sync_service_;
SharingSyncPreference sync_prefs_;
FakeGCMDriver fake_gcm_driver_;
syncer::FakeLocalDeviceInfoProvider fake_local_device_info_provider_;
syncer::TestSyncService test_sync_service_;
base::MockCallback<syncer::SyncableService::StartSyncFlare> mock_sync_flare_;
SharingFCMSender sharing_fcm_sender_;
};
TEST_F(SharingFCMSenderTest, NoFcmRegistration) {
sync_prefs_.ClearFCMRegistration();
// Do not populate sender ID channel.
components_sharing_message::FCMChannelConfiguration fcm_channel;
SharingSendMessageResult result;
std::optional<std::string> message_id;
SharingChannelType channel_type;
components_sharing_message::SharingMessage sharing_message;
sharing_message.mutable_ack_message();
sharing_fcm_sender_.SendMessageToFcmTarget(
fcm_channel, base::Seconds(kTtlSeconds), std::move(sharing_message),
base::BindOnce(&SharingFCMSenderTest::OnMessageSent,
base::Unretained(this), &result, &message_id,
&channel_type));
EXPECT_EQ(SharingSendMessageResult::kDeviceNotFound, result);
EXPECT_FALSE(message_id);
EXPECT_EQ(SharingChannelType::kUnknown, channel_type);
}
TEST_F(SharingFCMSenderTest, NoChannelsSpecified) {
sync_prefs_.SetFCMRegistration(
SharingSyncPreference::FCMRegistration(base::Time::Now()));
components_sharing_message::FCMChannelConfiguration fcm_channel;
// Don't set any channels.
SharingSendMessageResult result;
std::optional<std::string> message_id;
SharingChannelType channel_type;
components_sharing_message::SharingMessage sharing_message;
sharing_message.mutable_ack_message();
sharing_fcm_sender_.SendMessageToFcmTarget(
fcm_channel, base::Seconds(kTtlSeconds), std::move(sharing_message),
base::BindOnce(&SharingFCMSenderTest::OnMessageSent,
base::Unretained(this), &result, &message_id,
&channel_type));
EXPECT_EQ(SharingSendMessageResult::kDeviceNotFound, result);
EXPECT_FALSE(message_id);
EXPECT_EQ(SharingChannelType::kUnknown, channel_type);
}
TEST_F(SharingFCMSenderTest, PreferSync) {
sync_prefs_.SetFCMRegistration(
SharingSyncPreference::FCMRegistration(base::Time::Now()));
fake_sharing_message_bridge_.set_error_code(
sync_pb::SharingMessageCommitError::NONE);
components_sharing_message::FCMChannelConfiguration fcm_channel;
fcm_channel.set_sender_id_fcm_token(kSenderIdFcmToken);
fcm_channel.set_sender_id_p256dh(kSenderIdP256dh);
fcm_channel.set_sender_id_auth_secret(kSenderIdAuthSecret);
SharingSendMessageResult result;
std::optional<std::string> message_id;
SharingChannelType channel_type;
components_sharing_message::SharingMessage sharing_message;
sharing_message.mutable_ping_message();
sharing_fcm_sender_.SendMessageToFcmTarget(
fcm_channel, base::Seconds(kTtlSeconds), std::move(sharing_message),
base::BindOnce(&SharingFCMSenderTest::OnMessageSent,
base::Unretained(this), &result, &message_id,
&channel_type));
EXPECT_EQ(SharingSendMessageResult::kSuccessful, result);
// Ensures that a Ping message is sent through SharingMessageBridge.
components_sharing_message::SharingMessage message_sent;
ASSERT_TRUE(fake_sharing_message_bridge_.specifics());
ASSERT_TRUE(fake_sharing_message_bridge_.specifics()->has_payload());
message_sent.ParseFromString(
fake_sharing_message_bridge_.specifics()->payload());
EXPECT_TRUE(message_sent.has_ping_message());
}
struct CommitErrorCodeTestData {
const sync_pb::SharingMessageCommitError::ErrorCode commit_error_code;
const SharingSendMessageResult expected_result;
} kCommitErrorCodeTestData[] = {
{sync_pb::SharingMessageCommitError::NONE,
SharingSendMessageResult::kSuccessful},
{sync_pb::SharingMessageCommitError::NOT_FOUND,
SharingSendMessageResult::kDeviceNotFound},
{sync_pb::SharingMessageCommitError::INVALID_ARGUMENT,
SharingSendMessageResult::kPayloadTooLarge},
{sync_pb::SharingMessageCommitError::INTERNAL,
SharingSendMessageResult::kInternalError},
{sync_pb::SharingMessageCommitError::UNAVAILABLE,
SharingSendMessageResult::kInternalError},
{sync_pb::SharingMessageCommitError::RESOURCE_EXHAUSTED,
SharingSendMessageResult::kInternalError},
{sync_pb::SharingMessageCommitError::UNAUTHENTICATED,
SharingSendMessageResult::kInternalError},
{sync_pb::SharingMessageCommitError::PERMISSION_DENIED,
SharingSendMessageResult::kInternalError},
{sync_pb::SharingMessageCommitError::SYNC_TURNED_OFF,
SharingSendMessageResult::kInternalError},
{sync_pb::SharingMessageCommitError::SYNC_NETWORK_ERROR,
SharingSendMessageResult::kNetworkError},
{sync_pb::SharingMessageCommitError::SYNC_SERVER_ERROR,
SharingSendMessageResult::kInternalError},
{sync_pb::SharingMessageCommitError::SYNC_TIMEOUT,
SharingSendMessageResult::kCommitTimeout}};
class SharingFCMSenderCommitErrorCodeTest
: public SharingFCMSenderTest,
public testing::WithParamInterface<CommitErrorCodeTestData> {};
TEST_P(SharingFCMSenderCommitErrorCodeTest, ErrorCodeTest) {
fake_sharing_message_bridge_.set_error_code(GetParam().commit_error_code);
components_sharing_message::FCMChannelConfiguration fcm_channel;
fcm_channel.set_sender_id_fcm_token(kSenderIdFcmToken);
fcm_channel.set_sender_id_p256dh(kSenderIdP256dh);
fcm_channel.set_sender_id_auth_secret(kSenderIdAuthSecret);
SharingSendMessageResult result;
std::optional<std::string> message_id;
SharingChannelType channel_type;
components_sharing_message::SharingMessage sharing_message;
sharing_message.mutable_ping_message();
sharing_fcm_sender_.SendMessageToFcmTarget(
fcm_channel, base::Seconds(kTtlSeconds), std::move(sharing_message),
base::BindOnce(&SharingFCMSenderTest::OnMessageSent,
base::Unretained(this), &result, &message_id,
&channel_type));
EXPECT_EQ(kSharingFCMAppID, fake_gcm_driver_.app_id());
EXPECT_EQ(kSharingSenderID, fake_gcm_driver_.authorized_entity());
EXPECT_EQ(kSenderIdP256dh, fake_gcm_driver_.p256dh());
EXPECT_EQ(kSenderIdAuthSecret, fake_gcm_driver_.auth_secret());
auto specifics = fake_sharing_message_bridge_.specifics();
ASSERT_TRUE(specifics);
ASSERT_TRUE(specifics->has_channel_configuration());
ASSERT_TRUE(specifics->channel_configuration().has_fcm());
auto fcm = specifics->channel_configuration().fcm();
EXPECT_EQ(kSenderIdFcmToken, fcm.token());
EXPECT_EQ(kTtlSeconds, fcm.ttl());
EXPECT_EQ(10, fcm.priority());
components_sharing_message::SharingMessage message_sent;
ASSERT_TRUE(specifics->has_payload());
message_sent.ParseFromString(specifics->payload());
EXPECT_TRUE(message_sent.has_ping_message());
EXPECT_EQ(GetParam().expected_result, result);
EXPECT_TRUE(message_id);
EXPECT_EQ(SharingChannelType::kFcmSenderId, channel_type);
}
INSTANTIATE_TEST_SUITE_P(All,
SharingFCMSenderCommitErrorCodeTest,
testing::ValuesIn(kCommitErrorCodeTestData));
TEST_F(SharingFCMSenderTest, ServerTarget) {
fake_sharing_message_bridge_.set_error_code(
sync_pb::SharingMessageCommitError::NONE);
components_sharing_message::ServerChannelConfiguration server_channel;
server_channel.set_configuration(kServerConfiguration);
server_channel.set_p256dh(kServerP256dh);
server_channel.set_auth_secret(kServerAuthSecret);
SharingSendMessageResult result;
std::optional<std::string> message_id;
SharingChannelType channel_type;
components_sharing_message::SharingMessage sharing_message;
sharing_message.mutable_ping_message();
sharing_fcm_sender_.SendMessageToServerTarget(
server_channel, std::move(sharing_message),
base::BindOnce(&SharingFCMSenderTest::OnMessageSent,
base::Unretained(this), &result, &message_id,
&channel_type));
EXPECT_EQ(kSharingFCMAppID, fake_gcm_driver_.app_id());
EXPECT_EQ(kSharingSenderID, fake_gcm_driver_.authorized_entity());
EXPECT_EQ(kServerP256dh, fake_gcm_driver_.p256dh());
EXPECT_EQ(kServerAuthSecret, fake_gcm_driver_.auth_secret());
auto specifics = fake_sharing_message_bridge_.specifics();
ASSERT_TRUE(specifics);
ASSERT_TRUE(specifics->has_channel_configuration());
EXPECT_EQ(kServerConfiguration, specifics->channel_configuration().server());
components_sharing_message::SharingMessage message_sent;
ASSERT_TRUE(specifics->has_payload());
message_sent.ParseFromString(specifics->payload());
EXPECT_TRUE(message_sent.has_ping_message());
EXPECT_EQ(SharingSendMessageResult::kSuccessful, result);
EXPECT_TRUE(message_id);
EXPECT_EQ(SharingChannelType::kServer, channel_type);
}
TEST_F(SharingFCMSenderTest, ShouldPostponeSendingMessageViaSync) {
// Make sync unavailable to simulate browser startup.
test_sync_service_.SetFailedDataTypes({syncer::SHARING_MESSAGE});
sync_prefs_.SetFCMRegistration(
SharingSyncPreference::FCMRegistration(base::Time::Now()));
components_sharing_message::FCMChannelConfiguration fcm_channel;
fcm_channel.set_sender_id_fcm_token(kSenderIdFcmToken);
fcm_channel.set_sender_id_p256dh(kSenderIdP256dh);
fcm_channel.set_sender_id_auth_secret(kSenderIdAuthSecret);
base::MockCallback<
SharingMessageSender::SendMessageDelegate::SendMessageCallback>
callback;
EXPECT_CALL(callback, Run).Times(0);
// Sync flare should be called to speed up sync start.
EXPECT_CALL(mock_sync_flare_, Run(syncer::SHARING_MESSAGE));
components_sharing_message::SharingMessage sharing_message;
sharing_message.mutable_ack_message();
sharing_fcm_sender_.SendMessageToFcmTarget(
fcm_channel, base::Seconds(kTtlSeconds), std::move(sharing_message),
callback.Get());
testing::Mock::VerifyAndClearExpectations(&callback);
// Once SHARING_MESSAGE becomes active, the message should be sent.
EXPECT_CALL(callback, Run(SharingSendMessageResult::kSuccessful, _,
SharingChannelType::kFcmSenderId));
test_sync_service_.SetFailedDataTypes({});
test_sync_service_.FireStateChanged();
}
TEST_F(SharingFCMSenderTest, ShouldClearPendingMessages) {
// Make sync unavailable to simulate browser startup.
test_sync_service_.SetFailedDataTypes({syncer::SHARING_MESSAGE});
sync_prefs_.SetFCMRegistration(
SharingSyncPreference::FCMRegistration(base::Time::Now()));
components_sharing_message::FCMChannelConfiguration fcm_channel;
fcm_channel.set_sender_id_fcm_token(kSenderIdFcmToken);
fcm_channel.set_sender_id_p256dh(kSenderIdP256dh);
fcm_channel.set_sender_id_auth_secret(kSenderIdAuthSecret);
base::MockCallback<
SharingMessageSender::SendMessageDelegate::SendMessageCallback>
callback;
EXPECT_CALL(callback, Run).Times(0);
components_sharing_message::SharingMessage sharing_message;
sharing_message.mutable_ack_message();
sharing_fcm_sender_.SendMessageToFcmTarget(
fcm_channel, base::Seconds(kTtlSeconds), std::move(sharing_message),
callback.Get());
// Clear any pending messages and verify that nothing is sent once
// SHARING_MESSAGE becomes active (the `callback` above should not be called).
sharing_fcm_sender_.ClearPendingMessages();
test_sync_service_.SetFailedDataTypes({});
test_sync_service_.FireStateChanged();
}
} // namespace