summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarcel Holtmann <marcel@holtmann.org>2004-03-31 20:37:21 +0000
committerMarcel Holtmann <marcel@holtmann.org>2004-03-31 20:37:21 +0000
commitc787259b854f12bd827d1e05ccde53d748d22b30 (patch)
tree2cb4057b869a31329cab5db8700a204588af6cd7
parent5a8fbb23664d5b346164f058c9f57d5b01c0a1bb (diff)
Add sdpd server daemon
-rw-r--r--sdpd/Makefile.am9
-rw-r--r--sdpd/cstate.c96
-rw-r--r--sdpd/main.c471
-rw-r--r--sdpd/request.c849
-rw-r--r--sdpd/sdpd.h75
-rw-r--r--sdpd/service.c241
-rw-r--r--sdpd/servicedb.c209
7 files changed, 1950 insertions, 0 deletions
diff --git a/sdpd/Makefile.am b/sdpd/Makefile.am
new file mode 100644
index 00000000..9536bf04
--- /dev/null
+++ b/sdpd/Makefile.am
@@ -0,0 +1,9 @@
+#
+# $Id$
+#
+
+mandir = $(prefix)/usr/share/man
+
+sbin_PROGRAMS = sdpd
+
+sdpd_SOURCES = main.c request.c service.c cstate.c servicedb.c sdpd.h
diff --git a/sdpd/cstate.c b/sdpd/cstate.c
new file mode 100644
index 00000000..e9b51832
--- /dev/null
+++ b/sdpd/cstate.c
@@ -0,0 +1,96 @@
+/*
+ Service Discovery Protocol (SDP)
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>, Stephen Crane <steve.crane@rococosoft.com>
+
+ Based on original SDP implementation by Nokia Corporation.
+ Copyright (C) 2001,2002 Nokia Corporation.
+ Original author Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM,
+ OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+ RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS,
+ TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ Fixes:
+ Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com>
+*/
+
+/*
+ * $Id$
+ */
+
+#include <malloc.h>
+#include <sys/time.h>
+
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/sdp_internal.h>
+
+#include "sdpd.h"
+
+typedef struct _sdp_cstate_list sdp_cstate_list_t;
+
+struct _sdp_cstate_list {
+ sdp_cstate_list_t *next;
+ long timestamp;
+ sdp_buf_t buf;
+};
+
+static sdp_cstate_list_t *cstates;
+
+// FIXME: should probably remove it when it's found
+sdp_buf_t *sdp_get_cached_rsp(sdp_cont_state_t *cstate)
+{
+ sdp_cstate_list_t *p;
+
+ for (p = cstates; p; p = p->next)
+ if (p->timestamp == cstate->timestamp)
+ return &p->buf;
+ return 0;
+}
+
+long sdp_cstate_alloc_buf(sdp_buf_t *buf)
+{
+ sdp_cstate_list_t *cstate = (sdp_cstate_list_t *)malloc(sizeof(sdp_cstate_list_t));
+ char *data = (char *)malloc(buf->data_size);
+
+ memcpy(data, buf->data, buf->data_size);
+ memset((char *)cstate, 0, sizeof(sdp_cstate_list_t));
+ cstate->buf.data = data;
+ cstate->buf.data_size = buf->data_size;
+ cstate->buf.buf_size = buf->data_size;
+ cstate->timestamp = sdp_get_time();
+ cstate->next = cstates;
+ cstates = cstate;
+ return cstate->timestamp;
+}
+
+/*
+ * A simple function which returns the time of day in
+ * seconds. Used for updating the service db state
+ * attribute of the service record of the SDP server
+ */
+long sdp_get_time()
+{
+ /*
+ * To handle failure in gettimeofday, so an old
+ * value is returned and service does not fail
+ */
+ static struct timeval tm;
+
+ gettimeofday(&tm, NULL);
+ return tm.tv_sec;
+}
diff --git a/sdpd/main.c b/sdpd/main.c
new file mode 100644
index 00000000..fea283f2
--- /dev/null
+++ b/sdpd/main.c
@@ -0,0 +1,471 @@
+/*
+ Service Discovery Protocol (SDP)
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>, Stephen Crane <steve.crane@rococosoft.com>
+
+ Based on original SDP implementation by Nokia Corporation.
+ Copyright (C) 2001,2002 Nokia Corporation.
+ Original author Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM,
+ OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+ RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS,
+ TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ SDP server initialization and connection handling
+
+ Fixes:
+ Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com>
+ Imre Deak <ext-imre.deak@nokia.com>
+*/
+
+/*
+ * $Id$
+ */
+#include <stdlib.h>
+#include <unistd.h>
+#include <stdio.h>
+#include <errno.h>
+#include <signal.h>
+#include <getopt.h>
+#include <sys/un.h>
+#include <sys/select.h>
+#include <sys/stat.h>
+#include <sys/ioctl.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/sdp_internal.h>
+
+#include "sdpd.h"
+
+static int l2cap_sock, unix_sock;
+static fd_set active_fdset;
+static int active_maxfd;
+
+sdp_record_t *server;
+
+/*
+ * List of version numbers supported by the SDP server.
+ * Add to this list when newer versions are supported.
+ */
+static sdp_version_t sdpVnumArray[1] = {
+ {1, 0}
+};
+const int sdpServerVnumEntries = 1;
+
+/*
+ * The service database state is an attribute of the service record
+ * of the SDP server itself. This attribute is guaranteed to
+ * change if any of the contents of the service repository
+ * changes. This function updates the timestamp of value of
+ * the svcDBState attribute
+ * Set the SDP server DB. Simply a timestamp which is the marker
+ * when the DB was modified.
+ */
+void update_db_timestamp(void)
+{
+ uint32_t dbts = sdp_get_time();
+ sdp_data_t *d = sdp_data_alloc(SDP_UINT32, &dbts);
+ sdp_attr_replace(server, SDP_ATTR_SVCDB_STATE, d);
+}
+
+static void add_lang_attr(sdp_record_t *r)
+{
+ sdp_lang_attr_t base_lang;
+ sdp_list_t *langs = 0;
+
+ base_lang.code_ISO639 = (0x65 << 8) | 0x6e;
+ // UTF-8 MIBenum (http://www.iana.org/assignments/character-sets)
+ base_lang.encoding = 106;
+ base_lang.base_offset = SDP_PRIMARY_LANG_BASE;
+ langs = sdp_list_append(0, &base_lang);
+ sdp_set_lang_attr(r, langs);
+ sdp_list_free(langs, 0);
+}
+
+static void register_public_browse_group(void)
+{
+ sdp_list_t *browselist;
+ uuid_t bgscid, pbgid;
+ sdp_data_t *sdpdata;
+ sdp_record_t *browse = sdp_record_alloc();
+
+ browse->handle = (uint32_t)browse;
+ sdp_record_add(browse);
+ sdpdata = sdp_data_alloc(SDP_UINT32, &browse->handle);
+ sdp_attr_add(browse, SDP_ATTR_RECORD_HANDLE, sdpdata);
+
+ add_lang_attr(browse);
+ sdp_set_info_attr(browse, "Public Browse Group Root", "BlueZ", "Root of public browse hierarchy");
+
+ sdp_uuid16_create(&bgscid, BROWSE_GRP_DESC_SVCLASS_ID);
+ browselist = sdp_list_append(0, &bgscid);
+ sdp_set_service_classes(browse, browselist);
+ sdp_list_free(browselist, 0);
+
+ sdp_uuid16_create(&pbgid, PUBLIC_BROWSE_GROUP);
+ sdp_set_group_id(browse, pbgid);
+}
+
+/*
+ * The SDP server must present its own service record to
+ * the service repository. This can be accessed by service
+ * discovery clients. This method constructs a service record
+ * and stores it in the repository
+ */
+static void register_server_service(void)
+{
+ int i;
+ sdp_list_t *classIDList, *browseList;
+ sdp_list_t *access_proto = 0;
+ uuid_t l2cap, classID, browseGroupId, sdpSrvUUID;
+ void **versions, **versionDTDs;
+ uint8_t dtd;
+ uint16_t version, port;
+ sdp_data_t *pData, *port_data, *version_data;
+ sdp_list_t *pd, *seq;
+
+ server = sdp_record_alloc();
+ server->pattern = NULL;
+
+ /* Force the record to be SDP_SERVER_RECORD_HANDLE */
+ server->handle = SDP_SERVER_RECORD_HANDLE;
+
+ sdp_record_add(server);
+ sdp_attr_add(server, SDP_ATTR_RECORD_HANDLE, sdp_data_alloc(SDP_UINT32, &server->handle));
+
+ /*
+ * Add all attributes to service record. (No need to commit since we
+ * are the server and this record is already in the database.)
+ */
+ add_lang_attr(server);
+ sdp_set_info_attr(server, "SDP Server", "BlueZ", "Bluetooth service discovery server");
+
+ sdp_uuid16_create(&classID, SDP_SERVER_SVCLASS_ID);
+ classIDList = sdp_list_append(0, &classID);
+ sdp_set_service_classes(server, classIDList);
+ sdp_list_free(classIDList, 0);
+
+ /*
+ * Set the version numbers supported, these are passed as arguments
+ * to the server on command line. Now defaults to 1.0
+ * Build the version number sequence first
+ */
+ versions = (void **)malloc(sdpServerVnumEntries * sizeof(void *));
+ versionDTDs = (void **)malloc(sdpServerVnumEntries * sizeof(void *));
+ dtd = SDP_UINT16;
+ for (i = 0; i < sdpServerVnumEntries; i++) {
+ uint16_t *version = (uint16_t *)malloc(sizeof(uint16_t));
+ *version = sdpVnumArray[i].major;
+ *version = (*version << 8);
+ *version |= sdpVnumArray[i].minor;
+ versions[i] = version;
+ versionDTDs[i] = &dtd;
+ }
+ pData = sdp_seq_alloc(versionDTDs, versions, sdpServerVnumEntries);
+ for (i = 0; i < sdpServerVnumEntries; i++)
+ free(versions[i]);
+ free(versions);
+ free(versionDTDs);
+ sdp_attr_add(server, SDP_ATTR_VERSION_NUM_LIST, pData);
+
+ sdp_uuid16_create(&sdpSrvUUID, SDP_UUID);
+ sdp_set_service_id(server, sdpSrvUUID);
+
+ sdp_uuid16_create(&l2cap, L2CAP_UUID);
+ pd = sdp_list_append(0, &l2cap);
+ port = SDP_PSM;
+ port_data = sdp_data_alloc(SDP_UINT16, &port);
+ pd = sdp_list_append(pd, port_data);
+ version = 1;
+ version_data = sdp_data_alloc(SDP_UINT16, &version);
+ pd = sdp_list_append(pd, version_data);
+ seq = sdp_list_append(0, pd);
+
+ access_proto = sdp_list_append(0, seq);
+ sdp_set_access_protos(server, access_proto);
+ sdp_list_free(access_proto, free);
+ sdp_data_free(port_data);
+ sdp_data_free(version_data);
+ sdp_list_free(pd, 0);
+
+ sdp_uuid16_create(&browseGroupId, PUBLIC_BROWSE_GROUP);
+ browseList = sdp_list_append(0, &browseGroupId);
+ sdp_set_browse_groups(server, browseList);
+ sdp_list_free(browseList, 0);
+
+ update_db_timestamp();
+}
+
+/*
+ * SDP server initialization on startup includes creating the
+ * l2cap and unix sockets over which discovery and registration clients
+ * access us respectively
+ */
+int init_server(int master)
+{
+ struct sockaddr_l2 l2addr;
+ struct sockaddr_un unaddr;
+
+ /* Register the public browse group root */
+ register_public_browse_group();
+
+ /* Register the SDP server's service record */
+ register_server_service();
+
+ /* Create L2CAP socket */
+ l2cap_sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+ if (l2cap_sock == -1) {
+ SDPERR("opening L2CAP socket: %s", strerror(errno));
+ return -1;
+ }
+
+ l2addr.l2_bdaddr = *BDADDR_ANY;
+ l2addr.l2_family = AF_BLUETOOTH;
+ l2addr.l2_psm = htobs(SDP_PSM);
+ if (0 > bind(l2cap_sock, (struct sockaddr *)&l2addr, sizeof(l2addr))) {
+ SDPERR("binding L2CAP socket: %s", strerror(errno));
+ return -1;
+ }
+
+ if (master) {
+ int opt = L2CAP_LM_MASTER;
+ if (0 > setsockopt(l2cap_sock, SOL_L2CAP, L2CAP_LM, &opt, sizeof(opt))) {
+ SDPERR("setsockopt: %s", strerror(errno));
+ return -1;
+ }
+ }
+ listen(l2cap_sock, 5);
+ FD_SET(l2cap_sock, &active_fdset);
+ active_maxfd = l2cap_sock;
+
+ /* Create local Unix socket */
+ unix_sock = socket(PF_UNIX, SOCK_STREAM, 0);
+ if (unix_sock == -1) {
+ SDPERR("opening UNIX socket: %s", strerror(errno));
+ return -1;
+ }
+ unaddr.sun_family = AF_UNIX;
+ strcpy(unaddr.sun_path, SDP_UNIX_PATH);
+ unlink(unaddr.sun_path);
+ if (0 > bind(unix_sock, (struct sockaddr *)&unaddr, sizeof(unaddr))) {
+ SDPERR("binding UNIX socket: %s", strerror(errno));
+ return -1;
+ }
+ listen(unix_sock, 5);
+ FD_SET(unix_sock, &active_fdset);
+ active_maxfd = unix_sock;
+ chmod(SDP_UNIX_PATH, S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH);
+ return 0;
+}
+
+void sig_term(int sig)
+{
+ SDPINF("terminating... \n");
+ sdp_svcdb_reset();
+ close(l2cap_sock);
+ close(unix_sock);
+ exit(0);
+}
+
+int become_daemon(void)
+{
+ int fd;
+
+ if (getppid() != 1) {
+ signal(SIGTTOU, SIG_IGN);
+ signal(SIGTTIN, SIG_IGN);
+ signal(SIGTSTP, SIG_IGN);
+ if (fork())
+ return 0;
+ setsid();
+ }
+ for (fd = 0; fd < 3; fd++)
+ close(fd);
+
+ chdir("/");
+ return 1;
+}
+
+static inline void handle_request(int sk, char *data, int len)
+{
+ struct sockaddr_l2 sa;
+ int size;
+ sdp_req_t req;
+
+ size = sizeof(sa);
+ if (getpeername(sk, (struct sockaddr *)&sa, &size) < 0)
+ return;
+
+ if (sa.l2_family == AF_BLUETOOTH) {
+ struct l2cap_options lo;
+ size = sizeof(lo);
+ getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &lo, &size);
+ req.bdaddr = sa.l2_bdaddr;
+ req.mtu = lo.omtu;
+ req.local = 0;
+ } else {
+ req.bdaddr = *BDADDR_LOCAL;
+ req.mtu = 2048;
+ req.local = 1;
+ }
+ req.sock = sk;
+ req.buf = data;
+ req.len = len;
+ process_request(&req);
+}
+
+static void close_sock(int fd, int r)
+{
+ if (r < 0)
+ SDPERR("Read error: %s", strerror(errno));
+ FD_CLR(fd, &active_fdset);
+ close(fd);
+ sdp_svcdb_collect_all(fd);
+ if (fd == active_maxfd)
+ active_maxfd--;
+}
+
+static void check_active(fd_set *mask, int num)
+{
+ sdp_pdu_hdr_t hdr;
+ int size, fd, count, r;
+ char *buf;
+
+ for (fd = 0, count = 0; fd <= active_maxfd && count < num; fd++) {
+ if (fd == l2cap_sock || fd == unix_sock || !FD_ISSET(fd, mask))
+ continue;
+
+ count++;
+
+ r = recv(fd, (void *)&hdr, sizeof(sdp_pdu_hdr_t), MSG_PEEK);
+ if (r <= 0) {
+ close_sock(fd, r);
+ continue;
+ }
+
+ size = sizeof(sdp_pdu_hdr_t) + ntohs(hdr.plen);
+ buf = malloc(size);
+ if (!buf)
+ continue;
+
+ r = recv(fd, buf, size, 0);
+ if (r <= 0)
+ close_sock(fd, r);
+ else
+ handle_request(fd, buf, r);
+ }
+}
+
+void usage(void)
+{
+ printf("sdpd version %s\n", VERSION);
+ printf("Usage:\n"
+ "sdpd [-n] [-m]\n"
+ );
+}
+
+static struct option main_options[] = {
+ {"help", 0,0, 'h'},
+ {"nodaemon", 0,0, 'n'},
+ {"master", 0,0, 'm'},
+ {0, 0, 0, 0}
+};
+
+int main(int argc, char **argv)
+{
+ int daemon = 1;
+ int master = 0;
+ int opt;
+
+ while ((opt = getopt_long(argc, argv, "nm", main_options, NULL)) != -1)
+ switch (opt) {
+ case 'n':
+ daemon = 0;
+ break;
+ case 'm':
+ master = 1;
+ break;
+ default:
+ usage();
+ exit(0);
+ }
+ openlog("sdpd", LOG_PID | LOG_NDELAY, LOG_DAEMON);
+
+ if (daemon && !become_daemon())
+ return 0;
+
+ argc -= optind;
+ argv += optind;
+
+ if (init_server(master) < 0) {
+ SDPERR("Server initialization failed");
+ return -1;
+ }
+
+ SDPINF("sdpd v%s started", VERSION);
+
+ signal(SIGINT, sig_term);
+ signal(SIGTERM, sig_term);
+ signal(SIGABRT, sig_term);
+ signal(SIGQUIT, sig_term);
+ signal(SIGPIPE, SIG_IGN);
+
+ for (;;) {
+ int num, nfd;
+ fd_set mask;
+
+ FD_ZERO(&mask);
+ mask = active_fdset;
+
+ num = select(active_maxfd + 1, &mask, NULL, NULL, NULL);
+ if (num <= 0) {
+ SDPDBG("Select error:%s", strerror(errno));
+ goto exit;
+ }
+
+ if (FD_ISSET(l2cap_sock, &mask)) {
+ /* New L2CAP connection */
+ struct sockaddr_l2 caddr;
+ socklen_t len = sizeof(caddr);
+
+ nfd = accept(l2cap_sock, (struct sockaddr *)&caddr, &len);
+ if (nfd >= 0) {
+ if (nfd > active_maxfd)
+ active_maxfd = nfd;
+ FD_SET(nfd, &active_fdset);
+ }
+ } else if (FD_ISSET(unix_sock, &mask)) {
+ /* New unix connection */
+ struct sockaddr_un caddr;
+ socklen_t len = sizeof(caddr);
+
+ nfd = accept(unix_sock, (struct sockaddr *)&caddr, &len);
+ if (nfd != -1) {
+ if (nfd > active_maxfd)
+ active_maxfd = nfd;
+ FD_SET(nfd, &active_fdset);
+ }
+ } else
+ check_active(&mask, num);
+ }
+exit:
+ sdp_svcdb_reset();
+ return 0;
+}
diff --git a/sdpd/request.c b/sdpd/request.c
new file mode 100644
index 00000000..2354e40e
--- /dev/null
+++ b/sdpd/request.c
@@ -0,0 +1,849 @@
+/*
+ Service Discovery Protocol (SDP)
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>, Stephen Crane <steve.crane@rococosoft.com>
+
+ Based on original SDP implementation by Nokia Corporation.
+ Copyright (C) 2001,2002 Nokia Corporation.
+ Original author Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM,
+ OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+ RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS,
+ TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ SDP request handling code.
+
+ Fixes:
+ Guruprasad Krishnamurthy
+ Dmitry Kasatkin - Continuation mechanism
+*/
+
+/*
+ * $Id$
+ */
+
+#include <malloc.h>
+
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/sdp_internal.h>
+
+#include "sdpd.h"
+
+#define MIN(x, y) ((x) < (y))? (x): (y)
+
+/* Additional values for checking datatype (not in spec) */
+#define SDP_TYPE_UUID 0xfe
+#define SDP_TYPE_ANY 0xff
+
+/*
+ * Generic data element sequence extractor. Builds
+ * a list whose elements are those found in the
+ * sequence. The data type of elements found in the
+ * sequence is returned in the reference pDataType
+ */
+static int extract_des(char *buf, sdp_list_t **svcReqSeq, uint8_t *pDataType, uint8_t expectedType)
+{
+ uint8_t seqType;
+ int data_size = 0;
+ int scanned = sdp_extract_seqtype(buf, &seqType, &data_size);
+ short numberOfElements = 0;
+ int seqlen = 0;
+ sdp_list_t *pSeq = NULL;
+ uint8_t dataType;
+ int status = 0;
+ const char *p;
+
+ SDPDBG("Seq type : %d\n", seqType);
+ if (!scanned || (seqType != SDP_SEQ8 && seqType != SDP_SEQ16)) {
+ SDPERR("Unknown seq type \n");
+ return -1;
+ }
+ p = buf + scanned;
+
+ SDPDBG("Data size : %d\n", data_size);
+ for (;;) {
+ char *pElem = NULL;
+ int localSeqLength = 0;
+
+ dataType = *(uint8_t *)p;
+ SDPDBG("Data type: 0x%02x\n", dataType);
+
+ if (expectedType == SDP_TYPE_UUID) {
+ if (dataType != SDP_UUID16 && dataType != SDP_UUID32 && dataType != SDP_UUID128) {
+ SDPDBG("->Unexpected Data type (expected UUID_ANY)\n");
+ return -1;
+ }
+ } else if (expectedType != SDP_TYPE_ANY && dataType != expectedType) {
+ SDPDBG("->Unexpected Data type (expected 0x%02x)\n", expectedType);
+ return -1;
+ }
+
+ switch (dataType) {
+ case SDP_UINT16:
+ p += sizeof(uint8_t);
+ seqlen += sizeof(uint8_t);
+ pElem = (char *)malloc(sizeof(uint16_t));
+ sdp_put_unaligned(ntohs(sdp_get_unaligned((uint16_t *)p)), (uint16_t *)pElem);
+ p += sizeof(uint16_t);
+ seqlen += sizeof(uint16_t);
+ break;
+ case SDP_UINT32:
+ p += sizeof(uint8_t);
+ seqlen += sizeof(uint8_t);
+ pElem = (char *)malloc(sizeof(uint32_t));
+ sdp_put_unaligned(ntohl(sdp_get_unaligned((uint32_t *)p)), (uint32_t *)pElem);
+ p += sizeof(uint32_t);
+ seqlen += sizeof(uint32_t);
+ break;
+ case SDP_UUID16:
+ case SDP_UUID32:
+ case SDP_UUID128:
+ pElem = (char *)malloc(sizeof(uuid_t));
+ status = sdp_uuid_extract(p, (uuid_t *)pElem, &localSeqLength);
+ if (status == 0) {
+ seqlen += localSeqLength;
+ p += localSeqLength;
+ }
+ break;
+ }
+ if (status == 0) {
+ pSeq = sdp_list_append(pSeq, pElem);
+ numberOfElements++;
+ SDPDBG("No of elements : %d\n", numberOfElements);
+
+ if (seqlen == data_size)
+ break;
+ else if (seqlen > data_size)
+ return -1;
+ } else
+ free(pElem);
+ }
+ *svcReqSeq = pSeq;
+ scanned += seqlen;
+ *pDataType = dataType;
+ return scanned;
+}
+
+static int sdp_set_cstate_pdu(sdp_buf_t *buf, sdp_cont_state_t *cstate)
+{
+ char *pdata = buf->data + buf->data_size;
+ int length = 0;
+
+ if (cstate) {
+ SDPDBG("Non null sdp_cstate_t id : 0x%lx\n", cstate->timestamp);
+ *(uint8_t *)pdata = sizeof(sdp_cont_state_t);
+ pdata += sizeof(uint8_t);
+ length += sizeof(uint8_t);
+ memcpy(pdata, cstate, sizeof(sdp_cont_state_t));
+ length += sizeof(sdp_cont_state_t);
+ } else {
+ // set "null" continuation state
+ *(uint8_t *)pdata = 0;
+ pdata += sizeof(uint8_t);
+ length += sizeof(uint8_t);
+ }
+ buf->data_size += length;
+ return length;
+}
+
+static sdp_cont_state_t *sdp_cstate_get(char *buffer)
+{
+ char *pdata = buffer;
+ uint8_t cStateSize = *(uint8_t *)pdata;
+
+ /*
+ * Check if continuation state exists, if yes attempt
+ * to get response remainder from cache, else send error
+ */
+ SDPDBG("Continuation State size : %d\n", cStateSize);
+
+ pdata += sizeof(uint8_t);
+ if (cStateSize != 0) {
+ sdp_cont_state_t *cstate = (sdp_cont_state_t *)pdata;
+ SDPDBG("Cstate TS : 0x%lx\n", cstate->timestamp);
+ SDPDBG("Bytes sent : %d\n", cstate->cStateValue.maxBytesSent);
+ return cstate;
+ }
+ return NULL;
+}
+
+/*
+ * The matching process is defined as "each and every UUID
+ * specified in the "search pattern" must be present in the
+ * "target pattern". Here "search pattern" is the set of UUIDs
+ * specified by the service discovery client and "target pattern"
+ * is the set of UUIDs present in a service record.
+ *
+ * Return 1 if each and every UUID in the search
+ * pattern exists in the target pattern, 0 if the
+ * match succeeds and -1 on error.
+ */
+static int sdp_match_uuid(sdp_list_t *search, sdp_list_t *pattern)
+{
+ /*
+ * The target is a sorted list, so we need not look
+ * at all elements to confirm existence of an element
+ * from the search pattern
+ */
+ int patlen = sdp_list_len(pattern);
+
+ SDPDBG("");
+
+ if (patlen < sdp_list_len(search))
+ return -1;
+ for (; search; search = search->next) {
+ uuid_t *uuid128;
+ void *data = search->data;
+ sdp_list_t *list;
+ if (data == NULL)
+ return -1;
+
+ // create 128-bit form of the search UUID
+ uuid128 = sdp_uuid_to_uuid128((uuid_t *)data);
+ list = sdp_list_find(pattern, uuid128, sdp_uuid128_cmp);
+ free(uuid128);
+ if (!list)
+ return 0;
+ }
+ return 1;
+}
+
+/*
+ * Service search request PDU. This method extracts the search pattern
+ * (a sequence of UUIDs) and calls the matching function
+ * to find matching services
+ */
+static int service_search_req(sdp_req_t *req, sdp_buf_t *buf)
+{
+ int status = 0, i, plen, mlen;
+ sdp_list_t *pattern = NULL;
+ int expected, actual;
+ uint8_t dtd;
+ sdp_cont_state_t *cstate = NULL;
+ char *pCacheBuffer = NULL;
+ int handleSize = 0;
+ long cStateId = -1;
+ short rsp_count = 0;
+ short *pTotalRecordCount, *pCurrentRecordCount;
+ int mtu;
+ char *pdata = req->buf + sizeof(sdp_pdu_hdr_t);
+ int scanned = extract_des(pdata, &pattern, &dtd, SDP_TYPE_UUID);
+
+ SDPDBG("");
+
+ if (scanned == -1) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+ pdata += scanned;
+
+ plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen);
+ mlen = scanned + sizeof(uint16_t) + 1;
+ // ensure we don't read past buffer
+ if (plen < mlen || plen != mlen + *(uint8_t *)(pdata+sizeof(uint16_t))) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ expected = ntohs(sdp_get_unaligned((uint16_t *)pdata));
+
+ SDPDBG("Expected count: %d\n", expected);
+ SDPDBG("Bytes scanned : %d\n", scanned);
+
+ pdata += sizeof(uint16_t);
+
+ /*
+ * Check if continuation state exists, if yes attempt
+ * to get rsp remainder from cache, else send error
+ */
+ cstate = sdp_cstate_get(pdata);
+
+ mtu = req->mtu - sizeof(sdp_pdu_hdr_t) - sizeof(uint16_t) - sizeof(uint16_t) - SDP_CONT_STATE_SIZE;
+ actual = MIN(expected, mtu >> 2);
+
+ /* make space in the rsp buffer for total and current record counts */
+ pdata = buf->data;
+
+ /* total service record count = 0 */
+ pTotalRecordCount = (short *)pdata;
+ sdp_put_unaligned(0, (uint16_t *)pdata);
+ pdata += sizeof(uint16_t);
+ buf->data_size += sizeof(uint16_t);
+
+ /* current service record count = 0 */
+ pCurrentRecordCount = (short *)pdata;
+ sdp_put_unaligned(0, (uint16_t *)pdata);
+ pdata += sizeof(uint16_t);
+ buf->data_size += sizeof(uint16_t);
+
+ if (cstate == NULL) {
+ /* for every record in the DB, do a pattern search */
+ sdp_list_t *list = sdp_get_record_list();
+
+ handleSize = 0;
+ for (; list && rsp_count < expected; list = list->next) {
+ sdp_record_t *rec = (sdp_record_t *)list->data;
+
+ SDPDBG("Checking svcRec : 0x%x\n", rec->handle);
+
+ if (sdp_match_uuid(pattern, rec->pattern) > 0) {
+ rsp_count++;
+ sdp_put_unaligned(htonl(rec->handle), (uint32_t *)pdata);
+ pdata += sizeof(uint32_t);
+ handleSize += sizeof(uint32_t);
+ }
+ }
+
+ SDPDBG("Match count: %d\n", rsp_count);
+
+ buf->data_size += handleSize;
+ sdp_put_unaligned(htons(rsp_count), (uint16_t *)pTotalRecordCount);
+ sdp_put_unaligned(htons(rsp_count), (uint16_t *)pCurrentRecordCount);
+
+ if (rsp_count > actual) {
+ /* cache the rsp and generate a continuation state */
+ cStateId = sdp_cstate_alloc_buf(buf);
+ /*
+ * subtract handleSize since we now send only
+ * a subset of handles
+ */
+ buf->data_size -= handleSize;
+ } else {
+ /* NULL continuation state */
+ sdp_set_cstate_pdu(buf, NULL);
+ }
+ }
+
+ /* under both the conditions below, the rsp buffer is not built yet */
+ if (cstate || cStateId != -1) {
+ short lastIndex = 0;
+
+ if (cstate) {
+ /*
+ * Get the previous sdp_cont_state_t and obtain
+ * the cached rsp
+ */
+ sdp_buf_t *pCache = sdp_get_cached_rsp(cstate);
+ if (pCache) {
+ pCacheBuffer = pCache->data;
+ /* get the rsp_count from the cached buffer */
+ rsp_count = ntohs(sdp_get_unaligned((uint16_t *)pCacheBuffer));
+
+ /* get index of the last sdp_record_t sent */
+ lastIndex = cstate->cStateValue.lastIndexSent;
+ } else {
+ status = SDP_INVALID_CSTATE;
+ goto done;
+ }
+ } else {
+ pCacheBuffer = buf->data;
+ lastIndex = 0;
+ }
+
+ /*
+ * Set the local buffer pointer to after the
+ * current record count and increment the cached
+ * buffer pointer to beyond the counters
+ */
+ pdata = (char *)pCurrentRecordCount + sizeof(uint16_t);
+
+ /* increment beyond the totalCount and the currentCount */
+ pCacheBuffer += 2 * sizeof(uint16_t);
+
+ if (cstate) {
+ handleSize = 0;
+ for (i = lastIndex; (i - lastIndex) < actual && i < rsp_count; i++) {
+ sdp_put_unaligned(sdp_get_unaligned((uint32_t *)(pCacheBuffer + i * sizeof(uint32_t))), (uint32_t *)pdata);
+ pdata += sizeof(uint32_t);
+ handleSize += sizeof(uint32_t);
+ }
+ } else {
+ handleSize = actual << 2;
+ i = actual;
+ }
+
+ buf->data_size += handleSize;
+ sdp_put_unaligned(htons(rsp_count), (uint16_t *)pTotalRecordCount);
+ sdp_put_unaligned(htons(i - lastIndex), (uint16_t *)pCurrentRecordCount);
+
+ if (i == rsp_count) {
+ /* set "null" continuationState */
+ sdp_set_cstate_pdu(buf, NULL);
+ } else {
+ /*
+ * there's more: set lastIndexSent to
+ * the new value and move on
+ */
+ sdp_cont_state_t newState;
+
+ SDPDBG("Setting non-NULL sdp_cstate_t\n");
+
+ if (cstate)
+ memcpy((char *)&newState, cstate, sizeof(sdp_cont_state_t));
+ else {
+ memset((char *)&newState, 0, sizeof(sdp_cont_state_t));
+ newState.timestamp = cStateId;
+ }
+ newState.cStateValue.lastIndexSent = i;
+ sdp_set_cstate_pdu(buf, &newState);
+ }
+ }
+
+done:
+ if (pattern)
+ sdp_list_free(pattern, free);
+
+ return status;
+}
+
+/*
+ * Extract attribute identifiers from the request PDU.
+ * Clients could request a subset of attributes (by id)
+ * from a service record, instead of the whole set. The
+ * requested identifiers are present in the PDU form of
+ * the request
+ */
+static int extract_attrs(sdp_record_t *rec, sdp_list_t *seq, uint8_t dtd, sdp_buf_t *buf)
+{
+ if (!rec)
+ return SDP_INVALID_RECORD_HANDLE;
+
+#ifdef SDP_DEBUG
+ if (seq)
+ SDPDBG("Entries in attr seq : %d\n", sdp_list_len(seq));
+ else
+ SDPDBG("NULL attribute descriptor\n");
+ SDPDBG("AttrDataType : %d\n", dtd);
+#endif
+ if (seq == NULL) {
+ SDPDBG("Attribute sequence is NULL\n");
+ return 0;
+ }
+ if (dtd == SDP_UINT16)
+ for (; seq; seq = seq->next) {
+ uint16_t attr = sdp_get_unaligned((uint16_t *)seq->data);
+ sdp_data_t *a = (sdp_data_t *)sdp_data_get(rec, attr);
+ if (a)
+ sdp_append_to_pdu(buf, a);
+ }
+ else if (dtd == SDP_UINT32) {
+ sdp_buf_t pdu;
+ sdp_gen_record_pdu(rec, &pdu);
+ for (; seq; seq = seq->next) {
+ uint32_t range = sdp_get_unaligned((uint32_t *)seq->data);
+ uint16_t attr;
+ uint16_t low = (0xffff0000 & range) >> 16;
+ uint16_t high = 0x0000ffff & range;
+
+ SDPDBG("attr range : 0x%x\n", range);
+ SDPDBG("Low id : 0x%x\n", low);
+ SDPDBG("High id : 0x%x\n", high);
+
+ if (low == 0x0000 && high == 0xffff && pdu.data_size <= buf->buf_size) {
+ /* copy it */
+ memcpy(buf->data, pdu.data, pdu.data_size);
+ buf->data_size = pdu.data_size;
+ break;
+ }
+ /* (else) sub-range of attributes */
+ for (attr = low; attr <= high; attr++) {
+ sdp_data_t *a = (sdp_data_t *)sdp_data_get(rec, attr);
+ if (a)
+ sdp_append_to_pdu(buf, a);
+ }
+ }
+ free(pdu.data);
+ } else {
+ SDPERR("Unexpected data type : 0x%x\n", dtd);
+ SDPERR("Expect uint16_t or uint32_t\n");
+ return SDP_INVALID_SYNTAX;
+ }
+ return 0;
+}
+
+/*
+ * A request for the attributes of a service record.
+ * First check if the service record (specified by
+ * service record handle) exists, then call the attribute
+ * streaming function
+ */
+static int service_attr_req(sdp_req_t *req, sdp_buf_t *buf)
+{
+ sdp_cont_state_t *cstate = NULL;
+ char *pResponse = NULL;
+ short cstate_size = 0;
+ sdp_list_t *seq = NULL;
+ uint8_t dtd = 0;
+ int scanned = 0;
+ int max_rsp_size;
+ int status = 0, plen, mlen;
+ char *pdata = req->buf + sizeof(sdp_pdu_hdr_t);
+ uint32_t handle = ntohl(sdp_get_unaligned((uint32_t *)pdata));
+
+ SDPDBG("");
+
+ pdata += sizeof(uint32_t);
+ max_rsp_size = ntohs(sdp_get_unaligned((uint16_t *)pdata));
+ pdata += sizeof(uint16_t);
+
+ /* extract the attribute list */
+ scanned = extract_des(pdata, &seq, &dtd, SDP_TYPE_ANY);
+ if (scanned == -1) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+ pdata += scanned;
+
+ plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen);
+ mlen = scanned + sizeof(uint32_t) + sizeof(uint16_t) + 1;
+ // ensure we don't read past buffer
+ if (plen < mlen || plen != mlen + *(uint8_t *)pdata) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ /*
+ * if continuation state exists, attempt
+ * to get rsp remainder from cache, else send error
+ */
+ cstate = sdp_cstate_get(pdata);
+
+ SDPDBG("SvcRecHandle : 0x%x\n", handle);
+ SDPDBG("max_rsp_size : %d\n", max_rsp_size);
+
+ /*
+ * Calculate Attribute size acording to MTU
+ * We can send only (MTU - sizeof(sdp_pdu_hdr_t) - sizeof(sdp_cont_state_t))
+ */
+ max_rsp_size = MIN(max_rsp_size, req->mtu - sizeof(sdp_pdu_hdr_t) -
+ sizeof(uint32_t) - SDP_CONT_STATE_SIZE - sizeof(uint16_t));
+
+ /* pull header for AttributeList byte count */
+ buf->data += sizeof(uint16_t);
+ buf->buf_size -= sizeof(uint16_t);
+
+ if (cstate) {
+ sdp_buf_t *pCache = sdp_get_cached_rsp(cstate);
+
+ SDPDBG("Obtained cached rsp : %p\n", pCache);
+
+ if (pCache) {
+ short sent = MIN(max_rsp_size, pCache->data_size - cstate->cStateValue.maxBytesSent);
+ pResponse = pCache->data;
+ memcpy(buf->data, pResponse + cstate->cStateValue.maxBytesSent, sent);
+ buf->data_size += sent;
+ cstate->cStateValue.maxBytesSent += sent;
+
+ SDPDBG("Response size : %d sending now : %d bytes sent so far : %d\n",
+ pCache->data_size, sent, cstate->cStateValue.maxBytesSent);
+ if (cstate->cStateValue.maxBytesSent == pCache->data_size)
+ cstate_size = sdp_set_cstate_pdu(buf, NULL);
+ else
+ cstate_size = sdp_set_cstate_pdu(buf, cstate);
+ } else {
+ status = SDP_INVALID_CSTATE;
+ SDPERR("NULL cache buffer and non-NULL continuation state\n");
+ }
+ } else {
+ sdp_record_t *rec = sdp_record_find(handle);
+ status = extract_attrs(rec, seq, dtd, buf);
+ if (buf->data_size > max_rsp_size) {
+ sdp_cont_state_t newState;
+
+ memset((char *)&newState, 0, sizeof(sdp_cont_state_t));
+ newState.timestamp = sdp_cstate_alloc_buf(buf);
+ /*
+ * Reset the buffer size to the maximum expected and
+ * set the sdp_cont_state_t
+ */
+ SDPDBG("Creating continuation state of size : %d\n", buf->data_size);
+ buf->data_size = max_rsp_size;
+ newState.cStateValue.maxBytesSent = max_rsp_size;
+ cstate_size = sdp_set_cstate_pdu(buf, &newState);
+ } else {
+ if (buf->data_size == 0)
+ sdp_append_to_buf(buf, 0, 0);
+ cstate_size = sdp_set_cstate_pdu(buf, NULL);
+ }
+ }
+
+ // push header
+ buf->data -= sizeof(uint16_t);
+ buf->buf_size += sizeof(uint16_t);
+
+done:
+ if (seq)
+ sdp_list_free(seq, free);
+ if (status)
+ return status;
+
+ /* set attribute list byte count */
+ sdp_put_unaligned(htons(buf->data_size - cstate_size), (uint16_t *)buf->data);
+ buf->data_size += sizeof(uint16_t);
+ return 0;
+}
+
+/*
+ * combined service search and attribute extraction
+ */
+static int service_search_attr_req(sdp_req_t *req, sdp_buf_t *buf)
+{
+ int status = 0, plen, totscanned;
+ char *pdata, *pResponse = NULL;
+ int scanned, max, rsp_count = 0;
+ sdp_list_t *pattern = NULL, *seq = NULL, *svcList;
+ sdp_cont_state_t *cstate = NULL;
+ short cstate_size = 0;
+ uint8_t dtd = 0;
+ sdp_buf_t tmpbuf;
+
+ tmpbuf.data = NULL;
+ pdata = req->buf + sizeof(sdp_pdu_hdr_t);
+ scanned = extract_des(pdata, &pattern, &dtd, SDP_TYPE_UUID);
+ if (scanned == -1) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+ totscanned = scanned;
+
+ SDPDBG("Bytes scanned: %d", scanned);
+
+ pdata += scanned;
+ max = ntohs(sdp_get_unaligned((uint16_t *)pdata));
+ pdata += sizeof(uint16_t);
+
+ SDPDBG("Max Attr expected: %d", max);
+
+ /* extract the attribute list */
+ scanned = extract_des(pdata, &seq, &dtd, SDP_TYPE_ANY);
+ if (scanned == -1) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+ pdata += scanned;
+ totscanned += scanned + sizeof(uint16_t) + 1;
+
+ plen = ntohs(((sdp_pdu_hdr_t *)(req->buf))->plen);
+ if (plen < totscanned || plen != totscanned + *(uint8_t *)pdata) {
+ status = SDP_INVALID_SYNTAX;
+ goto done;
+ }
+
+ /*
+ * if continuation state exists attempt
+ * to get rsp remainder from cache, else send error
+ */
+ cstate = sdp_cstate_get(pdata); // continuation information
+
+ svcList = sdp_get_record_list();
+
+ tmpbuf.data = (char *)malloc(USHRT_MAX);
+ tmpbuf.data_size = 0;
+ tmpbuf.buf_size = USHRT_MAX;
+ memset(tmpbuf.data, 0, USHRT_MAX);
+
+ /*
+ * Calculate Attribute size acording to MTU
+ * We can send only (MTU - sizeof(sdp_pdu_hdr_t) - sizeof(sdp_cont_state_t))
+ */
+ max = MIN(max, req->mtu - sizeof(sdp_pdu_hdr_t) - SDP_CONT_STATE_SIZE - sizeof(uint16_t));
+
+ /* pull header for AttributeList byte count */
+ buf->data += sizeof(uint16_t);
+ buf->buf_size -= sizeof(uint16_t);
+
+ if (cstate == NULL) {
+ /* no continuation state -> create new response */
+ sdp_list_t *p;
+ for (p = svcList; p; p = p->next) {
+ sdp_record_t *rec = (sdp_record_t *)p->data;
+ if (sdp_match_uuid(pattern, rec->pattern) > 0) {
+ rsp_count++;
+ status = extract_attrs(rec, seq, dtd, &tmpbuf);
+
+ SDPDBG("Response count : %d\n", rsp_count);
+ SDPDBG("Local PDU size : %d\n", tmpbuf.data_size);
+ if (status) {
+ SDPDBG("Extract attr from record returns err\n");
+ break;
+ }
+ if (buf->data_size + tmpbuf.data_size < buf->buf_size) {
+ // to be sure no relocations
+ sdp_append_to_buf(buf, tmpbuf.data, tmpbuf.data_size);
+ tmpbuf.data_size = 0;
+ memset(tmpbuf.data, 0, USHRT_MAX);
+ } else {
+ SDPERR("Relocation needed\n");
+ break;
+ }
+ SDPDBG("Net PDU size : %d\n", buf->data_size);
+ }
+ }
+ if (buf->data_size > max) {
+ sdp_cont_state_t newState;
+
+ memset((char *)&newState, 0, sizeof(sdp_cont_state_t));
+ newState.timestamp = sdp_cstate_alloc_buf(buf);
+ /*
+ * Reset the buffer size to the maximum expected and
+ * set the sdp_cont_state_t
+ */
+ buf->data_size = max;
+ newState.cStateValue.maxBytesSent = max;
+ cstate_size = sdp_set_cstate_pdu(buf, &newState);
+ } else
+ cstate_size = sdp_set_cstate_pdu(buf, NULL);
+ } else {
+ /* continuation State exists -> get from cache */
+ sdp_buf_t *pCache = sdp_get_cached_rsp(cstate);
+ if (pCache) {
+ uint16_t sent = MIN(max, pCache->data_size - cstate->cStateValue.maxBytesSent);
+ pResponse = pCache->data;
+ memcpy(buf->data, pResponse + cstate->cStateValue.maxBytesSent, sent);
+ buf->data_size += sent;
+ cstate->cStateValue.maxBytesSent += sent;
+ if (cstate->cStateValue.maxBytesSent == pCache->data_size)
+ cstate_size = sdp_set_cstate_pdu(buf, NULL);
+ else
+ cstate_size = sdp_set_cstate_pdu(buf, cstate);
+ } else {
+ status = SDP_INVALID_CSTATE;
+ SDPDBG("Non-null continuation state, but null cache buffer\n");
+ }
+ }
+
+ if (!rsp_count && !cstate) {
+ // found nothing
+ buf->data_size = 0;
+ sdp_append_to_buf(buf, tmpbuf.data, tmpbuf.data_size);
+ sdp_set_cstate_pdu(buf, NULL);
+ }
+
+ // push header
+ buf->data -= sizeof(uint16_t);
+ buf->buf_size += sizeof(uint16_t);
+
+ if (!status) {
+ /* set attribute list byte count */
+ sdp_put_unaligned(htons(buf->data_size - cstate_size), (uint16_t *)buf->data);
+ buf->data_size += sizeof(uint16_t);
+ }
+
+done:
+ if (tmpbuf.data)
+ free(tmpbuf.data);
+ if (pattern)
+ sdp_list_free(pattern, free);
+ if (seq)
+ sdp_list_free(seq, free);
+ return status;
+}
+
+/*
+ * Top level request processor. Calls the appropriate processing
+ * function based on request type. Handles service registration
+ * client requests also.
+ */
+void process_request(sdp_req_t *req)
+{
+ sdp_pdu_hdr_t *reqhdr = (sdp_pdu_hdr_t *)req->buf;
+ sdp_pdu_hdr_t *rsphdr;
+ sdp_buf_t rsp;
+ char *buf = (char *)malloc(USHRT_MAX);
+ int sent = 0;
+ int status = SDP_INVALID_SYNTAX;
+
+ SDPDBG("");
+
+ memset((void *)buf, 0, USHRT_MAX);
+ rsp.data = buf + sizeof(sdp_pdu_hdr_t);
+ rsp.data_size = 0;
+ rsp.buf_size = USHRT_MAX - sizeof(sdp_pdu_hdr_t);
+ rsphdr = (sdp_pdu_hdr_t *)buf;
+
+ if (ntohs(reqhdr->plen) != req->len - sizeof(sdp_pdu_hdr_t)) {
+ status = SDP_INVALID_PDU_SIZE;
+ goto send_rsp;
+ }
+ switch (reqhdr->pdu_id) {
+ case SDP_SVC_SEARCH_REQ:
+ SDPDBG("Got a svc srch req\n");
+ status = service_search_req(req, &rsp);
+ rsphdr->pdu_id = SDP_SVC_SEARCH_RSP;
+ break;
+ case SDP_SVC_ATTR_REQ:
+ SDPDBG("Got a svc attr req\n");
+ status = service_attr_req(req, &rsp);
+ rsphdr->pdu_id = SDP_SVC_ATTR_RSP;
+ break;
+ case SDP_SVC_SEARCH_ATTR_REQ:
+ SDPDBG("Got a svc srch attr req\n");
+ status = service_search_attr_req(req, &rsp);
+ rsphdr->pdu_id = SDP_SVC_SEARCH_ATTR_RSP;
+ break;
+ /* Following requests are allowed only for local connections */
+ case SDP_SVC_REGISTER_REQ:
+ SDPDBG("Service register request\n");
+ if (req->local) {
+ status = service_register_req(req, &rsp);
+ rsphdr->pdu_id = SDP_SVC_REGISTER_RSP;
+ }
+ break;
+ case SDP_SVC_UPDATE_REQ:
+ SDPDBG("Service update request\n");
+ if (req->local) {
+ status = service_update_req(req, &rsp);
+ rsphdr->pdu_id = SDP_SVC_UPDATE_RSP;
+ }
+ break;
+ case SDP_SVC_REMOVE_REQ:
+ SDPDBG("Service removal request\n");
+ if (req->local) {
+ status = service_remove_req(req, &rsp);
+ rsphdr->pdu_id = SDP_SVC_REMOVE_RSP;
+ }
+ break;
+ default:
+ SDPERR("Unknown PDU ID : 0x%x received\n", reqhdr->pdu_id);
+ status = SDP_INVALID_SYNTAX;
+ break;
+ }
+
+send_rsp:
+ if (status) {
+ rsphdr->pdu_id = SDP_ERROR_RSP;
+ sdp_put_unaligned(htons(status), (uint16_t *)rsp.data);
+ rsp.data_size = sizeof(uint16_t);
+ }
+
+ SDPDBG("Sending rsp. status %d", status);
+
+ rsphdr->tid = reqhdr->tid;
+ rsphdr->plen = htons(rsp.data_size);
+
+ /* point back to the real buffer start and set the real rsp length */
+ rsp.data_size += sizeof(sdp_pdu_hdr_t);
+ rsp.data = buf;
+
+ /* stream the rsp PDU */
+ sent = send(req->sock, rsp.data, rsp.data_size, 0);
+
+ SDPDBG("Bytes Sent : %d\n", sent);
+
+ free(rsp.data);
+ free(req->buf);
+}
diff --git a/sdpd/sdpd.h b/sdpd/sdpd.h
new file mode 100644
index 00000000..1f0d7e47
--- /dev/null
+++ b/sdpd/sdpd.h
@@ -0,0 +1,75 @@
+/*
+ Service Discovery Protocol (SDP)
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>, Stephen Crane <steve.crane@rococosoft.com>
+
+ Based on original SDP implementation by Nokia Corporation.
+ Copyright (C) 2001,2002 Nokia Corporation.
+ Original author Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM,
+ OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+ RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS,
+ TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ * $Id$
+ */
+
+#ifndef SDPD_H
+#define SDPD_H
+
+typedef struct request {
+ bdaddr_t bdaddr;
+ int local;
+ int sock;
+ int mtu;
+ int flags;
+ char *buf;
+ int len;
+} sdp_req_t;
+
+void process_request(sdp_req_t *req);
+
+int service_register_req(sdp_req_t *req, sdp_buf_t *rsp);
+int service_update_req(sdp_req_t *req, sdp_buf_t *rsp);
+int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp);
+
+typedef struct {
+ long timestamp;
+ union {
+ uint16_t maxBytesSent;
+ uint16_t lastIndexSent;
+ } cStateValue;
+} sdp_cont_state_t;
+
+#define SDP_CONT_STATE_SIZE (sizeof(uint8_t) + sizeof(sdp_cont_state_t))
+
+sdp_buf_t *sdp_get_cached_rsp(sdp_cont_state_t *cstate);
+void sdp_cstate_cache_init(void);
+void sdp_cstate_clean_buf(void);
+long sdp_cstate_alloc_buf(sdp_buf_t *buf);
+
+void sdp_svcdb_reset(void);
+void sdp_svcdb_collect_all(int sock);
+void sdp_svcdb_set_collectable(sdp_record_t *rec, int sock);
+void sdp_svcdb_collect(sdp_record_t *rec);
+sdp_record_t *sdp_record_find(uint32_t handle);
+void sdp_record_add(sdp_record_t *rec);
+int sdp_record_remove(uint32_t handle);
+sdp_list_t *sdp_get_record_list();
+
+long sdp_get_time();
+
+#endif
diff --git a/sdpd/service.c b/sdpd/service.c
new file mode 100644
index 00000000..171a086d
--- /dev/null
+++ b/sdpd/service.c
@@ -0,0 +1,241 @@
+/*
+ Service Discovery Protocol (SDP)
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>, Stephen Crane <steve.crane@rococosoft.com>
+
+ Based on original SDP implementation by Nokia Corporation.
+ Copyright (C) 2001,2002 Nokia Corporation.
+ Original author Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM,
+ OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+ RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS,
+ TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ Service registration requests.
+
+ Fixes:
+ Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com>
+*/
+
+/*
+ * $Id$
+ */
+
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/sdp_internal.h>
+
+#include "sdpd.h"
+
+extern void update_db_timestamp(void);
+
+// FIXME: refactor for server-side
+static sdp_record_t *extract_pdu_server(char *p, uint32_t handleExpected, int *scanned)
+{
+ int extractStatus = -1, localExtractedLength = 0;
+ uint8_t dtd;
+ int seqlen = 0;
+ sdp_record_t *rec = NULL;
+ uint16_t attrId, lookAheadAttrId;
+ sdp_data_t *pAttr = NULL;
+ uint32_t handle = 0xffffffff;
+
+ *scanned = sdp_extract_seqtype(p, &dtd, &seqlen);
+ p += *scanned;
+ lookAheadAttrId = ntohs(sdp_get_unaligned((uint16_t *)(p + sizeof(uint8_t))));
+
+ SDPDBG("Look ahead attr id : %d\n", lookAheadAttrId);
+
+ if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) {
+ handle = ntohl(sdp_get_unaligned((uint32_t *)(p +
+ sizeof(uint8_t) + sizeof(uint16_t) +
+ sizeof(uint8_t))));
+ SDPDBG("SvcRecHandle : 0x%x\n", handle);
+ rec = sdp_record_find(handle);
+ } else if (handleExpected != 0xffffffff)
+ rec = sdp_record_find(handleExpected);
+
+ if (rec == NULL) {
+ rec = sdp_record_alloc();
+ rec->attrlist = NULL;
+ if (lookAheadAttrId == SDP_ATTR_RECORD_HANDLE) {
+ rec->handle = handle;
+ sdp_record_add(rec);
+ } else if (handleExpected != 0xffffffff) {
+ rec->handle = handleExpected;
+ sdp_record_add(rec);
+ }
+ }
+
+ while (localExtractedLength < seqlen) {
+ int attrSize = sizeof(uint8_t);
+ int attrValueLength = 0;
+
+ SDPDBG("Extract PDU, sequenceLength: %d localExtractedLength: %d", seqlen, localExtractedLength);
+ dtd = *(uint8_t *)p;
+
+ attrId = ntohs(sdp_get_unaligned((uint16_t *)(p + attrSize)));
+ attrSize += sizeof(uint16_t);
+
+ SDPDBG("DTD of attrId : %d Attr id : 0x%x \n", dtd, attrId);
+
+ pAttr = sdp_extract_attr(p + attrSize, &attrValueLength, rec);
+
+ SDPDBG("Attr id : 0x%x attrValueLength : %d\n", attrId, attrValueLength);
+
+ attrSize += attrValueLength;
+ if (pAttr == NULL) {
+ SDPDBG("Terminating extraction of attributes");
+ break;
+ }
+ localExtractedLength += attrSize;
+ p += attrSize;
+ sdp_attr_replace(rec, attrId, pAttr);
+ extractStatus = 0;
+ SDPDBG("Extract PDU, seqLength: %d localExtractedLength: %d",
+ seqlen, localExtractedLength);
+ }
+
+ if (extractStatus == 0) {
+ SDPDBG("Successful extracting of Svc Rec attributes\n");
+#ifdef SDP_DEBUG
+ sdp_print_service_attr(rec->attrlist);
+#endif
+ *scanned += seqlen;
+ }
+ return rec;
+}
+
+/*
+ * Add the newly created service record to the service repository
+ */
+int service_register_req(sdp_req_t *req, sdp_buf_t *rsp)
+{
+ int scanned = 0;
+ sdp_data_t *handle;
+ char *p = req->buf + sizeof(sdp_pdu_hdr_t);
+ sdp_record_t *rec;
+
+ req->flags = *p++;
+
+ // save image of PDU: we need it when clients request this attribute
+ rec = extract_pdu_server(p, 0xffffffff, &scanned);
+ if (rec == NULL) {
+ sdp_put_unaligned(htons(SDP_INVALID_SYNTAX), (uint16_t *)rsp->data);
+ rsp->data_size = sizeof(uint16_t);
+ return -1;
+ }
+
+ rec->handle = (uint32_t)rec;
+ sdp_record_add(rec);
+ if (!(req->flags & SDP_RECORD_PERSIST))
+ sdp_svcdb_set_collectable(rec, req->sock);
+ handle = sdp_data_alloc(SDP_UINT32, &rec->handle);
+ sdp_attr_replace(rec, SDP_ATTR_RECORD_HANDLE, handle);
+ /*
+ * if the browse group descriptor is NULL,
+ * ensure that the record belongs to the ROOT group
+ */
+ if (sdp_data_get(rec, SDP_ATTR_BROWSE_GRP_LIST) == NULL) {
+ uuid_t uuid;
+ sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP);
+ sdp_pattern_add_uuid(rec, &uuid);
+ }
+ update_db_timestamp();
+
+ /* Build a rsp buffer */
+ sdp_put_unaligned(htonl(rec->handle), (uint32_t *)rsp->data);
+ rsp->data_size = sizeof(uint32_t);
+ return 0;
+}
+
+/*
+ * Update a service record
+ */
+int service_update_req(sdp_req_t *req, sdp_buf_t *rsp)
+{
+ sdp_record_t *orec;
+ int status = 0, scanned = 0;
+ char *p = req->buf + sizeof(sdp_pdu_hdr_t);
+ uint32_t handle = ntohl(sdp_get_unaligned((uint32_t *)p));
+
+ SDPDBG("");
+
+ SDPDBG("Svc Rec Handle: 0x%x\n", handle);
+
+ p += sizeof(uint32_t);
+
+ orec = sdp_record_find(handle);
+
+ SDPDBG("SvcRecOld: 0x%x\n", (uint32_t)orec);
+
+ if (orec) {
+ sdp_record_t *nrec = extract_pdu_server(p, handle, &scanned);
+ if (nrec && handle == nrec->handle)
+ update_db_timestamp();
+ else {
+ SDPDBG("SvcRecHandle : 0x%x\n", handle);
+ SDPDBG("SvcRecHandleNew : 0x%x\n", nrec->handle);
+ SDPDBG("SvcRecNew : 0x%x\n", (uint32_t) nrec);
+ SDPDBG("SvcRecOld : 0x%x\n", (uint32_t) orec);
+ SDPDBG("Failure to update, restore old value\n");
+
+ if (nrec)
+ sdp_record_free(nrec);
+ status = SDP_INVALID_SYNTAX;
+ }
+ } else
+ status = SDP_INVALID_RECORD_HANDLE;
+
+ p = rsp->data;
+ sdp_put_unaligned(htons(status), (uint16_t *)p);
+ rsp->data_size = sizeof(uint16_t);
+ return status;
+}
+
+/*
+ * Remove a registered service record
+ */
+int service_remove_req(sdp_req_t *req, sdp_buf_t *rsp)
+{
+ char *p = req->buf + sizeof(sdp_pdu_hdr_t);
+ uint32_t handle = ntohl(sdp_get_unaligned((uint32_t *)p));
+ sdp_record_t *rec;
+ int status = 0;
+
+ SDPDBG("");
+
+ /* extract service record handle */
+ p += sizeof(uint32_t);
+
+ rec = sdp_record_find(handle);
+ if (rec) {
+ sdp_svcdb_collect(rec);
+ status = sdp_record_remove(handle);
+ sdp_record_free(rec);
+ if (status == 0)
+ update_db_timestamp();
+ } else {
+ status = SDP_INVALID_RECORD_HANDLE;
+ SDPDBG("Could not find record : 0x%x\n", handle);
+ }
+
+ p = rsp->data;
+ sdp_put_unaligned(htons(status), (uint16_t *)p);
+ rsp->data_size = sizeof(uint16_t);
+
+ return status;
+}
diff --git a/sdpd/servicedb.c b/sdpd/servicedb.c
new file mode 100644
index 00000000..d0b2b85e
--- /dev/null
+++ b/sdpd/servicedb.c
@@ -0,0 +1,209 @@
+/*
+ Service Discovery Protocol (SDP)
+ Copyright (C) 2002 Maxim Krasnyansky <maxk@qualcomm.com>, Stephen Crane <steve.crane@rococosoft.com>
+
+ Based on original SDP implementation by Nokia Corporation.
+ Copyright (C) 2001,2002 Nokia Corporation.
+ Original author Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com>
+
+ This program is free software; you can redistribute it and/or modify
+ it under the terms of the GNU General Public License version 2 as
+ published by the Free Software Foundation;
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS.
+ IN NO EVENT SHALL THE COPYRIGHT HOLDER(S) AND AUTHOR(S) BE LIABLE FOR ANY CLAIM,
+ OR ANY SPECIAL INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER
+ RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
+ NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE
+ USE OR PERFORMANCE OF THIS SOFTWARE.
+
+ ALL LIABILITY, INCLUDING LIABILITY FOR INFRINGEMENT OF ANY PATENTS, COPYRIGHTS,
+ TRADEMARKS OR OTHER RIGHTS, RELATING TO USE OF THIS SOFTWARE IS DISCLAIMED.
+*/
+
+/*
+ Service repository.
+ Has methods to create and clean the repository, besides
+ methods to add/find/modify/delete individual entries
+
+ Fixes:
+ Guruprasad Krishnamurthy <guruprasad.krishnamurthy@nokia.com>
+ Manel Guerrero Zapata <manel.guerrero-zapata@nokia.com>
+*/
+
+/*
+ * $Id$
+ */
+
+#include <malloc.h>
+#include <sys/socket.h>
+
+#include <bluetooth/bluetooth.h>
+#include <bluetooth/l2cap.h>
+#include <bluetooth/sdp.h>
+#include <bluetooth/sdp_lib.h>
+#include <bluetooth/sdp_internal.h>
+
+#include "sdpd.h"
+
+static sdp_list_t *service_db;
+
+/*
+ * Ordering function called when inserting a service record.
+ * The service repository is a linked list in sorted order
+ * and the service record handle is the sort key
+ */
+static int record_sort(const void *r1, const void *r2)
+{
+ const sdp_record_t *rec1 = (const sdp_record_t *)r1;
+ const sdp_record_t *rec2 = (const sdp_record_t *)r2;
+
+ if (!rec1 || !rec2) {
+ SDPERR("NULL RECORD LIST FATAL\n");
+ return -1;
+ }
+ return rec1->handle - rec2->handle;
+}
+
+/*
+ * Reset the service repository by deleting its contents
+ */
+void sdp_svcdb_reset()
+{
+ sdp_list_free(service_db, (sdp_free_func_t)sdp_record_free);
+}
+
+typedef struct _indexed {
+ int sock;
+ sdp_record_t *record;
+} sdp_indexed_t;
+
+static sdp_list_t *socket_index;
+
+/*
+ * collect all services registered over this socket
+ */
+void sdp_svcdb_collect_all(int sock)
+{
+ sdp_list_t *p, *q;
+
+ for (p=socket_index, q=0; p; ) {
+ sdp_indexed_t *item = (sdp_indexed_t *)p->data;
+ if (item->sock == sock) {
+ sdp_list_t *next = p->next;
+ sdp_record_remove(item->record->handle);
+ free(item);
+ if (q)
+ q->next = next;
+ else
+ socket_index = next;
+ free(p);
+ p = next;
+ } else if (item->sock > sock)
+ return;
+ else {
+ q = p;
+ p = p->next;
+ }
+ }
+}
+
+void sdp_svcdb_collect(sdp_record_t *rec)
+{
+ sdp_list_t *p, *q;
+
+ for (p=socket_index, q=0; p; q=p, p=p->next) {
+ sdp_indexed_t *item = (sdp_indexed_t *)p->data;
+ if (rec == item->record) {
+ free(item);
+ if (q)
+ q->next = p->next;
+ else
+ socket_index = p->next;
+ free(p);
+ return;
+ }
+ }
+}
+
+static int compare_indices(const void *i1, const void *i2)
+{
+ const sdp_indexed_t *s1 = (const sdp_indexed_t *)i1;
+ const sdp_indexed_t *s2 = (const sdp_indexed_t *)i2;
+ return s1->sock - s2->sock;
+}
+
+void sdp_svcdb_set_collectable(sdp_record_t *record, int sock)
+{
+ sdp_indexed_t *item = (sdp_indexed_t *)malloc(sizeof(sdp_indexed_t));
+ item->sock = sock;
+ item->record = record;
+ socket_index = sdp_list_insert_sorted(socket_index, item, compare_indices);
+}
+
+/*
+ * Add a service record to the repository
+ */
+void sdp_record_add(sdp_record_t *rec)
+{
+#ifdef SDP_DEBUG
+ SDPDBG("Adding rec : 0x%lx\n", (long)rec);
+ SDPDBG("with handle : 0x%lx\n", (long)rec->handle);
+#endif
+ service_db = sdp_list_insert_sorted(service_db, rec, record_sort);
+}
+
+static sdp_list_t *record_locate(uint32_t handle)
+{
+ if (service_db) {
+ sdp_list_t *p;
+ sdp_record_t r;
+
+ r.handle = handle;
+ p = sdp_list_find(service_db, &r, record_sort);
+ return p;
+ }
+ SDPDBG("Could not find svcRec for : 0x%x\n", handle);
+ return 0;
+}
+
+/*
+ * Given a service record handle, find the record associated with it.
+ */
+sdp_record_t *sdp_record_find(uint32_t handle)
+{
+ sdp_list_t *p = record_locate(handle);
+
+ if (p)
+ return (sdp_record_t *)p->data;
+ SDPDBG("Couldn't find record for : 0x%x\n", handle);
+ return 0;
+}
+
+/*
+ * Given a service record handle, remove its record from the repository
+ */
+int sdp_record_remove(uint32_t handle)
+{
+ sdp_list_t *p = record_locate(handle);
+
+ if (p) {
+ sdp_record_t *r = (sdp_record_t *)p->data;
+ if (r) {
+ service_db = sdp_list_remove(service_db, r);
+ return 0;
+ }
+ }
+ SDPERR("Remove : Couldn't find record for : 0x%x\n", handle);
+ return -1;
+}
+
+/*
+ * Return a pointer to the linked list containing the records in sorted order
+ */
+sdp_list_t *sdp_get_record_list()
+{
+ return service_db;
+}