| // Copyright 2014 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 "components/pairing/bluetooth_host_pairing_controller.h" |
| |
| #include "base/bind.h" |
| #include "base/hash.h" |
| #include "base/location.h" |
| #include "base/logging.h" |
| #include "base/single_thread_task_runner.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/task_runner_util.h" |
| #include "chromeos/system/devicetype.h" |
| #include "components/pairing/bluetooth_pairing_constants.h" |
| #include "components/pairing/pairing_api.pb.h" |
| #include "components/pairing/proto_decoder.h" |
| #include "device/bluetooth/bluetooth_adapter_factory.h" |
| #include "net/base/io_buffer.h" |
| |
| namespace pairing_chromeos { |
| |
| namespace { |
| const int kReceiveSize = 16384; |
| |
| pairing_api::HostStatusParameters::Connectivity PairingApiConnectivityStatus( |
| HostPairingController::Connectivity connectivity_status) { |
| switch (connectivity_status) { |
| case HostPairingController::CONNECTIVITY_UNTESTED: |
| return pairing_api::HostStatusParameters::CONNECTIVITY_UNTESTED; |
| case HostPairingController::CONNECTIVITY_NONE: |
| return pairing_api::HostStatusParameters::CONNECTIVITY_NONE; |
| case HostPairingController::CONNECTIVITY_LIMITED: |
| return pairing_api::HostStatusParameters::CONNECTIVITY_LIMITED; |
| case HostPairingController::CONNECTIVITY_CONNECTING: |
| return pairing_api::HostStatusParameters::CONNECTIVITY_CONNECTING; |
| case HostPairingController::CONNECTIVITY_CONNECTED: |
| return pairing_api::HostStatusParameters::CONNECTIVITY_CONNECTED; |
| default: |
| NOTREACHED(); |
| return pairing_api::HostStatusParameters::CONNECTIVITY_UNTESTED; |
| } |
| } |
| |
| pairing_api::HostStatusParameters::UpdateStatus PairingApiUpdateStatus( |
| HostPairingController::UpdateStatus update_status) { |
| switch(update_status) { |
| case HostPairingController::UPDATE_STATUS_UNKNOWN: |
| return pairing_api::HostStatusParameters::UPDATE_STATUS_UNKNOWN; |
| case HostPairingController::UPDATE_STATUS_UPDATING: |
| return pairing_api::HostStatusParameters::UPDATE_STATUS_UPDATING; |
| case HostPairingController::UPDATE_STATUS_REBOOTING: |
| return pairing_api::HostStatusParameters::UPDATE_STATUS_REBOOTING; |
| case HostPairingController::UPDATE_STATUS_UPDATED: |
| return pairing_api::HostStatusParameters::UPDATE_STATUS_UPDATED; |
| default: |
| NOTREACHED(); |
| return pairing_api::HostStatusParameters::UPDATE_STATUS_UNKNOWN; |
| } |
| } |
| |
| pairing_api::HostStatusParameters::EnrollmentStatus PairingApiEnrollmentStatus( |
| HostPairingController::EnrollmentStatus enrollment_status) { |
| switch(enrollment_status) { |
| case HostPairingController::ENROLLMENT_STATUS_UNKNOWN: |
| return pairing_api::HostStatusParameters::ENROLLMENT_STATUS_UNKNOWN; |
| case HostPairingController::ENROLLMENT_STATUS_ENROLLING: |
| return pairing_api::HostStatusParameters::ENROLLMENT_STATUS_ENROLLING; |
| case HostPairingController::ENROLLMENT_STATUS_FAILURE: |
| return pairing_api::HostStatusParameters::ENROLLMENT_STATUS_FAILURE; |
| case HostPairingController::ENROLLMENT_STATUS_SUCCESS: |
| return pairing_api::HostStatusParameters::ENROLLMENT_STATUS_SUCCESS; |
| default: |
| NOTREACHED(); |
| return pairing_api::HostStatusParameters::ENROLLMENT_STATUS_UNKNOWN; |
| } |
| } |
| |
| std::vector<BluetoothHostPairingController::InputDeviceInfo> GetDevices() { |
| std::vector<BluetoothHostPairingController::InputDeviceInfo> devices; |
| if (device::InputServiceLinux::HasInstance()) |
| device::InputServiceLinux::GetInstance()->GetDevices(&devices); |
| return devices; |
| } |
| |
| } // namespace |
| |
| BluetoothHostPairingController::BluetoothHostPairingController( |
| const scoped_refptr<base::SingleThreadTaskRunner>& file_task_runner) |
| : current_stage_(STAGE_NONE), |
| connectivity_status_(CONNECTIVITY_UNTESTED), |
| update_status_(UPDATE_STATUS_UNKNOWN), |
| enrollment_status_(ENROLLMENT_STATUS_UNKNOWN), |
| proto_decoder_(new ProtoDecoder(this)), |
| file_task_runner_(file_task_runner), |
| ptr_factory_(this) {} |
| |
| BluetoothHostPairingController::~BluetoothHostPairingController() { |
| Reset(); |
| } |
| |
| void BluetoothHostPairingController::SetDelegateForTesting( |
| TestDelegate* delegate) { |
| delegate_ = delegate; |
| } |
| |
| scoped_refptr<device::BluetoothAdapter> |
| BluetoothHostPairingController::GetAdapterForTesting() { |
| return adapter_; |
| } |
| |
| void BluetoothHostPairingController::ChangeStage(Stage new_stage) { |
| if (current_stage_ == new_stage) |
| return; |
| VLOG(1) << "ChangeStage " << new_stage; |
| current_stage_ = new_stage; |
| for (Observer& observer : observers_) |
| observer.PairingStageChanged(new_stage); |
| } |
| |
| void BluetoothHostPairingController::SendHostStatus() { |
| pairing_api::HostStatus host_status; |
| |
| host_status.set_api_version(kPairingAPIVersion); |
| if (!enrollment_domain_.empty()) |
| host_status.mutable_parameters()->set_domain(enrollment_domain_); |
| if (!permanent_id_.empty()) |
| host_status.mutable_parameters()->set_permanent_id(permanent_id_); |
| |
| // TODO(zork): Get these values from the UI. (http://crbug.com/405744) |
| host_status.mutable_parameters()->set_connectivity( |
| PairingApiConnectivityStatus(connectivity_status_)); |
| host_status.mutable_parameters()->set_update_status( |
| PairingApiUpdateStatus(update_status_)); |
| host_status.mutable_parameters()->set_enrollment_status( |
| PairingApiEnrollmentStatus(enrollment_status_)); |
| |
| // TODO(zork): Get a list of other paired controllers. |
| // (http://crbug.com/405757) |
| |
| int size = 0; |
| scoped_refptr<net::IOBuffer> io_buffer( |
| ProtoDecoder::SendHostStatus(host_status, &size)); |
| |
| controller_socket_->Send( |
| io_buffer, size, |
| base::Bind(&BluetoothHostPairingController::OnSendComplete, |
| ptr_factory_.GetWeakPtr()), |
| base::Bind(&BluetoothHostPairingController::OnSendError, |
| ptr_factory_.GetWeakPtr())); |
| } |
| |
| void BluetoothHostPairingController::Reset() { |
| if (adapter_.get()) { |
| device::BluetoothDevice* device = |
| adapter_->GetDevice(controller_device_address_); |
| if (device && device->IsPaired()) { |
| device->Forget(base::Bind(&BluetoothHostPairingController::OnForget, |
| ptr_factory_.GetWeakPtr()), |
| base::Bind(&BluetoothHostPairingController::OnForget, |
| ptr_factory_.GetWeakPtr())); |
| return; |
| } |
| } |
| OnForget(); |
| } |
| |
| void BluetoothHostPairingController::OnGetAdapter( |
| scoped_refptr<device::BluetoothAdapter> adapter) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| DCHECK(!adapter_.get()); |
| adapter_ = adapter; |
| |
| if (adapter_->IsPresent()) { |
| SetPowered(); |
| } else { |
| // Set the name once the adapter is present. |
| adapter_->AddObserver(this); |
| } |
| } |
| |
| void BluetoothHostPairingController::SetPowered() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (adapter_->IsPowered()) { |
| was_powered_ = true; |
| OnSetPowered(); |
| } else { |
| adapter_->SetPowered( |
| true, |
| base::Bind(&BluetoothHostPairingController::OnSetPowered, |
| ptr_factory_.GetWeakPtr()), |
| base::Bind(&BluetoothHostPairingController::OnSetError, |
| ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void BluetoothHostPairingController::OnSetPowered() { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| adapter_->AddPairingDelegate( |
| this, device::BluetoothAdapter::PAIRING_DELEGATE_PRIORITY_HIGH); |
| |
| device::BluetoothAdapter::ServiceOptions options; |
| options.name.reset(new std::string(kPairingServiceName)); |
| |
| adapter_->CreateRfcommService( |
| device::BluetoothUUID(kPairingServiceUUID), options, |
| base::Bind(&BluetoothHostPairingController::OnCreateService, |
| ptr_factory_.GetWeakPtr()), |
| base::Bind(&BluetoothHostPairingController::OnCreateServiceError, |
| ptr_factory_.GetWeakPtr())); |
| } |
| |
| void BluetoothHostPairingController::OnCreateService( |
| scoped_refptr<device::BluetoothSocket> socket) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| service_socket_ = socket; |
| |
| service_socket_->Accept( |
| base::Bind(&BluetoothHostPairingController::OnAccept, |
| ptr_factory_.GetWeakPtr()), |
| base::Bind(&BluetoothHostPairingController::OnAcceptError, |
| ptr_factory_.GetWeakPtr())); |
| |
| adapter_->SetDiscoverable( |
| true, |
| base::Bind(&BluetoothHostPairingController::OnSetDiscoverable, |
| ptr_factory_.GetWeakPtr(), true), |
| base::Bind(&BluetoothHostPairingController::OnSetError, |
| ptr_factory_.GetWeakPtr())); |
| } |
| |
| void BluetoothHostPairingController::OnAccept( |
| const device::BluetoothDevice* device, |
| scoped_refptr<device::BluetoothSocket> socket) { |
| controller_device_address_ = device->GetAddress(); |
| |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| adapter_->SetDiscoverable( |
| false, |
| base::Bind(&BluetoothHostPairingController::OnSetDiscoverable, |
| ptr_factory_.GetWeakPtr(), false), |
| base::Bind(&BluetoothHostPairingController::OnSetError, |
| ptr_factory_.GetWeakPtr())); |
| controller_socket_ = socket; |
| |
| SendHostStatus(); |
| |
| controller_socket_->Receive( |
| kReceiveSize, |
| base::Bind(&BluetoothHostPairingController::OnReceiveComplete, |
| ptr_factory_.GetWeakPtr()), |
| base::Bind(&BluetoothHostPairingController::OnReceiveError, |
| ptr_factory_.GetWeakPtr())); |
| } |
| |
| void BluetoothHostPairingController::OnSetDiscoverable(bool change_stage) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (change_stage) { |
| DCHECK_EQ(current_stage_, STAGE_NONE); |
| ChangeStage(STAGE_WAITING_FOR_CONTROLLER); |
| } |
| } |
| |
| void BluetoothHostPairingController::OnSendComplete(int bytes_sent) {} |
| |
| void BluetoothHostPairingController::OnReceiveComplete( |
| int bytes, scoped_refptr<net::IOBuffer> io_buffer) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| proto_decoder_->DecodeIOBuffer(bytes, io_buffer); |
| |
| if (controller_socket_.get()) { |
| controller_socket_->Receive( |
| kReceiveSize, |
| base::Bind(&BluetoothHostPairingController::OnReceiveComplete, |
| ptr_factory_.GetWeakPtr()), |
| base::Bind(&BluetoothHostPairingController::OnReceiveError, |
| ptr_factory_.GetWeakPtr())); |
| } |
| } |
| |
| void BluetoothHostPairingController::OnCreateServiceError( |
| const std::string& message) { |
| LOG(ERROR) << message; |
| ChangeStage(STAGE_INITIALIZATION_ERROR); |
| } |
| |
| void BluetoothHostPairingController::OnSetError() { |
| adapter_->RemovePairingDelegate(this); |
| ChangeStage(STAGE_INITIALIZATION_ERROR); |
| } |
| |
| void BluetoothHostPairingController::OnAcceptError( |
| const std::string& error_message) { |
| LOG(ERROR) << error_message; |
| ChangeStage(STAGE_CONTROLLER_CONNECTION_ERROR); |
| } |
| |
| void BluetoothHostPairingController::OnSendError( |
| const std::string& error_message) { |
| LOG(ERROR) << error_message; |
| if (enrollment_status_ != ENROLLMENT_STATUS_ENROLLING && |
| enrollment_status_ != ENROLLMENT_STATUS_SUCCESS) { |
| ChangeStage(STAGE_CONTROLLER_CONNECTION_ERROR); |
| } |
| } |
| |
| void BluetoothHostPairingController::PowerOffAdapterIfApplicable( |
| const std::vector<InputDeviceInfo>& devices) { |
| bool use_bluetooth = false; |
| for (const auto& device : devices) { |
| if (device.type == InputDeviceInfo::TYPE_BLUETOOTH) { |
| use_bluetooth = true; |
| break; |
| } |
| } |
| if (!was_powered_ && !use_bluetooth) { |
| adapter_->SetPowered( |
| false, base::Bind(&BluetoothHostPairingController::ResetAdapter, |
| ptr_factory_.GetWeakPtr()), |
| base::Bind(&BluetoothHostPairingController::ResetAdapter, |
| ptr_factory_.GetWeakPtr())); |
| } else { |
| ResetAdapter(); |
| } |
| } |
| |
| void BluetoothHostPairingController::ResetAdapter() { |
| adapter_->RemoveObserver(this); |
| adapter_ = nullptr; |
| if (delegate_) |
| delegate_->OnAdapterReset(); |
| } |
| |
| void BluetoothHostPairingController::OnForget() { |
| if (controller_socket_.get()) { |
| controller_socket_->Close(); |
| controller_socket_ = nullptr; |
| } |
| |
| if (service_socket_.get()) { |
| service_socket_->Close(); |
| service_socket_ = nullptr; |
| } |
| |
| if (adapter_.get()) { |
| if (adapter_->IsDiscoverable()) { |
| adapter_->SetDiscoverable(false, base::Bind(&base::DoNothing), |
| base::Bind(&base::DoNothing)); |
| } |
| |
| base::PostTaskAndReplyWithResult( |
| file_task_runner_.get(), FROM_HERE, base::Bind(&GetDevices), |
| base::Bind(&BluetoothHostPairingController::PowerOffAdapterIfApplicable, |
| ptr_factory_.GetWeakPtr())); |
| } |
| ChangeStage(STAGE_NONE); |
| } |
| |
| void BluetoothHostPairingController::SetControllerDeviceAddressForTesting( |
| const std::string& address) { |
| controller_device_address_ = address; |
| } |
| |
| void BluetoothHostPairingController::OnReceiveError( |
| device::BluetoothSocket::ErrorReason reason, |
| const std::string& error_message) { |
| LOG(ERROR) << reason << ", " << error_message; |
| ChangeStage(STAGE_CONTROLLER_CONNECTION_ERROR); |
| } |
| |
| void BluetoothHostPairingController::OnHostStatusMessage( |
| const pairing_api::HostStatus& message) { |
| NOTREACHED(); |
| } |
| |
| void BluetoothHostPairingController::OnConfigureHostMessage( |
| const pairing_api::ConfigureHost& message) { |
| ChangeStage(STAGE_SETUP_BASIC_CONFIGURATION); |
| for (Observer& observer : observers_) { |
| observer.ConfigureHostRequested( |
| message.parameters().accepted_eula(), message.parameters().lang(), |
| message.parameters().timezone(), message.parameters().send_reports(), |
| message.parameters().keyboard_layout()); |
| } |
| } |
| |
| void BluetoothHostPairingController::OnPairDevicesMessage( |
| const pairing_api::PairDevices& message) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| enrollment_domain_ = message.parameters().enrolling_domain(); |
| ChangeStage(STAGE_ENROLLING); |
| for (Observer& observer : observers_) |
| observer.EnrollHostRequested(message.parameters().admin_access_token()); |
| } |
| |
| void BluetoothHostPairingController::OnCompleteSetupMessage( |
| const pairing_api::CompleteSetup& message) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| if (current_stage_ != STAGE_ENROLLMENT_SUCCESS) { |
| ChangeStage(STAGE_ENROLLMENT_ERROR); |
| } else { |
| // TODO(zork): Handle adding another controller. (http://crbug.com/405757) |
| ChangeStage(STAGE_FINISHED); |
| } |
| Reset(); |
| } |
| |
| void BluetoothHostPairingController::OnErrorMessage( |
| const pairing_api::Error& message) { |
| NOTREACHED(); |
| } |
| |
| void BluetoothHostPairingController::OnRebootMessage( |
| const pairing_api::Reboot& message) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| for (Observer& observer : observers_) |
| observer.RebootHostRequested(); |
| } |
| |
| void BluetoothHostPairingController::OnAddNetworkMessage( |
| const pairing_api::AddNetwork& message) { |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| for (Observer& observer : observers_) |
| observer.AddNetworkRequested(message.parameters().onc_spec()); |
| } |
| |
| void BluetoothHostPairingController::AdapterPresentChanged( |
| device::BluetoothAdapter* adapter, |
| bool present) { |
| DCHECK_EQ(adapter, adapter_.get()); |
| if (present) { |
| adapter_->RemoveObserver(this); |
| SetPowered(); |
| } |
| } |
| |
| void BluetoothHostPairingController::AddObserver(Observer* observer) { |
| observers_.AddObserver(observer); |
| } |
| |
| void BluetoothHostPairingController::RemoveObserver(Observer* observer) { |
| observers_.RemoveObserver(observer); |
| } |
| |
| HostPairingController::Stage BluetoothHostPairingController::GetCurrentStage() { |
| return current_stage_; |
| } |
| |
| void BluetoothHostPairingController::StartPairing() { |
| DCHECK_EQ(current_stage_, STAGE_NONE); |
| const bool bluetooth_supported = |
| device::BluetoothAdapterFactory::IsBluetoothSupported(); |
| if (!bluetooth_supported) { |
| ChangeStage(STAGE_INITIALIZATION_ERROR); |
| return; |
| } |
| |
| device::BluetoothAdapterFactory::GetAdapter( |
| base::Bind(&BluetoothHostPairingController::OnGetAdapter, |
| ptr_factory_.GetWeakPtr())); |
| } |
| |
| std::string BluetoothHostPairingController::GetDeviceName() { |
| return adapter_.get() ? adapter_->GetName() : std::string(); |
| } |
| |
| std::string BluetoothHostPairingController::GetConfirmationCode() { |
| DCHECK_EQ(current_stage_, STAGE_WAITING_FOR_CODE_CONFIRMATION); |
| return confirmation_code_; |
| } |
| |
| std::string BluetoothHostPairingController::GetEnrollmentDomain() { |
| return enrollment_domain_; |
| } |
| |
| void BluetoothHostPairingController::OnNetworkConnectivityChanged( |
| Connectivity connectivity_status) { |
| connectivity_status_ = connectivity_status; |
| if (connectivity_status == CONNECTIVITY_NONE) |
| ChangeStage(STAGE_SETUP_NETWORK_ERROR); |
| SendHostStatus(); |
| } |
| |
| void BluetoothHostPairingController::OnUpdateStatusChanged( |
| UpdateStatus update_status) { |
| update_status_ = update_status; |
| if (update_status == UPDATE_STATUS_UPDATED) |
| ChangeStage(STAGE_WAITING_FOR_CREDENTIALS); |
| SendHostStatus(); |
| } |
| |
| void BluetoothHostPairingController::OnEnrollmentStatusChanged( |
| EnrollmentStatus enrollment_status) { |
| DCHECK_EQ(current_stage_, STAGE_ENROLLING); |
| DCHECK(thread_checker_.CalledOnValidThread()); |
| |
| enrollment_status_ = enrollment_status; |
| if (enrollment_status == ENROLLMENT_STATUS_SUCCESS) { |
| ChangeStage(STAGE_ENROLLMENT_SUCCESS); |
| } else if (enrollment_status == ENROLLMENT_STATUS_FAILURE) { |
| ChangeStage(STAGE_ENROLLMENT_ERROR); |
| } |
| SendHostStatus(); |
| } |
| |
| void BluetoothHostPairingController::SetPermanentId( |
| const std::string& permanent_id) { |
| permanent_id_ = permanent_id; |
| } |
| |
| void BluetoothHostPairingController::RequestPinCode( |
| device::BluetoothDevice* device) { |
| // Disallow unknown device. |
| device->RejectPairing(); |
| } |
| |
| void BluetoothHostPairingController::RequestPasskey( |
| device::BluetoothDevice* device) { |
| // Disallow unknown device. |
| device->RejectPairing(); |
| } |
| |
| void BluetoothHostPairingController::DisplayPinCode( |
| device::BluetoothDevice* device, |
| const std::string& pincode) { |
| // Disallow unknown device. |
| device->RejectPairing(); |
| } |
| |
| void BluetoothHostPairingController::DisplayPasskey( |
| device::BluetoothDevice* device, |
| uint32_t passkey) { |
| // Disallow unknown device. |
| device->RejectPairing(); |
| } |
| |
| void BluetoothHostPairingController::KeysEntered( |
| device::BluetoothDevice* device, |
| uint32_t entered) { |
| // Disallow unknown device. |
| device->RejectPairing(); |
| } |
| |
| void BluetoothHostPairingController::ConfirmPasskey( |
| device::BluetoothDevice* device, |
| uint32_t passkey) { |
| // If a new connection is occurring, reset the stage. This can occur if the |
| // pairing times out, or a new controller connects. |
| if (current_stage_ == STAGE_WAITING_FOR_CODE_CONFIRMATION) |
| ChangeStage(STAGE_WAITING_FOR_CONTROLLER); |
| |
| confirmation_code_ = base::StringPrintf("%06d", passkey); |
| device->ConfirmPairing(); |
| ChangeStage(STAGE_WAITING_FOR_CODE_CONFIRMATION); |
| } |
| |
| void BluetoothHostPairingController::AuthorizePairing( |
| device::BluetoothDevice* device) { |
| // Disallow unknown device. |
| device->RejectPairing(); |
| } |
| |
| } // namespace pairing_chromeos |