diff --git a/internal/platform/implementation/linux/BUILD b/internal/platform/implementation/linux/BUILD index 6b67fd4c..fa2e32f0 100644 --- a/internal/platform/implementation/linux/BUILD +++ b/internal/platform/implementation/linux/BUILD @@ -56,6 +56,7 @@ cc_library( hdrs = [ "avahi.h", "ble_gatt_server.h", + "ble_gatt_client.h", "ble_medium.h", "ble_v2_medium.h", "ble_v2_server_socket.h", @@ -70,8 +71,10 @@ cc_library( "bluez.h", "bluez_advertisement_monitor.h", "bluez_advertisement_monitor_manager.h", + "bluez_gatt_characteristic_client.h", "bluez_gatt_characteristic_server.h", "bluez_gatt_manager.h", + "bluez_gatt_service_client.h", "bluez_gatt_service_server.h", "bluez_le_advertisement.h", "dbus.h", @@ -133,6 +136,7 @@ cc_library( name = "linux", srcs = [ "avahi.cc", + "ble_gatt_client.cc", "ble_gatt_server.cc", "ble_v2_medium.cc", "bluetooth_adapter.cc", @@ -145,6 +149,7 @@ cc_library( "bluetooth_pairing.cc", "bluez.cc", "bluez_advertisement_monitor.cc", + "bluez_gatt_characteristic_client.cc", "bluez_gatt_characteristic_server.cc", "bluez_gatt_service_server.cc", "bluez_le_advertisement.cc", diff --git a/internal/platform/implementation/linux/ble_gatt_client.cc b/internal/platform/implementation/linux/ble_gatt_client.cc new file mode 100644 index 00000000..936ec6c2 --- /dev/null +++ b/internal/platform/implementation/linux/ble_gatt_client.cc @@ -0,0 +1,428 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include +#include + +#include + +#include "absl/strings/substitute.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/cancellation_flag_listener.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_gatt_client.h" +#include "internal/platform/implementation/linux/bluez_gatt_characteristic_client.h" +#include "internal/platform/implementation/linux/bluez_gatt_service_client.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_client.h" +#include "internal/platform/implementation/linux/utils.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +bool GattClient::DiscoverServiceAndCharacteristics( + const Uuid &service_uuid, const std::vector &characteristic_uuids) { + return gatt_discovery_->DiscoverServiceAndCharacteristics( + peripheral_object_path_, service_uuid, characteristic_uuids, + discovery_cancel_); +} + +absl::optional GattClient::GetCharacteristic( + const Uuid &service_uuid, const Uuid &characteristic_uuid) { + auto chr_proxy = gatt_discovery_->GetCharacteristic( + peripheral_object_path_, service_uuid, characteristic_uuid); + if (chr_proxy == nullptr) return std::nullopt; + + api::ble_v2::GattCharacteristic chr; + chr.service_uuid = service_uuid; + chr.uuid = characteristic_uuid; + chr.property = api::ble_v2::GattCharacteristic::Property::kNone; + chr.permission = api::ble_v2::GattCharacteristic::Permission::kNone; + + std::vector flags; + try { + flags = chr_proxy->Flags(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(chr_proxy, "Flags", e); + return std::nullopt; + } + + for (const auto &flag : flags) { + if (flag == "read") { + chr.property |= api::ble_v2::GattCharacteristic::Property::kRead; + chr.permission |= api::ble_v2::GattCharacteristic::Permission::kRead; + } else if (flag == "write") { + chr.property |= api::ble_v2::GattCharacteristic::Property::kWrite; + chr.permission |= api::ble_v2::GattCharacteristic::Permission::kWrite; + } else if (flag == "notify") { + chr.property |= api::ble_v2::GattCharacteristic::Property::kNotify; + } else if (flag == "indicate") { + chr.property |= api::ble_v2::GattCharacteristic::Property::kIndicate; + } + } + + absl::MutexLock lock(&characteristics_mutex_); + characteristics_.emplace(chr, std::move(chr_proxy)); + + return chr; +} + +absl::optional GattClient::ReadCharacteristic( + const api::ble_v2::GattCharacteristic &characteristic) { + absl::ReaderMutexLock lock(&characteristics_mutex_); + if (characteristics_.count(characteristic) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Unknown characteristic '" + << absl::Substitute("$0", characteristic) << "'"; + return std::nullopt; + } + + return std::visit( + [](auto &&chr) { + try { + auto value_bytes = chr->ReadValue({}); + return std::optional{ + std::string(value_bytes.begin(), value_bytes.end())}; + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(chr, "ReadValue", e); + return std::optional(); + } + }, + characteristics_[characteristic]); +} + +bool GattClient::WriteCharacteristic( + const api::ble_v2::GattCharacteristic &characteristic, + absl::string_view value, WriteType type) { + absl::ReaderMutexLock lock(&characteristics_mutex_); + if (characteristics_.count(characteristic) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Unknown characteristic '" + << absl::Substitute("$0", characteristic) << "'"; + return false; + } + + return std::visit( + [value, type](auto &&chr) { + std::vector value_bytes(value.begin(), value.end()); + try { + chr->WriteValue( + value_bytes, + {{"type", + type == api::ble_v2::GattClient::WriteType::kWithResponse + ? "request" + : "command"}}); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(chr, "WriteValue", e); + return false; + } + }, + characteristics_[characteristic]); +} + +bool GattClient::SetCharacteristicSubscription( + const api::ble_v2::GattCharacteristic &characteristic, bool enable, + absl::AnyInvocable + on_characteristic_changed_cb) { + absl::MutexLock lock(&characteristics_mutex_); + if (characteristics_.count(characteristic) == 0) { + NEARBY_LOGS(ERROR) << __func__ << ": Unknown characteristic '" + << absl::Substitute("$0", characteristic) << "'"; + return false; + } + + if (enable) { + auto subbed_chr = gatt_discovery_->GetSubscribedCharacteristic( + peripheral_object_path_, characteristic.service_uuid, + characteristic.uuid, std::move(on_characteristic_changed_cb)); + if (subbed_chr == nullptr) return false; + try { + subbed_chr->StartNotify(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(subbed_chr, "StartNotify", e); + return false; + } + characteristics_[characteristic] = std::move(subbed_chr); + } else if (std::holds_alternative< + std::unique_ptr>( + characteristics_[characteristic])) { + auto chr = gatt_discovery_->GetCharacteristic(peripheral_object_path_, + characteristic.service_uuid, + characteristic.uuid); + if (chr == nullptr) return false; + try { + chr->StopNotify(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(chr, "StopNotify", e); + return false; + } + + characteristics_[characteristic] = std::move(chr); + } + return true; +} + +void GattClient::Disconnect() { + absl::MutexLock lock(&disconnected_callback_mutex_); + if (!discovery_cancel_.Cancelled()) { + discovery_cancel_.Cancel(); + if (*disconnected_callback_it_ != nullptr) (*disconnected_callback_it_)(); + gatt_discovery_->RemovePeripheralConnection(peripheral_object_path_, + disconnected_callback_it_); + } +} + +void BluezGattDiscovery::Shutdown() { + auto no_discovery = [&]() { + mutex_.AssertReaderHeld(); + return pending_discovery_ == 0; + }; + + mutex_.Lock(); + shutdown_ = true; + mutex_.Await(absl::Condition(&no_discovery)); + mutex_.Unlock(); +} + +bool BluezGattDiscovery::InitializeKnownServices() { + std::map>> + objects; + try { + objects = GetManagedObjects(); + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(this, "GetManagedObjects", e); + return false; + } + + absl::flat_hash_map cached_services; + absl::MutexLock lock(&mutex_); + auto chr_it = std::find_if( + objects.cbegin(), objects.cend(), + [](std::pair>> + object) { + return object.second.count( + org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME) == 1; + }); + + for (; chr_it != objects.cend(); chr_it++) { + const auto &[path, ifaces] = *chr_it; + const auto &properties = + ifaces.at(org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME); + auto maybe_props = characteristicProperties(path, properties); + if (!maybe_props.has_value()) continue; + auto [chr_uuid, service_uuid, device_path] = *maybe_props; + + discovered_characteristics_.emplace( + std::make_tuple(chr_uuid, service_uuid, device_path), path); + characteristics_properties_.emplace( + path, std::make_tuple(chr_uuid, service_uuid, device_path)); + } + + return true; +} + +BluezGattDiscovery::CallbackIter BluezGattDiscovery::AddPeripheralConnection( + const sdbus::ObjectPath &device_object_path, + absl::AnyInvocable disconnected_callback_) { + absl::MutexLock lock(&peripheral_disconnected_callbacks_mutex_); + if (peripheral_disconnected_callbacks_.count(device_object_path) == 0) + peripheral_disconnected_callbacks_.emplace( + device_object_path, std::list>{}); + auto &list = peripheral_disconnected_callbacks_[device_object_path]; + list.push_back(std::move(disconnected_callback_)); + return list.begin(); +} + +void BluezGattDiscovery::RemovePeripheralConnection( + const sdbus::ObjectPath &device_object_path, + BluezGattDiscovery::CallbackIter cb) { + absl::MutexLock lock(&peripheral_disconnected_callbacks_mutex_); + auto it = peripheral_disconnected_callbacks_.find(device_object_path); + if (it != peripheral_disconnected_callbacks_.end()) { + it->second.erase(cb); + if (it->second.empty()) + peripheral_disconnected_callbacks_.erase(device_object_path); + } +} + +bool BluezGattDiscovery::DiscoverServiceAndCharacteristics( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const std::vector &characteristic_uuids, CancellationFlag &cancel) { + CancellationFlagListener cancel_listen(&cancel, [&]() { + mutex_.Lock(); + mutex_.Unlock(); + }); + + auto discovered = [this, device_object_path, service_uuid, + characteristic_uuids, &cancel]() { + mutex_.AssertReaderHeld(); + return cancel.Cancelled() || + std::all_of( + characteristic_uuids.cbegin(), characteristic_uuids.cend(), + [this, service_uuid, device_object_path](auto &chr_uuid) { + mutex_.AssertReaderHeld(); + return discovered_characteristics_.count( + {service_uuid, chr_uuid, device_object_path}) == 1; + }); + }; + + absl::ReaderMutexLock lock(&mutex_, absl::Condition(&discovered)); + return !cancel.Cancelled(); +} + +std::unique_ptr +BluezGattDiscovery::GetCharacteristic( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const Uuid &characteristic_uuid) { + auto key = + std::make_tuple(service_uuid, characteristic_uuid, device_object_path); + + absl::ReaderMutexLock lock(&mutex_); + auto path_it = discovered_characteristics_.find(key); + if (path_it == discovered_characteristics_.end()) { + NEARBY_LOGS(ERROR) << __func__ << ": No characteristic known for device " + << device_object_path << " with service " + << std::string{service_uuid} << " and UUID " + << std::string{characteristic_uuid}; + return nullptr; + } + + return std::make_unique(system_bus_, + path_it->second); +} + +std::unique_ptr +BluezGattDiscovery::GetSubscribedCharacteristic( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const Uuid &characteristic_uuid, + absl::AnyInvocable + on_characteristic_changed_cb) { + auto key = + std::make_tuple(service_uuid, characteristic_uuid, device_object_path); + + absl::ReaderMutexLock lock(&mutex_); + auto path_it = discovered_characteristics_.find(key); + if (path_it == discovered_characteristics_.end()) { + NEARBY_LOGS(ERROR) << __func__ << ": No characteristic known for device " + << device_object_path << " with service " + << std::string{service_uuid} << " and UUID " + << std::string{characteristic_uuid}; + return nullptr; + } + + return std::make_unique( + system_bus_, device_object_path, std::move(on_characteristic_changed_cb)); +} + +std::optional> +BluezGattDiscovery::characteristicProperties( + const sdbus::ObjectPath &path, + const std::map &properties) { + mutex_.AssertHeld(); + + const std::string &chr_uuid_str = properties.at("UUID"); + auto chr_uuid = UuidFromString(chr_uuid_str); + if (!chr_uuid.has_value()) { + NEARBY_LOGS(ERROR) << ": Couldn't parse UUID '" << chr_uuid_str + << "' in characteristic " << path; + return std::nullopt; + } + + const sdbus::ObjectPath &service_path = properties.at("Service"); + if (cached_services_.count(service_path) == 0) { + cached_services_.emplace( + path, std::make_unique(system_bus_, path)); + } + + auto &service = cached_services_.at(service_path); + nearby::Uuid service_uuid; + try { + const std::string &service_uuid_str = service->UUID(); + auto service_uuid_maybe = UuidFromString(service_uuid_str); + if (!service_uuid_maybe.has_value()) { + NEARBY_LOGS(ERROR) << ": Couldn't parse UUID '" << service_uuid_str + << "' in service " << service_path; + return std::nullopt; + } + service_uuid = *service_uuid_maybe; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(service, "UUID", e); + return std::nullopt; + } + + sdbus::ObjectPath device_path; + try { + device_path = service->Device(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(service, "Device", e); + return std::nullopt; + } + + return std::make_tuple(*chr_uuid, service_uuid, device_path); +} + +void BluezGattDiscovery::onInterfacesAdded( + const sdbus::ObjectPath &objectPath, + const std::map> + &interfacesAndProperties) { + if (interfacesAndProperties.count( + org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME) == 0) + return; + + const auto &properties = interfacesAndProperties.at( + org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME); + + absl::MutexLock lock(&mutex_); + auto maybe_props = characteristicProperties(objectPath, properties); + if (!maybe_props.has_value()) return; + auto [chr_uuid, service_uuid, device_path] = *maybe_props; + + discovered_characteristics_.emplace( + std::make_tuple(chr_uuid, service_uuid, device_path), objectPath); + characteristics_properties_.emplace( + objectPath, std::make_tuple(chr_uuid, service_uuid, device_path)); +} + +void BluezGattDiscovery::onInterfacesRemoved( + const sdbus::ObjectPath &objectPath, + const std::vector &interfaces) { + auto begin = interfaces.cbegin(); + auto end = interfaces.cend(); + + auto service_it = + std::find(begin, end, org::bluez::GattService1_proxy::INTERFACE_NAME); + if (service_it != end) { + absl::MutexLock lock(&mutex_); + cached_services_.erase(objectPath); + return; + } + + auto chr_it = std::find( + begin, end, org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME); + if (chr_it != end) { + absl::MutexLock lock(&mutex_); + { + auto &props = characteristics_properties_.at(objectPath); + discovered_characteristics_.erase(props); + } + characteristics_properties_.erase(objectPath); + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/ble_gatt_client.h b/internal/platform/implementation/linux/ble_gatt_client.h new file mode 100644 index 00000000..f86f9970 --- /dev/null +++ b/internal/platform/implementation/linux/ble_gatt_client.h @@ -0,0 +1,210 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_API_BLE_GATT_CLIENT_H_ +#define PLATFORM_IMPL_LINUX_API_BLE_GATT_CLIENT_H_ + +#include + +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/cancellation_flag.h" +#include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/bluez_gatt_characteristic_client.h" +#include "internal/platform/implementation/linux/bluez_gatt_service_client.h" + +namespace nearby { +namespace linux { + +class BluezGattDiscovery final : public bluez::BluezObjectManager { + public: + explicit BluezGattDiscovery(std::shared_ptr system_bus) + : bluez::BluezObjectManager(*system_bus), + system_bus_(system_bus), + shutdown_(false), + pending_discovery_(0) {} + ~BluezGattDiscovery() override { Shutdown(); } + + bool InitializeKnownServices() ABSL_LOCKS_EXCLUDED(mutex_); + + using CallbackIter = typename std::list>::iterator; + CallbackIter AddPeripheralConnection( + const sdbus::ObjectPath &device_object_path, + absl::AnyInvocable disconnected_callback_) + ABSL_LOCKS_EXCLUDED(peripheral_disconnected_callbacks_mutex_); + void RemovePeripheralConnection(const sdbus::ObjectPath &device_object_path, + BluezGattDiscovery::CallbackIter cb) + ABSL_LOCKS_EXCLUDED(peripheral_disconnected_callbacks_mutex_); + + bool DiscoverServiceAndCharacteristics( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const std::vector &characteristic_uuids, CancellationFlag &cancel) + ABSL_LOCKS_EXCLUDED(mutex_); + std::unique_ptr GetCharacteristic( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const Uuid &characteristic_uuid) ABSL_LOCKS_EXCLUDED(mutex_); + std::unique_ptr GetSubscribedCharacteristic( + const sdbus::ObjectPath &device_object_path, const Uuid &service_uuid, + const Uuid &characteristic_uuid, + absl::AnyInvocable + on_characteristic_changed_cb) ABSL_LOCKS_EXCLUDED(mutex_); + + protected: + void onInterfacesAdded( + const sdbus::ObjectPath &objectPath, + const std::map> + &interfacesAndProperties) override ABSL_LOCKS_EXCLUDED(mutex_); + void onInterfacesRemoved(const sdbus::ObjectPath &objectPath, + const std::vector &interfaces) override + ABSL_LOCKS_EXCLUDED(mutex_); + + private: + std::optional> + characteristicProperties( + const sdbus::ObjectPath &path, + const std::map &properties) + ABSL_EXCLUSIVE_LOCKS_REQUIRED(mutex_); + + void Shutdown() ABSL_LOCKS_EXCLUDED(mutex_); + + std::shared_ptr system_bus_; + + absl::Mutex peripheral_disconnected_callbacks_mutex_; + absl::flat_hash_map>> + peripheral_disconnected_callbacks_ + ABSL_GUARDED_BY(peripheral_disconnected_callbacks_mutex_); + + absl::Mutex mutex_; + absl::flat_hash_map> + cached_services_ ABSL_GUARDED_BY(mutex_); + // Tuple order: service uuid, characteristic uuid, device object path + absl::flat_hash_map, + sdbus::ObjectPath> + discovered_characteristics_ ABSL_GUARDED_BY(mutex_); + absl::flat_hash_map> + characteristics_properties_ ABSL_GUARDED_BY(mutex_); + bool shutdown_ ABSL_GUARDED_BY(mutex_); + std::size_t pending_discovery_ ABSL_GUARDED_BY(mutex_); +}; + +// https://developer.android.com/reference/android/bluetooth/BluetoothGatt +// +// Representation of a client GATT connection to a remote GATT server. +class GattClient : public api::ble_v2::GattClient { + public: + GattClient(const GattClient &) = delete; + GattClient(GattClient &&) = delete; + GattClient &operator=(const GattClient &) = delete; + GattClient &operator=(GattClient &&) = delete; + + explicit GattClient(std::shared_ptr system_bus, + const sdbus::ObjectPath &peripheral_object_path, + std::shared_ptr gatt_discovery, + absl::AnyInvocable disconnected_callback) + : system_bus_(std::move(system_bus)), + peripheral_object_path_(peripheral_object_path), + gatt_discovery_(std::move(gatt_discovery)), + discovery_cancel_(false) { + disconnected_callback_it_ = gatt_discovery->AddPeripheralConnection( + peripheral_object_path_, std::move(disconnected_callback)); + } + ~GattClient() override { + absl::MutexLock lock(&disconnected_callback_mutex_); + if (!discovery_cancel_.Cancelled()) { + discovery_cancel_.Cancel(); + gatt_discovery_->RemovePeripheralConnection(peripheral_object_path_, + disconnected_callback_it_); + } + } + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#discoverServices() + // + // Discovers available service and characteristics on this connection. + // Returns whether or not discovery finished successfully. + // + // This function should block until discovery has finished. + bool DiscoverServiceAndCharacteristics( + const Uuid &service_uuid, + const std::vector &characteristic_uuids) override; + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#getService(java.util.UUID) + // https://developer.android.com/reference/android/bluetooth/BluetoothGattService.html#getCharacteristic(java.util.UUID) + // + // Retrieves a GATT characteristic. On error, does not return a value. + // + // DiscoverServiceAndCharacteristics() should be called before this method to + // fetch all available services and characteristics first. + // + // It is okay for duplicate services to exist, as long as the specified + // characteristic UUID is unique among all services of the same UUID. + // NOLINTNEXTLINE(google3-legacy-absl-backports) + absl::optional GetCharacteristic( + const Uuid &service_uuid, const Uuid &characteristic_uuid) override + ABSL_LOCKS_EXCLUDED(characteristics_mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#readCharacteristic(android.bluetooth.BluetoothGattCharacteristic) + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#getValue() + // NOLINTNEXTLINE(google3-legacy-absl-backports) + absl::optional ReadCharacteristic( + const api::ble_v2::GattCharacteristic &characteristic) override + ABSL_LOCKS_EXCLUDED(characteristics_mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothGattCharacteristic.html#setValue(byte[]) + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#writeCharacteristic(android.bluetooth.BluetoothGattCharacteristic) + // + // Sends a remote characteristic write request to the server and returns + // whether or not it was successful. + bool WriteCharacteristic( + const api::ble_v2::GattCharacteristic &characteristic, + absl::string_view value, WriteType type) override + ABSL_LOCKS_EXCLUDED(characteristics_mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#setCharacteristicNotification(android.bluetooth.BluetoothGattCharacteristic,%20boolean) + // + // Enable or disable notifications/indications for a given characteristic. + bool SetCharacteristicSubscription( + const api::ble_v2::GattCharacteristic &characteristic, bool enable, + absl::AnyInvocable + on_characteristic_changed_cb) override + ABSL_LOCKS_EXCLUDED(characteristics_mutex_); + + // https://developer.android.com/reference/android/bluetooth/BluetoothGatt.html#disconnect() + void Disconnect() override; + + private: + std::shared_ptr system_bus_; + sdbus::ObjectPath peripheral_object_path_; + std::shared_ptr gatt_discovery_; + + absl::Mutex disconnected_callback_mutex_; + BluezGattDiscovery::CallbackIter disconnected_callback_it_ + ABSL_GUARDED_BY(disconnected_callback_mutex_); + CancellationFlag discovery_cancel_; + + using CharacteristicProxy = + std::variant, + std::unique_ptr>; + absl::Mutex characteristics_mutex_; + absl::flat_hash_map + characteristics_ ABSL_GUARDED_BY(characteristics_mutex_); +}; + +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/ble_v2_medium.cc b/internal/platform/implementation/linux/ble_v2_medium.cc index d51bc4cb..677aada5 100644 --- a/internal/platform/implementation/linux/ble_v2_medium.cc +++ b/internal/platform/implementation/linux/ble_v2_medium.cc @@ -18,11 +18,12 @@ #include #include -#include #include "absl/synchronization/mutex.h" #include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_gatt_client.h" #include "internal/platform/implementation/linux/ble_gatt_server.h" #include "internal/platform/implementation/linux/ble_v2_medium.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" #include "internal/platform/implementation/linux/bluetooth_devices.h" #include "internal/platform/implementation/linux/bluez_advertisement_monitor.h" #include "internal/platform/implementation/linux/bluez_advertisement_monitor_manager.h" @@ -38,6 +39,7 @@ BleV2Medium::BleV2Medium(BluetoothAdapter &adapter) adapter_(adapter), devices_(std::make_unique( *system_bus_, adapter_.GetObjectPath(), observers_)), + gatt_discovery_(std::make_shared(system_bus_)), root_object_manager_(std::make_unique(*system_bus_)), adv_monitor_manager_( bluez::AdvertisementMonitorManager:: @@ -56,6 +58,10 @@ BleV2Medium::BleV2Medium(BluetoothAdapter &adapter) DBUS_LOG_METHOD_CALL_ERROR(adv_monitor_manager_, "RegisterMonitor", e); } } + if (gatt_discovery_->InitializeKnownServices()) { + NEARBY_LOGS(ERROR) << __func__ + << ": Could not initialize known GATT services"; + } } bool BleV2Medium::StartAdvertising( @@ -205,6 +211,17 @@ std::unique_ptr BleV2Medium::StartGattServer( std::move(callback)); } +std::unique_ptr BleV2Medium::ConnectToGattServer( + api::ble_v2::BlePeripheral &peripheral, + api::ble_v2::TxPowerLevel tx_power_level, + api::ble_v2::ClientGattConnectionCallback callback) { + auto &device = dynamic_cast(peripheral); + + return std::make_unique(system_bus_, device.getObjectPath(), + gatt_discovery_, + std::move(callback.disconnected_cb)); +} + bool BleV2Medium::StartLEDiscovery() { std::map filter; filter["Transport"] = "auto"; diff --git a/internal/platform/implementation/linux/ble_v2_medium.h b/internal/platform/implementation/linux/ble_v2_medium.h index 8b59c0da..ed1238a7 100644 --- a/internal/platform/implementation/linux/ble_v2_medium.h +++ b/internal/platform/implementation/linux/ble_v2_medium.h @@ -23,6 +23,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/synchronization/mutex.h" #include "internal/platform/implementation/ble_v2.h" +#include "internal/platform/implementation/linux/ble_gatt_client.h" #include "internal/platform/implementation/linux/ble_v2_server_socket.h" #include "internal/platform/implementation/linux/bluetooth_adapter.h" #include "internal/platform/implementation/linux/bluetooth_devices.h" @@ -70,9 +71,7 @@ class BleV2Medium final : public api::ble_v2::BleMedium { std::unique_ptr ConnectToGattServer( api::ble_v2::BlePeripheral &peripheral, api::ble_v2::TxPowerLevel tx_power_level, - api::ble_v2::ClientGattConnectionCallback callback) override { - return nullptr; - } + api::ble_v2::ClientGattConnectionCallback callback) override; std::unique_ptr OpenServerSocket( const std::string &service_id) override { @@ -117,6 +116,7 @@ class BleV2Medium final : public api::ble_v2::BleMedium { BluetoothAdapter adapter_; ObserverList observers_ = {}; std::shared_ptr devices_; + std::shared_ptr gatt_discovery_; std::unique_ptr root_object_manager_; std::unique_ptr adv_monitor_manager_; diff --git a/internal/platform/implementation/linux/bluez.h b/internal/platform/implementation/linux/bluez.h index 55a3d02f..2d595a67 100644 --- a/internal/platform/implementation/linux/bluez.h +++ b/internal/platform/implementation/linux/bluez.h @@ -63,18 +63,18 @@ int16_t TxPowerLevelDbm(api::ble_v2::TxPowerLevel level); class BluezObjectManager : public sdbus::ProxyInterfaces { public: - BluezObjectManager(sdbus::IConnection &system_bus) + explicit BluezObjectManager(sdbus::IConnection &system_bus) : ProxyInterfaces(system_bus, "org.bluez", "/") { registerProxy(); } virtual ~BluezObjectManager() { unregisterProxy(); } protected: - void onInterfacesAdded( + void onInterfacesAdded( const sdbus::ObjectPath &objectPath, const std::map> &interfacesAndProperties) override {} - void onInterfacesRemoved( + void onInterfacesRemoved( const sdbus::ObjectPath &objectPath, const std::vector &interfaces) override {} }; diff --git a/internal/platform/implementation/linux/bluez_gatt_characteristic_client.cc b/internal/platform/implementation/linux/bluez_gatt_characteristic_client.cc new file mode 100644 index 00000000..19e4b7c5 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_characteristic_client.cc @@ -0,0 +1,41 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +#include +#include + +#include "internal/platform/implementation/linux/bluez_gatt_characteristic_client.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h" +namespace nearby { +namespace linux { +namespace bluez { +void SubscribedGattCharacteristicClient::onPropertiesChanged( + const std::string& interfaceName, + const std::map& changedProperties, + const std::vector& invalidatedProperties) { + if (interfaceName != org::bluez::GattCharacteristic1_proxy::INTERFACE_NAME) + return; + + if (changedProperties.count("Value") == 1) { + std::vector value_bytes = changedProperties.at("Value"); + if (notify_callback_ != nullptr) { + auto value = std::string(value_bytes.cbegin(), value_bytes.cend()); + notify_callback_(value); + } + } +} +} // namespace bluez +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluez_gatt_characteristic_client.h b/internal/platform/implementation/linux/bluez_gatt_characteristic_client.h new file mode 100644 index 00000000..38b731cb --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_characteristic_client.h @@ -0,0 +1,71 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUEZ_GATT_CHARACTERISTIC_CLIENT_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_GATT_CHARACTERISTIC_CLIENT_H_ + +#include +#include +#include + +#include "absl/functional/any_invocable.h" +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_characteristic_client.h" +namespace nearby { +namespace linux { +namespace bluez { +class GattCharacteristicClient + : public sdbus::ProxyInterfaces { + public: + GattCharacteristicClient(std::shared_ptr system_bus, + sdbus::ObjectPath path) + : ProxyInterfaces(*system_bus, "org.bluez", std::move(path)), + system_bus_(std::move(system_bus)) { + registerProxy(); + } + virtual ~GattCharacteristicClient() { unregisterProxy(); } + + protected: + void onPropertiesChanged( + const std::string& interfaceName, + const std::map& changedProperties, + const std::vector& invalidatedProperties) override {} + + std::shared_ptr system_bus_; +}; + +class SubscribedGattCharacteristicClient : public GattCharacteristicClient { + public: + SubscribedGattCharacteristicClient( + std::shared_ptr system_bus, sdbus::ObjectPath path, + absl::AnyInvocable notify_callback) + : GattCharacteristicClient(std::move(system_bus), std::move(path)), + notify_callback_(std::move(notify_callback)) {} + + protected: + void onPropertiesChanged( + const std::string& interfaceName, + const std::map& changedProperties, + const std::vector& invalidatedProperties) override; + + private: + absl::AnyInvocable notify_callback_; +}; +} // namespace bluez +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluez_gatt_service_client.h b/internal/platform/implementation/linux/bluez_gatt_service_client.h new file mode 100644 index 00000000..ac1d99d4 --- /dev/null +++ b/internal/platform/implementation/linux/bluez_gatt_service_client.h @@ -0,0 +1,40 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#ifndef PLATFORM_IMPL_LINUX_BLUEZ_GATT_SERVICE_CLIENT_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_GATT_SERVICE_CLIENT_H_ +#include + +#include +#include + +#include "internal/platform/implementation/linux/generated/dbus/bluez/gatt_service_client.h" + +namespace nearby { +namespace linux { +class GattServiceClient final + : public sdbus::ProxyInterfaces { + public: + GattServiceClient(std::shared_ptr system_bus, + sdbus::ObjectPath service_object_path) + : ProxyInterfaces(*system_bus, "org.bluez", + std::move(service_object_path)) { + registerProxy(); + } + ~GattServiceClient() { unregisterProxy(); } +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/gatt_service_client.h b/internal/platform/implementation/linux/gatt_service_client.h new file mode 100644 index 00000000..e69de29b