| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2010 GSyC/LibreSoft, Universidad Rey Juan Carlos. |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdint.h> |
| #include <stdbool.h> |
| |
| #include <glib.h> |
| |
| #include "lib/bluetooth.h" |
| #include "lib/sdp.h" |
| #include "lib/sdp_lib.h" |
| #include "lib/uuid.h" |
| |
| #include "gdbus/gdbus.h" |
| |
| #include "btio/btio.h" |
| #include "src/adapter.h" |
| #include "src/device.h" |
| #include "src/sdpd.h" |
| #include "src/sdp-client.h" |
| #include "src/uuid-helper.h" |
| #include "src/log.h" |
| #include "src/dbus-common.h" |
| |
| #include "mcap.h" |
| #include "hdp_types.h" |
| #include "hdp.h" |
| #include "hdp_util.h" |
| |
| typedef gboolean (*parse_item_f)(DBusMessageIter *iter, gpointer user_data, |
| GError **err); |
| |
| struct dict_entry_func { |
| char *key; |
| parse_item_f func; |
| }; |
| |
| struct get_mdep_data { |
| struct hdp_application *app; |
| gpointer data; |
| hdp_continue_mdep_f func; |
| GDestroyNotify destroy; |
| }; |
| |
| struct conn_mcl_data { |
| int refs; |
| gpointer data; |
| hdp_continue_proc_f func; |
| GDestroyNotify destroy; |
| struct hdp_device *dev; |
| }; |
| |
| struct get_dcpsm_data { |
| gpointer data; |
| hdp_continue_dcpsm_f func; |
| GDestroyNotify destroy; |
| }; |
| |
| static gboolean parse_dict_entry(struct dict_entry_func dict_context[], |
| DBusMessageIter *iter, |
| GError **err, |
| gpointer user_data) |
| { |
| DBusMessageIter entry; |
| char *key; |
| int ctype, i; |
| struct dict_entry_func df; |
| |
| dbus_message_iter_recurse(iter, &entry); |
| ctype = dbus_message_iter_get_arg_type(&entry); |
| if (ctype != DBUS_TYPE_STRING) { |
| g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, |
| "Dictionary entries should have a string as key"); |
| return FALSE; |
| } |
| |
| dbus_message_iter_get_basic(&entry, &key); |
| dbus_message_iter_next(&entry); |
| /* Find function and call it */ |
| for (i = 0, df = dict_context[0]; df.key; i++, df = dict_context[i]) { |
| if (g_ascii_strcasecmp(df.key, key) == 0) |
| return df.func(&entry, user_data, err); |
| } |
| |
| g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, |
| "No function found for parsing value for key %s", key); |
| return FALSE; |
| } |
| |
| static gboolean parse_dict(struct dict_entry_func dict_context[], |
| DBusMessageIter *iter, |
| GError **err, |
| gpointer user_data) |
| { |
| int ctype; |
| DBusMessageIter dict; |
| |
| ctype = dbus_message_iter_get_arg_type(iter); |
| if (ctype != DBUS_TYPE_ARRAY) { |
| g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR, |
| "Dictionary should be an array"); |
| return FALSE; |
| } |
| |
| dbus_message_iter_recurse(iter, &dict); |
| while ((ctype = dbus_message_iter_get_arg_type(&dict)) != |
| DBUS_TYPE_INVALID) { |
| if (ctype != DBUS_TYPE_DICT_ENTRY) { |
| g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR, |
| "Dictionary array should " |
| "contain dict entries"); |
| return FALSE; |
| } |
| |
| /* Start parsing entry */ |
| if (!parse_dict_entry(dict_context, &dict, err, |
| user_data)) |
| return FALSE; |
| /* Finish entry parsing */ |
| |
| dbus_message_iter_next(&dict); |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean parse_data_type(DBusMessageIter *iter, gpointer data, |
| GError **err) |
| { |
| struct hdp_application *app = data; |
| DBusMessageIter *value; |
| DBusMessageIter 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 *string; |
| DBusMessageIter value; |
| 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; |
| DBusMessageIter 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; |
| DBusMessageIter variant; |
| char *chan_type; |
| 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_STRING) { |
| g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, |
| "Final value for channel type should be an string"); |
| return FALSE; |
| } |
| |
| dbus_message_iter_get_basic(value, &chan_type); |
| |
| if (g_ascii_strcasecmp("reliable", chan_type) == 0) |
| app->chan_type = HDP_RELIABLE_DC; |
| else if (g_ascii_strcasecmp("streaming", chan_type) == 0) |
| app->chan_type = HDP_STREAMING_DC; |
| else { |
| g_set_error(err, HDP_ERROR, HDP_DIC_ENTRY_PARSE_ERROR, |
| "Invalid value for data type"); |
| return FALSE; |
| } |
| |
| app->chan_type_set = TRUE; |
| |
| return TRUE; |
| } |
| |
| static struct dict_entry_func dict_parser[] = { |
| {"DataType", parse_data_type}, |
| {"Role", parse_role}, |
| {"Description", parse_desc}, |
| {"ChannelType", parse_chan_type}, |
| {NULL, NULL} |
| }; |
| |
| struct hdp_application *hdp_get_app_config(DBusMessageIter *iter, GError **err) |
| { |
| struct hdp_application *app; |
| |
| app = g_new0(struct hdp_application, 1); |
| app->ref = 1; |
| if (!parse_dict(dict_parser, iter, err, app)) |
| goto fail; |
| if (!app->data_type_set || !app->role_set) { |
| g_set_error(err, HDP_ERROR, HDP_DIC_PARSE_ERROR, |
| "Mandatory fields aren't set"); |
| goto fail; |
| } |
| return app; |
| |
| fail: |
| hdp_application_unref(app); |
| return NULL; |
| } |
| |
| static gboolean is_app_role(GSList *app_list, HdpRole role) |
| { |
| GSList *l; |
| |
| for (l = app_list; l; l = l->next) { |
| struct hdp_application *app = l->data; |
| |
| if (app->role == role) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean set_sdp_services_uuid(sdp_record_t *record, HdpRole role) |
| { |
| uuid_t svc_uuid_source, svc_uuid_sink; |
| sdp_list_t *svc_list = NULL; |
| |
| sdp_uuid16_create(&svc_uuid_sink, HDP_SINK_SVCLASS_ID); |
| sdp_uuid16_create(&svc_uuid_source, HDP_SOURCE_SVCLASS_ID); |
| |
| sdp_get_service_classes(record, &svc_list); |
| |
| if (role == HDP_SOURCE) { |
| if (!sdp_list_find(svc_list, &svc_uuid_source, sdp_uuid_cmp)) |
| svc_list = sdp_list_append(svc_list, &svc_uuid_source); |
| } else if (role == HDP_SINK) { |
| if (!sdp_list_find(svc_list, &svc_uuid_sink, sdp_uuid_cmp)) |
| svc_list = sdp_list_append(svc_list, &svc_uuid_sink); |
| } |
| |
| if (sdp_set_service_classes(record, svc_list) < 0) { |
| sdp_list_free(svc_list, NULL); |
| return FALSE; |
| } |
| |
| sdp_list_free(svc_list, NULL); |
| |
| return TRUE; |
| } |
| |
| static gboolean register_service_protocols(struct hdp_adapter *adapter, |
| sdp_record_t *sdp_record) |
| { |
| gboolean ret; |
| uuid_t l2cap_uuid, mcap_c_uuid; |
| sdp_list_t *l2cap_list, *proto_list = NULL, *mcap_list = NULL; |
| sdp_list_t *access_proto_list = NULL; |
| sdp_data_t *psm = NULL, *mcap_ver = NULL; |
| uint16_t version = MCAP_VERSION; |
| |
| /* set l2cap information */ |
| sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); |
| l2cap_list = sdp_list_append(NULL, &l2cap_uuid); |
| if (l2cap_list == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| psm = sdp_data_alloc(SDP_UINT16, &adapter->ccpsm); |
| if (psm == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| if (sdp_list_append(l2cap_list, psm) == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| proto_list = sdp_list_append(NULL, l2cap_list); |
| if (proto_list == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| /* set mcap information */ |
| sdp_uuid16_create(&mcap_c_uuid, MCAP_CTRL_UUID); |
| mcap_list = sdp_list_append(NULL, &mcap_c_uuid); |
| if (mcap_list == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| mcap_ver = sdp_data_alloc(SDP_UINT16, &version); |
| if (mcap_ver == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| if (sdp_list_append(mcap_list, mcap_ver) == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| if (sdp_list_append(proto_list, mcap_list) == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| /* attach protocol information to service record */ |
| access_proto_list = sdp_list_append(NULL, proto_list); |
| if (access_proto_list == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| ret = TRUE; |
| sdp_set_access_protos(sdp_record, access_proto_list); |
| |
| end: |
| if (l2cap_list != NULL) |
| sdp_list_free(l2cap_list, NULL); |
| if (mcap_list != NULL) |
| sdp_list_free(mcap_list, NULL); |
| if (proto_list != NULL) |
| sdp_list_free(proto_list, NULL); |
| if (access_proto_list != NULL) |
| sdp_list_free(access_proto_list, NULL); |
| if (psm != NULL) |
| sdp_data_free(psm); |
| if (mcap_ver != NULL) |
| sdp_data_free(mcap_ver); |
| |
| return ret; |
| } |
| |
| static gboolean register_service_profiles(sdp_record_t *sdp_record) |
| { |
| gboolean ret; |
| sdp_list_t *profile_list; |
| sdp_profile_desc_t hdp_profile; |
| |
| /* set hdp information */ |
| sdp_uuid16_create(&hdp_profile.uuid, HDP_SVCLASS_ID); |
| hdp_profile.version = HDP_VERSION; |
| profile_list = sdp_list_append(NULL, &hdp_profile); |
| if (profile_list == NULL) |
| return FALSE; |
| |
| /* set profile descriptor list */ |
| if (sdp_set_profile_descs(sdp_record, profile_list) < 0) |
| ret = FALSE; |
| else |
| ret = TRUE; |
| |
| sdp_list_free(profile_list, NULL); |
| |
| return ret; |
| } |
| |
| static gboolean register_service_additional_protocols( |
| struct hdp_adapter *adapter, |
| sdp_record_t *sdp_record) |
| { |
| gboolean ret = TRUE; |
| uuid_t l2cap_uuid, mcap_d_uuid; |
| sdp_list_t *l2cap_list, *proto_list = NULL, *mcap_list = NULL; |
| sdp_list_t *access_proto_list = NULL; |
| sdp_data_t *psm = NULL; |
| |
| /* set l2cap information */ |
| sdp_uuid16_create(&l2cap_uuid, L2CAP_UUID); |
| l2cap_list = sdp_list_append(NULL, &l2cap_uuid); |
| if (l2cap_list == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| psm = sdp_data_alloc(SDP_UINT16, &adapter->dcpsm); |
| if (psm == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| if (sdp_list_append(l2cap_list, psm) == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| proto_list = sdp_list_append(NULL, l2cap_list); |
| if (proto_list == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| /* set mcap information */ |
| sdp_uuid16_create(&mcap_d_uuid, MCAP_DATA_UUID); |
| mcap_list = sdp_list_append(NULL, &mcap_d_uuid); |
| if (mcap_list == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| if (sdp_list_append(proto_list, mcap_list) == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| /* attach protocol information to service record */ |
| access_proto_list = sdp_list_append(NULL, proto_list); |
| if (access_proto_list == NULL) { |
| ret = FALSE; |
| goto end; |
| } |
| |
| sdp_set_add_access_protos(sdp_record, access_proto_list); |
| |
| end: |
| if (l2cap_list != NULL) |
| sdp_list_free(l2cap_list, NULL); |
| if (mcap_list != NULL) |
| sdp_list_free(mcap_list, NULL); |
| if (proto_list != NULL) |
| sdp_list_free(proto_list, NULL); |
| if (access_proto_list != NULL) |
| sdp_list_free(access_proto_list, NULL); |
| if (psm != NULL) |
| sdp_data_free(psm); |
| |
| return ret; |
| } |
| |
| static sdp_list_t *app_to_sdplist(struct hdp_application *app) |
| { |
| sdp_data_t *mdepid, |
| *dtype = NULL, |
| *role = NULL, |
| *desc = NULL; |
| sdp_list_t *f_list = NULL; |
| |
| mdepid = sdp_data_alloc(SDP_UINT8, &app->id); |
| if (mdepid == NULL) |
| return NULL; |
| |
| dtype = sdp_data_alloc(SDP_UINT16, &app->data_type); |
| if (dtype == NULL) |
| goto fail; |
| |
| role = sdp_data_alloc(SDP_UINT8, &app->role); |
| if (role == NULL) |
| goto fail; |
| |
| if (app->description != NULL) { |
| desc = sdp_data_alloc(SDP_TEXT_STR8, app->description); |
| if (desc == NULL) |
| goto fail; |
| } |
| |
| f_list = sdp_list_append(NULL, mdepid); |
| if (f_list == NULL) |
| goto fail; |
| |
| if (sdp_list_append(f_list, dtype) == NULL) |
| goto fail; |
| |
| if (sdp_list_append(f_list, role) == NULL) |
| goto fail; |
| |
| if (desc != NULL) |
| if (sdp_list_append(f_list, desc) == NULL) |
| goto fail; |
| |
| return f_list; |
| |
| fail: |
| if (f_list != NULL) |
| sdp_list_free(f_list, NULL); |
| if (mdepid != NULL) |
| sdp_data_free(mdepid); |
| if (dtype != NULL) |
| sdp_data_free(dtype); |
| if (role != NULL) |
| sdp_data_free(role); |
| if (desc != NULL) |
| sdp_data_free(desc); |
| |
| return NULL; |
| } |
| |
| static 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_features(struct hdp_application *app, |
| sdp_list_t **sup_features) |
| { |
| sdp_list_t *hdp_feature; |
| |
| hdp_feature = app_to_sdplist(app); |
| if (hdp_feature == NULL) |
| goto fail; |
| |
| if (*sup_features == NULL) { |
| *sup_features = sdp_list_append(NULL, hdp_feature); |
| if (*sup_features == NULL) |
| goto fail; |
| } else if (sdp_list_append(*sup_features, hdp_feature) == NULL) { |
| goto fail; |
| } |
| |
| return TRUE; |
| |
| fail: |
| if (hdp_feature != NULL) |
| sdp_list_free(hdp_feature, (sdp_free_func_t)sdp_data_free); |
| if (*sup_features != NULL) |
| sdp_list_free(*sup_features, free_hdp_list); |
| return FALSE; |
| } |
| |
| 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; |
| } |
| |
| sdp_list_free(sup_features, free_hdp_list); |
| |
| return TRUE; |
| } |
| |
| static gboolean register_data_exchange_spec(sdp_record_t *record) |
| { |
| sdp_data_t *spec; |
| uint8_t data_spec = DATA_EXCHANGE_SPEC_11073; |
| /* As by now 11073 is the only supported we set it by default */ |
| |
| spec = sdp_data_alloc(SDP_UINT8, &data_spec); |
| if (spec == NULL) |
| return FALSE; |
| |
| if (sdp_attr_add(record, SDP_ATTR_DATA_EXCHANGE_SPEC, spec) < 0) { |
| sdp_data_free(spec); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean register_mcap_features(sdp_record_t *sdp_record) |
| { |
| sdp_data_t *mcap_proc; |
| uint8_t mcap_sup_proc = MCAP_SUP_PROC; |
| |
| mcap_proc = sdp_data_alloc(SDP_UINT8, &mcap_sup_proc); |
| if (mcap_proc == NULL) |
| return FALSE; |
| |
| if (sdp_attr_add(sdp_record, SDP_ATTR_MCAP_SUPPORTED_PROCEDURES, |
| mcap_proc) < 0) { |
| sdp_data_free(mcap_proc); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| gboolean hdp_update_sdp_record(struct hdp_adapter *adapter, GSList *app_list) |
| { |
| sdp_record_t *sdp_record; |
| |
| if (adapter->sdp_handler > 0) |
| adapter_service_remove(adapter->btd_adapter, |
| adapter->sdp_handler); |
| |
| if (app_list == NULL) { |
| adapter->sdp_handler = 0; |
| return TRUE; |
| } |
| |
| sdp_record = sdp_record_alloc(); |
| if (sdp_record == NULL) |
| return FALSE; |
| |
| if (adapter->sdp_handler > 0) |
| sdp_record->handle = adapter->sdp_handler; |
| else |
| sdp_record->handle = 0xffffffff; /* Set automatically */ |
| |
| if (is_app_role(app_list, HDP_SINK)) |
| set_sdp_services_uuid(sdp_record, HDP_SINK); |
| if (is_app_role(app_list, HDP_SOURCE)) |
| set_sdp_services_uuid(sdp_record, HDP_SOURCE); |
| |
| if (!register_service_protocols(adapter, sdp_record)) |
| goto fail; |
| if (!register_service_profiles(sdp_record)) |
| goto fail; |
| if (!register_service_additional_protocols(adapter, sdp_record)) |
| goto fail; |
| |
| sdp_set_info_attr(sdp_record, HDP_SERVICE_NAME, HDP_SERVICE_PROVIDER, |
| HDP_SERVICE_DSC); |
| if (!register_service_sup_features(app_list, sdp_record)) |
| goto fail; |
| if (!register_data_exchange_spec(sdp_record)) |
| goto fail; |
| |
| register_mcap_features(sdp_record); |
| |
| if (sdp_set_record_state(sdp_record, adapter->record_state++) < 0) |
| goto fail; |
| |
| if (adapter_service_add(adapter->btd_adapter, sdp_record) < 0) |
| goto fail; |
| adapter->sdp_handler = sdp_record->handle; |
| return TRUE; |
| |
| fail: |
| if (sdp_record != NULL) |
| sdp_record_free(sdp_record); |
| return FALSE; |
| } |
| |
| static gboolean check_role(uint8_t rec_role, uint8_t app_role) |
| { |
| if ((rec_role == HDP_SINK && app_role == HDP_SOURCE) || |
| (rec_role == HDP_SOURCE && app_role == HDP_SINK)) |
| return TRUE; |
| |
| return FALSE; |
| } |
| |
| static gboolean get_mdep_from_rec(const sdp_record_t *rec, uint8_t role, |
| uint16_t d_type, uint8_t *mdep, char **desc) |
| { |
| sdp_data_t *list, *feat; |
| |
| if (desc == NULL && mdep == NULL) |
| return TRUE; |
| |
| list = sdp_data_get(rec, SDP_ATTR_SUPPORTED_FEATURES_LIST); |
| if (list == NULL || !SDP_IS_SEQ(list->dtd)) |
| return FALSE; |
| |
| for (feat = list->val.dataseq; feat; feat = feat->next) { |
| sdp_data_t *data_type, *mdepid, *role_t, *desc_t; |
| |
| if (!SDP_IS_SEQ(feat->dtd)) |
| continue; |
| |
| mdepid = feat->val.dataseq; |
| if (mdepid == NULL) |
| continue; |
| |
| data_type = mdepid->next; |
| if (data_type == NULL) |
| continue; |
| |
| role_t = data_type->next; |
| if (role_t == NULL) |
| continue; |
| |
| desc_t = role_t->next; |
| |
| if (data_type->dtd != SDP_UINT16 || mdepid->dtd != SDP_UINT8 || |
| role_t->dtd != SDP_UINT8) |
| continue; |
| |
| if (data_type->val.uint16 != d_type || |
| !check_role(role_t->val.uint8, role)) |
| continue; |
| |
| if (mdep != NULL) |
| *mdep = mdepid->val.uint8; |
| |
| if (desc != NULL && desc_t != NULL && |
| SDP_IS_TEXT_STR(desc_t->dtd)) |
| *desc = g_strdup(desc_t->val.str); |
| |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static void get_mdep_cb(sdp_list_t *recs, int err, gpointer user_data) |
| { |
| struct get_mdep_data *mdep_data = user_data; |
| GError *gerr = NULL; |
| uint8_t mdep; |
| |
| if (err < 0 || recs == NULL) { |
| g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, |
| "Error getting remote SDP records"); |
| mdep_data->func(0, mdep_data->data, gerr); |
| g_error_free(gerr); |
| return; |
| } |
| |
| if (!get_mdep_from_rec(recs->data, mdep_data->app->role, |
| mdep_data->app->data_type, &mdep, NULL)) { |
| g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, |
| "No matching MDEP found"); |
| mdep_data->func(0, mdep_data->data, gerr); |
| g_error_free(gerr); |
| return; |
| } |
| |
| mdep_data->func(mdep, mdep_data->data, NULL); |
| } |
| |
| static void free_mdep_data(gpointer data) |
| { |
| struct get_mdep_data *mdep_data = data; |
| |
| if (mdep_data->destroy) |
| mdep_data->destroy(mdep_data->data); |
| hdp_application_unref(mdep_data->app); |
| |
| g_free(mdep_data); |
| } |
| |
| gboolean hdp_get_mdep(struct hdp_device *device, struct hdp_application *app, |
| hdp_continue_mdep_f func, gpointer data, |
| GDestroyNotify destroy, GError **err) |
| { |
| struct get_mdep_data *mdep_data; |
| const bdaddr_t *src; |
| const bdaddr_t *dst; |
| uuid_t uuid; |
| |
| src = btd_adapter_get_address(device_get_adapter(device->dev)); |
| dst = device_get_address(device->dev); |
| |
| mdep_data = g_new0(struct get_mdep_data, 1); |
| mdep_data->app = hdp_application_ref(app); |
| mdep_data->func = func; |
| mdep_data->data = data; |
| mdep_data->destroy = destroy; |
| |
| bt_string2uuid(&uuid, HDP_UUID); |
| if (bt_search_service(src, dst, &uuid, get_mdep_cb, mdep_data, |
| free_mdep_data, 0) < 0) { |
| g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR, |
| "Can't get remote SDP record"); |
| g_free(mdep_data); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static gboolean get_prot_desc_entry(sdp_data_t *entry, int type, guint16 *val) |
| { |
| sdp_data_t *iter; |
| int proto; |
| |
| if (entry == NULL || !SDP_IS_SEQ(entry->dtd)) |
| return FALSE; |
| |
| iter = entry->val.dataseq; |
| if (!(iter->dtd & SDP_UUID_UNSPEC)) |
| return FALSE; |
| |
| proto = sdp_uuid_to_proto(&iter->val.uuid); |
| if (proto != type) |
| return FALSE; |
| |
| if (val == NULL) |
| return TRUE; |
| |
| iter = iter->next; |
| if (iter->dtd != SDP_UINT16) |
| return FALSE; |
| |
| *val = iter->val.uint16; |
| |
| return TRUE; |
| } |
| |
| static gboolean hdp_get_prot_desc_list(const sdp_record_t *rec, guint16 *psm, |
| guint16 *version) |
| { |
| sdp_data_t *pdl, *p0, *p1; |
| |
| if (psm == NULL && version == NULL) |
| return TRUE; |
| |
| pdl = sdp_data_get(rec, SDP_ATTR_PROTO_DESC_LIST); |
| if (pdl == NULL || !SDP_IS_SEQ(pdl->dtd)) |
| return FALSE; |
| |
| p0 = pdl->val.dataseq; |
| if (!get_prot_desc_entry(p0, L2CAP_UUID, psm)) |
| return FALSE; |
| |
| p1 = p0->next; |
| if (!get_prot_desc_entry(p1, MCAP_CTRL_UUID, version)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| static gboolean hdp_get_add_prot_desc_list(const sdp_record_t *rec, |
| guint16 *psm) |
| { |
| sdp_data_t *pdl, *p0, *p1; |
| |
| if (psm == NULL) |
| return TRUE; |
| |
| pdl = sdp_data_get(rec, SDP_ATTR_ADD_PROTO_DESC_LIST); |
| if (pdl == NULL || pdl->dtd != SDP_SEQ8) |
| return FALSE; |
| pdl = pdl->val.dataseq; |
| if (pdl->dtd != SDP_SEQ8) |
| return FALSE; |
| |
| p0 = pdl->val.dataseq; |
| |
| if (!get_prot_desc_entry(p0, L2CAP_UUID, psm)) |
| return FALSE; |
| p1 = p0->next; |
| if (!get_prot_desc_entry(p1, MCAP_DATA_UUID, NULL)) |
| return FALSE; |
| |
| return TRUE; |
| } |
| |
| static gboolean get_ccpsm(sdp_list_t *recs, uint16_t *ccpsm) |
| { |
| sdp_list_t *l; |
| |
| for (l = recs; l; l = l->next) { |
| sdp_record_t *rec = l->data; |
| |
| if (hdp_get_prot_desc_list(rec, ccpsm, NULL)) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static gboolean get_dcpsm(sdp_list_t *recs, uint16_t *dcpsm) |
| { |
| sdp_list_t *l; |
| |
| for (l = recs; l; l = l->next) { |
| sdp_record_t *rec = l->data; |
| |
| if (hdp_get_add_prot_desc_list(rec, dcpsm)) |
| return TRUE; |
| } |
| |
| return FALSE; |
| } |
| |
| static void con_mcl_data_unref(struct conn_mcl_data *conn_data) |
| { |
| if (conn_data == NULL) |
| return; |
| |
| if (--conn_data->refs > 0) |
| return; |
| |
| if (conn_data->destroy) |
| conn_data->destroy(conn_data->data); |
| |
| health_device_unref(conn_data->dev); |
| g_free(conn_data); |
| } |
| |
| static void destroy_con_mcl_data(gpointer data) |
| { |
| con_mcl_data_unref(data); |
| } |
| |
| static struct conn_mcl_data *con_mcl_data_ref(struct conn_mcl_data *conn_data) |
| { |
| if (conn_data == NULL) |
| return NULL; |
| |
| conn_data->refs++; |
| return conn_data; |
| } |
| |
| static void create_mcl_cb(struct mcap_mcl *mcl, GError *err, gpointer data) |
| { |
| struct conn_mcl_data *conn_data = data; |
| struct hdp_device *device = conn_data->dev; |
| GError *gerr = NULL; |
| |
| if (err != NULL) { |
| conn_data->func(conn_data->data, err); |
| return; |
| } |
| |
| if (device->mcl == NULL) |
| device->mcl = mcap_mcl_ref(mcl); |
| device->mcl_conn = TRUE; |
| |
| hdp_set_mcl_cb(device, &gerr); |
| |
| conn_data->func(conn_data->data, gerr); |
| if (gerr != NULL) |
| g_error_free(gerr); |
| } |
| |
| static void search_cb(sdp_list_t *recs, int err, gpointer user_data) |
| { |
| struct conn_mcl_data *conn_data = user_data; |
| GError *gerr = NULL; |
| uint16_t ccpsm; |
| |
| if (conn_data->dev->hdp_adapter->mi == NULL) { |
| g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, |
| "Mcap instance released"); |
| goto fail; |
| } |
| |
| if (err < 0 || recs == NULL) { |
| g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, |
| "Error getting remote SDP records"); |
| goto fail; |
| } |
| |
| if (!get_ccpsm(recs, &ccpsm)) { |
| g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, |
| "Can't get remote PSM for control channel"); |
| goto fail; |
| } |
| |
| conn_data = con_mcl_data_ref(conn_data); |
| |
| if (!mcap_create_mcl(conn_data->dev->hdp_adapter->mi, |
| device_get_address(conn_data->dev->dev), |
| 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; |
| const bdaddr_t *src; |
| const bdaddr_t *dst; |
| uuid_t uuid; |
| |
| src = btd_adapter_get_address(device_get_adapter(device->dev)); |
| dst = device_get_address(device->dev); |
| |
| conn_data = g_new0(struct conn_mcl_data, 1); |
| conn_data->refs = 1; |
| conn_data->func = func; |
| conn_data->data = data; |
| conn_data->destroy = destroy; |
| conn_data->dev = health_device_ref(device); |
| |
| bt_string2uuid(&uuid, HDP_UUID); |
| if (bt_search_service(src, dst, &uuid, search_cb, conn_data, |
| destroy_con_mcl_data, 0) < 0) { |
| g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR, |
| "Can't get remote SDP record"); |
| g_free(conn_data); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static void get_dcpsm_cb(sdp_list_t *recs, int err, gpointer data) |
| { |
| struct get_dcpsm_data *dcpsm_data = data; |
| GError *gerr = NULL; |
| uint16_t dcpsm; |
| |
| if (err < 0 || recs == NULL) { |
| g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, |
| "Error getting remote SDP records"); |
| goto fail; |
| } |
| |
| if (!get_dcpsm(recs, &dcpsm)) { |
| g_set_error(&gerr, HDP_ERROR, HDP_CONNECTION_ERROR, |
| "Can't get remote PSM for data channel"); |
| goto fail; |
| } |
| |
| dcpsm_data->func(dcpsm, dcpsm_data->data, NULL); |
| return; |
| |
| fail: |
| dcpsm_data->func(0, dcpsm_data->data, gerr); |
| g_error_free(gerr); |
| } |
| |
| static void free_dcpsm_data(gpointer data) |
| { |
| struct get_dcpsm_data *dcpsm_data = data; |
| |
| if (dcpsm_data == NULL) |
| return; |
| |
| if (dcpsm_data->destroy) |
| dcpsm_data->destroy(dcpsm_data->data); |
| |
| g_free(dcpsm_data); |
| } |
| |
| gboolean hdp_get_dcpsm(struct hdp_device *device, hdp_continue_dcpsm_f func, |
| gpointer data, |
| GDestroyNotify destroy, |
| GError **err) |
| { |
| struct get_dcpsm_data *dcpsm_data; |
| const bdaddr_t *src; |
| const bdaddr_t *dst; |
| uuid_t uuid; |
| |
| src = btd_adapter_get_address(device_get_adapter(device->dev)); |
| dst = device_get_address(device->dev); |
| |
| dcpsm_data = g_new0(struct get_dcpsm_data, 1); |
| dcpsm_data->func = func; |
| dcpsm_data->data = data; |
| dcpsm_data->destroy = destroy; |
| |
| bt_string2uuid(&uuid, HDP_UUID); |
| if (bt_search_service(src, dst, &uuid, get_dcpsm_cb, dcpsm_data, |
| free_dcpsm_data, 0) < 0) { |
| g_set_error(err, HDP_ERROR, HDP_CONNECTION_ERROR, |
| "Can't get remote SDP record"); |
| g_free(dcpsm_data); |
| return FALSE; |
| } |
| |
| return TRUE; |
| } |
| |
| static void hdp_free_application(struct hdp_application *app) |
| { |
| if (app->dbus_watcher > 0) |
| g_dbus_remove_watch(btd_get_dbus_connection(), |
| app->dbus_watcher); |
| |
| g_free(app->oname); |
| g_free(app->description); |
| g_free(app->path); |
| g_free(app); |
| } |
| |
| struct hdp_application *hdp_application_ref(struct hdp_application *app) |
| { |
| if (app == NULL) |
| return NULL; |
| |
| app->ref++; |
| |
| DBG("health_application_ref(%p): ref=%d", app, app->ref); |
| return app; |
| } |
| |
| void hdp_application_unref(struct hdp_application *app) |
| { |
| if (app == NULL) |
| return; |
| |
| app->ref--; |
| |
| DBG("health_application_unref(%p): ref=%d", app, app->ref); |
| if (app->ref > 0) |
| return; |
| |
| hdp_free_application(app); |
| } |