| // Copyright 2016 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_remote_gatt_characteristic_mac.h" |
| |
| #include "base/bind.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/strings/sys_string_conversions.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "device/bluetooth/bluetooth_adapter_mac.h" |
| #include "device/bluetooth/bluetooth_adapter_mac_metrics.h" |
| #include "device/bluetooth/bluetooth_device_mac.h" |
| #include "device/bluetooth/bluetooth_gatt_notify_session.h" |
| #include "device/bluetooth/bluetooth_remote_gatt_descriptor_mac.h" |
| #include "device/bluetooth/bluetooth_remote_gatt_service_mac.h" |
| |
| namespace device { |
| |
| namespace { |
| |
| static BluetoothGattCharacteristic::Properties ConvertProperties( |
| CBCharacteristicProperties cb_property) { |
| BluetoothGattCharacteristic::Properties result = |
| BluetoothGattCharacteristic::PROPERTY_NONE; |
| if (cb_property & CBCharacteristicPropertyBroadcast) { |
| result |= BluetoothGattCharacteristic::PROPERTY_BROADCAST; |
| } |
| if (cb_property & CBCharacteristicPropertyRead) { |
| result |= BluetoothGattCharacteristic::PROPERTY_READ; |
| } |
| if (cb_property & CBCharacteristicPropertyWriteWithoutResponse) { |
| result |= BluetoothGattCharacteristic::PROPERTY_WRITE_WITHOUT_RESPONSE; |
| } |
| if (cb_property & CBCharacteristicPropertyWrite) { |
| result |= BluetoothGattCharacteristic::PROPERTY_WRITE; |
| } |
| if (cb_property & CBCharacteristicPropertyNotify) { |
| result |= BluetoothGattCharacteristic::PROPERTY_NOTIFY; |
| } |
| if (cb_property & CBCharacteristicPropertyIndicate) { |
| result |= BluetoothGattCharacteristic::PROPERTY_INDICATE; |
| } |
| if (cb_property & CBCharacteristicPropertyAuthenticatedSignedWrites) { |
| result |= BluetoothGattCharacteristic::PROPERTY_AUTHENTICATED_SIGNED_WRITES; |
| } |
| if (cb_property & CBCharacteristicPropertyExtendedProperties) { |
| result |= BluetoothGattCharacteristic::PROPERTY_EXTENDED_PROPERTIES; |
| } |
| if (cb_property & CBCharacteristicPropertyNotifyEncryptionRequired) { |
| // This property is used only in CBMutableCharacteristic |
| // (local characteristic). So this value should never appear for |
| // CBCharacteristic (remote characteristic). Apple is not able to send |
| // this value over BLE since it is not part of the spec. |
| DCHECK(false); |
| result |= BluetoothGattCharacteristic::PROPERTY_NOTIFY; |
| } |
| if (cb_property & CBCharacteristicPropertyIndicateEncryptionRequired) { |
| // This property is used only in CBMutableCharacteristic |
| // (local characteristic). So this value should never appear for |
| // CBCharacteristic (remote characteristic). Apple is not able to send |
| // this value over BLE since it is not part of the spec. |
| DCHECK(false); |
| result |= BluetoothGattCharacteristic::PROPERTY_INDICATE; |
| } |
| return result; |
| } |
| } // namespace |
| |
| BluetoothRemoteGattCharacteristicMac::BluetoothRemoteGattCharacteristicMac( |
| BluetoothRemoteGattServiceMac* gatt_service, |
| CBCharacteristic* cb_characteristic) |
| : is_discovery_complete_(false), |
| discovery_pending_count_(0), |
| gatt_service_(gatt_service), |
| cb_characteristic_(cb_characteristic, base::scoped_policy::RETAIN), |
| weak_ptr_factory_(this) { |
| uuid_ = |
| BluetoothAdapterMac::BluetoothUUIDWithCBUUID([cb_characteristic_ UUID]); |
| identifier_ = base::SysNSStringToUTF8( |
| [NSString stringWithFormat:@"%s-%p", uuid_.canonical_value().c_str(), |
| cb_characteristic_.get()]); |
| } |
| |
| BluetoothRemoteGattCharacteristicMac::~BluetoothRemoteGattCharacteristicMac() { |
| if (HasPendingRead()) { |
| std::pair<ValueCallback, ErrorCallback> callbacks; |
| callbacks.swap(read_characteristic_value_callbacks_); |
| callbacks.second.Run(BluetoothGattService::GATT_ERROR_FAILED); |
| } |
| if (HasPendingWrite()) { |
| std::pair<base::Closure, ErrorCallback> callbacks; |
| callbacks.swap(write_characteristic_value_callbacks_); |
| callbacks.second.Run(BluetoothGattService::GATT_ERROR_FAILED); |
| } |
| } |
| |
| std::string BluetoothRemoteGattCharacteristicMac::GetIdentifier() const { |
| return identifier_; |
| } |
| |
| BluetoothUUID BluetoothRemoteGattCharacteristicMac::GetUUID() const { |
| return uuid_; |
| } |
| |
| BluetoothGattCharacteristic::Properties |
| BluetoothRemoteGattCharacteristicMac::GetProperties() const { |
| return ConvertProperties([cb_characteristic_ properties]); |
| } |
| |
| BluetoothGattCharacteristic::Permissions |
| BluetoothRemoteGattCharacteristicMac::GetPermissions() const { |
| // Not supported for remote characteristics for CoreBluetooth. |
| NOTIMPLEMENTED(); |
| return PERMISSION_NONE; |
| } |
| |
| const std::vector<uint8_t>& BluetoothRemoteGattCharacteristicMac::GetValue() |
| const { |
| return value_; |
| } |
| |
| BluetoothRemoteGattService* BluetoothRemoteGattCharacteristicMac::GetService() |
| const { |
| return static_cast<BluetoothRemoteGattService*>(gatt_service_); |
| } |
| |
| bool BluetoothRemoteGattCharacteristicMac::IsNotifying() const { |
| return [cb_characteristic_ isNotifying] == YES; |
| } |
| |
| std::vector<BluetoothRemoteGattDescriptor*> |
| BluetoothRemoteGattCharacteristicMac::GetDescriptors() const { |
| std::vector<BluetoothRemoteGattDescriptor*> gatt_descriptors; |
| for (const auto& iter : gatt_descriptor_macs_) { |
| BluetoothRemoteGattDescriptor* gatt_descriptor = |
| static_cast<BluetoothRemoteGattDescriptor*>(iter.second.get()); |
| gatt_descriptors.push_back(gatt_descriptor); |
| } |
| return gatt_descriptors; |
| } |
| |
| BluetoothRemoteGattDescriptor* |
| BluetoothRemoteGattCharacteristicMac::GetDescriptor( |
| const std::string& identifier) const { |
| auto searched_pair = gatt_descriptor_macs_.find(identifier); |
| if (searched_pair == gatt_descriptor_macs_.end()) { |
| return nullptr; |
| } |
| return static_cast<BluetoothRemoteGattDescriptor*>( |
| searched_pair->second.get()); |
| } |
| |
| void BluetoothRemoteGattCharacteristicMac::ReadRemoteCharacteristic( |
| const ValueCallback& callback, |
| const ErrorCallback& error_callback) { |
| if (!IsReadable()) { |
| VLOG(1) << *this << ": Characteristic not readable."; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(error_callback, |
| BluetoothRemoteGattService::GATT_ERROR_NOT_SUPPORTED)); |
| return; |
| } |
| if (HasPendingRead() || HasPendingWrite()) { |
| VLOG(1) << *this << ": Characteristic read already in progress."; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(error_callback, |
| BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS)); |
| return; |
| } |
| VLOG(1) << *this << ": Read characteristic."; |
| read_characteristic_value_callbacks_ = |
| std::make_pair(callback, error_callback); |
| [GetCBPeripheral() readValueForCharacteristic:cb_characteristic_]; |
| } |
| |
| void BluetoothRemoteGattCharacteristicMac::WriteRemoteCharacteristic( |
| const std::vector<uint8_t>& value, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| if (!IsWritable()) { |
| VLOG(1) << *this << ": Characteristic not writable."; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(error_callback, |
| BluetoothRemoteGattService::GATT_ERROR_NOT_PERMITTED)); |
| return; |
| } |
| if (HasPendingRead() || HasPendingWrite()) { |
| VLOG(1) << *this << ": Characteristic write already in progress."; |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(error_callback, |
| BluetoothRemoteGattService::GATT_ERROR_IN_PROGRESS)); |
| return; |
| } |
| VLOG(1) << *this << ": Write characteristic."; |
| write_characteristic_value_callbacks_ = |
| std::make_pair(callback, error_callback); |
| base::scoped_nsobject<NSData> nsdata_value( |
| [[NSData alloc] initWithBytes:value.data() length:value.size()]); |
| CBCharacteristicWriteType write_type = GetCBWriteType(); |
| [GetCBPeripheral() writeValue:nsdata_value |
| forCharacteristic:cb_characteristic_ |
| type:write_type]; |
| if (write_type == CBCharacteristicWriteWithoutResponse) { |
| base::ThreadTaskRunnerHandle::Get()->PostTask( |
| FROM_HERE, |
| base::Bind(&BluetoothRemoteGattCharacteristicMac::DidWriteValue, |
| weak_ptr_factory_.GetWeakPtr(), nil)); |
| } |
| } |
| |
| void BluetoothRemoteGattCharacteristicMac::SubscribeToNotifications( |
| BluetoothRemoteGattDescriptor* ccc_descriptor, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| VLOG(1) << *this << ": Subscribe to characteristic."; |
| DCHECK(subscribe_to_notification_callbacks_.first.is_null()); |
| DCHECK(subscribe_to_notification_callbacks_.second.is_null()); |
| DCHECK(unsubscribe_from_notification_callbacks_.first.is_null()); |
| DCHECK(unsubscribe_from_notification_callbacks_.second.is_null()); |
| subscribe_to_notification_callbacks_ = |
| std::make_pair(callback, error_callback); |
| [GetCBPeripheral() setNotifyValue:YES forCharacteristic:cb_characteristic_]; |
| } |
| |
| void BluetoothRemoteGattCharacteristicMac::UnsubscribeFromNotifications( |
| BluetoothRemoteGattDescriptor* ccc_descriptor, |
| const base::Closure& callback, |
| const ErrorCallback& error_callback) { |
| VLOG(1) << *this << ": Unsubscribe from characteristic."; |
| DCHECK(subscribe_to_notification_callbacks_.first.is_null()); |
| DCHECK(subscribe_to_notification_callbacks_.second.is_null()); |
| DCHECK(unsubscribe_from_notification_callbacks_.first.is_null()); |
| DCHECK(unsubscribe_from_notification_callbacks_.second.is_null()); |
| unsubscribe_from_notification_callbacks_ = |
| std::make_pair(callback, error_callback); |
| [GetCBPeripheral() setNotifyValue:NO forCharacteristic:cb_characteristic_]; |
| } |
| |
| void BluetoothRemoteGattCharacteristicMac::DiscoverDescriptors() { |
| VLOG(1) << *this << ": Discover descriptors."; |
| is_discovery_complete_ = false; |
| ++discovery_pending_count_; |
| [GetCBPeripheral() discoverDescriptorsForCharacteristic:cb_characteristic_]; |
| } |
| |
| void BluetoothRemoteGattCharacteristicMac::DidUpdateValue(NSError* error) { |
| CHECK_EQ(GetCBPeripheral().state, CBPeripheralStateConnected); |
| // This method is called when the characteristic is read and when a |
| // notification is received. |
| RecordDidUpdateValueResult(error); |
| if (HasPendingRead()) { |
| std::pair<ValueCallback, ErrorCallback> callbacks; |
| callbacks.swap(read_characteristic_value_callbacks_); |
| if (error) { |
| BluetoothGattService::GattErrorCode error_code = |
| BluetoothDeviceMac::GetGattErrorCodeFromNSError(error); |
| VLOG(1) << *this |
| << ": Bluetooth error while reading for characteristic, domain: " |
| << BluetoothAdapterMac::String(error) |
| << ", error code: " << error_code; |
| callbacks.second.Run(error_code); |
| return; |
| } |
| VLOG(1) << *this << ": Read request arrived."; |
| UpdateValue(); |
| callbacks.first.Run(value_); |
| } else if (IsNotifying()) { |
| VLOG(1) << *this << ": Notification arrived."; |
| UpdateValue(); |
| gatt_service_->GetMacAdapter()->NotifyGattCharacteristicValueChanged( |
| this, value_); |
| } else { |
| // In case of buggy device, nothing should be done if receiving extra |
| // read confirmation. |
| VLOG(1) |
| << *this |
| << ": Characteristic value updated while having no pending read nor " |
| "notification."; |
| } |
| } |
| |
| void BluetoothRemoteGattCharacteristicMac::UpdateValue() { |
| NSData* nsdata_value = [cb_characteristic_ value]; |
| const uint8_t* buffer = static_cast<const uint8_t*>(nsdata_value.bytes); |
| value_.assign(buffer, buffer + nsdata_value.length); |
| } |
| |
| void BluetoothRemoteGattCharacteristicMac::DidWriteValue(NSError* error) { |
| RecordDidWriteValueResult(error); |
| // We could have called cancelPeripheralConnection, which causes |
| // [CBPeripheral state] to be CBPeripheralStateDisconnected, before or during |
| // a write without response callback so we flush all pending writes. |
| // TODO(crbug.com/726534): Remove once we can avoid calling DidWriteValue |
| // when we disconnect before or during a write without response call. |
| if (HasPendingWrite() && |
| GetCBPeripheral().state != CBPeripheralStateConnected) { |
| std::pair<base::Closure, ErrorCallback> callbacks; |
| callbacks.swap(write_characteristic_value_callbacks_); |
| callbacks.second.Run(BluetoothGattService::GATT_ERROR_FAILED); |
| return; |
| } |
| |
| CHECK_EQ(GetCBPeripheral().state, CBPeripheralStateConnected); |
| if (!HasPendingWrite()) { |
| // In case of buggy device, nothing should be done if receiving extra |
| // write confirmation. |
| VLOG(1) << *this |
| << ": Write notification while no write operation pending."; |
| return; |
| } |
| |
| std::pair<base::Closure, ErrorCallback> callbacks; |
| callbacks.swap(write_characteristic_value_callbacks_); |
| if (error) { |
| BluetoothGattService::GattErrorCode error_code = |
| BluetoothDeviceMac::GetGattErrorCodeFromNSError(error); |
| VLOG(1) << *this |
| << ": Bluetooth error while writing for characteristic, error: " |
| << BluetoothAdapterMac::String(error) |
| << ", error code: " << error_code; |
| callbacks.second.Run(error_code); |
| return; |
| } |
| VLOG(1) << *this << ": Write value succeeded."; |
| callbacks.first.Run(); |
| } |
| |
| void BluetoothRemoteGattCharacteristicMac::DidUpdateNotificationState( |
| NSError* error) { |
| PendingNotifyCallbacks reentrant_safe_callbacks; |
| if (!subscribe_to_notification_callbacks_.first.is_null()) { |
| DCHECK([GetCBCharacteristic() isNotifying] || error); |
| reentrant_safe_callbacks.swap(subscribe_to_notification_callbacks_); |
| } else if (!unsubscribe_from_notification_callbacks_.first.is_null()) { |
| DCHECK(![GetCBCharacteristic() isNotifying] || error); |
| reentrant_safe_callbacks.swap(unsubscribe_from_notification_callbacks_); |
| } else { |
| VLOG(1) << *this << ": No pending notification update for characteristic."; |
| return; |
| } |
| RecordDidUpdateNotificationStateResult(error); |
| if (error) { |
| BluetoothGattService::GattErrorCode error_code = |
| BluetoothDeviceMac::GetGattErrorCodeFromNSError(error); |
| VLOG(1) << *this |
| << ": Bluetooth error while modifying notification state for " |
| "characteristic, error: " |
| << BluetoothAdapterMac::String(error) |
| << ", error code: " << error_code; |
| reentrant_safe_callbacks.second.Run(error_code); |
| return; |
| } |
| reentrant_safe_callbacks.first.Run(); |
| } |
| |
| void BluetoothRemoteGattCharacteristicMac::DidDiscoverDescriptors() { |
| if (discovery_pending_count_ == 0) { |
| // This should never happen, just in case it happens with a device, this |
| // notification should be ignored. |
| VLOG(1) << *this |
| << ": Unmatch DiscoverDescriptors and DidDiscoverDescriptors."; |
| return; |
| } |
| VLOG(1) << *this << ": Did discover descriptors."; |
| --discovery_pending_count_; |
| std::unordered_set<std::string> descriptor_identifier_to_remove; |
| for (const auto& iter : gatt_descriptor_macs_) { |
| descriptor_identifier_to_remove.insert(iter.first); |
| } |
| |
| for (CBDescriptor* cb_descriptor in [cb_characteristic_ descriptors]) { |
| BluetoothRemoteGattDescriptorMac* gatt_descriptor_mac = |
| GetBluetoothRemoteGattDescriptorMac(cb_descriptor); |
| if (gatt_descriptor_mac) { |
| VLOG(1) << *gatt_descriptor_mac << ": Known descriptor."; |
| const std::string& identifier = gatt_descriptor_mac->GetIdentifier(); |
| descriptor_identifier_to_remove.erase(identifier); |
| continue; |
| } |
| gatt_descriptor_mac = |
| new BluetoothRemoteGattDescriptorMac(this, cb_descriptor); |
| const std::string& identifier = gatt_descriptor_mac->GetIdentifier(); |
| auto result_iter = gatt_descriptor_macs_.insert( |
| {identifier, base::WrapUnique(gatt_descriptor_mac)}); |
| DCHECK(result_iter.second); |
| GetMacAdapter()->NotifyGattDescriptorAdded(gatt_descriptor_mac); |
| VLOG(1) << *gatt_descriptor_mac << ": New descriptor."; |
| } |
| |
| for (const std::string& identifier : descriptor_identifier_to_remove) { |
| auto pair_to_remove = gatt_descriptor_macs_.find(identifier); |
| std::unique_ptr<BluetoothRemoteGattDescriptorMac> descriptor_to_remove; |
| VLOG(1) << *descriptor_to_remove << ": Removed descriptor."; |
| pair_to_remove->second.swap(descriptor_to_remove); |
| gatt_descriptor_macs_.erase(pair_to_remove); |
| GetMacAdapter()->NotifyGattDescriptorRemoved(descriptor_to_remove.get()); |
| } |
| is_discovery_complete_ = discovery_pending_count_ == 0; |
| } |
| |
| bool BluetoothRemoteGattCharacteristicMac::IsReadable() const { |
| return GetProperties() & BluetoothGattCharacteristic::PROPERTY_READ; |
| } |
| |
| bool BluetoothRemoteGattCharacteristicMac::IsWritable() const { |
| BluetoothGattCharacteristic::Properties properties = GetProperties(); |
| return (properties & BluetoothGattCharacteristic::PROPERTY_WRITE) || |
| (properties & PROPERTY_WRITE_WITHOUT_RESPONSE); |
| } |
| |
| bool BluetoothRemoteGattCharacteristicMac::SupportsNotificationsOrIndications() |
| const { |
| BluetoothGattCharacteristic::Properties properties = GetProperties(); |
| return (properties & PROPERTY_NOTIFY) || (properties & PROPERTY_INDICATE); |
| } |
| |
| CBCharacteristicWriteType BluetoothRemoteGattCharacteristicMac::GetCBWriteType() |
| const { |
| return (GetProperties() & BluetoothGattCharacteristic::PROPERTY_WRITE) |
| ? CBCharacteristicWriteWithResponse |
| : CBCharacteristicWriteWithoutResponse; |
| } |
| |
| CBCharacteristic* BluetoothRemoteGattCharacteristicMac::GetCBCharacteristic() |
| const { |
| return cb_characteristic_; |
| } |
| |
| BluetoothAdapterMac* BluetoothRemoteGattCharacteristicMac::GetMacAdapter() |
| const { |
| return gatt_service_->GetMacAdapter(); |
| } |
| |
| CBPeripheral* BluetoothRemoteGattCharacteristicMac::GetCBPeripheral() const { |
| return gatt_service_->GetCBPeripheral(); |
| } |
| |
| bool BluetoothRemoteGattCharacteristicMac::IsDiscoveryComplete() const { |
| return is_discovery_complete_; |
| } |
| |
| BluetoothRemoteGattDescriptorMac* |
| BluetoothRemoteGattCharacteristicMac::GetBluetoothRemoteGattDescriptorMac( |
| CBDescriptor* cb_descriptor) const { |
| auto found = std::find_if( |
| gatt_descriptor_macs_.begin(), gatt_descriptor_macs_.end(), |
| [cb_descriptor]( |
| const std::pair<const std::string, |
| std::unique_ptr<BluetoothRemoteGattDescriptorMac>>& |
| pair) { |
| return pair.second->GetCBDescriptor() == cb_descriptor; |
| }); |
| if (found == gatt_descriptor_macs_.end()) { |
| return nullptr; |
| } else { |
| return found->second.get(); |
| } |
| } |
| |
| DEVICE_BLUETOOTH_EXPORT std::ostream& operator<<( |
| std::ostream& out, |
| const BluetoothRemoteGattCharacteristicMac& characteristic) { |
| const BluetoothRemoteGattServiceMac* service_mac = |
| static_cast<const BluetoothRemoteGattServiceMac*>( |
| characteristic.GetService()); |
| return out << "<BluetoothRemoteGattCharacteristicMac " |
| << characteristic.GetUUID().canonical_value() << "/" |
| << &characteristic |
| << ", service: " << service_mac->GetUUID().canonical_value() << "/" |
| << service_mac << ">"; |
| } |
| } // namespace device. |