diff options
Diffstat (limited to 'src/bt.cc')
| -rw-r--r-- | src/bt.cc | 953 |
1 files changed, 953 insertions, 0 deletions
diff --git a/src/bt.cc b/src/bt.cc new file mode 100644 index 0000000..6ab7d1d --- /dev/null +++ b/src/bt.cc @@ -0,0 +1,953 @@ +#include "bt.hh" + +#include "cfg.hh" +#include "logger.hh" +#include "looper.hh" + +#include <chrono> +#include <cstddef> +#include <cstdint> +#include <format> +#include <functional> +#include <map> +#include <memory> +#include <optional> +#include <poll.h> +#include <sdbus-c++/Error.h> +#include <sdbus-c++/IConnection.h> +#include <sdbus-c++/IObject.h> +#include <sdbus-c++/IProxy.h> +#include <sdbus-c++/MethodResult.h> +#include <sdbus-c++/ProxyInterfaces.h> +#include <sdbus-c++/StandardInterfaces.h> +#include <sdbus-c++/Types.h> +#include <sdbus-c++/VTableItems.h> +#include <string> +#include <utility> +#include <vector> + +namespace bt { + +namespace { + +class IManager { + public: + virtual ~IManager() = default; + + IManager(IManager const&) = delete; + IManager& operator=(IManager const&) = delete; + + virtual void add_device( + sdbus::ObjectPath const& path, + std::map<sdbus::PropertyName, sdbus::Variant> const& properties) = 0; + virtual void add_adapter( + sdbus::ObjectPath const& path, + std::map<sdbus::PropertyName, sdbus::Variant> const& properties) = 0; + virtual void add_agent_manager(sdbus::ObjectPath const& path) = 0; + + virtual void remove_device(sdbus::ObjectPath const& path) = 0; + virtual void remove_adapter(sdbus::ObjectPath const& path) = 0; + virtual void remove_agent_manager(sdbus::ObjectPath const& path) = 0; + virtual void remove_agent() = 0; + + virtual void update_adapter(sdbus::ObjectPath const& path) = 0; + virtual void update_device(sdbus::ObjectPath const& path) = 0; + + virtual std::vector<Device*> get_devices( + sdbus::ObjectPath const& adapter_path) = 0; + + virtual void agent_request_pincode( + sdbus::ObjectPath const& device, + std::function<void(std::optional<std::string>)> callback) = 0; + virtual void agent_display_pincode(sdbus::ObjectPath const& device, + std::string const& pincode) = 0; + virtual void agent_request_passkey( + sdbus::ObjectPath const& device, + std::function<void(std::optional<uint32_t>)> callback) = 0; + virtual void agent_display_passkey(sdbus::ObjectPath const& device, + uint32_t passkey, uint16_t entered) = 0; + virtual void agent_request_confirmation( + sdbus::ObjectPath const& device, uint32_t passkey, + std::function<void(bool)> callback) = 0; + virtual void agent_request_authorization( + sdbus::ObjectPath const& device, std::function<void(bool)> callback) = 0; + virtual void agent_authorize_service(sdbus::ObjectPath const& device, + std::string const& uuid, + std::function<void(bool)> callback) = 0; + virtual void agent_cancel() = 0; + + virtual logger::Logger& logger() = 0; + + protected: + IManager() = default; +}; + +const sdbus::ServiceName kService{"org.bluez"}; +const sdbus::InterfaceName kDeviceInterface{"org.bluez.Device1"}; +const sdbus::InterfaceName kAdapterInterface{"org.bluez.Adapter1"}; +const sdbus::InterfaceName kAgentInterface{"org.bluez.Agent1"}; +const sdbus::InterfaceName kAgentManagerInterface{"org.bluez.AgentManager1"}; +const sdbus::InterfaceName kPropertiesInterface{ + "org.freedesktop.DBus.Properties"}; + +class ObjectManagerProxy { + private: + static inline sdbus::InterfaceName kInterface{ + "org.freedesktop.DBus.ObjectManager"}; + + public: + ObjectManagerProxy(IManager& manager, sdbus::IConnection& connection, + sdbus::ServiceName destination, sdbus::ObjectPath path, + sdbus::ObjectPath filter) + : proxy_(sdbus::createProxy(connection, std::move(destination), + std::move(path))), + manager_(manager), + filter_(std::move(filter)) { + proxy_->uponSignal("InterfacesAdded") + .onInterface(kInterface) + .call([this]( + sdbus::ObjectPath const& path, + std::map<sdbus::InterfaceName, + std::map<sdbus::PropertyName, sdbus::Variant>> const& + interfaces_and_properties) { + interfaces_added(path, interfaces_and_properties); + }); + + proxy_->uponSignal("InterfacesRemoved") + .onInterface(kInterface) + .call([this](sdbus::ObjectPath const& path, + std::vector<sdbus::InterfaceName> const& interfaces) { + this->interfaces_removed(path, interfaces); + }); + + proxy_->callMethodAsync("GetManagedObjects") + .onInterface(kInterface) + .uponReplyInvoke( + [this]( + std::optional<sdbus::Error> err, + std::map<sdbus::ObjectPath, + std::map<sdbus::InterfaceName, + std::map<sdbus::PropertyName, + sdbus::Variant>>> const& objects) { + if (err.has_value()) { + if (err->getName() == + "org.freedesktop.DBus.Error.ServiceUnknown") { + manager_.logger().info("Waiting for server to start ..."); + } else { + manager_.logger().err(std::format("GetManagedObjects {}: {}", + std::string(err->getName()), + err->getMessage())); + } + return; + } + + for (auto const& pair : objects) { + interfaces_added(pair.first, pair.second); + } + }); + } + + ObjectManagerProxy(ObjectManagerProxy const&) = delete; + ObjectManagerProxy& operator=(ObjectManagerProxy const&) = delete; + + virtual ~ObjectManagerProxy() { proxy_->unregister(); } + + private: + void interfaces_added( + sdbus::ObjectPath const& object_path, + std::map<sdbus::InterfaceName, + std::map<sdbus::PropertyName, sdbus::Variant>> const& + interfaces_and_properties) { + if (!object_path.starts_with(filter_)) { + manager_.logger().dbg( + std::format("ignoring: {}", std::string(object_path))); + return; + } + + auto it = interfaces_and_properties.find(kDeviceInterface); + if (it != interfaces_and_properties.end()) { + manager_.add_device(object_path, it->second); + } + it = interfaces_and_properties.find(kAdapterInterface); + if (it != interfaces_and_properties.end()) { + manager_.add_adapter(object_path, it->second); + } + it = interfaces_and_properties.find(kAgentManagerInterface); + if (it != interfaces_and_properties.end()) { + manager_.add_agent_manager(object_path); + } + } + + void interfaces_removed(sdbus::ObjectPath const& object_path, + std::vector<sdbus::InterfaceName> const& interfaces) { + if (!object_path.starts_with(filter_)) + return; + + for (auto const& interface : interfaces) { + if (interface == kDeviceInterface) { + manager_.remove_device(object_path); + } else if (interface == kAdapterInterface) { + manager_.remove_adapter(object_path); + } else if (interface == kAgentManagerInterface) { + manager_.remove_agent_manager(object_path); + } + } + } + + std::unique_ptr<sdbus::IProxy> proxy_; + IManager& manager_; + sdbus::ObjectPath filter_; +}; + +class BaseProxy : public sdbus::ProxyInterfaces<sdbus::Properties_proxy> { + public: + virtual ~BaseProxy() { unregisterProxy(); } + + protected: + BaseProxy(sdbus::IConnection& connection, sdbus::ObjectPath const& path, + std::map<sdbus::PropertyName, size_t> const& names) + : sdbus::ProxyInterfaces<sdbus::Properties_proxy>(connection, kService, + path), + names_(names) {} + + virtual void update(size_t field, sdbus::Variant const& value) = 0; + virtual void notify_updated() = 0; + + void init(std::map<sdbus::PropertyName, sdbus::Variant> const& properties) { + for (auto const& pair : properties) { + auto it = names_.find(pair.first); + if (it != names_.end()) { + update(it->second, pair.second); + } + } + + registerProxy(); + } + + private: + void onPropertiesChanged( + const sdbus::InterfaceName& interface_name, + const std::map<sdbus::PropertyName, sdbus::Variant>& changed, + const std::vector<sdbus::PropertyName>& invalidated) override { + if (interface_name != kAdapterInterface) + return; + ++waiting_; + for (auto const& pair : changed) { + auto it = names_.find(pair.first); + if (it != names_.end()) { + updated_ = true; + update(it->second, pair.second); + } + } + for (auto const& property_name : invalidated) { + auto it = names_.find(property_name); + if (it != names_.end()) { + ++waiting_; + GetAsync(interface_name, property_name, + // NOLINTNEXTLINE(performance-unnecessary-value-param) + [this, field = it->second](std::optional<sdbus::Error> err, + sdbus::Variant const& value) { + if (!err.has_value()) { + updated_ = true; + update(field, value); + } + if (--waiting_ == 0 && updated_) { + notify_updated(); + } + }); + } + } + if (--waiting_ == 0 && updated_) { + notify_updated(); + } + } + + std::map<sdbus::PropertyName, size_t> const& names_; + uint8_t waiting_{0}; + bool updated_{false}; +}; + +class DeviceProxy : public BaseProxy, public Device { + private: + static inline const std::map<sdbus::PropertyName, size_t> kNames{ + {sdbus::PropertyName{"Address"}, 0}, {sdbus::PropertyName{"Name"}, 1}, + {sdbus::PropertyName{"Alias"}, 2}, {sdbus::PropertyName{"Paired"}, 3}, + {sdbus::PropertyName{"Connected"}, 4}, {sdbus::PropertyName{"Adapter"}, 5}, + }; + + public: + DeviceProxy(IManager& manager, sdbus::IConnection& connection, + sdbus::ObjectPath const& path, + std::map<sdbus::PropertyName, sdbus::Variant> const& properties) + : BaseProxy(connection, path, kNames), manager_(manager) { + init(properties); + } + + [[nodiscard]] + std::string const& address() const override { + return address_; + } + + [[nodiscard]] + std::string const& name() const override { + if (alias_.has_value() && !alias_->empty()) + return alias_.value(); + if (name_.has_value() && !name_->empty()) + return name_.value(); + return address_; + } + + [[nodiscard]] + bool paired() const override { + return paired_; + } + + [[nodiscard]] + bool connected() const override { + return connected_; + } + + [[nodiscard]] + sdbus::ObjectPath const& adapter() const { + return adapter_; + } + + private: + void update(size_t field, sdbus::Variant const& value) override { + switch (field) { + case 0: + address_ = value.get<std::string>(); + break; + case 1: + name_ = value.isEmpty() + ? std::nullopt + : std::optional<std::string>(value.get<std::string>()); + break; + case 2: + alias_ = value.isEmpty() + ? std::nullopt + : std::optional<std::string>(value.get<std::string>()); + break; + case 3: + paired_ = value.get<bool>(); + break; + case 4: + connected_ = value.get<bool>(); + break; + case 5: + adapter_ = value.get<std::string>(); + break; + default: + std::unreachable(); + } + } + + void notify_updated() override { + manager_.update_device(getProxy().getObjectPath()); + } + + IManager& manager_; + std::string address_; + std::optional<std::string> name_; + std::optional<std::string> alias_; + bool connected_{false}; + bool paired_{false}; + sdbus::ObjectPath adapter_; +}; + +class AdapterProxy : public BaseProxy, public Adapter { + private: + static inline const std::map<sdbus::PropertyName, size_t> kNames{ + {sdbus::PropertyName{"Address"}, 0}, + {sdbus::PropertyName{"Name"}, 1}, + {sdbus::PropertyName{"Alias"}, 2}, + {sdbus::PropertyName{"Discoverable"}, 3}, + {sdbus::PropertyName{"Pairable"}, 4}, + {sdbus::PropertyName{"Pairing"}, 5}, + }; + + public: + AdapterProxy(IManager& manager, sdbus::IConnection& connection, + sdbus::ObjectPath const& path, + std::map<sdbus::PropertyName, sdbus::Variant> const& properties) + : BaseProxy(connection, path, kNames), manager_(manager) { + init(properties); + } + + [[nodiscard]] + std::string const& address() const override { + return address_; + } + + [[nodiscard]] + std::string const& name() const override { + if (!alias_.empty()) + return alias_; + if (!name_.empty()) + return name_; + return address_; + } + + [[nodiscard]] + bool discoverable() const override { + return discoverable_; + } + + [[nodiscard]] + bool pairable() const override { + return pairable_; + } + + [[nodiscard]] + bool pairing() const override { + return pairing_; + } + + [[nodiscard]] + std::vector<Device*> devices() const override { + return manager_.get_devices(getProxy().getObjectPath()); + } + + private: + void update(size_t field, sdbus::Variant const& value) override { + switch (field) { + case 0: + address_ = value.get<std::string>(); + break; + case 1: + name_ = value.get<std::string>(); + break; + case 2: + alias_ = value.get<std::string>(); + break; + case 3: + discoverable_ = value.get<bool>(); + break; + case 4: + pairable_ = value.get<bool>(); + manager_.logger().dbg(std::format("pairable: {}", pairable_)); + break; + case 5: + pairing_ = value.get<bool>(); + break; + default: + std::unreachable(); + } + } + + void notify_updated() override { + manager_.update_adapter(getProxy().getObjectPath()); + } + + IManager& manager_; + std::string address_; + std::string name_; + std::string alias_; + bool discoverable_{false}; + bool pairable_{false}; + bool pairing_{false}; +}; + +class AgentManagerProxy { + public: + AgentManagerProxy(sdbus::IConnection& connection, + sdbus::ObjectPath const& path) + : proxy_(sdbus::createProxy(connection, kService, path)) {} + + void register_agent( + sdbus::ObjectPath const& path, std::string const& capability, + std::function<void(std::optional<sdbus::Error> err)> callback) { + proxy_->callMethodAsync("RegisterAgent") + .onInterface(kAgentManagerInterface) + .withArguments(path, capability) + .uponReplyInvoke(std::move(callback)); + } + + void unregister_agent_sync(sdbus::ObjectPath const& path) { + proxy_->callMethod("UnregisterAgent") + .onInterface(kAgentManagerInterface) + .withArguments(path); + } + + [[nodiscard]] + sdbus::ObjectPath const& object_path() const { + return proxy_->getObjectPath(); + } + + private: + std::unique_ptr<sdbus::IProxy> proxy_; +}; + +class AgentObject { + private: + static inline const sdbus::ObjectPath kObjectPath{"/org/bluez/agent"}; + static inline const sdbus::Error::Name kErrorCanceled{ + "org.bluez.Error.Canceled"}; + static inline const sdbus::Error::Name kErrorRejected{ + "org.bluez.Error.Rejected"}; + + public: + AgentObject(IManager& manager, sdbus::IConnection& connection) + : manager_(manager), + object_(sdbus::createObject(connection, kObjectPath)) { + object_ + ->addVTable( + sdbus::registerMethod("Release").withNoReply().implementedAs( + [this]() { manager_.remove_agent(); }), + sdbus::registerMethod("RequestPinCode") + .withInputParamNames("device") + .withOutputParamNames("pincode") + .implementedAs([this](sdbus::Result<std::string>&& result, + sdbus::ObjectPath const& device) { + cancel_pending(); + pending_pincode_ = std::move(result); + manager_.agent_request_pincode(device, [this](auto pincode) { + if (!pending_pincode_.has_value()) + return; + if (pincode.has_value()) { + pending_pincode_->returnResults(pincode.value()); + } else { + pending_pincode_->returnError( + sdbus::Error{kErrorRejected}); + } + pending_pincode_ = std::nullopt; + }); + }), + sdbus::registerMethod("DisplayPinCode") + .withInputParamNames("device", "pincode") + .implementedAs([this](sdbus::ObjectPath const& device, + std::string const& pincode) { + manager_.agent_display_pincode(device, pincode); + }), + sdbus::registerMethod("RequestPasskey") + .withInputParamNames("device") + .withOutputParamNames("passkey") + .implementedAs([this](sdbus::Result<uint32_t>&& result, + sdbus::ObjectPath const& device) { + cancel_pending(); + pending_passkey_ = std::move(result); + manager_.agent_request_passkey(device, [this](auto passkey) { + if (!pending_passkey_.has_value()) + return; + if (passkey.has_value()) { + pending_passkey_->returnResults(passkey.value()); + } else { + pending_passkey_->returnError( + sdbus::Error{kErrorRejected}); + } + pending_passkey_ = std::nullopt; + }); + }), + sdbus::registerMethod("DisplayPasskey") + .withInputParamNames("device", "passkey", "entered") + .implementedAs([this](sdbus::ObjectPath const& device, + uint32_t passkey, uint16_t entered) { + manager_.agent_display_passkey(device, passkey, entered); + }), + sdbus::registerMethod("RequestConfirmation") + .withInputParamNames("device", "passkey") + .withOutputParamNames("result") + .implementedAs([this](sdbus::Result<sdbus::Variant>&& result, + sdbus::ObjectPath const& device, + uint32_t passkey) { + cancel_pending(); + pending_confirm_ = std::move(result); + manager_.agent_request_confirmation( + device, passkey, [this](auto ok) { + if (!pending_confirm_.has_value()) + return; + if (ok) { + pending_confirm_->returnResults({}); + } else { + pending_confirm_->returnError( + sdbus::Error{kErrorRejected}); + } + pending_confirm_ = std::nullopt; + }); + }), + sdbus::registerMethod("RequestAuthorization") + .withInputParamNames("device") + .withOutputParamNames("result") + .implementedAs([this](sdbus::Result<sdbus::Variant>&& result, + sdbus::ObjectPath const& device) { + cancel_pending(); + pending_confirm_ = std::move(result); + manager_.agent_request_authorization(device, [this](auto ok) { + if (!pending_confirm_.has_value()) + return; + if (ok) { + pending_confirm_->returnResults({}); + } else { + pending_confirm_->returnError( + sdbus::Error{kErrorRejected}); + } + pending_confirm_ = std::nullopt; + }); + }), + sdbus::registerMethod("AuthorizeService") + .withInputParamNames("device", "uuid") + .withOutputParamNames("result") + .implementedAs([this](sdbus::Result<sdbus::Variant>&& result, + sdbus::ObjectPath const& device, + std::string const& uuid) { + cancel_pending(); + pending_confirm_ = std::move(result); + manager_.agent_authorize_service( + device, uuid, [this](auto ok) { + if (!pending_confirm_.has_value()) + return; + if (ok) { + pending_confirm_->returnResults({}); + } else { + pending_confirm_->returnError( + sdbus::Error{kErrorRejected}); + } + pending_confirm_ = std::nullopt; + }); + }), + sdbus::registerMethod("Cancel").withNoReply().implementedAs( + [this]() { + cancel_pending(); + manager_.agent_cancel(); + })) + .forInterface(kAgentInterface); + } + + [[nodiscard]] + sdbus::ObjectPath const& object_path() const { + return object_->getObjectPath(); + } + + private: + void cancel_pending() { + if (pending_pincode_.has_value()) { + pending_pincode_->returnError(sdbus::Error{kErrorCanceled}); + pending_pincode_ = std::nullopt; + } + if (pending_passkey_.has_value()) { + pending_passkey_->returnError(sdbus::Error{kErrorCanceled}); + pending_passkey_ = std::nullopt; + } + if (pending_confirm_.has_value()) { + pending_confirm_->returnError(sdbus::Error{kErrorCanceled}); + pending_confirm_ = std::nullopt; + } + } + + IManager& manager_; + std::unique_ptr<sdbus::IObject> object_; + + std::optional<sdbus::Result<std::string>> pending_pincode_; + std::optional<sdbus::Result<uint32_t>> pending_passkey_; + std::optional<sdbus::Result<sdbus::Variant>> pending_confirm_; +}; + +class ManagerImpl : public Manager, public IManager, looper::Hook { + public: + ManagerImpl(logger::Logger& logger, cfg::Config const& cfg, + looper::Looper& looper, Manager::Delegate& delegate) + : logger_(logger), + cfg_(cfg), + looper_(looper), + delegate_(delegate), + conn_(sdbus::createSystemBusConnection()) { + looper_.add_hook(this); + + root_proxy_ = std::make_unique<ObjectManagerProxy>( + *this, *conn_, kService, sdbus::ObjectPath{"/"}, + sdbus::ObjectPath{"/org/bluez"}); + } + + ~ManagerImpl() override { + if (agent_ && agent_manager_proxy_) { + agent_manager_proxy_->unregister_agent_sync(agent_->object_path()); + } + looper_.remove_hook(this); + } + + Adapter* adapter() override { + if (primary_adapter_.empty()) + return nullptr; + return adapters_.at(primary_adapter_).get(); + } + + private: + void add_device(sdbus::ObjectPath const& path, + std::map<sdbus::PropertyName, sdbus::Variant> const& + properties) override { + logger_.dbg(std::format("Add device: {}", std::string(path))); + auto [it, inserted] = devices_.emplace( + path, std::make_unique<DeviceProxy>(*this, *conn_, path, properties)); + + auto& device = *it->second; + if (device.adapter() == primary_adapter_) { + delegate_.added_device(device); + } + } + + void add_adapter(sdbus::ObjectPath const& path, + std::map<sdbus::PropertyName, sdbus::Variant> const& + properties) override { + logger_.dbg(std::format("Add adapter: {}", std::string(path))); + adapters_.emplace( + path, std::make_unique<AdapterProxy>(*this, *conn_, path, properties)); + + if (primary_adapter_.empty()) { + update_primary_adapter(); + } + } + + void add_agent_manager(sdbus::ObjectPath const& path) override { + logger_.dbg(std::format("Add agent manager: {}", std::string(path))); + if (agent_manager_proxy_) + return; + + agent_manager_proxy_ = std::make_unique<AgentManagerProxy>(*conn_, path); + agent_ = std::make_unique<AgentObject>(*this, *conn_); + agent_manager_proxy_->register_agent( + agent_->object_path(), "", [this](std::optional<sdbus::Error> err) { + if (err.has_value()) { + agent_.reset(); + logger_.warn(std::format("Failed to register agent: {} {}", + std::string(err->getName()), + err->getMessage())); + } else { + logger_.info("Registered agent"); + } + }); + } + + void remove_device(sdbus::ObjectPath const& path) override { + logger_.dbg(std::format("Remove device: {}", std::string(path))); + auto it = devices_.find(path); + if (it == devices_.end()) + return; + + auto& device = *it->second; + if (device.adapter() == primary_adapter_) { + auto address = device.address(); + devices_.erase(it); + delegate_.removed_device(address); + } else { + devices_.erase(it); + } + } + + void remove_adapter(sdbus::ObjectPath const& path) override { + logger_.dbg(std::format("Remove adapter: {}", std::string(path))); + auto it = adapters_.find(path); + if (it != adapters_.end()) + adapters_.erase(it); + + if (path == primary_adapter_) { + update_primary_adapter(); + } + } + + void remove_agent_manager(sdbus::ObjectPath const& path) override { + logger_.dbg(std::format("Remove agemtn manager: {}", std::string(path))); + + if (!agent_manager_proxy_) + return; + if (agent_manager_proxy_->object_path() != path) + return; + + agent_manager_proxy_.reset(); + agent_.reset(); + } + + void update_device(sdbus::ObjectPath const& path) override { + logger_.dbg(std::format("Update device: {}", std::string(path))); + + auto& device = *devices_.at(path); + if (device.adapter() == primary_adapter_) { + delegate_.updated_device(device); + } + } + + void update_adapter(sdbus::ObjectPath const& path) override { + logger_.dbg(std::format("Update adapter: {}", std::string(path))); + + if (path == primary_adapter_) { + delegate_.updated_adapter(*adapters_.at(path)); + } + } + + std::vector<Device*> get_devices( + sdbus::ObjectPath const& adapter_path) override { + std::vector<Device*> ret; + ret.reserve(devices_.size()); + for (auto& pair : devices_) { + if (pair.second->adapter() == adapter_path) { + ret.push_back(pair.second.get()); + } + } + return ret; + } + + [[nodiscard]] + logger::Logger& logger() override { + return logger_; + } + + void remove_agent() override { + logger_.info("Remove agent"); + agent_ = nullptr; + } + + void agent_request_pincode( + sdbus::ObjectPath const& device, + std::function<void(std::optional<std::string>)> callback) override { + auto it = devices_.find(device); + if (it == devices_.end() || it->second->adapter() != primary_adapter_) { + callback(std::nullopt); + return; + } + delegate_.agent_request_pincode(*it->second, std::move(callback)); + } + + void agent_display_pincode(sdbus::ObjectPath const& device, + std::string const& pincode) override { + auto it = devices_.find(device); + if (it == devices_.end() || it->second->adapter() != primary_adapter_) + return; + delegate_.agent_display_pincode(*it->second, pincode); + } + + void agent_request_passkey( + sdbus::ObjectPath const& device, + std::function<void(std::optional<uint32_t>)> callback) override { + auto it = devices_.find(device); + if (it == devices_.end() || it->second->adapter() != primary_adapter_) { + callback(std::nullopt); + return; + } + delegate_.agent_request_passkey(*it->second, std::move(callback)); + } + + void agent_display_passkey(sdbus::ObjectPath const& device, uint32_t passkey, + uint16_t entered) override { + auto it = devices_.find(device); + if (it == devices_.end() || it->second->adapter() != primary_adapter_) + return; + delegate_.agent_display_passkey(*it->second, passkey, entered); + } + + void agent_request_confirmation(sdbus::ObjectPath const& device, + uint32_t passkey, + std::function<void(bool)> callback) override { + auto it = devices_.find(device); + if (it == devices_.end() || it->second->adapter() != primary_adapter_) { + callback(false); + return; + } + delegate_.agent_request_confirmation(*it->second, passkey, + std::move(callback)); + } + + void agent_request_authorization( + sdbus::ObjectPath const& device, + std::function<void(bool)> callback) override { + auto it = devices_.find(device); + if (it == devices_.end() || it->second->adapter() != primary_adapter_) { + callback(false); + return; + } + delegate_.agent_request_authorization(*it->second, std::move(callback)); + } + + void agent_authorize_service(sdbus::ObjectPath const& device, + std::string const& uuid, + std::function<void(bool)> callback) override { + auto it = devices_.find(device); + if (it == devices_.end() || it->second->adapter() != primary_adapter_) { + callback(false); + return; + } + delegate_.agent_authorize_service(*it->second, uuid, std::move(callback)); + } + + void agent_cancel() override { delegate_.agent_cancel(); } + + void before(looper::HookHelper& helper) override { + auto data = conn_->getEventLoopPollData(); + + uint8_t events = 0; + // NOLINTNEXTLINE(misc-include-cleaner) + if (data.events & (POLLIN | POLLPRI | POLLHUP)) + events |= looper::EVENT_READ; + // NOLINTNEXTLINE(misc-include-cleaner) + if (data.events & POLLOUT) + events |= looper::EVENT_WRITE; + // NOLINTNEXTLINE(misc-include-cleaner) + if (data.events & (POLLERR | POLLNVAL)) + events |= looper::EVENT_ERROR; + + if (data.fd != -1 && events != 0) + helper.monitor(data.fd, events); + + helper.monitor(data.eventFd, looper::EVENT_READ); + + auto timeout = data.getRelativeTimeout(); + if (timeout != std::chrono::microseconds::max()) { + helper.timeout( + std::chrono::duration_cast<std::chrono::milliseconds>(timeout)); + } + } + + void after() override { + while (conn_->processPendingEvent()) + ; + } + + void update_primary_adapter() { + bool const was_empty = primary_adapter_.empty(); + primary_adapter_.clear(); + + auto cfg_adapter = cfg_.get("bluetooth.adapter"); + if (cfg_adapter.has_value()) { + for (auto const& pair : adapters_) { + if (pair.second->address() == cfg_adapter.value()) { + primary_adapter_ = pair.first; + delegate_.new_adapter(pair.second.get()); + return; + } + } + } else { + // Pick first (map is sorted) that is pairable. + for (auto const& pair : adapters_) { + if (pair.second->pairable()) { + primary_adapter_ = pair.first; + delegate_.new_adapter(pair.second.get()); + return; + } + } + } + + if (!was_empty) + delegate_.new_adapter(nullptr); + } + + logger::Logger& logger_; + cfg::Config const& cfg_; + looper::Looper& looper_; + Manager::Delegate& delegate_; + std::unique_ptr<sdbus::IConnection> conn_; + std::unique_ptr<ObjectManagerProxy> root_proxy_; + std::unique_ptr<AgentManagerProxy> agent_manager_proxy_; + std::unique_ptr<AgentObject> agent_; + std::map<sdbus::ObjectPath, std::unique_ptr<DeviceProxy>> devices_; + std::map<sdbus::ObjectPath, std::unique_ptr<AdapterProxy>> adapters_; + sdbus::ObjectPath primary_adapter_; +}; + +} // namespace + +std::unique_ptr<Manager> create_manager(logger::Logger& logger, + cfg::Config const& cfg, + looper::Looper& looper, + Manager::Delegate& delegate) { + return std::make_unique<ManagerImpl>(logger, cfg, looper, delegate); +} + +} // namespace bt |
