blob: a52d2bcf618f00e0e6c18768ed6259e400a02219 [file] [log] [blame]
// Copyright 2015 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_low_energy_device_mac.h"
#import <CoreFoundation/CoreFoundation.h>
#include <stddef.h>
#include "base/mac/mac_util.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/mac/sdk_forward_declarations.h"
#include "base/memory/ptr_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/sys_string_conversions.h"
#include "device/bluetooth/bluetooth_adapter_mac.h"
#include "device/bluetooth/bluetooth_device.h"
#include "device/bluetooth/bluetooth_low_energy_peripheral_delegate.h"
#include "device/bluetooth/bluetooth_remote_gatt_service_mac.h"
using device::BluetoothDevice;
using device::BluetoothLowEnergyDeviceMac;
BluetoothLowEnergyDeviceMac::BluetoothLowEnergyDeviceMac(
BluetoothAdapterMac* adapter,
CBPeripheral* peripheral)
: BluetoothDeviceMac(adapter),
peripheral_(peripheral, base::scoped_policy::RETAIN) {
DCHECK(BluetoothAdapterMac::IsLowEnergyAvailable());
DCHECK(peripheral_.get());
peripheral_delegate_.reset([[BluetoothLowEnergyPeripheralDelegate alloc]
initWithBluetoothLowEnergyDeviceMac:this]);
[peripheral_ setDelegate:peripheral_delegate_];
identifier_ = GetPeripheralIdentifier(peripheral);
hash_address_ = GetPeripheralHashAddress(peripheral);
UpdateTimestamp();
}
BluetoothLowEnergyDeviceMac::~BluetoothLowEnergyDeviceMac() {
if (IsGattConnected()) {
GetMacAdapter()->DisconnectGatt(this);
}
}
std::string BluetoothLowEnergyDeviceMac::GetIdentifier() const {
return identifier_;
}
uint32_t BluetoothLowEnergyDeviceMac::GetBluetoothClass() const {
return 0x1F00; // Unspecified Device Class
}
std::string BluetoothLowEnergyDeviceMac::GetAddress() const {
return hash_address_;
}
BluetoothDevice::VendorIDSource BluetoothLowEnergyDeviceMac::GetVendorIDSource()
const {
return VENDOR_ID_UNKNOWN;
}
uint16_t BluetoothLowEnergyDeviceMac::GetVendorID() const {
return 0;
}
uint16_t BluetoothLowEnergyDeviceMac::GetProductID() const {
return 0;
}
uint16_t BluetoothLowEnergyDeviceMac::GetDeviceID() const {
return 0;
}
uint16_t BluetoothLowEnergyDeviceMac::GetAppearance() const {
// TODO(crbug.com/588083): Implementing GetAppearance()
// on mac, win, and android platforms for chrome
NOTIMPLEMENTED();
return 0;
}
base::Optional<std::string> BluetoothLowEnergyDeviceMac::GetName() const {
if ([peripheral_ name])
return base::SysNSStringToUTF8([peripheral_ name]);
return base::nullopt;
}
bool BluetoothLowEnergyDeviceMac::IsPaired() const {
return false;
}
bool BluetoothLowEnergyDeviceMac::IsConnected() const {
return IsGattConnected();
}
bool BluetoothLowEnergyDeviceMac::IsGattConnected() const {
return ([peripheral_ state] == CBPeripheralStateConnected);
}
bool BluetoothLowEnergyDeviceMac::IsConnectable() const {
return connectable_;
}
bool BluetoothLowEnergyDeviceMac::IsConnecting() const {
return ([peripheral_ state] == CBPeripheralStateConnecting);
}
bool BluetoothLowEnergyDeviceMac::ExpectingPinCode() const {
return false;
}
bool BluetoothLowEnergyDeviceMac::ExpectingPasskey() const {
return false;
}
bool BluetoothLowEnergyDeviceMac::ExpectingConfirmation() const {
return false;
}
void BluetoothLowEnergyDeviceMac::GetConnectionInfo(
const ConnectionInfoCallback& callback) {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::Connect(
PairingDelegate* pairing_delegate,
const base::Closure& callback,
const ConnectErrorCallback& error_callback) {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::SetPinCode(const std::string& pincode) {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::SetPasskey(uint32_t passkey) {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::ConfirmPairing() {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::RejectPairing() {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::CancelPairing() {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::Disconnect(
const base::Closure& callback,
const ErrorCallback& error_callback) {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::Forget(const base::Closure& callback,
const ErrorCallback& error_callback) {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::ConnectToService(
const BluetoothUUID& uuid,
const ConnectToServiceCallback& callback,
const ConnectToServiceErrorCallback& error_callback) {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::ConnectToServiceInsecurely(
const device::BluetoothUUID& uuid,
const ConnectToServiceCallback& callback,
const ConnectToServiceErrorCallback& error_callback) {
NOTIMPLEMENTED();
}
void BluetoothLowEnergyDeviceMac::CreateGattConnectionImpl() {
if (!IsGattConnected()) {
GetMacAdapter()->CreateGattConnection(this);
}
}
void BluetoothLowEnergyDeviceMac::DisconnectGatt() {
GetMacAdapter()->DisconnectGatt(this);
}
void BluetoothLowEnergyDeviceMac::DidDiscoverPrimaryServices(NSError* error) {
if (error) {
// TODO(http://crbug.com/609320): Need to pass the error.
// TODO(http://crbug.com/609844): Decide what to do if discover failed
// a device services.
VLOG(1) << "Can't discover primary services: "
<< error.localizedDescription.UTF8String << " (" << error.domain
<< ": " << error.code << ")";
return;
}
VLOG(1) << "DidDiscoverPrimaryServices.";
if (!IsGattConnected()) {
// Don't create services if the device disconnected.
return;
}
for (CBService* cb_service in GetPeripheral().services) {
BluetoothRemoteGattServiceMac* gatt_service =
GetBluetoothRemoteGattService(cb_service);
if (!gatt_service) {
gatt_service = new BluetoothRemoteGattServiceMac(this, cb_service,
true /* is_primary */);
auto result_iter = gatt_services_.insert(std::make_pair(
gatt_service->GetIdentifier(), base::WrapUnique(gatt_service)));
DCHECK(result_iter.second);
adapter_->NotifyGattServiceAdded(gatt_service);
}
}
for (auto it = gatt_services_.begin(); it != gatt_services_.end(); ++it) {
device::BluetoothRemoteGattService* gatt_service = it->second.get();
device::BluetoothRemoteGattServiceMac* gatt_service_mac =
static_cast<BluetoothRemoteGattServiceMac*>(gatt_service);
gatt_service_mac->DiscoverCharacteristics();
}
}
void BluetoothLowEnergyDeviceMac::DidDiscoverCharacteristics(
CBService* cb_service,
NSError* error) {
if (error) {
// TODO(http://crbug.com/609320): Need to pass the error.
// TODO(http://crbug.com/609844): Decide what to do if discover failed
VLOG(1) << "Can't discover characteristics: "
<< error.localizedDescription.UTF8String << " (" << error.domain
<< ": " << error.code << ")";
return;
}
VLOG(1) << "DidDiscoverCharacteristics.";
if (!IsGattConnected()) {
// Don't create characteristics if the device disconnected.
return;
}
BluetoothRemoteGattServiceMac* gatt_service =
GetBluetoothRemoteGattService(cb_service);
DCHECK(gatt_service);
gatt_service->DidDiscoverCharacteristics();
SendNotificationIfDiscoveryComplete();
}
void BluetoothLowEnergyDeviceMac::DidModifyServices(
NSArray* invalidatedServices) {
VLOG(1) << "DidModifyServices: ";
for (CBService* cb_service in invalidatedServices) {
BluetoothRemoteGattServiceMac* gatt_service =
GetBluetoothRemoteGattService(cb_service);
DCHECK(gatt_service);
VLOG(1) << gatt_service->GetUUID().canonical_value();
std::unique_ptr<BluetoothRemoteGattService> scoped_service =
std::move(gatt_services_[gatt_service->GetIdentifier()]);
gatt_services_.erase(gatt_service->GetIdentifier());
adapter_->NotifyGattServiceRemoved(scoped_service.get());
}
device_uuids_.ClearServiceUUIDs();
SetGattServicesDiscoveryComplete(false);
adapter_->NotifyDeviceChanged(this);
[GetPeripheral() discoverServices:nil];
}
void BluetoothLowEnergyDeviceMac::DidUpdateValue(
CBCharacteristic* characteristic,
NSError* error) {
VLOG(1) << "DidUpdateValue.";
BluetoothRemoteGattServiceMac* gatt_service =
GetBluetoothRemoteGattService(characteristic.service);
DCHECK(gatt_service);
gatt_service->DidUpdateValue(characteristic, error);
}
void BluetoothLowEnergyDeviceMac::DidWriteValue(
CBCharacteristic* characteristic,
NSError* error) {
VLOG(1) << "DidWriteValue.";
BluetoothRemoteGattServiceMac* gatt_service =
GetBluetoothRemoteGattService(characteristic.service);
DCHECK(gatt_service);
gatt_service->DidWriteValue(characteristic, error);
}
void BluetoothLowEnergyDeviceMac::DidUpdateNotificationState(
CBCharacteristic* characteristic,
NSError* error) {
VLOG(1) << "DidUpdateNotificationState";
BluetoothRemoteGattServiceMac* gatt_service =
GetBluetoothRemoteGattService(characteristic.service);
DCHECK(gatt_service);
gatt_service->DidUpdateNotificationState(characteristic, error);
}
void BluetoothLowEnergyDeviceMac::DidDiscoverDescriptors(
CBCharacteristic* cb_characteristic,
NSError* error) {
if (error) {
// TODO(http://crbug.com/609320): Need to pass the error.
// TODO(http://crbug.com/609844): Decide what to do if discover failed
VLOG(1) << "Can't discover descriptors: "
<< error.localizedDescription.UTF8String << " (" << error.domain
<< ": " << error.code << ")";
return;
}
VLOG(1) << "DidDiscoverDescriptors.";
if (!IsGattConnected()) {
// Don't discover descriptors if the device disconnected.
return;
}
BluetoothRemoteGattServiceMac* gatt_service =
GetBluetoothRemoteGattService(cb_characteristic.service);
DCHECK(gatt_service);
gatt_service->DidDiscoverDescriptors(cb_characteristic);
SendNotificationIfDiscoveryComplete();
}
// static
std::string BluetoothLowEnergyDeviceMac::GetPeripheralIdentifier(
CBPeripheral* peripheral) {
DCHECK(BluetoothAdapterMac::IsLowEnergyAvailable());
NSUUID* uuid = [peripheral identifier];
NSString* uuidString = [uuid UUIDString];
return base::SysNSStringToUTF8(uuidString);
}
// static
std::string BluetoothLowEnergyDeviceMac::GetPeripheralHashAddress(
CBPeripheral* peripheral) {
const size_t kCanonicalAddressNumberOfBytes = 6;
char raw[kCanonicalAddressNumberOfBytes];
crypto::SHA256HashString(GetPeripheralIdentifier(peripheral), raw,
sizeof(raw));
std::string hash = base::HexEncode(raw, sizeof(raw));
return BluetoothDevice::CanonicalizeAddress(hash);
}
void BluetoothLowEnergyDeviceMac::SendNotificationIfDiscoveryComplete() {
// Notify when all services have been discovered.
bool discovery_complete =
std::find_if_not(
gatt_services_.begin(), gatt_services_.end(),
[](GattServiceMap::value_type& pair) {
BluetoothRemoteGattService* gatt_service = pair.second.get();
return static_cast<BluetoothRemoteGattServiceMac*>(gatt_service)
->IsDiscoveryComplete();
}) == gatt_services_.end();
if (discovery_complete) {
device_uuids_.ReplaceServiceUUIDs(gatt_services_);
SetGattServicesDiscoveryComplete(true);
adapter_->NotifyGattServicesDiscovered(this);
adapter_->NotifyDeviceChanged(this);
}
}
device::BluetoothAdapterMac* BluetoothLowEnergyDeviceMac::GetMacAdapter() {
return static_cast<BluetoothAdapterMac*>(this->adapter_);
}
CBPeripheral* BluetoothLowEnergyDeviceMac::GetPeripheral() {
return peripheral_;
}
device::BluetoothRemoteGattServiceMac*
BluetoothLowEnergyDeviceMac::GetBluetoothRemoteGattService(
CBService* cb_service) const {
for (auto it = gatt_services_.begin(); it != gatt_services_.end(); ++it) {
device::BluetoothRemoteGattService* gatt_service = it->second.get();
device::BluetoothRemoteGattServiceMac* gatt_service_mac =
static_cast<BluetoothRemoteGattServiceMac*>(gatt_service);
if (gatt_service_mac->GetService() == cb_service)
return gatt_service_mac;
}
return nullptr;
}
void BluetoothLowEnergyDeviceMac::DidDisconnectPeripheral(NSError* error) {
SetGattServicesDiscoveryComplete(false);
// Removing all services at once to ensure that calling GetGattService on
// removed service in GattServiceRemoved returns null.
GattServiceMap gatt_services_swapped;
gatt_services_swapped.swap(gatt_services_);
gatt_services_swapped.clear();
device_uuids_.ClearServiceUUIDs();
// There are two cases in which this function will be called:
// 1. When the connection to the device breaks (either because
// we closed it or the device closed it).
// 2. When we cancel a pending connection request.
if (create_gatt_connection_error_callbacks_.empty()) {
// If there are no pending callbacks then the connection broke (#1).
DidDisconnectGatt(true /* notifyDeviceChanged */);
return;
}
// Else we canceled the connection request (#2).
// TODO(http://crbug.com/585897): Need to pass the error.
DidFailToConnectGatt(BluetoothDevice::ConnectErrorCode::ERROR_FAILED);
}