/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. * Authors: * Santiago Carot Nemesio * Jose Antonio Santos-Cadenas * * 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 #include #include #include #include #include #include #include #include #include #include #include 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; struct hdp_application *app; gpointer data; hdp_continue_proc_f func; GDestroyNotify destroy; struct hdp_device *dev; }; 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, variant; int ctype; ctype = dbus_message_iter_get_arg_type(iter); value = iter; if (ctype == DBUS_TYPE_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 value; DBusMessageIter *string; int ctype; const char *role; ctype = dbus_message_iter_get_arg_type(iter); if (ctype == DBUS_TYPE_VARIANT) { /* 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, variant; int ctype; const char *desc; ctype = dbus_message_iter_get_arg_type(iter); if (ctype == DBUS_TYPE_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, variant; int ctype; ctype = dbus_message_iter_get_arg_type(iter); value = iter; if (ctype == DBUS_TYPE_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 channel type should be a uint16"); return FALSE; } dbus_message_iter_get_basic(value, &app->data_type); if (app->data_type < HDP_RELIABLE_DC || app->data_type > HDP_STREAMING_DC) { g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, "Invalid value for data type"); return FALSE; } app->data_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); 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: g_free(app); return NULL; } static gboolean is_app_role(GSList *app_list, HdpRole role) { struct hdp_application *app; GSList *l; for (l = app_list; l; l = l->next) { 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, *mcap_list, *access_proto_list; sdp_data_t *psm, *mcap_ver; 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_aditional_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, *mcap_list, *access_proto_list; 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, *role, *desc; sdp_list_t *f_list; 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_aditional_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, *data_type, *mdepid, *role_t, *desc_t; 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) { 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); 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 = 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 guint16 get_ccpsm(sdp_list_t *recs, uint16_t *ccpsm) { sdp_list_t *l; sdp_record_t *rec; for (l = recs; l; l = l->next) { rec = l->data; if (hdp_get_prot_desc_list(rec, ccpsm, NULL)) return TRUE; } return FALSE; } static void con_mcl_data_unref(gpointer data) { struct conn_mcl_data *conn_data = data; if (!conn_data) return; if (--conn_data->refs > 0) return; if (conn_data->destroy) conn_data->destroy(conn_data->data); g_free(conn_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; GError *gerr = NULL; if (err) conn_data->func(conn_data->data, err); /* TODO: implement create_mcl_cb */ g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, "create_mcl_cb not implemented"); conn_data->func(conn_data->data, 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 (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, con_mcl_data_unref, &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, struct hdp_application *app, 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->app = app; conn_data->func = func; conn_data->data = data; conn_data->destroy = destroy; conn_data->dev = device; bt_string2uuid(&uuid, HDP_UUID); if (bt_search_service(&src, &dst, &uuid, search_cb, conn_data, con_mcl_data_unref)) { g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR, "Can't get remote SDP record"); return FALSE; } return TRUE; }