diff --git a/internal/platform/implementation/linux/BUILD b/internal/platform/implementation/linux/BUILD index 4a70ca72..bbb67a2f 100644 --- a/internal/platform/implementation/linux/BUILD +++ b/internal/platform/implementation/linux/BUILD @@ -25,7 +25,7 @@ cc_library( "atomic_boolean.h", "atomic_reference.h", "atomic_uint32.h", - # "bluetooth_adapter.h", + "bluetooth_adapter.h", "condition_variable.h", "device_info.h", "executor.h", @@ -58,7 +58,7 @@ cc_library( "avahi.h", # "ble_medium.h", # "ble_v2_medium.h", - # "bluetooth_adapter.h", + "bluetooth_adapter.h", # "bluetooth_bluez_profile.h", # "bluetooth_classic_device.h", # "bluetooth_classic_medium.h", @@ -66,7 +66,7 @@ cc_library( # "bluetooth_classic_socket.h", # "bluetooth_devices.h", # "bluetooth_pairing.h", - # "bluez.h", + "bluez.h", "dbus.h", "network_manager.h", "network_manager_access_point.h", @@ -127,7 +127,7 @@ cc_library( name = "linux", srcs = [ "avahi.cc", - # "bluetooth_adapter.cc", + "bluetooth_adapter.cc", # "bluetooth_bluez_profile.cc", # "bluetooth_classic_device.cc", # "bluetooth_classic_medium.cc", @@ -135,7 +135,7 @@ cc_library( # "bluetooth_classic_socket.cc", # "bluetooth_devices.cc", # "bluetooth_pairing.cc", - # "bluez.cc", + "bluez.cc", "dbus.cc", "executor.cc", "network_manager.cc", diff --git a/internal/platform/implementation/linux/ble.h b/internal/platform/implementation/linux/ble.h new file mode 100644 index 00000000..e69de29b diff --git a/internal/platform/implementation/linux/ble_v2.h b/internal/platform/implementation/linux/ble_v2.h new file mode 100644 index 00000000..e69de29b diff --git a/internal/platform/implementation/linux/bluetooth_adapter.cc b/internal/platform/implementation/linux/bluetooth_adapter.cc new file mode 100644 index 00000000..99f1bb0f --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_adapter.cc @@ -0,0 +1,121 @@ +// 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 "internal/platform/implementation/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/adapter_client.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +bool BluetoothAdapter::SetStatus(Status status) { + try { + bool val = status == api::BluetoothAdapter::Status::kEnabled; + bluez_adapter_->Powered(val); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_SET_ERROR(bluez_adapter_, "Powered", e); + return false; + } +} + +bool BluetoothAdapter::IsEnabled() const { + try { + return bluez_adapter_->Powered(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(bluez_adapter_, "Powered", e); + return false; + } +} + +BluetoothAdapter::ScanMode BluetoothAdapter::GetScanMode() const { + bool powered = IsEnabled(); + if (!powered) { + return ScanMode::kNone; + } + + try { + bool discoverable = bluez_adapter_->Discoverable(); + return discoverable ? ScanMode::kConnectableDiscoverable + : ScanMode::kConnectable; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(bluez_adapter_, "Discoverable", e); + return ScanMode::kUnknown; + } +} + +bool BluetoothAdapter::SetScanMode(ScanMode scan_mode) { + switch (scan_mode) { + case ScanMode::kConnectable: + return SetStatus(Status::kEnabled); + case ScanMode::kConnectableDiscoverable: { + if (!SetStatus(Status::kEnabled)) { + return false; + } + + try { + bluez_adapter_->Discoverable(true); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_SET_ERROR(bluez_adapter_, "Discoverable", e); + return false; + } + + return true; + } + case ScanMode::kNone: + return SetStatus(Status::kDisabled); + default: + return false; + } +} + +std::string BluetoothAdapter::GetName() const { + try { + return bluez_adapter_->Alias(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(bluez_adapter_, "Alias", e); + return {}; + } +} + +bool BluetoothAdapter::SetName(absl::string_view name, bool /*persist*/) { + return SetName(name); +} + +bool BluetoothAdapter::SetName(absl::string_view name) { + try { + bluez_adapter_->Alias(std::string(name)); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_SET_ERROR(bluez_adapter_, "Alias", e); + return false; + } +} + +std::string BluetoothAdapter::GetMacAddress() const { + try { + return bluez_adapter_->Address(); + } catch (const sdbus::Error &e) { + DBUS_LOG_PROPERTY_GET_ERROR(bluez_adapter_, "Address", e); + return {}; + } +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_adapter.h b/internal/platform/implementation/linux/bluetooth_adapter.h new file mode 100644 index 00000000..810cae8f --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_adapter.h @@ -0,0 +1,84 @@ +// 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_BLUETOOTH_ADAPTER_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_ +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/bluetooth_adapter.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/implementation/linux/dbus.h" +#include "internal/platform/implementation/linux/generated/dbus/bluez/adapter_client.h" + +namespace nearby { +namespace linux { +class BluezAdapter : public sdbus::ProxyInterfaces { + public: + BluezAdapter(sdbus::IConnection &system_bus, + const sdbus::ObjectPath &adapter_object_path) + : ProxyInterfaces(system_bus, bluez::SERVICE_DEST, adapter_object_path) { + registerProxy(); + } + ~BluezAdapter() { unregisterProxy(); } +}; + +class BluetoothAdapter : public api::BluetoothAdapter { + public: + BluetoothAdapter(std::shared_ptr system_bus, + const sdbus::ObjectPath &adapter_object_path) + : system_bus_(std::move(system_bus)), + bluez_adapter_(std::make_shared(*system_bus_, + adapter_object_path)) {} + + ~BluetoothAdapter() override = default; + + bool SetStatus(Status status) override; + bool IsEnabled() const override; + + ScanMode GetScanMode() const override; + + bool SetScanMode(ScanMode scan_mode) override; + std::string GetName() const override; + + bool SetName(absl::string_view name) override; + bool SetName(absl::string_view name, bool persist) override; + std::string GetMacAddress() const override; + + bool RemoveDeviceByObjectPath(const sdbus::ObjectPath &device_object_path) { + try { + bluez_adapter_->RemoveDevice(device_object_path); + return true; + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(bluez_adapter_, "RemoveDevice", e); + return false; + } + } + + sdbus::ObjectPath GetObjectPath() const { + return bluez_adapter_->getObjectPath(); + } + + BluezAdapter &GetBluezAdapterObject() { return *bluez_adapter_; } + std::shared_ptr GetConnection() { return system_bus_; } + + private: + std::shared_ptr system_bus_; + std::shared_ptr bluez_adapter_; +}; +} // namespace linux +} // namespace nearby + +#endif // PLATFORM_IMPL_LINUX_BLUETOOTH_ADAPTER_H_ diff --git a/internal/platform/implementation/linux/bluez.cc b/internal/platform/implementation/linux/bluez.cc new file mode 100644 index 00000000..dd4755c1 --- /dev/null +++ b/internal/platform/implementation/linux/bluez.cc @@ -0,0 +1,79 @@ +// 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 "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "internal/platform/implementation/linux/bluez.h" + +namespace nearby { +namespace linux { +namespace bluez { +std::string device_object_path(const sdbus::ObjectPath &adapter_object_path, + absl::string_view mac_address) { + return absl::Substitute( + "$0/dev_$1", adapter_object_path, + absl::StrReplaceAll(absl::AsciiStrToUpper(mac_address), {{":", "_"}})); +} + +sdbus::ObjectPath profile_object_path(absl::string_view service_uuid) { + return absl::Substitute( + "/com/google/nearby/medium/bluetooth_classic/profiles/$0", + absl::StrReplaceAll(service_uuid, {{"-", "_"}})); +} + +sdbus::ObjectPath adapter_object_path(absl::string_view name) { + return absl::Substitute("/org/bluez/$0", name); +} + +sdbus::ObjectPath gatt_service_path(size_t num) { + return absl::Substitute("$0/service$1", NEARBY_BLE_GATT_PATH_ROOT, num); +} + +sdbus::ObjectPath gatt_characteristic_path( + const sdbus::ObjectPath &service_path, size_t num) { + return absl::Substitute("$0/char$1", service_path, num); +} + +sdbus::ObjectPath ble_advertisement_path(size_t num) { + return absl::Substitute("/com/google/nearby/medium/ble/advertisement/$0", + num); +} + +sdbus::ObjectPath advertisement_monitor_path(absl::string_view uuid) { + return absl::Substitute( + "/com/google/nearby/medium/ble/advertisement/monitor/$0", + absl::StrReplaceAll(uuid, {{"-", "_"}})); +} + +int16_t TxPowerLevelDbm(api::ble_v2::TxPowerLevel level) { + switch (level) { + case api::ble_v2::TxPowerLevel::kUnknown: + return 0; + case api::ble_v2::TxPowerLevel::kUltraLow: + return -3; + case api::ble_v2::TxPowerLevel::kLow: + return 0; + case api::ble_v2::TxPowerLevel::kMedium: + return 3; + case api::ble_v2::TxPowerLevel::kHigh: + return 6; + } +} + +} // namespace bluez +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluez.h b/internal/platform/implementation/linux/bluez.h new file mode 100644 index 00000000..2d595a67 --- /dev/null +++ b/internal/platform/implementation/linux/bluez.h @@ -0,0 +1,86 @@ +// 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_H_ +#define PLATFORM_IMPL_LINUX_BLUEZ_H_ + +#include +#include +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/ble_v2.h" + +#include + +#define BLUEZ_LOG_METHOD_CALL_ERROR(proxy, method, err) \ + do { \ + NEARBY_LOGS(ERROR) << __func__ << ": Got error '" << (err).getName() \ + << "' with message '" << (err).getMessage() \ + << "' while calling " << method << " on object " \ + << (proxy)->getObjectPath(); \ + } while (false) + +namespace nearby { +namespace linux { +namespace bluez { +static constexpr const char *SERVICE_DEST = "org.bluez"; + +static constexpr const char *ADAPTER_INTERFACE = "org.bluez.Adapter1"; + +static constexpr const char *DEVICE_INTERFACE = "org.bluez.Device1"; +static constexpr const char *DEVICE_PROP_ADDRESS = "Address"; +static constexpr const char *DEVICE_PROP_ALIAS = "Alias"; +static constexpr const char *DEVICE_PROP_PAIRED = "Paired"; +static constexpr const char *DEVICE_PROP_CONNECTED = "Connected"; +static constexpr const char *DEVICE_NAME = "Name"; + +static constexpr const char *NEARBY_BLE_GATT_PATH_ROOT = + "/com/google/nearby/medium/ble/gatt"; + +std::string device_object_path(const sdbus::ObjectPath &adapter_object_path, + absl::string_view mac_address); +sdbus::ObjectPath profile_object_path(absl::string_view service_uuid); +sdbus::ObjectPath adapter_object_path(absl::string_view name); +sdbus::ObjectPath gatt_service_path(size_t num); +sdbus::ObjectPath gatt_characteristic_path( + const sdbus::ObjectPath &service_path, size_t num); +sdbus::ObjectPath ble_advertisement_path(size_t num); +sdbus::ObjectPath advertisement_monitor_path(absl::string_view uuid); +int16_t TxPowerLevelDbm(api::ble_v2::TxPowerLevel level); + +class BluezObjectManager + : public sdbus::ProxyInterfaces { + public: + explicit BluezObjectManager(sdbus::IConnection &system_bus) + : ProxyInterfaces(system_bus, "org.bluez", "/") { + registerProxy(); + } + virtual ~BluezObjectManager() { unregisterProxy(); } + + protected: + void onInterfacesAdded( + const sdbus::ObjectPath &objectPath, + const std::map> + &interfacesAndProperties) override {} + void onInterfacesRemoved( + const sdbus::ObjectPath &objectPath, + const std::vector &interfaces) override {} +}; + +} // namespace bluez +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/dbus.cc b/internal/platform/implementation/linux/dbus.cc index 1bbaa7b3..6a45b1f6 100644 --- a/internal/platform/implementation/linux/dbus.cc +++ b/internal/platform/implementation/linux/dbus.cc @@ -19,47 +19,29 @@ #include #include "absl/base/call_once.h" +#include "absl/synchronization/mutex.h" #include "internal/platform/implementation/linux/dbus.h" namespace nearby { namespace linux { -static std::unique_ptr global_system_bus_connection = - nullptr; -static std::unique_ptr global_default_bus_connection = - nullptr; -static std::unique_ptr system_root_object_manager = nullptr; -static std::unique_ptr default_root_object_manager = nullptr; -static absl::once_flag bus_connection_init_; -static void disconnectBus() { - system_root_object_manager = nullptr; - default_root_object_manager = nullptr; - global_system_bus_connection = nullptr; - global_default_bus_connection = nullptr; -} +namespace { +static absl::Mutex global_system_bus_mutex; +static std::weak_ptr global_system_bus_connection + ABSL_GUARDED_BY(global_system_bus_mutex); +} // namespace -static void initBusConnections() { - global_system_bus_connection = sdbus::createSystemBusConnection(); - global_system_bus_connection->enterEventLoopAsync(); - global_default_bus_connection = sdbus::createDefaultBusConnection(); - global_default_bus_connection->enterEventLoopAsync(); - system_root_object_manager = - std::make_unique(*global_system_bus_connection); - default_root_object_manager = - std::make_unique(*global_default_bus_connection); +std::shared_ptr getSystemBusConnection() { + absl::MutexLock lock(&global_system_bus_mutex); + auto bus = global_system_bus_connection.lock(); + if (bus == nullptr) { + bus = + std::shared_ptr(sdbus::createSystemBusConnection()); + bus->enterEventLoopAsync(); + global_system_bus_connection = bus; + } - atexit(disconnectBus); -} - -sdbus::IConnection &getSystemBusConnection() { - absl::call_once(bus_connection_init_, initBusConnections); - assert(global_system_bus_connection != nullptr); - return *global_system_bus_connection; -} -sdbus::IConnection &getDefaultBusConnection() { - absl::call_once(bus_connection_init_, initBusConnections); - assert(global_default_bus_connection != nullptr); - return *global_default_bus_connection; + return bus; } } // namespace linux } // namespace nearby diff --git a/internal/platform/implementation/linux/dbus.h b/internal/platform/implementation/linux/dbus.h index 4c3d0699..d9908495 100644 --- a/internal/platform/implementation/linux/dbus.h +++ b/internal/platform/implementation/linux/dbus.h @@ -20,7 +20,6 @@ #include #include "internal/platform/logging.h" -// dbus error logging #define DBUS_LOG_METHOD_CALL_ERROR(p, m, e) \ do { \ NEARBY_LOGS(ERROR) << __func__ << ": Got error '" << (e).getName() \ @@ -47,8 +46,7 @@ namespace nearby { namespace linux { -extern sdbus::IConnection &getSystemBusConnection(); -extern sdbus::IConnection &getDefaultBusConnection(); +extern std::shared_ptr getSystemBusConnection(); class RootObjectManager final : public sdbus::AdaptorInterfaces { diff --git a/internal/platform/implementation/linux/file_path.cc b/internal/platform/implementation/linux/file_path.cc index 57a49860..36043e0d 100644 --- a/internal/platform/implementation/linux/file_path.cc +++ b/internal/platform/implementation/linux/file_path.cc @@ -51,7 +51,7 @@ std::wstring FilePath::GetDownloadPath(std::wstring parent_folder, std::wstring FilePath::GetDownloadPathInternal(std::wstring parent_folder, std::wstring file_name) { - DeviceInfo info = DeviceInfo(linux::getSystemBusConnection()); + DeviceInfo info = DeviceInfo(*linux::getSystemBusConnection()); std::optional download_path = info.GetDownloadPath(); diff --git a/internal/platform/implementation/linux/log_message.cc b/internal/platform/implementation/linux/log_message.cc index 0f2bc3b5..dbbe19e2 100644 --- a/internal/platform/implementation/linux/log_message.cc +++ b/internal/platform/implementation/linux/log_message.cc @@ -39,7 +39,7 @@ static void cleanup_log_control() { static void init_log_control(std::nullptr_t) { global_log_control_ = - std::make_unique(linux::getDefaultBusConnection()); + std::make_unique(*linux::getSystemBusConnection()); atexit(cleanup_log_control); } diff --git a/internal/platform/implementation/linux/platform.cc b/internal/platform/implementation/linux/platform.cc index e8852423..559bcd6e 100644 --- a/internal/platform/implementation/linux/platform.cc +++ b/internal/platform/implementation/linux/platform.cc @@ -50,10 +50,15 @@ #include "internal/platform/implementation/shared/file.h" #include "internal/platform/implementation/submittable_executor.h" // #include "internal/platform/implementation/wifi_hotspot.h" +#include "ble_medium.h" +#include "ble_v2_medium.h" +#include "bluetooth_adapter.h" +#include "bluez.h" #include "internal/platform/implementation/wifi_lan.h" #include "internal/platform/payload_id.h" #include "log_message.h" #include "scheduled_executor.h" +#include "generated/dbus/bluez/adapter_client.h" namespace nearby { namespace api { @@ -166,24 +171,24 @@ ImplementationPlatform::CreateScheduledExecutor() { std::unique_ptr ImplementationPlatform::CreateBluetoothAdapter() { - // auto manager = - // linux::bluez::BluezObjectManager(linux::getSystemBusConnection()); - // try { - // auto interfaces = manager.GetManagedObjects(); - // for (auto &[object, properties] : interfaces) { - // if (properties.count(org::bluez::Adapter1_proxy::INTERFACE_NAME) == 1) { - // NEARBY_LOGS(INFO) << __func__ << ": found bluetooth adapter " << object; - // return std::make_unique( - // linux::getSystemBusConnection(), object); - // } - // } - // } catch (const sdbus::Error &e) { - // DBUS_LOG_METHOD_CALL_ERROR(&manager, "GetManagedObjects", e); - // return nullptr; - // } + auto manager = + linux::bluez::BluezObjectManager(*linux::getSystemBusConnection()); + try { + auto interfaces = manager.GetManagedObjects(); + for (auto &[object, properties] : interfaces) { + if (properties.count(org::bluez::Adapter1_proxy::INTERFACE_NAME) == 1) { + NEARBY_LOGS(INFO) << __func__ << ": found bluetooth adapter " << object; + return std::make_unique( + linux::getSystemBusConnection(), object); + } + } + } catch (const sdbus::Error &e) { + DBUS_LOG_METHOD_CALL_ERROR(&manager, "GetManagedObjects", e); + return nullptr; + } - // NEARBY_LOGS(ERROR) << __func__ - // << ": couldn't find a bluetooth adapter on this system"; + NEARBY_LOGS(ERROR) << __func__ + << ": couldn't find a bluetooth adapter on this system"; return nullptr; //TODO: implement bluetooth adapter } @@ -198,14 +203,13 @@ ImplementationPlatform::CreateBluetoothClassicMedium( // std::unique_ptr ImplementationPlatform::CreateBleMedium( BluetoothAdapter &) { - return nullptr; //TODO: implement ble medium // return std::make_unique(); + return nullptr; } // std::unique_ptr ImplementationPlatform::CreateBleV2Medium(api::BluetoothAdapter &adapter) { - // return std::make_unique(); - return nullptr; //TODO: implement ble_v2 medium + return std::make_unique(adapter); } namespace { @@ -221,7 +225,7 @@ static std::unique_ptr createWifiMedium( } auto manager = - linux::NetworkManagerObjectManager(linux::getSystemBusConnection()); + linux::NetworkManagerObjectManager(*linux::getSystemBusConnection()); std::map>> @@ -241,7 +245,7 @@ static std::unique_ptr createWifiMedium( NEARBY_LOGS(INFO) << __func__ << ": Found a wireless device at :" << device_path; return std::make_unique( - nm, linux::getSystemBusConnection(), device_path); + nm, *linux::getSystemBusConnection(), device_path); } } } @@ -254,14 +258,14 @@ static std::unique_ptr createWifiMedium( std::unique_ptr ImplementationPlatform::CreateWifiMedium() { auto nm = - std::make_shared(linux::getSystemBusConnection()); + std::make_shared(*linux::getSystemBusConnection()); return createWifiMedium(nm); } std::unique_ptr ImplementationPlatform::CreateWifiLanMedium() { return std::make_unique( - linux::getSystemBusConnection()); + *linux::getSystemBusConnection()); } std::unique_ptr @@ -301,7 +305,7 @@ std::unique_ptr ImplementationPlatform::CreateTimer() { } std::unique_ptr ImplementationPlatform::CreateDeviceInfo() { - return std::make_unique(linux::getSystemBusConnection()); + return std::make_unique(*linux::getSystemBusConnection()); } absl::StatusOr ImplementationPlatform::SendRequest(