blob: 30c9de1e59d146355bec032a67e77fe55e097f8a [file] [log] [blame]
// 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 "device/bluetooth/bluetooth_pairing_winrt.h"
#include <utility>
#include "base/bind.h"
#include "base/logging.h"
#include "base/strings/string_piece.h"
#include "base/task/thread_pool.h"
#include "base/win/com_init_util.h"
#include "base/win/post_async_results.h"
#include "base/win/scoped_hstring.h"
#include "device/bluetooth/bluetooth_device_winrt.h"
#include "device/bluetooth/event_utils_winrt.h"
namespace device {
namespace {
using ABI::Windows::Devices::Enumeration::DevicePairingKinds;
using ABI::Windows::Devices::Enumeration::DevicePairingKinds_ConfirmOnly;
using ABI::Windows::Devices::Enumeration::DevicePairingKinds_ConfirmPinMatch;
using ABI::Windows::Devices::Enumeration::DevicePairingKinds_DisplayPin;
using ABI::Windows::Devices::Enumeration::DevicePairingKinds_ProvidePin;
using ABI::Windows::Devices::Enumeration::DevicePairingResult;
using ABI::Windows::Devices::Enumeration::DevicePairingResultStatus;
using ABI::Windows::Devices::Enumeration::
DevicePairingResultStatus_AlreadyPaired;
using ABI::Windows::Devices::Enumeration::
DevicePairingResultStatus_AuthenticationFailure;
using ABI::Windows::Devices::Enumeration::
DevicePairingResultStatus_AuthenticationTimeout;
using ABI::Windows::Devices::Enumeration::
DevicePairingResultStatus_ConnectionRejected;
using ABI::Windows::Devices::Enumeration::DevicePairingResultStatus_Failed;
using ABI::Windows::Devices::Enumeration::
DevicePairingResultStatus_OperationAlreadyInProgress;
using ABI::Windows::Devices::Enumeration::DevicePairingResultStatus_Paired;
using ABI::Windows::Devices::Enumeration::
DevicePairingResultStatus_PairingCanceled;
using ABI::Windows::Devices::Enumeration::
DevicePairingResultStatus_RejectedByHandler;
using CompletionCallback = base::OnceCallback<void(HRESULT hr)>;
using ConnectErrorCode = BluetoothDevice::ConnectErrorCode;
using ABI::Windows::Devices::Enumeration::IDeviceInformationCustomPairing;
using ABI::Windows::Devices::Enumeration::IDevicePairingRequestedEventArgs;
using ABI::Windows::Devices::Enumeration::IDevicePairingResult;
using ABI::Windows::Foundation::IAsyncOperation;
using Microsoft::WRL::ComPtr;
void PostTask(BluetoothPairingWinrt::ConnectCallback callback,
absl::optional<BluetoothDevice::ConnectErrorCode> error_code) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), error_code));
}
HRESULT CompleteDeferral(
Microsoft::WRL::ComPtr<ABI::Windows::Foundation::IDeferral> deferral) {
// Apparently deferrals may be created (aka obtained) on the main thread
// initialized for STA, but must be completed on a thread with COM initialized
// for MTA. If the deferral is completed on the main thread then the
// Complete() call will succeed, i.e return S_OK, but the Windows Device
// Association Service will be hung and all Bluetooth association changes
// (system wide) will fail.
base::win::AssertComApartmentType(base::win::ComApartmentType::MTA);
return deferral->Complete();
}
} // namespace
BluetoothPairingWinrt::BluetoothPairingWinrt(
BluetoothDeviceWinrt* device,
BluetoothDevice::PairingDelegate* pairing_delegate,
ComPtr<IDeviceInformationCustomPairing> custom_pairing,
ConnectCallback callback)
: device_(device),
pairing_delegate_(pairing_delegate),
custom_pairing_(std::move(custom_pairing)),
callback_(std::move(callback)) {
DCHECK(device_);
DCHECK(pairing_delegate_);
DCHECK(custom_pairing_);
}
BluetoothPairingWinrt::~BluetoothPairingWinrt() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (!pairing_requested_token_)
return;
HRESULT hr =
custom_pairing_->remove_PairingRequested(*pairing_requested_token_);
if (FAILED(hr)) {
DVLOG(2) << "Removing PairingRequested Handler failed: "
<< logging::SystemErrorCodeToString(hr);
}
}
void BluetoothPairingWinrt::StartPairing() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
pairing_requested_token_ = AddTypedEventHandler(
custom_pairing_.Get(),
&IDeviceInformationCustomPairing::add_PairingRequested,
base::BindRepeating(&BluetoothPairingWinrt::OnPairingRequested,
weak_ptr_factory_.GetWeakPtr()));
if (!pairing_requested_token_) {
PostTask(std::move(callback_),
BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
ComPtr<IAsyncOperation<DevicePairingResult*>> pair_op;
HRESULT hr = custom_pairing_->PairAsync(
DevicePairingKinds_ConfirmOnly | DevicePairingKinds_ProvidePin |
DevicePairingKinds_ConfirmPinMatch,
&pair_op);
if (FAILED(hr)) {
DVLOG(2) << "DeviceInformationCustomPairing::PairAsync() failed: "
<< logging::SystemErrorCodeToString(hr);
PostTask(std::move(callback_),
BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
hr = base::win::PostAsyncResults(
std::move(pair_op), base::BindOnce(&BluetoothPairingWinrt::OnPair,
weak_ptr_factory_.GetWeakPtr()));
if (FAILED(hr)) {
DVLOG(2) << "PostAsyncResults failed: "
<< logging::SystemErrorCodeToString(hr);
PostTask(std::move(callback_),
BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
}
bool BluetoothPairingWinrt::ExpectingPinCode() const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
return expecting_pin_code_;
}
void BluetoothPairingWinrt::OnSetPinCodeDeferralCompletion(HRESULT hr) {
if (FAILED(hr)) {
DVLOG(2) << "Completing Deferred Pairing Request failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
}
}
void BluetoothPairingWinrt::SetPinCode(base::StringPiece pin_code) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << "BluetoothPairingWinrt::SetPinCode(" << pin_code << ")";
auto pin_hstring = base::win::ScopedHString::Create(pin_code);
DCHECK(expecting_pin_code_);
expecting_pin_code_ = false;
DCHECK(pairing_requested_);
HRESULT hr = pairing_requested_->AcceptWithPin(pin_hstring.get());
if (FAILED(hr)) {
DVLOG(2) << "Accepting Pairing Request With Pin failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
DCHECK(pairing_deferral_);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&CompleteDeferral, std::move(pairing_deferral_)),
base::BindOnce(&BluetoothPairingWinrt::OnSetPinCodeDeferralCompletion,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothPairingWinrt::OnRejectPairing(HRESULT hr) {
if (FAILED(hr)) {
DVLOG(2) << "Completing Deferred Pairing Request failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
std::move(callback_).Run(
BluetoothDevice::ConnectErrorCode::ERROR_AUTH_REJECTED);
}
void BluetoothPairingWinrt::RejectPairing() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << "BluetoothPairingWinrt::RejectPairing()";
DCHECK(pairing_deferral_);
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&CompleteDeferral, std::move(pairing_deferral_)),
base::BindOnce(&BluetoothPairingWinrt::OnRejectPairing,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothPairingWinrt::OnCancelPairing(HRESULT hr) {
// This method is normally never called. Usually when CancelPairing() is
// invoked the deferral is completed, which immediately calls OnPair(), which
// runs |callback_| and destroys this object before this method can be
// executed. However, if the deferral fails to complete, this will be run.
if (FAILED(hr)) {
DVLOG(2) << "Completing Deferred Pairing Request failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
std::move(callback_).Run(
BluetoothDevice::ConnectErrorCode::ERROR_AUTH_CANCELED);
}
void BluetoothPairingWinrt::CancelPairing() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << "BluetoothPairingWinrt::CancelPairing()";
DCHECK(pairing_deferral_);
// There is no way to explicitly cancel an in-progress pairing as
// DevicePairingRequestedEventArgs has no Cancel() method. Our approach is to
// complete the deferral, without accepting, which results in a
// RejectedByHandler result status. |was_cancelled_| is set so that OnPair(),
// which is called when the deferral is completed, will know that cancellation
// was the actual result.
was_cancelled_ = true;
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, {base::MayBlock()},
base::BindOnce(&CompleteDeferral, std::move(pairing_deferral_)),
base::BindOnce(&BluetoothPairingWinrt::OnCancelPairing,
weak_ptr_factory_.GetWeakPtr()));
}
void BluetoothPairingWinrt::OnPairingRequested(
IDeviceInformationCustomPairing* custom_pairing,
IDevicePairingRequestedEventArgs* pairing_requested) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DVLOG(2) << "BluetoothPairingWinrt::OnPairingRequested()";
DevicePairingKinds pairing_kind;
HRESULT hr = pairing_requested->get_PairingKind(&pairing_kind);
if (FAILED(hr)) {
DVLOG(2) << "Getting Pairing Kind failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
DVLOG(2) << "DevicePairingKind: " << static_cast<int>(pairing_kind);
if (pairing_kind != DevicePairingKinds_ProvidePin) {
DVLOG(2) << "Unexpected DevicePairingKind.";
std::move(callback_).Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
hr = pairing_requested->GetDeferral(&pairing_deferral_);
if (FAILED(hr)) {
DVLOG(2) << "Getting Pairing Deferral failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
pairing_requested_ = pairing_requested;
expecting_pin_code_ = true;
pairing_delegate_->RequestPinCode(device_);
}
void BluetoothPairingWinrt::OnPair(
ComPtr<IDevicePairingResult> pairing_result) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DevicePairingResultStatus status;
HRESULT hr = pairing_result->get_Status(&status);
if (FAILED(hr)) {
DVLOG(2) << "Getting Pairing Result Status failed: "
<< logging::SystemErrorCodeToString(hr);
std::move(callback_).Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
if (was_cancelled_ && status == DevicePairingResultStatus_RejectedByHandler) {
// See comment in CancelPairing() for explanation of why was_cancelled_
// is used.
status = DevicePairingResultStatus_PairingCanceled;
}
DVLOG(2) << "Pairing Result Status: " << static_cast<int>(status);
switch (status) {
case DevicePairingResultStatus_AlreadyPaired:
case DevicePairingResultStatus_Paired:
std::move(callback_).Run(/*error_code=*/absl::nullopt);
return;
case DevicePairingResultStatus_PairingCanceled:
std::move(callback_).Run(
BluetoothDevice::ConnectErrorCode::ERROR_AUTH_CANCELED);
return;
case DevicePairingResultStatus_AuthenticationFailure:
std::move(callback_).Run(
BluetoothDevice::ConnectErrorCode::ERROR_AUTH_FAILED);
return;
case DevicePairingResultStatus_ConnectionRejected:
case DevicePairingResultStatus_RejectedByHandler:
std::move(callback_).Run(
BluetoothDevice::ConnectErrorCode::ERROR_AUTH_REJECTED);
return;
case DevicePairingResultStatus_AuthenticationTimeout:
std::move(callback_).Run(
BluetoothDevice::ConnectErrorCode::ERROR_AUTH_TIMEOUT);
return;
case DevicePairingResultStatus_Failed:
std::move(callback_).Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
case DevicePairingResultStatus_OperationAlreadyInProgress:
std::move(callback_).Run(
BluetoothDevice::ConnectErrorCode::ERROR_INPROGRESS);
return;
default:
std::move(callback_).Run(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
return;
}
}
} // namespace device