diff options
Diffstat (limited to 'health/hdp_util.c')
-rw-r--r-- | health/hdp_util.c | 1211 |
1 files changed, 1211 insertions, 0 deletions
diff --git a/health/hdp_util.c b/health/hdp_util.c new file mode 100644 index 0000000..aefe5f9 --- /dev/null +++ b/health/hdp_util.c @@ -0,0 +1,1211 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. + * Authors: + * Santiago Carot Nemesio <sancane at gmail.com> + * Jose Antonio Santos-Cadenas <santoscadenas at gmail.com> + * + * 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 <gdbus.h> + +#include <adapter.h> +#include <device.h> +#include <stdint.h> +#include <hdp_types.h> +#include <hdp_util.h> +#include <mcap.h> +#include <hdp.h> + +#include <sdpd.h> +#include <sdp_lib.h> +#include <glib-helper.h> + +#include <btio.h> +#include <mcap_lib.h> + +#include <log.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) { + ret = FALSE; + goto end; + } + + psm = sdp_data_alloc(SDP_UINT16, &adapter->ccpsm); + if (!psm) { + ret = FALSE; + goto end; + } + + if (!sdp_list_append(l2cap_list, psm)) { + ret = FALSE; + goto end; + } + + proto_list = sdp_list_append(NULL, l2cap_list); + if (!proto_list) { + 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) { + ret = FALSE; + goto end; + } + + mcap_ver = sdp_data_alloc(SDP_UINT16, &version); + if (!mcap_ver) { + ret = FALSE; + goto end; + } + + if (!sdp_list_append(mcap_list, mcap_ver)) { + ret = FALSE; + goto end; + } + + if (!sdp_list_append(proto_list, mcap_list)) { + ret = FALSE; + goto end; + } + + /* attach protocol information to service record */ + access_proto_list = sdp_list_append(NULL, proto_list); + if (!access_proto_list) { + 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) + sdp_list_free(l2cap_list, NULL); + if (mcap_list) + sdp_list_free(mcap_list, NULL); + if (proto_list) + sdp_list_free(proto_list, NULL); + if (access_proto_list) + sdp_list_free(access_proto_list, NULL); + if (psm) + sdp_data_free(psm); + if (mcap_ver) + 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) + 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) { + ret = FALSE; + goto end; + } + + psm = sdp_data_alloc(SDP_UINT16, &adapter->dcpsm); + if (!psm) { + ret = FALSE; + goto end; + } + + if (!sdp_list_append(l2cap_list, psm)) { + ret = FALSE; + goto end; + } + + proto_list = sdp_list_append(NULL, l2cap_list); + if (!proto_list) { + 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) { + ret = FALSE; + goto end; + } + + if (!sdp_list_append(proto_list, mcap_list)) { + ret = FALSE; + goto end; + } + + /* attach protocol information to service record */ + access_proto_list = sdp_list_append(NULL, proto_list); + if (!access_proto_list) { + 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) + sdp_list_free(l2cap_list, NULL); + if (mcap_list) + sdp_list_free(mcap_list, NULL); + if (proto_list) + sdp_list_free(proto_list, NULL); + if (access_proto_list) + sdp_list_free(access_proto_list, NULL); + if (psm) + 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) + return NULL; + + dtype = sdp_data_alloc(SDP_UINT16, &app->data_type); + if (!dtype) + goto fail; + + role = sdp_data_alloc(SDP_UINT8, &app->role); + if (!role) + goto fail; + + if (app->description) { + desc = sdp_data_alloc(SDP_TEXT_STR8, app->description); + if (!desc) + goto fail; + } + + f_list = sdp_list_append(NULL, mdepid); + if (!f_list) + goto fail; + + if (!sdp_list_append(f_list, dtype)) + goto fail; + + if (!sdp_list_append(f_list, role)) + goto fail; + + if (desc) + if (!sdp_list_append(f_list, desc)) + goto fail; + + return f_list; + +fail: + if (f_list) + sdp_list_free(f_list, NULL); + if (mdepid) + sdp_data_free(mdepid); + if (dtype) + sdp_data_free(dtype); + if (role) + sdp_data_free(role); + if (desc) + 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) + goto fail; + + if (!*sup_features) { + *sup_features = sdp_list_append(NULL, hdp_feature); + if (!*sup_features) + goto fail; + } else if (!sdp_list_append(*sup_features, hdp_feature)) { + goto fail; + } + + return TRUE; + +fail: + if (hdp_feature) + 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) + 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) + 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) + remove_record_from_server(adapter->sdp_handler); + + if (!app_list) { + adapter->sdp_handler = 0; + return TRUE; + } + + sdp_record = sdp_record_alloc(); + if (!sdp_record) + return FALSE; + + if (adapter->sdp_handler) + 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++)) + 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) + 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 && !mdep) + return TRUE; + + list = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES_LIST); + + if (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) + continue; + + data_type = mdepid->next; + if (!data_type) + continue; + + role_t = data_type->next; + if (!role_t) + 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) + *mdep = mdepid->val.uint8; + + if (desc && desc_t && (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 || !recs) { + 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); + 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)) { + 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 || (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) + 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 && !version) + return TRUE; + + pdl = sdp_data_get(rec, SDP_ATTR_PROTO_DESC_LIST); + if (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) + return TRUE; + + pdl = sdp_data_get(rec, SDP_ATTR_ADD_PROTO_DESC_LIST); + if (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) + 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) + 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) { + conn_data->func(conn_data->data, err); + return; + } + + if (!device->mcl) + 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) + 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) { + g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, + "Mcap instance released"); + goto fail; + } + + if (err || !recs) { + 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); + 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); + 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)) { + 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 || !recs) { + 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) + 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); + 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)) { + 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) + g_dbus_remove_watch(app->conn, app->dbus_watcher); + + if (app->conn) + 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) + 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) + return; + + app->ref --; + + DBG("health_application_unref(%p): ref=%d", app, app->ref); + if (app->ref > 0) + return; + + hdp_free_application(app); +} |