summaryrefslogtreecommitdiff
path: root/profiles/health
diff options
context:
space:
mode:
Diffstat (limited to 'profiles/health')
-rw-r--r--profiles/health/hdp.c2247
-rw-r--r--profiles/health/hdp.h32
-rw-r--r--profiles/health/hdp_main.c59
-rw-r--r--profiles/health/hdp_manager.c97
-rw-r--r--profiles/health/hdp_manager.h24
-rw-r--r--profiles/health/hdp_types.h122
-rw-r--r--profiles/health/hdp_util.c1218
-rw-r--r--profiles/health/hdp_util.h58
-rw-r--r--profiles/health/mcap.c2194
-rw-r--r--profiles/health/mcap.h164
-rw-r--r--profiles/health/mcap_internal.h137
-rw-r--r--profiles/health/mcap_lib.h224
-rw-r--r--profiles/health/mcap_sync.c1012
13 files changed, 7588 insertions, 0 deletions
diff --git a/profiles/health/hdp.c b/profiles/health/hdp.c
new file mode 100644
index 000000000..23162040b
--- /dev/null
+++ b/profiles/health/hdp.c
@@ -0,0 +1,2247 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <sdpd.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#include <bluetooth/l2cap.h>
+#include <gdbus.h>
+#include <dbus-common.h>
+#include <log.h>
+#include <error.h>
+#include <adapter.h>
+#include <device.h>
+#include <btio.h>
+
+#include "mcap_lib.h"
+#include "hdp_types.h"
+#include "hdp_util.h"
+#include "hdp.h"
+#include "mcap.h"
+
+#define ECHO_TIMEOUT 1 /* second */
+#define HDP_ECHO_LEN 15
+
+static DBusConnection *connection = NULL;
+
+static GSList *applications = NULL;
+static GSList *devices = NULL;
+static uint8_t next_app_id = HDP_MDEP_INITIAL;
+
+static GSList *adapters;
+
+static gboolean update_adapter(struct hdp_adapter *adapter);
+static struct hdp_device *create_health_device(DBusConnection *conn,
+ struct btd_device *device);
+static void free_echo_data(struct hdp_echo_data *edata);
+
+struct hdp_create_dc {
+ DBusConnection *conn;
+ DBusMessage *msg;
+ struct hdp_application *app;
+ struct hdp_device *dev;
+ uint8_t config;
+ uint8_t mdep;
+ guint ref;
+ mcap_mdl_operation_cb cb;
+};
+
+struct hdp_tmp_dc_data {
+ DBusConnection *conn;
+ DBusMessage *msg;
+ struct hdp_channel *hdp_chann;
+ guint ref;
+ mcap_mdl_operation_cb cb;
+};
+
+struct hdp_echo_data {
+ gboolean echo_done; /* Is a echo was already done */
+ gpointer buf; /* echo packet sent */
+ uint tid; /* echo timeout */
+};
+
+static struct hdp_channel *hdp_channel_ref(struct hdp_channel *chan)
+{
+ if (chan == NULL)
+ return NULL;
+
+ chan->ref++;
+
+ DBG("health_channel_ref(%p): ref=%d", chan, chan->ref);
+ return chan;
+}
+
+static void free_health_channel(struct hdp_channel *chan)
+{
+ if (chan->mdep == HDP_MDEP_ECHO) {
+ free_echo_data(chan->edata);
+ chan->edata = NULL;
+ }
+
+ mcap_mdl_unref(chan->mdl);
+ hdp_application_unref(chan->app);
+ health_device_unref(chan->dev);
+ g_free(chan->path);
+ g_free(chan);
+}
+
+static void hdp_channel_unref(struct hdp_channel *chan)
+{
+ if (chan == NULL)
+ return;
+
+ chan->ref --;
+ DBG("health_channel_unref(%p): ref=%d", chan, chan->ref);
+
+ if (chan->ref > 0)
+ return;
+
+ free_health_channel(chan);
+}
+
+static void free_hdp_create_dc(struct hdp_create_dc *dc_data)
+{
+ dbus_message_unref(dc_data->msg);
+ dbus_connection_unref(dc_data->conn);
+ hdp_application_unref(dc_data->app);
+ health_device_unref(dc_data->dev);
+
+ g_free(dc_data);
+}
+
+static struct hdp_create_dc *hdp_create_data_ref(struct hdp_create_dc *dc_data)
+{
+ dc_data->ref++;
+
+ DBG("hdp_create_data_ref(%p): ref=%d", dc_data, dc_data->ref);
+
+ return dc_data;
+}
+
+static void hdp_create_data_unref(struct hdp_create_dc *dc_data)
+{
+ dc_data->ref--;
+
+ DBG("hdp_create_data_unref(%p): ref=%d", dc_data, dc_data->ref);
+
+ if (dc_data->ref > 0)
+ return;
+
+ free_hdp_create_dc(dc_data);
+}
+
+static void free_hdp_conn_dc(struct hdp_tmp_dc_data *data)
+{
+ dbus_message_unref(data->msg);
+ dbus_connection_unref(data->conn);
+ hdp_channel_unref(data->hdp_chann);
+
+ g_free(data);
+}
+
+static struct hdp_tmp_dc_data *hdp_tmp_dc_data_ref(struct hdp_tmp_dc_data *data)
+{
+ data->ref++;
+
+ DBG("hdp_conn_data_ref(%p): ref=%d", data, data->ref);
+
+ return data;
+}
+
+static void hdp_tmp_dc_data_unref(struct hdp_tmp_dc_data *data)
+{
+ data->ref--;
+
+ DBG("hdp_conn_data_unref(%p): ref=%d", data, data->ref);
+
+ if (data->ref > 0)
+ return;
+
+ free_hdp_conn_dc(data);
+}
+
+static int cmp_app_id(gconstpointer a, gconstpointer b)
+{
+ const struct hdp_application *app = a;
+ const uint8_t *id = b;
+
+ return app->id - *id;
+}
+
+static int cmp_adapter(gconstpointer a, gconstpointer b)
+{
+ const struct hdp_adapter *hdp_adapter = a;
+ const struct btd_adapter *adapter = b;
+
+ if (hdp_adapter->btd_adapter == adapter)
+ return 0;
+
+ return -1;
+}
+
+static int cmp_device(gconstpointer a, gconstpointer b)
+{
+ const struct hdp_device *hdp_device = a;
+ const struct btd_device *device = b;
+
+ if (hdp_device->dev == device)
+ return 0;
+
+ return -1;
+}
+
+static gint cmp_dev_addr(gconstpointer a, gconstpointer dst)
+{
+ const struct hdp_device *device = a;
+ bdaddr_t addr;
+
+ device_get_address(device->dev, &addr, NULL);
+ return bacmp(&addr, dst);
+}
+
+static gint cmp_dev_mcl(gconstpointer a, gconstpointer mcl)
+{
+ const struct hdp_device *device = a;
+
+ if (mcl == device->mcl)
+ return 0;
+ return -1;
+}
+
+static gint cmp_chan_mdlid(gconstpointer a, gconstpointer b)
+{
+ const struct hdp_channel *chan = a;
+ const uint16_t *mdlid = b;
+
+ return chan->mdlid - *mdlid;
+}
+
+static gint cmp_chan_path(gconstpointer a, gconstpointer b)
+{
+ const struct hdp_channel *chan = a;
+ const char *path = b;
+
+ return g_ascii_strcasecmp(chan->path, path);
+}
+
+static gint cmp_chan_mdl(gconstpointer a, gconstpointer mdl)
+{
+ const struct hdp_channel *chan = a;
+
+ if (chan->mdl == mdl)
+ return 0;
+ return -1;
+}
+
+static uint8_t get_app_id(void)
+{
+ uint8_t id = next_app_id;
+
+ do {
+ GSList *l = g_slist_find_custom(applications, &id, cmp_app_id);
+
+ if (l == NULL) {
+ next_app_id = (id % HDP_MDEP_FINAL) + 1;
+ return id;
+ } else
+ id = (id % HDP_MDEP_FINAL) + 1;
+ } while (id != next_app_id);
+
+ /* No more ids available */
+ return 0;
+}
+
+static int cmp_app(gconstpointer a, gconstpointer b)
+{
+ const struct hdp_application *app = a;
+
+ return g_strcmp0(app->path, b);
+}
+
+static gboolean set_app_path(struct hdp_application *app)
+{
+ app->id = get_app_id();
+ if (app->id == 0)
+ return FALSE;
+ app->path = g_strdup_printf(MANAGER_PATH "/health_app_%d", app->id);
+
+ return TRUE;
+};
+
+static void device_unref_mcl(struct hdp_device *hdp_device)
+{
+ if (hdp_device->mcl == NULL)
+ return;
+
+ mcap_close_mcl(hdp_device->mcl, FALSE);
+ mcap_mcl_unref(hdp_device->mcl);
+ hdp_device->mcl = NULL;
+ hdp_device->mcl_conn = FALSE;
+}
+
+static void free_health_device(struct hdp_device *device)
+{
+ if (device->conn != NULL) {
+ dbus_connection_unref(device->conn);
+ device->conn = NULL;
+ }
+
+ if (device->dev != NULL) {
+ btd_device_unref(device->dev);
+ device->dev = NULL;
+ }
+
+ device_unref_mcl(device);
+
+ g_free(device);
+}
+
+static void remove_application(struct hdp_application *app)
+{
+ DBG("Application %s deleted", app->path);
+ hdp_application_unref(app);
+
+ g_slist_foreach(adapters, (GFunc) update_adapter, NULL);
+}
+
+static void client_disconnected(DBusConnection *conn, void *user_data)
+{
+ struct hdp_application *app = user_data;
+
+ DBG("Client disconnected from the bus, deleting hdp application");
+ applications = g_slist_remove(applications, app);
+
+ app->dbus_watcher = 0; /* Watcher shouldn't be freed in this case */
+ remove_application(app);
+}
+
+static DBusMessage *manager_create_application(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct hdp_application *app;
+ const char *name;
+ DBusMessageIter iter;
+ GError *err = NULL;
+
+ dbus_message_iter_init(msg, &iter);
+ app = hdp_get_app_config(&iter, &err);
+ if (err != NULL) {
+ g_error_free(err);
+ return btd_error_invalid_args(msg);
+ }
+
+ name = dbus_message_get_sender(msg);
+ if (name == NULL) {
+ hdp_application_unref(app);
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".HealthError",
+ "Can't get sender name");
+ }
+
+ if (!set_app_path(app)) {
+ hdp_application_unref(app);
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".HealthError",
+ "Can't get a valid id for the application");
+ }
+
+ app->oname = g_strdup(name);
+ app->conn = dbus_connection_ref(conn);
+
+ applications = g_slist_prepend(applications, app);
+
+ app->dbus_watcher = g_dbus_add_disconnect_watch(conn, name,
+ client_disconnected, app, NULL);
+ g_slist_foreach(adapters, (GFunc) update_adapter, NULL);
+
+ DBG("Health application created with id %s", app->path);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_OBJECT_PATH, &app->path,
+ DBUS_TYPE_INVALID);
+}
+
+static DBusMessage *manager_destroy_application(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ const char *path;
+ struct hdp_application *app;
+ GSList *l;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ l = g_slist_find_custom(applications, path, cmp_app);
+
+ if (l == NULL)
+ return g_dbus_create_error(msg,
+ ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call, "
+ "no such application");
+
+ app = l->data;
+ applications = g_slist_remove(applications, app);
+
+ remove_application(app);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static void manager_path_unregister(gpointer data)
+{
+ g_slist_foreach(applications, (GFunc) hdp_application_unref, NULL);
+
+ g_slist_free(applications);
+ applications = NULL;
+
+ g_slist_foreach(adapters, (GFunc) update_adapter, NULL);
+}
+
+static const GDBusMethodTable health_manager_methods[] = {
+ { GDBUS_METHOD("CreateApplication",
+ GDBUS_ARGS({ "config", "a{sv}" }),
+ GDBUS_ARGS({ "application", "o" }),
+ manager_create_application) },
+ { GDBUS_METHOD("DestroyApplication",
+ GDBUS_ARGS({ "application", "o" }), NULL,
+ manager_destroy_application) },
+ { }
+};
+
+static DBusMessage *channel_get_properties(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct hdp_channel *chan = user_data;
+ DBusMessageIter iter, dict;
+ DBusMessage *reply;
+ const char *path;
+ char *type;
+
+ reply = dbus_message_new_method_return(msg);
+ if (reply == NULL)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ path = device_get_path(chan->dev->dev);
+ dict_append_entry(&dict, "Device", DBUS_TYPE_OBJECT_PATH, &path);
+
+ path = chan->app->path;
+ dict_append_entry(&dict, "Application", DBUS_TYPE_OBJECT_PATH, &path);
+
+ if (chan->config == HDP_RELIABLE_DC)
+ type = g_strdup("Reliable");
+ else
+ type = g_strdup("Streaming");
+
+ dict_append_entry(&dict, "Type", DBUS_TYPE_STRING, &type);
+
+ g_free(type);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static void hdp_tmp_dc_data_destroy(gpointer data)
+{
+ struct hdp_tmp_dc_data *hdp_conn = data;
+
+ hdp_tmp_dc_data_unref(hdp_conn);
+}
+
+static void abort_mdl_cb(GError *err, gpointer data)
+{
+ if (err != NULL)
+ error("Aborting error: %s", err->message);
+}
+
+static void hdp_mdl_reconn_cb(struct mcap_mdl *mdl, GError *err, gpointer data)
+{
+ struct hdp_tmp_dc_data *dc_data = data;
+ DBusMessage *reply;
+ int fd;
+
+ if (err != NULL) {
+ struct hdp_channel *chan = dc_data->hdp_chann;
+ GError *gerr = NULL;
+
+ error("%s", err->message);
+ reply = g_dbus_create_error(dc_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "Cannot reconnect: %s", err->message);
+ g_dbus_send_message(dc_data->conn, reply);
+
+ /* Send abort request because remote side */
+ /* is now in PENDING state */
+ if (!mcap_mdl_abort(chan->mdl, abort_mdl_cb, NULL, NULL,
+ &gerr)) {
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ }
+ return;
+ }
+
+ fd = mcap_mdl_get_fd(dc_data->hdp_chann->mdl);
+ if (fd < 0) {
+ reply = g_dbus_create_error(dc_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "Cannot get file descriptor");
+ g_dbus_send_message(dc_data->conn, reply);
+ return;
+ }
+
+ reply = g_dbus_create_reply(dc_data->msg, DBUS_TYPE_UNIX_FD,
+ &fd, DBUS_TYPE_INVALID);
+ g_dbus_send_message(dc_data->conn, reply);
+
+ g_dbus_emit_signal(dc_data->conn,
+ device_get_path(dc_data->hdp_chann->dev->dev),
+ HEALTH_DEVICE, "ChannelConnected",
+ DBUS_TYPE_OBJECT_PATH, &dc_data->hdp_chann->path,
+ DBUS_TYPE_INVALID);
+}
+
+static void hdp_get_dcpsm_cb(uint16_t dcpsm, gpointer user_data, GError *err)
+{
+ struct hdp_tmp_dc_data *hdp_conn = user_data;
+ struct hdp_channel *hdp_chann = hdp_conn->hdp_chann;
+ GError *gerr = NULL;
+ uint8_t mode;
+
+ if (err != NULL) {
+ hdp_conn->cb(hdp_chann->mdl, err, hdp_conn);
+ return;
+ }
+
+ if (hdp_chann->config == HDP_RELIABLE_DC)
+ mode = L2CAP_MODE_ERTM;
+ else
+ mode = L2CAP_MODE_STREAMING;
+
+ if (mcap_connect_mdl(hdp_chann->mdl, mode, dcpsm, hdp_conn->cb,
+ hdp_tmp_dc_data_ref(hdp_conn),
+ hdp_tmp_dc_data_destroy, &gerr))
+ return;
+
+ hdp_tmp_dc_data_unref(hdp_conn);
+ hdp_conn->cb(hdp_chann->mdl, err, hdp_conn);
+ g_error_free(gerr);
+}
+
+static void device_reconnect_mdl_cb(struct mcap_mdl *mdl, GError *err,
+ gpointer data)
+{
+ struct hdp_tmp_dc_data *dc_data = data;
+ GError *gerr = NULL;
+ DBusMessage *reply;
+
+ if (err != NULL) {
+ reply = g_dbus_create_error(dc_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "Cannot reconnect: %s", err->message);
+ g_dbus_send_message(dc_data->conn, reply);
+ return;
+ }
+
+ dc_data->cb = hdp_mdl_reconn_cb;
+
+ if (hdp_get_dcpsm(dc_data->hdp_chann->dev, hdp_get_dcpsm_cb,
+ hdp_tmp_dc_data_ref(dc_data),
+ hdp_tmp_dc_data_destroy, &gerr))
+ return;
+
+ error("%s", gerr->message);
+
+ reply = g_dbus_create_error(dc_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "Cannot reconnect: %s", gerr->message);
+ g_dbus_send_message(dc_data->conn, reply);
+ hdp_tmp_dc_data_unref(dc_data);
+ g_error_free(gerr);
+
+ /* Send abort request because remote side is now in PENDING state */
+ if (!mcap_mdl_abort(mdl, abort_mdl_cb, NULL, NULL, &gerr)) {
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ }
+}
+
+static DBusMessage *channel_acquire_continue(struct hdp_tmp_dc_data *data,
+ GError *err)
+{
+ DBusMessage *reply;
+ GError *gerr = NULL;
+ int fd;
+
+ if (err != NULL) {
+ return g_dbus_create_error(data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "%s", err->message);
+ }
+
+ fd = mcap_mdl_get_fd(data->hdp_chann->mdl);
+ if (fd >= 0)
+ return g_dbus_create_reply(data->msg, DBUS_TYPE_UNIX_FD, &fd,
+ DBUS_TYPE_INVALID);
+
+ hdp_tmp_dc_data_ref(data);
+ if (mcap_reconnect_mdl(data->hdp_chann->mdl, device_reconnect_mdl_cb,
+ data, hdp_tmp_dc_data_destroy, &gerr))
+ return NULL;
+
+ hdp_tmp_dc_data_unref(data);
+ reply = g_dbus_create_error(data->msg, ERROR_INTERFACE ".HealthError",
+ "Cannot reconnect: %s", gerr->message);
+ g_error_free(gerr);
+
+ return reply;
+}
+
+static void channel_acquire_cb(gpointer data, GError *err)
+{
+ struct hdp_tmp_dc_data *dc_data = data;
+ DBusMessage *reply;
+
+ reply = channel_acquire_continue(data, err);
+
+ if (reply != NULL)
+ g_dbus_send_message(dc_data->conn, reply);
+}
+
+static DBusMessage *channel_acquire(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct hdp_channel *chan = user_data;
+ struct hdp_tmp_dc_data *dc_data;
+ GError *gerr = NULL;
+ DBusMessage *reply;
+
+ dc_data = g_new0(struct hdp_tmp_dc_data, 1);
+ dc_data->conn = dbus_connection_ref(conn);
+ dc_data->msg = dbus_message_ref(msg);
+ dc_data->hdp_chann = hdp_channel_ref(chan);
+
+ if (chan->dev->mcl_conn) {
+ reply = channel_acquire_continue(hdp_tmp_dc_data_ref(dc_data),
+ NULL);
+ hdp_tmp_dc_data_unref(dc_data);
+ return reply;
+ }
+
+ if (hdp_establish_mcl(chan->dev, channel_acquire_cb,
+ hdp_tmp_dc_data_ref(dc_data),
+ hdp_tmp_dc_data_destroy, &gerr))
+ return NULL;
+
+ reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
+ "%s", gerr->message);
+ hdp_tmp_dc_data_unref(dc_data);
+ g_error_free(gerr);
+
+ return reply;
+}
+
+static void close_mdl(struct hdp_channel *hdp_chann)
+{
+ int fd;
+
+ fd = mcap_mdl_get_fd(hdp_chann->mdl);
+ if (fd < 0)
+ return;
+
+ close(fd);
+}
+
+static DBusMessage *channel_release(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct hdp_channel *hdp_chann = user_data;
+
+ close_mdl(hdp_chann);
+
+ return g_dbus_create_reply(msg, DBUS_TYPE_INVALID);
+}
+
+static void free_echo_data(struct hdp_echo_data *edata)
+{
+ if (edata == NULL)
+ return;
+
+ if (edata->tid > 0)
+ g_source_remove(edata->tid);
+
+ if (edata->buf != NULL)
+ g_free(edata->buf);
+
+
+ g_free(edata);
+}
+
+static void health_channel_destroy(void *data)
+{
+ struct hdp_channel *hdp_chan = data;
+ struct hdp_device *dev = hdp_chan->dev;
+
+ DBG("Destroy Health Channel %s", hdp_chan->path);
+ if (g_slist_find(dev->channels, hdp_chan) == NULL)
+ goto end;
+
+ dev->channels = g_slist_remove(dev->channels, hdp_chan);
+
+ if (hdp_chan->mdep != HDP_MDEP_ECHO)
+ g_dbus_emit_signal(dev->conn, device_get_path(dev->dev),
+ HEALTH_DEVICE, "ChannelDeleted",
+ DBUS_TYPE_OBJECT_PATH, &hdp_chan->path,
+ DBUS_TYPE_INVALID);
+
+ if (hdp_chan == dev->fr) {
+ hdp_channel_unref(dev->fr);
+ dev->fr = NULL;
+ }
+
+end:
+ hdp_channel_unref(hdp_chan);
+}
+
+static const GDBusMethodTable health_channels_methods[] = {
+ { GDBUS_METHOD("GetProperties",
+ NULL, GDBUS_ARGS({ "properties", "a{sv}" }),
+ channel_get_properties) },
+ { GDBUS_ASYNC_METHOD("Acquire",
+ NULL, GDBUS_ARGS({ "fd", "h" }),
+ channel_acquire) },
+ { GDBUS_METHOD("Release", NULL, NULL, channel_release) },
+ { }
+};
+
+static struct hdp_channel *create_channel(struct hdp_device *dev,
+ uint8_t config,
+ struct mcap_mdl *mdl,
+ uint16_t mdlid,
+ struct hdp_application *app,
+ GError **err)
+{
+ struct hdp_channel *hdp_chann;
+
+ if (dev == NULL)
+ return NULL;
+
+ hdp_chann = g_new0(struct hdp_channel, 1);
+ hdp_chann->config = config;
+ hdp_chann->dev = health_device_ref(dev);
+ hdp_chann->mdlid = mdlid;
+
+ if (mdl != NULL)
+ hdp_chann->mdl = mcap_mdl_ref(mdl);
+
+ if (app != NULL) {
+ hdp_chann->mdep = app->id;
+ hdp_chann->app = hdp_application_ref(app);
+ } else
+ hdp_chann->edata = g_new0(struct hdp_echo_data, 1);
+
+ hdp_chann->path = g_strdup_printf("%s/chan%d",
+ device_get_path(hdp_chann->dev->dev),
+ hdp_chann->mdlid);
+
+ dev->channels = g_slist_append(dev->channels,
+ hdp_channel_ref(hdp_chann));
+
+ if (hdp_chann->mdep == HDP_MDEP_ECHO)
+ return hdp_channel_ref(hdp_chann);
+
+ if (!g_dbus_register_interface(dev->conn, hdp_chann->path,
+ HEALTH_CHANNEL,
+ health_channels_methods, NULL, NULL,
+ hdp_chann, health_channel_destroy)) {
+ g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR,
+ "Can't register the channel interface");
+ health_channel_destroy(hdp_chann);
+ return NULL;
+ }
+
+ return hdp_channel_ref(hdp_chann);
+}
+
+static void remove_channels(struct hdp_device *dev)
+{
+ struct hdp_channel *chan;
+ char *path;
+
+ while (dev->channels != NULL) {
+ chan = dev->channels->data;
+
+ path = g_strdup(chan->path);
+ if (!g_dbus_unregister_interface(dev->conn, path,
+ HEALTH_CHANNEL))
+ health_channel_destroy(chan);
+ g_free(path);
+ }
+}
+
+static void close_device_con(struct hdp_device *dev, gboolean cache)
+{
+ if (dev->mcl == NULL)
+ return;
+
+ mcap_close_mcl(dev->mcl, cache);
+ dev->mcl_conn = FALSE;
+
+ if (cache)
+ return;
+
+ device_unref_mcl(dev);
+ remove_channels(dev);
+
+ if (!dev->sdp_present) {
+ const char *path;
+
+ path = device_get_path(dev->dev);
+ g_dbus_unregister_interface(dev->conn, path, HEALTH_DEVICE);
+ }
+}
+
+static int send_echo_data(int sock, const void *buf, uint32_t size)
+{
+ const uint8_t *buf_b = buf;
+ uint32_t sent = 0;
+
+ while (sent < size) {
+ int n = write(sock, buf_b + sent, size - sent);
+ if (n < 0)
+ return -1;
+ sent += n;
+ }
+
+ return 0;
+}
+
+static gboolean serve_echo(GIOChannel *io_chan, GIOCondition cond,
+ gpointer data)
+{
+ struct hdp_channel *chan = data;
+ uint8_t buf[MCAP_DC_MTU];
+ int fd, len;
+
+ if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
+ hdp_channel_unref(chan);
+ return FALSE;
+ }
+
+ if (chan->edata->echo_done)
+ goto fail;
+
+ chan->edata->echo_done = TRUE;
+
+ fd = g_io_channel_unix_get_fd(io_chan);
+ len = read(fd, buf, sizeof(buf));
+
+ if (send_echo_data(fd, buf, len) >= 0)
+ return TRUE;
+
+fail:
+ close_device_con(chan->dev, FALSE);
+ hdp_channel_unref(chan);
+ return FALSE;
+}
+
+static gboolean check_channel_conf(struct hdp_channel *chan)
+{
+ GError *err = NULL;
+ GIOChannel *io;
+ uint8_t mode;
+ uint16_t imtu, omtu;
+ int fd;
+
+ fd = mcap_mdl_get_fd(chan->mdl);
+ if (fd < 0)
+ return FALSE;
+ io = g_io_channel_unix_new(fd);
+
+ if (!bt_io_get(io, BT_IO_L2CAP, &err,
+ BT_IO_OPT_MODE, &mode,
+ BT_IO_OPT_IMTU, &imtu,
+ BT_IO_OPT_OMTU, &omtu,
+ BT_IO_OPT_INVALID)) {
+ error("Error: %s", err->message);
+ g_io_channel_unref(io);
+ g_error_free(err);
+ return FALSE;
+ }
+
+ g_io_channel_unref(io);
+
+ switch (chan->config) {
+ case HDP_RELIABLE_DC:
+ if (mode != L2CAP_MODE_ERTM)
+ return FALSE;
+ break;
+ case HDP_STREAMING_DC:
+ if (mode != L2CAP_MODE_STREAMING)
+ return FALSE;
+ break;
+ default:
+ error("Error: Connected with unknown configuration");
+ return FALSE;
+ }
+
+ DBG("MDL imtu %d omtu %d Channel imtu %d omtu %d", imtu, omtu,
+ chan->imtu, chan->omtu);
+
+ if (chan->imtu == 0)
+ chan->imtu = imtu;
+ if (chan->omtu == 0)
+ chan->omtu = omtu;
+
+ if (chan->imtu != imtu || chan->omtu != omtu)
+ return FALSE;
+
+ return TRUE;
+}
+
+static void hdp_mcap_mdl_connected_cb(struct mcap_mdl *mdl, void *data)
+{
+ struct hdp_device *dev = data;
+ struct hdp_channel *chan;
+
+ DBG("hdp_mcap_mdl_connected_cb");
+ if (dev->ndc == NULL)
+ return;
+
+ chan = dev->ndc;
+ if (chan->mdl == NULL)
+ chan->mdl = mcap_mdl_ref(mdl);
+
+ if (g_slist_find(dev->channels, chan) == NULL)
+ dev->channels = g_slist_prepend(dev->channels,
+ hdp_channel_ref(chan));
+
+ if (!check_channel_conf(chan)) {
+ close_mdl(chan);
+ goto end;
+ }
+
+ if (chan->mdep == HDP_MDEP_ECHO) {
+ GIOChannel *io;
+ int fd;
+
+ fd = mcap_mdl_get_fd(chan->mdl);
+ if (fd < 0)
+ goto end;
+
+ chan->edata->echo_done = FALSE;
+ io = g_io_channel_unix_new(fd);
+ g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL | G_IO_IN,
+ serve_echo, hdp_channel_ref(chan));
+ g_io_channel_unref(io);
+ goto end;
+ }
+
+ g_dbus_emit_signal(dev->conn, device_get_path(dev->dev), HEALTH_DEVICE,
+ "ChannelConnected",
+ DBUS_TYPE_OBJECT_PATH, &chan->path,
+ DBUS_TYPE_INVALID);
+
+ if (dev->fr != NULL)
+ goto end;
+
+ dev->fr = hdp_channel_ref(chan);
+
+ emit_property_changed(dev->conn, device_get_path(dev->dev),
+ HEALTH_DEVICE, "MainChannel",
+ DBUS_TYPE_OBJECT_PATH, &dev->fr->path);
+
+end:
+ hdp_channel_unref(dev->ndc);
+ dev->ndc = NULL;
+}
+
+static void hdp_mcap_mdl_closed_cb(struct mcap_mdl *mdl, void *data)
+{
+ /* struct hdp_device *dev = data; */
+
+ DBG("hdp_mcap_mdl_closed_cb");
+
+ /* Nothing to do */
+}
+
+static void hdp_mcap_mdl_deleted_cb(struct mcap_mdl *mdl, void *data)
+{
+ struct hdp_device *dev = data;
+ struct hdp_channel *chan;
+ char *path;
+ GSList *l;
+
+ DBG("hdp_mcap_mdl_deleted_cb");
+ l = g_slist_find_custom(dev->channels, mdl, cmp_chan_mdl);
+ if (l == NULL)
+ return;
+
+ chan = l->data;
+
+ path = g_strdup(chan->path);
+ if (!g_dbus_unregister_interface(dev->conn, path, HEALTH_CHANNEL))
+ health_channel_destroy(chan);
+ g_free(path);
+}
+
+static void hdp_mcap_mdl_aborted_cb(struct mcap_mdl *mdl, void *data)
+{
+ struct hdp_device *dev = data;
+
+ DBG("hdp_mcap_mdl_aborted_cb");
+ if (dev->ndc == NULL)
+ return;
+
+ dev->ndc->mdl = mcap_mdl_ref(mdl);
+
+ if (g_slist_find(dev->channels, dev->ndc) == NULL)
+ dev->channels = g_slist_prepend(dev->channels,
+ hdp_channel_ref(dev->ndc));
+
+ if (dev->ndc->mdep != HDP_MDEP_ECHO)
+ g_dbus_emit_signal(dev->conn, device_get_path(dev->dev),
+ HEALTH_DEVICE, "ChannelConnected",
+ DBUS_TYPE_OBJECT_PATH, &dev->ndc->path,
+ DBUS_TYPE_INVALID);
+
+ hdp_channel_unref(dev->ndc);
+ dev->ndc = NULL;
+}
+
+static uint8_t hdp2l2cap_mode(uint8_t hdp_mode)
+{
+ return hdp_mode == HDP_STREAMING_DC ? L2CAP_MODE_STREAMING :
+ L2CAP_MODE_ERTM;
+}
+
+static uint8_t hdp_mcap_mdl_conn_req_cb(struct mcap_mcl *mcl, uint8_t mdepid,
+ uint16_t mdlid, uint8_t *conf, void *data)
+{
+ struct hdp_device *dev = data;
+ struct hdp_application *app;
+ GError *err = NULL;
+ GSList *l;
+
+ DBG("Data channel request");
+
+ if (mdepid == HDP_MDEP_ECHO) {
+ switch (*conf) {
+ case HDP_NO_PREFERENCE_DC:
+ *conf = HDP_RELIABLE_DC;
+ case HDP_RELIABLE_DC:
+ break;
+ case HDP_STREAMING_DC:
+ return MCAP_CONFIGURATION_REJECTED;
+ default:
+ /* Special case defined in HDP spec 3.4. When an invalid
+ * configuration is received we shall close the MCL when
+ * we are still processing the callback. */
+ close_device_con(dev, FALSE);
+ return MCAP_CONFIGURATION_REJECTED; /* not processed */
+ }
+
+ if (!mcap_set_data_chan_mode(dev->hdp_adapter->mi,
+ L2CAP_MODE_ERTM, &err)) {
+ error("Error: %s", err->message);
+ g_error_free(err);
+ return MCAP_MDL_BUSY;
+ }
+
+ dev->ndc = create_channel(dev, *conf, NULL, mdlid, NULL, NULL);
+ if (dev->ndc == NULL)
+ return MCAP_MDL_BUSY;
+
+ return MCAP_SUCCESS;
+ }
+
+ l = g_slist_find_custom(applications, &mdepid, cmp_app_id);
+ if (l == NULL)
+ return MCAP_INVALID_MDEP;
+
+ app = l->data;
+
+ /* Check if is the first dc if so,
+ * only reliable configuration is allowed */
+ switch (*conf) {
+ case HDP_NO_PREFERENCE_DC:
+ if (app->role == HDP_SINK)
+ return MCAP_CONFIGURATION_REJECTED;
+ else if (dev->fr && app->chan_type_set)
+ *conf = app->chan_type;
+ else
+ *conf = HDP_RELIABLE_DC;
+ break;
+ case HDP_STREAMING_DC:
+ if (!dev->fr || app->role == HDP_SOURCE)
+ return MCAP_CONFIGURATION_REJECTED;
+ case HDP_RELIABLE_DC:
+ if (app->role == HDP_SOURCE)
+ return MCAP_CONFIGURATION_REJECTED;
+ break;
+ default:
+ /* Special case defined in HDP spec 3.4. When an invalid
+ * configuration is received we shall close the MCL when
+ * we are still processing the callback. */
+ close_device_con(dev, FALSE);
+ return MCAP_CONFIGURATION_REJECTED; /* not processed */
+ }
+
+ l = g_slist_find_custom(dev->channels, &mdlid, cmp_chan_mdlid);
+ if (l != NULL) {
+ struct hdp_channel *chan = l->data;
+ char *path;
+
+ path = g_strdup(chan->path);
+ g_dbus_unregister_interface(dev->conn, path, HEALTH_CHANNEL);
+ g_free(path);
+ }
+
+ if (!mcap_set_data_chan_mode(dev->hdp_adapter->mi,
+ hdp2l2cap_mode(*conf), &err)) {
+ error("Error: %s", err->message);
+ g_error_free(err);
+ return MCAP_MDL_BUSY;
+ }
+
+ dev->ndc = create_channel(dev, *conf, NULL, mdlid, app, NULL);
+ if (dev->ndc == NULL)
+ return MCAP_MDL_BUSY;
+
+ return MCAP_SUCCESS;
+}
+
+static uint8_t hdp_mcap_mdl_reconn_req_cb(struct mcap_mdl *mdl, void *data)
+{
+ struct hdp_device *dev = data;
+ struct hdp_channel *chan;
+ GError *err = NULL;
+ GSList *l;
+
+ l = g_slist_find_custom(dev->channels, mdl, cmp_chan_mdl);
+ if (l == NULL)
+ return MCAP_INVALID_MDL;
+
+ chan = l->data;
+
+ if (dev->fr == NULL && chan->config != HDP_RELIABLE_DC &&
+ chan->mdep != HDP_MDEP_ECHO)
+ return MCAP_UNSPECIFIED_ERROR;
+
+ if (!mcap_set_data_chan_mode(dev->hdp_adapter->mi,
+ hdp2l2cap_mode(chan->config), &err)) {
+ error("Error: %s", err->message);
+ g_error_free(err);
+ return MCAP_MDL_BUSY;
+ }
+
+ dev->ndc = hdp_channel_ref(chan);
+
+ return MCAP_SUCCESS;
+}
+
+gboolean hdp_set_mcl_cb(struct hdp_device *device, GError **err)
+{
+ gboolean ret;
+
+ if (device->mcl == NULL)
+ return FALSE;
+
+ ret = mcap_mcl_set_cb(device->mcl, device, err,
+ MCAP_MDL_CB_CONNECTED, hdp_mcap_mdl_connected_cb,
+ MCAP_MDL_CB_CLOSED, hdp_mcap_mdl_closed_cb,
+ MCAP_MDL_CB_DELETED, hdp_mcap_mdl_deleted_cb,
+ MCAP_MDL_CB_ABORTED, hdp_mcap_mdl_aborted_cb,
+ MCAP_MDL_CB_REMOTE_CONN_REQ, hdp_mcap_mdl_conn_req_cb,
+ MCAP_MDL_CB_REMOTE_RECONN_REQ, hdp_mcap_mdl_reconn_req_cb,
+ MCAP_MDL_CB_INVALID);
+
+ if (ret)
+ return TRUE;
+
+ error("Can't set mcl callbacks, closing mcl");
+ close_device_con(device, TRUE);
+
+ return FALSE;
+}
+
+static void mcl_connected(struct mcap_mcl *mcl, gpointer data)
+{
+ struct hdp_device *hdp_device;
+ bdaddr_t addr;
+ GSList *l;
+
+ mcap_mcl_get_addr(mcl, &addr);
+ l = g_slist_find_custom(devices, &addr, cmp_dev_addr);
+ if (l == NULL) {
+ struct hdp_adapter *hdp_adapter = data;
+ struct btd_device *device;
+ char str[18];
+
+ ba2str(&addr, str);
+ device = adapter_get_device(connection,
+ hdp_adapter->btd_adapter, str);
+ if (!device)
+ return;
+ hdp_device = create_health_device(connection, device);
+ if (!hdp_device)
+ return;
+ devices = g_slist_append(devices, hdp_device);
+ } else
+ hdp_device = l->data;
+
+ hdp_device->mcl = mcap_mcl_ref(mcl);
+ hdp_device->mcl_conn = TRUE;
+
+ DBG("New mcl connected from %s", device_get_path(hdp_device->dev));
+
+ hdp_set_mcl_cb(hdp_device, NULL);
+}
+
+static void mcl_reconnected(struct mcap_mcl *mcl, gpointer data)
+{
+ struct hdp_device *hdp_device;
+ GSList *l;
+
+ l = g_slist_find_custom(devices, mcl, cmp_dev_mcl);
+ if (l == NULL)
+ return;
+
+ hdp_device = l->data;
+ hdp_device->mcl_conn = TRUE;
+
+ DBG("MCL reconnected %s", device_get_path(hdp_device->dev));
+
+ hdp_set_mcl_cb(hdp_device, NULL);
+}
+
+static void mcl_disconnected(struct mcap_mcl *mcl, gpointer data)
+{
+ struct hdp_device *hdp_device;
+ GSList *l;
+
+ l = g_slist_find_custom(devices, mcl, cmp_dev_mcl);
+ if (l == NULL)
+ return;
+
+ hdp_device = l->data;
+ hdp_device->mcl_conn = FALSE;
+
+ DBG("Mcl disconnected %s", device_get_path(hdp_device->dev));
+}
+
+static void mcl_uncached(struct mcap_mcl *mcl, gpointer data)
+{
+ struct hdp_device *hdp_device;
+ const char *path;
+ GSList *l;
+
+ l = g_slist_find_custom(devices, mcl, cmp_dev_mcl);
+ if (l == NULL)
+ return;
+
+ hdp_device = l->data;
+ device_unref_mcl(hdp_device);
+
+ if (hdp_device->sdp_present)
+ return;
+
+ /* Because remote device hasn't announced an HDP record */
+ /* the Bluetooth daemon won't notify when the device shall */
+ /* be removed. Then we have to remove the HealthDevice */
+ /* interface manually */
+ path = device_get_path(hdp_device->dev);
+ g_dbus_unregister_interface(hdp_device->conn, path, HEALTH_DEVICE);
+ DBG("Mcl uncached %s", path);
+}
+
+static void check_devices_mcl(void)
+{
+ struct hdp_device *dev;
+ GSList *l, *to_delete = NULL;
+
+ for (l = devices; l; l = l->next) {
+ dev = l->data;
+ device_unref_mcl(dev);
+
+ if (!dev->sdp_present)
+ to_delete = g_slist_append(to_delete, dev);
+ else
+ remove_channels(dev);
+ }
+
+ for (l = to_delete; l; l = l->next) {
+ const char *path;
+
+ path = device_get_path(dev->dev);
+ g_dbus_unregister_interface(dev->conn, path, HEALTH_DEVICE);
+ }
+
+ g_slist_free(to_delete);
+}
+
+static void release_adapter_instance(struct hdp_adapter *hdp_adapter)
+{
+ if (hdp_adapter->mi == NULL)
+ return;
+
+ check_devices_mcl();
+ mcap_release_instance(hdp_adapter->mi);
+ mcap_instance_unref(hdp_adapter->mi);
+ hdp_adapter->mi = NULL;
+}
+
+static gboolean update_adapter(struct hdp_adapter *hdp_adapter)
+{
+ GError *err = NULL;
+ bdaddr_t addr;
+
+ if (applications == NULL) {
+ release_adapter_instance(hdp_adapter);
+ goto update;
+ }
+
+ if (hdp_adapter->mi != NULL)
+ goto update;
+
+ adapter_get_address(hdp_adapter->btd_adapter, &addr);
+ hdp_adapter->mi = mcap_create_instance(&addr, BT_IO_SEC_MEDIUM, 0, 0,
+ mcl_connected, mcl_reconnected,
+ mcl_disconnected, mcl_uncached,
+ NULL, /* CSP is not used by now */
+ hdp_adapter, &err);
+
+ if (hdp_adapter->mi == NULL) {
+ error("Error creating the MCAP instance: %s", err->message);
+ g_error_free(err);
+ return FALSE;
+ }
+
+ hdp_adapter->ccpsm = mcap_get_ctrl_psm(hdp_adapter->mi, &err);
+ if (err != NULL) {
+ error("Error getting MCAP control PSM: %s", err->message);
+ goto fail;
+ }
+
+ hdp_adapter->dcpsm = mcap_get_data_psm(hdp_adapter->mi, &err);
+ if (err != NULL) {
+ error("Error getting MCAP data PSM: %s", err->message);
+ goto fail;
+ }
+
+update:
+ if (hdp_update_sdp_record(hdp_adapter, applications))
+ return TRUE;
+ error("Error updating the SDP record");
+
+fail:
+ release_adapter_instance(hdp_adapter);
+ if (err != NULL)
+ g_error_free(err);
+
+ return FALSE;
+}
+
+int hdp_adapter_register(DBusConnection *conn, struct btd_adapter *adapter)
+{
+ struct hdp_adapter *hdp_adapter;
+
+ hdp_adapter = g_new0(struct hdp_adapter, 1);
+ hdp_adapter->btd_adapter = btd_adapter_ref(adapter);
+
+ if(!update_adapter(hdp_adapter))
+ goto fail;
+
+ adapters = g_slist_append(adapters, hdp_adapter);
+
+ return 0;
+
+fail:
+ btd_adapter_unref(hdp_adapter->btd_adapter);
+ g_free(hdp_adapter);
+ return -1;
+}
+
+void hdp_adapter_unregister(struct btd_adapter *adapter)
+{
+ struct hdp_adapter *hdp_adapter;
+ GSList *l;
+
+ l = g_slist_find_custom(adapters, adapter, cmp_adapter);
+
+ if (l == NULL)
+ return;
+
+ hdp_adapter = l->data;
+ adapters = g_slist_remove(adapters, hdp_adapter);
+ if (hdp_adapter->sdp_handler > 0)
+ remove_record_from_server(hdp_adapter->sdp_handler);
+ release_adapter_instance(hdp_adapter);
+ btd_adapter_unref(hdp_adapter->btd_adapter);
+ g_free(hdp_adapter);
+}
+
+static void delete_echo_channel_cb(GError *err, gpointer chan)
+{
+ if (err != NULL && err->code != MCAP_INVALID_MDL) {
+ /* TODO: Decide if more action is required here */
+ error("Error deleting echo channel: %s", err->message);
+ return;
+ }
+
+ health_channel_destroy(chan);
+}
+
+static void delete_echo_channel(struct hdp_channel *chan)
+{
+ GError *err = NULL;
+
+ if (!chan->dev->mcl_conn) {
+ error("Echo channel cannot be deleted: mcl closed");
+ return;
+ }
+
+ if (mcap_delete_mdl(chan->mdl, delete_echo_channel_cb,
+ hdp_channel_ref(chan),
+ (GDestroyNotify) hdp_channel_unref, &err))
+ return;
+
+ hdp_channel_unref(chan);
+ error("Error deleting the echo channel: %s", err->message);
+ g_error_free(err);
+
+ /* TODO: Decide if more action is required here */
+}
+
+static void abort_echo_channel_cb(GError *err, gpointer data)
+{
+ struct hdp_channel *chan = data;
+
+ if (err != NULL && err->code != MCAP_ERROR_INVALID_OPERATION) {
+ error("Aborting error: %s", err->message);
+ if (err->code == MCAP_INVALID_MDL) {
+ /* MDL is removed from MCAP so we can */
+ /* free the data channel without sending */
+ /* a MD_DELETE_MDL_REQ */
+ /* TODO review the above comment */
+ /* hdp_channel_unref(chan); */
+ }
+ return;
+ }
+
+ delete_echo_channel(chan);
+}
+
+static void destroy_create_dc_data(gpointer data)
+{
+ struct hdp_create_dc *dc_data = data;
+
+ hdp_create_data_unref(dc_data);
+}
+
+static void *generate_echo_packet(void)
+{
+ uint8_t *buf;
+ int i;
+
+ buf = g_malloc(HDP_ECHO_LEN);
+ srand(time(NULL));
+
+ for(i = 0; i < HDP_ECHO_LEN; i++)
+ buf[i] = rand() % UINT8_MAX;
+
+ return buf;
+}
+
+static gboolean check_echo(GIOChannel *io_chan, GIOCondition cond,
+ gpointer data)
+{
+ struct hdp_tmp_dc_data *hdp_conn = data;
+ struct hdp_echo_data *edata = hdp_conn->hdp_chann->edata;
+ struct hdp_channel *chan = hdp_conn->hdp_chann;
+ uint8_t buf[MCAP_DC_MTU];
+ DBusMessage *reply;
+ gboolean value;
+ int fd, len;
+
+ if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
+ value = FALSE;
+ goto end;
+ }
+
+ fd = g_io_channel_unix_get_fd(io_chan);
+ len = read(fd, buf, sizeof(buf));
+
+ if (len != HDP_ECHO_LEN) {
+ value = FALSE;
+ goto end;
+ }
+
+ value = (memcmp(buf, edata->buf, len) == 0);
+
+end:
+ reply = g_dbus_create_reply(hdp_conn->msg, DBUS_TYPE_BOOLEAN, &value,
+ DBUS_TYPE_INVALID);
+ g_dbus_send_message(hdp_conn->conn, reply);
+ g_source_remove(edata->tid);
+ edata->tid = 0;
+ g_free(edata->buf);
+ edata->buf = NULL;
+
+ if (!value)
+ close_device_con(chan->dev, FALSE);
+ else
+ delete_echo_channel(chan);
+ hdp_tmp_dc_data_unref(hdp_conn);
+
+ return FALSE;
+}
+
+static gboolean echo_timeout(gpointer data)
+{
+ struct hdp_channel *chan = data;
+ GIOChannel *io;
+ int fd;
+
+ error("Error: Echo request timeout");
+ chan->edata->tid = 0;
+
+ fd = mcap_mdl_get_fd(chan->mdl);
+ if (fd < 0)
+ return FALSE;
+
+ io = g_io_channel_unix_new(fd);
+ g_io_channel_shutdown(io, TRUE, NULL);
+
+ return FALSE;
+}
+
+static void hdp_echo_connect_cb(struct mcap_mdl *mdl, GError *err,
+ gpointer data)
+{
+ struct hdp_tmp_dc_data *hdp_conn = data;
+ struct hdp_echo_data *edata;
+ GError *gerr = NULL;
+ DBusMessage *reply;
+ GIOChannel *io;
+ int fd;
+
+ if (err != NULL) {
+ reply = g_dbus_create_error(hdp_conn->msg,
+ ERROR_INTERFACE ".HealthError",
+ "%s", err->message);
+ g_dbus_send_message(hdp_conn->conn, reply);
+
+ /* Send abort request because remote */
+ /* side is now in PENDING state. */
+ if (!mcap_mdl_abort(hdp_conn->hdp_chann->mdl,
+ abort_echo_channel_cb,
+ hdp_channel_ref(hdp_conn->hdp_chann),
+ (GDestroyNotify) hdp_channel_unref,
+ &gerr)) {
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ hdp_channel_unref(hdp_conn->hdp_chann);
+ }
+ return;
+ }
+
+ fd = mcap_mdl_get_fd(hdp_conn->hdp_chann->mdl);
+ if (fd < 0) {
+ reply = g_dbus_create_error(hdp_conn->msg,
+ ERROR_INTERFACE ".HealthError",
+ "Can't write in echo channel");
+ g_dbus_send_message(hdp_conn->conn, reply);
+ delete_echo_channel(hdp_conn->hdp_chann);
+ return;
+ }
+
+ edata = hdp_conn->hdp_chann->edata;
+ edata->buf = generate_echo_packet();
+ send_echo_data(fd, edata->buf, HDP_ECHO_LEN);
+
+ io = g_io_channel_unix_new(fd);
+ g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL | G_IO_IN,
+ check_echo, hdp_tmp_dc_data_ref(hdp_conn));
+
+ edata->tid = g_timeout_add_seconds_full(G_PRIORITY_DEFAULT,
+ ECHO_TIMEOUT, echo_timeout,
+ hdp_channel_ref(hdp_conn->hdp_chann),
+ (GDestroyNotify) hdp_channel_unref);
+
+ g_io_channel_unref(io);
+}
+
+static void delete_mdl_cb(GError *err, gpointer data)
+{
+ if (err != NULL)
+ error("Deleting error: %s", err->message);
+}
+
+static void abort_and_del_mdl_cb(GError *err, gpointer data)
+{
+ struct mcap_mdl *mdl = data;
+ GError *gerr = NULL;
+
+ if (err != NULL) {
+ error("%s", err->message);
+ if (err->code == MCAP_INVALID_MDL) {
+ /* MDL is removed from MCAP so we don't */
+ /* need to delete it. */
+ return;
+ }
+ }
+
+ if (!mcap_delete_mdl(mdl, delete_mdl_cb, NULL, NULL, &gerr)) {
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ }
+}
+
+static void abort_mdl_connection_cb(GError *err, gpointer data)
+{
+ struct hdp_tmp_dc_data *hdp_conn = data;
+ struct hdp_channel *hdp_chann = hdp_conn->hdp_chann;
+
+ if (err != NULL)
+ error("Aborting error: %s", err->message);
+
+ /* Connection operation has failed but we have to */
+ /* notify the channel created at MCAP level */
+ if (hdp_chann->mdep != HDP_MDEP_ECHO)
+ g_dbus_emit_signal(hdp_conn->conn,
+ device_get_path(hdp_chann->dev->dev),
+ HEALTH_DEVICE,
+ "ChannelConnected",
+ DBUS_TYPE_OBJECT_PATH, &hdp_chann->path,
+ DBUS_TYPE_INVALID);
+}
+
+static void hdp_mdl_conn_cb(struct mcap_mdl *mdl, GError *err, gpointer data)
+{
+ struct hdp_tmp_dc_data *hdp_conn = data;
+ struct hdp_channel *hdp_chann = hdp_conn->hdp_chann;
+ struct hdp_device *dev = hdp_chann->dev;
+ DBusMessage *reply;
+ GError *gerr = NULL;
+
+ if (err != NULL) {
+ error("%s", err->message);
+ reply = g_dbus_create_reply(hdp_conn->msg,
+ DBUS_TYPE_OBJECT_PATH, &hdp_chann->path,
+ DBUS_TYPE_INVALID);
+ g_dbus_send_message(hdp_conn->conn, reply);
+
+ /* Send abort request because remote side */
+ /* is now in PENDING state */
+ if (!mcap_mdl_abort(hdp_chann->mdl, abort_mdl_connection_cb,
+ hdp_tmp_dc_data_ref(hdp_conn),
+ hdp_tmp_dc_data_destroy, &gerr)) {
+ hdp_tmp_dc_data_unref(hdp_conn);
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ }
+ return;
+ }
+
+ reply = g_dbus_create_reply(hdp_conn->msg,
+ DBUS_TYPE_OBJECT_PATH, &hdp_chann->path,
+ DBUS_TYPE_INVALID);
+ g_dbus_send_message(hdp_conn->conn, reply);
+
+ g_dbus_emit_signal(hdp_conn->conn,
+ device_get_path(hdp_chann->dev->dev),
+ HEALTH_DEVICE,
+ "ChannelConnected",
+ DBUS_TYPE_OBJECT_PATH, &hdp_chann->path,
+ DBUS_TYPE_INVALID);
+
+ if (!check_channel_conf(hdp_chann)) {
+ close_mdl(hdp_chann);
+ return;
+ }
+
+ if (dev->fr != NULL)
+ return;
+
+ dev->fr = hdp_channel_ref(hdp_chann);
+
+ emit_property_changed(dev->conn, device_get_path(dev->dev),
+ HEALTH_DEVICE, "MainChannel",
+ DBUS_TYPE_OBJECT_PATH, &dev->fr->path);
+}
+
+static void device_create_mdl_cb(struct mcap_mdl *mdl, uint8_t conf,
+ GError *err, gpointer data)
+{
+ struct hdp_create_dc *user_data = data;
+ struct hdp_tmp_dc_data *hdp_conn;
+ struct hdp_channel *hdp_chan;
+ GError *gerr = NULL;
+ DBusMessage *reply;
+
+ if (err != NULL) {
+ reply = g_dbus_create_error(user_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "%s", err->message);
+ g_dbus_send_message(user_data->conn, reply);
+ return;
+ }
+
+ if (user_data->mdep != HDP_MDEP_ECHO &&
+ user_data->config == HDP_NO_PREFERENCE_DC) {
+ if (user_data->dev->fr == NULL && conf != HDP_RELIABLE_DC) {
+ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Data channel aborted, first data "
+ "channel should be reliable");
+ goto fail;
+ } else if (conf == HDP_NO_PREFERENCE_DC ||
+ conf > HDP_STREAMING_DC) {
+ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Data channel aborted, "
+ "configuration error");
+ goto fail;
+ }
+ }
+
+ hdp_chan = create_channel(user_data->dev, conf, mdl,
+ mcap_mdl_get_mdlid(mdl),
+ user_data->app, &gerr);
+ if (hdp_chan == NULL)
+ goto fail;
+
+ hdp_conn = g_new0(struct hdp_tmp_dc_data, 1);
+ hdp_conn->msg = dbus_message_ref(user_data->msg);
+ hdp_conn->conn = dbus_connection_ref(user_data->conn);
+ hdp_conn->hdp_chann = hdp_chan;
+ hdp_conn->cb = user_data->cb;
+ hdp_chan->mdep = user_data->mdep;
+
+ if (hdp_get_dcpsm(hdp_chan->dev, hdp_get_dcpsm_cb,
+ hdp_tmp_dc_data_ref(hdp_conn),
+ hdp_tmp_dc_data_destroy, &gerr))
+ return;
+
+ error("%s", gerr->message);
+ g_error_free(gerr);
+
+ reply = g_dbus_create_reply(hdp_conn->msg,
+ DBUS_TYPE_OBJECT_PATH, &hdp_chan->path,
+ DBUS_TYPE_INVALID);
+ g_dbus_send_message(hdp_conn->conn, reply);
+ hdp_tmp_dc_data_unref(hdp_conn);
+
+ /* Send abort request because remote side is now in PENDING state */
+ if (!mcap_mdl_abort(hdp_chan->mdl, abort_mdl_connection_cb,
+ hdp_tmp_dc_data_ref(hdp_conn),
+ hdp_tmp_dc_data_destroy, &gerr)) {
+ hdp_tmp_dc_data_unref(hdp_conn);
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ }
+
+ return;
+
+fail:
+ reply = g_dbus_create_error(user_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "%s", gerr->message);
+ g_dbus_send_message(user_data->conn, reply);
+ g_error_free(gerr);
+
+ /* Send abort request because remote side is now in PENDING */
+ /* state. Then we have to delete it because we couldn't */
+ /* register the HealthChannel interface */
+ if (!mcap_mdl_abort(mdl, abort_and_del_mdl_cb, mcap_mdl_ref(mdl),
+ (GDestroyNotify) mcap_mdl_unref, &gerr)) {
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ mcap_mdl_unref(mdl);
+ }
+}
+
+static void device_create_dc_cb(gpointer user_data, GError *err)
+{
+ struct hdp_create_dc *data = user_data;
+ DBusMessage *reply;
+ GError *gerr = NULL;
+
+ if (err != NULL) {
+ reply = g_dbus_create_error(data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "%s", err->message);
+ g_dbus_send_message(data->conn, reply);
+ return;
+ }
+
+ if (data->dev->mcl == NULL) {
+ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Mcl was closed");
+ goto fail;
+ }
+
+ hdp_create_data_ref(data);
+
+ if (mcap_create_mdl(data->dev->mcl, data->mdep, data->config,
+ device_create_mdl_cb, data,
+ destroy_create_dc_data, &gerr))
+ return;
+ hdp_create_data_unref(data);
+
+fail:
+ reply = g_dbus_create_error(data->msg, ERROR_INTERFACE ".HealthError",
+ "%s", gerr->message);
+ g_error_free(gerr);
+ g_dbus_send_message(data->conn, reply);
+}
+
+static DBusMessage *device_echo(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct hdp_device *device = user_data;
+ struct hdp_create_dc *data;
+ DBusMessage *reply;
+ GError *err = NULL;
+
+ data = g_new0(struct hdp_create_dc, 1);
+ data->dev = health_device_ref(device);
+ data->mdep = HDP_MDEP_ECHO;
+ data->config = HDP_RELIABLE_DC;
+ data->msg = dbus_message_ref(msg);
+ data->conn = dbus_connection_ref(conn);
+ data->cb = hdp_echo_connect_cb;
+ hdp_create_data_ref(data);
+
+ if (device->mcl_conn && device->mcl != NULL) {
+ if (mcap_create_mdl(device->mcl, data->mdep, data->config,
+ device_create_mdl_cb, data,
+ destroy_create_dc_data, &err))
+ return NULL;
+ goto fail;
+ }
+
+ if (hdp_establish_mcl(data->dev, device_create_dc_cb,
+ data, destroy_create_dc_data, &err))
+ return NULL;
+
+fail:
+ reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
+ "%s", err->message);
+ g_error_free(err);
+ hdp_create_data_unref(data);
+ return reply;
+}
+
+static void device_get_mdep_cb(uint8_t mdep, gpointer data, GError *err)
+{
+ struct hdp_create_dc *dc_data, *user_data = data;
+ DBusMessage *reply;
+ GError *gerr = NULL;
+
+ if (err != NULL) {
+ reply = g_dbus_create_error(user_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "%s", err->message);
+ g_dbus_send_message(user_data->conn, reply);
+ return;
+ }
+
+ dc_data = hdp_create_data_ref(user_data);
+ dc_data->mdep = mdep;
+
+ if (user_data->dev->mcl_conn) {
+ device_create_dc_cb(dc_data, NULL);
+ hdp_create_data_unref(dc_data);
+ return;
+ }
+
+ if (hdp_establish_mcl(dc_data->dev, device_create_dc_cb,
+ dc_data, destroy_create_dc_data, &gerr))
+ return;
+
+ reply = g_dbus_create_error(user_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "%s", gerr->message);
+ hdp_create_data_unref(dc_data);
+ g_error_free(gerr);
+ g_dbus_send_message(user_data->conn, reply);
+}
+
+static DBusMessage *device_create_channel(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct hdp_device *device = user_data;
+ struct hdp_application *app;
+ struct hdp_create_dc *data;
+ char *app_path, *conf;
+ DBusMessage *reply;
+ GError *err = NULL;
+ uint8_t config;
+ GSList *l;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &app_path,
+ DBUS_TYPE_STRING, &conf,
+ DBUS_TYPE_INVALID))
+ return btd_error_invalid_args(msg);
+
+ l = g_slist_find_custom(applications, app_path, cmp_app);
+ if (l == NULL)
+ return btd_error_invalid_args(msg);
+
+ app = l->data;
+
+ if (g_ascii_strcasecmp("Reliable", conf) == 0)
+ config = HDP_RELIABLE_DC;
+ else if (g_ascii_strcasecmp("Streaming", conf) == 0)
+ config = HDP_STREAMING_DC;
+ else if (g_ascii_strcasecmp("Any", conf) == 0)
+ config = HDP_NO_PREFERENCE_DC;
+ else
+ return btd_error_invalid_args(msg);
+
+ if (app->role == HDP_SINK && config != HDP_NO_PREFERENCE_DC)
+ return btd_error_invalid_args(msg);
+
+ if (app->role == HDP_SOURCE && config == HDP_NO_PREFERENCE_DC)
+ return btd_error_invalid_args(msg);
+
+ if (!device->fr && config == HDP_STREAMING_DC)
+ return btd_error_invalid_args(msg);
+
+ data = g_new0(struct hdp_create_dc, 1);
+ data->dev = health_device_ref(device);
+ data->config = config;
+ data->app = hdp_application_ref(app);
+ data->msg = dbus_message_ref(msg);
+ data->conn = dbus_connection_ref(conn);
+ data->cb = hdp_mdl_conn_cb;
+
+ if (hdp_get_mdep(device, l->data, device_get_mdep_cb,
+ hdp_create_data_ref(data),
+ destroy_create_dc_data, &err))
+ return NULL;
+
+ reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
+ "%s", err->message);
+ g_error_free(err);
+ hdp_create_data_unref(data);
+ return reply;
+}
+
+static void hdp_mdl_delete_cb(GError *err, gpointer data)
+{
+ struct hdp_tmp_dc_data *del_data = data;
+ DBusMessage *reply;
+ char *path;
+
+ if (err != NULL && err->code != MCAP_INVALID_MDL) {
+ reply = g_dbus_create_error(del_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "%s", err->message);
+ g_dbus_send_message(del_data->conn, reply);
+ return;
+ }
+
+ path = g_strdup(del_data->hdp_chann->path);
+ g_dbus_unregister_interface(del_data->conn, path, HEALTH_CHANNEL);
+ g_free(path);
+
+ reply = g_dbus_create_reply(del_data->msg, DBUS_TYPE_INVALID);
+ g_dbus_send_message(del_data->conn, reply);
+}
+
+static void hdp_continue_del_cb(gpointer user_data, GError *err)
+{
+ struct hdp_tmp_dc_data *del_data = user_data;
+ GError *gerr = NULL;
+ DBusMessage *reply;
+
+ if (err != NULL) {
+ reply = g_dbus_create_error(del_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "%s", err->message);
+ g_dbus_send_message(del_data->conn, reply);
+ return;
+ }
+
+ if (mcap_delete_mdl(del_data->hdp_chann->mdl, hdp_mdl_delete_cb,
+ hdp_tmp_dc_data_ref(del_data),
+ hdp_tmp_dc_data_destroy, &gerr))
+ return;
+
+ reply = g_dbus_create_error(del_data->msg,
+ ERROR_INTERFACE ".HealthError",
+ "%s", gerr->message);
+ hdp_tmp_dc_data_unref(del_data);
+ g_error_free(gerr);
+ g_dbus_send_message(del_data->conn, reply);
+}
+
+static DBusMessage *device_destroy_channel(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct hdp_device *device = user_data;
+ struct hdp_tmp_dc_data *del_data;
+ struct hdp_channel *hdp_chan;
+ DBusMessage *reply;
+ GError *err = NULL;
+ char *path;
+ GSList *l;
+
+ if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_OBJECT_PATH, &path,
+ DBUS_TYPE_INVALID)){
+ return btd_error_invalid_args(msg);
+ }
+
+ l = g_slist_find_custom(device->channels, path, cmp_chan_path);
+ if (l == NULL)
+ return btd_error_invalid_args(msg);
+
+ hdp_chan = l->data;
+ del_data = g_new0(struct hdp_tmp_dc_data, 1);
+ del_data->msg = dbus_message_ref(msg);
+ del_data->conn = dbus_connection_ref(conn);
+ del_data->hdp_chann = hdp_channel_ref(hdp_chan);
+
+ if (device->mcl_conn) {
+ if (mcap_delete_mdl(hdp_chan->mdl, hdp_mdl_delete_cb,
+ hdp_tmp_dc_data_ref(del_data),
+ hdp_tmp_dc_data_destroy, &err))
+ return NULL;
+ goto fail;
+ }
+
+ if (hdp_establish_mcl(device, hdp_continue_del_cb,
+ hdp_tmp_dc_data_ref(del_data),
+ hdp_tmp_dc_data_destroy, &err))
+ return NULL;
+
+fail:
+ reply = g_dbus_create_error(msg, ERROR_INTERFACE ".HealthError",
+ "%s", err->message);
+ hdp_tmp_dc_data_unref(del_data);
+ g_error_free(err);
+ return reply;
+}
+
+static DBusMessage *device_get_properties(DBusConnection *conn,
+ DBusMessage *msg, void *user_data)
+{
+ struct hdp_device *device = user_data;
+ DBusMessageIter iter, dict;
+ DBusMessage *reply;
+
+ reply = dbus_message_new_method_return(msg);
+ if (reply == NULL)
+ return NULL;
+
+ dbus_message_iter_init_append(reply, &iter);
+
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING
+ DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
+ DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);
+
+ if (device->fr != NULL)
+ dict_append_entry(&dict, "MainChannel", DBUS_TYPE_OBJECT_PATH,
+ &device->fr->path);
+
+ dbus_message_iter_close_container(&iter, &dict);
+
+ return reply;
+}
+
+static void health_device_destroy(void *data)
+{
+ struct hdp_device *device = data;
+
+ DBG("Unregistered interface %s on path %s", HEALTH_DEVICE,
+ device_get_path(device->dev));
+
+ remove_channels(device);
+ if (device->ndc != NULL) {
+ hdp_channel_unref(device->ndc);
+ device->ndc = NULL;
+ }
+
+ devices = g_slist_remove(devices, device);
+ health_device_unref(device);
+}
+
+static const GDBusMethodTable health_device_methods[] = {
+ { GDBUS_ASYNC_METHOD("Echo",
+ NULL, GDBUS_ARGS({ "value", "b" }), device_echo) },
+ { GDBUS_ASYNC_METHOD("CreateChannel",
+ GDBUS_ARGS({ "application", "o" },
+ { "configuration", "s" }),
+ GDBUS_ARGS({ "channel", "o" }),
+ device_create_channel) },
+ { GDBUS_ASYNC_METHOD("DestroyChannel",
+ GDBUS_ARGS({ "channel", "o" }), NULL,
+ device_destroy_channel) },
+ { GDBUS_METHOD("GetProperties",
+ NULL, GDBUS_ARGS({ "properties", "a{sv}" }),
+ device_get_properties) },
+ { }
+};
+
+static const GDBusSignalTable health_device_signals[] = {
+ { GDBUS_SIGNAL("ChannelConnected",
+ GDBUS_ARGS({ "channel", "o" })) },
+ { GDBUS_SIGNAL("ChannelDeleted",
+ GDBUS_ARGS({ "channel", "o" })) },
+ { GDBUS_SIGNAL("PropertyChanged",
+ GDBUS_ARGS({ "name", "s" }, { "value", "v" })) },
+ { }
+};
+
+static struct hdp_device *create_health_device(DBusConnection *conn,
+ struct btd_device *device)
+{
+ struct btd_adapter *adapter = device_get_adapter(device);
+ const gchar *path = device_get_path(device);
+ struct hdp_device *dev;
+ GSList *l;
+
+ if (device == NULL)
+ return NULL;
+
+ dev = g_new0(struct hdp_device, 1);
+ dev->conn = dbus_connection_ref(conn);
+ dev->dev = btd_device_ref(device);
+ health_device_ref(dev);
+
+ l = g_slist_find_custom(adapters, adapter, cmp_adapter);
+ if (l == NULL)
+ goto fail;
+
+ dev->hdp_adapter = l->data;
+
+ if (!g_dbus_register_interface(conn, path,
+ HEALTH_DEVICE,
+ health_device_methods,
+ health_device_signals, NULL,
+ dev, health_device_destroy)) {
+ error("D-Bus failed to register %s interface", HEALTH_DEVICE);
+ goto fail;
+ }
+
+ DBG("Registered interface %s on path %s", HEALTH_DEVICE, path);
+ return dev;
+
+fail:
+ health_device_unref(dev);
+ return NULL;
+}
+
+int hdp_device_register(DBusConnection *conn, struct btd_device *device)
+{
+ struct hdp_device *hdev;
+ GSList *l;
+
+ l = g_slist_find_custom(devices, device, cmp_device);
+ if (l != NULL) {
+ hdev = l->data;
+ hdev->sdp_present = TRUE;
+ return 0;
+ }
+
+ hdev = create_health_device(conn, device);
+ if (hdev == NULL)
+ return -1;
+
+ hdev->sdp_present = TRUE;
+
+ devices = g_slist_prepend(devices, hdev);
+ return 0;
+}
+
+void hdp_device_unregister(struct btd_device *device)
+{
+ struct hdp_device *hdp_dev;
+ const char *path;
+ GSList *l;
+
+ l = g_slist_find_custom(devices, device, cmp_device);
+ if (l == NULL)
+ return;
+
+ hdp_dev = l->data;
+ path = device_get_path(hdp_dev->dev);
+ g_dbus_unregister_interface(hdp_dev->conn, path, HEALTH_DEVICE);
+}
+
+int hdp_manager_start(DBusConnection *conn)
+{
+ DBG("Starting Health manager");
+
+ if (!g_dbus_register_interface(conn, MANAGER_PATH,
+ HEALTH_MANAGER,
+ health_manager_methods, NULL, NULL,
+ NULL, manager_path_unregister)) {
+ error("D-Bus failed to register %s interface", HEALTH_MANAGER);
+ return -1;
+ }
+
+ connection = dbus_connection_ref(conn);
+
+ return 0;
+}
+
+void hdp_manager_stop(void)
+{
+ g_dbus_unregister_interface(connection, MANAGER_PATH, HEALTH_MANAGER);
+
+ dbus_connection_unref(connection);
+ DBG("Stopped Health manager");
+}
+
+struct hdp_device *health_device_ref(struct hdp_device *hdp_dev)
+{
+ hdp_dev->ref++;
+
+ DBG("health_device_ref(%p): ref=%d", hdp_dev, hdp_dev->ref);
+
+ return hdp_dev;
+}
+
+void health_device_unref(struct hdp_device *hdp_dev)
+{
+ hdp_dev->ref--;
+
+ DBG("health_device_unref(%p): ref=%d", hdp_dev, hdp_dev->ref);
+
+ if (hdp_dev->ref > 0)
+ return;
+
+ free_health_device(hdp_dev);
+}
diff --git a/profiles/health/hdp.h b/profiles/health/hdp.h
new file mode 100644
index 000000000..39f0441b4
--- /dev/null
+++ b/profiles/health/hdp.h
@@ -0,0 +1,32 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+int hdp_adapter_register(DBusConnection *conn, struct btd_adapter *btd_adapter);
+void hdp_adapter_unregister(struct btd_adapter *btd_adapter);
+
+int hdp_device_register(DBusConnection *conn, struct btd_device *device);
+void hdp_device_unregister(struct btd_device *device);
+
+int hdp_manager_start(DBusConnection *conn);
+void hdp_manager_stop(void);
+
+gboolean hdp_set_mcl_cb(struct hdp_device *device, GError **err);
diff --git a/profiles/health/hdp_main.c b/profiles/health/hdp_main.c
new file mode 100644
index 000000000..9367e7328
--- /dev/null
+++ b/profiles/health/hdp_main.c
@@ -0,0 +1,59 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <errno.h>
+
+#include <gdbus.h>
+
+#include "plugin.h"
+#include "hdp_manager.h"
+
+static DBusConnection *connection = NULL;
+
+static int hdp_init(void)
+{
+ connection = dbus_bus_get(DBUS_BUS_SYSTEM, NULL);
+ if (connection == NULL)
+ return -EIO;
+
+ if (hdp_manager_init(connection) < 0) {
+ dbus_connection_unref(connection);
+ return -EIO;
+ }
+
+ return 0;
+}
+
+static void hdp_exit(void)
+{
+ hdp_manager_exit();
+
+ dbus_connection_unref(connection);
+ connection = NULL;
+}
+
+BLUETOOTH_PLUGIN_DEFINE(health, VERSION,
+ BLUETOOTH_PLUGIN_PRIORITY_DEFAULT, hdp_init, hdp_exit)
diff --git a/profiles/health/hdp_manager.c b/profiles/health/hdp_manager.c
new file mode 100644
index 000000000..ffaed5d64
--- /dev/null
+++ b/profiles/health/hdp_manager.c
@@ -0,0 +1,97 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/uuid.h>
+
+#include <btio.h>
+#include <adapter.h>
+#include <device.h>
+#include <glib-helper.h>
+#include <log.h>
+
+#include "hdp_types.h"
+
+#include "hdp_manager.h"
+#include "hdp.h"
+
+static DBusConnection *connection = NULL;
+
+static int hdp_adapter_probe(struct btd_adapter *adapter)
+{
+ return hdp_adapter_register(connection, adapter);
+}
+
+static void hdp_adapter_remove(struct btd_adapter *adapter)
+{
+ hdp_adapter_unregister(adapter);
+}
+
+static struct btd_adapter_driver hdp_adapter_driver = {
+ .name = "hdp-adapter-driver",
+ .probe = hdp_adapter_probe,
+ .remove = hdp_adapter_remove,
+};
+
+static int hdp_driver_probe(struct btd_device *device, GSList *uuids)
+{
+ return hdp_device_register(connection, device);
+}
+
+static void hdp_driver_remove(struct btd_device *device)
+{
+ hdp_device_unregister(device);
+}
+
+static struct btd_device_driver hdp_device_driver = {
+ .name = "hdp-device-driver",
+ .uuids = BTD_UUIDS(HDP_UUID, HDP_SOURCE_UUID, HDP_SINK_UUID),
+ .probe = hdp_driver_probe,
+ .remove = hdp_driver_remove
+};
+
+int hdp_manager_init(DBusConnection *conn)
+{
+ if (hdp_manager_start(conn) < 0)
+ return -1;
+
+ connection = dbus_connection_ref(conn);
+ btd_register_adapter_driver(&hdp_adapter_driver);
+ btd_register_device_driver(&hdp_device_driver);
+
+ return 0;
+}
+
+void hdp_manager_exit(void)
+{
+ btd_unregister_device_driver(&hdp_device_driver);
+ btd_unregister_adapter_driver(&hdp_adapter_driver);
+ hdp_manager_stop();
+
+ dbus_connection_unref(connection);
+ connection = NULL;
+}
diff --git a/profiles/health/hdp_manager.h b/profiles/health/hdp_manager.h
new file mode 100644
index 000000000..d39f190d1
--- /dev/null
+++ b/profiles/health/hdp_manager.h
@@ -0,0 +1,24 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+int hdp_manager_init(DBusConnection *conn);
+void hdp_manager_exit(void);
diff --git a/profiles/health/hdp_types.h b/profiles/health/hdp_types.h
new file mode 100644
index 000000000..7f8b0151a
--- /dev/null
+++ b/profiles/health/hdp_types.h
@@ -0,0 +1,122 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __HDP_TYPES_H__
+#define __HDP_TYPES_H__
+
+#define MANAGER_PATH "/org/bluez"
+
+#define HEALTH_MANAGER "org.bluez.HealthManager"
+#define HEALTH_DEVICE "org.bluez.HealthDevice"
+#define HEALTH_CHANNEL "org.bluez.HealthChannel"
+
+#define HDP_VERSION 0x0100
+
+#define HDP_SERVICE_NAME "Bluez HDP"
+#define HDP_SERVICE_DSC "A Bluez health device profile implementation"
+#define HDP_SERVICE_PROVIDER "Bluez"
+
+#define HDP_MDEP_ECHO 0x00
+#define HDP_MDEP_INITIAL 0x01
+#define HDP_MDEP_FINAL 0x7F
+
+#define HDP_ERROR g_quark_from_static_string("hdp-error-quark")
+
+#define HDP_NO_PREFERENCE_DC 0x00
+#define HDP_RELIABLE_DC 0x01
+#define HDP_STREAMING_DC 0x02
+
+#define HDP_SINK_ROLE_AS_STRING "sink"
+#define HDP_SOURCE_ROLE_AS_STRING "source"
+
+typedef enum {
+ HDP_SOURCE = 0x00,
+ HDP_SINK = 0x01
+} HdpRole;
+
+typedef enum {
+ HDP_DIC_PARSE_ERROR,
+ HDP_DIC_ENTRY_PARSE_ERROR,
+ HDP_CONNECTION_ERROR,
+ HDP_UNSPECIFIED_ERROR,
+ HDP_UNKNOWN_ERROR
+} HdpError;
+
+enum data_specs {
+ DATA_EXCHANGE_SPEC_11073 = 0x01
+};
+
+struct hdp_application {
+ DBusConnection *conn; /* For dbus watcher */
+ char *path; /* The path of the application */
+ uint16_t data_type; /* Data type handled for this application */
+ gboolean data_type_set; /* Flag for dictionary parsing */
+ uint8_t role; /* Role of this application */
+ gboolean role_set; /* Flag for dictionary parsing */
+ uint8_t chan_type; /* QoS preferred by source applications */
+ gboolean chan_type_set; /* Flag for dictionary parsing */
+ char *description; /* Options description for SDP record */
+ uint8_t id; /* The identification is also the mdepid */
+ char *oname; /* Name of the owner application */
+ guint dbus_watcher; /* Watch for clients disconnection */
+ gint ref; /* Reference counter */
+};
+
+struct hdp_adapter {
+ struct btd_adapter *btd_adapter; /* Bluetooth adapter */
+ struct mcap_instance *mi; /* Mcap instance in */
+ uint16_t ccpsm; /* Control channel psm */
+ uint16_t dcpsm; /* Data channel psm */
+ uint32_t sdp_handler; /* SDP record handler */
+ uint32_t record_state; /* Service record state */
+};
+
+struct hdp_device {
+ DBusConnection *conn; /* For name listener handling */
+ struct btd_device *dev; /* Device reference */
+ struct hdp_adapter *hdp_adapter; /* hdp_adapater */
+ struct mcap_mcl *mcl; /* The mcap control channel */
+ gboolean mcl_conn; /* Mcl status */
+ gboolean sdp_present; /* Has an sdp record */
+ GSList *channels; /* Data Channel list */
+ struct hdp_channel *ndc; /* Data channel being negotiated */
+ struct hdp_channel *fr; /* First reliable data channel */
+ gint ref; /* Reference counting */
+};
+
+struct hdp_echo_data;
+
+struct hdp_channel {
+ struct hdp_device *dev; /* Device where this channel belongs */
+ struct hdp_application *app; /* Application */
+ struct mcap_mdl *mdl; /* The data channel reference */
+ char *path; /* The path of the channel */
+ uint8_t config; /* Channel configuration */
+ uint8_t mdep; /* Remote MDEP */
+ uint16_t mdlid; /* Data channel Id */
+ uint16_t imtu; /* Channel incoming MTU */
+ uint16_t omtu; /* Channel outgoing MTU */
+ struct hdp_echo_data *edata; /* private data used by echo channels */
+ gint ref; /* Reference counter */
+};
+
+#endif /* __HDP_TYPES_H__ */
diff --git a/profiles/health/hdp_util.c b/profiles/health/hdp_util.c
new file mode 100644
index 000000000..744e3902c
--- /dev/null
+++ b/profiles/health/hdp_util.c
@@ -0,0 +1,1218 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+
+#include <glib.h>
+
+#include <gdbus.h>
+
+#include <adapter.h>
+#include <device.h>
+
+#include <sdpd.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/uuid.h>
+#include <sdp-client.h>
+#include <glib-helper.h>
+
+#include <btio.h>
+
+#include <log.h>
+
+#include "mcap.h"
+#include "mcap_lib.h"
+#include "hdp_types.h"
+#include "hdp.h"
+#include "hdp_util.h"
+
+typedef gboolean (*parse_item_f)(DBusMessageIter *iter, gpointer user_data,
+ GError **err);
+
+struct dict_entry_func {
+ char *key;
+ parse_item_f func;
+};
+
+struct get_mdep_data {
+ struct hdp_application *app;
+ gpointer data;
+ hdp_continue_mdep_f func;
+ GDestroyNotify destroy;
+};
+
+struct conn_mcl_data {
+ int refs;
+ gpointer data;
+ hdp_continue_proc_f func;
+ GDestroyNotify destroy;
+ struct hdp_device *dev;
+};
+
+struct get_dcpsm_data {
+ gpointer data;
+ hdp_continue_dcpsm_f func;
+ GDestroyNotify destroy;
+};
+
+static gboolean parse_dict_entry(struct dict_entry_func dict_context[],
+ DBusMessageIter *iter,
+ GError **err,
+ gpointer user_data)
+{
+ DBusMessageIter entry;
+ char *key;
+ int ctype, i;
+ struct dict_entry_func df;
+
+ dbus_message_iter_recurse(iter, &entry);
+ ctype = dbus_message_iter_get_arg_type(&entry);
+ if (ctype != DBUS_TYPE_STRING) {
+ g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+ "Dictionary entries should have a string as key");
+ return FALSE;
+ }
+
+ dbus_message_iter_get_basic(&entry, &key);
+ dbus_message_iter_next(&entry);
+ /* Find function and call it */
+ for (i = 0, df = dict_context[0]; df.key; i++, df = dict_context[i]) {
+ if (g_ascii_strcasecmp(df.key, key) == 0)
+ return df.func(&entry, user_data, err);
+ }
+
+ g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+ "No function found for parsing value for key %s", key);
+ return FALSE;
+}
+
+static gboolean parse_dict(struct dict_entry_func dict_context[],
+ DBusMessageIter *iter,
+ GError **err,
+ gpointer user_data)
+{
+ int ctype;
+ DBusMessageIter dict;
+
+ ctype = dbus_message_iter_get_arg_type(iter);
+ if (ctype != DBUS_TYPE_ARRAY) {
+ g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR,
+ "Dictionary should be an array");
+ return FALSE;
+ }
+
+ dbus_message_iter_recurse(iter, &dict);
+ while ((ctype = dbus_message_iter_get_arg_type(&dict)) !=
+ DBUS_TYPE_INVALID) {
+ if (ctype != DBUS_TYPE_DICT_ENTRY) {
+ g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR,
+ "Dictionary array should "
+ "contain dict entries");
+ return FALSE;
+ }
+
+ /* Start parsing entry */
+ if (!parse_dict_entry(dict_context, &dict, err,
+ user_data))
+ return FALSE;
+ /* Finish entry parsing */
+
+ dbus_message_iter_next(&dict);
+ }
+
+ return TRUE;
+}
+
+static gboolean parse_data_type(DBusMessageIter *iter, gpointer data,
+ GError **err)
+{
+ struct hdp_application *app = data;
+ DBusMessageIter *value;
+ int ctype;
+
+ ctype = dbus_message_iter_get_arg_type(iter);
+ value = iter;
+ if (ctype == DBUS_TYPE_VARIANT) {
+ DBusMessageIter variant;
+
+ /* Get value inside the variable */
+ dbus_message_iter_recurse(iter, &variant);
+ ctype = dbus_message_iter_get_arg_type(&variant);
+ value = &variant;
+ }
+
+ if (ctype != DBUS_TYPE_UINT16) {
+ g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+ "Final value for data type should be uint16");
+ return FALSE;
+ }
+
+ dbus_message_iter_get_basic(value, &app->data_type);
+ app->data_type_set = TRUE;
+ return TRUE;
+}
+
+static gboolean parse_role(DBusMessageIter *iter, gpointer data, GError **err)
+{
+ struct hdp_application *app = data;
+ DBusMessageIter *string;
+ int ctype;
+ const char *role;
+
+ ctype = dbus_message_iter_get_arg_type(iter);
+ if (ctype == DBUS_TYPE_VARIANT) {
+ DBusMessageIter value;
+
+ /* Get value inside the variable */
+ dbus_message_iter_recurse(iter, &value);
+ ctype = dbus_message_iter_get_arg_type(&value);
+ string = &value;
+ } else {
+ string = iter;
+ }
+
+ if (ctype != DBUS_TYPE_STRING) {
+ g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR,
+ "Value data spec should be variable or string");
+ return FALSE;
+ }
+
+ dbus_message_iter_get_basic(string, &role);
+ if (g_ascii_strcasecmp(role, HDP_SINK_ROLE_AS_STRING) == 0) {
+ app->role = HDP_SINK;
+ } else if (g_ascii_strcasecmp(role, HDP_SOURCE_ROLE_AS_STRING) == 0) {
+ app->role = HDP_SOURCE;
+ } else {
+ g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR,
+ "Role value should be \"source\" or \"sink\"");
+ return FALSE;
+ }
+
+ app->role_set = TRUE;
+
+ return TRUE;
+}
+
+static gboolean parse_desc(DBusMessageIter *iter, gpointer data, GError **err)
+{
+ struct hdp_application *app = data;
+ DBusMessageIter *string;
+ int ctype;
+ const char *desc;
+
+ ctype = dbus_message_iter_get_arg_type(iter);
+ if (ctype == DBUS_TYPE_VARIANT) {
+ DBusMessageIter variant;
+
+ /* Get value inside the variable */
+ dbus_message_iter_recurse(iter, &variant);
+ ctype = dbus_message_iter_get_arg_type(&variant);
+ string = &variant;
+ } else {
+ string = iter;
+ }
+
+ if (ctype != DBUS_TYPE_STRING) {
+ g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+ "Value data spec should be variable or string");
+ return FALSE;
+ }
+
+ dbus_message_iter_get_basic(string, &desc);
+ app->description = g_strdup(desc);
+ return TRUE;
+}
+
+static gboolean parse_chan_type(DBusMessageIter *iter, gpointer data,
+ GError **err)
+{
+ struct hdp_application *app = data;
+ DBusMessageIter *value;
+ char *chan_type;
+ int ctype;
+
+ ctype = dbus_message_iter_get_arg_type(iter);
+ value = iter;
+ if (ctype == DBUS_TYPE_VARIANT) {
+ DBusMessageIter variant;
+
+ /* Get value inside the variable */
+ dbus_message_iter_recurse(iter, &variant);
+ ctype = dbus_message_iter_get_arg_type(&variant);
+ value = &variant;
+ }
+
+ if (ctype != DBUS_TYPE_STRING) {
+ g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+ "Final value for channel type should be an string");
+ return FALSE;
+ }
+
+ dbus_message_iter_get_basic(value, &chan_type);
+
+ if (g_ascii_strcasecmp("Reliable", chan_type) == 0)
+ app->chan_type = HDP_RELIABLE_DC;
+ else if (g_ascii_strcasecmp("Streaming", chan_type) == 0)
+ app->chan_type = HDP_STREAMING_DC;
+ else {
+ g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR,
+ "Invalid value for data type");
+ return FALSE;
+ }
+
+ app->chan_type_set = TRUE;
+
+ return TRUE;
+}
+
+static struct dict_entry_func dict_parser[] = {
+ {"DataType", parse_data_type},
+ {"Role", parse_role},
+ {"Description", parse_desc},
+ {"ChannelType", parse_chan_type},
+ {NULL, NULL}
+};
+
+struct hdp_application *hdp_get_app_config(DBusMessageIter *iter, GError **err)
+{
+ struct hdp_application *app;
+
+ app = g_new0(struct hdp_application, 1);
+ app->ref = 1;
+ if (!parse_dict(dict_parser, iter, err, app))
+ goto fail;
+ if (!app->data_type_set || !app->role_set) {
+ g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR,
+ "Mandatory fields aren't set");
+ goto fail;
+ }
+ return app;
+
+fail:
+ hdp_application_unref(app);
+ return NULL;
+}
+
+static gboolean is_app_role(GSList *app_list, HdpRole role)
+{
+ GSList *l;
+
+ for (l = app_list; l; l = l->next) {
+ struct hdp_application *app = l->data;
+
+ if (app->role == role)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean set_sdp_services_uuid(sdp_record_t *record, HdpRole role)
+{
+ uuid_t svc_uuid_source, svc_uuid_sink;
+ sdp_list_t *svc_list = NULL;
+
+ sdp_uuid16_create(&svc_uuid_sink, HDP_SINK_SVCLASS_ID);
+ sdp_uuid16_create(&svc_uuid_source, HDP_SOURCE_SVCLASS_ID);
+
+ sdp_get_service_classes(record, &svc_list);
+
+ if (role == HDP_SOURCE) {
+ if (!sdp_list_find(svc_list, &svc_uuid_source, sdp_uuid_cmp))
+ svc_list = sdp_list_append(svc_list, &svc_uuid_source);
+ } else if (role == HDP_SINK) {
+ if (!sdp_list_find(svc_list, &svc_uuid_sink, sdp_uuid_cmp))
+ svc_list = sdp_list_append(svc_list, &svc_uuid_sink);
+ }
+
+ if (sdp_set_service_classes(record, svc_list) < 0) {
+ sdp_list_free(svc_list, NULL);
+ return FALSE;
+ }
+
+ sdp_list_free(svc_list, NULL);
+
+ return TRUE;
+}
+
+static gboolean register_service_protocols(struct hdp_adapter *adapter,
+ sdp_record_t *sdp_record)
+{
+ gboolean ret;
+ uuid_t l2cap_uuid, mcap_c_uuid;
+ sdp_list_t *l2cap_list, *proto_list = NULL, *mcap_list = NULL;
+ sdp_list_t *access_proto_list = NULL;
+ sdp_data_t *psm = NULL, *mcap_ver = NULL;
+ uint16_t version = MCAP_VERSION;
+
+ /* set l2cap information */
+ sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+ l2cap_list = sdp_list_append(NULL, &l2cap_uuid);
+ if (l2cap_list == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ psm = sdp_data_alloc(SDP_UINT16, &adapter->ccpsm);
+ if (psm == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ if (sdp_list_append(l2cap_list, psm) == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ proto_list = sdp_list_append(NULL, l2cap_list);
+ if (proto_list == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ /* set mcap information */
+ sdp_uuid16_create(&mcap_c_uuid, MCAP_CTRL_UUID);
+ mcap_list = sdp_list_append(NULL, &mcap_c_uuid);
+ if (mcap_list == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ mcap_ver = sdp_data_alloc(SDP_UINT16, &version);
+ if (mcap_ver == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ if (sdp_list_append(mcap_list, mcap_ver) == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ if (sdp_list_append(proto_list, mcap_list) == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ /* attach protocol information to service record */
+ access_proto_list = sdp_list_append(NULL, proto_list);
+ if (access_proto_list == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ if (sdp_set_access_protos(sdp_record, access_proto_list) < 0) {
+ ret = FALSE;
+ goto end;
+ }
+ ret = TRUE;
+
+end:
+ if (l2cap_list != NULL)
+ sdp_list_free(l2cap_list, NULL);
+ if (mcap_list != NULL)
+ sdp_list_free(mcap_list, NULL);
+ if (proto_list != NULL)
+ sdp_list_free(proto_list, NULL);
+ if (access_proto_list != NULL)
+ sdp_list_free(access_proto_list, NULL);
+ if (psm != NULL)
+ sdp_data_free(psm);
+ if (mcap_ver != NULL)
+ sdp_data_free(mcap_ver);
+
+ return ret;
+}
+
+static gboolean register_service_profiles(sdp_record_t *sdp_record)
+{
+ gboolean ret;
+ sdp_list_t *profile_list;
+ sdp_profile_desc_t hdp_profile;
+
+ /* set hdp information */
+ sdp_uuid16_create(&hdp_profile.uuid, HDP_SVCLASS_ID);
+ hdp_profile.version = HDP_VERSION;
+ profile_list = sdp_list_append(NULL, &hdp_profile);
+ if (profile_list == NULL)
+ return FALSE;
+
+ /* set profile descriptor list */
+ if (sdp_set_profile_descs(sdp_record, profile_list) < 0)
+ ret = FALSE;
+ else
+ ret = TRUE;
+
+ sdp_list_free(profile_list, NULL);
+
+ return ret;
+}
+
+static gboolean register_service_additional_protocols(
+ struct hdp_adapter *adapter,
+ sdp_record_t *sdp_record)
+{
+ gboolean ret;
+ uuid_t l2cap_uuid, mcap_d_uuid;
+ sdp_list_t *l2cap_list, *proto_list = NULL, *mcap_list = NULL;
+ sdp_list_t *access_proto_list = NULL;
+ sdp_data_t *psm = NULL;
+
+ /* set l2cap information */
+ sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID);
+ l2cap_list = sdp_list_append(NULL, &l2cap_uuid);
+ if (l2cap_list == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ psm = sdp_data_alloc(SDP_UINT16, &adapter->dcpsm);
+ if (psm == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ if (sdp_list_append(l2cap_list, psm) == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ proto_list = sdp_list_append(NULL, l2cap_list);
+ if (proto_list == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ /* set mcap information */
+ sdp_uuid16_create(&mcap_d_uuid, MCAP_DATA_UUID);
+ mcap_list = sdp_list_append(NULL, &mcap_d_uuid);
+ if (mcap_list == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ if (sdp_list_append(proto_list, mcap_list) == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ /* attach protocol information to service record */
+ access_proto_list = sdp_list_append(NULL, proto_list);
+ if (access_proto_list == NULL) {
+ ret = FALSE;
+ goto end;
+ }
+
+ if (sdp_set_add_access_protos(sdp_record, access_proto_list) < 0)
+ ret = FALSE;
+ else
+ ret = TRUE;
+
+end:
+ if (l2cap_list != NULL)
+ sdp_list_free(l2cap_list, NULL);
+ if (mcap_list != NULL)
+ sdp_list_free(mcap_list, NULL);
+ if (proto_list != NULL)
+ sdp_list_free(proto_list, NULL);
+ if (access_proto_list != NULL)
+ sdp_list_free(access_proto_list, NULL);
+ if (psm != NULL)
+ sdp_data_free(psm);
+
+ return ret;
+}
+
+static sdp_list_t *app_to_sdplist(struct hdp_application *app)
+{
+ sdp_data_t *mdepid,
+ *dtype = NULL,
+ *role = NULL,
+ *desc = NULL;
+ sdp_list_t *f_list = NULL;
+
+ mdepid = sdp_data_alloc(SDP_UINT8, &app->id);
+ if (mdepid == NULL)
+ return NULL;
+
+ dtype = sdp_data_alloc(SDP_UINT16, &app->data_type);
+ if (dtype == NULL)
+ goto fail;
+
+ role = sdp_data_alloc(SDP_UINT8, &app->role);
+ if (role == NULL)
+ goto fail;
+
+ if (app->description != NULL) {
+ desc = sdp_data_alloc(SDP_TEXT_STR8, app->description);
+ if (desc == NULL)
+ goto fail;
+ }
+
+ f_list = sdp_list_append(NULL, mdepid);
+ if (f_list == NULL)
+ goto fail;
+
+ if (sdp_list_append(f_list, dtype) == NULL)
+ goto fail;
+
+ if (sdp_list_append(f_list, role) == NULL)
+ goto fail;
+
+ if (desc != NULL)
+ if (sdp_list_append(f_list, desc) == NULL)
+ goto fail;
+
+ return f_list;
+
+fail:
+ if (f_list != NULL)
+ sdp_list_free(f_list, NULL);
+ if (mdepid != NULL)
+ sdp_data_free(mdepid);
+ if (dtype != NULL)
+ sdp_data_free(dtype);
+ if (role != NULL)
+ sdp_data_free(role);
+ if (desc != NULL)
+ sdp_data_free(desc);
+
+ return NULL;
+}
+
+static gboolean register_features(struct hdp_application *app,
+ sdp_list_t **sup_features)
+{
+ sdp_list_t *hdp_feature;
+
+ hdp_feature = app_to_sdplist(app);
+ if (hdp_feature == NULL)
+ goto fail;
+
+ if (*sup_features == NULL) {
+ *sup_features = sdp_list_append(NULL, hdp_feature);
+ if (*sup_features == NULL)
+ goto fail;
+ } else if (sdp_list_append(*sup_features, hdp_feature) == NULL) {
+ goto fail;
+ }
+
+ return TRUE;
+
+fail:
+ if (hdp_feature != NULL)
+ sdp_list_free(hdp_feature, (sdp_free_func_t)sdp_data_free);
+ return FALSE;
+}
+
+static void free_hdp_list(void *list)
+{
+ sdp_list_t *hdp_list = list;
+
+ sdp_list_free(hdp_list, (sdp_free_func_t)sdp_data_free);
+}
+
+static gboolean register_service_sup_features(GSList *app_list,
+ sdp_record_t *sdp_record)
+{
+ GSList *l;
+ sdp_list_t *sup_features = NULL;
+
+ for (l = app_list; l; l = l->next) {
+ if (!register_features(l->data, &sup_features))
+ return FALSE;
+ }
+
+ if (sdp_set_supp_feat(sdp_record, sup_features) < 0) {
+ sdp_list_free(sup_features, free_hdp_list);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean register_data_exchange_spec(sdp_record_t *record)
+{
+ sdp_data_t *spec;
+ uint8_t data_spec = DATA_EXCHANGE_SPEC_11073;
+ /* As by now 11073 is the only supported we set it by default */
+
+ spec = sdp_data_alloc(SDP_UINT8, &data_spec);
+ if (spec == NULL)
+ return FALSE;
+
+ if (sdp_attr_add(record, SDP_ATTR_DATA_EXCHANGE_SPEC, spec) < 0) {
+ sdp_data_free(spec);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean register_mcap_features(sdp_record_t *sdp_record)
+{
+ sdp_data_t *mcap_proc;
+ uint8_t mcap_sup_proc = MCAP_SUP_PROC;
+
+ mcap_proc = sdp_data_alloc(SDP_UINT8, &mcap_sup_proc);
+ if (mcap_proc == NULL)
+ return FALSE;
+
+ if (sdp_attr_add(sdp_record, SDP_ATTR_MCAP_SUPPORTED_PROCEDURES,
+ mcap_proc) < 0) {
+ sdp_data_free(mcap_proc);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean hdp_update_sdp_record(struct hdp_adapter *adapter, GSList *app_list)
+{
+ sdp_record_t *sdp_record;
+ bdaddr_t addr;
+
+ if (adapter->sdp_handler > 0)
+ remove_record_from_server(adapter->sdp_handler);
+
+ if (app_list == NULL) {
+ adapter->sdp_handler = 0;
+ return TRUE;
+ }
+
+ sdp_record = sdp_record_alloc();
+ if (sdp_record == NULL)
+ return FALSE;
+
+ if (adapter->sdp_handler > 0)
+ sdp_record->handle = adapter->sdp_handler;
+ else
+ sdp_record->handle = 0xffffffff; /* Set automatically */
+
+ if (is_app_role(app_list, HDP_SINK))
+ set_sdp_services_uuid(sdp_record, HDP_SINK);
+ if (is_app_role(app_list, HDP_SOURCE))
+ set_sdp_services_uuid(sdp_record, HDP_SOURCE);
+
+ if (!register_service_protocols(adapter, sdp_record))
+ goto fail;
+ if (!register_service_profiles(sdp_record))
+ goto fail;
+ if (!register_service_additional_protocols(adapter, sdp_record))
+ goto fail;
+
+ sdp_set_info_attr(sdp_record, HDP_SERVICE_NAME, HDP_SERVICE_PROVIDER,
+ HDP_SERVICE_DSC);
+ if (!register_service_sup_features(app_list, sdp_record))
+ goto fail;
+ if (!register_data_exchange_spec(sdp_record))
+ goto fail;
+
+ register_mcap_features(sdp_record);
+
+ if (sdp_set_record_state(sdp_record, adapter->record_state++) < 0)
+ goto fail;
+
+ adapter_get_address(adapter->btd_adapter, &addr);
+
+ if (add_record_to_server(&addr, sdp_record) < 0)
+ goto fail;
+ adapter->sdp_handler = sdp_record->handle;
+ return TRUE;
+
+fail:
+ if (sdp_record != NULL)
+ sdp_record_free(sdp_record);
+ return FALSE;
+}
+
+static gboolean check_role(uint8_t rec_role, uint8_t app_role)
+{
+ if ((rec_role == HDP_SINK && app_role == HDP_SOURCE) ||
+ (rec_role == HDP_SOURCE && app_role == HDP_SINK))
+ return TRUE;
+
+ return FALSE;
+}
+
+static gboolean get_mdep_from_rec(const sdp_record_t *rec, uint8_t role,
+ uint16_t d_type, uint8_t *mdep, char **desc)
+{
+ sdp_data_t *list, *feat;
+
+ if (desc == NULL && mdep == NULL)
+ return TRUE;
+
+ list = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES_LIST);
+ if (list == NULL || (list->dtd != SDP_SEQ8 && list->dtd != SDP_SEQ16 &&
+ list->dtd != SDP_SEQ32))
+ return FALSE;
+
+ for (feat = list->val.dataseq; feat; feat = feat->next) {
+ sdp_data_t *data_type, *mdepid, *role_t, *desc_t;
+
+ if (feat->dtd != SDP_SEQ8 && feat->dtd != SDP_SEQ16 &&
+ feat->dtd != SDP_SEQ32)
+ continue;
+
+ mdepid = feat->val.dataseq;
+ if (mdepid == NULL)
+ continue;
+
+ data_type = mdepid->next;
+ if (data_type == NULL)
+ continue;
+
+ role_t = data_type->next;
+ if (role_t == NULL)
+ continue;
+
+ desc_t = role_t->next;
+
+ if (data_type->dtd != SDP_UINT16 || mdepid->dtd != SDP_UINT8 ||
+ role_t->dtd != SDP_UINT8)
+ continue;
+
+ if (data_type->val.uint16 != d_type ||
+ !check_role(role_t->val.uint8, role))
+ continue;
+
+ if (mdep != NULL)
+ *mdep = mdepid->val.uint8;
+
+ if (desc != NULL && desc_t != NULL &&
+ (desc_t->dtd == SDP_TEXT_STR8 ||
+ desc_t->dtd == SDP_TEXT_STR16 ||
+ desc_t->dtd == SDP_TEXT_STR32))
+ *desc = g_strdup(desc_t->val.str);
+
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void get_mdep_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct get_mdep_data *mdep_data = user_data;
+ GError *gerr = NULL;
+ uint8_t mdep;
+
+ if (err < 0 || recs == NULL) {
+ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Error getting remote SDP records");
+ mdep_data->func(0, mdep_data->data, gerr);
+ g_error_free(gerr);
+ return;
+ }
+
+ if (!get_mdep_from_rec(recs->data, mdep_data->app->role,
+ mdep_data->app->data_type, &mdep, NULL)) {
+ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "No matching MDEP found");
+ mdep_data->func(0, mdep_data->data, gerr);
+ g_error_free(gerr);
+ return;
+ }
+
+ mdep_data->func(mdep, mdep_data->data, NULL);
+}
+
+static void free_mdep_data(gpointer data)
+{
+ struct get_mdep_data *mdep_data = data;
+
+ if (mdep_data->destroy)
+ mdep_data->destroy(mdep_data->data);
+ hdp_application_unref(mdep_data->app);
+
+ g_free(mdep_data);
+}
+
+gboolean hdp_get_mdep(struct hdp_device *device, struct hdp_application *app,
+ hdp_continue_mdep_f func, gpointer data,
+ GDestroyNotify destroy, GError **err)
+{
+ struct get_mdep_data *mdep_data;
+ bdaddr_t dst, src;
+ uuid_t uuid;
+
+ device_get_address(device->dev, &dst, NULL);
+ adapter_get_address(device_get_adapter(device->dev), &src);
+
+ mdep_data = g_new0(struct get_mdep_data, 1);
+ mdep_data->app = hdp_application_ref(app);
+ mdep_data->func = func;
+ mdep_data->data = data;
+ mdep_data->destroy = destroy;
+
+ bt_string2uuid(&uuid, HDP_UUID);
+ if (bt_search_service(&src, &dst, &uuid, get_mdep_cb, mdep_data,
+ free_mdep_data) < 0) {
+ g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Can't get remote SDP record");
+ g_free(mdep_data);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean get_prot_desc_entry(sdp_data_t *entry, int type, guint16 *val)
+{
+ sdp_data_t *iter;
+ int proto;
+
+ if (entry == NULL || (entry->dtd != SDP_SEQ8 &&
+ entry->dtd != SDP_SEQ16 && entry->dtd != SDP_SEQ32))
+ return FALSE;
+
+ iter = entry->val.dataseq;
+ if (!(iter->dtd & SDP_UUID_UNSPEC))
+ return FALSE;
+
+ proto = sdp_uuid_to_proto(&iter->val.uuid);
+ if (proto != type)
+ return FALSE;
+
+ if (val == NULL)
+ return TRUE;
+
+ iter = iter->next;
+ if (iter->dtd != SDP_UINT16)
+ return FALSE;
+
+ *val = iter->val.uint16;
+
+ return TRUE;
+}
+
+static gboolean hdp_get_prot_desc_list(const sdp_record_t *rec, guint16 *psm,
+ guint16 *version)
+{
+ sdp_data_t *pdl, *p0, *p1;
+
+ if (psm == NULL && version == NULL)
+ return TRUE;
+
+ pdl = sdp_data_get(rec, SDP_ATTR_PROTO_DESC_LIST);
+ if (pdl == NULL || (pdl->dtd != SDP_SEQ8 && pdl->dtd != SDP_SEQ16 &&
+ pdl->dtd != SDP_SEQ32))
+ return FALSE;
+
+ p0 = pdl->val.dataseq;
+ if (!get_prot_desc_entry(p0, L2CAP_UUID, psm))
+ return FALSE;
+
+ p1 = p0->next;
+ if (!get_prot_desc_entry(p1, MCAP_CTRL_UUID, version))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean hdp_get_add_prot_desc_list(const sdp_record_t *rec,
+ guint16 *psm)
+{
+ sdp_data_t *pdl, *p0, *p1;
+
+ if (psm == NULL)
+ return TRUE;
+
+ pdl = sdp_data_get(rec, SDP_ATTR_ADD_PROTO_DESC_LIST);
+ if (pdl == NULL || pdl->dtd != SDP_SEQ8)
+ return FALSE;
+ pdl = pdl->val.dataseq;
+ if (pdl->dtd != SDP_SEQ8)
+ return FALSE;
+
+ p0 = pdl->val.dataseq;
+
+ if (!get_prot_desc_entry(p0, L2CAP_UUID, psm))
+ return FALSE;
+ p1 = p0->next;
+ if (!get_prot_desc_entry(p1, MCAP_DATA_UUID, NULL))
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean get_ccpsm(sdp_list_t *recs, uint16_t *ccpsm)
+{
+ sdp_list_t *l;
+
+ for (l = recs; l; l = l->next) {
+ sdp_record_t *rec = l->data;
+
+ if (hdp_get_prot_desc_list(rec, ccpsm, NULL))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static gboolean get_dcpsm(sdp_list_t *recs, uint16_t *dcpsm)
+{
+ sdp_list_t *l;
+
+ for (l = recs; l; l = l->next) {
+ sdp_record_t *rec = l->data;
+
+ if (hdp_get_add_prot_desc_list(rec, dcpsm))
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static void con_mcl_data_unref(struct conn_mcl_data *conn_data)
+{
+ if (conn_data == NULL)
+ return;
+
+ if (--conn_data->refs > 0)
+ return;
+
+ if (conn_data->destroy)
+ conn_data->destroy(conn_data->data);
+
+ health_device_unref(conn_data->dev);
+ g_free(conn_data);
+}
+
+static void destroy_con_mcl_data(gpointer data)
+{
+ con_mcl_data_unref(data);
+}
+
+static struct conn_mcl_data *con_mcl_data_ref(struct conn_mcl_data *conn_data)
+{
+ if (conn_data == NULL)
+ return NULL;
+
+ conn_data->refs++;
+ return conn_data;
+}
+
+static void create_mcl_cb(struct mcap_mcl *mcl, GError *err, gpointer data)
+{
+ struct conn_mcl_data *conn_data = data;
+ struct hdp_device *device = conn_data->dev;
+ GError *gerr = NULL;
+
+ if (err != NULL) {
+ conn_data->func(conn_data->data, err);
+ return;
+ }
+
+ if (device->mcl == NULL)
+ device->mcl = mcap_mcl_ref(mcl);
+ device->mcl_conn = TRUE;
+
+ hdp_set_mcl_cb(device, &gerr);
+
+ conn_data->func(conn_data->data, gerr);
+ if (gerr != NULL)
+ g_error_free(gerr);
+}
+
+static void search_cb(sdp_list_t *recs, int err, gpointer user_data)
+{
+ struct conn_mcl_data *conn_data = user_data;
+ GError *gerr = NULL;
+ bdaddr_t dst;
+ uint16_t ccpsm;
+
+ if (conn_data->dev->hdp_adapter->mi == NULL) {
+ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Mcap instance released");
+ goto fail;
+ }
+
+ if (err < 0 || recs == NULL) {
+ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Error getting remote SDP records");
+ goto fail;
+ }
+
+ if (!get_ccpsm(recs, &ccpsm)) {
+ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Can't get remote PSM for control channel");
+ goto fail;
+ }
+
+ conn_data = con_mcl_data_ref(conn_data);
+
+ device_get_address(conn_data->dev->dev, &dst, NULL);
+ if (!mcap_create_mcl(conn_data->dev->hdp_adapter->mi, &dst, ccpsm,
+ create_mcl_cb, conn_data,
+ destroy_con_mcl_data, &gerr)) {
+ con_mcl_data_unref(conn_data);
+ goto fail;
+ }
+ return;
+fail:
+ conn_data->func(conn_data->data, gerr);
+ g_error_free(gerr);
+}
+
+gboolean hdp_establish_mcl(struct hdp_device *device,
+ hdp_continue_proc_f func,
+ gpointer data,
+ GDestroyNotify destroy,
+ GError **err)
+{
+ struct conn_mcl_data *conn_data;
+ bdaddr_t dst, src;
+ uuid_t uuid;
+
+ device_get_address(device->dev, &dst, NULL);
+ adapter_get_address(device_get_adapter(device->dev), &src);
+
+ conn_data = g_new0(struct conn_mcl_data, 1);
+ conn_data->refs = 1;
+ conn_data->func = func;
+ conn_data->data = data;
+ conn_data->destroy = destroy;
+ conn_data->dev = health_device_ref(device);
+
+ bt_string2uuid(&uuid, HDP_UUID);
+ if (bt_search_service(&src, &dst, &uuid, search_cb, conn_data,
+ destroy_con_mcl_data) < 0) {
+ g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Can't get remote SDP record");
+ g_free(conn_data);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void get_dcpsm_cb(sdp_list_t *recs, int err, gpointer data)
+{
+ struct get_dcpsm_data *dcpsm_data = data;
+ GError *gerr = NULL;
+ uint16_t dcpsm;
+
+ if (err < 0 || recs == NULL) {
+ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Error getting remote SDP records");
+ goto fail;
+ }
+
+ if (!get_dcpsm(recs, &dcpsm)) {
+ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Can't get remote PSM for data channel");
+ goto fail;
+ }
+
+ dcpsm_data->func(dcpsm, dcpsm_data->data, NULL);
+ return;
+
+fail:
+ dcpsm_data->func(0, dcpsm_data->data, gerr);
+ g_error_free(gerr);
+}
+
+static void free_dcpsm_data(gpointer data)
+{
+ struct get_dcpsm_data *dcpsm_data = data;
+
+ if (dcpsm_data == NULL)
+ return;
+
+ if (dcpsm_data->destroy)
+ dcpsm_data->destroy(dcpsm_data->data);
+
+ g_free(dcpsm_data);
+}
+
+gboolean hdp_get_dcpsm(struct hdp_device *device, hdp_continue_dcpsm_f func,
+ gpointer data,
+ GDestroyNotify destroy,
+ GError **err)
+{
+ struct get_dcpsm_data *dcpsm_data;
+ bdaddr_t dst, src;
+ uuid_t uuid;
+
+ device_get_address(device->dev, &dst, NULL);
+ adapter_get_address(device_get_adapter(device->dev), &src);
+
+ dcpsm_data = g_new0(struct get_dcpsm_data, 1);
+ dcpsm_data->func = func;
+ dcpsm_data->data = data;
+ dcpsm_data->destroy = destroy;
+
+ bt_string2uuid(&uuid, HDP_UUID);
+ if (bt_search_service(&src, &dst, &uuid, get_dcpsm_cb, dcpsm_data,
+ free_dcpsm_data) < 0) {
+ g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR,
+ "Can't get remote SDP record");
+ g_free(dcpsm_data);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void hdp_free_application(struct hdp_application *app)
+{
+ if (app->dbus_watcher > 0)
+ g_dbus_remove_watch(app->conn, app->dbus_watcher);
+
+ if (app->conn != NULL)
+ dbus_connection_unref(app->conn);
+ g_free(app->oname);
+ g_free(app->description);
+ g_free(app->path);
+ g_free(app);
+}
+
+struct hdp_application *hdp_application_ref(struct hdp_application *app)
+{
+ if (app == NULL)
+ return NULL;
+
+ app->ref++;
+
+ DBG("health_application_ref(%p): ref=%d", app, app->ref);
+ return app;
+}
+
+void hdp_application_unref(struct hdp_application *app)
+{
+ if (app == NULL)
+ return;
+
+ app->ref--;
+
+ DBG("health_application_unref(%p): ref=%d", app, app->ref);
+ if (app->ref > 0)
+ return;
+
+ hdp_free_application(app);
+}
diff --git a/profiles/health/hdp_util.h b/profiles/health/hdp_util.h
new file mode 100644
index 000000000..35e1196b2
--- /dev/null
+++ b/profiles/health/hdp_util.h
@@ -0,0 +1,58 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __HDP_UTIL_H__
+#define __HDP_UTIL_H__
+
+typedef void (*hdp_continue_mdep_f)(uint8_t mdep, gpointer user_data,
+ GError *err);
+typedef void (*hdp_continue_dcpsm_f)(uint16_t dcpsm, gpointer user_data,
+ GError *err);
+typedef void (*hdp_continue_proc_f)(gpointer user_data, GError *err);
+
+struct hdp_application *hdp_get_app_config(DBusMessageIter *iter, GError **err);
+gboolean hdp_update_sdp_record(struct hdp_adapter *adapter, GSList *app_list);
+gboolean hdp_get_mdep(struct hdp_device *device, struct hdp_application *app,
+ hdp_continue_mdep_f func,
+ gpointer data, GDestroyNotify destroy,
+ GError **err);
+
+gboolean hdp_establish_mcl(struct hdp_device *device,
+ hdp_continue_proc_f func,
+ gpointer data,
+ GDestroyNotify destroy,
+ GError **err);
+
+gboolean hdp_get_dcpsm(struct hdp_device *device, hdp_continue_dcpsm_f func,
+ gpointer data,
+ GDestroyNotify destroy,
+ GError **err);
+
+
+struct hdp_application *hdp_application_ref(struct hdp_application *app);
+void hdp_application_unref(struct hdp_application *app);
+
+struct hdp_device *health_device_ref(struct hdp_device *hdp_dev);
+void health_device_unref(struct hdp_device *hdp_dev);
+
+
+#endif /* __HDP_UTIL_H__ */
diff --git a/profiles/health/mcap.c b/profiles/health/mcap.c
new file mode 100644
index 000000000..b76d88a5a
--- /dev/null
+++ b/profiles/health/mcap.c
@@ -0,0 +1,2194 @@
+/*
+ *
+ * MCAP for BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#include <netinet/in.h>
+#include <stdlib.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include <glib.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+
+#include <btio.h>
+#include <log.h>
+#include <error.h>
+
+#include "mcap.h"
+#include "mcap_lib.h"
+#include "mcap_internal.h"
+
+#define RESPONSE_TIMER 6 /* seconds */
+#define MAX_CACHED 10 /* 10 devices */
+
+#define MCAP_ERROR g_quark_from_static_string("mcap-error-quark")
+
+#define RELEASE_TIMER(__mcl) do { \
+ if (__mcl->tid) { \
+ g_source_remove(__mcl->tid); \
+ __mcl->tid = 0; \
+ } \
+} while(0)
+
+struct connect_mcl {
+ struct mcap_mcl *mcl; /* MCL for this operation */
+ mcap_mcl_connect_cb connect_cb; /* Connect callback */
+ GDestroyNotify destroy; /* Destroy callback */
+ gpointer user_data; /* Callback user data */
+};
+
+typedef union {
+ mcap_mdl_operation_cb op;
+ mcap_mdl_operation_conf_cb op_conf;
+ mcap_mdl_notify_cb notify;
+} mcap_cb_type;
+
+struct mcap_mdl_op_cb {
+ struct mcap_mdl *mdl; /* MDL for this operation */
+ mcap_cb_type cb; /* Operation callback */
+ GDestroyNotify destroy; /* Destroy callback */
+ gpointer user_data; /* Callback user data */
+};
+
+/* MCAP finite state machine functions */
+static void proc_req_connected(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
+static void proc_req_pending(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
+static void proc_req_active(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t l);
+
+static void (*proc_req[])(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len) = {
+ proc_req_connected,
+ proc_req_pending,
+ proc_req_active
+};
+
+static void mcap_cache_mcl(struct mcap_mcl *mcl);
+
+static void default_mdl_connected_cb(struct mcap_mdl *mdl, gpointer data)
+{
+ DBG("MCAP Unmanaged mdl connection");
+}
+
+static void default_mdl_closed_cb(struct mcap_mdl *mdl, gpointer data)
+{
+ DBG("MCAP Unmanaged mdl closed");
+}
+
+static void default_mdl_deleted_cb(struct mcap_mdl *mdl, gpointer data)
+{
+ DBG("MCAP Unmanaged mdl deleted");
+}
+
+static void default_mdl_aborted_cb(struct mcap_mdl *mdl, gpointer data)
+{
+ DBG("MCAP Unmanaged mdl aborted");
+}
+
+static uint8_t default_mdl_conn_req_cb(struct mcap_mcl *mcl,
+ uint8_t mdepid, uint16_t mdlid,
+ uint8_t *conf, gpointer data)
+{
+ DBG("MCAP mdl remote connection aborted");
+ /* Due to this callback isn't managed this request won't be supported */
+ return MCAP_REQUEST_NOT_SUPPORTED;
+}
+
+static uint8_t default_mdl_reconn_req_cb(struct mcap_mdl *mdl,
+ gpointer data)
+{
+ DBG("MCAP mdl remote reconnection aborted");
+ /* Due to this callback isn't managed this request won't be supported */
+ return MCAP_REQUEST_NOT_SUPPORTED;
+}
+
+static void set_default_cb(struct mcap_mcl *mcl)
+{
+ if (!mcl->cb)
+ mcl->cb = g_new0(struct mcap_mdl_cb, 1);
+
+ mcl->cb->mdl_connected = default_mdl_connected_cb;
+ mcl->cb->mdl_closed = default_mdl_closed_cb;
+ mcl->cb->mdl_deleted = default_mdl_deleted_cb;
+ mcl->cb->mdl_aborted = default_mdl_aborted_cb;
+ mcl->cb->mdl_conn_req = default_mdl_conn_req_cb;
+ mcl->cb->mdl_reconn_req = default_mdl_reconn_req_cb;
+}
+
+static char *error2str(uint8_t rc)
+{
+ switch (rc) {
+ case MCAP_SUCCESS:
+ return "Success";
+ case MCAP_INVALID_OP_CODE:
+ return "Invalid Op Code";
+ case MCAP_INVALID_PARAM_VALUE:
+ return "Invalid Parameter Value";
+ case MCAP_INVALID_MDEP:
+ return "Invalid MDEP";
+ case MCAP_MDEP_BUSY:
+ return "MDEP Busy";
+ case MCAP_INVALID_MDL:
+ return "Invalid MDL";
+ case MCAP_MDL_BUSY:
+ return "MDL Busy";
+ case MCAP_INVALID_OPERATION:
+ return "Invalid Operation";
+ case MCAP_RESOURCE_UNAVAILABLE:
+ return "Resource Unavailable";
+ case MCAP_UNSPECIFIED_ERROR:
+ return "Unspecified Error";
+ case MCAP_REQUEST_NOT_SUPPORTED:
+ return "Request Not Supported";
+ case MCAP_CONFIGURATION_REJECTED:
+ return "Configuration Rejected";
+ default:
+ return "Unknown Response Code";
+ }
+}
+
+static gboolean mcap_send_std_opcode(struct mcap_mcl *mcl, void *cmd,
+ uint32_t size, GError **err)
+{
+ if (mcl->state == MCL_IDLE) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+ "MCL is not connected");
+ return FALSE;
+ }
+
+ if (mcl->req != MCL_AVAILABLE) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_RESOURCE_UNAVAILABLE,
+ "Pending request");
+ return FALSE;
+ }
+
+ if (!(mcl->ctrl & MCAP_CTRL_STD_OP)) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_REQUEST_NOT_SUPPORTED,
+ "Remote does not support standard opcodes");
+ return FALSE;
+ }
+
+ if (mcl->state == MCL_PENDING) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_OPERATION,
+ "Not Std Op. Codes can be sent in PENDING State");
+ return FALSE;
+ }
+
+ if (mcap_send_data(g_io_channel_unix_get_fd(mcl->cc), cmd, size) < 0) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+ "Command can't be sent, write error");
+ return FALSE;
+ }
+
+ mcl->lcmd = cmd;
+ mcl->req = MCL_WAITING_RSP;
+
+ return TRUE;
+}
+
+static void update_mcl_state(struct mcap_mcl *mcl)
+{
+ GSList *l;
+ struct mcap_mdl *mdl;
+
+ if (mcl->state == MCL_PENDING)
+ return;
+
+ for (l = mcl->mdls; l; l = l->next) {
+ mdl = l->data;
+
+ if (mdl->state == MDL_CONNECTED) {
+ mcl->state = MCL_ACTIVE;
+ return;
+ }
+ }
+
+ mcl->state = MCL_CONNECTED;
+}
+
+static void shutdown_mdl(struct mcap_mdl *mdl)
+{
+ mdl->state = MDL_CLOSED;
+
+ if (mdl->wid) {
+ g_source_remove(mdl->wid);
+ mdl->wid = 0;
+ }
+
+ if (mdl->dc) {
+ g_io_channel_shutdown(mdl->dc, TRUE, NULL);
+ g_io_channel_unref(mdl->dc);
+ mdl->dc = NULL;
+ }
+}
+
+static void free_mdl(struct mcap_mdl *mdl)
+{
+ if (!mdl)
+ return;
+
+ mcap_mcl_unref(mdl->mcl);
+ g_free(mdl);
+}
+
+static gint cmp_mdl_state(gconstpointer a, gconstpointer b)
+{
+ const struct mcap_mdl *mdl = a;
+ const MDLState *st = b;
+
+ if (mdl->state == *st)
+ return 0;
+ else if (mdl->state < *st)
+ return -1;
+ else
+ return 1;
+}
+
+static void free_mcap_mdl_op(struct mcap_mdl_op_cb *op)
+{
+ if (op->destroy)
+ op->destroy(op->user_data);
+
+ if (op->mdl)
+ mcap_mdl_unref(op->mdl);
+
+ g_free(op);
+}
+
+static void free_mcl_priv_data(struct mcap_mcl *mcl)
+{
+ free_mcap_mdl_op(mcl->priv_data);
+ mcl->priv_data = NULL;
+}
+
+static void mcap_notify_error(struct mcap_mcl *mcl, GError *err)
+{
+ struct mcap_mdl_op_cb *con = mcl->priv_data;
+ struct mcap_mdl *mdl;
+ MDLState st;
+ GSList *l;
+
+ if (!con || !mcl->lcmd)
+ return;
+
+ switch (mcl->lcmd[0]) {
+ case MCAP_MD_CREATE_MDL_REQ:
+ st = MDL_WAITING;
+ l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
+ mdl = l->data;
+ mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+ mcap_mdl_unref(mdl);
+ update_mcl_state(mcl);
+ con->cb.op_conf(NULL, 0, err, con->user_data);
+ break;
+ case MCAP_MD_ABORT_MDL_REQ:
+ st = MDL_WAITING;
+ l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
+ shutdown_mdl(l->data);
+ update_mcl_state(mcl);
+ con->cb.notify(err, con->user_data);
+ break;
+ case MCAP_MD_DELETE_MDL_REQ:
+ for (l = mcl->mdls; l; l = l->next) {
+ mdl = l->data;
+ if (mdl->state == MDL_DELETING)
+ mdl->state = (mdl->dc) ? MDL_CONNECTED :
+ MDL_CLOSED;
+ }
+ update_mcl_state(mcl);
+ con->cb.notify(err, con->user_data);
+ break;
+ case MCAP_MD_RECONNECT_MDL_REQ:
+ st = MDL_WAITING;
+ l = g_slist_find_custom(mcl->mdls, &st, cmp_mdl_state);
+ shutdown_mdl(l->data);
+ update_mcl_state(mcl);
+ con->cb.op(NULL, err, con->user_data);
+ break;
+ }
+
+ free_mcl_priv_data(mcl);
+ g_free(mcl->lcmd);
+ mcl->lcmd = NULL;
+}
+
+int mcap_send_data(int sock, const void *buf, uint32_t size)
+{
+ const uint8_t *buf_b = buf;
+ uint32_t sent = 0;
+
+ while (sent < size) {
+ int n = write(sock, buf_b + sent, size - sent);
+ if (n < 0)
+ return -1;
+ sent += n;
+ }
+
+ return 0;
+}
+
+static int mcap_send_cmd(struct mcap_mcl *mcl, uint8_t oc, uint8_t rc,
+ uint16_t mdl, uint8_t *data, size_t len)
+{
+ mcap_rsp *cmd;
+ int sock, sent;
+
+ if (mcl->cc == NULL)
+ return -1;
+
+ sock = g_io_channel_unix_get_fd(mcl->cc);
+
+ cmd = g_malloc(sizeof(mcap_rsp) + len);
+ cmd->op = oc;
+ cmd->rc = rc;
+ cmd->mdl = htons(mdl);
+
+ if (data && len > 0)
+ memcpy(cmd->data, data, len);
+
+ sent = mcap_send_data(sock, cmd, sizeof(mcap_rsp) + len);
+ g_free(cmd);
+
+ return sent;
+}
+
+static struct mcap_mdl *get_mdl(struct mcap_mcl *mcl, uint16_t mdlid)
+{
+ GSList *l;
+ struct mcap_mdl *mdl;
+
+ for (l = mcl->mdls; l; l = l->next) {
+ mdl = l->data;
+ if (mdlid == mdl->mdlid)
+ return mdl;
+ }
+
+ return NULL;
+}
+
+static uint16_t generate_mdlid(struct mcap_mcl *mcl)
+{
+ uint16_t mdlid = mcl->next_mdl;
+ struct mcap_mdl *mdl;
+
+ do {
+ mdl = get_mdl(mcl, mdlid);
+ if (!mdl) {
+ mcl->next_mdl = (mdlid % MCAP_MDLID_FINAL) + 1;
+ return mdlid;
+ } else
+ mdlid = (mdlid % MCAP_MDLID_FINAL) + 1;
+ } while (mdlid != mcl->next_mdl);
+
+ /* No more mdlids availables */
+ return 0;
+}
+
+static mcap_md_req *create_req(uint8_t op, uint16_t mdl_id)
+{
+ mcap_md_req *req_cmd;
+
+ req_cmd = g_new0(mcap_md_req, 1);
+
+ req_cmd->op = op;
+ req_cmd->mdl = htons(mdl_id);
+
+ return req_cmd;
+}
+
+static mcap_md_create_mdl_req *create_mdl_req(uint16_t mdl_id, uint8_t mdep,
+ uint8_t conf)
+{
+ mcap_md_create_mdl_req *req_mdl;
+
+ req_mdl = g_new0(mcap_md_create_mdl_req, 1);
+
+ req_mdl->op = MCAP_MD_CREATE_MDL_REQ;
+ req_mdl->mdl = htons(mdl_id);
+ req_mdl->mdep = mdep;
+ req_mdl->conf = conf;
+
+ return req_mdl;
+}
+
+static gint compare_mdl(gconstpointer a, gconstpointer b)
+{
+ const struct mcap_mdl *mdla = a;
+ const struct mcap_mdl *mdlb = b;
+
+ if (mdla->mdlid == mdlb->mdlid)
+ return 0;
+ else if (mdla->mdlid < mdlb->mdlid)
+ return -1;
+ else
+ return 1;
+}
+
+static gboolean wait_response_timer(gpointer data)
+{
+ struct mcap_mcl *mcl = data;
+
+ GError *gerr = NULL;
+
+ RELEASE_TIMER(mcl);
+
+ g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_FAILED,
+ "Timeout waiting response");
+
+ mcap_notify_error(mcl, gerr);
+
+ g_error_free(gerr);
+ mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data);
+ mcap_cache_mcl(mcl);
+
+ return FALSE;
+}
+
+gboolean mcap_create_mdl(struct mcap_mcl *mcl,
+ uint8_t mdepid,
+ uint8_t conf,
+ mcap_mdl_operation_conf_cb connect_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err)
+{
+ struct mcap_mdl *mdl;
+ struct mcap_mdl_op_cb *con;
+ mcap_md_create_mdl_req *cmd;
+ uint16_t id;
+
+ id = generate_mdlid(mcl);
+ if (!id) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+ "Not more mdlids available");
+ return FALSE;
+ }
+
+ mdl = g_new0(struct mcap_mdl, 1);
+ mdl->mcl = mcap_mcl_ref(mcl);
+ mdl->mdlid = id;
+ mdl->mdep_id = mdepid;
+ mdl->state = MDL_WAITING;
+
+ con = g_new0(struct mcap_mdl_op_cb, 1);
+ con->mdl = mcap_mdl_ref(mdl);
+ con->cb.op_conf = connect_cb;
+ con->destroy = destroy;
+ con->user_data = user_data;
+
+ cmd = create_mdl_req(id, mdepid, conf);
+ if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_create_mdl_req),
+ err)) {
+ mcap_mdl_unref(con->mdl);
+ g_free(con);
+ g_free(cmd);
+ return FALSE;
+ }
+
+ mcl->state = MCL_ACTIVE;
+ mcl->priv_data = con;
+
+ mcl->mdls = g_slist_insert_sorted(mcl->mdls, mcap_mdl_ref(mdl),
+ compare_mdl);
+ mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
+ mcl);
+ return TRUE;
+}
+
+gboolean mcap_reconnect_mdl(struct mcap_mdl *mdl,
+ mcap_mdl_operation_cb reconnect_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err)
+{
+ struct mcap_mdl_op_cb *con;
+ struct mcap_mcl *mcl = mdl->mcl;
+ mcap_md_req *cmd;
+
+ if (mdl->state != MDL_CLOSED) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+ "MDL is not closed");
+ return FALSE;
+ }
+
+ cmd = create_req(MCAP_MD_RECONNECT_MDL_REQ, mdl->mdlid);
+ if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
+ g_free(cmd);
+ return FALSE;
+ }
+
+ mdl->state = MDL_WAITING;
+
+ con = g_new0(struct mcap_mdl_op_cb, 1);
+ con->mdl = mcap_mdl_ref(mdl);
+ con->cb.op = reconnect_cb;
+ con->destroy = destroy;
+ con->user_data = user_data;
+
+ mcl->state = MCL_ACTIVE;
+ mcl->priv_data = con;
+
+ mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
+ mcl);
+ return TRUE;
+}
+
+static gboolean send_delete_req(struct mcap_mcl *mcl,
+ struct mcap_mdl_op_cb *con,
+ uint16_t mdlid,
+ GError **err)
+{
+ mcap_md_req *cmd;
+
+ cmd = create_req(MCAP_MD_DELETE_MDL_REQ, mdlid);
+ if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
+ g_free(cmd);
+ return FALSE;
+ }
+
+ mcl->priv_data = con;
+
+ mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
+ mcl);
+ return TRUE;
+}
+
+gboolean mcap_delete_all_mdls(struct mcap_mcl *mcl,
+ mcap_mdl_notify_cb delete_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err)
+{
+ GSList *l;
+ struct mcap_mdl *mdl;
+ struct mcap_mdl_op_cb *con;
+
+ DBG("MCL in state: %d", mcl->state);
+ if (!mcl->mdls) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+ "There are not MDLs created");
+ return FALSE;
+ }
+
+ for (l = mcl->mdls; l; l = l->next) {
+ mdl = l->data;
+ if (mdl->state != MDL_WAITING)
+ mdl->state = MDL_DELETING;
+ }
+
+ con = g_new0(struct mcap_mdl_op_cb, 1);
+ con->mdl = NULL;
+ con->cb.notify = delete_cb;
+ con->destroy = destroy;
+ con->user_data = user_data;
+
+
+ if (!send_delete_req(mcl, con, MCAP_ALL_MDLIDS, err)) {
+ g_free(con);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean mcap_delete_mdl(struct mcap_mdl *mdl, mcap_mdl_notify_cb delete_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err)
+{
+ struct mcap_mcl *mcl= mdl->mcl;
+ struct mcap_mdl_op_cb *con;
+ GSList *l;
+
+ l = g_slist_find(mcl->mdls, mdl);
+
+ if (!l) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_MDL,
+ "%s" , error2str(MCAP_INVALID_MDEP));
+ return FALSE;
+ }
+
+ if (mdl->state == MDL_WAITING) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+ "Mdl is not created");
+ return FALSE;
+ }
+
+ mdl->state = MDL_DELETING;
+
+ con = g_new0(struct mcap_mdl_op_cb, 1);
+ con->mdl = mcap_mdl_ref(mdl);
+ con->cb.notify = delete_cb;
+ con->destroy = destroy;
+ con->user_data = user_data;
+
+ if (!send_delete_req(mcl, con, mdl->mdlid, err)) {
+ mcap_mdl_unref(con->mdl);
+ g_free(con);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+gboolean mcap_mdl_abort(struct mcap_mdl *mdl, mcap_mdl_notify_cb abort_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err)
+{
+ struct mcap_mdl_op_cb *con;
+ struct mcap_mcl *mcl = mdl->mcl;
+ mcap_md_req *cmd;
+
+ if (mdl->state != MDL_WAITING) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_FAILED,
+ "Mdl in invalid state");
+ return FALSE;
+ }
+
+ cmd = create_req(MCAP_MD_ABORT_MDL_REQ, mdl->mdlid);
+ if (!mcap_send_std_opcode(mcl, cmd, sizeof(mcap_md_req), err)) {
+ g_free(cmd);
+ return FALSE;
+ }
+
+ con = g_new0(struct mcap_mdl_op_cb, 1);
+ con->mdl = mcap_mdl_ref(mdl);
+ con->cb.notify = abort_cb;
+ con->destroy = destroy;
+ con->user_data = user_data;
+
+ mcl->priv_data = con;
+ mcl->tid = g_timeout_add_seconds(RESPONSE_TIMER, wait_response_timer,
+ mcl);
+ return TRUE;
+}
+
+static struct mcap_mcl *find_mcl(GSList *list, const bdaddr_t *addr)
+{
+ struct mcap_mcl *mcl;
+
+ for (; list; list = list->next) {
+ mcl = list->data;
+
+ if (!bacmp(&mcl->addr, addr))
+ return mcl;
+ }
+
+ return NULL;
+}
+
+int mcap_mdl_get_fd(struct mcap_mdl *mdl)
+{
+ if (!mdl || mdl->state != MDL_CONNECTED)
+ return -ENOTCONN;
+
+ return g_io_channel_unix_get_fd(mdl->dc);
+}
+
+uint16_t mcap_mdl_get_mdlid(struct mcap_mdl *mdl)
+{
+ if (!mdl)
+ return MCAP_MDLID_RESERVED;
+
+ return mdl->mdlid;
+}
+
+static void close_mcl(struct mcap_mcl *mcl, gboolean cache_requested)
+{
+ gboolean save = ((!(mcl->ctrl & MCAP_CTRL_FREE)) && cache_requested);
+
+ RELEASE_TIMER(mcl);
+
+ if (mcl->cc) {
+ g_io_channel_shutdown(mcl->cc, TRUE, NULL);
+ g_io_channel_unref(mcl->cc);
+ mcl->cc = NULL;
+ }
+
+ if (mcl->wid) {
+ g_source_remove(mcl->wid);
+ mcl->wid = 0;
+ }
+
+ if (mcl->lcmd) {
+ g_free(mcl->lcmd);
+ mcl->lcmd = NULL;
+ }
+
+ if (mcl->priv_data)
+ free_mcl_priv_data(mcl);
+
+ g_slist_foreach(mcl->mdls, (GFunc) shutdown_mdl, NULL);
+
+ mcap_sync_stop(mcl);
+
+ mcl->state = MCL_IDLE;
+
+ if (save)
+ return;
+
+ g_slist_foreach(mcl->mdls, (GFunc) mcap_mdl_unref, NULL);
+ g_slist_free(mcl->mdls);
+ mcl->mdls = NULL;
+}
+
+static void mcap_mcl_shutdown(struct mcap_mcl *mcl)
+{
+ close_mcl(mcl, TRUE);
+}
+
+static void mcap_mcl_release(struct mcap_mcl *mcl)
+{
+ close_mcl(mcl, FALSE);
+}
+
+static void mcap_cache_mcl(struct mcap_mcl *mcl)
+{
+ GSList *l;
+ struct mcap_mcl *last;
+ int len;
+
+ if (mcl->ctrl & MCAP_CTRL_CACHED)
+ return;
+
+ mcl->mi->mcls = g_slist_remove(mcl->mi->mcls, mcl);
+
+ if (mcl->ctrl & MCAP_CTRL_NOCACHE) {
+ mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
+ mcap_mcl_release(mcl);
+ mcap_mcl_unref(mcl);
+ return;
+ }
+
+ DBG("Caching MCL");
+
+ len = g_slist_length(mcl->mi->cached);
+ if (len == MAX_CACHED) {
+ /* Remove the latest cached mcl */
+ l = g_slist_last(mcl->mi->cached);
+ last = l->data;
+ mcl->mi->cached = g_slist_remove(mcl->mi->cached, last);
+ last->ctrl &= ~MCAP_CTRL_CACHED;
+ if (last->ctrl & MCAP_CTRL_CONN) {
+ /* We have to release this MCL if */
+ /* connection is not successful */
+ last->ctrl |= MCAP_CTRL_FREE;
+ } else {
+ mcap_mcl_release(last);
+ last->mi->mcl_uncached_cb(last, last->mi->user_data);
+ }
+ mcap_mcl_unref(last);
+ }
+
+ mcl->mi->cached = g_slist_prepend(mcl->mi->cached, mcl);
+ mcl->ctrl |= MCAP_CTRL_CACHED;
+ mcap_mcl_shutdown(mcl);
+}
+
+static void mcap_uncache_mcl(struct mcap_mcl *mcl)
+{
+ if (!(mcl->ctrl & MCAP_CTRL_CACHED))
+ return;
+
+ DBG("Got MCL from cache");
+
+ mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
+ mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls, mcl);
+ mcl->ctrl &= ~MCAP_CTRL_CACHED;
+ mcl->ctrl &= ~MCAP_CTRL_FREE;
+}
+
+void mcap_close_mcl(struct mcap_mcl *mcl, gboolean cache)
+{
+ if (!mcl)
+ return;
+
+ if (mcl->ctrl & MCAP_CTRL_FREE) {
+ mcap_mcl_release(mcl);
+ return;
+ }
+
+ if (!cache)
+ mcl->ctrl |= MCAP_CTRL_NOCACHE;
+
+ if (mcl->cc) {
+ g_io_channel_shutdown(mcl->cc, TRUE, NULL);
+ g_io_channel_unref(mcl->cc);
+ mcl->cc = NULL;
+ mcl->state = MCL_IDLE;
+ } else if ((mcl->ctrl & MCAP_CTRL_CACHED) &&
+ (mcl->ctrl & MCAP_CTRL_NOCACHE)) {
+ mcl->ctrl &= ~MCAP_CTRL_CACHED;
+ mcl->mi->cached = g_slist_remove(mcl->mi->cached, mcl);
+ mcap_mcl_release(mcl);
+ mcap_mcl_unref(mcl);
+ }
+}
+
+struct mcap_mcl *mcap_mcl_ref(struct mcap_mcl *mcl)
+{
+ mcl->ref++;
+
+ DBG("mcap_mcl_ref(%p): ref=%d", mcl, mcl->ref);
+
+ return mcl;
+}
+
+void mcap_mcl_unref(struct mcap_mcl *mcl)
+{
+ mcl->ref--;
+
+ DBG("mcap_mcl_unref(%p): ref=%d", mcl, mcl->ref);
+
+ if (mcl->ref > 0)
+ return;
+
+ mcap_mcl_release(mcl);
+ mcap_instance_unref(mcl->mi);
+ g_free(mcl->cb);
+ g_free(mcl);
+}
+
+static gboolean parse_set_opts(struct mcap_mdl_cb *mdl_cb, GError **err,
+ McapMclCb cb1, va_list args)
+{
+ McapMclCb cb = cb1;
+ struct mcap_mdl_cb *c;
+
+ c = g_new0(struct mcap_mdl_cb, 1);
+
+ while (cb != MCAP_MDL_CB_INVALID) {
+ switch (cb) {
+ case MCAP_MDL_CB_CONNECTED:
+ c->mdl_connected = va_arg(args, mcap_mdl_event_cb);
+ break;
+ case MCAP_MDL_CB_CLOSED:
+ c->mdl_closed = va_arg(args, mcap_mdl_event_cb);
+ break;
+ case MCAP_MDL_CB_DELETED:
+ c->mdl_deleted = va_arg(args, mcap_mdl_event_cb);
+ break;
+ case MCAP_MDL_CB_ABORTED:
+ c->mdl_aborted = va_arg(args, mcap_mdl_event_cb);
+ break;
+ case MCAP_MDL_CB_REMOTE_CONN_REQ:
+ c->mdl_conn_req = va_arg(args,
+ mcap_remote_mdl_conn_req_cb);
+ break;
+ case MCAP_MDL_CB_REMOTE_RECONN_REQ:
+ c->mdl_reconn_req = va_arg(args,
+ mcap_remote_mdl_reconn_req_cb);
+ break;
+ default:
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+ "Unknown option %d", cb);
+ g_free(c);
+ return FALSE;
+ }
+ cb = va_arg(args, int);
+ }
+
+ /* Set new callbacks */
+ if (c->mdl_connected)
+ mdl_cb->mdl_connected = c->mdl_connected;
+ if (c->mdl_closed)
+ mdl_cb->mdl_closed = c->mdl_closed;
+ if (c->mdl_deleted)
+ mdl_cb->mdl_deleted = c->mdl_deleted;
+ if (c->mdl_aborted)
+ mdl_cb->mdl_aborted = c->mdl_aborted;
+ if (c->mdl_conn_req)
+ mdl_cb->mdl_conn_req = c->mdl_conn_req;
+ if (c->mdl_reconn_req)
+ mdl_cb->mdl_reconn_req = c->mdl_reconn_req;
+
+ g_free(c);
+
+ return TRUE;
+}
+
+gboolean mcap_mcl_set_cb(struct mcap_mcl *mcl, gpointer user_data,
+ GError **gerr, McapMclCb cb1, ...)
+{
+ va_list args;
+ gboolean ret;
+
+ va_start(args, cb1);
+ ret = parse_set_opts(mcl->cb, gerr, cb1, args);
+ va_end(args);
+
+ if (!ret)
+ return FALSE;
+
+ mcl->cb->user_data = user_data;
+ return TRUE;
+}
+
+void mcap_mcl_get_addr(struct mcap_mcl *mcl, bdaddr_t *addr)
+{
+ bacpy(addr, &mcl->addr);
+}
+
+static void mcap_del_mdl(gpointer elem, gpointer user_data)
+{
+ struct mcap_mdl *mdl = elem;
+ gboolean notify = *(gboolean *) user_data;
+
+ shutdown_mdl(mdl);
+ if (notify)
+ mdl->mcl->cb->mdl_deleted(mdl, mdl->mcl->cb->user_data);
+
+ mcap_mdl_unref(mdl);
+}
+
+static gboolean check_cmd_req_length(struct mcap_mcl *mcl, void *cmd,
+ uint32_t rlen, uint32_t explen, uint8_t rspcod)
+{
+ mcap_md_req *req;
+ uint16_t mdl_id;
+
+ if (rlen != explen) {
+ if (rlen >= sizeof(mcap_md_req)) {
+ req = cmd;
+ mdl_id = ntohs(req->mdl);
+ } else {
+ /* We can't get mdlid */
+ mdl_id = MCAP_MDLID_RESERVED;
+ }
+ mcap_send_cmd(mcl, rspcod, MCAP_INVALID_PARAM_VALUE, mdl_id,
+ NULL, 0);
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static void process_md_create_mdl_req(struct mcap_mcl *mcl, void *cmd,
+ uint32_t len)
+{
+ mcap_md_create_mdl_req *req;
+ struct mcap_mdl *mdl;
+ uint16_t mdl_id;
+ uint8_t mdep_id;
+ uint8_t cfga, conf;
+ uint8_t rsp;
+
+ if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_create_mdl_req),
+ MCAP_MD_CREATE_MDL_RSP))
+ return;
+
+ req = cmd;
+ mdl_id = ntohs(req->mdl);
+ if (mdl_id < MCAP_MDLID_INITIAL || mdl_id > MCAP_MDLID_FINAL) {
+ mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_INVALID_MDL,
+ mdl_id, NULL, 0);
+ return;
+ }
+
+ mdep_id = req->mdep;
+ if (mdep_id > MCAP_MDEPID_FINAL) {
+ mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_INVALID_MDEP,
+ mdl_id, NULL, 0);
+ return;
+ }
+
+ mdl = get_mdl(mcl, mdl_id);
+ if (mdl && (mdl->state == MDL_WAITING || mdl->state == MDL_DELETING )) {
+ /* Creation request arrives for a MDL that is being managed
+ * at current moment */
+ mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_MDL_BUSY,
+ mdl_id, NULL, 0);
+ return;
+ }
+
+ cfga = conf = req->conf;
+ /* Callback to upper layer */
+ rsp = mcl->cb->mdl_conn_req(mcl, mdep_id, mdl_id, &conf,
+ mcl->cb->user_data);
+ if (mcl->state == MCL_IDLE) {
+ /* MCL has been closed int the callback */
+ return;
+ }
+
+ if (cfga != 0 && cfga != conf) {
+ /* Remote device set default configuration but upper profile */
+ /* has changed it. Protocol Error: force closing the MCL by */
+ /* remote device using UNSPECIFIED_ERROR response */
+ mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP,
+ MCAP_UNSPECIFIED_ERROR, mdl_id, NULL, 0);
+ return;
+ }
+ if (rsp != MCAP_SUCCESS) {
+ mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, rsp, mdl_id,
+ NULL, 0);
+ return;
+ }
+
+ if (!mdl) {
+ mdl = g_new0(struct mcap_mdl, 1);
+ mdl->mcl = mcap_mcl_ref(mcl);
+ mdl->mdlid = mdl_id;
+ mcl->mdls = g_slist_insert_sorted(mcl->mdls, mcap_mdl_ref(mdl),
+ compare_mdl);
+ } else if (mdl->state == MDL_CONNECTED) {
+ /* MCAP specification says that we should close the MCL if
+ * it is open when we receive a MD_CREATE_MDL_REQ */
+ shutdown_mdl(mdl);
+ }
+
+ mdl->mdep_id = mdep_id;
+ mdl->state = MDL_WAITING;
+
+ mcl->state = MCL_PENDING;
+ mcap_send_cmd(mcl, MCAP_MD_CREATE_MDL_RSP, MCAP_SUCCESS, mdl_id,
+ &conf, 1);
+}
+
+static void process_md_reconnect_mdl_req(struct mcap_mcl *mcl, void *cmd,
+ uint32_t len)
+{
+ mcap_md_req *req;
+ struct mcap_mdl *mdl;
+ uint16_t mdl_id;
+ uint8_t rsp;
+
+ if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
+ MCAP_MD_RECONNECT_MDL_RSP))
+ return;
+
+ req = cmd;
+ mdl_id = ntohs(req->mdl);
+
+ mdl = get_mdl(mcl, mdl_id);
+ if (!mdl) {
+ mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_INVALID_MDL,
+ mdl_id, NULL, 0);
+ return;
+ } else if (mdl->state == MDL_WAITING || mdl->state == MDL_DELETING ) {
+ /* Creation request arrives for a MDL that is being managed
+ * at current moment */
+ mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_MDL_BUSY,
+ mdl_id, NULL, 0);
+ return;
+ }
+
+ /* Callback to upper layer */
+ rsp = mcl->cb->mdl_reconn_req(mdl, mcl->cb->user_data);
+ if (mcl->state == MCL_IDLE)
+ return;
+
+ if (rsp != MCAP_SUCCESS) {
+ mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, rsp, mdl_id,
+ NULL, 0);
+ return;
+ }
+
+ if (mdl->state == MDL_CONNECTED)
+ shutdown_mdl(mdl);
+
+ mdl->state = MDL_WAITING;
+ mcl->state = MCL_PENDING;
+ mcap_send_cmd(mcl, MCAP_MD_RECONNECT_MDL_RSP, MCAP_SUCCESS, mdl_id,
+ NULL, 0);
+}
+
+static void process_md_abort_mdl_req(struct mcap_mcl *mcl, void *cmd,
+ uint32_t len)
+{
+ mcap_md_req *req;
+ GSList *l;
+ struct mcap_mdl *mdl, *abrt;
+ uint16_t mdl_id;
+
+ if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
+ MCAP_MD_ABORT_MDL_RSP))
+ return;
+
+ req = cmd;
+ mdl_id = ntohs(req->mdl);
+ mcl->state = MCL_CONNECTED;
+ abrt = NULL;
+ for (l = mcl->mdls; l; l = l->next) {
+ mdl = l->data;
+ if (mdl_id == mdl->mdlid && mdl->state == MDL_WAITING) {
+ abrt = mdl;
+ if (mcl->state != MCL_CONNECTED)
+ break;
+ continue;
+ }
+ if (mdl->state == MDL_CONNECTED && mcl->state != MCL_ACTIVE)
+ mcl->state = MCL_ACTIVE;
+
+ if (abrt && mcl->state == MCL_ACTIVE)
+ break;
+ }
+
+ if (!abrt) {
+ mcap_send_cmd(mcl, MCAP_MD_ABORT_MDL_RSP, MCAP_INVALID_MDL,
+ mdl_id, NULL, 0);
+ return;
+ }
+
+ mcl->cb->mdl_aborted(abrt, mcl->cb->user_data);
+ abrt->state = MDL_CLOSED;
+ mcap_send_cmd(mcl, MCAP_MD_ABORT_MDL_RSP, MCAP_SUCCESS, mdl_id,
+ NULL, 0);
+}
+
+static void process_md_delete_mdl_req(struct mcap_mcl *mcl, void *cmd,
+ uint32_t len)
+{
+ mcap_md_req *req;
+ struct mcap_mdl *mdl, *aux;
+ uint16_t mdlid;
+ gboolean notify;
+ GSList *l;
+
+ if (!check_cmd_req_length(mcl, cmd, len, sizeof(mcap_md_req),
+ MCAP_MD_DELETE_MDL_RSP))
+ return;
+
+ req = cmd;
+ mdlid = ntohs(req->mdl);
+ if (mdlid == MCAP_ALL_MDLIDS) {
+ notify = FALSE;
+ g_slist_foreach(mcl->mdls, mcap_del_mdl, &notify);
+ g_slist_free(mcl->mdls);
+ mcl->mdls = NULL;
+ mcl->state = MCL_CONNECTED;
+ /* NULL mdl means ALL_MDLS */
+ mcl->cb->mdl_deleted(NULL, mcl->cb->user_data);
+ goto resp;
+ }
+
+ if (mdlid < MCAP_MDLID_INITIAL || mdlid > MCAP_MDLID_FINAL) {
+ mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_INVALID_MDL,
+ mdlid, NULL, 0);
+ return;
+ }
+
+ for (l = mcl->mdls, mdl = NULL; l; l = l->next) {
+ aux = l->data;
+ if (aux->mdlid == mdlid) {
+ mdl = aux;
+ break;
+ }
+ }
+
+ if (!mdl || mdl->state == MDL_WAITING) {
+ mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_INVALID_MDL,
+ mdlid, NULL, 0);
+ return;
+ }
+
+ mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+ update_mcl_state(mcl);
+ notify = TRUE;
+ mcap_del_mdl(mdl, &notify);
+
+resp:
+ mcap_send_cmd(mcl, MCAP_MD_DELETE_MDL_RSP, MCAP_SUCCESS, mdlid,
+ NULL, 0);
+}
+
+static void invalid_req_state(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ uint16_t mdlr;
+
+ error("Invalid cmd received (op code = %d) in state %d", cmd[0],
+ mcl->state);
+ /* Get previously mdlid sent to generate an appropriate
+ * response if it is possible */
+ mdlr = len < sizeof(mcap_md_req) ? MCAP_MDLID_RESERVED :
+ ntohs(((mcap_md_req *) cmd)->mdl);
+ mcap_send_cmd(mcl, cmd[0]+1, MCAP_INVALID_OPERATION, mdlr, NULL, 0);
+}
+
+/* Function used to process commands depending of MCL state */
+static void proc_req_connected(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ switch (cmd[0]) {
+ case MCAP_MD_CREATE_MDL_REQ:
+ process_md_create_mdl_req(mcl, cmd, len);
+ break;
+ case MCAP_MD_RECONNECT_MDL_REQ:
+ process_md_reconnect_mdl_req(mcl, cmd, len);
+ break;
+ case MCAP_MD_DELETE_MDL_REQ:
+ process_md_delete_mdl_req(mcl, cmd, len);
+ break;
+ default:
+ invalid_req_state(mcl, cmd, len);
+ }
+}
+
+static void proc_req_pending(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ if (cmd[0] == MCAP_MD_ABORT_MDL_REQ)
+ process_md_abort_mdl_req(mcl, cmd, len);
+ else
+ invalid_req_state(mcl, cmd, len);
+}
+
+static void proc_req_active(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ switch (cmd[0]) {
+ case MCAP_MD_CREATE_MDL_REQ:
+ process_md_create_mdl_req(mcl, cmd, len);
+ break;
+ case MCAP_MD_RECONNECT_MDL_REQ:
+ process_md_reconnect_mdl_req(mcl, cmd, len);
+ break;
+ case MCAP_MD_DELETE_MDL_REQ:
+ process_md_delete_mdl_req(mcl, cmd, len);
+ break;
+ default:
+ invalid_req_state(mcl, cmd, len);
+ }
+}
+
+/* Function used to process replies */
+static gboolean check_err_rsp(struct mcap_mcl *mcl, mcap_rsp *rsp,
+ uint32_t rlen, uint32_t len, GError **gerr)
+{
+ mcap_md_req *cmdlast = (mcap_md_req *) mcl->lcmd;
+ gint err = MCAP_ERROR_FAILED;
+ gboolean close = FALSE;
+ char *msg;
+
+ if (rsp->op == MCAP_ERROR_RSP) {
+ msg = "MCAP_ERROR_RSP received";
+ close = FALSE;
+ goto fail;
+ }
+
+ /* Check if the response matches with the last request */
+ if (rlen < sizeof(mcap_rsp) || (mcl->lcmd[0] + 1) != rsp->op) {
+ msg = "Protocol error";
+ close = FALSE;
+ goto fail;
+ }
+
+ if (rlen < len) {
+ msg = "Protocol error";
+ close = FALSE;
+ goto fail;
+ }
+
+ if (rsp->mdl != cmdlast->mdl) {
+ msg = "MDLID received doesn't match with MDLID sent";
+ close = TRUE;
+ goto fail;
+ }
+
+ if (rsp->rc == MCAP_REQUEST_NOT_SUPPORTED) {
+ msg = "Remote does not support opcodes";
+ mcl->ctrl &= ~MCAP_CTRL_STD_OP;
+ goto fail;
+ }
+
+ if (rsp->rc == MCAP_UNSPECIFIED_ERROR) {
+ msg = "Unspecified error";
+ close = TRUE;
+ goto fail;
+ }
+
+ if (rsp->rc != MCAP_SUCCESS) {
+ msg = error2str(rsp->rc);
+ err = rsp->rc;
+ goto fail;
+ }
+
+ return FALSE;
+
+fail:
+ g_set_error(gerr, MCAP_ERROR, err, "%s", msg);
+ return close;
+}
+
+static gboolean process_md_create_mdl_rsp(struct mcap_mcl *mcl,
+ mcap_rsp *rsp, uint32_t len)
+{
+ mcap_md_create_mdl_req *cmdlast = (mcap_md_create_mdl_req *) mcl->lcmd;
+ struct mcap_mdl_op_cb *conn = mcl->priv_data;
+ mcap_mdl_operation_conf_cb connect_cb = conn->cb.op_conf;
+ gpointer user_data = conn->user_data;
+ struct mcap_mdl *mdl = conn->mdl;
+ uint8_t conf = cmdlast->conf;
+ gboolean close;
+ GError *gerr = NULL;
+
+ close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp) + 1, &gerr);
+ g_free(mcl->lcmd);
+ mcl->lcmd = NULL;
+ mcl->req = MCL_AVAILABLE;
+
+ if (gerr)
+ goto fail;
+
+ /* Check if preferences changed */
+ if (conf != 0x00 && rsp->data[0] != conf) {
+ g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_FAILED,
+ "Configuration changed");
+ close = TRUE;
+ goto fail;
+ }
+
+ connect_cb(mdl, rsp->data[0], gerr, user_data);
+ return close;
+
+fail:
+ connect_cb(NULL, 0, gerr, user_data);
+ mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+ mcap_mdl_unref(mdl);
+ g_error_free(gerr);
+ update_mcl_state(mcl);
+ return close;
+}
+
+static gboolean process_md_reconnect_mdl_rsp(struct mcap_mcl *mcl,
+ mcap_rsp *rsp, uint32_t len)
+{
+ struct mcap_mdl_op_cb *reconn = mcl->priv_data;
+ mcap_mdl_operation_cb reconn_cb = reconn->cb.op;
+ gpointer user_data = reconn->user_data;
+ struct mcap_mdl *mdl = reconn->mdl;
+ GError *gerr = NULL;
+ gboolean close;
+
+ close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
+
+ g_free(mcl->lcmd);
+ mcl->lcmd = NULL;
+ mcl->req = MCL_AVAILABLE;
+
+ reconn_cb(mdl, gerr, user_data);
+ if (!gerr)
+ return close;
+
+ g_error_free(gerr);
+ shutdown_mdl(mdl);
+ update_mcl_state(mcl);
+
+ if (rsp->rc != MCAP_INVALID_MDL)
+ return close;
+
+ /* Remove cached mdlid */
+ mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+ mcl->cb->mdl_deleted(mdl, mcl->cb->user_data);
+ mcap_mdl_unref(mdl);
+
+ return close;
+}
+
+static gboolean process_md_abort_mdl_rsp(struct mcap_mcl *mcl,
+ mcap_rsp *rsp, uint32_t len)
+{
+ struct mcap_mdl_op_cb *abrt = mcl->priv_data;
+ mcap_mdl_notify_cb abrt_cb = abrt->cb.notify;
+ gpointer user_data = abrt->user_data;
+ struct mcap_mdl *mdl = abrt->mdl;
+ GError *gerr = NULL;
+ gboolean close;
+
+ close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
+
+ g_free(mcl->lcmd);
+ mcl->lcmd = NULL;
+ mcl->req = MCL_AVAILABLE;
+
+ abrt_cb(gerr, user_data);
+ shutdown_mdl(mdl);
+
+ if (len >= sizeof(mcap_rsp) && rsp->rc == MCAP_INVALID_MDL) {
+ mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+ mcl->cb->mdl_deleted(mdl, mcl->cb->user_data);
+ mcap_mdl_unref(mdl);
+ }
+
+ if (gerr)
+ g_error_free(gerr);
+
+ update_mcl_state(mcl);
+
+ return close;
+}
+
+static void restore_mdl(gpointer elem, gpointer data)
+{
+ struct mcap_mdl *mdl = elem;
+
+ if (mdl->state == MDL_DELETING) {
+ if (mdl->dc)
+ mdl->state = MDL_CONNECTED;
+ else
+ mdl->state = MDL_CLOSED;
+ } else if (mdl->state == MDL_CLOSED)
+ mdl->mcl->cb->mdl_closed(mdl, mdl->mcl->cb->user_data);
+}
+
+static void check_mdl_del_err(struct mcap_mdl *mdl, mcap_rsp *rsp)
+{
+ if (rsp->rc != MCAP_ERROR_INVALID_MDL) {
+ restore_mdl(mdl, NULL);
+ return;
+ }
+
+ /* MDL does not exist in remote side, we can delete it */
+ mdl->mcl->mdls = g_slist_remove(mdl->mcl->mdls, mdl);
+ mcap_mdl_unref(mdl);
+}
+
+static gboolean process_md_delete_mdl_rsp(struct mcap_mcl *mcl, mcap_rsp *rsp,
+ uint32_t len)
+{
+ struct mcap_mdl_op_cb *del = mcl->priv_data;
+ struct mcap_mdl *mdl = del->mdl;
+ mcap_mdl_notify_cb deleted_cb = del->cb.notify;
+ gpointer user_data = del->user_data;
+ mcap_md_req *cmdlast = (mcap_md_req *) mcl->lcmd;
+ uint16_t mdlid = ntohs(cmdlast->mdl);
+ GError *gerr = NULL;
+ gboolean close;
+ gboolean notify = FALSE;
+
+ close = check_err_rsp(mcl, rsp, len, sizeof(mcap_rsp), &gerr);
+
+ g_free(mcl->lcmd);
+ mcl->lcmd = NULL;
+ mcl->req = MCL_AVAILABLE;
+
+ if (gerr) {
+ if (mdl)
+ check_mdl_del_err(mdl, rsp);
+ else
+ g_slist_foreach(mcl->mdls, restore_mdl, NULL);
+ deleted_cb(gerr, user_data);
+ g_error_free(gerr);
+ return close;
+ }
+
+ if (mdlid == MCAP_ALL_MDLIDS) {
+ g_slist_foreach(mcl->mdls, mcap_del_mdl, &notify);
+ g_slist_free(mcl->mdls);
+ mcl->mdls = NULL;
+ mcl->state = MCL_CONNECTED;
+ } else {
+ mcl->mdls = g_slist_remove(mcl->mdls, mdl);
+ update_mcl_state(mcl);
+ mcap_del_mdl(mdl, &notify);
+ }
+
+ deleted_cb(gerr, user_data);
+
+ return close;
+}
+
+static void post_process_rsp(struct mcap_mcl *mcl, struct mcap_mdl_op_cb *op)
+{
+ if (mcl->priv_data != op) {
+ /* Queued MCAP request in some callback. */
+ /* We should not delete the mcl private data */
+ free_mcap_mdl_op(op);
+ } else {
+ /* This is not a queued request. It's safe */
+ /* delete the mcl private data here. */
+ free_mcl_priv_data(mcl);
+ }
+}
+
+static void proc_response(struct mcap_mcl *mcl, void *buf, uint32_t len)
+{
+ struct mcap_mdl_op_cb *op = mcl->priv_data;
+ mcap_rsp *rsp = buf;
+ gboolean close;
+
+ RELEASE_TIMER(mcl);
+
+ switch (mcl->lcmd[0] + 1) {
+ case MCAP_MD_CREATE_MDL_RSP:
+ close = process_md_create_mdl_rsp(mcl, rsp, len);
+ post_process_rsp(mcl, op);
+ break;
+ case MCAP_MD_RECONNECT_MDL_RSP:
+ close = process_md_reconnect_mdl_rsp(mcl, rsp, len);
+ post_process_rsp(mcl, op);
+ break;
+ case MCAP_MD_ABORT_MDL_RSP:
+ close = process_md_abort_mdl_rsp(mcl, rsp, len);
+ post_process_rsp(mcl, op);
+ break;
+ case MCAP_MD_DELETE_MDL_RSP:
+ close = process_md_delete_mdl_rsp(mcl, rsp, len);
+ post_process_rsp(mcl, op);
+ break;
+ default:
+ DBG("Unknown cmd response received (op code = %d)", rsp->op);
+ close = TRUE;
+ break;
+ }
+
+ if (close) {
+ mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data);
+ mcap_cache_mcl(mcl);
+ }
+}
+
+static void proc_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ GError *gerr = NULL;
+
+ if (cmd[0] > MCAP_MD_SYNC_INFO_IND ||
+ (cmd[0] > MCAP_MD_DELETE_MDL_RSP &&
+ cmd[0] < MCAP_MD_SYNC_CAP_REQ)) {
+ error("Unknown cmd received (op code = %d)", cmd[0]);
+ mcap_send_cmd(mcl, MCAP_ERROR_RSP, MCAP_INVALID_OP_CODE,
+ MCAP_MDLID_RESERVED, NULL, 0);
+ return;
+ }
+
+ if (cmd[0] >= MCAP_MD_SYNC_CAP_REQ &&
+ cmd[0] <= MCAP_MD_SYNC_INFO_IND) {
+ proc_sync_cmd(mcl, cmd, len);
+ return;
+ }
+
+ if (!(mcl->ctrl & MCAP_CTRL_STD_OP)) {
+ /* In case the remote device doesn't work correctly */
+ error("Remote device does not support opcodes, cmd ignored");
+ return;
+ }
+
+ if (mcl->req == MCL_WAITING_RSP) {
+ if (cmd[0] & 0x01) {
+ /* Request arrived when a response is expected */
+ if (mcl->role == MCL_INITIATOR)
+ /* ignore */
+ return;
+ /* Initiator will ignore our last request */
+ RELEASE_TIMER(mcl);
+ mcl->req = MCL_AVAILABLE;
+ g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_REQ_IGNORED,
+ "Initiator sent a request with more priority");
+ mcap_notify_error(mcl, gerr);
+ proc_req[mcl->state](mcl, cmd, len);
+ return;
+ }
+ proc_response(mcl, cmd, len);
+ } else if (cmd[0] & 0x01)
+ proc_req[mcl->state](mcl, cmd, len);
+}
+
+static gboolean mdl_event_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+
+ struct mcap_mdl *mdl = data;
+ gboolean notify;
+
+ DBG("Close MDL %d", mdl->mdlid);
+
+ notify = (mdl->state == MDL_CONNECTED);
+ shutdown_mdl(mdl);
+
+ update_mcl_state(mdl->mcl);
+
+ if (notify) {
+ /*Callback to upper layer */
+ mdl->mcl->cb->mdl_closed(mdl, mdl->mcl->cb->user_data);
+ }
+
+ return FALSE;
+}
+
+static void mcap_connect_mdl_cb(GIOChannel *chan, GError *conn_err,
+ gpointer data)
+{
+ struct mcap_mdl_op_cb *con = data;
+ struct mcap_mdl *mdl = con->mdl;
+ mcap_mdl_operation_cb cb = con->cb.op;
+ gpointer user_data = con->user_data;
+
+ DBG("mdl connect callback");
+
+ if (conn_err) {
+ DBG("ERROR: mdl connect callback");
+ mdl->state = MDL_CLOSED;
+ g_io_channel_unref(mdl->dc);
+ mdl->dc = NULL;
+ cb(mdl, conn_err, user_data);
+ return;
+ }
+
+ mdl->state = MDL_CONNECTED;
+ mdl->wid = g_io_add_watch_full(mdl->dc, G_PRIORITY_DEFAULT,
+ G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) mdl_event_cb,
+ mcap_mdl_ref(mdl),
+ (GDestroyNotify) mcap_mdl_unref);
+
+ cb(mdl, conn_err, user_data);
+}
+
+gboolean mcap_connect_mdl(struct mcap_mdl *mdl, uint8_t mode,
+ uint16_t dcpsm,
+ mcap_mdl_operation_cb connect_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err)
+{
+ struct mcap_mdl_op_cb *con;
+
+ if (mdl->state != MDL_WAITING) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_MDL,
+ "%s", error2str(MCAP_INVALID_MDL));
+ return FALSE;
+ }
+
+ if ((mode != L2CAP_MODE_ERTM) && (mode != L2CAP_MODE_STREAMING)) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+ "Invalid MDL configuration");
+ return FALSE;
+ }
+
+ con = g_new0(struct mcap_mdl_op_cb, 1);
+ con->mdl = mcap_mdl_ref(mdl);
+ con->cb.op = connect_cb;
+ con->destroy = destroy;
+ con->user_data = user_data;
+
+ mdl->dc = bt_io_connect(BT_IO_L2CAP, mcap_connect_mdl_cb, con,
+ (GDestroyNotify) free_mcap_mdl_op, err,
+ BT_IO_OPT_SOURCE_BDADDR, &mdl->mcl->mi->src,
+ BT_IO_OPT_DEST_BDADDR, &mdl->mcl->addr,
+ BT_IO_OPT_PSM, dcpsm,
+ BT_IO_OPT_MTU, MCAP_DC_MTU,
+ BT_IO_OPT_SEC_LEVEL, mdl->mcl->mi->sec,
+ BT_IO_OPT_MODE, mode,
+ BT_IO_OPT_INVALID);
+ if (!mdl->dc) {
+ DBG("MDL Connection error");
+ mdl->state = MDL_CLOSED;
+ mcap_mdl_unref(con->mdl);
+ g_free(con);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean mcl_control_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ GError *gerr = NULL;
+ struct mcap_mcl *mcl = data;
+ int sk, len;
+ uint8_t buf[MCAP_CC_MTU];
+
+ if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+ goto fail;
+
+ sk = g_io_channel_unix_get_fd(chan);
+ len = read(sk, buf, sizeof(buf));
+ if (len < 0)
+ goto fail;
+
+ proc_cmd(mcl, buf, (uint32_t) len);
+ return TRUE;
+
+fail:
+ if (mcl->state != MCL_IDLE) {
+ if (mcl->req == MCL_WAITING_RSP) {
+ /* notify error in pending callback */
+ g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_MCL_CLOSED,
+ "MCL closed");
+ mcap_notify_error(mcl, gerr);
+ g_error_free(gerr);
+ }
+ mcl->mi->mcl_disconnected_cb(mcl, mcl->mi->user_data);
+ }
+ mcap_cache_mcl(mcl);
+ return FALSE;
+}
+
+static void mcap_connect_mcl_cb(GIOChannel *chan, GError *conn_err,
+ gpointer user_data)
+{
+ char dstaddr[18];
+ struct connect_mcl *con = user_data;
+ struct mcap_mcl *aux, *mcl = con->mcl;
+ mcap_mcl_connect_cb connect_cb = con->connect_cb;
+ gpointer data = con->user_data;
+ GError *gerr = NULL;
+
+ mcl->ctrl &= ~MCAP_CTRL_CONN;
+
+ if (conn_err) {
+ if (mcl->ctrl & MCAP_CTRL_FREE) {
+ mcap_mcl_release(mcl);
+ mcl->mi->mcl_uncached_cb(mcl, mcl->mi->user_data);
+ }
+ connect_cb(NULL, conn_err, data);
+ return;
+ }
+
+ ba2str(&mcl->addr, dstaddr);
+
+ aux = find_mcl(mcl->mi->mcls, &mcl->addr);
+ if (aux) {
+ /* Double MCL connection case */
+ error("MCL error: Device %s is already connected", dstaddr);
+ g_set_error(&gerr, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS,
+ "MCL %s is already connected", dstaddr);
+ connect_cb(NULL, gerr, data);
+ g_error_free(gerr);
+ return;
+ }
+
+ mcl->state = MCL_CONNECTED;
+ mcl->role = MCL_INITIATOR;
+ mcl->req = MCL_AVAILABLE;
+ mcl->ctrl |= MCAP_CTRL_STD_OP;
+
+ mcap_sync_init(mcl);
+
+ if (mcl->ctrl & MCAP_CTRL_CACHED)
+ mcap_uncache_mcl(mcl);
+ else {
+ mcl->ctrl &= ~MCAP_CTRL_FREE;
+ mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls,
+ mcap_mcl_ref(mcl));
+ }
+
+ mcl->wid = g_io_add_watch_full(mcl->cc, G_PRIORITY_DEFAULT,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) mcl_control_cb,
+ mcap_mcl_ref(mcl),
+ (GDestroyNotify) mcap_mcl_unref);
+ connect_cb(mcl, gerr, data);
+}
+
+static void set_mdl_properties(GIOChannel *chan, struct mcap_mdl *mdl)
+{
+ struct mcap_mcl *mcl = mdl->mcl;
+
+ mdl->state = MDL_CONNECTED;
+ mdl->dc = g_io_channel_ref(chan);
+ mdl->wid = g_io_add_watch_full(mdl->dc, G_PRIORITY_DEFAULT,
+ G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) mdl_event_cb,
+ mcap_mdl_ref(mdl),
+ (GDestroyNotify) mcap_mdl_unref);
+
+ mcl->state = MCL_ACTIVE;
+ mcl->cb->mdl_connected(mdl, mcl->cb->user_data);
+}
+
+static void mcl_io_destroy(gpointer data)
+{
+ struct connect_mcl *con = data;
+
+ mcap_mcl_unref(con->mcl);
+ if (con->destroy)
+ con->destroy(con->user_data);
+ g_free(con);
+}
+
+gboolean mcap_create_mcl(struct mcap_instance *mi,
+ const bdaddr_t *addr,
+ uint16_t ccpsm,
+ mcap_mcl_connect_cb connect_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err)
+{
+ struct mcap_mcl *mcl;
+ struct connect_mcl *con;
+
+ mcl = find_mcl(mi->mcls, addr);
+ if (mcl) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_ALREADY_EXISTS,
+ "MCL is already connected.");
+ return FALSE;
+ }
+
+ mcl = find_mcl(mi->cached, addr);
+ if (!mcl) {
+ mcl = g_new0(struct mcap_mcl, 1);
+ mcl->mi = mcap_instance_ref(mi);
+ mcl->state = MCL_IDLE;
+ bacpy(&mcl->addr, addr);
+ set_default_cb(mcl);
+ mcl->next_mdl = (rand() % MCAP_MDLID_FINAL) + 1;
+ }
+
+ mcl->ctrl |= MCAP_CTRL_CONN;
+
+ con = g_new0(struct connect_mcl, 1);
+ con->mcl = mcap_mcl_ref(mcl);
+ con->connect_cb = connect_cb;
+ con->destroy = destroy;
+ con->user_data = user_data;
+
+ mcl->cc = bt_io_connect(BT_IO_L2CAP, mcap_connect_mcl_cb, con,
+ mcl_io_destroy, err,
+ BT_IO_OPT_SOURCE_BDADDR, &mi->src,
+ BT_IO_OPT_DEST_BDADDR, addr,
+ BT_IO_OPT_PSM, ccpsm,
+ BT_IO_OPT_MTU, MCAP_CC_MTU,
+ BT_IO_OPT_SEC_LEVEL, mi->sec,
+ BT_IO_OPT_MODE, L2CAP_MODE_ERTM,
+ BT_IO_OPT_INVALID);
+ if (!mcl->cc) {
+ mcl->ctrl &= ~MCAP_CTRL_CONN;
+ if (mcl->ctrl & MCAP_CTRL_FREE) {
+ mcap_mcl_release(mcl);
+ mcl->mi->mcl_uncached_cb(mcl, mcl->mi->user_data);
+ }
+ mcap_mcl_unref(con->mcl);
+ g_free(con);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static void connect_dc_event_cb(GIOChannel *chan, GError *gerr,
+ gpointer user_data)
+{
+ struct mcap_instance *mi = user_data;
+ struct mcap_mcl *mcl;
+ struct mcap_mdl *mdl;
+ GError *err = NULL;
+ bdaddr_t dst;
+ GSList *l;
+
+ if (gerr)
+ return;
+
+ bt_io_get(chan, BT_IO_L2CAP, &err,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ goto drop;
+ }
+
+ mcl = find_mcl(mi->mcls, &dst);
+ if (!mcl || mcl->state != MCL_PENDING)
+ goto drop;
+
+ for (l = mcl->mdls; l; l = l->next) {
+ mdl = l->data;
+ if (mdl->state == MDL_WAITING) {
+ set_mdl_properties(chan, mdl);
+ return;
+ }
+ }
+
+drop:
+ g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+static void set_mcl_conf(GIOChannel *chan, struct mcap_mcl *mcl)
+{
+ gboolean reconn;
+
+ mcl->state = MCL_CONNECTED;
+ mcl->role = MCL_ACCEPTOR;
+ mcl->req = MCL_AVAILABLE;
+ mcl->cc = g_io_channel_ref(chan);
+ mcl->ctrl |= MCAP_CTRL_STD_OP;
+
+ mcap_sync_init(mcl);
+
+ reconn = (mcl->ctrl & MCAP_CTRL_CACHED);
+ if (reconn)
+ mcap_uncache_mcl(mcl);
+ else
+ mcl->mi->mcls = g_slist_prepend(mcl->mi->mcls,
+ mcap_mcl_ref(mcl));
+
+ mcl->wid = g_io_add_watch_full(mcl->cc, G_PRIORITY_DEFAULT,
+ G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) mcl_control_cb,
+ mcap_mcl_ref(mcl),
+ (GDestroyNotify) mcap_mcl_unref);
+
+ /* Callback to report new MCL */
+ if (reconn)
+ mcl->mi->mcl_reconnected_cb(mcl, mcl->mi->user_data);
+ else
+ mcl->mi->mcl_connected_cb(mcl, mcl->mi->user_data);
+}
+
+static void connect_mcl_event_cb(GIOChannel *chan, GError *gerr,
+ gpointer user_data)
+{
+ struct mcap_instance *mi = user_data;
+ struct mcap_mcl *mcl;
+ bdaddr_t dst;
+ char address[18], srcstr[18];
+ GError *err = NULL;
+
+ if (gerr)
+ return;
+
+ bt_io_get(chan, BT_IO_L2CAP, &err,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_DEST, address,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ goto drop;
+ }
+
+ ba2str(&mi->src, srcstr);
+ mcl = find_mcl(mi->mcls, &dst);
+ if (mcl) {
+ error("Control channel already created with %s on adapter %s",
+ address, srcstr);
+ goto drop;
+ }
+
+ mcl = find_mcl(mi->cached, &dst);
+ if (!mcl) {
+ mcl = g_new0(struct mcap_mcl, 1);
+ mcl->mi = mcap_instance_ref(mi);
+ bacpy(&mcl->addr, &dst);
+ set_default_cb(mcl);
+ mcl->next_mdl = (rand() % MCAP_MDLID_FINAL) + 1;
+ }
+
+ set_mcl_conf(chan, mcl);
+
+ return;
+drop:
+ g_io_channel_shutdown(chan, TRUE, NULL);
+}
+
+struct mcap_instance *mcap_create_instance(bdaddr_t *src,
+ BtIOSecLevel sec,
+ uint16_t ccpsm,
+ uint16_t dcpsm,
+ mcap_mcl_event_cb mcl_connected,
+ mcap_mcl_event_cb mcl_reconnected,
+ mcap_mcl_event_cb mcl_disconnected,
+ mcap_mcl_event_cb mcl_uncached,
+ mcap_info_ind_event_cb mcl_sync_info_ind,
+ gpointer user_data,
+ GError **gerr)
+{
+ struct mcap_instance *mi;
+
+ if (sec < BT_IO_SEC_MEDIUM) {
+ g_set_error(gerr, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+ "Security level can't be minor of %d",
+ BT_IO_SEC_MEDIUM);
+ return NULL;
+ }
+
+ if (!(mcl_connected && mcl_reconnected &&
+ mcl_disconnected && mcl_uncached)) {
+ g_set_error(gerr, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+ "The callbacks can't be null");
+ return NULL;
+ }
+
+ mi = g_new0(struct mcap_instance, 1);
+
+ bacpy(&mi->src, src);
+
+ mi->sec = sec;
+ mi->mcl_connected_cb = mcl_connected;
+ mi->mcl_reconnected_cb = mcl_reconnected;
+ mi->mcl_disconnected_cb = mcl_disconnected;
+ mi->mcl_uncached_cb = mcl_uncached;
+ mi->mcl_sync_infoind_cb = mcl_sync_info_ind;
+ mi->user_data = user_data;
+ mi->csp_enabled = FALSE;
+
+ /* Listen incoming connections in control channel */
+ mi->ccio = bt_io_listen(BT_IO_L2CAP, connect_mcl_event_cb, NULL, mi,
+ NULL, gerr,
+ BT_IO_OPT_SOURCE_BDADDR, &mi->src,
+ BT_IO_OPT_PSM, ccpsm,
+ BT_IO_OPT_MTU, MCAP_CC_MTU,
+ BT_IO_OPT_SEC_LEVEL, sec,
+ BT_IO_OPT_MODE, L2CAP_MODE_ERTM,
+ BT_IO_OPT_INVALID);
+ if (!mi->ccio) {
+ error("%s", (*gerr)->message);
+ g_free(mi);
+ return NULL;
+ }
+
+ /* Listen incoming connections in data channels */
+ mi->dcio = bt_io_listen(BT_IO_L2CAP, connect_dc_event_cb, NULL, mi,
+ NULL, gerr,
+ BT_IO_OPT_SOURCE_BDADDR, &mi->src,
+ BT_IO_OPT_PSM, dcpsm,
+ BT_IO_OPT_MTU, MCAP_DC_MTU,
+ BT_IO_OPT_SEC_LEVEL, sec,
+ BT_IO_OPT_INVALID);
+ if (!mi->dcio) {
+ g_io_channel_shutdown(mi->ccio, TRUE, NULL);
+ g_io_channel_unref(mi->ccio);
+ mi->ccio = NULL;
+ error("%s", (*gerr)->message);
+ g_free(mi);
+ return NULL;
+ }
+
+ /* Initialize random seed to generate mdlids for this instance */
+ srand(time(NULL));
+
+ return mcap_instance_ref(mi);
+}
+
+void mcap_release_instance(struct mcap_instance *mi)
+{
+ GSList *l;
+
+ if (!mi)
+ return;
+
+ if (mi->ccio) {
+ g_io_channel_shutdown(mi->ccio, TRUE, NULL);
+ g_io_channel_unref(mi->ccio);
+ mi->ccio = NULL;
+ }
+
+ if (mi->dcio) {
+ g_io_channel_shutdown(mi->dcio, TRUE, NULL);
+ g_io_channel_unref(mi->dcio);
+ mi->dcio = NULL;
+ }
+
+ for (l = mi->mcls; l; l = l->next) {
+ mcap_mcl_release(l->data);
+ mcap_mcl_unref(l->data);
+ }
+
+ g_slist_free(mi->mcls);
+ mi->mcls = NULL;
+
+ for (l = mi->cached; l; l = l->next) {
+ mcap_mcl_release(l->data);
+ mcap_mcl_unref(l->data);
+ }
+
+ g_slist_free(mi->cached);
+ mi->cached = NULL;
+}
+
+struct mcap_instance *mcap_instance_ref(struct mcap_instance *mi)
+{
+ mi->ref++;
+
+ DBG("mcap_instance_ref(%p): ref=%d", mi, mi->ref);
+
+ return mi;
+}
+
+void mcap_instance_unref(struct mcap_instance *mi)
+{
+ mi->ref--;
+
+ DBG("mcap_instance_unref(%p): ref=%d", mi, mi->ref);
+
+ if (mi->ref > 0)
+ return;
+
+ mcap_release_instance(mi);
+ g_free(mi);
+}
+
+uint16_t mcap_get_ctrl_psm(struct mcap_instance *mi, GError **err)
+{
+ uint16_t lpsm;
+
+ if (!(mi && mi->ccio)) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+ "Invalid MCAP instance");
+ return 0;
+ }
+
+ if (!bt_io_get(mi->ccio, BT_IO_L2CAP, err,
+ BT_IO_OPT_PSM, &lpsm,
+ BT_IO_OPT_INVALID))
+ return 0;
+
+ return lpsm;
+}
+
+uint16_t mcap_get_data_psm(struct mcap_instance *mi, GError **err)
+{
+ uint16_t lpsm;
+
+ if (!(mi && mi->dcio)) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+ "Invalid MCAP instance");
+ return 0;
+ }
+
+ if (!bt_io_get(mi->dcio, BT_IO_L2CAP, err,
+ BT_IO_OPT_PSM, &lpsm,
+ BT_IO_OPT_INVALID))
+ return 0;
+
+ return lpsm;
+}
+
+gboolean mcap_set_data_chan_mode(struct mcap_instance *mi, uint8_t mode,
+ GError **err)
+{
+ if (!(mi && mi->dcio)) {
+ g_set_error(err, MCAP_ERROR, MCAP_ERROR_INVALID_ARGS,
+ "Invalid MCAP instance");
+ return FALSE;
+ }
+
+ return bt_io_set(mi->dcio, BT_IO_L2CAP, err, BT_IO_OPT_MODE, mode,
+ BT_IO_OPT_INVALID);
+}
+
+struct mcap_mdl *mcap_mdl_ref(struct mcap_mdl *mdl)
+{
+ mdl->ref++;
+
+ DBG("mcap_mdl_ref(%p): ref=%d", mdl, mdl->ref);
+
+ return mdl;
+}
+
+void mcap_mdl_unref(struct mcap_mdl *mdl)
+{
+ mdl->ref--;
+
+ DBG("mcap_mdl_unref(%p): ref=%d", mdl, mdl->ref);
+
+ if (mdl->ref > 0)
+ return;
+
+ free_mdl(mdl);
+}
diff --git a/profiles/health/mcap.h b/profiles/health/mcap.h
new file mode 100644
index 000000000..34a838207
--- /dev/null
+++ b/profiles/health/mcap.h
@@ -0,0 +1,164 @@
+/*
+ *
+ * MCAP for BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ * Copyright (C) 2010 Signove
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __MCAP_H
+#define __MCAP_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+#define MCAP_VERSION 0x0100 /* current version 01.00 */
+
+/* bytes to get MCAP Supported Procedures */
+#define MCAP_SUP_PROC 0x06
+
+/* maximum transmission unit for channels */
+#define MCAP_CC_MTU 48
+#define MCAP_DC_MTU L2CAP_DEFAULT_MTU
+
+/* MCAP Standard Op Codes */
+#define MCAP_ERROR_RSP 0x00
+#define MCAP_MD_CREATE_MDL_REQ 0x01
+#define MCAP_MD_CREATE_MDL_RSP 0x02
+#define MCAP_MD_RECONNECT_MDL_REQ 0x03
+#define MCAP_MD_RECONNECT_MDL_RSP 0x04
+#define MCAP_MD_ABORT_MDL_REQ 0x05
+#define MCAP_MD_ABORT_MDL_RSP 0x06
+#define MCAP_MD_DELETE_MDL_REQ 0x07
+#define MCAP_MD_DELETE_MDL_RSP 0x08
+
+/* MCAP Clock Sync Op Codes */
+#define MCAP_MD_SYNC_CAP_REQ 0x11
+#define MCAP_MD_SYNC_CAP_RSP 0x12
+#define MCAP_MD_SYNC_SET_REQ 0x13
+#define MCAP_MD_SYNC_SET_RSP 0x14
+#define MCAP_MD_SYNC_INFO_IND 0x15
+
+/* MCAP Response codes */
+#define MCAP_SUCCESS 0x00
+#define MCAP_INVALID_OP_CODE 0x01
+#define MCAP_INVALID_PARAM_VALUE 0x02
+#define MCAP_INVALID_MDEP 0x03
+#define MCAP_MDEP_BUSY 0x04
+#define MCAP_INVALID_MDL 0x05
+#define MCAP_MDL_BUSY 0x06
+#define MCAP_INVALID_OPERATION 0x07
+#define MCAP_RESOURCE_UNAVAILABLE 0x08
+#define MCAP_UNSPECIFIED_ERROR 0x09
+#define MCAP_REQUEST_NOT_SUPPORTED 0x0A
+#define MCAP_CONFIGURATION_REJECTED 0x0B
+
+/* MDL IDs */
+#define MCAP_MDLID_RESERVED 0x0000
+#define MCAP_MDLID_INITIAL 0x0001
+#define MCAP_MDLID_FINAL 0xFEFF
+#define MCAP_ALL_MDLIDS 0xFFFF
+
+/* MDEP IDs */
+#define MCAP_MDEPID_INITIAL 0x00
+#define MCAP_MDEPID_FINAL 0x7F
+
+/* CSP special values */
+#define MCAP_BTCLOCK_IMMEDIATE 0xffffffffUL
+#define MCAP_TMSTAMP_DONTSET 0xffffffffffffffffULL
+#define MCAP_BTCLOCK_MAX 0x0fffffff
+#define MCAP_BTCLOCK_FIELD (MCAP_BTCLOCK_MAX + 1)
+
+/*
+ * MCAP Request Packet Format
+ */
+
+typedef struct {
+ uint8_t op;
+ uint16_t mdl;
+ uint8_t mdep;
+ uint8_t conf;
+} __attribute__ ((packed)) mcap_md_create_mdl_req;
+
+typedef struct {
+ uint8_t op;
+ uint16_t mdl;
+} __attribute__ ((packed)) mcap_md_req;
+
+/*
+ * MCAP Response Packet Format
+ */
+
+typedef struct {
+ uint8_t op;
+ uint8_t rc;
+ uint16_t mdl;
+ uint8_t data[0];
+} __attribute__ ((packed)) mcap_rsp;
+
+/*
+ * MCAP Clock Synchronization Protocol
+ */
+
+typedef struct {
+ uint8_t op;
+ uint16_t timest;
+} __attribute__ ((packed)) mcap_md_sync_cap_req;
+
+typedef struct {
+ uint8_t op;
+ uint8_t rc;
+} __attribute__ ((packed)) mcap_md_sync_rsp;
+
+typedef struct {
+ uint8_t op;
+ uint8_t rc;
+ uint8_t btclock;
+ uint16_t sltime;
+ uint16_t timestnr;
+ uint16_t timestna;
+} __attribute__ ((packed)) mcap_md_sync_cap_rsp;
+
+typedef struct {
+ uint8_t op;
+ uint8_t timestui;
+ uint32_t btclock;
+ uint64_t timestst;
+} __attribute__ ((packed)) mcap_md_sync_set_req;
+
+typedef struct {
+ int8_t op;
+ uint8_t rc;
+ uint32_t btclock;
+ uint64_t timestst;
+ uint16_t timestsa;
+} __attribute__ ((packed)) mcap_md_sync_set_rsp;
+
+typedef struct {
+ uint8_t op;
+ uint32_t btclock;
+ uint64_t timestst;
+ uint16_t timestsa;
+} __attribute__ ((packed)) mcap_md_sync_info_ind;
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MCAP_H */
diff --git a/profiles/health/mcap_internal.h b/profiles/health/mcap_internal.h
new file mode 100644
index 000000000..7b044ef42
--- /dev/null
+++ b/profiles/health/mcap_internal.h
@@ -0,0 +1,137 @@
+/*
+ *
+ * MCAP for BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __MCAP_INTERNAL_H
+#define __MCAP_INTERNAL_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+ MCL_CONNECTED,
+ MCL_PENDING,
+ MCL_ACTIVE,
+ MCL_IDLE
+} MCLState;
+
+typedef enum {
+ MCL_ACCEPTOR,
+ MCL_INITIATOR
+} MCLRole;
+
+typedef enum {
+ MCL_AVAILABLE,
+ MCL_WAITING_RSP
+} MCAPCtrl;
+
+typedef enum {
+ MDL_WAITING,
+ MDL_CONNECTED,
+ MDL_DELETING,
+ MDL_CLOSED
+} MDLState;
+
+struct mcap_mdl_cb {
+ mcap_mdl_event_cb mdl_connected; /* Remote device has created a MDL */
+ mcap_mdl_event_cb mdl_closed; /* Remote device has closed a MDL */
+ mcap_mdl_event_cb mdl_deleted; /* Remote device requested deleting a MDL */
+ mcap_mdl_event_cb mdl_aborted; /* Remote device aborted the mdl creation */
+ mcap_remote_mdl_conn_req_cb mdl_conn_req; /* Remote device requested creating a MDL */
+ mcap_remote_mdl_reconn_req_cb mdl_reconn_req; /* Remote device requested reconnecting a MDL */
+ gpointer user_data; /* User data */
+};
+
+struct mcap_instance {
+ bdaddr_t src; /* Source address */
+ GIOChannel *ccio; /* Control Channel IO */
+ GIOChannel *dcio; /* Data Channel IO */
+ GSList *mcls; /* MCAP instance list */
+ GSList *cached; /* List with all cached MCLs (MAX_CACHED macro) */
+ BtIOSecLevel sec; /* Security level */
+ mcap_mcl_event_cb mcl_connected_cb; /* New MCL connected */
+ mcap_mcl_event_cb mcl_reconnected_cb; /* Old MCL has been reconnected */
+ mcap_mcl_event_cb mcl_disconnected_cb; /* MCL disconnected */
+ mcap_mcl_event_cb mcl_uncached_cb; /* MCL has been removed from MCAP cache */
+ mcap_info_ind_event_cb mcl_sync_infoind_cb; /* (CSP Master) Received info indication */
+ gpointer user_data; /* Data to be provided in callbacks */
+ gint ref; /* Reference counter */
+
+ gboolean csp_enabled; /* CSP: functionality enabled */
+};
+
+struct mcap_csp;
+struct mcap_mdl_op_cb;
+
+struct mcap_mcl {
+ struct mcap_instance *mi; /* MCAP instance where this MCL belongs */
+ bdaddr_t addr; /* Device address */
+ GIOChannel *cc; /* MCAP Control Channel IO */
+ guint wid; /* MCL Watcher id */
+ GSList *mdls; /* List of Data Channels shorted by mdlid */
+ MCLState state; /* Current MCL State */
+ MCLRole role; /* Initiator or acceptor of this MCL */
+ MCAPCtrl req; /* Request control flag */
+ struct mcap_mdl_op_cb *priv_data; /* Temporal data to manage responses */
+ struct mcap_mdl_cb *cb; /* MDL callbacks */
+ guint tid; /* Timer id for waiting for a response */
+ uint8_t *lcmd; /* Last command sent */
+ gint ref; /* References counter */
+ uint8_t ctrl; /* MCL control flag */
+ uint16_t next_mdl; /* id used to create next MDL */
+ struct mcap_csp *csp; /* CSP control structure */
+};
+
+#define MCAP_CTRL_CACHED 0x01 /* MCL is cached */
+#define MCAP_CTRL_STD_OP 0x02 /* Support for standard op codes */
+#define MCAP_CTRL_SYNC_OP 0x04 /* Support for synchronization commands */
+#define MCAP_CTRL_CONN 0x08 /* MCL is in connecting process */
+#define MCAP_CTRL_FREE 0x10 /* MCL is marked as releasable */
+#define MCAP_CTRL_NOCACHE 0x20 /* MCL is marked as not cacheable */
+
+struct mcap_mdl {
+ struct mcap_mcl *mcl; /* MCL where this MDL belongs */
+ GIOChannel *dc; /* MCAP Data Channel IO */
+ guint wid; /* MDL Watcher id */
+ uint16_t mdlid; /* MDL id */
+ uint8_t mdep_id; /* MCAP Data End Point */
+ MDLState state; /* MDL state */
+ gint ref; /* References counter */
+};
+
+struct sync_info_ind_data {
+ uint32_t btclock;
+ uint64_t timestamp;
+ uint16_t accuracy;
+};
+
+int mcap_send_data(int sock, const void *buf, uint32_t size);
+
+void proc_sync_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len);
+void mcap_sync_init(struct mcap_mcl *mcl);
+void mcap_sync_stop(struct mcap_mcl *mcl);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MCAP_INTERNAL_H */
diff --git a/profiles/health/mcap_lib.h b/profiles/health/mcap_lib.h
new file mode 100644
index 000000000..8fcc1415b
--- /dev/null
+++ b/profiles/health/mcap_lib.h
@@ -0,0 +1,224 @@
+/*
+ *
+ * MCAP for BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifndef __MCAP_LIB_H
+#define __MCAP_LIB_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+typedef enum {
+/* MCAP Error Response Codes */
+ MCAP_ERROR_INVALID_OP_CODE = 1,
+ MCAP_ERROR_INVALID_PARAM_VALUE,
+ MCAP_ERROR_INVALID_MDEP,
+ MCAP_ERROR_MDEP_BUSY,
+ MCAP_ERROR_INVALID_MDL,
+ MCAP_ERROR_MDL_BUSY,
+ MCAP_ERROR_INVALID_OPERATION,
+ MCAP_ERROR_RESOURCE_UNAVAILABLE,
+ MCAP_ERROR_UNSPECIFIED_ERROR,
+ MCAP_ERROR_REQUEST_NOT_SUPPORTED,
+ MCAP_ERROR_CONFIGURATION_REJECTED,
+/* MCAP Internal Errors */
+ MCAP_ERROR_INVALID_ARGS,
+ MCAP_ERROR_ALREADY_EXISTS,
+ MCAP_ERROR_REQ_IGNORED,
+ MCAP_ERROR_MCL_CLOSED,
+ MCAP_ERROR_FAILED
+} McapError;
+
+typedef enum {
+ MCAP_MDL_CB_INVALID,
+ MCAP_MDL_CB_CONNECTED, /* mcap_mdl_event_cb */
+ MCAP_MDL_CB_CLOSED, /* mcap_mdl_event_cb */
+ MCAP_MDL_CB_DELETED, /* mcap_mdl_event_cb */
+ MCAP_MDL_CB_ABORTED, /* mcap_mdl_event_cb */
+ MCAP_MDL_CB_REMOTE_CONN_REQ, /* mcap_remote_mdl_conn_req_cb */
+ MCAP_MDL_CB_REMOTE_RECONN_REQ /* mcap_remote_mdl_reconn_req_cb */
+} McapMclCb;
+
+struct mcap_instance;
+struct mcap_mcl;
+struct mcap_mdl;
+struct sync_info_ind_data;
+
+/************ Callbacks ************/
+
+/* MDL callbacks */
+
+typedef void (* mcap_mdl_event_cb) (struct mcap_mdl *mdl, gpointer data);
+typedef void (* mcap_mdl_operation_conf_cb) (struct mcap_mdl *mdl, uint8_t conf,
+ GError *err, gpointer data);
+typedef void (* mcap_mdl_operation_cb) (struct mcap_mdl *mdl, GError *err,
+ gpointer data);
+typedef void (* mcap_mdl_notify_cb) (GError *err, gpointer data);
+
+/* Next function should return an MCAP appropriate response code */
+typedef uint8_t (* mcap_remote_mdl_conn_req_cb) (struct mcap_mcl *mcl,
+ uint8_t mdepid, uint16_t mdlid,
+ uint8_t *conf, gpointer data);
+typedef uint8_t (* mcap_remote_mdl_reconn_req_cb) (struct mcap_mdl *mdl,
+ gpointer data);
+
+/* MCL callbacks */
+
+typedef void (* mcap_mcl_event_cb) (struct mcap_mcl *mcl, gpointer data);
+typedef void (* mcap_mcl_connect_cb) (struct mcap_mcl *mcl, GError *err,
+ gpointer data);
+
+/* CSP callbacks */
+
+typedef void (* mcap_info_ind_event_cb) (struct mcap_mcl *mcl,
+ struct sync_info_ind_data *data);
+
+typedef void (* mcap_sync_cap_cb) (struct mcap_mcl *mcl,
+ uint8_t mcap_err,
+ uint8_t btclockres,
+ uint16_t synclead,
+ uint16_t tmstampres,
+ uint16_t tmstampacc,
+ GError *err,
+ gpointer data);
+
+typedef void (* mcap_sync_set_cb) (struct mcap_mcl *mcl,
+ uint8_t mcap_err,
+ uint32_t btclock,
+ uint64_t timestamp,
+ uint16_t accuracy,
+ GError *err,
+ gpointer data);
+
+/************ Operations ************/
+
+/* MDL operations */
+
+gboolean mcap_create_mdl(struct mcap_mcl *mcl,
+ uint8_t mdepid,
+ uint8_t conf,
+ mcap_mdl_operation_conf_cb connect_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err);
+gboolean mcap_reconnect_mdl(struct mcap_mdl *mdl,
+ mcap_mdl_operation_cb reconnect_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err);
+gboolean mcap_delete_all_mdls(struct mcap_mcl *mcl,
+ mcap_mdl_notify_cb delete_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err);
+gboolean mcap_delete_mdl(struct mcap_mdl *mdl,
+ mcap_mdl_notify_cb delete_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err);
+gboolean mcap_connect_mdl(struct mcap_mdl *mdl,
+ uint8_t mode,
+ uint16_t dcpsm,
+ mcap_mdl_operation_cb connect_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err);
+gboolean mcap_mdl_abort(struct mcap_mdl *mdl,
+ mcap_mdl_notify_cb abort_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err);
+
+int mcap_mdl_get_fd(struct mcap_mdl *mdl);
+uint16_t mcap_mdl_get_mdlid(struct mcap_mdl *mdl);
+
+struct mcap_mdl *mcap_mdl_ref(struct mcap_mdl *mdl);
+void mcap_mdl_unref(struct mcap_mdl *mdl);
+
+/* MCL operations */
+
+gboolean mcap_create_mcl(struct mcap_instance *mi,
+ const bdaddr_t *addr,
+ uint16_t ccpsm,
+ mcap_mcl_connect_cb connect_cb,
+ gpointer user_data,
+ GDestroyNotify destroy,
+ GError **err);
+void mcap_close_mcl(struct mcap_mcl *mcl, gboolean cache);
+gboolean mcap_mcl_set_cb(struct mcap_mcl *mcl, gpointer user_data,
+ GError **gerr, McapMclCb cb1, ...);
+void mcap_mcl_get_addr(struct mcap_mcl *mcl, bdaddr_t *addr);
+
+struct mcap_mcl *mcap_mcl_ref(struct mcap_mcl *mcl);
+void mcap_mcl_unref(struct mcap_mcl *mcl);
+
+/* CSP operations */
+
+void mcap_enable_csp(struct mcap_instance *mi);
+void mcap_disable_csp(struct mcap_instance *mi);
+
+uint64_t mcap_get_timestamp(struct mcap_mcl *mcl,
+ struct timespec *given_time);
+uint32_t mcap_get_btclock(struct mcap_mcl *mcl);
+
+void mcap_sync_cap_req(struct mcap_mcl *mcl,
+ uint16_t reqacc,
+ mcap_sync_cap_cb cb,
+ gpointer user_data,
+ GError **err);
+
+void mcap_sync_set_req(struct mcap_mcl *mcl,
+ uint8_t update,
+ uint32_t btclock,
+ uint64_t timestamp,
+ mcap_sync_set_cb cb,
+ gpointer user_data,
+ GError **err);
+
+/* MCAP main operations */
+
+struct mcap_instance *mcap_create_instance(bdaddr_t *src,
+ BtIOSecLevel sec, uint16_t ccpsm,
+ uint16_t dcpsm,
+ mcap_mcl_event_cb mcl_connected,
+ mcap_mcl_event_cb mcl_reconnected,
+ mcap_mcl_event_cb mcl_disconnected,
+ mcap_mcl_event_cb mcl_uncached,
+ mcap_info_ind_event_cb mcl_sync_info_ind,
+ gpointer user_data,
+ GError **gerr);
+void mcap_release_instance(struct mcap_instance *mi);
+
+struct mcap_instance *mcap_instance_ref(struct mcap_instance *mi);
+void mcap_instance_unref(struct mcap_instance *mi);
+
+uint16_t mcap_get_ctrl_psm(struct mcap_instance *mi, GError **err);
+uint16_t mcap_get_data_psm(struct mcap_instance *mi, GError **err);
+
+gboolean mcap_set_data_chan_mode(struct mcap_instance *mi, uint8_t mode,
+ GError **err);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MCAP_LIB_H */
diff --git a/profiles/health/mcap_sync.c b/profiles/health/mcap_sync.c
new file mode 100644
index 000000000..6d8d66b66
--- /dev/null
+++ b/profiles/health/mcap_sync.c
@@ -0,0 +1,1012 @@
+/*
+ *
+ * MCAP for BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos.
+ * Copyright (C) 2010 Signove
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdint.h>
+#include <netinet/in.h>
+#include <time.h>
+#include <stdlib.h>
+#include <sys/ioctl.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <adapter.h>
+#include <manager.h>
+#include <btio.h>
+#include <log.h>
+
+#include "mcap.h"
+#include "mcap_lib.h"
+#include "mcap_internal.h"
+
+#define MCAP_BTCLOCK_HALF (MCAP_BTCLOCK_FIELD / 2)
+#define CLK CLOCK_MONOTONIC
+
+#define MCAP_CSP_ERROR g_quark_from_static_string("mcap-csp-error-quark")
+#define MAX_RETRIES 10
+#define SAMPLE_COUNT 20
+
+struct mcap_csp {
+ uint64_t base_tmstamp; /* CSP base timestamp */
+ struct timespec base_time; /* CSP base time when timestamp set */
+ guint local_caps; /* CSP-Master: have got remote caps */
+ guint remote_caps; /* CSP-Slave: remote master got caps */
+ guint rem_req_acc; /* CSP-Slave: accuracy required by master */
+ guint ind_expected; /* CSP-Master: indication expected */
+ MCAPCtrl csp_req; /* CSP-Master: Request control flag */
+ guint ind_timer; /* CSP-Slave: indication timer */
+ guint set_timer; /* CSP-Slave: delayed set timer */
+ void *set_data; /* CSP-Slave: delayed set data */
+ void *csp_priv_data; /* CSP-Master: In-flight request data */
+};
+
+struct mcap_sync_cap_cbdata {
+ mcap_sync_cap_cb cb;
+ gpointer user_data;
+};
+
+struct mcap_sync_set_cbdata {
+ mcap_sync_set_cb cb;
+ gpointer user_data;
+};
+
+struct csp_caps {
+ int ts_acc; /* timestamp accuracy */
+ int ts_res; /* timestamp resolution */
+ int latency; /* Read BT clock latency */
+ int preempt_thresh; /* Preemption threshold for latency */
+ int syncleadtime_ms; /* SyncLeadTime in ms */
+};
+
+struct sync_set_data {
+ uint8_t update;
+ uint32_t sched_btclock;
+ uint64_t timestamp;
+ int ind_freq;
+ gboolean role;
+};
+
+#define hton64(x) ntoh64(x)
+
+static gboolean csp_caps_initialized = FALSE;
+struct csp_caps _caps;
+
+static int send_sync_cmd(struct mcap_mcl *mcl, const void *buf, uint32_t size)
+{
+ int sock;
+
+ if (mcl->cc == NULL)
+ return -1;
+
+ sock = g_io_channel_unix_get_fd(mcl->cc);
+ return mcap_send_data(sock, buf, size);
+}
+
+static int send_unsupported_cap_req(struct mcap_mcl *mcl)
+{
+ mcap_md_sync_cap_rsp *cmd;
+ int sent;
+
+ cmd = g_new0(mcap_md_sync_cap_rsp, 1);
+ cmd->op = MCAP_MD_SYNC_CAP_RSP;
+ cmd->rc = MCAP_REQUEST_NOT_SUPPORTED;
+
+ sent = send_sync_cmd(mcl, cmd, sizeof(*cmd));
+ g_free(cmd);
+
+ return sent;
+}
+
+static int send_unsupported_set_req(struct mcap_mcl *mcl)
+{
+ mcap_md_sync_set_rsp *cmd;
+ int sent;
+
+ cmd = g_new0(mcap_md_sync_set_rsp, 1);
+ cmd->op = MCAP_MD_SYNC_SET_RSP;
+ cmd->rc = MCAP_REQUEST_NOT_SUPPORTED;
+
+ sent = send_sync_cmd(mcl, cmd, sizeof(*cmd));
+ g_free(cmd);
+
+ return sent;
+}
+
+static void reset_tmstamp(struct mcap_csp *csp, struct timespec *base_time,
+ uint64_t new_tmstamp)
+{
+ csp->base_tmstamp = new_tmstamp;
+ if (base_time)
+ csp->base_time = *base_time;
+ else
+ clock_gettime(CLK, &csp->base_time);
+}
+
+void mcap_sync_init(struct mcap_mcl *mcl)
+{
+ if (!mcl->mi->csp_enabled) {
+ mcl->csp = NULL;
+ return;
+ }
+
+ mcl->csp = g_new0(struct mcap_csp, 1);
+
+ mcl->csp->rem_req_acc = 10000; /* safe divisor */
+ mcl->csp->set_data = NULL;
+ mcl->csp->csp_priv_data = NULL;
+
+ reset_tmstamp(mcl->csp, NULL, 0);
+}
+
+void mcap_sync_stop(struct mcap_mcl *mcl)
+{
+ if (!mcl->csp)
+ return;
+
+ if (mcl->csp->ind_timer)
+ g_source_remove(mcl->csp->ind_timer);
+
+ if (mcl->csp->set_timer)
+ g_source_remove(mcl->csp->set_timer);
+
+ if (mcl->csp->set_data)
+ g_free(mcl->csp->set_data);
+
+ if (mcl->csp->csp_priv_data)
+ g_free(mcl->csp->csp_priv_data);
+
+ mcl->csp->ind_timer = 0;
+ mcl->csp->set_timer = 0;
+ mcl->csp->set_data = NULL;
+ mcl->csp->csp_priv_data = NULL;
+
+ g_free(mcl->csp);
+ mcl->csp = NULL;
+}
+
+static uint64_t time_us(struct timespec *tv)
+{
+ return tv->tv_sec * 1000000 + tv->tv_nsec / 1000;
+}
+
+static int64_t bt2us(int bt)
+{
+ return bt * 312.5;
+}
+
+static int bt2ms(int bt)
+{
+ return bt * 312.5 / 1000;
+}
+
+static int btoffset(uint32_t btclk1, uint32_t btclk2)
+{
+ int offset = btclk2 - btclk1;
+
+ if (offset <= -MCAP_BTCLOCK_HALF)
+ offset += MCAP_BTCLOCK_FIELD;
+ else if (offset > MCAP_BTCLOCK_HALF)
+ offset -= MCAP_BTCLOCK_FIELD;
+
+ return offset;
+}
+
+static int btdiff(uint32_t btclk1, uint32_t btclk2)
+{
+ return btoffset(btclk1, btclk2);
+}
+
+static gboolean valid_btclock(uint32_t btclk)
+{
+ return btclk <= MCAP_BTCLOCK_MAX;
+}
+
+/* This call may fail; either deal with retry or use read_btclock_retry */
+static gboolean read_btclock(struct mcap_mcl *mcl, uint32_t *btclock,
+ uint16_t *btaccuracy)
+{
+ int which = 1;
+ struct btd_adapter *adapter;
+
+ adapter = manager_find_adapter(&mcl->mi->src);
+
+ if (!adapter)
+ return FALSE;
+
+ if (btd_adapter_read_clock(adapter, &mcl->addr, which, 1000,
+ btclock, btaccuracy) < 0)
+ return FALSE;
+
+ return TRUE;
+}
+
+static gboolean read_btclock_retry(struct mcap_mcl *mcl, uint32_t *btclock,
+ uint16_t *btaccuracy)
+{
+ int retries = 5;
+
+ while (--retries >= 0) {
+ if (read_btclock(mcl, btclock, btaccuracy))
+ return TRUE;
+ DBG("CSP: retrying to read bt clock...");
+ }
+
+ return FALSE;
+}
+
+static gboolean get_btrole(struct mcap_mcl *mcl)
+{
+ int sock, flags;
+ socklen_t len;
+
+ if (mcl->cc == NULL)
+ return -1;
+
+ sock = g_io_channel_unix_get_fd(mcl->cc);
+ len = sizeof(flags);
+
+ if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, &flags, &len))
+ DBG("CSP: could not read role");
+
+ return flags & L2CAP_LM_MASTER;
+}
+
+uint64_t mcap_get_timestamp(struct mcap_mcl *mcl,
+ struct timespec *given_time)
+{
+ struct timespec now;
+ uint64_t tmstamp;
+
+ if (!mcl->csp)
+ return MCAP_TMSTAMP_DONTSET;
+
+ if (given_time)
+ now = *given_time;
+ else
+ clock_gettime(CLK, &now);
+
+ tmstamp = time_us(&now) - time_us(&mcl->csp->base_time)
+ + mcl->csp->base_tmstamp;
+
+ return tmstamp;
+}
+
+uint32_t mcap_get_btclock(struct mcap_mcl *mcl)
+{
+ uint32_t btclock;
+ uint16_t accuracy;
+
+ if (!mcl->csp)
+ return MCAP_BTCLOCK_IMMEDIATE;
+
+ if (!read_btclock_retry(mcl, &btclock, &accuracy))
+ btclock = 0xffffffff;
+
+ return btclock;
+}
+
+static gboolean initialize_caps(struct mcap_mcl *mcl)
+{
+ struct timespec t1, t2;
+ int latencies[SAMPLE_COUNT];
+ int latency, avg, dev;
+ uint32_t btclock;
+ uint16_t btaccuracy;
+ int i;
+ int retries;
+
+ clock_getres(CLK, &t1);
+
+ _caps.ts_res = time_us(&t1);
+ if (_caps.ts_res < 1)
+ _caps.ts_res = 1;
+
+ _caps.ts_acc = 20; /* ppm, estimated */
+
+ /* A little exercise before measuing latency */
+ clock_gettime(CLK, &t1);
+ read_btclock_retry(mcl, &btclock, &btaccuracy);
+
+ /* Read clock a number of times and measure latency */
+ avg = 0;
+ i = 0;
+ retries = MAX_RETRIES;
+ while (i < SAMPLE_COUNT && retries > 0) {
+ clock_gettime(CLK, &t1);
+ if (!read_btclock(mcl, &btclock, &btaccuracy)) {
+ retries--;
+ continue;
+ }
+ clock_gettime(CLK, &t2);
+
+ latency = time_us(&t2) - time_us(&t1);
+ latencies[i] = latency;
+ avg += latency;
+ i++;
+ }
+
+ if (retries <= 0)
+ return FALSE;
+
+ /* Calculate average and deviation */
+ avg /= SAMPLE_COUNT;
+ dev = 0;
+ for (i = 0; i < SAMPLE_COUNT; ++i)
+ dev += abs(latencies[i] - avg);
+ dev /= SAMPLE_COUNT;
+
+ /* Calculate corrected average, without 'freak' latencies */
+ latency = 0;
+ for (i = 0; i < SAMPLE_COUNT; ++i) {
+ if (latencies[i] > (avg + dev * 6))
+ latency += avg;
+ else
+ latency += latencies[i];
+ }
+ latency /= SAMPLE_COUNT;
+
+ _caps.latency = latency;
+ _caps.preempt_thresh = latency * 4;
+ _caps.syncleadtime_ms = latency * 50 / 1000;
+
+ csp_caps_initialized = TRUE;
+ return TRUE;
+}
+
+static struct csp_caps *caps(struct mcap_mcl *mcl)
+{
+ if (!csp_caps_initialized)
+ if (!initialize_caps(mcl)) {
+ /* Temporary failure in reading BT clock */
+ return NULL;
+ }
+
+ return &_caps;
+}
+
+static int send_sync_cap_rsp(struct mcap_mcl *mcl, uint8_t rspcode,
+ uint8_t btclockres, uint16_t synclead,
+ uint16_t tmstampres, uint16_t tmstampacc)
+{
+ mcap_md_sync_cap_rsp *rsp;
+ int sent;
+
+ rsp = g_new0(mcap_md_sync_cap_rsp, 1);
+
+ rsp->op = MCAP_MD_SYNC_CAP_RSP;
+ rsp->rc = rspcode;
+
+ rsp->btclock = btclockres;
+ rsp->sltime = htons(synclead);
+ rsp->timestnr = htons(tmstampres);
+ rsp->timestna = htons(tmstampacc);
+
+ sent = send_sync_cmd(mcl, rsp, sizeof(*rsp));
+ g_free(rsp);
+
+ return sent;
+}
+
+static void proc_sync_cap_req(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ mcap_md_sync_cap_req *req;
+ uint16_t required_accuracy;
+ uint16_t our_accuracy;
+ uint32_t btclock;
+ uint16_t btres;
+
+ if (len != sizeof(mcap_md_sync_cap_req)) {
+ send_sync_cap_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
+ 0, 0, 0, 0);
+ return;
+ }
+
+ if (!caps(mcl)) {
+ send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE,
+ 0, 0, 0, 0);
+ return;
+ }
+
+ req = (mcap_md_sync_cap_req *) cmd;
+ required_accuracy = ntohs(req->timest);
+ our_accuracy = caps(mcl)->ts_acc;
+
+ if (required_accuracy < our_accuracy || required_accuracy < 1) {
+ send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE,
+ 0, 0, 0, 0);
+ return;
+ }
+
+ if (!read_btclock_retry(mcl, &btclock, &btres)) {
+ send_sync_cap_rsp(mcl, MCAP_RESOURCE_UNAVAILABLE,
+ 0, 0, 0, 0);
+ return;
+ }
+
+ mcl->csp->remote_caps = 1;
+ mcl->csp->rem_req_acc = required_accuracy;
+
+ send_sync_cap_rsp(mcl, MCAP_SUCCESS, btres,
+ caps(mcl)->syncleadtime_ms,
+ caps(mcl)->ts_res, our_accuracy);
+}
+
+static int send_sync_set_rsp(struct mcap_mcl *mcl, uint8_t rspcode,
+ uint32_t btclock, uint64_t timestamp,
+ uint16_t tmstampres)
+{
+ mcap_md_sync_set_rsp *rsp;
+ int sent;
+
+ rsp = g_new0(mcap_md_sync_set_rsp, 1);
+
+ rsp->op = MCAP_MD_SYNC_SET_RSP;
+ rsp->rc = rspcode;
+ rsp->btclock = htonl(btclock);
+ rsp->timestst = hton64(timestamp);
+ rsp->timestsa = htons(tmstampres);
+
+ sent = send_sync_cmd(mcl, rsp, sizeof(*rsp));
+ g_free(rsp);
+
+ return sent;
+}
+
+static gboolean get_all_clocks(struct mcap_mcl *mcl, uint32_t *btclock,
+ struct timespec *base_time,
+ uint64_t *timestamp)
+{
+ int latency;
+ int retry = 5;
+ uint16_t btres;
+ struct timespec t0;
+
+ if (!caps(mcl))
+ return FALSE;
+
+ latency = caps(mcl)->preempt_thresh + 1;
+
+ while (latency > caps(mcl)->preempt_thresh && --retry >= 0) {
+
+ clock_gettime(CLK, &t0);
+
+ if (!read_btclock(mcl, btclock, &btres))
+ continue;
+
+ clock_gettime(CLK, base_time);
+
+ /* Tries to detect preemption between clock_gettime
+ * and read_btclock by measuring transaction time
+ */
+ latency = time_us(base_time) - time_us(&t0);
+ }
+
+ *timestamp = mcap_get_timestamp(mcl, base_time);
+
+ return TRUE;
+}
+
+static gboolean sync_send_indication(gpointer user_data)
+{
+ struct mcap_mcl *mcl;
+ mcap_md_sync_info_ind *cmd;
+ uint32_t btclock;
+ uint64_t tmstamp;
+ struct timespec base_time;
+ int sent;
+
+ if (!user_data)
+ return FALSE;
+
+ mcl = user_data;
+
+ if (!caps(mcl))
+ return FALSE;
+
+ if (!get_all_clocks(mcl, &btclock, &base_time, &tmstamp))
+ return FALSE;
+
+ cmd = g_new0(mcap_md_sync_info_ind, 1);
+
+ cmd->op = MCAP_MD_SYNC_INFO_IND;
+ cmd->btclock = htonl(btclock);
+ cmd->timestst = hton64(tmstamp);
+ cmd->timestsa = htons(caps(mcl)->latency);
+
+ sent = send_sync_cmd(mcl, cmd, sizeof(*cmd));
+ g_free(cmd);
+
+ return !sent;
+}
+
+static gboolean proc_sync_set_req_phase2(gpointer user_data)
+{
+ struct mcap_mcl *mcl;
+ struct sync_set_data *data;
+ uint8_t update;
+ uint32_t sched_btclock;
+ uint64_t new_tmstamp;
+ int ind_freq;
+ int role;
+ uint32_t btclock;
+ uint64_t tmstamp;
+ struct timespec base_time;
+ uint16_t tmstampacc;
+ gboolean reset;
+ int delay;
+
+ if (!user_data)
+ return FALSE;
+
+ mcl = user_data;
+
+ if (!mcl->csp->set_data)
+ return FALSE;
+
+ data = mcl->csp->set_data;
+ update = data->update;
+ sched_btclock = data->sched_btclock;
+ new_tmstamp = data->timestamp;
+ ind_freq = data->ind_freq;
+ role = data->role;
+
+ if (!caps(mcl)) {
+ send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
+ return FALSE;
+ }
+
+ if (!get_all_clocks(mcl, &btclock, &base_time, &tmstamp)) {
+ send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
+ return FALSE;
+ }
+
+ if (get_btrole(mcl) != role) {
+ send_sync_set_rsp(mcl, MCAP_INVALID_OPERATION, 0, 0, 0);
+ return FALSE;
+ }
+
+ reset = (new_tmstamp != MCAP_TMSTAMP_DONTSET);
+
+ if (reset) {
+ if (sched_btclock != MCAP_BTCLOCK_IMMEDIATE) {
+ delay = bt2us(btdiff(sched_btclock, btclock));
+ if (delay >= 0 || ((new_tmstamp - delay) > 0)) {
+ new_tmstamp += delay;
+ DBG("CSP: reset w/ delay %dus, compensated",
+ delay);
+ } else
+ DBG("CSP: reset w/ delay %dus, uncompensated",
+ delay);
+ }
+
+ reset_tmstamp(mcl->csp, &base_time, new_tmstamp);
+ tmstamp = new_tmstamp;
+ }
+
+ tmstampacc = caps(mcl)->latency + caps(mcl)->ts_acc;
+
+ if (mcl->csp->ind_timer) {
+ g_source_remove(mcl->csp->ind_timer);
+ mcl->csp->ind_timer = 0;
+ }
+
+ if (update) {
+ int when = ind_freq + caps(mcl)->syncleadtime_ms;
+ mcl->csp->ind_timer = g_timeout_add(when,
+ sync_send_indication,
+ mcl);
+ }
+
+ send_sync_set_rsp(mcl, MCAP_SUCCESS, btclock, tmstamp, tmstampacc);
+
+ /* First indication after set is immediate */
+ if (update)
+ sync_send_indication(mcl);
+
+ return FALSE;
+}
+
+static void proc_sync_set_req(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ mcap_md_sync_set_req *req;
+ uint32_t sched_btclock, cur_btclock;
+ uint16_t btres;
+ uint8_t update;
+ uint64_t timestamp;
+ struct sync_set_data *set_data;
+ int phase2_delay, ind_freq, when;
+
+ if (len != sizeof(mcap_md_sync_set_req)) {
+ send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
+ return;
+ }
+
+ req = (mcap_md_sync_set_req *) cmd;
+ sched_btclock = ntohl(req->btclock);
+ update = req->timestui;
+ timestamp = ntoh64(req->timestst);
+
+ if (sched_btclock != MCAP_BTCLOCK_IMMEDIATE &&
+ !valid_btclock(sched_btclock)) {
+ send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
+ return;
+ }
+
+ if (update > 1) {
+ send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
+ return;
+ }
+
+ if (!mcl->csp->remote_caps) {
+ /* Remote side did not ask our capabilities yet */
+ send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE, 0, 0, 0);
+ return;
+ }
+
+ if (!caps(mcl)) {
+ send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
+ return;
+ }
+
+ if (!read_btclock_retry(mcl, &cur_btclock, &btres)) {
+ send_sync_set_rsp(mcl, MCAP_UNSPECIFIED_ERROR, 0, 0, 0);
+ return;
+ }
+
+ if (sched_btclock == MCAP_BTCLOCK_IMMEDIATE)
+ phase2_delay = 0;
+ else {
+ phase2_delay = btdiff(cur_btclock, sched_btclock);
+
+ if (phase2_delay < 0) {
+ /* can not reset in the past tense */
+ send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
+ 0, 0, 0);
+ return;
+ }
+
+ /* Convert to miliseconds */
+ phase2_delay = bt2ms(phase2_delay);
+
+ if (phase2_delay > 61*1000) {
+ /* More than 60 seconds in the future */
+ send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
+ 0, 0, 0);
+ return;
+ } else if (phase2_delay < caps(mcl)->latency / 1000) {
+ /* Too fast for us to do in time */
+ send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
+ 0, 0, 0);
+ return;
+ }
+ }
+
+ if (update) {
+ /* Indication frequency: required accuracy divided by ours */
+ /* Converted to milisseconds */
+ ind_freq = (1000 * mcl->csp->rem_req_acc) / caps(mcl)->ts_acc;
+
+ if (ind_freq < MAX(caps(mcl)->latency * 2 / 1000, 100)) {
+ /* Too frequent, we can't handle */
+ send_sync_set_rsp(mcl, MCAP_INVALID_PARAM_VALUE,
+ 0, 0, 0);
+ return;
+ }
+
+ DBG("CSP: indication every %dms", ind_freq);
+ } else
+ ind_freq = 0;
+
+ if (mcl->csp->ind_timer) {
+ /* Old indications are no longer sent */
+ g_source_remove(mcl->csp->ind_timer);
+ mcl->csp->ind_timer = 0;
+ }
+
+ if (!mcl->csp->set_data)
+ mcl->csp->set_data = g_new0(struct sync_set_data, 1);
+
+ set_data = (struct sync_set_data *) mcl->csp->set_data;
+
+ set_data->update = update;
+ set_data->sched_btclock = sched_btclock;
+ set_data->timestamp = timestamp;
+ set_data->ind_freq = ind_freq;
+ set_data->role = get_btrole(mcl);
+
+ /* TODO is there some way to schedule a call based directly on
+ * a BT clock value, instead of this estimation that uses
+ * the SO clock? */
+
+ if (phase2_delay > 0) {
+ when = phase2_delay + caps(mcl)->syncleadtime_ms;
+ mcl->csp->set_timer = g_timeout_add(when,
+ proc_sync_set_req_phase2,
+ mcl);
+ } else
+ proc_sync_set_req_phase2(mcl);
+
+ /* First indication is immediate */
+ if (update)
+ sync_send_indication(mcl);
+}
+
+static void proc_sync_cap_rsp(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ mcap_md_sync_cap_rsp *rsp;
+ uint8_t mcap_err;
+ uint8_t btclockres;
+ uint16_t synclead;
+ uint16_t tmstampres;
+ uint16_t tmstampacc;
+ struct mcap_sync_cap_cbdata *cbdata;
+ mcap_sync_cap_cb cb;
+ gpointer user_data;
+
+ if (mcl->csp->csp_req != MCAP_MD_SYNC_CAP_REQ) {
+ DBG("CSP: got unexpected cap respose");
+ return;
+ }
+
+ if (!mcl->csp->csp_priv_data) {
+ DBG("CSP: no priv data for cap respose");
+ return;
+ }
+
+ cbdata = mcl->csp->csp_priv_data;
+ cb = cbdata->cb;
+ user_data = cbdata->user_data;
+ g_free(cbdata);
+
+ mcl->csp->csp_priv_data = NULL;
+ mcl->csp->csp_req = 0;
+
+ if (len != sizeof(mcap_md_sync_cap_rsp)) {
+ DBG("CSP: got corrupted cap respose");
+ return;
+ }
+
+ rsp = (mcap_md_sync_cap_rsp *) cmd;
+ mcap_err = rsp->rc;
+ btclockres = rsp->btclock;
+ synclead = ntohs(rsp->sltime);
+ tmstampres = ntohs(rsp->timestnr);
+ tmstampacc = ntohs(rsp->timestna);
+
+ if (!mcap_err)
+ mcl->csp->local_caps = TRUE;
+
+ cb(mcl, mcap_err, btclockres, synclead, tmstampres, tmstampacc, NULL,
+ user_data);
+}
+
+static void proc_sync_set_rsp(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ mcap_md_sync_set_rsp *rsp;
+ uint8_t mcap_err;
+ uint32_t btclock;
+ uint64_t timestamp;
+ uint16_t accuracy;
+ struct mcap_sync_set_cbdata *cbdata;
+ mcap_sync_set_cb cb;
+ gpointer user_data;
+
+ if (mcl->csp->csp_req != MCAP_MD_SYNC_SET_REQ) {
+ DBG("CSP: got unexpected set respose");
+ return;
+ }
+
+ if (!mcl->csp->csp_priv_data) {
+ DBG("CSP: no priv data for set respose");
+ return;
+ }
+
+ cbdata = mcl->csp->csp_priv_data;
+ cb = cbdata->cb;
+ user_data = cbdata->user_data;
+ g_free(cbdata);
+
+ mcl->csp->csp_priv_data = NULL;
+ mcl->csp->csp_req = 0;
+
+ if (len != sizeof(mcap_md_sync_set_rsp)) {
+ DBG("CSP: got corrupted set respose");
+ return;
+ }
+
+ rsp = (mcap_md_sync_set_rsp *) cmd;
+ mcap_err = rsp->rc;
+ btclock = ntohl(rsp->btclock);
+ timestamp = ntoh64(rsp->timestst);
+ accuracy = ntohs(rsp->timestsa);
+
+ if (!mcap_err && !valid_btclock(btclock))
+ mcap_err = MCAP_ERROR_INVALID_ARGS;
+
+ cb(mcl, mcap_err, btclock, timestamp, accuracy, NULL, user_data);
+}
+
+static void proc_sync_info_ind(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ mcap_md_sync_info_ind *req;
+ struct sync_info_ind_data data;
+ uint32_t btclock;
+
+ if (!mcl->csp->ind_expected) {
+ DBG("CSP: received unexpected info indication");
+ return;
+ }
+
+ if (len != sizeof(mcap_md_sync_info_ind))
+ return;
+
+ req = (mcap_md_sync_info_ind *) cmd;
+
+ btclock = ntohl(req->btclock);
+
+ if (!valid_btclock(btclock))
+ return;
+
+ data.btclock = btclock;
+ data.timestamp = ntoh64(req->timestst);
+ data.accuracy = ntohs(req->timestsa);
+
+ if (mcl->mi->mcl_sync_infoind_cb)
+ mcl->mi->mcl_sync_infoind_cb(mcl, &data);
+}
+
+void proc_sync_cmd(struct mcap_mcl *mcl, uint8_t *cmd, uint32_t len)
+{
+ if (!mcl->mi->csp_enabled || !mcl->csp) {
+ switch (cmd[0]) {
+ case MCAP_MD_SYNC_CAP_REQ:
+ send_unsupported_cap_req(mcl);
+ break;
+ case MCAP_MD_SYNC_SET_REQ:
+ send_unsupported_set_req(mcl);
+ break;
+ }
+ return;
+ }
+
+ switch (cmd[0]) {
+ case MCAP_MD_SYNC_CAP_REQ:
+ proc_sync_cap_req(mcl, cmd, len);
+ break;
+ case MCAP_MD_SYNC_CAP_RSP:
+ proc_sync_cap_rsp(mcl, cmd, len);
+ break;
+ case MCAP_MD_SYNC_SET_REQ:
+ proc_sync_set_req(mcl, cmd, len);
+ break;
+ case MCAP_MD_SYNC_SET_RSP:
+ proc_sync_set_rsp(mcl, cmd, len);
+ break;
+ case MCAP_MD_SYNC_INFO_IND:
+ proc_sync_info_ind(mcl, cmd, len);
+ break;
+ }
+}
+
+void mcap_sync_cap_req(struct mcap_mcl *mcl, uint16_t reqacc,
+ mcap_sync_cap_cb cb, gpointer user_data,
+ GError **err)
+{
+ struct mcap_sync_cap_cbdata *cbdata;
+ mcap_md_sync_cap_req *cmd;
+
+ if (!mcl->mi->csp_enabled || !mcl->csp) {
+ g_set_error(err,
+ MCAP_CSP_ERROR,
+ MCAP_ERROR_RESOURCE_UNAVAILABLE,
+ "CSP not enabled for the instance");
+ return;
+ }
+
+ if (mcl->csp->csp_req) {
+ g_set_error(err,
+ MCAP_CSP_ERROR,
+ MCAP_ERROR_RESOURCE_UNAVAILABLE,
+ "Pending CSP request");
+ return;
+ }
+
+ mcl->csp->csp_req = MCAP_MD_SYNC_CAP_REQ;
+ cmd = g_new0(mcap_md_sync_cap_req, 1);
+
+ cmd->op = MCAP_MD_SYNC_CAP_REQ;
+ cmd->timest = htons(reqacc);
+
+ cbdata = g_new0(struct mcap_sync_cap_cbdata, 1);
+ cbdata->cb = cb;
+ cbdata->user_data = user_data;
+ mcl->csp->csp_priv_data = cbdata;
+
+ send_sync_cmd(mcl, cmd, sizeof(*cmd));
+
+ g_free(cmd);
+}
+
+void mcap_sync_set_req(struct mcap_mcl *mcl, uint8_t update, uint32_t btclock,
+ uint64_t timestamp, mcap_sync_set_cb cb,
+ gpointer user_data, GError **err)
+{
+ mcap_md_sync_set_req *cmd;
+ struct mcap_sync_set_cbdata *cbdata;
+
+ if (!mcl->mi->csp_enabled || !mcl->csp) {
+ g_set_error(err,
+ MCAP_CSP_ERROR,
+ MCAP_ERROR_RESOURCE_UNAVAILABLE,
+ "CSP not enabled for the instance");
+ return;
+ }
+
+ if (!mcl->csp->local_caps) {
+ g_set_error(err,
+ MCAP_CSP_ERROR,
+ MCAP_ERROR_RESOURCE_UNAVAILABLE,
+ "Did not get CSP caps from slave yet");
+ return;
+ }
+
+ if (mcl->csp->csp_req) {
+ g_set_error(err,
+ MCAP_CSP_ERROR,
+ MCAP_ERROR_RESOURCE_UNAVAILABLE,
+ "Pending CSP request");
+ return;
+ }
+
+ mcl->csp->csp_req = MCAP_MD_SYNC_SET_REQ;
+ cmd = g_new0(mcap_md_sync_set_req, 1);
+
+ cmd->op = MCAP_MD_SYNC_SET_REQ;
+ cmd->timestui = update;
+ cmd->btclock = htonl(btclock);
+ cmd->timestst = hton64(timestamp);
+
+ mcl->csp->ind_expected = update;
+
+ cbdata = g_new0(struct mcap_sync_set_cbdata, 1);
+ cbdata->cb = cb;
+ cbdata->user_data = user_data;
+ mcl->csp->csp_priv_data = cbdata;
+
+ send_sync_cmd(mcl, cmd, sizeof(*cmd));
+
+ g_free(cmd);
+}
+
+void mcap_enable_csp(struct mcap_instance *mi)
+{
+ mi->csp_enabled = TRUE;
+}
+
+void mcap_disable_csp(struct mcap_instance *mi)
+{
+ mi->csp_enabled = FALSE;
+}