blob: 95267cefd0561a9f12ad81c210bebe7892209f7c [file] [log] [blame]
// 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/device_interface_handler.h"
#include <memory>
#include <set>
#include <string>
#include <utility>
#include <vector>
#include <base/compiler_specific.h>
#include <base/stl_util.h>
#include <base/strings/stringprintf.h>
#include <chromeos/dbus/service_constants.h>
#include "bluetooth/newblued/adapter_interface_handler.h"
#include "bluetooth/newblued/util.h"
namespace bluetooth {
namespace {
// The default values of Flags, TX Power, EIR Class, Appearance and
// Manufacturer ID come from the assigned numbers and Supplement to the
// Bluetooth Core Specification defined by Bluetooth SIG. These default values
// are used to determine whether the fields are ever received in EIR and set
// for a device.
constexpr int8_t kDefaultRssi = -128;
constexpr int8_t kDefaultTxPower = -128;
constexpr uint32_t kDefaultEirClass = 0x1F00;
constexpr uint16_t kDefaultAppearance = 0;
constexpr uint16_t kDefaultManufacturerId = 0xFFFF;
const std::vector<uint8_t> kDefaultFlags({0});
constexpr char kDeviceTypeLe[] = "LE";
// Updates device alias based on its name or address.
void UpdateDeviceAlias(Device* device) {
// In BlueZ, if alias is never provided or is set to empty for a device, the
// value of Alias property is set to the value of Name property if
// |device.name| is not empty. If |device.name| is also empty, then Alias
// is set to |device.address| in the following string format.
// xx-xx-xx-xx-xx-xx
std::string alias;
if (!device->internal_alias.empty()) {
alias = device->internal_alias;
} else if (!device->name.value().empty()) {
alias = device->name.value();
} else {
alias = device->address;
std::replace(alias.begin(), alias.end(), ':', '-');
}
device->alias.SetValue(std::move(alias));
}
// Canonicalizes UUIDs and wraps them as a vector for exposing or updating
// service UUIDs.
std::vector<std::string> CanonicalizeUuids(const std::set<Uuid>& uuids) {
std::vector<std::string> result;
result.reserve(uuids.size());
for (const auto& uuid : uuids)
result.push_back(uuid.canonical_value());
return result;
}
// Canonicalizes UUIDs associated with service data for exposing or updating
// service data.
std::map<std::string, std::vector<uint8_t>> CanonicalizeServiceData(
const std::map<Uuid, std::vector<uint8_t>>& service_data) {
std::map<std::string, std::vector<uint8_t>> result;
for (const auto& data : service_data)
result.emplace(data.first.canonical_value(), data.second);
return result;
}
// Converts pair state to string.
std::string ConvertPairStateToString(PairState state) {
switch (state) {
case PairState::CANCELED:
return "canceled";
case PairState::NOT_PAIRED:
return "not paired";
case PairState::FAILED:
return "failed";
case PairState::PAIRED:
return "paired";
case PairState::STARTED:
return "started";
default:
return "unknown";
}
}
std::string ConvertConnectStateToString(ConnectState state) {
switch (state) {
case ConnectState::CONNECTED:
return "Connected";
case ConnectState::DISCONNECTED:
return "Disconnected";
case ConnectState::ERROR:
return "Disconnected with error";
case ConnectState::DISCONNECTED_BY_US:
return "Disconnected by us";
default:
return "Unknown";
}
}
// Converts the security manager pairing errors to BlueZ's D-Bus errors.
std::string ConvertSmPairErrorToDbusError(PairError error_code) {
switch (error_code) {
case PairError::NONE:
// This comes along with the pairing states other than
// SM_PAIR_STATE_FAILED.
return std::string();
case PairError::ALREADY_PAIRED:
return bluetooth_device::kErrorAlreadyExists;
case PairError::IN_PROGRESS:
return bluetooth_device::kErrorInProgress;
case PairError::INVALID_PAIR_REQ:
return bluetooth_device::kErrorInvalidArguments;
case PairError::L2C_CONN:
return bluetooth_device::kErrorConnectionAttemptFailed;
case PairError::NO_SUCH_DEVICE:
return bluetooth_device::kErrorDoesNotExist;
case PairError::PASSKEY_FAILED:
case PairError::OOB_NOT_AVAILABLE:
case PairError::AUTH_REQ_INFEASIBLE:
case PairError::CONF_VALUE_MISMATCHED:
case PairError::PAIRING_NOT_SUPPORTED:
case PairError::ENCR_KEY_SIZE:
case PairError::REPEATED_ATTEMPT:
case PairError::INVALID_PARAM:
case PairError::UNEXPECTED_SM_CMD:
case PairError::SEND_SM_CMD:
case PairError::ENCR_CONN:
case PairError::UNEXPECTED_L2C_EVT:
return bluetooth_device::kErrorAuthenticationFailed;
case PairError::STALLED:
return bluetooth_device::kErrorAuthenticationTimeout;
case PairError::MEMORY:
case PairError::UNKNOWN:
default:
return bluetooth_device::kErrorFailed;
}
}
bool IsHid(uint16_t appearance) {
return true;
// TODO(mcchou): Check appearance after we memorize the value of properties.
// This check is preventing the reconnection of HID devices.
// return ((appearance & kAppearanceMask) >> 6) == 0x0f;
}
} // namespace
Device::Device() : Device("") {}
Device::Device(const std::string& address)
: address(address),
is_random_address(false),
paired(false),
connected(false),
trusted(false),
blocked(false),
services_resolved(false),
tx_power(kDefaultTxPower),
rssi(kDefaultRssi),
eir_class(kDefaultEirClass),
appearance(kDefaultAppearance),
icon(ConvertAppearanceToIcon(kDefaultAppearance)),
flags(kDefaultFlags),
manufacturer(ParseDataIntoManufacturer(kDefaultManufacturerId,
std::vector<uint8_t>())),
identity_address("") {}
DeviceInfo::DeviceInfo(bool has_active_discovery_client,
const std::string& adv_address,
uint8_t address_type,
const std::string& resolved_address,
int8_t rssi,
uint8_t reply_type)
: has_active_discovery_client(has_active_discovery_client),
advertised_address(adv_address),
address_type(address_type),
resolved_address(resolved_address),
rssi(rssi),
reply_type(reply_type),
flags(kDefaultFlags),
service_uuids(std::set<Uuid>()),
name(""),
tx_power(kDefaultTxPower),
eir_class(kDefaultEirClass),
service_data(std::map<Uuid, std::vector<uint8_t>>()),
appearance(kDefaultAppearance),
icon(ConvertAppearanceToIcon(kDefaultAppearance)),
manufacturer(ParseDataIntoManufacturer(kDefaultManufacturerId,
std::vector<uint8_t>())) {}
DeviceInterfaceHandler::DeviceInterfaceHandler(
scoped_refptr<dbus::Bus> bus,
Newblue* newblue,
ExportedObjectManagerWrapper* exported_object_manager_wrapper)
: bus_(bus),
newblue_(newblue),
exported_object_manager_wrapper_(exported_object_manager_wrapper),
weak_ptr_factory_(this) {}
bool DeviceInterfaceHandler::Init() {
// Retrieve the previously saved scan results, and export only the paired
// devices.
std::vector<KnownDevice> known_devices = newblue_->GetKnownDevices();
for (const auto& known_device : known_devices) {
if (!known_device.is_paired)
continue;
Device* device = AddOrGetDiscoveredDevice(
known_device.address, known_device.address, known_device.address_type);
SetDevicePaired(device, true);
device->name.SetValue(known_device.name + kNewblueNameSuffix);
if (!known_device.identity_address.empty())
device->identity_address.SetValue(known_device.identity_address);
UpdateDeviceAlias(device);
ExportOrUpdateDevice(device);
}
// Register for pairing state changed events.
pair_observer_id_ = newblue_->RegisterAsPairObserver(
base::Bind(&DeviceInterfaceHandler::OnPairStateChanged,
weak_ptr_factory_.GetWeakPtr()));
if (pair_observer_id_ == kInvalidUniqueId)
return false;
if (!newblue_->RegisterGattClientConnectCallback(
base::Bind(&DeviceInterfaceHandler::OnGattClientConnectCallback,
weak_ptr_factory_.GetWeakPtr()))) {
return false;
}
return true;
}
base::WeakPtr<DeviceInterfaceHandler> DeviceInterfaceHandler::GetWeakPtr() {
return weak_ptr_factory_.GetWeakPtr();
}
void DeviceInterfaceHandler::OnDeviceDiscovered(const DeviceInfo& device_info) {
const std::string& key_address = device_info.resolved_address.empty()
? device_info.advertised_address
: device_info.resolved_address;
Device* device = FindDevice(key_address);
if (device && device->paired.value()) {
device->advertised_address.SetValue(device_info.advertised_address);
ConnectInternal(key_address, /* connect_response */ nullptr,
/* connect_by_us */ true);
}
if (!device_info.has_active_discovery_client)
return;
device = AddOrGetDiscoveredDevice(key_address, device_info.advertised_address,
device_info.address_type);
device->rssi.SetValue(device_info.rssi);
UpdateDevice(device, device_info);
ExportOrUpdateDevice(device);
}
bool DeviceInterfaceHandler::RemoveDevice(const std::string& address,
std::string* dbus_error) {
Device* device = FindDevice(address);
// If the device does not exist, assume the operation has succeeded. This is
// because the dispatcher may forward RemoveDevice to both BlueZ and NewBlue
// without knowing who owns the device.
if (!device)
return true;
// For ongoing pairing, reply to D-Bus calls to prevent timeout.
if (ongoing_pairing_.address == address) {
ongoing_pairing_.pair_response->ReplyWithError(
FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorAuthenticationCanceled, "Pairing canceled");
if (ongoing_pairing_.cancel_pair_response) {
ongoing_pairing_.cancel_pair_response->SendRawResponse(
ongoing_pairing_.cancel_pair_response->CreateCustomResponse());
}
ongoing_pairing_.address.clear();
ongoing_pairing_.pair_response.reset();
ongoing_pairing_.cancel_pair_response.reset();
}
// Clear internal pairing state.
newblue_->CancelPair(address, device->is_random_address);
for (auto& observer : observers_)
observer.OnDeviceUnpaired(address);
// For ongoing connection, reply to D-Bus calls to prevent timeout.
auto connection_session = connection_sessions_.find(address);
if (connection_session != connection_sessions_.end()) {
// The ongoing connection would be canceled and the disconnection would be
// honored.
if (connection_session->second.disconnect_response ||
connection_session->second.disconnect_by_us) {
ConnectReply(address, true, "");
} else if (connection_session->second.connect_response ||
connection_session->second.connect_by_us) {
ConnectReply(address, false, bluetooth_device::kErrorFailed);
}
}
// For device which is either connected or to-be-connected, disconnect it.
if (base::ContainsKey(connections_, address) ||
base::ContainsKey(connection_attempts_, address)) {
DisconnectInternal(address, /* disconnect_response */ nullptr,
/* disconnect_by_us */ true);
}
dbus::ObjectPath device_path(ConvertDeviceAddressToObjectPath(address));
exported_object_manager_wrapper_->RemoveExportedInterface(
device_path, bluetooth_device::kBluetoothDeviceInterface);
discovered_devices_.erase(address);
return true;
}
void DeviceInterfaceHandler::AddDeviceObserver(
DeviceInterfaceHandler::DeviceObserver* observer) {
CHECK(observer);
observers_.AddObserver(observer);
}
void DeviceInterfaceHandler::RemoveDeviceObserver(
DeviceInterfaceHandler::DeviceObserver* observer) {
CHECK(observer);
observers_.RemoveObserver(observer);
}
std::string DeviceInterfaceHandler::GetAddressByConnectionId(
gatt_client_conn_t conn_id) {
if (conn_id == kInvalidGattConnectionId)
return "";
for (auto& connection : connections_) {
if (connection.second.conn_id == conn_id)
return connection.first;
}
return "";
}
gatt_client_conn_t DeviceInterfaceHandler::GetConnectionIdByAddress(
const std::string& address) {
const auto connection = connections_.find(address);
return (connection != connections_.end() ? connection->second.conn_id
: kInvalidGattConnectionId);
}
void DeviceInterfaceHandler::SetGattServicesResolved(
const std::string& device_address, bool resolved) {
Device* device = FindDevice(device_address);
CHECK(device != nullptr);
if (device->services_resolved.value() == resolved)
return;
device->services_resolved.SetValue(resolved);
ExportOrUpdateDevice(device);
}
Device* DeviceInterfaceHandler::AddOrGetDiscoveredDevice(
const std::string& key_address,
const std::string& adv_address,
uint8_t address_type) {
Device* device = FindDevice(key_address);
if (!device) {
discovered_devices_[key_address] = std::make_unique<Device>(key_address);
device = discovered_devices_[key_address].get();
}
device->is_random_address = (address_type == BT_ADDR_TYPE_LE_RANDOM);
device->advertised_address.SetValue(adv_address);
return device;
}
void DeviceInterfaceHandler::ExportOrUpdateDevice(Device* device) {
bool is_new_device = false;
dbus::ObjectPath device_path(
ConvertDeviceAddressToObjectPath(device->address.c_str()));
ExportedInterface* device_interface =
exported_object_manager_wrapper_->GetExportedInterface(
device_path, bluetooth_device::kBluetoothDeviceInterface);
// The first time a device of this address is discovered, create the D-Bus
// object representing that device.
if (device_interface == nullptr) {
VLOG(1) << base::StringPrintf(
"Discovered a new device with %s address %s, rssi %d",
device->is_random_address ? "random" : "public",
device->address.c_str(), device->rssi.value());
is_new_device = true;
exported_object_manager_wrapper_->AddExportedInterface(
device_path, bluetooth_device::kBluetoothDeviceInterface,
base::Bind(
&ExportedObjectManagerWrapper::SetupStandardPropertyHandlers));
device_interface = exported_object_manager_wrapper_->GetExportedInterface(
device_path, bluetooth_device::kBluetoothDeviceInterface);
AddDeviceMethodHandlers(device_interface);
device_interface
->EnsureExportedPropertyRegistered<dbus::ObjectPath>(
bluetooth_device::kAdapterProperty)
->SetValue(dbus::ObjectPath(kAdapterObjectPath));
} else {
VLOG(2) << base::StringPrintf(
"Discovered device with %s address %s, rssi %d",
device->is_random_address ? "random" : "public",
device->address.c_str(), device->rssi.value());
}
UpdateDeviceProperties(device_interface, *device, is_new_device);
// The property updates above have to be done before ExportAndBlock() to make
// sure that client receives the newly added object complete with its
// populated properties.
if (is_new_device)
device_interface->ExportAndBlock();
ClearPropertiesUpdated(device);
}
void DeviceInterfaceHandler::AddDeviceMethodHandlers(
ExportedInterface* device_interface) {
CHECK(device_interface);
device_interface->AddMethodHandlerWithMessage(
bluetooth_device::kPair,
base::Bind(&DeviceInterfaceHandler::HandlePair, base::Unretained(this)));
device_interface->AddMethodHandlerWithMessage(
bluetooth_device::kCancelPairing,
base::Bind(&DeviceInterfaceHandler::HandleCancelPairing,
base::Unretained(this)));
device_interface->AddMethodHandlerWithMessage(
bluetooth_device::kConnect,
base::Bind(&DeviceInterfaceHandler::HandleConnect,
base::Unretained(this)));
device_interface->AddMethodHandlerWithMessage(
bluetooth_device::kDisconnect,
base::Bind(&DeviceInterfaceHandler::HandleDisconnect,
base::Unretained(this)));
device_interface->AddMethodHandlerWithMessage(
bluetooth_device::kExecuteWrite,
base::Bind(&DeviceInterfaceHandler::HandleExecuteWrite,
base::Unretained(this)));
}
void DeviceInterfaceHandler::HandlePair(
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response,
dbus::Message* message) {
CHECK(message);
std::string device_address =
ConvertDeviceObjectPathToAddress(message->GetPath().value());
VLOG(1) << "Handling Pair for device " << device_address;
if (!ongoing_pairing_.address.empty()) {
LOG(WARNING) << "Pairing in progress with " << device_address;
response->ReplyWithError(FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorInProgress,
"Pairing in progress");
return;
}
ongoing_pairing_.address = device_address;
ongoing_pairing_.pair_response = std::move(response);
ongoing_pairing_.cancel_pair_response.reset();
Device* device = FindDevice(device_address);
if (!device || !newblue_->Pair(device->address, device->is_random_address,
DetermineSecurityRequirements(*device))) {
ongoing_pairing_.pair_response->ReplyWithError(
FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorFailed, "Unknown device");
// Clear the existing pairing to allow the new pairing request.
ongoing_pairing_.address.clear();
ongoing_pairing_.pair_response.reset();
}
}
void DeviceInterfaceHandler::HandleCancelPairing(
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response,
dbus::Message* message) {
CHECK(message);
std::string device_address =
ConvertDeviceObjectPathToAddress(message->GetPath().value());
VLOG(1) << "Handling CancelPairing for device " << device_address;
if (device_address.empty() || !ongoing_pairing_.pair_response) {
response->ReplyWithError(FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorDoesNotExist,
"No ongoing pairing");
return;
}
ongoing_pairing_.cancel_pair_response = std::move(response);
Device* device = FindDevice(device_address);
if (!device ||
!newblue_->CancelPair(device->address, device->is_random_address)) {
response->ReplyWithError(FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorFailed, "Unknown device");
ongoing_pairing_.cancel_pair_response.reset();
}
}
void DeviceInterfaceHandler::HandleConnect(
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response,
dbus::Message* message) {
CHECK(message);
std::string device_address =
ConvertDeviceObjectPathToAddress(message->GetPath().value());
VLOG(1) << "Handling Connect for device " << device_address;
if (base::ContainsKey(connection_sessions_, device_address)) {
response->ReplyWithError(FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorInProgress,
"Connection/disconnection in progress");
return;
}
ConnectInternal(device_address, std::move(response),
/* connect_by_us */ false);
}
void DeviceInterfaceHandler::HandleDisconnect(
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response,
dbus::Message* message) {
CHECK(message);
std::string device_address =
ConvertDeviceObjectPathToAddress(message->GetPath().value());
VLOG(1) << "Handling Disconnect for device " << device_address;
if (base::ContainsKey(connection_sessions_, device_address)) {
response->ReplyWithError(FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorInProgress,
"Connection/disconnection in progress");
return;
}
VLOG(1) << "Disconnect from device " << device_address << " with conn ID "
<< connections_[device_address].conn_id;
DisconnectInternal(device_address, std::move(response),
/* disconnect_by_us */ false);
}
void DeviceInterfaceHandler::HandleExecuteWrite(
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response,
dbus::Message* message) {
CHECK(message);
response->ReplyWithError(FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorFailed, "Not implemented");
}
void DeviceInterfaceHandler::ConnectInternal(
const std::string& device_address,
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> connect_response,
bool connect_by_us) {
CHECK((!connect_by_us && connect_response != nullptr) ||
(connect_by_us && connect_response == nullptr));
struct ConnectSession session;
session.connect_by_us = connect_by_us;
session.connect_response = std::move(connect_response);
connection_sessions_.emplace(device_address, std::move(session));
const Device* device = FindDevice(device_address);
if (!device) {
LOG(WARNING) << "device " << device_address << " not found";
ConnectReply(device_address, false, bluetooth_device::kErrorDoesNotExist);
return;
}
struct bt_addr address;
CHECK(ConvertToBtAddr(device->is_random_address, device_address, &address));
if (base::ContainsKey(connection_attempts_, device_address)) {
LOG(WARNING) << "Connection with device " << device_address
<< " in progress";
ConnectReply(device_address, false, bluetooth_device::kErrorInProgress);
return;
}
if (connections_.find(device_address) != connections_.end()) {
LOG(WARNING) << "Connection with device " << device_address
<< " already exists";
ConnectReply(device_address, false, bluetooth_device::kErrorAlreadyExists);
return;
}
gatt_client_conn_t conn_id = newblue_->GattClientConnect(
device->advertised_address.value(), device->is_random_address);
if (conn_id == kInvalidGattConnectionId) {
LOG(WARNING) << "Failed GATT client connect";
ConnectReply(device_address, false, bluetooth_device::kErrorFailed);
return;
}
connection_attempts_.emplace(device_address, conn_id);
}
void DeviceInterfaceHandler::DisconnectInternal(
const std::string& device_address,
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>>
disconnect_response,
bool disconnect_by_us) {
CHECK((!disconnect_by_us && disconnect_response != nullptr) ||
(disconnect_by_us && disconnect_response == nullptr));
struct ConnectSession session;
session.disconnect_by_us = disconnect_by_us;
session.disconnect_response = std::move(disconnect_response);
connection_sessions_.emplace(device_address, std::move(session));
if (!FindDevice(device_address)) {
LOG(WARNING) << "device " << device_address << " not found";
ConnectReply(device_address, false, bluetooth_device::kErrorDoesNotExist);
return;
}
gatt_client_conn_t id = kInvalidGattConnectionId;
auto attempt = connection_attempts_.find(device_address);
auto connection = connections_.find(device_address);
bool is_attempt = (attempt != connection_attempts_.end());
bool is_connected = (connection != connections_.end());
if (!is_attempt && !is_connected) {
VLOG(1) << "device " << device_address
<< " doesn't have an active connection nor an ongoing connection";
ConnectReply(device_address, false, bluetooth_device::kErrorNotConnected);
return;
}
// There can be either connection attempt or existing connection but not both.
CHECK(!(is_attempt && is_connected));
id = is_attempt ? attempt->second : connection->second.conn_id;
if (id == kInvalidGattConnectionId) {
LOG(WARNING) << "Invalid conn ID to disconnect from device "
<< device_address;
ConnectReply(device_address, false, bluetooth_device::kErrorFailed);
return;
}
if (connection->second.hid_id)
newblue_->libnewblue()->BtleHidDetach(connection->second.hid_id);
if (newblue_->GattClientDisconnect(id) != GattClientOperationStatus::OK) {
LOG(ERROR) << "Failed to disconnect from device " << device_address;
ConnectReply(device_address, false, bluetooth_device::kErrorFailed);
}
}
void DeviceInterfaceHandler::ConnectReply(const std::string& device_address,
bool success,
const std::string& dbus_error) {
// In cases where connection/disconnection events were issued by the remote
// device OR RemoveDevice() was called, there wouldn't be connection session.
auto iter = connection_sessions_.find(device_address);
if (iter == connection_sessions_.end()) {
VLOG(1) << "No connection session with device " << device_address
<< ", ignoring";
return;
}
if (iter->second.connect_by_us || iter->second.disconnect_by_us) {
connection_sessions_.erase(iter);
return;
}
if (!(iter->second.connect_response || iter->second.disconnect_response)) {
LOG(WARNING) << "Cannot find ongoing connection session or response for "
<< "device " << device_address;
return;
}
std::unique_ptr<brillo::dbus_utils::DBusMethodResponse<>> response =
iter->second.connect_response
? std::move(iter->second.connect_response)
: std::move(iter->second.disconnect_response);
if (success) {
response->SendRawResponse(response->CreateCustomResponse());
} else {
response->ReplyWithError(FROM_HERE, brillo::errors::dbus::kDomain,
dbus_error, "");
}
connection_sessions_.erase(iter);
}
void DeviceInterfaceHandler::OnGattClientConnectCallback(
gatt_client_conn_t conn_id, uint8_t status) {
Device* dev_to_notify = nullptr;
Device* dev_to_be_connected = nullptr;
auto temp_connections = connections_;
std::map<std::string, gatt_client_conn_t>::iterator iter =
connection_attempts_.end();
bool is_disconnected_by_newblue = true;
// See if there is a match in ongoing connection attempts.
for (auto it = connection_attempts_.begin(); it != connection_attempts_.end();
++it) {
if (it->second == conn_id) {
dev_to_be_connected = FindDevice(it->first);
dev_to_notify = dev_to_be_connected;
iter = it;
break;
}
}
if (iter != connection_attempts_.end())
connection_attempts_.erase(iter);
ConnectState state = static_cast<ConnectState>(status);
switch (state) {
case ConnectState::CONNECTED:
// Skip updating connection state if there is no device to associate with,
// e.g. the device could be removed if RemoveDevice() was called.
if (dev_to_be_connected == nullptr) {
VLOG(1) << "Ignoring connected event with conn ID " << conn_id;
return;
}
VLOG(1) << "Connection with ID " << conn_id << " established for device "
<< dev_to_be_connected->address;
struct Connection connection;
connection.conn_id = conn_id;
connection.hid_id = 0;
// TODO(crbug/991880): Wait for the link to be encrypted before starting
// any GATT operations for paired devices.
// Obtain a HID ID for a HID generic device.
if (IsHid(dev_to_be_connected->appearance.value()))
connection.hid_id = newblue_->libnewblue()->BtleHidAttach(
conn_id, dev_to_be_connected->name.value().c_str());
// Track the new connection and close the attempt.
connections_.emplace(dev_to_be_connected->address, connection);
ConnectReply(dev_to_be_connected->address, true, "");
for (auto& observer : observers_)
observer.OnGattConnected(dev_to_be_connected->address, conn_id);
break;
case ConnectState::ERROR:
VLOG(1) << "Unexpected GATT connection error";
FALLTHROUGH;
case ConnectState::DISCONNECTED:
is_disconnected_by_newblue = false;
FALLTHROUGH;
case ConnectState::DISCONNECTED_BY_US:
// Close the connection attempt when there is a match.
if (dev_to_be_connected) {
VLOG(1) << "Connection with ID " << conn_id
<< " failed to established for device "
<< dev_to_be_connected->address;
ConnectReply(dev_to_be_connected->address, false,
bluetooth_device::kErrorFailed);
break;
}
VLOG(1) << "GATT disconnection on conn id " << conn_id << " with status "
<< static_cast<int>(status);
// If there is no match on connection attempt, check if the update is for
// the existing connection and update the connection state accordingly.
for (auto& connection : temp_connections) {
if (connection.second.conn_id != conn_id)
continue;
connections_.erase(connection.first);
ConnectReply(connection.first, true, "");
dev_to_notify = FindDevice(connection.first);
for (auto& observer : observers_)
observer.OnGattDisconnected(connection.first, conn_id,
is_disconnected_by_newblue);
break;
}
break;
default:
VLOG(1) << "Unexpected GATT connection result, conn " << conn_id
<< " status = " << status;
return;
}
if (dev_to_notify) {
VLOG(1) << "Connection state changed to "
<< ConvertConnectStateToString(state) << " for device "
<< dev_to_notify->address;
SetDeviceConnected(dev_to_notify, state == ConnectState::CONNECTED);
ExportOrUpdateDevice(dev_to_notify);
}
}
void DeviceInterfaceHandler::SetDeviceConnected(Device* device,
bool is_connected) {
CHECK(device != nullptr);
device->connected.SetValue(is_connected);
}
void DeviceInterfaceHandler::SetDevicePaired(Device* device, bool is_paired) {
CHECK(device != nullptr);
device->paired.SetValue(is_paired);
for (auto& observer : observers_)
observer.OnDevicePaired(device->address);
}
Device* DeviceInterfaceHandler::FindDevice(const std::string& device_address) {
auto iter = discovered_devices_.find(device_address);
return iter != discovered_devices_.end() ? iter->second.get() : nullptr;
}
void DeviceInterfaceHandler::UpdateDeviceProperties(
ExportedInterface* interface, const Device& device, bool is_new_device) {
CHECK(interface);
// TODO(mcchou): Properties Modalias and MTU is not yet sorted out.
// The following properties are exported when |is_new_device| is true or when
// they are updated.
if (is_new_device) {
// Expose immutable and non-optional properties for the new device.
// The address property might be overwritten later by identity address.
interface->EnsureExportedPropertyRegistered<std::string>(
bluetooth_device::kAddressProperty)->SetValue(device.address);
interface->EnsureExportedPropertyRegistered<std::string>(
bluetooth_device::kTypeProperty)->SetValue(kDeviceTypeLe);
interface->EnsureExportedPropertyRegistered<bool>(
bluetooth_device::kLegacyPairingProperty)->SetValue(false);
}
ExportDBusProperty(interface, bluetooth_device::kPairedProperty,
device.paired, is_new_device);
ExportDBusProperty(interface, bluetooth_device::kConnectedProperty,
device.connected, is_new_device);
ExportDBusProperty(interface, bluetooth_device::kTrustedProperty,
device.trusted, is_new_device);
ExportDBusProperty(interface, bluetooth_device::kBlockedProperty,
device.blocked, is_new_device);
ExportDBusProperty(interface, bluetooth_device::kAliasProperty, device.alias,
is_new_device);
ExportDBusProperty(interface, bluetooth_device::kServicesResolvedProperty,
device.services_resolved, is_new_device);
ExportDBusProperty(interface, bluetooth_device::kAdvertisingDataFlagsProperty,
device.flags, is_new_device);
// Although RSSI is an optional device property in BlueZ, it is always
// provided by libnewblue, thus it is exposed by default.
ExportDBusProperty(interface, bluetooth_device::kRSSIProperty, device.rssi,
is_new_device);
// The following properties are exported only when they are updated.
ExportDBusProperty(interface, bluetooth_device::kUUIDsProperty,
device.service_uuids, &CanonicalizeUuids, false);
ExportDBusProperty(interface, bluetooth_device::kServiceDataProperty,
device.service_data, &CanonicalizeServiceData, false);
ExportDBusProperty(interface, bluetooth_device::kNameProperty, device.name,
false);
ExportDBusProperty(interface, bluetooth_device::kTxPowerProperty,
device.tx_power, false);
ExportDBusProperty(interface, bluetooth_device::kClassProperty,
device.eir_class, false);
ExportDBusProperty(interface, bluetooth_device::kAppearanceProperty,
device.appearance, false);
ExportDBusProperty(interface, bluetooth_device::kIconProperty, device.icon,
false);
ExportDBusProperty(interface, bluetooth_device::kManufacturerDataProperty,
device.manufacturer, false);
// Overwrite the address property with identity address.
ExportDBusProperty(interface, bluetooth_device::kAddressProperty,
device.identity_address, false);
}
void DeviceInterfaceHandler::UpdateDevice(Device* device,
const DeviceInfo& device_info) {
CHECK(device);
// Update device information only if received update from EIR
if (device_info.flags != kDefaultFlags)
device->flags.SetValue(device_info.flags);
if (!device_info.name.empty())
device->name.SetValue(device_info.name);
if (device_info.tx_power != kDefaultTxPower)
device->tx_power.SetValue(device_info.tx_power);
if (device_info.eir_class != kDefaultEirClass)
device->eir_class.SetValue(device_info.eir_class);
if (device_info.appearance != kDefaultAppearance) {
device->appearance.SetValue(device_info.appearance);
device->icon.SetValue(device_info.icon);
}
if (device_info.manufacturer !=
ParseDataIntoManufacturer(kDefaultManufacturerId,
std::vector<uint8_t>())) {
device->manufacturer.SetValue(device_info.manufacturer);
}
if (!device_info.service_uuids.empty())
device->service_uuids.SetValue(device_info.service_uuids);
if (!device_info.service_data.empty())
device->service_data.SetValue(device_info.service_data);
UpdateDeviceAlias(device);
}
void DeviceInterfaceHandler::ClearPropertiesUpdated(Device* device) {
device->paired.ClearUpdated();
device->connected.ClearUpdated();
device->trusted.ClearUpdated();
device->blocked.ClearUpdated();
device->services_resolved.ClearUpdated();
device->alias.ClearUpdated();
device->name.ClearUpdated();
device->tx_power.ClearUpdated();
device->rssi.ClearUpdated();
device->eir_class.ClearUpdated();
device->appearance.ClearUpdated();
device->icon.ClearUpdated();
device->flags.ClearUpdated();
device->service_uuids.ClearUpdated();
device->service_data.ClearUpdated();
device->manufacturer.ClearUpdated();
device->identity_address.ClearUpdated();
}
struct smPairSecurityRequirements
DeviceInterfaceHandler::DetermineSecurityRequirements(const Device& device) {
struct smPairSecurityRequirements security_requirement = {.bond = false,
.mitm = false};
bool security_determined = false;
// TODO(mcchou): Determine the security requirement for different type of
// devices based on appearance.
// These value are defined at https://www.bluetooth.com/specifications/gatt/
// viewer?attributeXmlFile=org.bluetooth.characteristic.gap.appearance.xml.
// The translated strings come from BlueZ.
switch ((device.appearance.value() & kAppearanceMask) >> 6) {
case 0x01: // phone
case 0x02: // computer
security_requirement.bond = true;
security_requirement.mitm = true;
security_determined = true;
break;
case 0x03: // watch
security_requirement.bond = true;
security_determined = true;
break;
case 0x0f: // HID Generic
switch (device.appearance.value() & 0x3f) {
case 0x01: // keyboard
case 0x05: // tablet
security_requirement.bond = true;
security_requirement.mitm = true;
security_determined = true;
break;
case 0x02: // mouse
case 0x03: // joystick
case 0x04: // gamepad
security_requirement.bond = true;
security_determined = true;
break;
case 0x08: // barcode-scanner
default:
break;
}
break;
case 0x00: // unknown
case 0x04: // clock
case 0x05: // video-display
case 0x06: // remote-control
case 0x07: // eye-glasses
case 0x08: // tag
case 0x09: // key-ring
case 0x0a: // multimedia-player
case 0x0b: // barcode-scanner
case 0x0c: // thermometer
case 0x0d: // heart-rate-sensor
case 0x0e: // blood-pressure
case 0x10: // glucose-meter
case 0x11: // running-walking-sensor
case 0x12: // cycling
case 0x31: // pulse-oximeter
case 0x32: // weight-scale
case 0x33: // personal-mobility-device
case 0x34: // continuous-glucose-monitor
case 0x35: // insulin-pump
case 0x36: // medication-delivery
case 0x51: // outdoor-sports-activity
default:
break;
}
if (!security_determined) {
security_requirement.bond = true;
LOG(WARNING) << base::StringPrintf(
"The default security level (bond:%s MITM:%s) will be "
"used in pairing with device with appearance 0x%4X",
security_requirement.bond ? "true" : "false",
security_requirement.mitm ? "true" : "false",
device.appearance.value());
}
return security_requirement;
}
void DeviceInterfaceHandler::OnPairStateChanged(
const std::string& address,
PairState pair_state,
PairError pair_error,
const std::string& identity_address) {
// The device D-Bus object may have already been unexported, e.g. pairing
// information is removed during the device removal.
Device* device = FindDevice(address);
if (!device)
return;
VLOG(1) << "Pairing state changed to " << ConvertPairStateToString(pair_state)
<< " for device " << device->address;
std::string dbus_error;
switch (pair_state) {
case PairState::CANCELED:
dbus_error = bluetooth_device::kErrorAuthenticationCanceled;
FALLTHROUGH;
case PairState::NOT_PAIRED:
SetDevicePaired(device, false);
break;
case PairState::FAILED:
// If a device is previously paired, security manager will throw a
// SM_PAIR_ERR_ALREADY_PAIRED error which should not set the pairing state
// to false.
SetDevicePaired(device, pair_error == PairError::ALREADY_PAIRED);
dbus_error = ConvertSmPairErrorToDbusError(pair_error);
break;
case PairState::PAIRED:
if (!identity_address.empty())
device->identity_address.SetValue(identity_address);
SetDevicePaired(device, true);
break;
case PairState::STARTED:
default:
// Do not change paired property.
break;
}
if (device->address != ongoing_pairing_.address) {
ExportOrUpdateDevice(device);
return;
}
CHECK(!ongoing_pairing_.address.empty());
CHECK(ongoing_pairing_.pair_response);
// Reply to the Pair/CancelPairing method calls according to the pairing
// state.
switch (pair_state) {
case PairState::NOT_PAIRED:
// Falling back to this state indicate an unknown error, so the cancel
// pairing request should fail as well.
ongoing_pairing_.pair_response->ReplyWithError(
FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorFailed, "Unknown");
if (ongoing_pairing_.cancel_pair_response) {
ongoing_pairing_.cancel_pair_response->ReplyWithError(
FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorDoesNotExist, "No ongoing pairing");
}
break;
case PairState::STARTED:
// For the start of the pairing, we wait for the result.
CHECK(!ongoing_pairing_.cancel_pair_response);
break;
case PairState::PAIRED:
ongoing_pairing_.pair_response->SendRawResponse(
ongoing_pairing_.pair_response->CreateCustomResponse());
if (ongoing_pairing_.cancel_pair_response) {
ongoing_pairing_.cancel_pair_response->ReplyWithError(
FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorFailed, "Unknown - pairing done");
}
break;
case PairState::CANCELED:
ongoing_pairing_.pair_response->ReplyWithError(
FROM_HERE, brillo::errors::dbus::kDomain, dbus_error,
"Pairing canceled");
if (ongoing_pairing_.cancel_pair_response) {
ongoing_pairing_.cancel_pair_response->SendRawResponse(
ongoing_pairing_.cancel_pair_response->CreateCustomResponse());
}
break;
case PairState::FAILED:
ongoing_pairing_.pair_response->ReplyWithError(
FROM_HERE, brillo::errors::dbus::kDomain, dbus_error,
"Pairing failed");
if (ongoing_pairing_.cancel_pair_response) {
ongoing_pairing_.cancel_pair_response->ReplyWithError(
FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorDoesNotExist, "No ongoing pairing");
}
break;
default:
LOG(WARNING) << "Unexpected pairing state change";
ongoing_pairing_.pair_response->ReplyWithError(
FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorFailed, "Unknown");
if (ongoing_pairing_.cancel_pair_response) {
ongoing_pairing_.cancel_pair_response->ReplyWithError(
FROM_HERE, brillo::errors::dbus::kDomain,
bluetooth_device::kErrorFailed, "Unknown");
}
break;
}
if (pair_state != PairState::STARTED) {
ongoing_pairing_.address.clear();
ongoing_pairing_.pair_response.reset();
}
ongoing_pairing_.cancel_pair_response.reset();
ExportOrUpdateDevice(device);
}
} // namespace bluetooth