[PATCH BlueZ v2 4/5] rap: Add CS Distance provider D-Bus API

Naga Bhavani Akella <[email protected]>
Newsgroups org.kernel.vger.linux-bluetooth
Message-ID <[email protected]>
From: Naga Bhavani Akella <[email protected]>

Introduce org.bluez.CSDistance1 and org.bluez.CSDistanceProvider1/
CSDistanceProviderManager1 D-Bus interfaces following the existing
Battery Provider design.

External providers can register a root path
via RegisterDistanceProvider and expose per-device
CSDistanceProvider1 objects; updates to their DistanceMeters
property are mirrored onto exported CSDistance1 objects via
btd_cs_distance_update(), emitting a PropertiesChanged signal.

The CS provider manager is wired into the adapter lifecycle,
created on adapter setup and destroyed on adapter
removal, mirroring how battery_provider_manager is handled.
---
 Makefile.am       |   1 +
 src/adapter.c     |  12 +
 src/cs_distance.c | 578 ++++++++++++++++++++++++++++++++++++++++++++++
 src/cs_distance.h |  23 ++
 4 files changed, 614 insertions(+)
 create mode 100644 src/cs_distance.c
 create mode 100644 src/cs_distance.h

diff --git a/Makefile.am b/Makefile.am
index f003b041c..b6ac8b866 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -332,6 +332,7 @@ bluetoothd_internal_sources = \
 			src/eir.h src/eir.c \
 			src/adv_monitor.h src/adv_monitor.c \
 			src/battery.h src/battery.c \
+			src/cs_distance.h src/cs_distance.c \
 			src/settings.h src/settings.c \
 			src/set.h src/set.c \
 			src/bearer.h src/bearer.c
diff --git a/src/adapter.c b/src/adapter.c
index c21b3e7fb..3b35c7ca0 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -67,6 +67,7 @@
 #include "adv_monitor.h"
 #include "eir.h"
 #include "battery.h"
+#include "cs_distance.h"
 
 #define MODE_OFF		0x00
 #define MODE_CONNECTABLE	0x01
@@ -336,6 +337,8 @@ struct btd_adapter {
 
 	struct btd_battery_provider_manager *battery_provider_manager;
 
+	struct btd_cs_distance_provider_manager *cs_distance_provider_manager;
+
 	GHashTable *allowed_uuid_set;	/* Set of allowed service UUIDs */
 
 	gboolean initialized;
@@ -7196,6 +7199,7 @@ static void adapter_remove(struct btd_adapter *adapter)
 {
 	GSList *l;
 	struct gatt_db *db;
+	struct btd_cs_distance_provider_manager *cs_distance_manager;
 
 	DBG("Removing adapter %s", adapter->path);
 
@@ -7230,6 +7234,11 @@ static void adapter_remove(struct btd_adapter *adapter)
 	btd_battery_provider_manager_destroy(adapter->battery_provider_manager);
 	adapter->battery_provider_manager = NULL;
 
+	cs_distance_manager = adapter->cs_distance_provider_manager;
+
+	btd_cs_distance_provider_manager_destroy(cs_distance_manager);
+	adapter->cs_distance_provider_manager = NULL;
+
 	g_slist_free(adapter->pin_callbacks);
 	adapter->pin_callbacks = NULL;
 
@@ -9645,6 +9654,9 @@ static int adapter_register(struct btd_adapter *adapter)
 	adapter->battery_provider_manager =
 		btd_battery_provider_manager_create(adapter);
 
+	adapter->cs_distance_provider_manager =
+		btd_cs_distance_provider_manager_create(adapter);
+
 	/* Don't start GATT database and advertising managers on
 	 * non-LE controllers.
 	 */
diff --git a/src/cs_distance.c b/src/cs_distance.c
new file mode 100644
index 000000000..ac2fd1d9c
--- /dev/null
+++ b/src/cs_distance.c
@@ -0,0 +1,578 @@
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <glib.h>
+
+#include "gdbus/gdbus.h"
+#include "bluetooth/bluetooth.h"
+#include "src/shared/queue.h"
+#include "src/shared/util.h"
+#include "cs_distance.h"
+#include "dbus-common.h"
+#include "adapter.h"
+#include "device.h"
+#include "log.h"
+#include "error.h"
+
+#define CS_DISTANCE_INTERFACE "org.bluez.CSDistance1"
+#define CS_DISTANCE_PROVIDER_INTERFACE "org.bluez.CSDistanceProvider1"
+#define CS_DISTANCE_PROVIDER_MANAGER_INTERFACE \
+	"org.bluez.CSDistanceProviderManager1"
+
+struct btd_cs_distance {
+	char *path; /* D-Bus object path */
+	double meters; /* Estimated distance to the remote device */
+	bool has_value; /* false until the first estimate is reported */
+	char *provider_path; /* The provider root path, if any */
+};
+
+struct btd_cs_distance_provider_manager {
+	struct btd_adapter *adapter; /* Does not own pointer */
+	struct queue *providers;
+};
+
+struct cs_distance_provider {
+	struct btd_cs_distance_provider_manager *manager; /* Does not own */
+
+	char *owner; /* Owner D-Bus address */
+	char *path; /* D-Bus object path */
+
+	GDBusClient *client;
+};
+
+static struct queue *distances;
+
+static void provider_disconnect_cb(DBusConnection *conn, void *user_data);
+
+static void distance_add(struct btd_cs_distance *distance)
+{
+	if (!distances)
+		distances = queue_new();
+
+	queue_push_head(distances, distance);
+}
+
+static void distance_remove(struct btd_cs_distance *distance)
+{
+	queue_remove(distances, distance);
+	if (queue_isempty(distances)) {
+		queue_destroy(distances, NULL);
+		distances = NULL;
+	}
+}
+
+static bool match_path(const void *data, const void *user_data)
+{
+	const struct btd_cs_distance *distance = data;
+	const char *path = user_data;
+
+	return g_strcmp0(distance->path, path) == 0;
+}
+
+static struct btd_cs_distance *distance_new(const char *path,
+					    const char *provider_path)
+{
+	struct btd_cs_distance *distance;
+
+	distance = new0(struct btd_cs_distance, 1);
+	distance->path = g_strdup(path);
+	if (provider_path)
+		distance->provider_path = g_strdup(provider_path);
+
+	return distance;
+}
+
+static void distance_free(struct btd_cs_distance *distance)
+{
+	if (distance->path)
+		g_free(distance->path);
+
+	if (distance->provider_path)
+		g_free(distance->provider_path);
+
+	free(distance);
+}
+
+static gboolean property_distance_get(const GDBusPropertyTable *property,
+				      DBusMessageIter *iter, void *data)
+{
+	struct btd_cs_distance *distance = data;
+
+	dbus_message_iter_append_basic(iter, DBUS_TYPE_DOUBLE,
+				       &distance->meters);
+
+	return TRUE;
+}
+
+static gboolean property_distance_exists(const GDBusPropertyTable *property,
+					 void *data)
+{
+	struct btd_cs_distance *distance = data;
+
+	return distance->has_value;
+}
+
+static const GDBusPropertyTable cs_distance_properties[] = {
+	{ "DistanceMeters", "d", property_distance_get, NULL,
+	  property_distance_exists },
+	{}
+};
+
+struct btd_cs_distance *btd_cs_distance_register(const char *path,
+						 const char *provider_path)
+{
+	struct btd_cs_distance *distance;
+
+	DBG("path = %s", path);
+
+	if (queue_find(distances, match_path, path)) {
+		error("error registering CS distance: path exists");
+		return NULL;
+	}
+
+	if (!g_str_has_prefix(path, "/")) {
+		error("error registering CS distance: "
+		      "invalid D-Bus object path");
+		return NULL;
+	}
+
+	distance = distance_new(path, provider_path);
+	distance_add(distance);
+
+	if (!g_dbus_register_interface(btd_get_dbus_connection(),
+				       distance->path, CS_DISTANCE_INTERFACE,
+				       NULL, NULL, cs_distance_properties,
+				       distance, NULL)) {
+		error("error registering D-Bus interface for %s",
+		      distance->path);
+
+		distance_remove(distance);
+		distance_free(distance);
+
+		return NULL;
+	}
+
+	DBG("registered CSDistance object: %s", distance->path);
+
+	return distance;
+}
+
+bool btd_cs_distance_unregister(struct btd_cs_distance *distance)
+{
+	DBG("path = %s", distance->path);
+
+	if (!queue_find(distances, NULL, distance)) {
+		error("error unregistering CS distance: "
+		      "distance %s is not registered",
+		      distance->path);
+		return false;
+	}
+
+	if (!g_dbus_unregister_interface(btd_get_dbus_connection(),
+					 distance->path,
+					 CS_DISTANCE_INTERFACE)) {
+		error("error unregistering CS distance %s from "
+		      "D-Bus interface", distance->path);
+		return false;
+	}
+
+	distance_remove(distance);
+	distance_free(distance);
+
+	return true;
+}
+
+bool btd_cs_distance_update(struct btd_cs_distance *distance, double meters)
+{
+	DBG("path = %s", distance->path);
+
+	if (!queue_find(distances, NULL, distance)) {
+		error("error updating CS distance: distance is not "
+		      "registered");
+		return false;
+	}
+
+	distance->meters = meters;
+	distance->has_value = true;
+
+	info("CS distance updated: path = %s, meters = %f", distance->path,
+	     meters);
+	fprintf(stderr, "CS distance updated: path = %s, meters = %f\n",
+		distance->path, meters);
+
+	g_dbus_emit_property_changed(btd_get_dbus_connection(),
+				     distance->path, CS_DISTANCE_INTERFACE,
+				     "DistanceMeters");
+
+	return true;
+}
+
+static struct btd_cs_distance *find_distance_by_path(const char *path)
+{
+	return queue_find(distances, match_path, path);
+}
+
+static void provided_distance_property_changed_cb(GDBusProxy *proxy,
+						   const char *name,
+						   DBusMessageIter *iter,
+						   void *user_data)
+{
+	double meters = 0;
+	const char *export_path;
+	DBusMessageIter dev_iter;
+
+	if (g_dbus_proxy_get_property(proxy, "Device", &dev_iter) == FALSE)
+		return;
+
+	dbus_message_iter_get_basic(&dev_iter, &export_path);
+
+	if (strcmp(name, "DistanceMeters") != 0)
+		return;
+
+	if (iter) {
+		if (dbus_message_iter_get_arg_type(iter) != DBUS_TYPE_DOUBLE)
+			return;
+
+		dbus_message_iter_get_basic(iter, &meters);
+	}
+
+	DBG("CS distance changed on %s, meters = %f",
+	    g_dbus_proxy_get_path(proxy), meters);
+
+	btd_cs_distance_update(find_distance_by_path(export_path), meters);
+}
+
+static void provided_distance_added_cb(GDBusProxy *proxy, void *user_data)
+{
+	struct cs_distance_provider *provider = user_data;
+	struct btd_cs_distance *distance;
+	struct btd_device *device;
+	const char *path = g_dbus_proxy_get_path(proxy);
+	const char *export_path;
+	double meters;
+	DBusMessageIter iter;
+
+	if (strcmp(g_dbus_proxy_get_interface(proxy),
+		   CS_DISTANCE_PROVIDER_INTERFACE) != 0)
+		return;
+
+	if (g_dbus_proxy_get_property(proxy, "Device", &iter) == FALSE) {
+		warn("CS distance object %s does not specify device path",
+		     path);
+		return;
+	}
+
+	dbus_message_iter_get_basic(&iter, &export_path);
+
+	device = btd_adapter_find_device_by_path(provider->manager->adapter,
+						 export_path);
+	if (!device || device_is_temporary(device)) {
+		warn("Ignoring non-existent device path for CS distance %s",
+		     export_path);
+		return;
+	}
+
+	if (find_distance_by_path(export_path)) {
+		DBG("CS distance for %s is already provided, ignoring the "
+		    "new one", export_path);
+		return;
+	}
+
+	g_dbus_proxy_set_property_watch(proxy,
+		provided_distance_property_changed_cb, provider);
+
+	distance = btd_cs_distance_register(export_path, provider->path);
+
+	DBG("provided CS distance added %s", path);
+
+	/* DistanceMeters property may not be immediately available, that's
+	 * okay since we monitor changes to this property.
+	 */
+	if (g_dbus_proxy_get_property(proxy, "DistanceMeters", &iter) == FALSE)
+		return;
+
+	dbus_message_iter_get_basic(&iter, &meters);
+
+	btd_cs_distance_update(distance, meters);
+}
+
+static void provided_distance_removed_cb(GDBusProxy *proxy, void *user_data)
+{
+	struct cs_distance_provider *provider = user_data;
+	struct btd_cs_distance *distance;
+	const char *export_path;
+	DBusMessageIter iter;
+
+	if (strcmp(g_dbus_proxy_get_interface(proxy),
+		   CS_DISTANCE_PROVIDER_INTERFACE) != 0)
+		return;
+
+	if (g_dbus_proxy_get_property(proxy, "Device", &iter) == FALSE)
+		return;
+
+	dbus_message_iter_get_basic(&iter, &export_path);
+
+	DBG("provided CS distance removed %s", g_dbus_proxy_get_path(proxy));
+
+	distance = find_distance_by_path(export_path);
+	if (!distance)
+		return;
+
+	if (g_strcmp0(distance->provider_path, provider->path) != 0)
+		return;
+
+	g_dbus_proxy_set_property_watch(proxy, NULL, NULL);
+
+	btd_cs_distance_unregister(distance);
+}
+
+static bool match_provider_path(const void *data, const void *user_data)
+{
+	const struct cs_distance_provider *provider = data;
+	const char *path = user_data;
+
+	return strcmp(provider->path, path) == 0;
+}
+
+static void unregister_if_path_has_prefix(void *data, void *user_data)
+{
+	struct btd_cs_distance *distance = data;
+	struct cs_distance_provider *provider = user_data;
+
+	if (g_strcmp0(distance->provider_path, provider->path) == 0)
+		btd_cs_distance_unregister(distance);
+}
+
+static void cs_distance_provider_free(gpointer data)
+{
+	struct cs_distance_provider *provider = data;
+
+	/* Unregister distances under the root path of provider->path */
+	queue_foreach(distances, unregister_if_path_has_prefix, provider);
+
+	if (provider->owner)
+		g_free(provider->owner);
+
+	if (provider->path)
+		g_free(provider->path);
+
+	if (provider->client) {
+		g_dbus_client_set_disconnect_watch(provider->client, NULL,
+						   NULL);
+		g_dbus_client_set_proxy_handlers(provider->client, NULL, NULL,
+						 NULL, NULL);
+		g_dbus_client_unref(provider->client);
+	}
+
+	free(provider);
+}
+
+static struct cs_distance_provider *
+cs_distance_provider_new(DBusConnection *conn,
+			 struct btd_cs_distance_provider_manager *manager,
+			 const char *path, const char *sender)
+{
+	struct cs_distance_provider *provider;
+
+	provider = new0(struct cs_distance_provider, 1);
+	provider->manager = manager;
+	provider->owner = g_strdup(sender);
+	provider->path = g_strdup(path);
+
+	provider->client = g_dbus_client_new_full(conn, sender, path, path);
+
+	if (!provider->client) {
+		error("error creating D-Bus client %s", path);
+		cs_distance_provider_free(provider);
+		return NULL;
+	}
+
+	g_dbus_client_set_disconnect_watch(provider->client,
+					   provider_disconnect_cb, provider);
+
+	g_dbus_client_set_proxy_handlers(provider->client,
+					 provided_distance_added_cb,
+					 provided_distance_removed_cb, NULL,
+					 provider);
+
+	return provider;
+}
+
+static void provider_disconnect_cb(DBusConnection *conn, void *user_data)
+{
+	struct cs_distance_provider *provider = user_data;
+	struct btd_cs_distance_provider_manager *manager = provider->manager;
+
+	DBG("CS distance provider client disconnected %s root path %s",
+	    provider->owner, provider->path);
+
+	if (!queue_find(manager->providers, NULL, provider)) {
+		warn("Disconnection on a non-existing provider %s",
+		     provider->path);
+		return;
+	}
+
+	queue_remove(manager->providers, provider);
+	cs_distance_provider_free(provider);
+}
+
+static DBusMessage *register_distance_provider(DBusConnection *conn,
+						DBusMessage *msg,
+						void *user_data)
+{
+	struct btd_cs_distance_provider_manager *manager = user_data;
+	const char *sender = dbus_message_get_sender(msg);
+	DBusMessageIter args;
+	const char *path;
+	struct cs_distance_provider *provider;
+
+	if (!dbus_message_iter_init(msg, &args))
+		return btd_error_invalid_args(msg);
+
+	if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
+		return btd_error_invalid_args(msg);
+
+	dbus_message_iter_get_basic(&args, &path);
+
+	DBG("register CS distance provider path = %s", path);
+
+	if (!g_str_has_prefix(path, "/"))
+		return btd_error_invalid_args(msg);
+
+	if (queue_find(manager->providers, match_provider_path, path)) {
+		return dbus_message_new_error(msg,
+					      ERROR_INTERFACE ".AlreadyExists",
+					      "Provider already exists");
+	}
+
+	provider = cs_distance_provider_new(conn, manager, path, sender);
+	queue_push_head(manager->providers, provider);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static DBusMessage *unregister_distance_provider(DBusConnection *conn,
+						  DBusMessage *msg,
+						  void *user_data)
+{
+	struct btd_cs_distance_provider_manager *manager = user_data;
+	const char *sender = dbus_message_get_sender(msg);
+	DBusMessageIter args;
+	const char *path;
+	struct cs_distance_provider *provider;
+
+	if (!dbus_message_iter_init(msg, &args))
+		return btd_error_invalid_args(msg);
+
+	if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_OBJECT_PATH)
+		return btd_error_invalid_args(msg);
+
+	dbus_message_iter_get_basic(&args, &path);
+
+	DBG("unregister CS distance provider path = %s", path);
+
+	provider = queue_find(manager->providers, match_provider_path, path);
+	if (!provider || strcmp(provider->owner, sender) != 0) {
+		return dbus_message_new_error(msg,
+					      ERROR_INTERFACE ".DoesNotExist",
+					      "Provider does not exist");
+	}
+
+	queue_remove(manager->providers, provider);
+	cs_distance_provider_free(provider);
+
+	return dbus_message_new_method_return(msg);
+}
+
+static const GDBusMethodTable methods[] = {
+	{ GDBUS_METHOD("RegisterDistanceProvider",
+				    GDBUS_ARGS({ "provider", "o" }), NULL,
+				    register_distance_provider) },
+	{ GDBUS_METHOD("UnregisterDistanceProvider",
+				    GDBUS_ARGS({ "provider", "o" }), NULL,
+				    unregister_distance_provider) },
+	{}
+};
+
+static struct btd_cs_distance_provider_manager *
+manager_new(struct btd_adapter *adapter)
+{
+	struct btd_cs_distance_provider_manager *manager;
+
+	DBG("");
+
+	manager = new0(struct btd_cs_distance_provider_manager, 1);
+	manager->adapter = adapter;
+	manager->providers = queue_new();
+
+	return manager;
+}
+
+static void manager_free(struct btd_cs_distance_provider_manager *manager)
+{
+	if (!manager)
+		return;
+
+	DBG("");
+
+	queue_destroy(manager->providers, cs_distance_provider_free);
+
+	free(manager);
+}
+
+struct btd_cs_distance_provider_manager *
+btd_cs_distance_provider_manager_create(struct btd_adapter *adapter)
+{
+	struct btd_cs_distance_provider_manager *manager;
+
+	if (!adapter)
+		return NULL;
+
+	manager = manager_new(adapter);
+	if (!manager)
+		return NULL;
+
+	if (!g_dbus_register_interface(btd_get_dbus_connection(),
+				       adapter_get_path(manager->adapter),
+				       CS_DISTANCE_PROVIDER_MANAGER_INTERFACE,
+				       methods, NULL, NULL, manager, NULL)) {
+		error("error registering "
+		      CS_DISTANCE_PROVIDER_MANAGER_INTERFACE " interface");
+		manager_free(manager);
+		return NULL;
+	}
+
+	info("CS Distance Provider Manager created");
+
+	return manager;
+}
+
+void btd_cs_distance_provider_manager_destroy(struct
+	btd_cs_distance_provider_manager *manager)
+{
+	if (!manager)
+		return;
+
+	g_dbus_unregister_interface(btd_get_dbus_connection(),
+				    adapter_get_path(manager->adapter),
+				    CS_DISTANCE_PROVIDER_MANAGER_INTERFACE);
+
+	info("CS Distance Provider Manager destroyed");
+
+	manager_free(manager);
+}
diff --git a/src/cs_distance.h b/src/cs_distance.h
new file mode 100644
index 000000000..b3cccd5cb
--- /dev/null
+++ b/src/cs_distance.h
@@ -0,0 +1,23 @@
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (c) Qualcomm Technologies, Inc. and/or its subsidiaries.
+ *
+ *
+ */
+
+struct btd_adapter;
+struct btd_cs_distance;
+struct btd_cs_distance_provider_manager;
+
+struct btd_cs_distance *btd_cs_distance_register(const char *path,
+						 const char *provider_path);
+bool btd_cs_distance_unregister(struct btd_cs_distance *distance);
+bool btd_cs_distance_update(struct btd_cs_distance *distance, double meters);
+
+struct btd_cs_distance_provider_manager *
+btd_cs_distance_provider_manager_create(struct btd_adapter *adapter);
+void btd_cs_distance_provider_manager_destroy(struct
+	btd_cs_distance_provider_manager *manager);
--
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.