Commit ab93f9f0 authored by Josh Nohle's avatar Josh Nohle Committed by Commit Bot

[DeviceSync v2] Add CryptAuthDeviceNotifier class

Handles the BatchNotifyGroupDevices portion of the CryptAuth v2
DeviceSync protocol, which sends a GCM message--via CryptAuth--to
a subset of devices in the "DeviceSync:BetterTogether" group.

The implementation queues requests made via NotifyDevices() and
processes the requests sequentially. The implementation also handles
timeouts internally, so a callback passed to NotifyDevices() is always
guaranteed to be invoked.

CryptAuthDeviceNotifier::NotifyDevices() will replace the CryptAuth v1
method SoftwareFeatureManager::FindEligibleDevices(), which was used to
notify a phone that it was selected as the multi-device host.

Bug: 951969
Change-Id: I155e35847f5d6d9d99d8cf4477e70819d82d8e21
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1888734
Commit-Queue: Josh Nohle <nohle@chromium.org>
Reviewed-by: default avatarKyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#710817}
parent 868f7128
......@@ -23,6 +23,9 @@ static_library("device_sync") {
"cryptauth_device_manager.h",
"cryptauth_device_manager_impl.cc",
"cryptauth_device_manager_impl.h",
"cryptauth_device_notifier.h",
"cryptauth_device_notifier_impl.cc",
"cryptauth_device_notifier_impl.h",
"cryptauth_device_registry.cc",
"cryptauth_device_registry.h",
"cryptauth_device_registry_impl.cc",
......@@ -170,6 +173,8 @@ static_library("test_support") {
"cryptauth_v2_device_sync_test_devices.h",
"fake_cryptauth_device_manager.cc",
"fake_cryptauth_device_manager.h",
"fake_cryptauth_device_notifier.cc",
"fake_cryptauth_device_notifier.h",
"fake_cryptauth_device_syncer.cc",
"fake_cryptauth_device_syncer.h",
"fake_cryptauth_ecies_encryptor.cc",
......@@ -236,6 +241,7 @@ source_set("unit_tests") {
"cryptauth_client_impl_unittest.cc",
"cryptauth_device_activity_getter_impl_unittest.cc",
"cryptauth_device_manager_impl_unittest.cc",
"cryptauth_device_notifier_impl_unittest.cc",
"cryptauth_device_registry_impl_unittest.cc",
"cryptauth_device_syncer_impl_unittest.cc",
"cryptauth_device_unittest.cc",
......
// Copyright 2019 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 CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_DEVICE_NOTIFIER_H_
#define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_DEVICE_NOTIFIER_H_
#include <string>
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/macros.h"
#include "chromeos/services/device_sync/cryptauth_feature_type.h"
#include "chromeos/services/device_sync/network_request_error.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
namespace chromeos {
namespace device_sync {
// Handles the BatchNotifyGroupDevices portion of the CryptAuth v2 DeviceSync
// protocol, which sends a GCM message--via CryptAuth--to a subset of devices in
// the "DeviceSync:BetterTogether" group.
class CryptAuthDeviceNotifier {
public:
CryptAuthDeviceNotifier() = default;
virtual ~CryptAuthDeviceNotifier() = default;
// Sends a GCM message to devices with Instance IDs |device_ids|. The message
// includes the CryptAuth service--Enrollment or DeviceSync--and the feature
// type--kBetterTogetherHostEnabled, for example--indicating the reason for
// the notification; these are specified by |target_service| and
// |feature_type|, respectively.
virtual void NotifyDevices(
const base::flat_set<std::string>& device_ids,
cryptauthv2::TargetService target_service,
CryptAuthFeatureType feature_type,
base::OnceClosure success_callback,
base::OnceCallback<void(NetworkRequestError)> error_callback) = 0;
private:
DISALLOW_COPY_AND_ASSIGN(CryptAuthDeviceNotifier);
};
} // namespace device_sync
} // namespace chromeos
#endif // CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_DEVICE_NOTIFIER_H_
// Copyright 2019 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 "chromeos/services/device_sync/cryptauth_device_notifier_impl.h"
#include <utility>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "chromeos/components/multidevice/logging/logging.h"
#include "chromeos/services/device_sync/async_execution_time_metrics_logger.h"
#include "chromeos/services/device_sync/cryptauth_client.h"
#include "chromeos/services/device_sync/cryptauth_gcm_manager.h"
#include "chromeos/services/device_sync/cryptauth_key_bundle.h"
#include "chromeos/services/device_sync/cryptauth_task_metrics_logger.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
#include "chromeos/services/device_sync/public/cpp/client_app_metadata_provider.h"
namespace chromeos {
namespace device_sync {
namespace {
// Timeout values for asynchronous operation.
// TODO(https://crbug.com/933656): Use async execution time metric to tune these
// timeout values.
constexpr base::TimeDelta kWaitingForClientAppMetadataTimeout =
base::TimeDelta::FromSeconds(60);
constexpr base::TimeDelta kWaitingForBatchNotifyGroupDevicesResponseTimeout =
kMaxAsyncExecutionTime;
void RecordClientAppMetadataFetchMetrics(const base::TimeDelta& execution_time,
CryptAuthAsyncTaskResult result) {
// TODO(https://crbug.com/933656, https://crbug.com/936273): Add metrics to
// track async execution times and failure rates due to async timeouts.
}
void RecordBatchNotifyGroupDevicesMetrics(const base::TimeDelta& execution_time,
CryptAuthApiCallResult result) {
// TODO(https://crbug.com/933656, https://crbug.com/936273): Add metrics to
// track async execution times and failure rates due to async timeouts.
}
} // namespace
// static
CryptAuthDeviceNotifierImpl::Factory*
CryptAuthDeviceNotifierImpl::Factory::test_factory_ = nullptr;
// static
CryptAuthDeviceNotifierImpl::Factory*
CryptAuthDeviceNotifierImpl::Factory::Get() {
if (test_factory_)
return test_factory_;
static base::NoDestructor<CryptAuthDeviceNotifierImpl::Factory> factory;
return factory.get();
}
// static
void CryptAuthDeviceNotifierImpl::Factory::SetFactoryForTesting(
Factory* test_factory) {
test_factory_ = test_factory;
}
CryptAuthDeviceNotifierImpl::Factory::~Factory() = default;
std::unique_ptr<CryptAuthDeviceNotifier>
CryptAuthDeviceNotifierImpl::Factory::BuildInstance(
ClientAppMetadataProvider* client_app_metadata_provider,
CryptAuthClientFactory* client_factory,
CryptAuthGCMManager* gcm_manager,
std::unique_ptr<base::OneShotTimer> timer) {
return base::WrapUnique(new CryptAuthDeviceNotifierImpl(
client_app_metadata_provider, client_factory, gcm_manager,
std::move(timer)));
}
CryptAuthDeviceNotifierImpl::CryptAuthDeviceNotifierImpl(
ClientAppMetadataProvider* client_app_metadata_provider,
CryptAuthClientFactory* client_factory,
CryptAuthGCMManager* gcm_manager,
std::unique_ptr<base::OneShotTimer> timer)
: client_app_metadata_provider_(client_app_metadata_provider),
client_factory_(client_factory),
gcm_manager_(gcm_manager),
timer_(std::move(timer)) {}
CryptAuthDeviceNotifierImpl::~CryptAuthDeviceNotifierImpl() = default;
// static
base::Optional<base::TimeDelta> CryptAuthDeviceNotifierImpl::GetTimeoutForState(
State state) {
switch (state) {
case State::kWaitingForClientAppMetadata:
return kWaitingForClientAppMetadataTimeout;
case State::kWaitingForBatchNotifyGroupDevicesResponse:
return kWaitingForBatchNotifyGroupDevicesResponseTimeout;
default:
// Signifies that there should not be a timeout.
return base::nullopt;
}
}
CryptAuthDeviceNotifierImpl::Request::Request(
const base::flat_set<std::string>& device_ids,
cryptauthv2::TargetService target_service,
CryptAuthFeatureType feature_type,
base::OnceClosure success_callback,
base::OnceCallback<void(NetworkRequestError)> error_callback)
: device_ids(device_ids),
target_service(target_service),
feature_type(feature_type),
success_callback(std::move(success_callback)),
error_callback(std::move(error_callback)) {}
CryptAuthDeviceNotifierImpl::Request::Request(Request&& request)
: device_ids(std::move(request.device_ids)),
target_service(request.target_service),
feature_type(request.feature_type),
success_callback(std::move(request.success_callback)),
error_callback(std::move(request.error_callback)) {}
CryptAuthDeviceNotifierImpl::Request::~Request() = default;
void CryptAuthDeviceNotifierImpl::NotifyDevices(
const base::flat_set<std::string>& device_ids,
cryptauthv2::TargetService target_service,
CryptAuthFeatureType feature_type,
base::OnceClosure success_callback,
base::OnceCallback<void(NetworkRequestError)> error_callback) {
DCHECK(!device_ids.empty());
DCHECK_NE(cryptauthv2::TargetService::TARGET_SERVICE_UNSPECIFIED,
target_service);
pending_requests_.emplace(device_ids, target_service, feature_type,
std::move(success_callback),
std::move(error_callback));
if (state_ == State::kIdle)
ProcessRequestQueue();
}
void CryptAuthDeviceNotifierImpl::SetState(State state) {
timer_->Stop();
PA_LOG(INFO) << "Transitioning from " << state_ << " to " << state;
state_ = state;
last_state_change_timestamp_ = base::TimeTicks::Now();
base::Optional<base::TimeDelta> timeout_for_state = GetTimeoutForState(state);
if (!timeout_for_state)
return;
timer_->Start(FROM_HERE, *timeout_for_state,
base::BindOnce(&CryptAuthDeviceNotifierImpl::OnTimeout,
base::Unretained(this)));
}
void CryptAuthDeviceNotifierImpl::OnTimeout() {
base::TimeDelta execution_time =
base::TimeTicks::Now() - last_state_change_timestamp_;
switch (state_) {
case State::kWaitingForClientAppMetadata:
RecordClientAppMetadataFetchMetrics(execution_time,
CryptAuthAsyncTaskResult::kTimeout);
break;
case State::kWaitingForBatchNotifyGroupDevicesResponse:
RecordBatchNotifyGroupDevicesMetrics(execution_time,
CryptAuthApiCallResult::kTimeout);
break;
default:
NOTREACHED();
}
PA_LOG(ERROR) << "Timed out in state " << state_ << ".";
// TODO(https://crbug.com/1011358): Use more specific error codes.
FinishAttempt(NetworkRequestError::kUnknown);
}
void CryptAuthDeviceNotifierImpl::ProcessRequestQueue() {
if (pending_requests_.empty())
return;
if (!client_app_metadata_) {
// GCM registration is expected to be completed before the first enrollment.
DCHECK(!gcm_manager_->GetRegistrationId().empty())
<< "DeviceSync requested before GCM registration complete.";
SetState(State::kWaitingForClientAppMetadata);
client_app_metadata_provider_->GetClientAppMetadata(
gcm_manager_->GetRegistrationId(),
base::BindOnce(&CryptAuthDeviceNotifierImpl::OnClientAppMetadataFetched,
weak_ptr_factory_.GetWeakPtr()));
return;
}
cryptauthv2::BatchNotifyGroupDevicesRequest request;
request.mutable_context()->set_group(
CryptAuthKeyBundle::KeyBundleNameEnumToString(
CryptAuthKeyBundle::Name::kDeviceSyncBetterTogether));
request.mutable_context()->mutable_client_metadata()->set_invocation_reason(
cryptauthv2::ClientMetadata::INVOCATION_REASON_UNSPECIFIED);
request.mutable_context()->set_device_id(client_app_metadata_->instance_id());
request.mutable_context()->set_device_id_token(
client_app_metadata_->instance_id_token());
*request.mutable_notify_device_ids() = {
pending_requests_.front().device_ids.begin(),
pending_requests_.front().device_ids.end()};
request.set_target_service(pending_requests_.front().target_service);
request.set_feature_type(
CryptAuthFeatureTypeToString(pending_requests_.front().feature_type));
SetState(State::kWaitingForBatchNotifyGroupDevicesResponse);
cryptauth_client_ = client_factory_->CreateInstance();
cryptauth_client_->BatchNotifyGroupDevices(
request,
base::Bind(&CryptAuthDeviceNotifierImpl::OnBatchNotifyGroupDevicesSuccess,
base::Unretained(this)),
base::Bind(&CryptAuthDeviceNotifierImpl::OnBatchNotifyGroupDevicesFailure,
base::Unretained(this)));
}
void CryptAuthDeviceNotifierImpl::OnClientAppMetadataFetched(
const base::Optional<cryptauthv2::ClientAppMetadata>& client_app_metadata) {
DCHECK_EQ(State::kWaitingForClientAppMetadata, state_);
bool success = client_app_metadata.has_value();
RecordClientAppMetadataFetchMetrics(
base::TimeTicks::Now() - last_state_change_timestamp_,
success ? CryptAuthAsyncTaskResult::kSuccess
: CryptAuthAsyncTaskResult::kError);
if (!success) {
PA_LOG(ERROR) << "ClientAppMetadata fetch failed.";
// TODO(https://crbug.com/1011358): Use more specific error codes.
FinishAttempt(NetworkRequestError::kUnknown);
return;
}
client_app_metadata_ = client_app_metadata;
ProcessRequestQueue();
}
void CryptAuthDeviceNotifierImpl::OnBatchNotifyGroupDevicesSuccess(
const cryptauthv2::BatchNotifyGroupDevicesResponse& response) {
DCHECK_EQ(State::kWaitingForBatchNotifyGroupDevicesResponse, state_);
FinishAttempt(base::nullopt /* error */);
}
void CryptAuthDeviceNotifierImpl::OnBatchNotifyGroupDevicesFailure(
NetworkRequestError error) {
DCHECK_EQ(State::kWaitingForBatchNotifyGroupDevicesResponse, state_);
PA_LOG(ERROR) << "BatchNotifyGroupDevices call failed with error " << error
<< ".";
FinishAttempt(error);
}
void CryptAuthDeviceNotifierImpl::FinishAttempt(
base::Optional<NetworkRequestError> error) {
DCHECK(!pending_requests_.empty());
Request current_request = std::move(pending_requests_.front());
pending_requests_.pop();
if (error) {
std::move(current_request.error_callback).Run(*error);
} else {
PA_LOG(VERBOSE) << "NotifyDevices attempt succeeded.";
std::move(current_request.success_callback).Run();
}
SetState(State::kIdle);
ProcessRequestQueue();
}
std::ostream& operator<<(std::ostream& stream,
const CryptAuthDeviceNotifierImpl::State& state) {
switch (state) {
case CryptAuthDeviceNotifierImpl::State::kIdle:
stream << "[CryptAuthDeviceNotifier state: Idle]";
break;
case CryptAuthDeviceNotifierImpl::State::kWaitingForClientAppMetadata:
stream << "[CryptAuthDeviceNotifier state: Waiting for "
<< "ClientAppMetadata]";
break;
case CryptAuthDeviceNotifierImpl::State::
kWaitingForBatchNotifyGroupDevicesResponse:
stream << "[CryptAuthDeviceNotifier state: Waiting for "
<< "BatchNotifyGroupDevices response]";
break;
}
return stream;
}
} // namespace device_sync
} // namespace chromeos
// Copyright 2019 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 CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_DEVICE_NOTIFIER_IMPL_H_
#define CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_DEVICE_NOTIFIER_IMPL_H_
#include <memory>
#include <ostream>
#include <string>
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/containers/queue.h"
#include "base/macros.h"
#include "base/timer/timer.h"
#include "chromeos/services/device_sync/cryptauth_device_notifier.h"
#include "chromeos/services/device_sync/cryptauth_feature_type.h"
#include "chromeos/services/device_sync/network_request_error.h"
#include "chromeos/services/device_sync/proto/cryptauth_client_app_metadata.pb.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
#include "chromeos/services/device_sync/proto/cryptauth_devicesync.pb.h"
namespace chromeos {
namespace device_sync {
class ClientAppMetadataProvider;
class CryptAuthClient;
class CryptAuthClientFactory;
class CryptAuthGCMManager;
// An implementation of CryptAuthDeviceNotifier, using instances of
// CryptAuthClient to make the BatchNotifyGroupDevices API calls to CryptAuth.
// The requests made via NotifyDevices() are queued and processed sequentially.
// This implementation handles timeouts internally, so a callback passed to
// NotifyDevices() is always guaranteed to be invoked.
class CryptAuthDeviceNotifierImpl : public CryptAuthDeviceNotifier {
public:
class Factory {
public:
static Factory* Get();
static void SetFactoryForTesting(Factory* test_factory);
virtual ~Factory();
virtual std::unique_ptr<CryptAuthDeviceNotifier> BuildInstance(
ClientAppMetadataProvider* client_app_metadata_provider,
CryptAuthClientFactory* client_factory,
CryptAuthGCMManager* gcm_manager,
std::unique_ptr<base::OneShotTimer> timer =
std::make_unique<base::OneShotTimer>());
private:
static Factory* test_factory_;
};
~CryptAuthDeviceNotifierImpl() override;
private:
enum class State {
kIdle,
kWaitingForClientAppMetadata,
kWaitingForBatchNotifyGroupDevicesResponse
};
friend std::ostream& operator<<(std::ostream& stream, const State& state);
static base::Optional<base::TimeDelta> GetTimeoutForState(State state);
struct Request {
Request(const base::flat_set<std::string>& device_ids,
cryptauthv2::TargetService target_service,
CryptAuthFeatureType feature_type,
base::OnceClosure success_callback,
base::OnceCallback<void(NetworkRequestError)> error_callback);
Request(Request&& request);
~Request();
base::flat_set<std::string> device_ids;
cryptauthv2::TargetService target_service;
CryptAuthFeatureType feature_type;
base::OnceClosure success_callback;
base::OnceCallback<void(NetworkRequestError)> error_callback;
};
CryptAuthDeviceNotifierImpl(
ClientAppMetadataProvider* client_app_metadata_provider,
CryptAuthClientFactory* client_factory,
CryptAuthGCMManager* gcm_manager,
std::unique_ptr<base::OneShotTimer> timer);
// CryptAuthDeviceNotifier:
void NotifyDevices(
const base::flat_set<std::string>& device_ids,
cryptauthv2::TargetService target_service,
CryptAuthFeatureType feature_type,
base::OnceClosure success_callback,
base::OnceCallback<void(NetworkRequestError)> error_callback) override;
void SetState(State state);
void OnTimeout();
void ProcessRequestQueue();
void OnClientAppMetadataFetched(
const base::Optional<cryptauthv2::ClientAppMetadata>&
client_app_metadata);
void OnBatchNotifyGroupDevicesSuccess(
const cryptauthv2::BatchNotifyGroupDevicesResponse& response);
void OnBatchNotifyGroupDevicesFailure(NetworkRequestError error);
void FinishAttempt(base::Optional<NetworkRequestError> error);
State state_ = State::kIdle;
base::TimeTicks last_state_change_timestamp_;
base::Optional<cryptauthv2::ClientAppMetadata> client_app_metadata_;
base::queue<Request> pending_requests_;
ClientAppMetadataProvider* client_app_metadata_provider_ = nullptr;
CryptAuthClientFactory* client_factory_ = nullptr;
CryptAuthGCMManager* gcm_manager_ = nullptr;
std::unique_ptr<CryptAuthClient> cryptauth_client_;
std::unique_ptr<base::OneShotTimer> timer_;
base::WeakPtrFactory<CryptAuthDeviceNotifierImpl> weak_ptr_factory_{this};
DISALLOW_COPY_AND_ASSIGN(CryptAuthDeviceNotifierImpl);
};
} // namespace device_sync
} // namespace chromeos
#endif // CHROMEOS_SERVICES_DEVICE_SYNC_CRYPTAUTH_DEVICE_NOTIFIER_IMPL_H_
// Copyright 2019 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 "chromeos/services/device_sync/cryptauth_device_notifier_impl.h"
#include <memory>
#include <string>
#include <utility>
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/containers/queue.h"
#include "base/macros.h"
#include "base/no_destructor.h"
#include "base/optional.h"
#include "base/timer/mock_timer.h"
#include "chromeos/services/device_sync/cryptauth_client.h"
#include "chromeos/services/device_sync/cryptauth_device_notifier.h"
#include "chromeos/services/device_sync/cryptauth_feature_type.h"
#include "chromeos/services/device_sync/cryptauth_key_bundle.h"
#include "chromeos/services/device_sync/fake_cryptauth_gcm_manager.h"
#include "chromeos/services/device_sync/mock_cryptauth_client.h"
#include "chromeos/services/device_sync/network_request_error.h"
#include "chromeos/services/device_sync/proto/cryptauth_client_app_metadata.pb.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
#include "chromeos/services/device_sync/proto/cryptauth_devicesync.pb.h"
#include "chromeos/services/device_sync/proto/cryptauth_v2_test_util.h"
#include "chromeos/services/device_sync/public/cpp/fake_client_app_metadata_provider.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace device_sync {
namespace {
const char kAccessTokenUsed[] = "access token used by CryptAuthClient";
const cryptauthv2::ClientMetadata& GetClientMetadata() {
static const base::NoDestructor<cryptauthv2::ClientMetadata> client_metadata(
[] {
cryptauthv2::ClientMetadata client_metadata;
client_metadata.set_invocation_reason(
cryptauthv2::ClientMetadata::INVOCATION_REASON_UNSPECIFIED);
return client_metadata;
}());
return *client_metadata;
}
const cryptauthv2::RequestContext& GetRequestContext() {
static const base::NoDestructor<cryptauthv2::RequestContext> request_context(
cryptauthv2::BuildRequestContext(
CryptAuthKeyBundle::KeyBundleNameEnumToString(
CryptAuthKeyBundle::Name::kDeviceSyncBetterTogether),
GetClientMetadata(),
cryptauthv2::GetClientAppMetadataForTest().instance_id(),
cryptauthv2::GetClientAppMetadataForTest().instance_id_token()));
return *request_context;
}
// Request with |device_ids|, Enrollment as the target service, and
// BetterTogether host (enabled) as the feature type.
cryptauthv2::BatchNotifyGroupDevicesRequest
NotifyEnrollmentBetterTogetherHostEnabledRequest(
const base::flat_set<std::string>& device_ids) {
cryptauthv2::BatchNotifyGroupDevicesRequest request;
request.mutable_context()->CopyFrom(GetRequestContext());
*request.mutable_notify_device_ids() = {device_ids.begin(), device_ids.end()};
request.set_target_service(cryptauthv2::TargetService::ENROLLMENT);
request.set_feature_type(CryptAuthFeatureTypeToString(
CryptAuthFeatureType::kBetterTogetherHostEnabled));
return request;
}
// Request with |device_ids|, DeviceSync as the target service, and
// MagicTether client (supported) as the feature type.
cryptauthv2::BatchNotifyGroupDevicesRequest
NotifyDeviceSyncMagicTetherSupportedRequest(
const base::flat_set<std::string>& device_ids) {
cryptauthv2::BatchNotifyGroupDevicesRequest request;
request.mutable_context()->CopyFrom(GetRequestContext());
*request.mutable_notify_device_ids() = {device_ids.begin(), device_ids.end()};
request.set_target_service(cryptauthv2::TargetService::DEVICE_SYNC);
request.set_feature_type(CryptAuthFeatureTypeToString(
CryptAuthFeatureType::kMagicTetherClientSupported));
return request;
}
} // namespace
class DeviceSyncCryptAuthDeviceNotifierImplTest
: public testing::Test,
public MockCryptAuthClientFactory::Observer {
protected:
enum class RequestAction { kSucceed, kFail, kTimeout };
DeviceSyncCryptAuthDeviceNotifierImplTest()
: mock_client_factory_(
MockCryptAuthClientFactory::MockType::MAKE_NICE_MOCKS),
fake_gcm_manager_(cryptauthv2::kTestGcmRegistrationId) {
mock_client_factory_.AddObserver(this);
}
~DeviceSyncCryptAuthDeviceNotifierImplTest() override {
mock_client_factory_.RemoveObserver(this);
}
// testing::Test:
void SetUp() override {
auto mock_timer = std::make_unique<base::MockOneShotTimer>();
mock_timer_ = mock_timer.get();
device_notifier_ =
CryptAuthDeviceNotifierImpl::Factory::Get()->BuildInstance(
&fake_client_app_metadata_provider_, &mock_client_factory_,
&fake_gcm_manager_, std::move(mock_timer));
}
// MockCryptAuthClientFactory::Observer:
void OnCryptAuthClientCreated(MockCryptAuthClient* client) override {
ON_CALL(*client,
BatchNotifyGroupDevices(testing::_, testing::_, testing::_))
.WillByDefault(Invoke(this, &DeviceSyncCryptAuthDeviceNotifierImplTest::
OnBatchNotifyGroupDevices));
ON_CALL(*client, GetAccessTokenUsed())
.WillByDefault(testing::Return(kAccessTokenUsed));
}
void NotifyDevices(const base::flat_set<std::string>& device_ids,
cryptauthv2::TargetService target_service,
CryptAuthFeatureType feature_type) {
device_notifier_->NotifyDevices(
device_ids, target_service, feature_type,
base::BindOnce(
&DeviceSyncCryptAuthDeviceNotifierImplTest::OnNotifyDevicesSuccess,
base::Unretained(this)),
base::BindOnce(
&DeviceSyncCryptAuthDeviceNotifierImplTest::OnNotifyDevicesFailure,
base::Unretained(this)));
}
void HandleClientAppMetadataRequest(RequestAction request_action) {
ASSERT_FALSE(
fake_client_app_metadata_provider_.metadata_requests().empty());
EXPECT_EQ(cryptauthv2::kTestGcmRegistrationId,
fake_client_app_metadata_provider_.metadata_requests()
.back()
.gcm_registration_id);
switch (request_action) {
case RequestAction::kSucceed:
std::move(fake_client_app_metadata_provider_.metadata_requests()
.back()
.callback)
.Run(cryptauthv2::GetClientAppMetadataForTest());
return;
case RequestAction::kFail:
std::move(fake_client_app_metadata_provider_.metadata_requests()
.back()
.callback)
.Run(base::nullopt /* client_app_metadata */);
return;
case RequestAction::kTimeout:
mock_timer_->Fire();
return;
}
}
void HandleNextBatchNotifyGroupDevicesRequest(
const cryptauthv2::BatchNotifyGroupDevicesRequest& expected_request,
RequestAction request_action,
base::Optional<NetworkRequestError> error = base::nullopt) {
ASSERT_FALSE(batch_notify_group_devices_requests_.empty());
cryptauthv2::BatchNotifyGroupDevicesRequest current_request =
std::move(batch_notify_group_devices_requests_.front());
batch_notify_group_devices_requests_.pop();
CryptAuthClient::BatchNotifyGroupDevicesCallback current_success_callback =
std::move(batch_notify_group_devices_success_callbacks_.front());
batch_notify_group_devices_success_callbacks_.pop();
CryptAuthClient::ErrorCallback current_failure_callback =
std::move(batch_notify_group_devices_failure_callbacks_.front());
batch_notify_group_devices_failure_callbacks_.pop();
EXPECT_EQ(expected_request.SerializeAsString(),
current_request.SerializeAsString());
switch (request_action) {
case RequestAction::kSucceed:
std::move(current_success_callback)
.Run(cryptauthv2::BatchNotifyGroupDevicesResponse());
break;
case RequestAction::kFail:
ASSERT_TRUE(error);
std::move(current_failure_callback).Run(*error);
break;
case RequestAction::kTimeout:
mock_timer_->Fire();
break;
}
}
void VerifyNumberOfClientAppMetadataFetchAttempts(size_t num_attempts) {
EXPECT_EQ(num_attempts,
fake_client_app_metadata_provider_.metadata_requests().size());
}
void VerifyResults(
const std::vector<base::Optional<NetworkRequestError>> expected_results) {
// Verify that all requests were processed.
EXPECT_TRUE(batch_notify_group_devices_requests_.empty());
EXPECT_TRUE(batch_notify_group_devices_success_callbacks_.empty());
EXPECT_TRUE(batch_notify_group_devices_failure_callbacks_.empty());
EXPECT_EQ(expected_results, results_);
}
private:
void OnBatchNotifyGroupDevices(
const cryptauthv2::BatchNotifyGroupDevicesRequest& request,
const CryptAuthClient::BatchNotifyGroupDevicesCallback& callback,
const CryptAuthClient::ErrorCallback& error_callback) {
batch_notify_group_devices_requests_.push(request);
batch_notify_group_devices_success_callbacks_.push(std::move(callback));
batch_notify_group_devices_failure_callbacks_.push(
std::move(error_callback));
}
void OnNotifyDevicesSuccess() { results_.push_back(base::nullopt); }
void OnNotifyDevicesFailure(NetworkRequestError error) {
results_.push_back(error);
}
base::queue<cryptauthv2::BatchNotifyGroupDevicesRequest>
batch_notify_group_devices_requests_;
base::queue<CryptAuthClient::BatchNotifyGroupDevicesCallback>
batch_notify_group_devices_success_callbacks_;
base::queue<CryptAuthClient::ErrorCallback>
batch_notify_group_devices_failure_callbacks_;
// base::nullopt indicates a success.
std::vector<base::Optional<NetworkRequestError>> results_;
FakeClientAppMetadataProvider fake_client_app_metadata_provider_;
MockCryptAuthClientFactory mock_client_factory_;
FakeCryptAuthGCMManager fake_gcm_manager_;
base::MockOneShotTimer* mock_timer_ = nullptr;
std::unique_ptr<CryptAuthDeviceNotifier> device_notifier_;
DISALLOW_COPY_AND_ASSIGN(DeviceSyncCryptAuthDeviceNotifierImplTest);
};
TEST_F(DeviceSyncCryptAuthDeviceNotifierImplTest, Test) {
// Queue up 6 requests before any finish. They should be processed
// sequentially.
NotifyDevices({"device_id_1"}, cryptauthv2::TargetService::ENROLLMENT,
CryptAuthFeatureType::kEasyUnlockClientEnabled);
NotifyDevices({"device_id_2"}, cryptauthv2::TargetService::DEVICE_SYNC,
CryptAuthFeatureType::kSmsConnectHostSupported);
NotifyDevices({"device_id_3"}, cryptauthv2::TargetService::ENROLLMENT,
CryptAuthFeatureType::kBetterTogetherHostEnabled);
NotifyDevices({"device_id_4", "device_id_5"},
cryptauthv2::TargetService::DEVICE_SYNC,
CryptAuthFeatureType::kMagicTetherClientSupported);
NotifyDevices({"device_id_6", "device_id_7"},
cryptauthv2::TargetService::ENROLLMENT,
CryptAuthFeatureType::kBetterTogetherHostEnabled);
NotifyDevices({"device_id_8"}, cryptauthv2::TargetService::DEVICE_SYNC,
CryptAuthFeatureType::kMagicTetherClientSupported);
// base::nullopt indicates a success.
std::vector<base::Optional<NetworkRequestError>> expected_results;
// Timeout waiting for ClientAppMetadata.
HandleClientAppMetadataRequest(RequestAction::kTimeout);
expected_results.push_back(NetworkRequestError::kUnknown);
// Fail ClientAppMetadata fetch.
HandleClientAppMetadataRequest(RequestAction::kFail);
expected_results.push_back(NetworkRequestError::kUnknown);
// Timeout waiting for BatchNotifyGroupDevices.
HandleClientAppMetadataRequest(RequestAction::kSucceed);
HandleNextBatchNotifyGroupDevicesRequest(
NotifyEnrollmentBetterTogetherHostEnabledRequest({"device_id_3"}),
RequestAction::kTimeout);
expected_results.push_back(NetworkRequestError::kUnknown);
// Fail BatchNotifyGroupDevices call with "Bad Request".
HandleNextBatchNotifyGroupDevicesRequest(
NotifyDeviceSyncMagicTetherSupportedRequest(
{"device_id_4", "device_id_5"}),
RequestAction::kFail, NetworkRequestError::kBadRequest);
expected_results.push_back(NetworkRequestError::kBadRequest);
// Succeed notifying devices.
HandleNextBatchNotifyGroupDevicesRequest(
NotifyEnrollmentBetterTogetherHostEnabledRequest(
{"device_id_6", "device_id_7"}),
RequestAction::kSucceed);
expected_results.push_back(base::nullopt);
// Succeed notifying devices.
HandleNextBatchNotifyGroupDevicesRequest(
NotifyDeviceSyncMagicTetherSupportedRequest({"device_id_8"}),
RequestAction::kSucceed);
expected_results.push_back(base::nullopt);
// There was 1 timeout, 1 failed attempt, and 1 successful attempt to retrieve
// ClientAppMetadata.
VerifyNumberOfClientAppMetadataFetchAttempts(3u);
VerifyResults(expected_results);
}
} // namespace device_sync
} // namespace chromeos
// Copyright 2019 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 <utility>
#include "chromeos/services/device_sync/fake_cryptauth_device_notifier.h"
namespace chromeos {
namespace device_sync {
FakeCryptAuthDeviceNotifier::Request::Request(
const base::flat_set<std::string>& device_ids,
cryptauthv2::TargetService target_service,
CryptAuthFeatureType feature_type,
base::OnceClosure success_callback,
base::OnceCallback<void(NetworkRequestError)> error_callback)
: device_ids(device_ids),
target_service(target_service),
feature_type(feature_type),
success_callback(std::move(success_callback)),
error_callback(std::move(error_callback)) {}
FakeCryptAuthDeviceNotifier::Request::Request(Request&& request)
: device_ids(std::move(request.device_ids)),
target_service(request.target_service),
feature_type(request.feature_type),
success_callback(std::move(request.success_callback)),
error_callback(std::move(request.error_callback)) {}
FakeCryptAuthDeviceNotifier::Request::~Request() = default;
FakeCryptAuthDeviceNotifier::FakeCryptAuthDeviceNotifier() = default;
FakeCryptAuthDeviceNotifier::~FakeCryptAuthDeviceNotifier() = default;
void FakeCryptAuthDeviceNotifier::NotifyDevices(
const base::flat_set<std::string>& device_ids,
cryptauthv2::TargetService target_service,
CryptAuthFeatureType feature_type,
base::OnceClosure success_callback,
base::OnceCallback<void(NetworkRequestError)> error_callback) {
requests_.emplace(device_ids, target_service, feature_type,
std::move(success_callback), std::move(error_callback));
}
FakeCryptAuthDeviceNotifierFactory::FakeCryptAuthDeviceNotifierFactory() =
default;
FakeCryptAuthDeviceNotifierFactory::~FakeCryptAuthDeviceNotifierFactory() =
default;
std::unique_ptr<CryptAuthDeviceNotifier>
FakeCryptAuthDeviceNotifierFactory::BuildInstance(
ClientAppMetadataProvider* client_app_metadata_provider,
CryptAuthClientFactory* client_factory,
CryptAuthGCMManager* gcm_manager,
std::unique_ptr<base::OneShotTimer> timer) {
last_client_app_metadata_provider_ = client_app_metadata_provider;
last_client_factory_ = client_factory;
last_gcm_manager_ = gcm_manager;
auto instance = std::make_unique<FakeCryptAuthDeviceNotifier>();
instances_.push_back(instance.get());
return instance;
}
} // namespace device_sync
} // namespace chromeos
// Copyright 2019 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 CHROMEOS_SERVICES_DEVICE_SYNC_FAKE_CRYPTAUTH_DEVICE_NOTIFIER_H_
#define CHROMEOS_SERVICES_DEVICE_SYNC_FAKE_CRYPTAUTH_DEVICE_NOTIFIER_H_
#include <memory>
#include <string>
#include "base/callback.h"
#include "base/containers/flat_set.h"
#include "base/containers/queue.h"
#include "base/macros.h"
#include "base/timer/timer.h"
#include "chromeos/services/device_sync/cryptauth_device_notifier.h"
#include "chromeos/services/device_sync/cryptauth_device_notifier_impl.h"
#include "chromeos/services/device_sync/network_request_error.h"
#include "chromeos/services/device_sync/proto/cryptauth_common.pb.h"
namespace chromeos {
namespace device_sync {
class ClientAppMetadataProvider;
class CryptAuthClientFactory;
class CryptAuthGCMManager;
class FakeCryptAuthDeviceNotifier : public CryptAuthDeviceNotifier {
public:
struct Request {
Request(const base::flat_set<std::string>& device_ids,
cryptauthv2::TargetService target_service,
CryptAuthFeatureType feature_type,
base::OnceClosure success_callback,
base::OnceCallback<void(NetworkRequestError)> error_callback);
Request(Request&& request);
~Request();
base::flat_set<std::string> device_ids;
cryptauthv2::TargetService target_service;
CryptAuthFeatureType feature_type;
base::OnceClosure success_callback;
base::OnceCallback<void(NetworkRequestError)> error_callback;
};
FakeCryptAuthDeviceNotifier();
~FakeCryptAuthDeviceNotifier() override;
base::queue<Request>& requests() { return requests_; }
private:
// CryptAuthDeviceNotifier:
void NotifyDevices(
const base::flat_set<std::string>& device_ids,
cryptauthv2::TargetService target_service,
CryptAuthFeatureType feature_type,
base::OnceClosure success_callback,
base::OnceCallback<void(NetworkRequestError)> error_callback) override;
base::queue<Request> requests_;
DISALLOW_COPY_AND_ASSIGN(FakeCryptAuthDeviceNotifier);
};
class FakeCryptAuthDeviceNotifierFactory
: public CryptAuthDeviceNotifierImpl::Factory {
public:
FakeCryptAuthDeviceNotifierFactory();
~FakeCryptAuthDeviceNotifierFactory() override;
const std::vector<FakeCryptAuthDeviceNotifier*>& instances() const {
return instances_;
}
const ClientAppMetadataProvider* last_client_app_metadata_provider() const {
return last_client_app_metadata_provider_;
}
const CryptAuthClientFactory* last_client_factory() const {
return last_client_factory_;
}
const CryptAuthGCMManager* last_gcm_manager() const {
return last_gcm_manager_;
}
private:
// CryptAuthDeviceNotifierImpl::Factory:
std::unique_ptr<CryptAuthDeviceNotifier> BuildInstance(
ClientAppMetadataProvider* client_app_metadata_provider,
CryptAuthClientFactory* client_factory,
CryptAuthGCMManager* gcm_manager,
std::unique_ptr<base::OneShotTimer> timer = nullptr) override;
std::vector<FakeCryptAuthDeviceNotifier*> instances_;
ClientAppMetadataProvider* last_client_app_metadata_provider_ = nullptr;
CryptAuthClientFactory* last_client_factory_ = nullptr;
CryptAuthGCMManager* last_gcm_manager_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(FakeCryptAuthDeviceNotifierFactory);
};
} // namespace device_sync
} // namespace chromeos
#endif // CHROMEOS_SERVICES_DEVICE_SYNC_FAKE_CRYPTAUTH_DEVICE_NOTIFIER_H_
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