| /* |
| * |
| * 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 <stdlib.h> |
| #include <stdint.h> |
| #include <stdbool.h> |
| #include <unistd.h> |
| |
| #include <glib.h> |
| |
| #include "lib/bluetooth.h" |
| #include "lib/l2cap.h" |
| #include "lib/sdp.h" |
| |
| #include "gdbus/gdbus.h" |
| |
| #include "src/dbus-common.h" |
| #include "src/log.h" |
| #include "src/error.h" |
| #include "src/adapter.h" |
| #include "src/device.h" |
| #include "src/sdpd.h" |
| #include "btio/btio.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 GSList *applications = NULL; |
| static GSList *devices = NULL; |
| static uint8_t next_app_id = HDP_MDEP_INITIAL; |
| |
| static GSList *adapters; |
| |
| static struct hdp_device *create_health_device(struct btd_device *device); |
| static void free_echo_data(struct hdp_echo_data *edata); |
| |
| struct hdp_create_dc { |
| 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 { |
| 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 */ |
| guint tid; /* echo timeout */ |
| }; |
| |
| static struct hdp_channel *hdp_channel_ref(struct hdp_channel *chan) |
| { |
| if (chan == NULL) |
| return NULL; |
| |
| chan->ref++; |
| |
| DBG("(%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("(%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); |
| 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("(%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("(%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); |
| 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 int cmp_dev_addr(gconstpointer a, gconstpointer dst) |
| { |
| const struct hdp_device *device = a; |
| |
| return bacmp(device_get_address(device->dev), dst); |
| } |
| |
| static int cmp_dev_mcl(gconstpointer a, gconstpointer mcl) |
| { |
| const struct hdp_device *device = a; |
| |
| if (mcl == device->mcl) |
| return 0; |
| return -1; |
| } |
| |
| static int cmp_chan_mdlid(gconstpointer a, gconstpointer b) |
| { |
| const struct hdp_channel *chan = a; |
| const uint16_t *mdlid = b; |
| |
| return chan->mdlid - *mdlid; |
| } |
| |
| static int 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 int 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->dev != NULL) { |
| btd_device_unref(device->dev); |
| device->dev = NULL; |
| } |
| |
| device_unref_mcl(device); |
| |
| g_free(device); |
| } |
| |
| static void update_adapter_cb(void *data, void *user_data); |
| |
| static void remove_application(struct hdp_application *app) |
| { |
| DBG("Application %s deleted", app->path); |
| hdp_application_unref(app); |
| |
| g_slist_foreach(adapters, update_adapter_cb, 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); |
| |
| applications = g_slist_prepend(applications, app); |
| |
| app->dbus_watcher = |
| g_dbus_add_disconnect_watch(btd_get_dbus_connection(), |
| name, client_disconnected, app, NULL); |
| g_slist_foreach(adapters, update_adapter_cb, 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 application_unref(void *data, void *user_data) |
| { |
| hdp_application_unref(data); |
| } |
| |
| static void manager_path_unregister(gpointer data) |
| { |
| g_slist_foreach(applications, application_unref, NULL); |
| |
| g_slist_free(applications); |
| applications = NULL; |
| |
| g_slist_foreach(adapters, update_adapter_cb, 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 gboolean channel_property_get_device(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct hdp_channel *chan = data; |
| const char *path = device_get_path(chan->dev->dev); |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); |
| |
| return TRUE; |
| } |
| |
| static gboolean channel_property_get_application( |
| const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct hdp_channel *chan = data; |
| const char *path = chan->app->path; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, &path); |
| |
| return TRUE; |
| } |
| |
| static gboolean channel_property_get_type(const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct hdp_channel *chan = data; |
| const char *type; |
| |
| if (chan->config == HDP_RELIABLE_DC) |
| type = "reliable"; |
| else |
| type = "streaming"; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &type); |
| |
| return TRUE; |
| } |
| |
| 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) |
| { |
| DBusConnection *conn = btd_get_dbus_connection(); |
| 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(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(conn, reply); |
| return; |
| } |
| |
| reply = g_dbus_create_reply(dc_data->msg, DBUS_TYPE_UNIX_FD, |
| &fd, DBUS_TYPE_INVALID); |
| g_dbus_send_message(conn, reply); |
| |
| g_dbus_emit_signal(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_conn->cb(hdp_chann->mdl, err, hdp_conn); |
| g_error_free(gerr); |
| hdp_tmp_dc_data_unref(hdp_conn); |
| } |
| |
| static void device_reconnect_mdl_cb(struct mcap_mdl *mdl, GError *err, |
| gpointer data) |
| { |
| DBusConnection *conn = btd_get_dbus_connection(); |
| 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(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(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; |
| |
| reply = g_dbus_create_error(data->msg, ERROR_INTERFACE ".HealthError", |
| "Cannot reconnect: %s", gerr->message); |
| g_error_free(gerr); |
| hdp_tmp_dc_data_unref(data); |
| |
| return reply; |
| } |
| |
| static void channel_acquire_cb(gpointer data, GError *err) |
| { |
| DBusMessage *reply; |
| |
| reply = channel_acquire_continue(data, err); |
| |
| if (reply != NULL) |
| g_dbus_send_message(btd_get_dbus_connection(), 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->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(btd_get_dbus_connection(), |
| 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_ASYNC_METHOD("Acquire", |
| NULL, GDBUS_ARGS({ "fd", "h" }), |
| channel_acquire) }, |
| { GDBUS_METHOD("Release", NULL, NULL, channel_release) }, |
| { } |
| }; |
| |
| static const GDBusPropertyTable health_channels_properties[] = { |
| { "Device", "o", channel_property_get_device }, |
| { "Application", "o", channel_property_get_application }, |
| { "Type", "s", channel_property_get_type }, |
| { } |
| }; |
| |
| 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) { |
| g_set_error(err, HDP_ERROR, HDP_UNSPECIFIED_ERROR, |
| "HDP device uninitialized"); |
| 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(btd_get_dbus_connection(), |
| hdp_chann->path, HEALTH_CHANNEL, |
| health_channels_methods, NULL, |
| health_channels_properties, 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(btd_get_dbus_connection(), |
| 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(btd_get_dbus_connection(), |
| 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 (len < 0) |
| goto fail; |
| |
| 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, &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(""); |
| |
| 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(btd_get_dbus_connection(), 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); |
| |
| g_dbus_emit_property_changed(btd_get_dbus_connection(), |
| device_get_path(dev->dev), HEALTH_DEVICE, |
| "MainChannel"); |
| |
| 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(""); |
| |
| /* 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(""); |
| |
| 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(btd_get_dbus_connection(), |
| 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(""); |
| |
| 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(btd_get_dbus_connection(), |
| 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; |
| break; |
| 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; |
| break; |
| 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(btd_get_dbus_connection(), |
| 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; |
| |
| device = btd_adapter_get_device(hdp_adapter->btd_adapter, |
| &addr, BDADDR_BREDR); |
| if (!device) |
| return; |
| hdp_device = create_health_device(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(btd_get_dbus_connection(), |
| 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(btd_get_dbus_connection(), |
| 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; |
| const bdaddr_t *src; |
| |
| if (applications == NULL) { |
| release_adapter_instance(hdp_adapter); |
| goto update; |
| } |
| |
| if (hdp_adapter->mi != NULL) |
| goto update; |
| |
| src = btd_adapter_get_address(hdp_adapter->btd_adapter); |
| |
| hdp_adapter->mi = mcap_create_instance(src, |
| 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; |
| } |
| |
| static void update_adapter_cb(void *data, void *user_data) |
| { |
| update_adapter(data); |
| } |
| |
| int hdp_adapter_register(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) |
| adapter_service_remove(adapter, 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(btd_get_dbus_connection(), 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) |
| { |
| DBusConnection *conn = btd_get_dbus_connection(); |
| 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(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(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(btd_get_dbus_connection(), |
| 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) |
| { |
| DBusConnection *conn = btd_get_dbus_connection(); |
| 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(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(conn, reply); |
| |
| g_dbus_emit_signal(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); |
| |
| g_dbus_emit_property_changed(btd_get_dbus_connection(), |
| device_get_path(dev->dev), HEALTH_DEVICE, |
| "MainChannel"); |
| } |
| |
| static void device_create_mdl_cb(struct mcap_mdl *mdl, uint8_t conf, |
| GError *err, gpointer data) |
| { |
| DBusConnection *conn = btd_get_dbus_connection(); |
| 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(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->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(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(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) |
| { |
| DBusConnection *conn = btd_get_dbus_connection(); |
| 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(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(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->cb = hdp_echo_connect_cb; |
| hdp_create_data_ref(data); |
| |
| if (device->mcl_conn && device->mcl) { |
| 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) |
| { |
| DBusConnection *conn = btd_get_dbus_connection(); |
| 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(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(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->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) |
| { |
| DBusConnection *conn = btd_get_dbus_connection(); |
| 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(conn, reply); |
| return; |
| } |
| |
| path = g_strdup(del_data->hdp_chann->path); |
| g_dbus_unregister_interface(conn, path, HEALTH_CHANNEL); |
| g_free(path); |
| |
| reply = g_dbus_create_reply(del_data->msg, DBUS_TYPE_INVALID); |
| g_dbus_send_message(conn, reply); |
| } |
| |
| static void hdp_continue_del_cb(gpointer user_data, GError *err) |
| { |
| DBusConnection *conn = btd_get_dbus_connection(); |
| 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(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(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->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 gboolean dev_property_exists_main_channel( |
| const GDBusPropertyTable *property, void *data) |
| { |
| struct hdp_device *device = data; |
| return device->fr != NULL; |
| } |
| |
| static gboolean dev_property_get_main_channel( |
| const GDBusPropertyTable *property, |
| DBusMessageIter *iter, void *data) |
| { |
| struct hdp_device *device = data; |
| |
| if (device->fr == NULL) |
| return FALSE; |
| |
| dbus_message_iter_append_basic(iter, DBUS_TYPE_OBJECT_PATH, |
| &device->fr->path); |
| |
| return TRUE; |
| } |
| |
| 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) }, |
| { } |
| }; |
| |
| static const GDBusSignalTable health_device_signals[] = { |
| { GDBUS_SIGNAL("ChannelConnected", |
| GDBUS_ARGS({ "channel", "o" })) }, |
| { GDBUS_SIGNAL("ChannelDeleted", |
| GDBUS_ARGS({ "channel", "o" })) }, |
| { } |
| }; |
| |
| static const GDBusPropertyTable health_device_properties[] = { |
| { "MainChannel", "o", dev_property_get_main_channel, NULL, |
| dev_property_exists_main_channel }, |
| { } |
| }; |
| |
| static struct hdp_device *create_health_device(struct btd_device *device) |
| { |
| struct btd_adapter *adapter = device_get_adapter(device); |
| const char *path = device_get_path(device); |
| struct hdp_device *dev; |
| GSList *l; |
| |
| if (device == NULL) |
| return NULL; |
| |
| dev = g_new0(struct hdp_device, 1); |
| 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(btd_get_dbus_connection(), |
| path, HEALTH_DEVICE, |
| health_device_methods, |
| health_device_signals, |
| health_device_properties, |
| 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(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(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(btd_get_dbus_connection(), |
| path, HEALTH_DEVICE); |
| } |
| |
| int hdp_manager_start(void) |
| { |
| DBG("Starting Health manager"); |
| |
| if (!g_dbus_register_interface(btd_get_dbus_connection(), |
| 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; |
| } |
| |
| return 0; |
| } |
| |
| void hdp_manager_stop(void) |
| { |
| g_dbus_unregister_interface(btd_get_dbus_connection(), |
| MANAGER_PATH, HEALTH_MANAGER); |
| |
| DBG("Stopped Health manager"); |
| } |
| |
| struct hdp_device *health_device_ref(struct hdp_device *hdp_dev) |
| { |
| hdp_dev->ref++; |
| |
| DBG("(%p): ref=%d", hdp_dev, hdp_dev->ref); |
| |
| return hdp_dev; |
| } |
| |
| void health_device_unref(struct hdp_device *hdp_dev) |
| { |
| hdp_dev->ref--; |
| |
| DBG("(%p): ref=%d", hdp_dev, hdp_dev->ref); |
| |
| if (hdp_dev->ref > 0) |
| return; |
| |
| free_health_device(hdp_dev); |
| } |