diff --git a/src/gatt-database.c b/src/gatt-database.c
index e07c70f..867cc7e 100644
--- a/src/gatt-database.c
+++ b/src/gatt-database.c
#include <stdint.h>
#include <stdlib.h>
+#include <errno.h>
#include "lib/bluetooth.h"
#include "lib/sdp.h"
#include "device.h"
#include "gatt-database.h"
#include "dbus-common.h"
+#include "profile.h"
#ifndef ATT_CID
#define ATT_CID 4
struct gatt_db_attribute *svc_chngd;
struct gatt_db_attribute *svc_chngd_ccc;
struct queue *services;
+ struct queue *profiles;
};
struct external_service {
struct queue *descs;
};
+struct external_profile {
+ struct btd_gatt_database *database;
+ char *owner;
+ char *path; /* Path to GattProfile1 */
+ unsigned int id;
+ struct queue *profiles; /* btd_profile list */
+};
+
struct external_chrc {
struct external_service *service;
char *path;
free(service);
}
+static void profile_remove(void *data)
+{
+ struct btd_profile *p = data;
+
+ DBG("Removed \"%s\"", p->name);
+
+ adapter_foreach(adapter_remove_profile, p);
+
+ g_free((void *) p->name);
+ g_free((void *) p->remote_uuid);
+
+ free(p);
+}
+
+static void profile_release(struct external_profile *profile)
+{
+ DBusMessage *msg;
+
+ if (!profile->id)
+ return;
+
+ DBG("Releasing \"%s\"", profile->owner);
+
+ g_dbus_remove_watch(btd_get_dbus_connection(), profile->id);
+
+ msg = dbus_message_new_method_call(profile->owner, profile->path,
+ "org.bluez.GattProfile1",
+ "Release");
+ if (msg)
+ g_dbus_send_message(btd_get_dbus_connection(), msg);
+}
+
+static void profile_free(void *data)
+{
+ struct external_profile *profile = data;
+
+ queue_destroy(profile->profiles, profile_remove);
+
+ profile_release(profile);
+
+ g_free(profile->owner);
+ g_free(profile->path);
+
+ free(profile);
+}
+
static void gatt_database_free(void *data)
{
struct btd_gatt_database *database = data;
queue_destroy(database->device_states, device_state_free);
queue_destroy(database->services, service_free);
+ queue_destroy(database->profiles, profile_free);
queue_destroy(database->ccc_callbacks, ccc_cb_free);
database->device_states = NULL;
database->ccc_callbacks = NULL;
return dbus_message_new_method_return(msg);
}
+static void profile_exited(DBusConnection *conn, void *user_data)
+{
+ struct external_profile *profile = user_data;
+
+ DBG("\"%s\" exited", profile->owner);
+
+ profile->id = 0;
+
+ queue_remove(profile->database->profiles, profile);
+
+ profile_free(profile);
+}
+
+static int profile_add(struct external_profile *profile, const char *uuid)
+{
+ struct btd_profile *p;
+
+ p = new0(struct btd_profile, 1);
+ if (!p)
+ return -ENOMEM;
+
+ /* Assign directly to avoid having extra fields */
+ p->name = (const void *) g_strdup_printf("%s%s/%s", profile->owner,
+ profile->path, uuid);
+ if (!p->name)
+ return -ENOMEM;
+
+ p->remote_uuid = (const void *) g_strdup(uuid);
+ if (!p->remote_uuid)
+ return -ENOMEM;
+
+ p->auto_connect = true;
+
+ queue_push_tail(profile->profiles, p);
+
+ DBG("Added \"%s\"", p->name);
+
+ return 0;
+}
+
+static void add_profile(void *data, void *user_data)
+{
+ struct btd_adapter *adapter = user_data;
+
+ adapter_add_profile(adapter, data);
+}
+
+static int profile_create(DBusConnection *conn,
+ struct btd_gatt_database *database,
+ const char *sender, const char *path,
+ DBusMessageIter *iter)
+{
+ struct external_profile *profile;
+ DBusMessageIter uuids;
+
+ if (!path || !g_str_has_prefix(path, "/"))
+ return -EINVAL;
+
+ profile = new0(struct external_profile, 1);
+ if (!profile)
+ return -ENOMEM;
+
+ profile->owner = g_strdup(sender);
+ if (!profile->owner)
+ goto fail;
+
+ profile->path = g_strdup(path);
+ if (!profile->path)
+ goto fail;
+
+ profile->profiles = queue_new();
+ if (!profile->profiles)
+ goto fail;
+
+ profile->database = database;
+ profile->id = g_dbus_add_disconnect_watch(conn, sender, profile_exited,
+ profile, NULL);
+
+ dbus_message_iter_recurse(iter, &uuids);
+
+ while (dbus_message_iter_get_arg_type(&uuids) == DBUS_TYPE_STRING) {
+ const char *uuid;
+
+ dbus_message_iter_get_basic(&uuids, &uuid);
+
+ if (profile_add(profile, uuid) < 0)
+ goto fail;
+
+ dbus_message_iter_next(&uuids);
+ }
+
+ if (queue_isempty(profile->profiles))
+ goto fail;
+
+ queue_foreach(profile->profiles, add_profile, database->adapter);
+ queue_push_tail(database->profiles, profile);
+
+ return 0;
+
+fail:
+ profile_free(profile);
+ return -EINVAL;
+}
+
+static bool match_profile(const void *a, const void *b)
+{
+ const struct external_profile *profile = a;
+ const struct svc_match_data *data = b;
+
+ return g_strcmp0(profile->path, data->path) == 0 &&
+ g_strcmp0(profile->owner, data->sender) == 0;
+}
+
+static DBusMessage *manager_register_profile(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct btd_gatt_database *database = user_data;
+ const char *sender = dbus_message_get_sender(msg);
+ DBusMessageIter args;
+ const char *path;
+ struct svc_match_data match_data;
+
+ DBG("sender %s", sender);
+
+ 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);
+
+ match_data.path = path;
+ match_data.sender = sender;
+
+ if (queue_find(database->profiles, match_profile, &match_data))
+ return btd_error_already_exists(msg);
+
+ dbus_message_iter_next(&args);
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_ARRAY)
+ return btd_error_invalid_args(msg);
+
+ if (profile_create(conn, database, sender, path, &args) < 0)
+ return btd_error_failed(msg, "Failed to register profile");
+
+ return dbus_message_new_method_return(msg);
+}
+
static const GDBusMethodTable manager_methods[] = {
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("RegisterService",
GDBUS_ARGS({ "service", "o" }, { "options", "a{sv}" }),
{ GDBUS_EXPERIMENTAL_ASYNC_METHOD("UnregisterService",
GDBUS_ARGS({ "service", "o" }),
NULL, manager_unregister_service) },
+ { GDBUS_EXPERIMENTAL_ASYNC_METHOD("RegisterProfile",
+ GDBUS_ARGS({ "profile", "o" }, { "UUIDs", "as" },
+ { "options", "a{sv}" }), NULL,
+ manager_register_profile) },
{ }
};
if (!database->services)
goto fail;
+ database->profiles = queue_new();
+ if (!database->profiles)
+ goto fail;
+
database->ccc_callbacks = queue_new();
if (!database->ccc_callbacks)
goto fail;