Commit a69a2eb1 authored by Kyle Horimoto's avatar Kyle Horimoto Committed by Commit Bot

[CrOS MultiDevice] Add HostVerifierOperation.

This class represents an operation for completing the verification step
for the current host device. A HostVerifierOperation instance is meant
to be used for a single verification attempt; if verification needs to
be retried, a new instance should be created for the next attempt.

This class completes the following steps to verify a device:
(1) Call FindEligibleDevices(). This step sends a message to the host
    device, which in turn enables background advertising.
(2) Creates a connection to the device using the BLE listener role.
(3) Sends an EnableBetterTogetherRequest message to the host device.
(4) Waits for an EnableBetterTogetherResponse messages to be returned by
    the host device.

Bug: 824568
Change-Id: I520ff9388334467831342af4d0e270cb4e351eeb
Reviewed-on: https://chromium-review.googlesource.com/1119466Reviewed-by: default avatarRyan Hansberry <hansberry@chromium.org>
Commit-Queue: Kyle Horimoto <khorimoto@chromium.org>
Cr-Commit-Position: refs/heads/master@{#572339}
parent 005dc127
......@@ -29,6 +29,10 @@ static_library("multidevice_setup") {
"host_verifier.h",
"host_verifier_impl.cc",
"host_verifier_impl.h",
"host_verifier_operation.cc",
"host_verifier_operation.h",
"host_verifier_operation_impl.cc",
"host_verifier_operation_impl.h",
"multidevice_setup_base.cc",
"multidevice_setup_base.h",
"multidevice_setup_impl.cc",
......@@ -47,6 +51,7 @@ static_library("multidevice_setup") {
"//chromeos/components/proximity_auth/logging",
"//chromeos/services/device_sync/public/cpp",
"//chromeos/services/device_sync/public/mojom",
"//chromeos/services/multidevice_setup/proto",
"//chromeos/services/multidevice_setup/public/mojom",
"//chromeos/services/secure_channel/public/cpp/client",
"//chromeos/services/secure_channel/public/mojom",
......@@ -77,6 +82,8 @@ static_library("test_support") {
"fake_host_status_provider.h",
"fake_host_verifier.cc",
"fake_host_verifier.h",
"fake_host_verifier_operation.cc",
"fake_host_verifier_operation.h",
"fake_setup_flow_completion_recorder.cc",
"fake_setup_flow_completion_recorder.h",
]
......@@ -100,6 +107,7 @@ source_set("unit_tests") {
"host_backend_delegate_impl_unittest.cc",
"host_status_provider_impl_unittest.cc",
"host_verifier_impl_unittest.cc",
"host_verifier_operation_impl_unittest.cc",
"multidevice_setup_impl_unittest.cc",
"multidevice_setup_service_unittest.cc",
"setup_flow_completion_recorder_impl_unittest.cc",
......
// 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 "chromeos/services/multidevice_setup/fake_host_verifier_operation.h"
namespace chromeos {
namespace multidevice_setup {
FakeHostVerifierOperation::FakeHostVerifierOperation(Delegate* delegate)
: HostVerifierOperation(delegate) {}
FakeHostVerifierOperation::~FakeHostVerifierOperation() = default;
void FakeHostVerifierOperation::PerformCancelOperation() {}
FakeHostVerifierOperationDelegate::FakeHostVerifierOperationDelegate() =
default;
FakeHostVerifierOperationDelegate::~FakeHostVerifierOperationDelegate() =
default;
void FakeHostVerifierOperationDelegate::OnOperationFinished(
HostVerifierOperation::Result result) {
DCHECK(!result_);
result_ = result;
}
} // namespace multidevice_setup
} // namespace chromeos
// 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 CHROMEOS_SERVICES_MULTIDEVICE_SETUP_FAKE_HOST_VERIFIER_OPERATION_H_
#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_FAKE_HOST_VERIFIER_OPERATION_H_
#include "base/macros.h"
#include "base/optional.h"
#include "chromeos/services/multidevice_setup/host_verifier_operation.h"
namespace chromeos {
namespace multidevice_setup {
// Test HostVerifierOperation implementation.
class FakeHostVerifierOperation : public HostVerifierOperation {
public:
FakeHostVerifierOperation(Delegate* delegate);
~FakeHostVerifierOperation() override;
using HostVerifierOperation::NotifyOperationFinished;
private:
// HostVerifierOperation:
void PerformCancelOperation() override;
DISALLOW_COPY_AND_ASSIGN(FakeHostVerifierOperation);
};
// Test HostVerifierOperation::Delegate implementation.
class FakeHostVerifierOperationDelegate
: public HostVerifierOperation::Delegate {
public:
FakeHostVerifierOperationDelegate();
~FakeHostVerifierOperationDelegate() override;
const base::Optional<HostVerifierOperation::Result>& result() const {
return result_;
}
private:
// HostVerifierOperation::Delegate:
void OnOperationFinished(HostVerifierOperation::Result result) override;
base::Optional<HostVerifierOperation::Result> result_;
DISALLOW_COPY_AND_ASSIGN(FakeHostVerifierOperationDelegate);
};
} // namespace multidevice_setup
} // namespace chromeos
#endif // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_FAKE_HOST_VERIFIER_OPERATION_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 "chromeos/services/multidevice_setup/host_verifier_operation.h"
#include "chromeos/components/proximity_auth/logging/logging.h"
namespace chromeos {
namespace multidevice_setup {
HostVerifierOperation::HostVerifierOperation(Delegate* delegate)
: delegate_(delegate) {}
HostVerifierOperation::~HostVerifierOperation() = default;
void HostVerifierOperation::CancelOperation() {
if (result_) {
PA_LOG(ERROR) << "HostVerifierOperation::CancelOperation(): Tried to "
<< "cancel operation, but it was already finished. Result: "
<< *result_;
NOTREACHED();
}
PerformCancelOperation();
NotifyOperationFinished(Result::kCanceled);
}
void HostVerifierOperation::NotifyOperationFinished(Result result) {
if (result_) {
PA_LOG(ERROR) << "HostVerifierOperation::NotifyOperationFinished(): Tried "
<< "to finish operation, but it was already finished. "
<< "Result: " << *result_;
}
result_ = result;
delegate_->OnOperationFinished(*result_);
}
std::ostream& operator<<(std::ostream& stream,
const HostVerifierOperation::Result& result) {
switch (result) {
case HostVerifierOperation::Result::kTimeoutFindingEligibleDevices:
stream << "[timeout calling FindEligibleDevices()]";
break;
case HostVerifierOperation::Result::kErrorCallingFindEligibleDevices:
stream << "[error calling FindEligibleDevices()]";
break;
case HostVerifierOperation::Result::kDeviceToVerifyIsNotEligible:
stream << "[device to verify was not included in FindEligibleDevices() "
<< "response];";
break;
case HostVerifierOperation::Result::kTimeoutFindingConnection:
stream << "[timeout finding connection]";
break;
case HostVerifierOperation::Result::kConnectionAttemptFailed:
stream << "[connection attempt failed]";
break;
case HostVerifierOperation::Result::kConnectionDisconnectedUnexpectedly:
stream << "[connection disconnected unexpectedly]";
break;
case HostVerifierOperation::Result::kTimeoutReceivingResponse:
stream << "[timeout receiving EnableBetterTogetherResponse]";
break;
case HostVerifierOperation::Result::kReceivedInvalidResponse:
stream << "[received invalid EnableBetterTogetherResponse message]";
break;
case HostVerifierOperation::Result::kReceivedErrorResponse:
stream << "[received EnableBetterTogetherResponse with error]";
break;
case HostVerifierOperation::Result::kCanceled:
stream << "[request canceled]";
break;
case HostVerifierOperation::Result::kSuccess:
stream << "[success]";
break;
}
return stream;
}
} // namespace multidevice_setup
} // namespace chromeos
// 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 CHROMEOS_SERVICES_MULTIDEVICE_SETUP_HOST_VERIFIER_OPERATION_H_
#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_HOST_VERIFIER_OPERATION_H_
#include <ostream>
#include "base/macros.h"
#include "base/optional.h"
namespace chromeos {
namespace multidevice_setup {
// Operation for completing the verification step for the current host device.
// A HostVerifierOperation instance is meant to be used for a single
// verification attempt; if verification needs to be retried, a new instance
// should be created for the next attempt.
class HostVerifierOperation {
public:
enum class Result {
kTimeoutFindingEligibleDevices,
kErrorCallingFindEligibleDevices,
kDeviceToVerifyIsNotEligible,
kTimeoutFindingConnection,
kConnectionAttemptFailed,
kConnectionDisconnectedUnexpectedly,
kTimeoutReceivingResponse,
kReceivedInvalidResponse,
kReceivedErrorResponse,
kCanceled,
kSuccess
};
class Delegate {
public:
virtual ~Delegate() = default;
virtual void OnOperationFinished(Result result) = 0;
};
virtual ~HostVerifierOperation();
// Cancels the operation, triggering a delegate callback with the kCanceled
// result.
//
// It is invalid to call this function after the operation has already
// completed.
void CancelOperation();
// Returns the result of the operation. If the operation has not yet finished,
// null is returned.
const base::Optional<Result>& result() const { return result_; }
protected:
HostVerifierOperation(Delegate* delegate);
// Derived types should use this function to cancel the operation, but they
// should not call NotifyOperationFinished() during cancellation.
virtual void PerformCancelOperation() = 0;
void NotifyOperationFinished(Result result);
private:
Delegate* delegate_;
base::Optional<Result> result_;
DISALLOW_COPY_AND_ASSIGN(HostVerifierOperation);
};
std::ostream& operator<<(std::ostream& stream,
const HostVerifierOperation::Result& result);
} // namespace multidevice_setup
} // namespace chromeos
#endif // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_HOST_VERIFIER_OPERATION_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 "chromeos/services/multidevice_setup/host_verifier_operation_impl.h"
#include <sstream>
#include "base/bind.h"
#include "base/memory/ptr_util.h"
#include "base/no_destructor.h"
#include "base/stl_util.h"
#include "base/time/time.h"
#include "chromeos/components/proximity_auth/logging/logging.h"
#include "chromeos/services/device_sync/public/cpp/device_sync_client.h"
#include "chromeos/services/secure_channel/public/cpp/client/secure_channel_client.h"
namespace chromeos {
namespace multidevice_setup {
namespace {
const char kFeature[] = "better_together_setup";
const int kNumMinutesForTimeout = 1;
const char kEligibleDeviceIdsLogString[] = "Eligible device IDs";
const char kIneligibleDeviceIdsLogString[] = "Ineligible device IDs";
void LogDeviceIds(const cryptauth::RemoteDeviceRefList& device_list,
const std::string& device_type_name,
std::stringstream* ss) {
*ss << device_type_name << ": [";
if (!device_list.empty()) {
for (const auto& device : device_list)
*ss << "\"" << device.GetTruncatedDeviceIdForLogs() << "\", ";
ss->seekp(-2, ss->cur); // Remove last ", " from the stream.
}
*ss << "]";
}
std::string CreateLogString(
const cryptauth::RemoteDeviceRefList& eligible_devices,
const cryptauth::RemoteDeviceRefList& ineligible_devices) {
std::stringstream ss;
LogDeviceIds(eligible_devices, kEligibleDeviceIdsLogString, &ss);
ss << ", ";
LogDeviceIds(ineligible_devices, kIneligibleDeviceIdsLogString, &ss);
return ss.str();
}
base::Optional<EnableBetterTogetherResponse> DeserializePossibleResponse(
const std::string& payload) {
BetterTogetherSetupMessageWrapper wrapper;
// If |payload| does not correspond to a BetterTogetherSetupMessageWrapper,
// return null.
if (!wrapper.ParseFromString(payload))
return base::nullopt;
// If |wrapper|'s type indicates that it does not contain a
// EnableBetterTogetherResponse, return null.
if (!wrapper.has_type() ||
wrapper.type() != MessageType::ENABLE_BETTER_TOGETHER_RESPONSE) {
return base::nullopt;
}
EnableBetterTogetherResponse response;
// If |wrapper|'s payload does not represent an EnableBetterTogetherResponse,
// return null.
if (!wrapper.has_payload() || !response.ParseFromString(wrapper.payload()))
return base::nullopt;
return response;
}
} // namespace
// static
HostVerifierOperationImpl::Factory*
HostVerifierOperationImpl::Factory::test_factory_ = nullptr;
// static
HostVerifierOperationImpl::Factory* HostVerifierOperationImpl::Factory::Get() {
if (test_factory_)
return test_factory_;
static base::NoDestructor<Factory> factory;
return factory.get();
}
// static
void HostVerifierOperationImpl::Factory::SetFactoryForTesting(
Factory* test_factory) {
test_factory_ = test_factory;
}
HostVerifierOperationImpl::Factory::~Factory() = default;
std::unique_ptr<HostVerifierOperation>
HostVerifierOperationImpl::Factory::BuildInstance(
HostVerifierOperation::Delegate* delegate,
cryptauth::RemoteDeviceRef device_to_connect,
cryptauth::RemoteDeviceRef local_device,
device_sync::DeviceSyncClient* device_sync_client,
secure_channel::SecureChannelClient* secure_channel_client,
std::unique_ptr<base::OneShotTimer> timer) {
return base::WrapUnique(new HostVerifierOperationImpl(
delegate, device_to_connect, local_device, device_sync_client,
secure_channel_client, std::move(timer)));
}
// static
BetterTogetherSetupMessageWrapper
HostVerifierOperationImpl::CreateWrappedEnableBetterTogetherRequest() {
BetterTogetherSetupMessageWrapper wrapper;
wrapper.set_type(MessageType::ENABLE_BETTER_TOGETHER_REQUEST);
EnableBetterTogetherRequest request;
wrapper.set_payload(request.SerializeAsString());
return wrapper;
}
HostVerifierOperationImpl::HostVerifierOperationImpl(
HostVerifierOperation::Delegate* delegate,
cryptauth::RemoteDeviceRef device_to_connect,
cryptauth::RemoteDeviceRef local_device,
device_sync::DeviceSyncClient* device_sync_client,
secure_channel::SecureChannelClient* secure_channel_client,
std::unique_ptr<base::OneShotTimer> timer)
: HostVerifierOperation(delegate),
device_to_connect_(device_to_connect),
local_device_(local_device),
device_sync_client_(device_sync_client),
secure_channel_client_(secure_channel_client),
timer_(std::move(timer)),
weak_ptr_factory_(this) {
timer_->Start(FROM_HERE, base::TimeDelta::FromMinutes(kNumMinutesForTimeout),
base::Bind(&HostVerifierOperationImpl::OnTimeout,
base::Unretained(this)));
device_sync_client_->FindEligibleDevices(
cryptauth::SoftwareFeature::BETTER_TOGETHER_HOST,
base::BindOnce(&HostVerifierOperationImpl::OnFindEligibleDevicesResponse,
weak_ptr_factory_.GetWeakPtr()));
}
HostVerifierOperationImpl::~HostVerifierOperationImpl() = default;
void HostVerifierOperationImpl::PerformCancelOperation() {
FinishOperation(status_, Result::kCanceled);
}
void HostVerifierOperationImpl::OnConnectionAttemptFailure(
secure_channel::mojom::ConnectionAttemptFailureReason reason) {
PA_LOG(WARNING) << "HostVerifierOperationImpl::OnConnectionAttemptFailure(): "
<< "Failed to establish connection to device with ID \""
<< device_to_connect_.GetTruncatedDeviceIdForLogs() << "\". "
<< "Reason: " << reason;
FinishOperation(Status::kWaitingForConnection,
Result::kConnectionAttemptFailed);
}
void HostVerifierOperationImpl::OnConnection(
std::unique_ptr<secure_channel::ClientChannel> channel) {
client_channel_ = std::move(channel);
client_channel_->AddObserver(this);
TransitionStatus(Status::kWaitingForConnection, Status::kWaitingForResponse);
client_channel_->SendMessage(
CreateWrappedEnableBetterTogetherRequest().SerializeAsString(),
base::DoNothing() /* on_sent_callback */);
}
void HostVerifierOperationImpl::OnDisconnected() {
// Disconnections may occur after the operation is finished.
if (status_ == Status::kFinished)
return;
PA_LOG(WARNING) << "HostVerifierOperationImpl::OnDisconnected(): "
<< "Channel disconnected unexpectedly; could not complete "
<< "verification of device with ID \""
<< device_to_connect_.GetTruncatedDeviceIdForLogs() << "\". ";
FinishOperation(Status::kWaitingForResponse,
Result::kConnectionDisconnectedUnexpectedly);
}
void HostVerifierOperationImpl::OnMessageReceived(const std::string& payload) {
base::Optional<EnableBetterTogetherResponse> possible_response =
DeserializePossibleResponse(payload);
// The message could have been unrelated; continue waiting.
if (!possible_response)
return;
// If the received message is malformed, fail the operation.
if (!possible_response->has_result_code() ||
!EnableBetterTogetherResponse::ResultCode_IsValid(
possible_response->result_code())) {
FinishOperation(Status::kWaitingForResponse,
Result::kReceivedInvalidResponse);
return;
}
// If the received message includes an error, fail the operation.
if (possible_response->result_code() == EnableBetterTogetherResponse::ERROR) {
FinishOperation(Status::kWaitingForResponse,
Result::kReceivedErrorResponse);
return;
}
FinishOperation(Status::kWaitingForResponse, Result::kSuccess);
}
void HostVerifierOperationImpl::OnTimeout() {
switch (status_) {
case Status::kWaitingForFindEligibleDevicesResponse:
FinishOperation(status_, Result::kTimeoutFindingEligibleDevices);
break;
case Status::kWaitingForConnection:
FinishOperation(status_, Result::kTimeoutFindingConnection);
break;
case Status::kWaitingForResponse:
FinishOperation(status_, Result::kTimeoutReceivingResponse);
break;
case Status::kFinished:
PA_LOG(ERROR) << "HostVerifierOperationImpl::OnTimeout(): Timeout "
<< "occurred, but the operation had already finished.";
NOTREACHED();
break;
}
}
void HostVerifierOperationImpl::OnFindEligibleDevicesResponse(
const base::Optional<std::string>& error_code,
cryptauth::RemoteDeviceRefList eligible_devices,
cryptauth::RemoteDeviceRefList ineligible_devices) {
// A response may be received after the operation is finished.
if (status_ == Status::kFinished)
return;
if (error_code) {
PA_LOG(WARNING) << "HostVerifierOperationImpl::"
<< "OnFindEligibleDevicesResponse(): Failed to complete "
<< "FindEligibleDevices() call. Error code: "
<< *error_code;
FinishOperation(Status::kWaitingForFindEligibleDevicesResponse,
Result::kErrorCallingFindEligibleDevices);
return;
}
PA_LOG(INFO) << "HostVerifierOperationImpl::OnFindEligibleDevicesResponse(): "
<< "Received FindEligibleDevices() response. "
<< CreateLogString(eligible_devices, ineligible_devices);
if (!base::ContainsValue(eligible_devices, device_to_connect_)) {
PA_LOG(WARNING) << "HostVerifierOperationImpl::"
<< "OnFindEligibleDevicesResponse(): FindEligibleDevices() "
<< "response does not include the device to connect. ID: "
<< device_to_connect_.GetTruncatedDeviceIdForLogs();
FinishOperation(Status::kWaitingForFindEligibleDevicesResponse,
Result::kDeviceToVerifyIsNotEligible);
return;
}
TransitionStatus(Status::kWaitingForFindEligibleDevicesResponse,
Status::kWaitingForConnection);
connection_attempt_ = secure_channel_client_->ListenForConnectionFromDevice(
device_to_connect_, local_device_, kFeature,
secure_channel::ConnectionPriority::kHigh);
connection_attempt_->SetDelegate(this);
}
void HostVerifierOperationImpl::FinishOperation(Status expected_current_status,
Result result) {
TransitionStatus(expected_current_status, Status::kFinished);
if (client_channel_) {
client_channel_->RemoveObserver(this);
client_channel_.reset();
}
connection_attempt_.reset();
timer_->Stop();
if (result == Result::kCanceled)
return;
NotifyOperationFinished(result);
}
void HostVerifierOperationImpl::TransitionStatus(Status expected_current_status,
Status new_status) {
if (status_ != expected_current_status) {
PA_LOG(ERROR) << "HostVerifierOperationImpl::VerifyCurrentStatus(): "
<< "Current status is unexpected. Current: " << status_
<< ", Expected: " << expected_current_status
<< ", Attempted new status: " << new_status;
NOTREACHED();
}
PA_LOG(INFO) << "HostVerifierOperationImpl::TransitionStatus(): "
<< "Transitioning from " << status_ << " to " << new_status
<< ".";
status_ = new_status;
}
std::ostream& operator<<(std::ostream& stream,
const HostVerifierOperationImpl::Status& status) {
switch (status) {
case HostVerifierOperationImpl::Status::
kWaitingForFindEligibleDevicesResponse:
stream << "[waiting for FindEligibleDevices() response]";
break;
case HostVerifierOperationImpl::Status::kWaitingForConnection:
stream << "[waiting for connection]";
break;
case HostVerifierOperationImpl::Status::kWaitingForResponse:
stream << "[waiting for response]";
break;
case HostVerifierOperationImpl::Status::kFinished:
stream << "[finished]";
break;
}
return stream;
}
} // namespace multidevice_setup
} // namespace chromeos
// 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 CHROMEOS_SERVICES_MULTIDEVICE_SETUP_HOST_VERIFIER_OPERATION_IMPL_H_
#define CHROMEOS_SERVICES_MULTIDEVICE_SETUP_HOST_VERIFIER_OPERATION_IMPL_H_
#include <memory>
#include <ostream>
#include <string>
#include "base/macros.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "base/timer/timer.h"
#include "chromeos/services/multidevice_setup/host_verifier_operation.h"
#include "chromeos/services/multidevice_setup/proto/multidevice_setup.pb.h"
#include "chromeos/services/secure_channel/public/cpp/client/client_channel.h"
#include "chromeos/services/secure_channel/public/cpp/client/connection_attempt.h"
#include "chromeos/services/secure_channel/public/mojom/secure_channel.mojom.h"
#include "components/cryptauth/remote_device_ref.h"
namespace chromeos {
namespace device_sync {
class DeviceSyncClient;
} // namespace device_sync
namespace secure_channel {
class SecureChannelClient;
} // namespace secure_channel
namespace multidevice_setup {
// Concrete HostVerifierOperation implementation. To verify the host, this class
// performs the following steps:
// (1) Call FindEligibleDevices(). This step sends a message to the host device,
// which in turn enables background advertising.
// (2) Creates a connection to the device using the BLE listener role.
// (3) Sends an EnableBetterTogetherRequest message to the host device.
// (4) Waits for an EnableBetterTogetherResponse messages to be returned by the
// host device.
class HostVerifierOperationImpl
: public HostVerifierOperation,
public secure_channel::ConnectionAttempt::Delegate,
public secure_channel::ClientChannel::Observer {
public:
class Factory {
public:
static Factory* Get();
static void SetFactoryForTesting(Factory* test_factory);
virtual ~Factory();
virtual std::unique_ptr<HostVerifierOperation> BuildInstance(
HostVerifierOperation::Delegate* delegate,
cryptauth::RemoteDeviceRef device_to_connect,
cryptauth::RemoteDeviceRef local_device,
device_sync::DeviceSyncClient* device_sync_client,
secure_channel::SecureChannelClient* secure_channel_client,
std::unique_ptr<base::OneShotTimer> timer =
std::make_unique<base::OneShotTimer>());
private:
static Factory* test_factory_;
};
~HostVerifierOperationImpl() override;
private:
friend class MultiDeviceSetupHostVerifierOperationImplTest;
enum class Status {
kWaitingForFindEligibleDevicesResponse,
kWaitingForConnection,
kWaitingForResponse,
kFinished
};
friend std::ostream& operator<<(std::ostream& stream, const Status& status);
HostVerifierOperationImpl(
HostVerifierOperation::Delegate* delegate,
cryptauth::RemoteDeviceRef device_to_connect,
cryptauth::RemoteDeviceRef local_device,
device_sync::DeviceSyncClient* device_sync_client,
secure_channel::SecureChannelClient* secure_channel_client,
std::unique_ptr<base::OneShotTimer> timer);
static BetterTogetherSetupMessageWrapper
CreateWrappedEnableBetterTogetherRequest();
// HostVerifierOperation:
void PerformCancelOperation() override;
// secure_channel::ConnectionAttempt::Delegate:
void OnConnectionAttemptFailure(
secure_channel::mojom::ConnectionAttemptFailureReason reason) override;
void OnConnection(
std::unique_ptr<secure_channel::ClientChannel> channel) override;
// secure_channel::ClientChannel::Observer:
void OnDisconnected() override;
void OnMessageReceived(const std::string& payload) override;
void OnTimeout();
void OnFindEligibleDevicesResponse(
const base::Optional<std::string>& error_code,
cryptauth::RemoteDeviceRefList eligible_devices,
cryptauth::RemoteDeviceRefList ineligible_devices);
void FinishOperation(Status expected_current_status, Result result);
void TransitionStatus(Status expected_current_status, Status new_status);
cryptauth::RemoteDeviceRef device_to_connect_;
cryptauth::RemoteDeviceRef local_device_;
device_sync::DeviceSyncClient* device_sync_client_;
secure_channel::SecureChannelClient* secure_channel_client_;
std::unique_ptr<base::OneShotTimer> timer_;
Status status_ = Status::kWaitingForFindEligibleDevicesResponse;
std::unique_ptr<secure_channel::ConnectionAttempt> connection_attempt_;
std::unique_ptr<secure_channel::ClientChannel> client_channel_;
base::WeakPtrFactory<HostVerifierOperationImpl> weak_ptr_factory_;
DISALLOW_COPY_AND_ASSIGN(HostVerifierOperationImpl);
};
std::ostream& operator<<(std::ostream& stream,
const HostVerifierOperationImpl::Status& status);
} // namespace multidevice_setup
} // namespace chromeos
#endif // CHROMEOS_SERVICES_MULTIDEVICE_SETUP_HOST_VERIFIER_OPERATION_IMPL_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 "chromeos/services/multidevice_setup/host_verifier_operation_impl.h"
#include <memory>
#include "base/macros.h"
#include "base/timer/mock_timer.h"
#include "chromeos/services/device_sync/public/cpp/fake_device_sync_client.h"
#include "chromeos/services/multidevice_setup/fake_host_verifier_operation.h"
#include "chromeos/services/secure_channel/public/cpp/client/fake_client_channel.h"
#include "chromeos/services/secure_channel/public/cpp/client/fake_connection_attempt.h"
#include "chromeos/services/secure_channel/public/cpp/client/fake_secure_channel_client.h"
#include "components/cryptauth/remote_device_test_util.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace chromeos {
namespace multidevice_setup {
namespace {
const size_t kNumTestDevices = 5;
enum class ResponseType {
kUnrelated,
kNoResultCode,
kInvalidResultCode,
kErrorResultCode,
kSuccess
};
std::string CreatePayloadForResponseType(ResponseType response_type) {
BetterTogetherSetupMessageWrapper wrapper;
wrapper.set_type(MessageType::ENABLE_BETTER_TOGETHER_RESPONSE);
switch (response_type) {
case ResponseType::kUnrelated: {
return "unrelated";
}
case ResponseType::kNoResultCode: {
EnableBetterTogetherResponse response;
wrapper.set_payload(response.SerializeAsString());
return wrapper.SerializeAsString();
}
case ResponseType::kInvalidResultCode: {
EnableBetterTogetherResponse response;
response.set_result_code(
static_cast<EnableBetterTogetherResponse_ResultCode>(1337));
wrapper.set_payload(response.SerializeAsString());
return wrapper.SerializeAsString();
}
case ResponseType::kErrorResultCode: {
EnableBetterTogetherResponse response;
response.set_result_code(EnableBetterTogetherResponse::ERROR);
wrapper.set_payload(response.SerializeAsString());
return wrapper.SerializeAsString();
}
case ResponseType::kSuccess: {
EnableBetterTogetherResponse response;
response.set_result_code(EnableBetterTogetherResponse::NORMAL);
wrapper.set_payload(response.SerializeAsString());
return wrapper.SerializeAsString();
}
}
}
} // namespace
class MultiDeviceSetupHostVerifierOperationImplTest : public testing::Test {
protected:
MultiDeviceSetupHostVerifierOperationImplTest()
: test_devices_(
cryptauth::CreateRemoteDeviceRefListForTest(kNumTestDevices)) {}
~MultiDeviceSetupHostVerifierOperationImplTest() override = default;
// testing::Test:
void SetUp() override {
fake_delegate_ = std::make_unique<FakeHostVerifierOperationDelegate>();
fake_device_sync_client_ =
std::make_unique<device_sync::FakeDeviceSyncClient>();
fake_secure_channel_client_ =
std::make_unique<secure_channel::FakeSecureChannelClient>();
auto mock_timer = std::make_unique<base::MockOneShotTimer>();
mock_timer_ = mock_timer.get();
operation_ = HostVerifierOperationImpl::Factory::Get()->BuildInstance(
fake_delegate_.get(), remote_device(), local_device(),
fake_device_sync_client_.get(), fake_secure_channel_client_.get(),
std::move(mock_timer));
// The operation should have started its timer immediately.
EXPECT_TRUE(mock_timer_->IsRunning());
}
void Timeout() { mock_timer_->Fire(); }
void CancelAndVerifyResult() {
operation_->CancelOperation();
EXPECT_EQ(HostVerifierOperation::Result::kCanceled, GetOperationResult());
}
// Note: If |error_code| is set, then |should_remote_device_be_eligible| is
// ignored. The eligible/ineligible device lists are only provided if there
// was no error.
void CompletePendingFindEligibleDevicesResponse(
const base::Optional<std::string>& error_code = base::nullopt,
bool should_remote_device_be_eligible = true) {
cryptauth::RemoteDeviceRefList eligible_devices;
cryptauth::RemoteDeviceRefList ineligible_devices;
if (!error_code) {
// Always make device 2 eligible.
eligible_devices.push_back(test_devices_[2]);
if (should_remote_device_be_eligible)
eligible_devices.push_back(remote_device());
else
ineligible_devices.push_back(remote_device());
// Always make the local device as well as devices 3 and 4 ineligible.
ineligible_devices.push_back(local_device());
ineligible_devices.push_back(test_devices_[3]);
ineligible_devices.push_back(test_devices_[4]);
if (should_remote_device_be_eligible) {
auto fake_connection_attempt =
std::make_unique<secure_channel::FakeConnectionAttempt>();
fake_connection_attempt_ = fake_connection_attempt.get();
fake_secure_channel_client_->set_next_listen_connection_attempt(
remote_device(), local_device(),
std::move(fake_connection_attempt));
}
}
EXPECT_FALSE(GetOperationResult());
fake_device_sync_client_->InvokePendingFindEligibleDevicesCallback(
error_code, eligible_devices, ineligible_devices);
if (error_code) {
EXPECT_EQ(HostVerifierOperation::Result::kErrorCallingFindEligibleDevices,
GetOperationResult());
} else if (!should_remote_device_be_eligible) {
EXPECT_EQ(HostVerifierOperation::Result::kDeviceToVerifyIsNotEligible,
GetOperationResult());
} else {
EXPECT_FALSE(GetOperationResult());
}
}
void FailToCreateConnectionAndVerifyState(
secure_channel::mojom::ConnectionAttemptFailureReason failure_reason) {
EXPECT_FALSE(GetOperationResult());
fake_connection_attempt_->NotifyConnectionAttemptFailure(failure_reason);
EXPECT_EQ(HostVerifierOperation::Result::kConnectionAttemptFailed,
GetOperationResult());
}
void CreateConnectionSuccessfully() {
EXPECT_FALSE(GetOperationResult());
auto fake_client_channel =
std::make_unique<secure_channel::FakeClientChannel>();
fake_client_channel_ = fake_client_channel.get();
fake_connection_attempt_->NotifyConnection(std::move(fake_client_channel));
EXPECT_EQ(1u, fake_client_channel_->sent_messages().size());
EXPECT_EQ(
HostVerifierOperationImpl::CreateWrappedEnableBetterTogetherRequest()
.SerializeAsString(),
fake_client_channel_->sent_messages()[0].first);
}
void ReceiveResponseAndVerifyState(ResponseType response_type) {
fake_client_channel_->NotifyMessageReceived(
CreatePayloadForResponseType(response_type));
switch (response_type) {
case ResponseType::kUnrelated:
EXPECT_FALSE(GetOperationResult());
break;
case ResponseType::kNoResultCode:
EXPECT_EQ(HostVerifierOperation::Result::kReceivedInvalidResponse,
GetOperationResult());
break;
case ResponseType::kInvalidResultCode:
EXPECT_EQ(HostVerifierOperation::Result::kReceivedInvalidResponse,
GetOperationResult());
break;
case ResponseType::kErrorResultCode:
EXPECT_EQ(HostVerifierOperation::Result::kReceivedErrorResponse,
GetOperationResult());
break;
case ResponseType::kSuccess:
EXPECT_EQ(HostVerifierOperation::Result::kSuccess,
GetOperationResult());
break;
}
}
base::Optional<HostVerifierOperation::Result> GetOperationResult() {
// Both |operation_| and |fake_delegate_| should have identical result
// values.
EXPECT_EQ(operation_->result(), fake_delegate_->result());
return operation_->result();
}
secure_channel::FakeClientChannel* fake_client_channel() {
return fake_client_channel_;
}
const cryptauth::RemoteDeviceRef& local_device() { return test_devices_[0]; }
const cryptauth::RemoteDeviceRef& remote_device() { return test_devices_[1]; }
private:
const cryptauth::RemoteDeviceRefList test_devices_;
std::unique_ptr<FakeHostVerifierOperationDelegate> fake_delegate_;
std::unique_ptr<device_sync::FakeDeviceSyncClient> fake_device_sync_client_;
std::unique_ptr<secure_channel::FakeSecureChannelClient>
fake_secure_channel_client_;
base::MockOneShotTimer* mock_timer_ = nullptr;
secure_channel::FakeConnectionAttempt* fake_connection_attempt_ = nullptr;
secure_channel::FakeClientChannel* fake_client_channel_ = nullptr;
std::unique_ptr<HostVerifierOperation> operation_;
DISALLOW_COPY_AND_ASSIGN(MultiDeviceSetupHostVerifierOperationImplTest);
};
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
TimeoutFindingEligibleDevices) {
Timeout();
EXPECT_EQ(HostVerifierOperation::Result::kTimeoutFindingEligibleDevices,
GetOperationResult());
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
CancelWhileFindingEligibleDevices) {
CancelAndVerifyResult();
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
ErrorCallingFindEligibleDevices) {
CompletePendingFindEligibleDevicesResponse("errorCode");
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
DeviceToVerifyIsNotEligible) {
CompletePendingFindEligibleDevicesResponse(
base::nullopt /* error_code */,
false /* should_remote_device_be_eligible */);
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
TimeoutFindingConnection) {
CompletePendingFindEligibleDevicesResponse();
Timeout();
EXPECT_EQ(HostVerifierOperation::Result::kTimeoutFindingConnection,
GetOperationResult());
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
CancelWhileFindingConnection) {
CompletePendingFindEligibleDevicesResponse();
CancelAndVerifyResult();
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest, ConnectionAttemptFailed) {
CompletePendingFindEligibleDevicesResponse();
FailToCreateConnectionAndVerifyState(
secure_channel::mojom::ConnectionAttemptFailureReason::
AUTHENTICATION_ERROR);
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
ConnectionDisconnectedUnexpectedly) {
CompletePendingFindEligibleDevicesResponse();
CreateConnectionSuccessfully();
fake_client_channel()->NotifyDisconnected();
EXPECT_EQ(HostVerifierOperation::Result::kConnectionDisconnectedUnexpectedly,
GetOperationResult());
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
TimeoutReceivingResponse) {
CompletePendingFindEligibleDevicesResponse();
CreateConnectionSuccessfully();
Timeout();
EXPECT_EQ(HostVerifierOperation::Result::kTimeoutReceivingResponse,
GetOperationResult());
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
CancelWhileWaitingForResponse) {
CompletePendingFindEligibleDevicesResponse();
CreateConnectionSuccessfully();
CancelAndVerifyResult();
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
ReceivedInvalidResponse_NoResultCode) {
CompletePendingFindEligibleDevicesResponse();
CreateConnectionSuccessfully();
ReceiveResponseAndVerifyState(ResponseType::kNoResultCode);
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
ReceivedInvalidResponse_InvalidResultCode) {
CompletePendingFindEligibleDevicesResponse();
CreateConnectionSuccessfully();
ReceiveResponseAndVerifyState(ResponseType::kInvalidResultCode);
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
ReceivedInvalidResponse_ErrorResultCode) {
CompletePendingFindEligibleDevicesResponse();
CreateConnectionSuccessfully();
ReceiveResponseAndVerifyState(ResponseType::kErrorResultCode);
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest, Success) {
CompletePendingFindEligibleDevicesResponse();
CreateConnectionSuccessfully();
ReceiveResponseAndVerifyState(ResponseType::kSuccess);
}
TEST_F(MultiDeviceSetupHostVerifierOperationImplTest,
ReceiveUnrelatedMessageThenSuccess) {
CompletePendingFindEligibleDevicesResponse();
CreateConnectionSuccessfully();
ReceiveResponseAndVerifyState(ResponseType::kUnrelated);
ReceiveResponseAndVerifyState(ResponseType::kSuccess);
}
} // namespace multidevice_setup
} // namespace chromeos
# 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.
import("//third_party/protobuf/proto_library.gni")
proto_library("proto") {
sources = [
"multidevice_setup.proto",
]
}
// 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.
syntax = "proto2";
package chromeos.multidevice_setup;
option optimize_for = LITE_RUNTIME;
enum MessageType {
UNKNOWN_TYPE = 0;
ENABLE_BETTER_TOGETHER_REQUEST = 1;
ENABLE_BETTER_TOGETHER_RESPONSE = 2;
}
// Client to host, indicating that the client is requesting Better Together
// setup.
message EnableBetterTogetherRequest {}
// Host to client, indicating that Better Together setup was completed.
// Next id: 2
message EnableBetterTogetherResponse {
enum ResultCode {
NORMAL = 0;
ERROR = 1;
}
optional ResultCode result_code = 1;
}
// Wrapper that Better Together setup messages use to explicitly indicate
// message type.
// Next id: 3
message BetterTogetherSetupMessageWrapper {
required MessageType type = 1;
optional bytes payload = 2;
}
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