| // Copyright 2018 The Chromium OS 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 "bluetooth/newblued/newblue.h" |
| |
| #include <newblue/sm.h> |
| |
| #include <algorithm> |
| #include <memory> |
| #include <utility> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/message_loop/message_loop.h> |
| #include <base/strings/stringprintf.h> |
| #include <chromeos/dbus/service_constants.h> |
| |
| #include "bluetooth/common/util.h" |
| |
| namespace bluetooth { |
| |
| namespace { |
| |
| // Converts the uint8_t[6] MAC address into std::string form, e.g. |
| // {0x05, 0x04, 0x03, 0x02, 0x01, 0x00} will be 00:01:02:03:04:05. |
| std::string ConvertBtAddrToString(const struct bt_addr& addr) { |
| return base::StringPrintf("%02X:%02X:%02X:%02X:%02X:%02X", addr.addr[5], |
| addr.addr[4], addr.addr[3], addr.addr[2], |
| addr.addr[1], addr.addr[0]); |
| } |
| |
| } // namespace |
| |
| Newblue::Newblue(std::unique_ptr<LibNewblue> libnewblue) |
| : libnewblue_(std::move(libnewblue)), weak_ptr_factory_(this) {} |
| |
| base::WeakPtr<Newblue> Newblue::GetWeakPtr() { |
| return weak_ptr_factory_.GetWeakPtr(); |
| } |
| |
| bool Newblue::Init() { |
| if (base::MessageLoop::current()) |
| origin_task_runner_ = base::MessageLoop::current()->task_runner(); |
| return true; |
| } |
| |
| void Newblue::RegisterPairingAgent(PairingAgent* pairing_agent) { |
| pairing_agent_ = pairing_agent; |
| } |
| |
| void Newblue::UnregisterPairingAgent() { |
| pairing_agent_ = nullptr; |
| } |
| |
| bool Newblue::ListenReadyForUp(base::Closure callback) { |
| // Dummy MAC address. NewBlue doesn't actually use the MAC address as it's |
| // exclusively controlled by BlueZ. |
| static const uint8_t kZeroMac[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; |
| |
| if (!libnewblue_->HciUp(kZeroMac, &Newblue::OnStackReadyForUpThunk, this)) |
| return false; |
| |
| ready_for_up_callback_ = callback; |
| return true; |
| } |
| |
| bool Newblue::BringUp() { |
| if (!libnewblue_->HciIsUp()) { |
| LOG(ERROR) << "HCI is not ready for up"; |
| return false; |
| } |
| |
| if (libnewblue_->L2cInit()) { |
| LOG(ERROR) << "Failed to initialize L2CAP"; |
| return false; |
| } |
| |
| if (!libnewblue_->AttInit()) { |
| LOG(ERROR) << "Failed to initialize ATT"; |
| return false; |
| } |
| |
| if (!libnewblue_->GattProfileInit()) { |
| LOG(ERROR) << "Failed to initialize GATT"; |
| return false; |
| } |
| |
| if (!libnewblue_->GattBuiltinInit()) { |
| LOG(ERROR) << "Failed to initialize GATT services"; |
| return false; |
| } |
| |
| if (!libnewblue_->SmInit()) { |
| LOG(ERROR) << "Failed to init SM"; |
| return false; |
| } |
| |
| // Always register passkey display observer, assuming that our UI always |
| // supports this. |
| // TODO(sonnysasaka): We may optimize this by registering passkey display |
| // observer only if there is currently a default agent registered. |
| passkey_display_observer_id_ = libnewblue_->SmRegisterPasskeyDisplayObserver( |
| this, &Newblue::PasskeyDisplayObserverCallbackThunk); |
| |
| pair_state_handle_ = libnewblue_->SmRegisterPairStateObserver( |
| this, &Newblue::PairStateCallbackThunk); |
| if (!pair_state_handle_) { |
| LOG(ERROR) << "Failed to register as an observer of pairing state"; |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool Newblue::StartDiscovery(DeviceDiscoveredCallback callback) { |
| if (discovery_handle_ != 0) { |
| LOG(WARNING) << "Discovery is already started, ignoring request"; |
| return false; |
| } |
| |
| discovery_handle_ = libnewblue_->HciDiscoverLeStart( |
| &Newblue::DiscoveryCallbackThunk, this, true /* active */, |
| false /* use_random_addr */); |
| if (discovery_handle_ == 0) { |
| LOG(ERROR) << "Failed to start LE discovery"; |
| return false; |
| } |
| |
| device_discovered_callback_ = callback; |
| return true; |
| } |
| |
| bool Newblue::StopDiscovery() { |
| if (discovery_handle_ == 0) { |
| LOG(WARNING) << "Discovery is not started, ignoring request"; |
| return false; |
| } |
| |
| bool ret = libnewblue_->HciDiscoverLeStop(discovery_handle_); |
| if (!ret) { |
| LOG(ERROR) << "Failed to stop LE discovery"; |
| return false; |
| } |
| |
| device_discovered_callback_.Reset(); |
| discovery_handle_ = 0; |
| return true; |
| } |
| |
| UniqueId Newblue::RegisterAsPairObserver(PairStateChangedCallback callback) { |
| UniqueId observer_id = GetNextId(); |
| if (observer_id != kInvalidUniqueId) |
| pair_observers_.emplace(observer_id, callback); |
| return observer_id; |
| } |
| |
| void Newblue::UnregisterAsPairObserver(UniqueId observer_id) { |
| pair_observers_.erase(observer_id); |
| } |
| |
| bool Newblue::PostTask(const tracked_objects::Location& from_here, |
| const base::Closure& task) { |
| CHECK(origin_task_runner_.get()); |
| return origin_task_runner_->PostTask(from_here, task); |
| } |
| |
| void Newblue::OnStackReadyForUpThunk(void* data) { |
| Newblue* newblue = static_cast<Newblue*>(data); |
| newblue->PostTask(FROM_HERE, base::Bind(&Newblue::OnStackReadyForUp, |
| newblue->GetWeakPtr())); |
| } |
| |
| void Newblue::OnStackReadyForUp() { |
| if (ready_for_up_callback_.is_null()) { |
| // libnewblue says it's ready for up but I don't have any callback. Most |
| // probably another stack (e.g. BlueZ) just re-initialized the adapter. |
| LOG(WARNING) << "No callback when stack is ready for up"; |
| return; |
| } |
| |
| ready_for_up_callback_.Run(); |
| // It only makes sense to bring up the stack once and for all. Reset the |
| // callback here so we won't bring up the stack twice. |
| ready_for_up_callback_.Reset(); |
| } |
| |
| void Newblue::DiscoveryCallbackThunk(void* data, |
| const struct bt_addr* addr, |
| int8_t rssi, |
| uint8_t reply_type, |
| const void* eir, |
| uint8_t eir_len) { |
| Newblue* newblue = static_cast<Newblue*>(data); |
| std::vector<uint8_t> eir_vector(static_cast<const uint8_t*>(eir), |
| static_cast<const uint8_t*>(eir) + eir_len); |
| std::string address = ConvertBtAddrToString(*addr); |
| newblue->PostTask( |
| FROM_HERE, base::Bind(&Newblue::DiscoveryCallback, newblue->GetWeakPtr(), |
| address, addr->type, rssi, reply_type, eir_vector)); |
| } |
| |
| void Newblue::DiscoveryCallback(const std::string& address, |
| uint8_t address_type, |
| int8_t rssi, |
| uint8_t reply_type, |
| const std::vector<uint8_t>& eir) { |
| VLOG(1) << __func__; |
| |
| if (device_discovered_callback_.is_null()) { |
| LOG(WARNING) << "DiscoveryCallback called when not discovering"; |
| return; |
| } |
| |
| device_discovered_callback_.Run(address, address_type, rssi, reply_type, eir); |
| } |
| |
| bool Newblue::Pair(const std::string& device_address, |
| bool is_random_address, |
| smPairSecurityRequirements security_requirement) { |
| struct bt_addr address; |
| if (!ConvertToBtAddr(is_random_address, device_address, &address)) |
| return false; |
| |
| libnewblue_->SmPair(&address, &security_requirement); |
| return true; |
| } |
| |
| bool Newblue::CancelPair(const std::string& device_address, |
| bool is_random_address) { |
| struct bt_addr address; |
| if (!ConvertToBtAddr(is_random_address, device_address, &address)) |
| return false; |
| |
| libnewblue_->SmUnpair(&address); |
| return true; |
| } |
| |
| void Newblue::PairStateCallbackThunk(void* data, |
| const void* pair_state_change, |
| uniq_t observer_id) { |
| CHECK(data && pair_state_change); |
| |
| Newblue* newblue = static_cast<Newblue*>(data); |
| const smPairStateChange* change = |
| static_cast<const smPairStateChange*>(pair_state_change); |
| |
| newblue->PostTask( |
| FROM_HERE, base::Bind(&Newblue::PairStateCallback, newblue->GetWeakPtr(), |
| *change, observer_id)); |
| } |
| |
| void Newblue::PasskeyDisplayObserverCallbackThunk( |
| void* data, |
| const struct smPasskeyDisplay* passkey_display, |
| uniq_t observer_id) { |
| if (!passkey_display) { |
| LOG(WARNING) << "passkey display is not given"; |
| return; |
| } |
| |
| Newblue* newblue = static_cast<Newblue*>(data); |
| newblue->PostTask( |
| FROM_HERE, |
| base::Bind(&Newblue::PasskeyDisplayObserverCallback, |
| newblue->GetWeakPtr(), *passkey_display, observer_id)); |
| } |
| |
| void Newblue::PasskeyDisplayObserverCallback( |
| struct smPasskeyDisplay passkey_display, uniq_t observer_id) { |
| if (observer_id != passkey_display_observer_id_) { |
| LOG(WARNING) << "passkey display observer id mismatch"; |
| return; |
| } |
| |
| if (passkey_display.valid) { |
| CHECK(pairing_agent_); |
| pairing_agent_->DisplayPasskey( |
| ConvertBtAddrToString(passkey_display.peerAddr), |
| passkey_display.passkey); |
| } else { |
| VLOG(1) << "The passkey session expired with the device"; |
| } |
| } |
| |
| void Newblue::PairStateCallback(const smPairStateChange& change, |
| uniq_t observer_id) { |
| VLOG(1) << __func__; |
| |
| CHECK_EQ(observer_id, pair_state_handle_); |
| |
| std::string address = ConvertBtAddrToString(change.peerAddr); |
| PairState state = static_cast<PairState>(change.pairState); |
| PairError error = static_cast<PairError>(change.pairErr); |
| |
| // Notify |pair_observers|. |
| for (const auto& observer : pair_observers_) |
| observer.second.Run(address, state, error); |
| } |
| |
| } // namespace bluetooth |