| // 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 |