// Copyright 2015 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chromeos/ash/components/proximity_auth/remote_device_life_cycle_impl.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/task/single_thread_task_runner.h"
#include "chromeos/ash/components/multidevice/logging/logging.h"
#include "chromeos/ash/components/proximity_auth/messenger_impl.h"
#include "chromeos/ash/services/secure_channel/public/cpp/client/secure_channel_client.h"
#include "chromeos/ash/services/secure_channel/public/cpp/shared/connection_priority.h"
namespace proximity_auth {
namespace {
const char kSmartLockFeatureName[] = "easy_unlock";
} // namespace
RemoteDeviceLifeCycleImpl::RemoteDeviceLifeCycleImpl(
ash::multidevice::RemoteDeviceRef remote_device,
std::optional<ash::multidevice::RemoteDeviceRef> local_device,
ash::secure_channel::SecureChannelClient* secure_channel_client)
: remote_device_(remote_device),
local_device_(local_device),
secure_channel_client_(secure_channel_client),
state_(RemoteDeviceLifeCycle::State::STOPPED) {}
RemoteDeviceLifeCycleImpl::~RemoteDeviceLifeCycleImpl() {}
void RemoteDeviceLifeCycleImpl::Start() {
PA_LOG(VERBOSE) << "Life cycle for " << remote_device_.name() << " started.";
DCHECK(state_ == RemoteDeviceLifeCycle::State::STOPPED);
FindConnection();
}
ash::multidevice::RemoteDeviceRef RemoteDeviceLifeCycleImpl::GetRemoteDevice()
const {
return remote_device_;
}
ash::secure_channel::ClientChannel* RemoteDeviceLifeCycleImpl::GetChannel()
const {
if (channel_)
return channel_.get();
if (messenger_)
return messenger_->GetChannel();
return nullptr;
}
RemoteDeviceLifeCycle::State RemoteDeviceLifeCycleImpl::GetState() const {
return state_;
}
Messenger* RemoteDeviceLifeCycleImpl::GetMessenger() {
return messenger_.get();
}
void RemoteDeviceLifeCycleImpl::AddObserver(Observer* observer) {
observers_.AddObserver(observer);
}
void RemoteDeviceLifeCycleImpl::RemoveObserver(Observer* observer) {
observers_.RemoveObserver(observer);
}
void RemoteDeviceLifeCycleImpl::TransitionToState(
RemoteDeviceLifeCycle::State new_state) {
PA_LOG(VERBOSE) << "Life cycle transition: " << state_ << " => " << new_state;
RemoteDeviceLifeCycle::State old_state = state_;
state_ = new_state;
for (auto& observer : observers_)
observer.OnLifeCycleStateChanged(old_state, new_state);
}
void RemoteDeviceLifeCycleImpl::FindConnection() {
connection_attempt_ = secure_channel_client_->ListenForConnectionFromDevice(
remote_device_, *local_device_, kSmartLockFeatureName,
ash::secure_channel::ConnectionMedium::kBluetoothLowEnergy,
ash::secure_channel::ConnectionPriority::kHigh);
connection_attempt_->SetDelegate(this);
TransitionToState(RemoteDeviceLifeCycle::State::FINDING_CONNECTION);
}
void RemoteDeviceLifeCycleImpl::CreateMessenger() {
DCHECK(state_ == RemoteDeviceLifeCycle::State::AUTHENTICATING);
messenger_ = std::make_unique<MessengerImpl>(std::move(channel_));
messenger_->AddObserver(this);
TransitionToState(RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
}
void RemoteDeviceLifeCycleImpl::OnConnectionAttemptFailure(
ash::secure_channel::mojom::ConnectionAttemptFailureReason reason) {
connection_attempt_.reset();
if (reason == ash::secure_channel::mojom::ConnectionAttemptFailureReason::
ADAPTER_DISABLED ||
reason == ash::secure_channel::mojom::ConnectionAttemptFailureReason::
ADAPTER_NOT_PRESENT) {
// Transition to state STOPPED, and wait for Bluetooth to become powered.
// If it does, UnlockManager will start RemoteDeviceLifeCycle again.
PA_LOG(WARNING) << "Life cycle for "
<< remote_device_.GetTruncatedDeviceIdForLogs()
<< " stopped because Bluetooth is not available.";
TransitionToState(RemoteDeviceLifeCycle::State::STOPPED);
} else {
// TODO(crbug.com/41474905): Improve the name AUTHENTICATION_FAILED (it can
// encompass errors other than authentication failures) and create a metric
// with buckets corresponding to the ConnectionAttemptFailureReason.
PA_LOG(ERROR) << "Failed to authenticate with remote device: "
<< remote_device_.GetTruncatedDeviceIdForLogs()
<< ", for reason: " << reason << ". Giving up.";
TransitionToState(RemoteDeviceLifeCycle::State::AUTHENTICATION_FAILED);
}
}
void RemoteDeviceLifeCycleImpl::OnConnection(
std::unique_ptr<ash::secure_channel::ClientChannel> channel) {
DCHECK(state_ == RemoteDeviceLifeCycle::State::FINDING_CONNECTION);
TransitionToState(RemoteDeviceLifeCycle::State::AUTHENTICATING);
channel_ = std::move(channel);
// Create the MessengerImpl asynchronously. |messenger_| registers itself as
// an observer of |channel_|, so creating it synchronously would trigger
// |OnSendCompleted()| as an observer call for |messenger_|.
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(&RemoteDeviceLifeCycleImpl::CreateMessenger,
weak_ptr_factory_.GetWeakPtr()));
}
void RemoteDeviceLifeCycleImpl::OnDisconnected() {
DCHECK(state_ == RemoteDeviceLifeCycle::State::SECURE_CHANNEL_ESTABLISHED);
messenger_->RemoveObserver(this);
messenger_.reset();
FindConnection();
}
} // namespace proximity_auth