diff --git a/internal/platform/implementation/linux/bluetooth_bluez_profile.cc b/internal/platform/implementation/linux/bluetooth_bluez_profile.cc new file mode 100644 index 00000000..a2b2c8cc --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_bluez_profile.cc @@ -0,0 +1,107 @@ +#include +#include + +#include +#include + +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_bluez_profile.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/logging.h" + +const char *BLUEZ_PROFILEMANAGER_INTERFACE = "org.bluez.ProfileManager1"; +static int profile_release(sd_bus_message *m, void *userdata, + sd_bus_error *error) { + // TODO +} +static int profile_new_connection(sd_bus_message *m, void *userdata, + sd_bus_error *error) { + // TODO +} +static int profile_new(sd_bus_message *m, void *userdata, sd_bus_error *error) { + // TODO +} + +static const sd_bus_vtable vtable[] = { + SD_BUS_VTABLE_START(0), + SD_BUS_METHOD_WITH_ARGS("Release", SD_BUS_NO_ARGS, SD_BUS_NO_RESULT, + profile_release, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS( + "NewConnection", SD_BUS_ARGS("o", path, "h", fd, "a{sq}", properties), + SD_BUS_NO_RESULT, profile_new_connection, SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_METHOD_WITH_ARGS("RequestDisconnection", SD_BUS_ARGS("o", object), + SD_BUS_NO_RESULT, profile_new, + SD_BUS_VTABLE_UNPRIVILEGED), + SD_BUS_VTABLE_END}; + +namespace nearby { +namespace linux { +std::unique_ptr NewProfileManager() { + sd_bus *system_bus; + if (auto ret = sd_bus_default_system(&system_bus); ret < 0) { + __attribute__((cleanup(sd_bus_error_free))) sd_bus_error err = + SD_BUS_ERROR_NULL; + sd_bus_error_set_errno(&err, ret); + + NEARBY_LOGS(ERROR) << __func__ + << "Error connecting to system bus: " << err.name << ": " + << err.message; + return nullptr; + } + + sd_bus_slot *slot = nullptr; + auto manager = new ProfileManager(system_bus, slot); + + if (auto ret = sd_bus_add_object_vtable( + system_bus, &slot, "/com/github/google/nearby", "org.bluez.Profile1", + vtable, manager->GetMethodData()); + ret < 0) { + __attribute__((cleanup(sd_bus_error_free))) sd_bus_error err = + SD_BUS_ERROR_NULL; + sd_bus_error_set_errno(&err, ret); + + NEARBY_LOGS(ERROR) << __func__ + << "Error adding object /com/github/google/nearby: " + << err.name << ": " << err.message; + return nullptr; + } + + return std::unique_ptr(manager); +} + +bool ProfileManager::ProfileRegistered(absl::string_view service_uuid) { + registered_service_uuids_lock_.ReaderLock(); + bool registered = + registered_service_uuids_.count(std::string(service_uuid)) == 1; + registered_service_uuids_lock_.ReaderUnlock(); + return registered; +} + +bool ProfileManager::RegisterProfile(absl::string_view service_uuid) { + if (ProfileRegistered(service_uuid)) { + return true; + } + + __attribute__((cleanup(sd_bus_error_free))) sd_bus_error err = + SD_BUS_ERROR_NULL; + std::string uuid(service_uuid); + + registered_service_uuids_lock_.Lock(); + if (sd_bus_call_method(system_bus_, BLUEZ_SERVICE, "/org/bluez", + BLUEZ_PROFILEMANAGER_INTERFACE, "RegisterProfile", &err, + nullptr, "osa{sq}", "/com/github/google/nearby", + uuid.c_str(), 0, nullptr) < 0) { + NEARBY_LOGS(ERROR) << __func__ + << "Error calling RegisterProfile: " << err.name << ": " + << err.message; + registered_service_uuids_lock_.Unlock(); + return false; + } + registered_service_uuids_.insert(uuid); + registered_service_uuids_lock_.Unlock(); + return true; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_bluez_profile.h b/internal/platform/implementation/linux/bluetooth_bluez_profile.h new file mode 100644 index 00000000..ba7e6e3a --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_bluez_profile.h @@ -0,0 +1,58 @@ +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_BLUEZ_PROFILE_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_BLUEZ_PROFILE_H_ + +#include +#include +#include +#include +#include + +#include + +#include "absl/strings/string_view.h" +#include "absl/synchronization/mutex.h" +#include "internal/platform/implementation/bluetooth_classic.h" + +namespace nearby { +namespace linux { + +class ProfileManager { +public: + ProfileManager(sd_bus *system_bus, sd_bus_slot *slot) { + system_bus_ = system_bus; + slot_ = slot; + } + ~ProfileManager() { sd_bus_unref(system_bus_); } + + bool ProfileRegistered(absl::string_view service_uuid); + bool RegisterProfile(absl::string_view sevice_uuid); + + std::optional GetServiceRecordFD(api::BluetoothDevice &remote_device, + absl::string_view service_uuid); + +struct MethodData { + std::map>, int> &connections_; + absl::Mutex &connections_lock_; + }; + struct MethodData *GetMethodData() { return &data_; } + +private: + bool InitManagerObj(); + + // Maps (mac address, service uuid) tuples to FDs. Probably + // an awful way to do this, but whatever. + std::map>, int> connections_; + absl::Mutex connections_lock_; + + MethodData data_{connections_, connections_lock_}; + + std::set registered_service_uuids_; + absl::Mutex registered_service_uuids_lock_; + + sd_bus *system_bus_; + sd_bus_slot *slot_; +}; + +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/bluetooth_classic_device.cc b/internal/platform/implementation/linux/bluetooth_classic_device.cc new file mode 100644 index 00000000..8f64ee2c --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_device.cc @@ -0,0 +1,78 @@ +#include + +#include "absl/strings/str_replace.h" +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +BluetoothDevice::BluetoothDevice(absl::string_view adapter, + absl::string_view address) { + mac_addr_ = std::string(address); + object_path_ = absl::Substitute("/org/bluez/$0/dev_$1", adapter, + absl::StrReplaceAll(address, {{":", "_"}})); + if (sd_bus_default_system(&system_bus) < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error connecting to system bus"; + } +} + +BluetoothDevice::BluetoothDevice(absl::string_view device_object_path) { + if (sd_bus_default_system(&system_bus) < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error connecting to system bus"; + return; + } + + object_path_ = device_object_path; +} + +std::string BluetoothDevice::GetName() const { + if (!system_bus) { + return std::string(); + } + __attribute__((cleanup(sd_bus_error_free))) sd_bus_error err = + SD_BUS_ERROR_NULL; + char *cname = nullptr; + if (sd_bus_get_property_string(system_bus, BLUEZ_SERVICE, + object_path_.c_str(), BLUEZ_DEVICE_INTERFACE, + "Alias", &err, &cname) < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error getting alias for device " + << object_path_ << " :" << err.message; + return std::string(); + } + + std::string name(cname); + free(cname); + return name; +} + +std::string BluetoothDevice::GetMacAddress() const { + if (!system_bus) { + return std::string(); + } + + if (!mac_addr_.empty()) { + return mac_addr_; + } + + __attribute__((cleanup(sd_bus_error_free))) sd_bus_error err = + SD_BUS_ERROR_NULL; + char *c_addr = nullptr; + if (sd_bus_get_property_string(system_bus, BLUEZ_SERVICE, + object_path_.c_str(), BLUEZ_DEVICE_INTERFACE, + "Address", &err, &c_addr) < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error getting address for device " + << object_path_ << " :" << err.message; + return std::string(); + } + + std::string addr(c_addr); + free(c_addr); + return addr; +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_classic_device.h b/internal/platform/implementation/linux/bluetooth_classic_device.h new file mode 100644 index 00000000..fcdb1d5f --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_device.h @@ -0,0 +1,34 @@ +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_CLASSIC_DEVICE_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_CLASSIC_DEVICE_H_ + +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/bluetooth_classic.h" + +namespace nearby { +namespace linux { +const char *BLUEZ_DEVICE_INTERFACE = "org.bluez.Device1"; +// https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html. +class BluetoothDevice : public api::BluetoothDevice { +public: + BluetoothDevice(absl::string_view adapter, absl::string_view address); + BluetoothDevice(absl::string_view device_object_path); + + virtual ~BluetoothDevice() override { sd_bus_unref(system_bus); }; + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#getName() + std::string GetName() const override; + + // Returns BT MAC address assigned to this device. + std::string GetMacAddress() const override; + +private: + sd_bus *system_bus; + std::string object_path_; + std::string mac_addr_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluetooth_classic_medium.cc b/internal/platform/implementation/linux/bluetooth_classic_medium.cc new file mode 100644 index 00000000..713aa8cb --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_medium.cc @@ -0,0 +1,171 @@ +#include + +#include +#include + +#include "absl/strings/string_view.h" +#include "absl/strings/substitute.h" +#include "absl/strings/str_replace.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" +#include "internal/platform/implementation/linux/bluetooth_classic_medium.h" +#include "internal/platform/implementation/linux/bluez.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { + +int bluez_interfaces_added_signal_handler(sd_bus_message *m, void *userdata, + sd_bus_error *ret_error) { + const sd_bus_error *reply_err = sd_bus_message_get_error(m); + if (reply_err) { + NEARBY_LOGS(ERROR) << __func__ + << "Received error while listening for InterfacesAdded: " + << reply_err->message; + return 0; + } + + struct BluetoothClassicMedium::DiscoveryParams *params = + static_cast(userdata); + char *c_object_path = nullptr; + int ret = sd_bus_message_read(m, "o", &c_object_path); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ + << "Error reading object path from message: " << ret; + return ret; + } + + std::string object_path(c_object_path); + + if (!absl::StrContains(object_path, absl::StrCat(params->adapter_object_path, + "/", "dev_"))) { + // Interface added for an object we dont care about. + return 0; + } + + if (params->devices_by_path.count(object_path) != 0) { + // Object already exists + return 0; + } + + ret = sd_bus_message_enter_container(m, 'a', "{sa{sv}}"); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error entering container: " << ret; + return 0; + } + + while (true) { + const char *interface_name = nullptr; + ret = sd_bus_message_read(m, "s", &interface_name); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error reading dict entry: " << ret; + } + if (ret == 0) + break; + + if (strcmp(interface_name, "org.bluez.Device1") == 0) { + NEARBY_LOGS(INFO) << __func__ << "Encountered new device at " + << c_object_path; + auto bluetoothDevice = + std::make_unique(BluetoothDevice(object_path)); + params->devices_by_path[object_path] = std::move(bluetoothDevice); + + if (params->cb.device_discovered_cb != nullptr) { + params->cb.device_discovered_cb(*params->devices_by_path[object_path]); + } + for (auto &observer : params->observers_.GetObservers()) { + observer->DeviceAdded(*params->devices_by_path[object_path]); + } + return 0; + } + } + + return 0; +} + +BluetoothClassicMedium::BluetoothClassicMedium(absl::string_view adapter) { + if (sd_bus_default_system(&system_bus_) < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error connecting to system bus"; + } + adapter_object_path_ = absl::Substitute("/org/bluez/$0/", adapter); +} + +BluetoothClassicMedium::~BluetoothClassicMedium() { + if (system_bus_) + sd_bus_unref(system_bus_); + if (system_bus_slot_) + sd_bus_slot_unref(system_bus_slot_); +} + +bool BluetoothClassicMedium::StartDiscovery( + DiscoveryCallback discovery_callback) { + if (!system_bus_) + return false; + + __attribute__((cleanup(sd_bus_error_free))) sd_bus_error err = + SD_BUS_ERROR_NULL; + __attribute__((cleanup(sd_bus_message_unrefp))) sd_bus_message *reply = + nullptr; + + discovery_params_.cb = std::move(discovery_callback); + discovery_params_.adapter_object_path = adapter_object_path_; + + sd_bus_match_signal(system_bus_, &system_bus_slot_, BLUEZ_SERVICE, "/", + "org.freedesktop.DBus.ObjectManager", "InterfacesAdded", + bluez_interfaces_added_signal_handler, + &discovery_params_); + + if (sd_bus_call_method(system_bus_, BLUEZ_SERVICE, + adapter_object_path_.c_str(), BLUEZ_ADAPTER_INTERFACE, + "StartDiscovery", &err, &reply, nullptr) < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error calling StartDiscovery on adapter " + << adapter_object_path_ << ": " << err.message; + return false; + } + + return true; +} + +bool BluetoothClassicMedium::StopDiscovery() { + if (!system_bus_) + return false; + + __attribute__((cleanup(sd_bus_error_free))) sd_bus_error err = + SD_BUS_ERROR_NULL; + __attribute__((cleanup(sd_bus_message_unrefp))) sd_bus_message *reply = + nullptr; + + int ret = sd_bus_call_method( + system_bus_, BLUEZ_SERVICE, adapter_object_path_.c_str(), + BLUEZ_ADAPTER_INTERFACE, "StopDiscovery", &err, &reply, nullptr); + if (ret < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error calling StopDiscovery on " + << adapter_object_path_ << ": " << err.message; + return false; + } + const sd_bus_error *m_err = sd_bus_message_get_error(reply); + + if (m_err) { + NEARBY_LOGS(ERROR) << __func__ << "Error calling StopDiscovery on " + << adapter_object_path_ << ": " << err.message; + return false; + } + + return true; +} + +std::unique_ptr +BluetoothClassicMedium::ConnectToService(api::BluetoothDevice &remote_device, + const std::string &service_uuid, + CancellationFlag *cancellation_flag) { + auto device_object_path = GetDeviceObjectPath(remote_device.GetMacAddress()); + +} + +std::string +BluetoothClassicMedium::GetDeviceObjectPath(absl::string_view mac_address) { + return absl::Substitute("$0/dev_$1", adapter_object_path_, absl::StrReplaceAll(mac_address, {{":", "_"}})); +} + +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_classic_medium.h b/internal/platform/implementation/linux/bluetooth_classic_medium.h new file mode 100644 index 00000000..84b3cca2 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_medium.h @@ -0,0 +1,99 @@ +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_CLASSIC_MEDIUM_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_CLASSIC_MEDIUM_H_ + +#include +#include + +#include + +#include "internal/base/observer_list.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" + +namespace nearby { +namespace linux { +// Container of operations that can be performed over the Bluetooth Classic +// medium. +class BluetoothClassicMedium : public api::BluetoothClassicMedium { +public: + BluetoothClassicMedium(absl::string_view adapter); + ~BluetoothClassicMedium(); + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#startDiscovery() + // + // Returns true once the process of discovery has been initiated. + bool StartDiscovery(DiscoveryCallback discovery_callback) override; + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#cancelDiscovery() + // + // Returns true once discovery is well and truly stopped; after this returns, + // there must be no more invocations of the DiscoveryCallback passed in to + // StartDiscovery(). + bool StopDiscovery() override; + + // A combination of + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createInsecureRfcommSocketToServiceRecord + // followed by + // https://developer.android.com/reference/android/bluetooth/BluetoothSocket.html#connect(). + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // On success, returns a new BluetoothSocket. + // On error, returns nullptr. + std::unique_ptr + ConnectToService(api::BluetoothDevice &remote_device, + const std::string &service_uuid, + CancellationFlag *cancellation_flag) override; + + // https://developer.android.com/reference/android/bluetooth/BluetoothAdapter.html#listenUsingInsecureRfcommWithServiceRecord + // + // service_uuid is the canonical textual representation + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Format) of a + // type 3 name-based + // (https://en.wikipedia.org/wiki/Universally_unique_identifier#Versions_3_and_5_(namespace_name-based)) + // UUID. + // + // Returns nullptr error. + std::unique_ptr + ListenForService(const std::string &service_name, + const std::string &service_uuid) override; + + // https://developer.android.com/reference/android/bluetooth/BluetoothDevice.html#createBond() + // + // Start the bonding (pairing) process with the remote device. + // Return a Bluetooth pairing instance to handle the pairing process with the + // remote device. + std::unique_ptr + CreatePairing(api::BluetoothDevice &remote_device) override; + + api::BluetoothDevice * + GetRemoteDevice(const std::string &mac_address) override; + + void AddObserver(Observer *observer) override; + void RemoveObserver(Observer *observer) override; + + struct DiscoveryParams { + std::string &adapter_object_path; + std::map> &devices_by_path; + ObserverList &observers_; + BluetoothClassicMedium::DiscoveryCallback cb; + }; + +private: + std::string GetDeviceObjectPath(absl::string_view mac_address); + + sd_bus *system_bus_ = nullptr; + sd_bus_slot *system_bus_slot_ = nullptr; + std::string adapter_object_path_ = std::string(); + std::map> devices_by_id_; + ObserverList observers_; + + DiscoveryParams discovery_params_ = {adapter_object_path_, devices_by_id_, observers_}; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluetooth_classic_server_socket.h b/internal/platform/implementation/linux/bluetooth_classic_server_socket.h new file mode 100644 index 00000000..1361557c --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_server_socket.h @@ -0,0 +1,32 @@ +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_SERVER_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_SERVER_SOCKET_H_ + +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/exception.h" + +namespace nearby { +namespace linux { +class BluetoothServerSocket : api::BluetoothServerSocket { +public: + ~BluetoothServerSocket() = default; + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#accept() + // + // Blocks until either: + // - at least one incoming connection request is available, or + // - ServerSocket is closed. + // On success, returns connected socket, ready to exchange data. + // Returns nullptr on error. + // Once error is reported, it is permanent, and ServerSocket has to be + // closed. + std::unique_ptr Accept() override; + + // https://developer.android.com/reference/android/bluetooth/BluetoothServerSocket.html#close() + // + // Returns Exception::kIo on error, Exception::kSuccess otherwise. + Exception Close() override; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluetooth_classic_socket.cc b/internal/platform/implementation/linux/bluetooth_classic_socket.cc new file mode 100644 index 00000000..c1552ea0 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_socket.cc @@ -0,0 +1,68 @@ +#include +#include +#include +#include + +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/linux/bluetooth_classic_socket.h" + +namespace nearby { +namespace linux { + +ExceptionOr BluetoothInputStream::Read(std::int64_t size) { + char *data = new char[size]; + ssize_t ret = read(fd_, data, size); + if (ret == 0) { + delete[] data; + return ExceptionOr(ByteArray()); + } else if (ret < 0) { + delete[] data; + return Exception::kIo; + } + + return ExceptionOr(ByteArray(data, size)); +} + +ExceptionOr BluetoothInputStream::Skip(std::size_t offset) { + auto off = lseek(fd_, offset, SEEK_CUR); + if (off != offset) { + auto end = lseek(fd_, 0, SEEK_END); + return off == end ? ExceptionOr((std::size_t)off) : Exception::kIo; + } + return ExceptionOr((std::size_t)(off)); +} + +ExceptionOr BluetoothInputStream::ReadExactly(std::size_t size) { + char *data = new char[size]; + ssize_t ret = read(fd_, data, size); + if (ret < 0) { + delete[] data; + return Exception::kIo; + } + + return ExceptionOr(ByteArray(data, size)); +} + +Exception BluetoothOutputStream::Write(const ByteArray &data) { + ssize_t written = 0; + while (written < data.size()) { + ssize_t ret = write(fd_, data.data(), data.size()); + if (ret < 1) { + return Exception{Exception::kIo}; + } + written += ret; + } + return Exception{Exception::kSuccess}; +} + +Exception BluetoothOutputStream::Flush() { + return Exception{Exception::kSuccess}; +} + +Exception BluetoothOutputStream::Close() { + return close(fd_) < 0 ? Exception{Exception::kIo} + : Exception{Exception::kSuccess}; +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_classic_socket.h b/internal/platform/implementation/linux/bluetooth_classic_socket.h new file mode 100644 index 00000000..d7384b80 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_classic_socket.h @@ -0,0 +1,61 @@ +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_SOCKET_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_SOCKET_H_ + +#include + +#include + +#include "internal/platform/byte_array.h" +#include "internal/platform/exception.h" +#include "internal/platform/implementation/bluetooth_classic.h" + +namespace nearby { +namespace linux { + +class BluetoothInputStream : public InputStream { +public: + BluetoothInputStream(int fd) { fd_ = fd; }; + + ExceptionOr Read(std::int64_t size) override; + ExceptionOr Skip(size_t offset) override; + ExceptionOr ReadExactly(std::size_t size); + + Exception Close() override; + +private: + int fd_; +}; + +class BluetoothOutputStream : public OutputStream { +public: + BluetoothOutputStream(int fd) { fd_ = fd; }; + + Exception Write(const ByteArray &data) override; + Exception Flush() override; + Exception Close() override; + +private: + int fd_; +}; + +class BluetoothSocket : public api::BluetoothSocket { +public: + BluetoothSocket(std::string object, int fd) { + fd_ = fd; + object_ = object; + input_stream_ = BluetoothInputStream(fd_); + output_stream_ = BluetoothOutputStream(fd_); + } + + InputStream &GetInputStream() override { return input_stream_; } + OutputStream &GetOutputStream() override { return output_stream_; } + +private: + int fd_; + std::string object_; + BluetoothInputStream input_stream_ = {-1}; + BluetoothOutputStream output_stream_ = {-1}; +}; +} // namespace linux +} // namespace nearby +#endif diff --git a/internal/platform/implementation/linux/bluetooth_pairing.cc b/internal/platform/implementation/linux/bluetooth_pairing.cc new file mode 100644 index 00000000..e2007b4e --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_pairing.cc @@ -0,0 +1,132 @@ +#include +#include + +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/implementation/linux/bluetooth_classic_device.h" +#include "internal/platform/implementation/linux/bluetooth_pairing.h" +#include "internal/platform/implementation/linux/bluez.h" + +namespace nearby { +namespace linux { + +int pairing_reply_handler(sd_bus_message *m, void *userdata, + sd_bus_error *err) { + auto pairing_cb = static_cast(userdata); + if (sd_bus_message_is_method_error(m, nullptr)) { + if (sd_bus_message_is_method_error( + m, "org.bluez.Error.AuthenticationCanceled")) { + pairing_cb->on_pairing_error_cb( + api::BluetoothPairingCallback::PairingError::kAuthCanceled); + } else if (sd_bus_message_is_method_error( + m, "org.bluez.Error.AuthenticationFailed")) { + pairing_cb->on_pairing_error_cb( + api::BluetoothPairingCallback::PairingError::kAuthFailed); + } else if (sd_bus_message_is_method_error( + m, "org.bluez.Error.AuthenticationRejected")) { + pairing_cb->on_pairing_error_cb( + api::BluetoothPairingCallback::PairingError::kAuthRejected); + } else if (sd_bus_message_is_method_error( + m, "org.bluez.Error.AuthenticationTimeout")) { + pairing_cb->on_pairing_error_cb( + api::BluetoothPairingCallback::PairingError::kAuthTimeout); + } else { + pairing_cb->on_pairing_error_cb( + api::BluetoothPairingCallback::PairingError::kAuthFailed); + } + return 0; + } + if (err) { + NEARBY_LOGS(ERROR) << __func__ + << "Error pairing with device: " << err->message; + pairing_cb->on_pairing_error_cb( + api::BluetoothPairingCallback::PairingError::kUnknown); + } else { + pairing_cb->on_paired_cb(); + } + return 0; +} + +BluetoothPairing::BluetoothPairing(absl::string_view object_path) { + object_path_ = object_path; + if (sd_bus_default_system(&system_bus_) < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error connecting to system bus"; + } +} + +bool BluetoothPairing::InitiatePairing( + api::BluetoothPairingCallback pairing_cb) { + if (!system_bus_) + return false; + + pairing_cb_ = std::move(pairing_cb); + + if (sd_bus_call_method_async(system_bus_, nullptr, BLUEZ_SERVICE, + object_path_.c_str(), BLUEZ_DEVICE_INTERFACE, + "Pair", &pairing_reply_handler, &pairing_cb_, + nullptr) < 0) { + NEARBY_LOGS(ERROR) << __func__ << "Error calling method Pair on device " + << object_path_; + return false; + } + pairing_cb.on_pairing_initiated_cb(api::PairingParams{ + api::PairingParams::PairingType::kConsent, std::string()}); + return true; +} + +bool BluetoothPairing::FinishPairing( + std::optional pin_code) { + return true; +} + +bool BluetoothPairing::CancelPairing() { + if (!system_bus_) + return false; + + __attribute__((cleanup(sd_bus_error_free))) sd_bus_error err = + SD_BUS_ERROR_NULL; + if (sd_bus_call_method(system_bus_, BLUEZ_SERVICE, object_path_.c_str(), + BLUEZ_DEVICE_INTERFACE, "CancelPairing", &err, nullptr, + nullptr)) { + NEARBY_LOGS(ERROR) << __func__ + << "Error calling method CancelPairing on device " + << object_path_ << ": " << err.message; + return false; + } + return true; +} + +bool BluetoothPairing::Unpair() { + if (!system_bus_) + return false; + + __attribute__((cleanup(sd_bus_error_free))) sd_bus_error err = + SD_BUS_ERROR_NULL; + if (sd_bus_call_method(system_bus_, BLUEZ_SERVICE, "/org/bluez/hci0", + BLUEZ_ADAPTER_INTERFACE, "RemoveDevice", &err, nullptr, + "o", object_path_.c_str())) { + NEARBY_LOGS(ERROR) << __func__ + << "Error calling method CancelPairing on device " + << object_path_ << ": " << err.message; + return false; + } + return true; +} + +bool BluetoothPairing::IsPaired() { + if (!system_bus_) + return false; + + __attribute__((cleanup(sd_bus_error_free))) sd_bus_error err = + SD_BUS_ERROR_NULL; + int paired = 0; + if (sd_bus_get_property_trivial(system_bus_, BLUEZ_SERVICE, + object_path_.c_str(), BLUEZ_DEVICE_INTERFACE, + "Bonded", &err, 'b', &paired) < 0) { + NEARBY_LOGS(ERROR) << __func__ + << "Error getting Bonded property for device " + << object_path_ << ": " << err.message; + } + return paired; +} +} // namespace linux +} // namespace nearby diff --git a/internal/platform/implementation/linux/bluetooth_pairing.h b/internal/platform/implementation/linux/bluetooth_pairing.h new file mode 100644 index 00000000..3e5ee4e4 --- /dev/null +++ b/internal/platform/implementation/linux/bluetooth_pairing.h @@ -0,0 +1,33 @@ +#ifndef PLATFORM_IMPL_LINUX_BLUETOOTH_PROFILE_H_ +#define PLATFORM_IMPL_LINUX_BLUETOOTH_PAIRING_H_ + +#include + +#include + +#include "absl/strings/string_view.h" +#include "internal/platform/implementation/bluetooth_classic.h" +#include "internal/platform/logging.h" + +namespace nearby { +namespace linux { +class BluetoothPairing : public api::BluetoothPairing { +public: + BluetoothPairing(absl::string_view object_path); + ~BluetoothPairing() { sd_bus_unref(system_bus_); } + + bool InitiatePairing(api::BluetoothPairingCallback pairing_cb) override; + bool FinishPairing(std::optional pin_code) override; + bool CancelPairing() override; + bool Unpair() override; + bool IsPaired() override; + +private: + std::string object_path_; + sd_bus *system_bus_; + api::BluetoothPairingCallback pairing_cb_; +}; +} // namespace linux +} // namespace nearby + +#endif diff --git a/internal/platform/implementation/linux/bluez.h b/internal/platform/implementation/linux/bluez.h index 472885a1..50316c95 100644 --- a/internal/platform/implementation/linux/bluez.h +++ b/internal/platform/implementation/linux/bluez.h @@ -4,7 +4,6 @@ namespace nearby { namespace linux { const char *BLUEZ_SERVICE = "org.bluez"; - const char *BLUEZ_ADAPTER_INTERFACE = "org.bluez.Adapter1"; } // namespace linux