Commit 539ace77 authored by Tanja Gornak's avatar Tanja Gornak Committed by Commit Bot

FCM Network handler for the invalidations.

The network handler purpose is:

1. Connect to the GCM channel
2. Retrieve the instance_id token and forward it to the registration manager.
3. Forward incoming messages to the message handlers.

Bug: 863893, 801985
Change-Id: I236e5a2374468aaceb69641b05d535a3685d716e
Reviewed-on: https://chromium-review.googlesource.com/1138317
Commit-Queue: Tatiana Gornak <melandory@chromium.org>
Reviewed-by: default avatarPavel Yatsuk <pavely@chromium.org>
Cr-Commit-Position: refs/heads/master@{#576506}
parent 705e1516
......@@ -65,6 +65,8 @@ static_library("impl") {
if (!is_android) {
sources += [
"fcm_network_handler.cc",
"fcm_network_handler.h",
"fcm_sync_invalidation_listener.cc",
"fcm_sync_invalidation_listener.h",
"fcm_sync_network_channel.cc",
......@@ -154,6 +156,7 @@ source_set("unit_tests") {
# Non-Android tests.
sources += [
"fake_invalidator_unittest.cc",
"fcm_network_handler_unittests.cc",
"fcm_sync_invalidation_listener_unittest.cc",
"gcm_invalidation_bridge_unittest.cc",
"gcm_network_channel_unittest.cc",
......@@ -180,6 +183,7 @@ source_set("unit_tests") {
"//components/signin/core/browser:test_support",
"//components/sync_preferences:test_support",
"//google_apis:test_support",
"//google_apis/gcm:gcm",
"//net",
"//services/identity/public/cpp:test_support",
]
......
// Copyright 2018 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 "components/invalidation/impl/fcm_network_handler.h"
#include "base/base64url.h"
#include "base/callback.h"
#include "base/observer_list.h"
#include "base/task_scheduler/post_task.h"
#include "build/build_config.h"
#include "components/gcm_driver/gcm_driver.h"
#include "components/gcm_driver/gcm_profile_service.h"
#include "components/gcm_driver/instance_id/instance_id.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "components/invalidation/public/invalidator_state.h"
using instance_id::InstanceID;
namespace syncer {
namespace {
const char kInvalidationsAppId[] = "com.google.chrome.fcm.invalidations";
const char kInvalidationGCMSenderId[] = "8181035976";
const char kContentKey[] = "data";
// OAuth2 Scope passed to getToken to obtain GCM registration tokens.
// Must match Java GoogleCloudMessaging.INSTANCE_ID_SCOPE.
const char kGCMScope[] = "GCM";
} // namespace
FCMNetworkHandler::FCMNetworkHandler(
gcm::GCMDriver* gcm_driver,
instance_id::InstanceIDDriver* instance_id_driver)
: gcm_driver_(gcm_driver),
instance_id_driver_(instance_id_driver),
weak_ptr_factory_(this) {}
FCMNetworkHandler::~FCMNetworkHandler() {
StopListening();
}
void FCMNetworkHandler::StartListening() {
instance_id_driver_->GetInstanceID(kInvalidationsAppId)
->GetToken(kInvalidationGCMSenderId, kGCMScope,
/*options=*/std::map<std::string, std::string>(),
base::BindRepeating(&FCMNetworkHandler::DidRetrieveToken,
weak_ptr_factory_.GetWeakPtr()));
gcm_driver_->AddAppHandler(kInvalidationsAppId, this);
}
void FCMNetworkHandler::StopListening() {
if (gcm_driver_->GetAppHandler(kInvalidationsAppId))
gcm_driver_->RemoveAppHandler(kInvalidationsAppId);
}
void FCMNetworkHandler::DidRetrieveToken(const std::string& subscription_token,
InstanceID::Result result) {
switch (result) {
case InstanceID::SUCCESS:
DeliverToken(subscription_token);
return;
case InstanceID::INVALID_PARAMETER:
case InstanceID::DISABLED:
case InstanceID::ASYNC_OPERATION_PENDING:
case InstanceID::SERVER_ERROR:
case InstanceID::UNKNOWN_ERROR:
case InstanceID::NETWORK_ERROR:
DLOG(WARNING) << "Messaging subscription failed; InstanceID::Result = "
<< result;
UpdateGcmChannelState(false);
break;
}
}
void FCMNetworkHandler::UpdateGcmChannelState(bool online) {
if (gcm_channel_online_ == online)
return;
gcm_channel_online_ = online;
NotifyChannelStateChange(gcm_channel_online_ ? INVALIDATIONS_ENABLED
: TRANSIENT_INVALIDATION_ERROR);
}
void FCMNetworkHandler::ShutdownHandler() {}
void FCMNetworkHandler::OnStoreReset() {}
void FCMNetworkHandler::OnMessage(const std::string& app_id,
const gcm::IncomingMessage& message) {
DCHECK_EQ(app_id, kInvalidationsAppId);
std::string content;
auto it = message.data.find(kContentKey);
if (it != message.data.end())
content = it->second;
if (content.empty()) {
return;
}
// TODO(melandory): check if content is empty and report.
// TODO(melandory): decode base64 and report in case it is invalid.
// TODO(melandory): parse proto and record histogram in case of invalid proto.
// TODO(melandory): report histogram in case of success.
UpdateGcmChannelState(true);
DeliverIncomingMessage(content);
}
void FCMNetworkHandler::OnMessagesDeleted(const std::string& app_id) {
// TODO(melandory): consider notifyint the client that messages were
// deleted. So the client can act on it, e.g. in case of sync request
// GetUpdates from the server.
}
void FCMNetworkHandler::OnSendError(
const std::string& app_id,
const gcm::GCMClient::SendErrorDetails& details) {
// Should never be called because we don't send GCM messages to
// the server.
NOTREACHED() << "FCMNetworkHandler doesn't send GCM messages.";
}
void FCMNetworkHandler::OnSendAcknowledged(const std::string& app_id,
const std::string& message_id) {
// Should never be called because we don't send GCM messages to
// the server.
NOTREACHED() << "FCMNetworkHandler doesn't send GCM messages.";
}
} // namespace syncer
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#ifndef COMPONENTS_INVALIDATION_IMPL_FCM_NETWORK_HANDLER_H_
#define COMPONENTS_INVALIDATION_IMPL_FCM_NETWORK_HANDLER_H_
#include "base/memory/weak_ptr.h"
#include "components/gcm_driver/gcm_app_handler.h"
#include "components/gcm_driver/instance_id/instance_id.h"
#include "components/invalidation/impl/fcm_sync_network_channel.h"
namespace gcm {
class GCMDriver;
}
namespace instance_id {
class InstanceIDDriver;
}
namespace syncer {
/*
* The class responsible for communication via GCM channel:
* - It retrieves the token required for the subscription
* and passes it by invoking token callback.
* - It receives the messages and passes them to the
* invalidation infrustructure, so they can be converted to the
* invalidations and consumed by listeners.
*/
class FCMNetworkHandler : public gcm::GCMAppHandler,
public FCMSyncNetworkChannel {
public:
FCMNetworkHandler(gcm::GCMDriver* gcm_driver,
instance_id::InstanceIDDriver* instance_id_driver);
~FCMNetworkHandler() override;
void StartListening();
void StopListening();
void UpdateGcmChannelState(bool);
// GCMAppHandler overrides.
void ShutdownHandler() override;
void OnStoreReset() override;
void OnMessage(const std::string& app_id,
const gcm::IncomingMessage& message) override;
void OnMessagesDeleted(const std::string& app_id) override;
void OnSendError(const std::string& app_id,
const gcm::GCMClient::SendErrorDetails& details) override;
void OnSendAcknowledged(const std::string& app_id,
const std::string& message_id) override;
private:
// Called when a subscription token is obtained from the GCM server.
void DidRetrieveToken(const std::string& subscription_token,
instance_id::InstanceID::Result result);
gcm::GCMDriver* const gcm_driver_;
instance_id::InstanceIDDriver* const instance_id_driver_;
bool gcm_channel_online_ = false;
base::WeakPtrFactory<FCMNetworkHandler> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(FCMNetworkHandler);
};
} // namespace syncer
#endif // COMPONENTS_INVALIDATION_IMPL_FCM_NETWORK_HANDLER_H_
// Copyright 2018 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 "components/invalidation/impl/fcm_network_handler.h"
#include <memory>
#include <string>
#include "base/bind_helpers.h"
#include "base/files/file_path.h"
#include "base/message_loop/message_loop.h"
#include "base/test/mock_callback.h"
#include "build/build_config.h"
#include "components/gcm_driver/gcm_driver.h"
#include "components/gcm_driver/instance_id/instance_id.h"
#include "components/gcm_driver/instance_id/instance_id_driver.h"
#include "google_apis/gcm/engine/account_mapping.h"
#include "testing/gmock/include/gmock/gmock.h"
using gcm::InstanceIDHandler;
using instance_id::InstanceID;
using instance_id::InstanceIDDriver;
using testing::_;
using testing::StrictMock;
namespace syncer {
namespace {
const char kInvalidationsAppId[] = "com.google.chrome.fcm.invalidations";
using DataCallback = base::RepeatingCallback<void(const std::string& message)>;
class MockInstanceID : public InstanceID {
public:
MockInstanceID() : InstanceID("app_id", /*gcm_driver=*/nullptr) {}
~MockInstanceID() override = default;
MOCK_METHOD1(GetID, void(const GetIDCallback& callback));
MOCK_METHOD1(GetCreationTime, void(const GetCreationTimeCallback& callback));
MOCK_METHOD4(GetToken,
void(const std::string& authorized_entity,
const std::string& scope,
const std::map<std::string, std::string>& options,
const GetTokenCallback& callback));
MOCK_METHOD4(ValidateToken,
void(const std::string& authorized_entity,
const std::string& scope,
const std::string& token,
const ValidateTokenCallback& callback));
protected:
MOCK_METHOD3(DeleteTokenImpl,
void(const std::string& authorized_entity,
const std::string& scope,
const DeleteTokenCallback& callback));
MOCK_METHOD1(DeleteIDImpl, void(const DeleteIDCallback& callback));
private:
DISALLOW_COPY_AND_ASSIGN(MockInstanceID);
};
class MockGCMDriver : public gcm::GCMDriver {
public:
MockGCMDriver()
: GCMDriver(/*store_path=*/base::FilePath(),
/*blocking_task_runner=*/nullptr) {}
~MockGCMDriver() override = default;
MOCK_METHOD4(ValidateRegistration,
void(const std::string& app_id,
const std::vector<std::string>& sender_ids,
const std::string& registration_id,
const ValidateRegistrationCallback& callback));
MOCK_METHOD0(OnSignedIn, void());
MOCK_METHOD0(OnSignedOut, void());
MOCK_METHOD1(AddConnectionObserver,
void(gcm::GCMConnectionObserver* observer));
MOCK_METHOD1(RemoveConnectionObserver,
void(gcm::GCMConnectionObserver* observer));
MOCK_METHOD0(Enable, void());
MOCK_METHOD0(Disable, void());
MOCK_CONST_METHOD0(GetGCMClientForTesting, gcm::GCMClient*());
MOCK_CONST_METHOD0(IsStarted, bool());
MOCK_CONST_METHOD0(IsConnected, bool());
MOCK_METHOD2(GetGCMStatistics,
void(const GetGCMStatisticsCallback& callback,
ClearActivityLogs clear_logs));
MOCK_METHOD2(SetGCMRecording,
void(const GetGCMStatisticsCallback& callback, bool recording));
MOCK_METHOD1(SetAccountTokens,
void(const std::vector<gcm::GCMClient::AccountTokenInfo>&
account_tokens));
MOCK_METHOD1(UpdateAccountMapping,
void(const gcm::AccountMapping& account_mapping));
MOCK_METHOD1(RemoveAccountMapping, void(const std::string& account_id));
MOCK_METHOD0(GetLastTokenFetchTime, base::Time());
MOCK_METHOD1(SetLastTokenFetchTime, void(const base::Time& time));
MOCK_METHOD1(WakeFromSuspendForHeartbeat, void(bool wake));
MOCK_METHOD0(GetInstanceIDHandlerInternal, InstanceIDHandler*());
MOCK_METHOD2(AddHeartbeatInterval,
void(const std::string& scope, int interval_ms));
MOCK_METHOD1(RemoveHeartbeatInterval, void(const std::string& scope));
protected:
MOCK_METHOD1(EnsureStarted,
gcm::GCMClient::Result(gcm::GCMClient::StartMode start_mode));
MOCK_METHOD2(RegisterImpl,
void(const std::string& app_id,
const std::vector<std::string>& sender_ids));
MOCK_METHOD1(UnregisterImpl, void(const std::string& app_id));
MOCK_METHOD3(SendImpl,
void(const std::string& app_id,
const std::string& receiver_id,
const gcm::OutgoingMessage& message));
MOCK_METHOD2(RecordDecryptionFailure,
void(const std::string& app_id,
gcm::GCMDecryptionResult result));
private:
DISALLOW_COPY_AND_ASSIGN(MockGCMDriver);
};
class MockInstanceIDDriver : public InstanceIDDriver {
public:
MockInstanceIDDriver() : InstanceIDDriver(/*gcm_driver=*/nullptr){};
~MockInstanceIDDriver() override = default;
MOCK_METHOD1(GetInstanceID, InstanceID*(const std::string& app_id));
MOCK_METHOD1(RemoveInstanceID, void(const std::string& app_id));
MOCK_CONST_METHOD1(ExistsInstanceID, bool(const std::string& app_id));
private:
DISALLOW_COPY_AND_ASSIGN(MockInstanceIDDriver);
};
class MockOnDataCallback {
public:
// Workaround for gMock's lack of support for movable-only arguments.
void WrappedRun(const std::string& token) { Run(token); }
DataCallback Get() {
return base::BindRepeating(&MockOnDataCallback::WrappedRun,
base::Unretained(this));
}
MOCK_METHOD1(Run, void(const std::string&));
};
ACTION_TEMPLATE(InvokeCallbackArgument,
HAS_1_TEMPLATE_PARAMS(int, k),
AND_1_VALUE_PARAMS(p0)) {
std::get<k>(args).Run(p0);
}
ACTION_TEMPLATE(InvokeCallbackArgument,
HAS_1_TEMPLATE_PARAMS(int, k),
AND_2_VALUE_PARAMS(p0, p1)) {
std::get<k>(args).Run(p0, p1);
}
} // namespace
class FCMNetworkHandlerTest : public testing::Test {
public:
void SetUp() override {
// Our app handler obtains InstanceID through InstanceIDDriver. We mock
// InstanceIDDriver and return MockInstanceID through it.
mock_instance_id_driver_ =
std::make_unique<StrictMock<MockInstanceIDDriver>>();
mock_instance_id_ = std::make_unique<StrictMock<MockInstanceID>>();
mock_gcm_driver_ = std::make_unique<StrictMock<MockGCMDriver>>();
// This is called in FCMNetworkHandler.
EXPECT_CALL(*mock_instance_id_driver_, GetInstanceID(kInvalidationsAppId))
.WillRepeatedly(Return(mock_instance_id_.get()));
}
std::unique_ptr<FCMNetworkHandler> MakeHandler() {
return std::make_unique<FCMNetworkHandler>(mock_gcm_driver_.get(),
mock_instance_id_driver_.get());
}
StrictMock<MockInstanceID>* mock_instance_id() {
return mock_instance_id_.get();
}
private:
base::MessageLoop message_loop_;
std::unique_ptr<StrictMock<MockGCMDriver>> mock_gcm_driver_;
std::unique_ptr<StrictMock<MockInstanceIDDriver>> mock_instance_id_driver_;
std::unique_ptr<StrictMock<MockInstanceID>> mock_instance_id_;
};
TEST_F(FCMNetworkHandlerTest, ShouldPassTheTokenOnceRecieved) {
std::unique_ptr<FCMNetworkHandler> handler = MakeHandler();
MockOnDataCallback mock_on_token_callback;
handler->SetTokenReceiver(mock_on_token_callback.Get());
// Check that the handler gets the token through GetToken.
EXPECT_CALL(*mock_instance_id(), GetToken(_, _, _, _))
.WillOnce(
InvokeCallbackArgument<3>("token", InstanceID::Result::SUCCESS));
EXPECT_CALL(mock_on_token_callback, Run("token")).Times(1);
handler->StartListening();
}
TEST_F(FCMNetworkHandlerTest, ShouldPassTheTokenOnceSubscribed) {
std::unique_ptr<FCMNetworkHandler> handler = MakeHandler();
MockOnDataCallback mock_on_token_callback;
// Check that the handler gets the token through GetToken.
EXPECT_CALL(*mock_instance_id(), GetToken(_, _, _, _))
.WillOnce(
InvokeCallbackArgument<3>("token", InstanceID::Result::SUCCESS));
EXPECT_CALL(mock_on_token_callback, Run(_)).Times(0);
handler->StartListening();
base::RunLoop().RunUntilIdle();
EXPECT_CALL(mock_on_token_callback, Run("token")).Times(1);
handler->SetTokenReceiver(mock_on_token_callback.Get());
}
TEST_F(FCMNetworkHandlerTest, ShouldNotInvokeMessageCallbackOnEmptyMessage) {
MockOnDataCallback mock_on_message_callback;
std::unique_ptr<FCMNetworkHandler> handler = MakeHandler();
EXPECT_CALL(mock_on_message_callback, Run(_)).Times(0);
handler->SetMessageReceiver(mock_on_message_callback.Get());
EXPECT_CALL(*mock_instance_id(), GetToken(_, _, _, _))
.WillOnce(
InvokeCallbackArgument<3>("token", InstanceID::Result::SUCCESS));
handler->StartListening();
handler->OnMessage(kInvalidationsAppId, gcm::IncomingMessage());
}
TEST_F(FCMNetworkHandlerTest, ShouldInvokeMessageCallbackOnValidMessage) {
MockOnDataCallback mock_on_message_callback;
gcm::IncomingMessage message;
message.data["data"] = "test";
std::unique_ptr<FCMNetworkHandler> handler = MakeHandler();
EXPECT_CALL(*mock_instance_id(), GetToken(_, _, _, _))
.WillOnce(
InvokeCallbackArgument<3>("token", InstanceID::Result::SUCCESS));
handler->StartListening();
EXPECT_CALL(mock_on_message_callback, Run("test")).Times(0);
handler->SetMessageReceiver(mock_on_message_callback.Get());
handler->OnMessage(kInvalidationsAppId, gcm::IncomingMessage());
}
} // namespace syncer
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment