summaryrefslogtreecommitdiffstats
path: root/hcid/dbus-sdp.c
diff options
context:
space:
mode:
Diffstat (limited to 'hcid/dbus-sdp.c')
-rw-r--r--hcid/dbus-sdp.c1125
1 files changed, 1125 insertions, 0 deletions
diff --git a/hcid/dbus-sdp.c b/hcid/dbus-sdp.c
new file mode 100644
index 00000000..02f9e31c
--- /dev/null
+++ b/hcid/dbus-sdp.c
@@ -0,0 +1,1125 @@
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2007 Nokia Corporation
+ * Copyright (C) 2004-2008 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ * 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 <stdio.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/hci.h>
+#include <bluetooth/hci_lib.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <netinet/in.h>
+
+#include <glib.h>
+#include <dbus/dbus.h>
+#include <gdbus.h>
+
+#include "hcid.h"
+#include "textfile.h"
+#include "adapter.h"
+#include "dbus-hci.h"
+#include "dbus-common.h"
+#include "dbus-error.h"
+#include "error.h"
+#include "dbus-sdp.h"
+#include "sdp-xml.h"
+#include "glib-helper.h"
+
+#define SESSION_TIMEOUT 2000
+#define DEFAULT_XML_BUF_SIZE 1024
+
+struct transaction_context {
+ char *src;
+ char *dst;
+ DBusConnection *conn;
+ DBusMessage *rq;
+ sdp_session_t *session;
+ GIOChannel *io;
+ guint io_id;
+ uuid_t uuid;
+ GSList *identifiers;
+};
+
+typedef int connect_cb_t(struct transaction_context *t);
+
+struct pending_connect {
+ DBusConnection *conn;
+ DBusMessage *rq;
+ char *src;
+ char *dst;
+ sdp_session_t *session;
+ connect_cb_t *conn_cb;
+};
+
+struct cached_session {
+ sdp_session_t *session;
+ guint timeout_id;
+ guint io_id;
+};
+
+static GSList *cached_sessions = NULL;
+
+static inline DBusMessage *invalid_args(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InvalidArguments",
+ "Invalid arguments in method call");
+}
+
+static inline DBusMessage *in_progress(DBusMessage *msg, const char *str)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".InProgress", str);
+}
+
+static inline DBusMessage *adapter_not_ready(DBusMessage *msg)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".NotReady",
+ "Adapter is not ready");
+}
+
+static inline DBusMessage *failed_strerror(DBusMessage *msg, int err)
+{
+ return g_dbus_create_error(msg, ERROR_INTERFACE ".Failed",
+ strerror(err));
+}
+
+static gboolean session_timeout(gpointer user_data)
+{
+ struct cached_session *s = user_data;
+
+ debug("sdp session timed out. closing");
+
+ cached_sessions = g_slist_remove(cached_sessions, s);
+
+ g_source_remove(s->io_id);
+ sdp_close(s->session);
+ g_free(s);
+
+ return FALSE;
+}
+
+gboolean idle_callback(GIOChannel *io, GIOCondition cond, gpointer user_data)
+{
+ struct cached_session *s = user_data;
+
+ if (cond & G_IO_NVAL)
+ return FALSE;
+
+ if (cond & (G_IO_ERR | G_IO_HUP))
+ debug("idle_callback: session got disconnected");
+
+ if (cond & G_IO_IN)
+ debug("got unexpected input on idle SDP socket");
+
+ cached_sessions = g_slist_remove(cached_sessions, s);
+
+ g_source_remove(s->timeout_id);
+ sdp_close(s->session);
+ g_free(s);
+
+ return FALSE;
+}
+
+static void cache_sdp_session(sdp_session_t *sess, GIOChannel *io)
+{
+ struct cached_session *s;
+
+ s = g_new0(struct cached_session, 1);
+
+ s->session = sess;
+ s->timeout_id = g_timeout_add(SESSION_TIMEOUT, session_timeout, s);
+ s->io_id = g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ idle_callback, s);
+
+ cached_sessions = g_slist_append(cached_sessions, s);
+
+ debug("sdp session added to cache");
+}
+
+static int get_bdaddrs(int sock, bdaddr_t *sba, bdaddr_t *dba)
+{
+ struct sockaddr_l2 a;
+ socklen_t len;
+
+ len = sizeof(a);
+ if (getsockname(sock, (struct sockaddr *) &a, &len) < 0) {
+ error("getsockname: %s (%d)", strerror(errno), errno);
+ return -1;
+ }
+
+ bacpy(sba, &a.l2_bdaddr);
+
+ len = sizeof(a);
+ if (getpeername(sock, (struct sockaddr *) &a, &len) < 0) {
+ error("getpeername: %s (%d)", strerror(errno), errno);
+ return -1;
+ }
+
+ bacpy(dba, &a.l2_bdaddr);
+
+ return 0;
+}
+
+static struct cached_session *get_cached_session(bdaddr_t *src, bdaddr_t *dst)
+{
+ GSList *l;
+
+ for (l = cached_sessions; l != NULL; l = l->next) {
+ struct cached_session *s = l->data;
+ int sock = sdp_get_socket(s->session);
+ bdaddr_t sba, dba;
+
+ if (get_bdaddrs(sock, &sba, &dba) < 0)
+ continue;
+
+ if (bacmp(&sba, src) || bacmp(&dba, dst))
+ continue;
+
+ debug("found matching session, removing from list");
+
+ cached_sessions = g_slist_remove(cached_sessions, s);
+
+ return s;
+ }
+
+ return NULL;
+}
+
+static sdp_session_t *get_sdp_session(bdaddr_t *src, bdaddr_t *dst)
+{
+ struct cached_session *s;
+ sdp_session_t *session;
+
+ s = get_cached_session(src, dst);
+ if (!s) {
+ debug("no matching session found. creating a new one");
+ return sdp_connect(src, dst, SDP_NON_BLOCKING);
+ }
+
+ session = s->session;
+
+ g_source_remove(s->timeout_id);
+ g_source_remove(s->io_id);
+ g_free(s);
+
+ return session;
+}
+
+void append_and_grow_string(void *data, const char *str)
+{
+ sdp_buf_t *buff = data;
+ int len;
+
+ len = strlen(str);
+
+ if (!buff->data) {
+ buff->data = malloc(DEFAULT_XML_BUF_SIZE);
+ if (!buff->data)
+ return;
+ buff->buf_size = DEFAULT_XML_BUF_SIZE;
+ }
+
+ /* Grow string */
+ while (buff->buf_size < (buff->data_size + len + 1)) {
+ void *tmp;
+ uint32_t new_size;
+
+ /* Grow buffer by a factor of 2 */
+ new_size = (buff->buf_size << 1);
+
+ tmp = realloc(buff->data, new_size);
+ if (!tmp)
+ return;
+
+ buff->data = tmp;
+ buff->buf_size = new_size;
+ }
+
+ /* Include the NULL character */
+ memcpy(buff->data + buff->data_size, str, len + 1);
+ buff->data_size += len;
+}
+
+/* list of remote and local service records */
+static GSList *pending_connects = NULL;
+
+static struct pending_connect *pending_connect_new(DBusConnection *conn,
+ DBusMessage *msg, const char *src,
+ const char *dst, connect_cb_t *cb)
+{
+ struct pending_connect *c;
+
+ if (!dst)
+ return NULL;
+
+ c = g_new0(struct pending_connect, 1);
+ c->src = g_strdup(src);
+ c->dst = g_strdup(dst);
+ c->conn = dbus_connection_ref(conn);
+ c->rq = dbus_message_ref(msg);
+ c->conn_cb = cb;
+
+ return c;
+}
+
+static void pending_connect_free(struct pending_connect *c)
+{
+ if (!c)
+ return;
+
+ g_free(c->src);
+ g_free(c->dst);
+
+ if (c->rq)
+ dbus_message_unref(c->rq);
+
+ if (c->conn)
+ dbus_connection_unref(c->conn);
+
+ g_free(c);
+}
+
+static struct pending_connect *find_pending_connect(const char *dst)
+{
+ GSList *l;
+
+ for (l = pending_connects; l != NULL; l = l->next) {
+ struct pending_connect *pending = l->data;
+ if (!strcmp(dst, pending->dst))
+ return pending;
+ }
+
+ return NULL;
+}
+
+static int sdp_store_record(const char *src, const char *dst, uint32_t handle, uint8_t *buf, size_t size)
+{
+ char filename[PATH_MAX + 1], key[28], *value;
+ int i, err;
+
+ create_name(filename, PATH_MAX, STORAGEDIR, src, "sdp");
+
+ create_file(filename, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
+
+ snprintf(key, sizeof(key), "%17s#%08X", dst, handle);
+
+ value = g_malloc0(size * 2 + 1);
+
+ for (i = 0; i < size; i++)
+ sprintf(value + (i * 2), "%02X", buf[i]);
+
+ err = textfile_put(filename, key, value);
+
+ g_free(value);
+
+ return err;
+}
+
+static void transaction_context_free(void *udata, gboolean cache)
+{
+ struct transaction_context *ctxt = udata;
+
+ if (!ctxt)
+ return;
+
+ g_free(ctxt->src);
+ g_free(ctxt->dst);
+
+ if (ctxt->conn)
+ dbus_connection_unref(ctxt->conn);
+
+ if (ctxt->rq)
+ dbus_message_unref(ctxt->rq);
+
+ if (ctxt->session && !ctxt->io)
+ sdp_close(ctxt->session);
+
+ if (ctxt->session && ctxt->io) {
+ g_source_remove(ctxt->io_id);
+
+ if (cache)
+ cache_sdp_session(ctxt->session, ctxt->io);
+ else
+ sdp_close(ctxt->session);
+
+ g_io_channel_unref(ctxt->io);
+ }
+
+ if (ctxt->identifiers) {
+ g_slist_foreach(ctxt->identifiers, (GFunc) g_free, NULL);
+ g_slist_free(ctxt->identifiers);
+ }
+
+ g_free(ctxt);
+}
+
+static gboolean search_process_cb(GIOChannel *chan,
+ GIOCondition cond, void *udata)
+{
+ struct transaction_context *ctxt = udata;
+ int err = 0;
+
+ if (cond & G_IO_NVAL) {
+ g_io_channel_unref(chan);
+ return FALSE;
+ }
+
+ if (cond & (G_IO_ERR | G_IO_HUP)) {
+ err = EIO;
+ goto failed;
+ }
+
+ if (sdp_process(ctxt->session) < 0)
+ goto failed;
+
+ return TRUE;
+
+failed:
+ if (err) {
+ error_failed_errno(ctxt->conn, ctxt->rq, err);
+ transaction_context_free(ctxt, FALSE);
+ }
+
+ return TRUE;
+}
+
+static void remote_svc_rec_completed_cb(uint8_t type, uint16_t err,
+ uint8_t *rsp, size_t size, void *udata)
+{
+ struct transaction_context *ctxt = udata;
+ sdp_record_t *rec;
+ DBusMessage *reply;
+ DBusMessageIter iter, array_iter;
+ int scanned;
+
+ if (!ctxt)
+ return;
+
+ if (err == 0xffff) {
+ /* Check for protocol error or I/O error */
+ int sdp_err = sdp_get_error(ctxt->session);
+ if (sdp_err < 0) {
+ error("search failed: Invalid session!");
+ error_failed_errno(ctxt->conn, ctxt->rq, EINVAL);
+ goto failed;
+ }
+
+ error("search failed: %s (%d)", strerror(sdp_err), sdp_err);
+ error_failed_errno(ctxt->conn, ctxt->rq, sdp_err);
+ goto failed;
+ }
+
+ if (type == SDP_ERROR_RSP) {
+ error_sdp_failed(ctxt->conn, ctxt->rq, err);
+ goto failed;
+ }
+
+ /* check response PDU ID */
+ if (type != SDP_SVC_ATTR_RSP) {
+ error("SDP error: %s (%d)", strerror(EPROTO), EPROTO);
+ error_failed_errno(ctxt->conn, ctxt->rq, EPROTO);
+ goto failed;
+ }
+
+ reply = dbus_message_new_method_return(ctxt->rq);
+ dbus_message_iter_init_append(reply, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_BYTE_AS_STRING, &array_iter);
+
+ rec = sdp_extract_pdu_safe(rsp, size, &scanned);
+ if (rec == NULL || size != scanned) {
+ error("Invalid service record!");
+ goto done;
+ }
+
+ sdp_store_record(ctxt->src, ctxt->dst, rec->handle, rsp, size);
+
+ sdp_record_free(rec);
+
+ dbus_message_iter_append_fixed_array(&array_iter,
+ DBUS_TYPE_BYTE, &rsp, size);
+
+done:
+ dbus_message_iter_close_container(&iter, &array_iter);
+ dbus_connection_send(ctxt->conn, reply, NULL);
+ dbus_message_unref(reply);
+
+failed:
+ transaction_context_free(ctxt, TRUE);
+}
+
+static void remote_svc_rec_completed_xml_cb(uint8_t type, uint16_t err,
+ uint8_t *rsp, size_t size,
+ void *udata)
+{
+ struct transaction_context *ctxt = udata;
+ sdp_record_t *rec;
+ DBusMessage *reply;
+ int scanned;
+ sdp_buf_t result;
+
+ if (!ctxt)
+ return;
+
+ if (err == 0xffff) {
+ /* Check for protocol error or I/O error */
+ int sdp_err = sdp_get_error(ctxt->session);
+ if (sdp_err < 0) {
+ error("search failed: Invalid session!");
+ error_failed_errno(ctxt->conn, ctxt->rq, EINVAL);
+ goto failed;
+ }
+
+ error("search failed: %s (%d)", strerror(sdp_err), sdp_err);
+ error_failed_errno(ctxt->conn, ctxt->rq, sdp_err);
+ goto failed;
+ }
+
+ if (type == SDP_ERROR_RSP) {
+ error_sdp_failed(ctxt->conn, ctxt->rq, err);
+ goto failed;
+ }
+
+ /* check response PDU ID */
+ if (type != SDP_SVC_ATTR_RSP) {
+ error("SDP error: %s (%d)", strerror(EPROTO), EPROTO);
+ error_failed_errno(ctxt->conn, ctxt->rq, EPROTO);
+ goto failed;
+ }
+
+ reply = dbus_message_new_method_return(ctxt->rq);
+
+ rec = sdp_extract_pdu_safe(rsp, size, &scanned);
+ if (rec == NULL || size != scanned) {
+ error("Invalid service record!");
+ goto done;
+ }
+
+ sdp_store_record(ctxt->src, ctxt->dst, rec->handle, rsp, size);
+
+ memset(&result, 0, sizeof(sdp_buf_t));
+
+ convert_sdp_record_to_xml(rec, &result, append_and_grow_string);
+
+ sdp_record_free(rec);
+
+ if (result.data) {
+ dbus_message_append_args(reply,
+ DBUS_TYPE_STRING, &result.data,
+ DBUS_TYPE_INVALID);
+
+ free(result.data);
+ }
+done:
+ dbus_connection_send(ctxt->conn, reply, NULL);
+ dbus_message_unref(reply);
+
+failed:
+ transaction_context_free(ctxt, TRUE);
+}
+
+static void remote_svc_handles_completed_cb(uint8_t type, uint16_t err,
+ uint8_t *rsp, size_t size, void *udata)
+{
+ struct transaction_context *ctxt = udata;
+ DBusMessage *reply;
+ DBusMessageIter iter, array_iter;
+ uint8_t *pdata;
+ int csrc, tsrc;
+
+ if (!ctxt)
+ return;
+
+ if (err == 0xffff) {
+ /* Check for protocol error or I/O error */
+ int sdp_err = sdp_get_error(ctxt->session);
+ if (sdp_err < 0) {
+ error("search failed: Invalid session!");
+ error_failed_errno(ctxt->conn, ctxt->rq, EINVAL);
+ goto failed;
+ }
+
+ error("search failed: %s (%d)", strerror(sdp_err), sdp_err);
+ error_failed_errno(ctxt->conn, ctxt->rq, sdp_err);
+ goto failed;
+ }
+
+ if (type == SDP_ERROR_RSP) {
+ error_sdp_failed(ctxt->conn, ctxt->rq, err);
+ goto failed;
+ }
+
+ /* check response PDU ID */
+ if (type != SDP_SVC_SEARCH_RSP) {
+ error("SDP error: %s (%d)", strerror(EPROTO), EPROTO);
+ error_failed_errno(ctxt->conn, ctxt->rq, EPROTO);
+ goto failed;
+ }
+
+ reply = dbus_message_new_method_return(ctxt->rq);
+ dbus_message_iter_init_append(reply, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_UINT32_AS_STRING, &array_iter);
+
+ pdata = rsp;
+
+ tsrc = ntohs(bt_get_unaligned((uint16_t *) pdata));
+ if (tsrc <= 0)
+ goto done;
+
+ pdata += sizeof(uint16_t);
+
+ csrc = ntohs(bt_get_unaligned((uint16_t *) pdata));
+ if (csrc <= 0)
+ goto done;
+
+ pdata += sizeof(uint16_t);
+
+ do {
+ uint32_t handle = ntohl(bt_get_unaligned((uint32_t*)pdata));
+ pdata += sizeof(uint32_t);
+
+ dbus_message_iter_append_basic(&array_iter,
+ DBUS_TYPE_UINT32, &handle);
+ } while (--tsrc);
+
+
+done:
+ dbus_message_iter_close_container(&iter, &array_iter);
+ dbus_connection_send(ctxt->conn, reply, NULL);
+ dbus_message_unref(reply);
+
+failed:
+ transaction_context_free(ctxt, TRUE);
+}
+
+static const char *extract_service_class(sdp_data_t *d)
+{
+ sdp_data_t *seq;
+ uuid_t *uuid;
+ static char uuid_str[37];
+
+ /* Expected sequence of UUID16 */
+ if (d->attrId != SDP_ATTR_SVCLASS_ID_LIST)
+ return NULL;
+
+ if (d->dtd != SDP_SEQ8 && d->dtd != SDP_SEQ16 && d->dtd != SDP_SEQ32)
+ return NULL;
+
+ if (!d->val.dataseq)
+ return NULL;
+
+ seq = d->val.dataseq;
+ if (!SDP_IS_UUID(seq->dtd))
+ return NULL;
+
+ uuid = &seq->val.uuid;
+ if (uuid->type != SDP_UUID16)
+ return NULL;
+
+ sprintf(uuid_str, "0000%04x-0000-1000-8000-00805f9b34fb",
+ uuid->value.uuid16);
+
+ return uuid_str;
+}
+
+static int service_search_attr(struct transaction_context *ctxt, uint16_t uuid)
+{
+ sdp_list_t *attrids, *search;
+ uint32_t range = 0x0000ffff;
+ int ret = 0;
+
+ sdp_uuid16_create(&ctxt->uuid, uuid);
+
+ search = sdp_list_append(0, &ctxt->uuid);
+ attrids = sdp_list_append(NULL, &range);
+
+ /*
+ * Create/send the search request and set the
+ * callback to indicate the request completion
+ */
+ if (sdp_service_search_attr_async(ctxt->session, search,
+ SDP_ATTR_REQ_RANGE, attrids) < 0)
+ ret = -sdp_get_error(ctxt->session);
+
+ sdp_list_free(search, NULL);
+ sdp_list_free(attrids, NULL);
+
+ return ret;
+}
+
+static void remote_svc_identifiers_completed_cb(uint8_t type, uint16_t err,
+ uint8_t *rsp, size_t size, void *udata)
+{
+ struct transaction_context *ctxt = udata;
+ const char *puuid;
+ const char *devid_uuid = "00001200-0000-1000-8000-00805f9b34fb";
+ char **identifiers;
+ DBusMessage *reply;
+ GSList *l = NULL;
+ int scanned, extracted = 0, len = 0, recsize = 0, bytesleft = size;
+ uint8_t dtd = 0;
+
+ if (!ctxt)
+ return;
+
+ if (err == 0xffff) {
+ /* Check for protocol error or I/O error */
+ int sdp_err = sdp_get_error(ctxt->session);
+ if (sdp_err < 0) {
+ error("search failed: Invalid session!");
+ error_failed_errno(ctxt->conn, ctxt->rq, EINVAL);
+ goto failed;
+ }
+
+ error("search failed: %s (%d)", strerror(sdp_err), sdp_err);
+ error_failed_errno(ctxt->conn, ctxt->rq, sdp_err);
+ goto failed;
+ }
+
+ if (type == SDP_ERROR_RSP) {
+ error_sdp_failed(ctxt->conn, ctxt->rq, err);
+ goto failed;
+ }
+
+ /* Check response PDU ID */
+ if (type != SDP_SVC_SEARCH_ATTR_RSP) {
+ error("SDP error: %s (%d)", strerror(EPROTO), EPROTO);
+ error_failed_errno(ctxt->conn, ctxt->rq, EPROTO);
+ goto failed;
+ }
+
+ scanned = sdp_extract_seqtype_safe(rsp, bytesleft, &dtd, &len);
+ rsp += scanned;
+ bytesleft -= scanned;
+ for (; extracted < len; rsp += recsize, extracted += recsize, bytesleft -= recsize) {
+ sdp_record_t *rec;
+ sdp_data_t *d;
+
+ recsize = 0;
+ rec = sdp_extract_pdu_safe(rsp, bytesleft, &recsize);
+ if (!rec)
+ break;
+
+ sdp_store_record(ctxt->src, ctxt->dst, rec->handle, rsp, recsize);
+
+ d = sdp_data_get(rec, SDP_ATTR_SVCLASS_ID_LIST);
+ if (!d) {
+ sdp_record_free(rec);
+ continue;
+ }
+
+ puuid = extract_service_class(d);
+ sdp_record_free(rec);
+ if (!puuid)
+ continue;
+
+ /* Ignore repeated identifiers */
+ l = g_slist_find_custom(ctxt->identifiers,
+ puuid, (GCompareFunc) strcmp);
+ if (l)
+ continue;
+
+ ctxt->identifiers = g_slist_append(ctxt->identifiers,
+ g_strdup(puuid));
+ }
+
+ /* If public browse response is empty: search for L2CAP */
+ if (!ctxt->identifiers && ctxt->uuid.value.uuid16 == PUBLIC_BROWSE_GROUP)
+ if (service_search_attr(ctxt, L2CAP_UUID) == 0)
+ return; /* Wait the response */
+
+ /* Request DeviceID if it was not returned previously */
+ l = g_slist_find_custom(ctxt->identifiers,
+ devid_uuid, (GCompareFunc) strcmp);
+ if (!l && ctxt->uuid.value.uuid16 != PNP_INFO_SVCLASS_ID)
+ if (service_search_attr(ctxt, PNP_INFO_SVCLASS_ID) == 0)
+ return; /* Wait the response */
+
+ reply = dbus_message_new_method_return(ctxt->rq);
+
+ identifiers = g_new(char *, g_slist_length(ctxt->identifiers));
+
+ for (l = ctxt->identifiers, len = 0; l; l = l->next, len++)
+ identifiers[len] = l->data;
+
+ dbus_message_append_args(reply,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
+ &identifiers, len,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(ctxt->conn, reply, NULL);
+ dbus_message_unref(reply);
+
+ if (len)
+ g_dbus_emit_signal(ctxt->conn,
+ dbus_message_get_path(ctxt->rq),
+ ADAPTER_INTERFACE,
+ "RemoteIdentifiersUpdated",
+ DBUS_TYPE_STRING, &ctxt->dst,
+ DBUS_TYPE_ARRAY, DBUS_TYPE_STRING,
+ &identifiers, len,
+ DBUS_TYPE_INVALID);
+
+ if (identifiers)
+ g_free(identifiers);
+
+failed:
+ transaction_context_free(ctxt, TRUE);
+}
+
+static gboolean sdp_client_connect_cb(GIOChannel *chan,
+ GIOCondition cond, void *udata)
+{
+ struct pending_connect *c = udata;
+ struct transaction_context *ctxt = NULL;
+ int sdp_err, err = 0, sk;
+ socklen_t len;
+
+ sk = g_io_channel_unix_get_fd(chan);
+
+ len = sizeof(err);
+ if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
+ error("getsockopt(): %s (%d)", strerror(errno), errno);
+ err = errno;
+ goto failed;
+ }
+ if (err != 0) {
+ error("connect(): %s (%d)", strerror(err), err);
+ goto failed;
+ }
+
+ ctxt = g_new0(struct transaction_context, 1);
+ ctxt->src = g_strdup(c->src);
+ ctxt->dst = g_strdup(c->dst);
+ ctxt->conn = dbus_connection_ref(c->conn);
+ ctxt->rq = dbus_message_ref(c->rq);
+ ctxt->session = c->session;
+
+ /* set the complete transaction callback and send the search request */
+ sdp_err = c->conn_cb(ctxt);
+ if (sdp_err < 0) {
+ err = -sdp_err;
+ error("search failed: %s (%d)", strerror(err), err);
+ goto failed;
+ }
+
+ /* set the callback responsible for update the transaction data */
+ ctxt->io_id = g_io_add_watch(chan,
+ G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+ search_process_cb, ctxt);
+ ctxt->io = g_io_channel_ref(chan);
+
+ goto done;
+
+failed:
+ error_connection_attempt_failed(c->conn, c->rq, err);
+
+ if (ctxt)
+ transaction_context_free(ctxt, FALSE);
+ else
+ sdp_close(c->session);
+
+done:
+ pending_connects = g_slist_remove(pending_connects, c);
+ pending_connect_free(c);
+
+ return FALSE;
+}
+
+static struct pending_connect *connect_request(DBusConnection *conn,
+ DBusMessage *msg,
+ const char *src,
+ const char *dst,
+ connect_cb_t *cb, int *err)
+{
+ struct pending_connect *c;
+ bdaddr_t srcba, dstba;
+ GIOChannel *chan;
+
+ c = pending_connect_new(conn, msg, src, dst, cb);
+ if (!c) {
+ if (err)
+ *err = ENOMEM;
+ return NULL;
+ }
+
+ str2ba(src, &srcba);
+ str2ba(dst, &dstba);
+ c->session = get_sdp_session(&srcba, &dstba);
+ if (!c->session) {
+ if (err)
+ *err = errno;
+ error("sdp_connect() failed: %s (%d)", strerror(errno), errno);
+ pending_connect_free(c);
+ return NULL;
+ }
+
+ chan = g_io_channel_unix_new(sdp_get_socket(c->session));
+ g_io_add_watch(chan, G_IO_OUT, sdp_client_connect_cb, c);
+ g_io_channel_unref(chan);
+ pending_connects = g_slist_append(pending_connects, c);
+
+ return c;
+}
+
+static int remote_svc_rec_conn_cb(struct transaction_context *ctxt)
+{
+ sdp_list_t *attrids;
+ uint32_t range = 0x0000ffff;
+ const char *dst;
+ uint32_t handle;
+
+ if (sdp_set_notify(ctxt->session, remote_svc_rec_completed_cb, ctxt) < 0)
+ return -EINVAL;
+
+ dbus_message_get_args(ctxt->rq, NULL,
+ DBUS_TYPE_STRING, &dst,
+ DBUS_TYPE_UINT32, &handle,
+ DBUS_TYPE_INVALID);
+
+ attrids = sdp_list_append(NULL, &range);
+ /*
+ * Create/send the search request and set the
+ * callback to indicate the request completion
+ */
+ if (sdp_service_attr_async(ctxt->session, handle,
+ SDP_ATTR_REQ_RANGE, attrids) < 0) {
+ sdp_list_free(attrids, NULL);
+ return -sdp_get_error(ctxt->session);
+ }
+
+ sdp_list_free(attrids, NULL);
+
+ return 0;
+}
+
+static int remote_svc_rec_conn_xml_cb(struct transaction_context *ctxt)
+{
+ sdp_list_t *attrids;
+ uint32_t range = 0x0000ffff;
+ const char *dst;
+ uint32_t handle;
+
+ if (sdp_set_notify(ctxt->session, remote_svc_rec_completed_xml_cb, ctxt) < 0)
+ return -EINVAL;
+
+ dbus_message_get_args(ctxt->rq, NULL,
+ DBUS_TYPE_STRING, &dst,
+ DBUS_TYPE_UINT32, &handle,
+ DBUS_TYPE_INVALID);
+
+ attrids = sdp_list_append(NULL, &range);
+ /*
+ * Create/send the search request and set the
+ * callback to indicate the request completion
+ */
+ if (sdp_service_attr_async(ctxt->session, handle,
+ SDP_ATTR_REQ_RANGE, attrids) < 0) {
+ sdp_list_free(attrids, NULL);
+ return -sdp_get_error(ctxt->session);
+ }
+
+ sdp_list_free(attrids, NULL);
+
+ return 0;
+}
+
+DBusMessage *get_remote_svc_rec(DBusConnection *conn, DBusMessage *msg,
+ void *data, sdp_format_t format)
+{
+ struct adapter *adapter = data;
+ const char *dst;
+ uint32_t handle;
+ int err;
+ connect_cb_t *cb;
+
+ if (!adapter->up)
+ return adapter_not_ready(msg);
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &dst,
+ DBUS_TYPE_UINT32, &handle,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ if (find_pending_connect(dst))
+ return in_progress(msg, "Service search in progress");
+
+ cb = remote_svc_rec_conn_cb;
+ if (format == SDP_FORMAT_XML)
+ cb = remote_svc_rec_conn_xml_cb;
+
+ if (!connect_request(conn, msg, adapter->address,
+ dst, cb, &err)) {
+ error("Search request failed: %s (%d)", strerror(err), err);
+ return failed_strerror(msg, err);
+ }
+
+ return NULL;
+}
+
+static int remote_svc_handles_conn_cb(struct transaction_context *ctxt)
+{
+ sdp_list_t *search = NULL;
+ const char *dst, *svc;
+
+ if (sdp_set_notify(ctxt->session, remote_svc_handles_completed_cb, ctxt) < 0)
+ return -EINVAL;
+
+ dbus_message_get_args(ctxt->rq, NULL,
+ DBUS_TYPE_STRING, &dst,
+ DBUS_TYPE_STRING, &svc,
+ DBUS_TYPE_INVALID);
+
+ if (strlen(svc) > 0)
+ bt_string2uuid(&ctxt->uuid, svc);
+ else
+ sdp_uuid16_create(&ctxt->uuid, PUBLIC_BROWSE_GROUP);
+
+ search = sdp_list_append(0, &ctxt->uuid);
+
+ /* Create/send the search request and set the callback to indicate the request completion */
+ if (sdp_service_search_async(ctxt->session, search, 64) < 0) {
+ error("send request failed: %s (%d)", strerror(errno), errno);
+ sdp_list_free(search, NULL);
+ return -sdp_get_error(ctxt->session);
+ }
+
+ sdp_list_free(search, NULL);
+
+ return 0;
+}
+
+static int remote_svc_identifiers_conn_cb(struct transaction_context *ctxt)
+{
+ if (sdp_set_notify(ctxt->session,
+ remote_svc_identifiers_completed_cb, ctxt) < 0)
+ return -EINVAL;
+
+ return service_search_attr(ctxt, PUBLIC_BROWSE_GROUP);
+}
+
+DBusMessage *get_remote_svc_handles(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter = data;
+ const char *dst, *svc;
+ int err;
+ uuid_t uuid;
+
+ if (!adapter->up)
+ return adapter_not_ready(msg);
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &dst,
+ DBUS_TYPE_STRING, &svc,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ if (strlen(svc) > 0) {
+ /* Check if it is a service name string */
+ if (bt_string2uuid(&uuid, svc) < 0) {
+ error("Invalid service class name");
+ return invalid_args(msg);
+ }
+ }
+
+ if (find_pending_connect(dst))
+ return in_progress(msg, "Service search in progress");
+
+ if (!connect_request(conn, msg, adapter->address,
+ dst, remote_svc_handles_conn_cb, &err)) {
+ error("Search request failed: %s (%d)", strerror(err), err);
+ return failed_strerror(msg, err);
+ }
+
+ return NULL;
+}
+
+DBusMessage *get_remote_svc_identifiers(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct adapter *adapter = data;
+ const char *dst;
+ int err;
+
+ if (!adapter->up)
+ return adapter_not_ready(msg);
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &dst,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ if (find_pending_connect(dst))
+ return in_progress(msg, "Service search in progress");
+
+ if (!connect_request(conn, msg, adapter->address,
+ dst, remote_svc_identifiers_conn_cb, &err)) {
+ error("Search request failed: %s (%d)", strerror(err), err);
+ return failed_strerror(msg, err);
+ }
+
+ return NULL;
+}
+
+DBusMessage *finish_remote_svc_transact(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct cached_session *s;
+ const char *address;
+ struct adapter *adapter = data;
+ bdaddr_t sba, dba;
+
+ if (!dbus_message_get_args(msg, NULL,
+ DBUS_TYPE_STRING, &address,
+ DBUS_TYPE_INVALID))
+ return invalid_args(msg);
+
+ str2ba(adapter->address, &sba);
+ str2ba(address, &dba);
+
+ while ((s = get_cached_session(&sba, &dba))) {
+ sdp_close(s->session);
+ g_source_remove(s->timeout_id);
+ g_source_remove(s->io_id);
+ g_free(s);
+ }
+
+ return dbus_message_new_method_return(msg);
+}