Diff between 9b98a6c7646bc0404c602ca0b5383aec4b996455 and 68838679e1d985ce4494206ace6e30b608f745a6

Changed Files

File Additions Deletions Status
Makefile.am +1 -0 modified
audio/gateway.c +1 -1 modified
audio/headset.c +1 -1 modified
health/hdp_util.c +1 -0 modified
serial/port.c +1 -0 modified
src/device.c +1 -0 modified
src/glib-helper.c +0 -335 modified
src/glib-helper.h +0 -8 modified
src/sdp-client.c +369 -0 added
src/sdp-client.h +30 -0 added

Full Patch

diff --git a/Makefile.am b/Makefile.am
index eec458e..06332de 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -301,6 +301,7 @@ src_bluetoothd_SOURCES = $(gdbus_sources) $(builtin_sources) \
 			src/sdpd-service.c src/sdpd-database.c \
 			src/attrib-server.h src/attrib-server.c \
 			src/sdp-xml.h src/sdp-xml.c \
+			src/sdp-client.h src/sdp-client.c \
 			src/textfile.h src/textfile.c src/glib-compat.h \
 			src/glib-helper.h src/glib-helper.c \
 			src/oui.h src/oui.c src/uinput.h src/ppoll.h \
diff --git a/audio/gateway.c b/audio/gateway.c
index fb16a3d..9b1aab3 100644
--- a/audio/gateway.c
+++ b/audio/gateway.c
@@ -43,7 +43,7 @@
 #include <bluetooth/sdp_lib.h>
 
 #include "glib-compat.h"
-#include "glib-helper.h"
+#include "sdp-client.h"
 #include "device.h"
 #include "gateway.h"
 #include "log.h"
diff --git a/audio/headset.c b/audio/headset.c
index e45e216..6aef6a8 100644
--- a/audio/headset.c
+++ b/audio/headset.c
@@ -54,7 +54,7 @@
 #include "telephony.h"
 #include "headset.h"
 #include "glib-compat.h"
-#include "glib-helper.h"
+#include "sdp-client.h"
 #include "btio.h"
 #include "dbus-common.h"
 #include "../src/adapter.h"
diff --git a/health/hdp_util.c b/health/hdp_util.c
index 9c088d4..e62ed46 100644
--- a/health/hdp_util.c
+++ b/health/hdp_util.c
@@ -32,6 +32,7 @@
 
 #include <sdpd.h>
 #include <bluetooth/sdp_lib.h>
+#include <sdp-client.h>
 #include <glib-helper.h>
 
 #include <btio.h>
diff --git a/serial/port.c b/serial/port.c
index 045f459..fa302be 100644
--- a/serial/port.c
+++ b/serial/port.c
@@ -50,6 +50,7 @@
 #include "log.h"
 #include "glib-compat.h"
 #include "glib-helper.h"
+#include "sdp-client.h"
 #include "btio.h"
 
 #include "error.h"
diff --git a/src/device.c b/src/device.c
index 339c001..bb97089 100644
--- a/src/device.c
+++ b/src/device.c
@@ -56,6 +56,7 @@
 #include "error.h"
 #include "glib-compat.h"
 #include "glib-helper.h"
+#include "sdp-client.h"
 #include "gatt.h"
 #include "agent.h"
 #include "sdp-xml.h"
diff --git a/src/glib-helper.c b/src/glib-helper.c
index bb8883e..8189a09 100644
--- a/src/glib-helper.c
+++ b/src/glib-helper.c
@@ -27,9 +27,6 @@
 
 #include <stdlib.h>
 #include <errno.h>
-#include <unistd.h>
-#include <sys/ioctl.h>
-#include <sys/socket.h>
 
 #include <bluetooth/bluetooth.h>
 #include <bluetooth/sdp.h>
@@ -38,341 +35,9 @@
 #include <glib.h>
 
 #include "btio.h"
-#include "sdpd.h"
 #include "glib-compat.h"
 #include "glib-helper.h"
 
-/* Number of seconds to keep a sdp_session_t in the cache */
-#define CACHE_TIMEOUT 2
-
-struct cached_sdp_session {
-	bdaddr_t src;
-	bdaddr_t dst;
-	sdp_session_t *session;
-	guint timer;
-};
-
-static GSList *cached_sdp_sessions = NULL;
-
-static gboolean cached_session_expired(gpointer user_data)
-{
-	struct cached_sdp_session *cached = user_data;
-
-	cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, cached);
-
-	sdp_close(cached->session);
-
-	g_free(cached);
-
-	return FALSE;
-}
-
-static sdp_session_t *get_sdp_session(const bdaddr_t *src, const bdaddr_t *dst)
-{
-	GSList *l;
-
-	for (l = cached_sdp_sessions; l != NULL; l = l->next) {
-		struct cached_sdp_session *c = l->data;
-		sdp_session_t *session;
-
-		if (bacmp(&c->src, src) || bacmp(&c->dst, dst))
-			continue;
-
-		g_source_remove(c->timer);
-
-		session = c->session;
-
-		cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, c);
-		g_free(c);
-
-		return session;
-	}
-
-	return sdp_connect(src, dst, SDP_NON_BLOCKING);
-}
-
-static void cache_sdp_session(bdaddr_t *src, bdaddr_t *dst,
-						sdp_session_t *session)
-{
-	struct cached_sdp_session *cached;
-
-	cached = g_new0(struct cached_sdp_session, 1);
-
-	bacpy(&cached->src, src);
-	bacpy(&cached->dst, dst);
-
-	cached->session = session;
-
-	cached_sdp_sessions = g_slist_append(cached_sdp_sessions, cached);
-
-	cached->timer = g_timeout_add_seconds(CACHE_TIMEOUT,
-						cached_session_expired,
-						cached);
-}
-
-struct search_context {
-	bdaddr_t		src;
-	bdaddr_t		dst;
-	sdp_session_t		*session;
-	bt_callback_t		cb;
-	bt_destroy_t		destroy;
-	gpointer		user_data;
-	uuid_t			uuid;
-	guint			io_id;
-};
-
-static GSList *context_list = NULL;
-
-static void search_context_cleanup(struct search_context *ctxt)
-{
-	context_list = g_slist_remove(context_list, ctxt);
-
-	if (ctxt->destroy)
-		ctxt->destroy(ctxt->user_data);
-
-	g_free(ctxt);
-}
-
-static void search_completed_cb(uint8_t type, uint16_t status,
-			uint8_t *rsp, size_t size, void *user_data)
-{
-	struct search_context *ctxt = user_data;
-	sdp_list_t *recs = NULL;
-	int scanned, seqlen = 0, bytesleft = size;
-	uint8_t dataType;
-	int err = 0;
-
-	if (status || type != SDP_SVC_SEARCH_ATTR_RSP) {
-		err = -EPROTO;
-		goto done;
-	}
-
-	scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen);
-	if (!scanned || !seqlen)
-		goto done;
-
-	rsp += scanned;
-	bytesleft -= scanned;
-	do {
-		sdp_record_t *rec;
-		int recsize;
-
-		recsize = 0;
-		rec = sdp_extract_pdu(rsp, bytesleft, &recsize);
-		if (!rec)
-			break;
-
-		if (!recsize) {
-			sdp_record_free(rec);
-			break;
-		}
-
-		scanned += recsize;
-		rsp += recsize;
-		bytesleft -= recsize;
-
-		recs = sdp_list_append(recs, rec);
-	} while (scanned < (ssize_t) size && bytesleft > 0);
-
-done:
-	cache_sdp_session(&ctxt->src, &ctxt->dst, ctxt->session);
-
-	if (ctxt->cb)
-		ctxt->cb(recs, err, ctxt->user_data);
-
-	if (recs)
-		sdp_list_free(recs, (sdp_free_func_t) sdp_record_free);
-
-	search_context_cleanup(ctxt);
-}
-
-static gboolean search_process_cb(GIOChannel *chan, GIOCondition cond,
-							gpointer user_data)
-{
-	struct search_context *ctxt = user_data;
-	int err = 0;
-
-	if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
-		err = EIO;
-		goto failed;
-	}
-
-	if (sdp_process(ctxt->session) < 0)
-		goto failed;
-
-	return TRUE;
-
-failed:
-	if (err) {
-		sdp_close(ctxt->session);
-		ctxt->session = NULL;
-
-		if (ctxt->cb)
-			ctxt->cb(NULL, err, ctxt->user_data);
-
-		search_context_cleanup(ctxt);
-	}
-
-	return FALSE;
-}
-
-static gboolean connect_watch(GIOChannel *chan, GIOCondition cond,
-							gpointer user_data)
-{
-	struct search_context *ctxt = user_data;
-	sdp_list_t *search, *attrids;
-	uint32_t range = 0x0000ffff;
-	socklen_t len;
-	int sk, err = 0;
-
-	sk = g_io_channel_unix_get_fd(chan);
-	ctxt->io_id = 0;
-
-	len = sizeof(err);
-	if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
-		err = errno;
-		goto failed;
-	}
-
-	if (err != 0)
-		goto failed;
-
-	if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) {
-		err = EIO;
-		goto failed;
-	}
-
-	search = sdp_list_append(NULL, &ctxt->uuid);
-	attrids = sdp_list_append(NULL, &range);
-	if (sdp_service_search_attr_async(ctxt->session,
-				search, SDP_ATTR_REQ_RANGE, attrids) < 0) {
-		sdp_list_free(attrids, NULL);
-		sdp_list_free(search, NULL);
-		err = EIO;
-		goto failed;
-	}
-
-	sdp_list_free(attrids, NULL);
-	sdp_list_free(search, NULL);
-
-	/* Set callback responsible for update the internal SDP transaction */
-	ctxt->io_id = g_io_add_watch(chan,
-				G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-				search_process_cb, ctxt);
-	return FALSE;
-
-failed:
-	sdp_close(ctxt->session);
-	ctxt->session = NULL;
-
-	if (ctxt->cb)
-		ctxt->cb(NULL, -err, ctxt->user_data);
-
-	search_context_cleanup(ctxt);
-
-	return FALSE;
-}
-
-static int create_search_context(struct search_context **ctxt,
-					const bdaddr_t *src,
-					const bdaddr_t *dst,
-					uuid_t *uuid)
-{
-	sdp_session_t *s;
-	GIOChannel *chan;
-
-	if (!ctxt)
-		return -EINVAL;
-
-	s = get_sdp_session(src, dst);
-	if (!s)
-		return -errno;
-
-	*ctxt = g_try_malloc0(sizeof(struct search_context));
-	if (!*ctxt) {
-		sdp_close(s);
-		return -ENOMEM;
-	}
-
-	bacpy(&(*ctxt)->src, src);
-	bacpy(&(*ctxt)->dst, dst);
-	(*ctxt)->session = s;
-	(*ctxt)->uuid = *uuid;
-
-	chan = g_io_channel_unix_new(sdp_get_socket(s));
-	(*ctxt)->io_id = g_io_add_watch(chan,
-				G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
-				connect_watch, *ctxt);
-	g_io_channel_unref(chan);
-
-	return 0;
-}
-
-int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst,
-			uuid_t *uuid, bt_callback_t cb, void *user_data,
-			bt_destroy_t destroy)
-{
-	struct search_context *ctxt = NULL;
-	int err;
-
-	if (!cb)
-		return -EINVAL;
-
-	err = create_search_context(&ctxt, src, dst, uuid);
-	if (err < 0)
-		return err;
-
-	ctxt->cb	= cb;
-	ctxt->destroy	= destroy;
-	ctxt->user_data	= user_data;
-
-	context_list = g_slist_append(context_list, ctxt);
-
-	return 0;
-}
-
-static gint find_by_bdaddr(gconstpointer data, gconstpointer user_data)
-{
-	const struct search_context *ctxt = data, *search = user_data;
-	int ret;
-
-	ret = bacmp(&ctxt->src, &search->src);
-	if (ret != 0)
-		return ret;
-
-	return bacmp(&ctxt->dst, &search->dst);
-}
-
-int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst)
-{
-	struct search_context match, *ctxt;
-	GSList *l;
-
-	memset(&match, 0, sizeof(match));
-	bacpy(&match.src, src);
-	bacpy(&match.dst, dst);
-
-	/* Ongoing SDP Discovery */
-	l = g_slist_find_custom(context_list, &match, find_by_bdaddr);
-	if (l == NULL)
-		return -ENOENT;
-
-	ctxt = l->data;
-
-	if (!ctxt->session)
-		return -ENOTCONN;
-
-	if (ctxt->io_id)
-		g_source_remove(ctxt->io_id);
-
-	if (ctxt->session)
-		sdp_close(ctxt->session);
-
-	search_context_cleanup(ctxt);
-
-	return 0;
-}
-
 char *bt_uuid2string(uuid_t *uuid)
 {
 	gchar *str;
diff --git a/src/glib-helper.h b/src/glib-helper.h
index c83f5e2..8836804 100644
--- a/src/glib-helper.h
+++ b/src/glib-helper.h
@@ -21,14 +21,6 @@
  *
  */
 
-typedef void (*bt_callback_t) (sdp_list_t *recs, int err, gpointer user_data);
-typedef void (*bt_destroy_t) (gpointer user_data);
-
-int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst,
-			uuid_t *uuid, bt_callback_t cb, void *user_data,
-			bt_destroy_t destroy);
-int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst);
-
 gchar *bt_uuid2string(uuid_t *uuid);
 char *bt_name2string(const char *string);
 int bt_string2uuid(uuid_t *uuid, const char *string);
diff --git a/src/sdp-client.c b/src/sdp-client.c
new file mode 100644
index 0000000..ebd760e
--- /dev/null
+++ b/src/sdp-client.c
@@ -0,0 +1,369 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2011  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 <errno.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+
+#include <glib.h>
+
+#include "btio.h"
+#include "sdp-client.h"
+
+/* Number of seconds to keep a sdp_session_t in the cache */
+#define CACHE_TIMEOUT 2
+
+struct cached_sdp_session {
+	bdaddr_t src;
+	bdaddr_t dst;
+	sdp_session_t *session;
+	guint timer;
+};
+
+static GSList *cached_sdp_sessions = NULL;
+
+static gboolean cached_session_expired(gpointer user_data)
+{
+	struct cached_sdp_session *cached = user_data;
+
+	cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, cached);
+
+	sdp_close(cached->session);
+
+	g_free(cached);
+
+	return FALSE;
+}
+
+static sdp_session_t *get_sdp_session(const bdaddr_t *src, const bdaddr_t *dst)
+{
+	GSList *l;
+
+	for (l = cached_sdp_sessions; l != NULL; l = l->next) {
+		struct cached_sdp_session *c = l->data;
+		sdp_session_t *session;
+
+		if (bacmp(&c->src, src) || bacmp(&c->dst, dst))
+			continue;
+
+		g_source_remove(c->timer);
+
+		session = c->session;
+
+		cached_sdp_sessions = g_slist_remove(cached_sdp_sessions, c);
+		g_free(c);
+
+		return session;
+	}
+
+	return sdp_connect(src, dst, SDP_NON_BLOCKING);
+}
+
+static void cache_sdp_session(bdaddr_t *src, bdaddr_t *dst,
+						sdp_session_t *session)
+{
+	struct cached_sdp_session *cached;
+
+	cached = g_new0(struct cached_sdp_session, 1);
+
+	bacpy(&cached->src, src);
+	bacpy(&cached->dst, dst);
+
+	cached->session = session;
+
+	cached_sdp_sessions = g_slist_append(cached_sdp_sessions, cached);
+
+	cached->timer = g_timeout_add_seconds(CACHE_TIMEOUT,
+						cached_session_expired,
+						cached);
+}
+
+struct search_context {
+	bdaddr_t		src;
+	bdaddr_t		dst;
+	sdp_session_t		*session;
+	bt_callback_t		cb;
+	bt_destroy_t		destroy;
+	gpointer		user_data;
+	uuid_t			uuid;
+	guint			io_id;
+};
+
+static GSList *context_list = NULL;
+
+static void search_context_cleanup(struct search_context *ctxt)
+{
+	context_list = g_slist_remove(context_list, ctxt);
+
+	if (ctxt->destroy)
+		ctxt->destroy(ctxt->user_data);
+
+	g_free(ctxt);
+}
+
+static void search_completed_cb(uint8_t type, uint16_t status,
+			uint8_t *rsp, size_t size, void *user_data)
+{
+	struct search_context *ctxt = user_data;
+	sdp_list_t *recs = NULL;
+	int scanned, seqlen = 0, bytesleft = size;
+	uint8_t dataType;
+	int err = 0;
+
+	if (status || type != SDP_SVC_SEARCH_ATTR_RSP) {
+		err = -EPROTO;
+		goto done;
+	}
+
+	scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen);
+	if (!scanned || !seqlen)
+		goto done;
+
+	rsp += scanned;
+	bytesleft -= scanned;
+	do {
+		sdp_record_t *rec;
+		int recsize;
+
+		recsize = 0;
+		rec = sdp_extract_pdu(rsp, bytesleft, &recsize);
+		if (!rec)
+			break;
+
+		if (!recsize) {
+			sdp_record_free(rec);
+			break;
+		}
+
+		scanned += recsize;
+		rsp += recsize;
+		bytesleft -= recsize;
+
+		recs = sdp_list_append(recs, rec);
+	} while (scanned < (ssize_t) size && bytesleft > 0);
+
+done:
+	cache_sdp_session(&ctxt->src, &ctxt->dst, ctxt->session);
+
+	if (ctxt->cb)
+		ctxt->cb(recs, err, ctxt->user_data);
+
+	if (recs)
+		sdp_list_free(recs, (sdp_free_func_t) sdp_record_free);
+
+	search_context_cleanup(ctxt);
+}
+
+static gboolean search_process_cb(GIOChannel *chan, GIOCondition cond,
+							gpointer user_data)
+{
+	struct search_context *ctxt = user_data;
+	int err = 0;
+
+	if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL)) {
+		err = EIO;
+		goto failed;
+	}
+
+	if (sdp_process(ctxt->session) < 0)
+		goto failed;
+
+	return TRUE;
+
+failed:
+	if (err) {
+		sdp_close(ctxt->session);
+		ctxt->session = NULL;
+
+		if (ctxt->cb)
+			ctxt->cb(NULL, err, ctxt->user_data);
+
+		search_context_cleanup(ctxt);
+	}
+
+	return FALSE;
+}
+
+static gboolean connect_watch(GIOChannel *chan, GIOCondition cond,
+							gpointer user_data)
+{
+	struct search_context *ctxt = user_data;
+	sdp_list_t *search, *attrids;
+	uint32_t range = 0x0000ffff;
+	socklen_t len;
+	int sk, err = 0;
+
+	sk = g_io_channel_unix_get_fd(chan);
+	ctxt->io_id = 0;
+
+	len = sizeof(err);
+	if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) {
+		err = errno;
+		goto failed;
+	}
+
+	if (err != 0)
+		goto failed;
+
+	if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) {
+		err = EIO;
+		goto failed;
+	}
+
+	search = sdp_list_append(NULL, &ctxt->uuid);
+	attrids = sdp_list_append(NULL, &range);
+	if (sdp_service_search_attr_async(ctxt->session,
+				search, SDP_ATTR_REQ_RANGE, attrids) < 0) {
+		sdp_list_free(attrids, NULL);
+		sdp_list_free(search, NULL);
+		err = EIO;
+		goto failed;
+	}
+
+	sdp_list_free(attrids, NULL);
+	sdp_list_free(search, NULL);
+
+	/* Set callback responsible for update the internal SDP transaction */
+	ctxt->io_id = g_io_add_watch(chan,
+				G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+				search_process_cb, ctxt);
+	return FALSE;
+
+failed:
+	sdp_close(ctxt->session);
+	ctxt->session = NULL;
+
+	if (ctxt->cb)
+		ctxt->cb(NULL, -err, ctxt->user_data);
+
+	search_context_cleanup(ctxt);
+
+	return FALSE;
+}
+
+static int create_search_context(struct search_context **ctxt,
+					const bdaddr_t *src,
+					const bdaddr_t *dst,
+					uuid_t *uuid)
+{
+	sdp_session_t *s;
+	GIOChannel *chan;
+
+	if (!ctxt)
+		return -EINVAL;
+
+	s = get_sdp_session(src, dst);
+	if (!s)
+		return -errno;
+
+	*ctxt = g_try_malloc0(sizeof(struct search_context));
+	if (!*ctxt) {
+		sdp_close(s);
+		return -ENOMEM;
+	}
+
+	bacpy(&(*ctxt)->src, src);
+	bacpy(&(*ctxt)->dst, dst);
+	(*ctxt)->session = s;
+	(*ctxt)->uuid = *uuid;
+
+	chan = g_io_channel_unix_new(sdp_get_socket(s));
+	(*ctxt)->io_id = g_io_add_watch(chan,
+				G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
+				connect_watch, *ctxt);
+	g_io_channel_unref(chan);
+
+	return 0;
+}
+
+int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst,
+			uuid_t *uuid, bt_callback_t cb, void *user_data,
+			bt_destroy_t destroy)
+{
+	struct search_context *ctxt = NULL;
+	int err;
+
+	if (!cb)
+		return -EINVAL;
+
+	err = create_search_context(&ctxt, src, dst, uuid);
+	if (err < 0)
+		return err;
+
+	ctxt->cb	= cb;
+	ctxt->destroy	= destroy;
+	ctxt->user_data	= user_data;
+
+	context_list = g_slist_append(context_list, ctxt);
+
+	return 0;
+}
+
+static gint find_by_bdaddr(gconstpointer data, gconstpointer user_data)
+{
+	const struct search_context *ctxt = data, *search = user_data;
+	int ret;
+
+	ret = bacmp(&ctxt->src, &search->src);
+	if (ret != 0)
+		return ret;
+
+	return bacmp(&ctxt->dst, &search->dst);
+}
+
+int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst)
+{
+	struct search_context match, *ctxt;
+	GSList *l;
+
+	memset(&match, 0, sizeof(match));
+	bacpy(&match.src, src);
+	bacpy(&match.dst, dst);
+
+	/* Ongoing SDP Discovery */
+	l = g_slist_find_custom(context_list, &match, find_by_bdaddr);
+	if (l == NULL)
+		return -ENOENT;
+
+	ctxt = l->data;
+
+	if (!ctxt->session)
+		return -ENOTCONN;
+
+	if (ctxt->io_id)
+		g_source_remove(ctxt->io_id);
+
+	if (ctxt->session)
+		sdp_close(ctxt->session);
+
+	search_context_cleanup(ctxt);
+
+	return 0;
+}
+
diff --git a/src/sdp-client.h b/src/sdp-client.h
new file mode 100644
index 0000000..13d9121
--- /dev/null
+++ b/src/sdp-client.h
@@ -0,0 +1,30 @@
+/*
+ *
+ *  BlueZ - Bluetooth protocol stack for Linux
+ *
+ *  Copyright (C) 2004-2010  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
+ *
+ */
+
+typedef void (*bt_callback_t) (sdp_list_t *recs, int err, gpointer user_data);
+typedef void (*bt_destroy_t) (gpointer user_data);
+
+int bt_search_service(const bdaddr_t *src, const bdaddr_t *dst,
+			uuid_t *uuid, bt_callback_t cb, void *user_data,
+			bt_destroy_t destroy);
+int bt_cancel_discovery(const bdaddr_t *src, const bdaddr_t *dst);