/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2004-2006 Marcel Holtmann * * * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "dbus.h" #include "hcid.h" #include "textfile.h" #define SDP_UUID_SEQ_SIZE 256 #define SDP_MAX_ATTR_LEN 65535 struct service_provider { char *owner; /* null for remote services or unique name if local */ bdaddr_t prov; /* remote Bluetooth address or local address */ struct slist *lrec; }; struct service_record { uint32_t identifier; sdp_record_t *record; }; struct pending_connect { DBusConnection *conn; DBusMessage *rq; char *svc; bdaddr_t dba; }; struct transaction_context { DBusConnection *conn; DBusMessage *rq; char *svc; sdp_session_t *session; sdp_cstate_t *cstate; uint8_t *reqbuf; bdaddr_t dba; sdp_buf_t rspbuf; uint32_t reqsize; int attr_list_len; }; /* FIXME: store the arguments or just the pointer to the message */ /* list of remote and local service records * FIXME: free the cache when the local sock(sdpd) is closed */ static struct slist *sdp_cache = NULL; static struct slist *pending_connects = NULL; static const char *ecode2str(uint16_t ecode) { switch (ecode) { case 0x0000: return "Reserved"; case 0x0001: return "Invalid/Unsupported SDP version"; case 0x0002: return "Invalid Service Record Handle"; case 0x0003: return "Invalid request syntax"; case 0x0004: return "Invalid PDU size"; case 0x0005: return "Invalid Continuation State"; case 0x0006: return "Insufficient Resources to satisfy Request"; default: return "Reserved"; } } static struct pending_connect *pending_connect_new(DBusConnection *conn, DBusMessage *msg, const bdaddr_t *bda, const char *svc) { struct pending_connect *c; if (!bda) return NULL; c = malloc(sizeof(*c)); memset(c, 0, sizeof(*c)); if (svc) { c->svc = strdup(svc); if (!c->svc) goto failed; } bacpy(&c->dba, bda); c->conn = dbus_connection_ref(conn); c->rq = dbus_message_ref(msg); return c; failed: if (c) free(c); return NULL; } static void pending_connect_free(struct pending_connect *c) { if (!c) return; if (c->svc) free(c->svc); if (c->rq) dbus_message_unref(c->rq); if (c->conn) dbus_connection_unref(c->conn); free(c); } static struct pending_connect *find_pending_connect(const bdaddr_t *bda) { struct slist *l; for (l = pending_connects; l != NULL; l = l->next) { struct pending_connect *pending = l->data; if (!bacmp(bda, &pending->dba)) return pending; } return NULL; } /* FIXME: duplicated function. Make this function public on bluez-libs */ static int gen_dataseq_pdu(uint8_t *dst, const sdp_list_t *seq, uint8_t dtd) { sdp_data_t *dataseq; void **types, **values; sdp_buf_t buf; int i, seqlen = sdp_list_len(seq); // Fill up the value and the dtd arrays memset(&buf, 0, sizeof(sdp_buf_t)); buf.data = malloc(SDP_UUID_SEQ_SIZE); buf.buf_size = SDP_UUID_SEQ_SIZE; types = malloc(seqlen * sizeof(void *)); values = malloc(seqlen * sizeof(void *)); for (i = 0; i < seqlen; i++) { void *data = seq->data; types[i] = &dtd; if (SDP_IS_UUID(dtd)) data = &((uuid_t *)data)->value; values[i] = data; seq = seq->next; } dataseq = sdp_seq_alloc(types, values, seqlen); seqlen = sdp_gen_pdu(&buf, dataseq); memcpy(dst, buf.data, buf.data_size); sdp_data_free(dataseq); free(types); free(values); free(buf.data); return seqlen; } /* FIXME: duplicated function */ static int gen_searchseq_pdu(uint8_t *dst, const sdp_list_t *seq) { uuid_t *uuid = (uuid_t *) seq->data; return gen_dataseq_pdu(dst, seq, uuid->type); } /* FIXME: duplicated function */ static int gen_attridseq_pdu(uint8_t *dst, const sdp_list_t *seq, uint8_t dataType) { return gen_dataseq_pdu(dst, seq, dataType); } struct transaction_context *transaction_context_new(DBusConnection *conn, DBusMessage *msg, bdaddr_t *dba, const char *svc, int sock, uint32_t flags) { struct transaction_context *ctxt; sdp_pdu_hdr_t *reqhdr; sdp_list_t *pattern = NULL; sdp_list_t *attrids = NULL; uint8_t *pdata; uuid_t uuid; uint32_t range = 0x0000ffff; int seqlen; ctxt = malloc(sizeof(*ctxt)); if (!ctxt) return NULL; memset(ctxt, 0, sizeof(*ctxt)); if (svc) { ctxt->svc = strdup(svc); if (!ctxt->svc) goto failed; } if (dba) bacpy(&ctxt->dba, dba); ctxt->session = malloc(sizeof(sdp_session_t)); if (!ctxt->session) goto failed; memset(ctxt->session, 0, sizeof(sdp_session_t)); ctxt->conn = dbus_connection_ref(conn); ctxt->rq = dbus_message_ref(msg); ctxt->session->sock = sock; ctxt->session->flags = flags; ctxt->reqbuf = malloc(SDP_REQ_BUFFER_SIZE); if (!ctxt->reqbuf) goto failed; memset(ctxt->reqbuf, 0, SDP_REQ_BUFFER_SIZE); reqhdr = (sdp_pdu_hdr_t *) ctxt->reqbuf; reqhdr->pdu_id = SDP_SVC_SEARCH_ATTR_REQ; reqhdr->tid = 0; // Generate PDU pdata = ctxt->reqbuf + sizeof(sdp_pdu_hdr_t); ctxt->reqsize = sizeof(sdp_pdu_hdr_t); /* FIXME: it should be generic to handle other kind of search requests */ sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); pattern = sdp_list_append(0, &uuid); attrids = sdp_list_append(0, &range); seqlen = gen_searchseq_pdu(pdata, pattern); // set the length and increment the pointer ctxt->reqsize += seqlen; pdata +=seqlen; bt_put_unaligned(htons(SDP_MAX_ATTR_LEN), (uint16_t *) pdata); ctxt->reqsize += sizeof(uint16_t); pdata += sizeof(uint16_t); if (attrids) { seqlen = gen_attridseq_pdu(pdata, attrids, SDP_UINT32); if (seqlen == -1) goto failed; } if (pattern) sdp_list_free(pattern, 0); if (attrids) sdp_list_free(attrids, 0); pdata += seqlen; ctxt->reqsize += seqlen; reqhdr->plen = htons(ctxt->reqsize - sizeof(sdp_pdu_hdr_t)); return ctxt; failed: if (ctxt->session) free(ctxt->session); if (ctxt->reqbuf) free(ctxt->reqbuf); free(ctxt); return NULL; } void transaction_context_free(struct transaction_context *ctxt) { if (!ctxt) return; if (ctxt->conn) dbus_connection_unref(ctxt->conn); if (ctxt->rq) dbus_message_unref(ctxt->rq); if (ctxt->svc) free(ctxt->svc); if (ctxt->session) free(ctxt->session); if (ctxt->reqbuf) free(ctxt->reqbuf); if (ctxt->rspbuf.data) free(ctxt->rspbuf.data); free(ctxt); } /* FIXME: generate the pseudo random id */ static uint32_t gen_next_id(const bdaddr_t *prov, uint32_t handle) { static uint32_t id; return ++id; } static struct service_record *service_record_new(const bdaddr_t *prov, sdp_record_t *rec) { struct service_record *r; if (!prov) return NULL; r = malloc(sizeof(*r)); if (!r) return NULL; memset(r, 0, sizeof(*r)); r->identifier = gen_next_id(prov, rec->handle); r->record = rec; return r; } static void service_record_free(struct service_record *r, void *data) { if (!r) return; sdp_record_free(r->record); free(r); } static void service_provider_free(struct service_provider *p) { if (p->owner) free(p->owner); if (p->lrec) { slist_foreach(p->lrec, (slist_func_t)service_record_free, NULL); slist_free(p->lrec); } free(p); } static struct service_provider *service_provider_new(const char *owner, const bdaddr_t *prov) { struct service_provider *p; if (!prov) return NULL; p = malloc(sizeof(struct service_provider)); if (!p) return NULL; memset(p, 0, sizeof(*p)); if (owner) { p->owner = strdup(owner); if (!p->owner) goto fail; } bacpy(&p->prov, prov); return p; fail: service_provider_free(p); return NULL; } static int service_provider_cmp(const struct service_provider *a, const struct service_provider *b) { int ret; if (b->owner) { if (!a->owner) return -1; ret = strcmp(a->owner, b->owner); if (ret) return ret; } if (bacmp(&b->prov, BDADDR_ANY)) { if (!bacmp(&a->prov, BDADDR_ANY)) return -1; ret = bacmp(&a->prov, &b->prov); if (ret) return ret; } return 0; } static uint32_t sdp_cache_append(const char *owner, const bdaddr_t *prov, sdp_record_t *rec) { struct slist *l; struct service_provider *p; struct service_provider *ref; struct service_record *r; if (!prov || !rec) return 0; ref = service_provider_new(owner, prov); if (!ref) return 0; l = slist_find(sdp_cache, (const void*)ref, (cmp_func_t)service_provider_cmp); if (!l) { p = service_provider_new(owner, prov); sdp_cache = slist_append(sdp_cache, p); } else p = l->data; r = service_record_new(prov, rec); p->lrec = slist_append(p->lrec, r); if (ref) service_provider_free(ref); return r->identifier; } static void owner_exited(const char *owner, struct hci_dbus_data *dbus_data) { struct slist *cur, *next; debug("SDP provider owner %s exited", owner); for (cur = sdp_cache; cur != NULL; cur = next) { struct service_provider *p = cur->data; next = cur->next; if(!p->owner) continue; if (strcmp(p->owner, owner)) continue; sdp_cache = slist_remove(sdp_cache, p); service_provider_free(p); } } /* FIXME: duplicated function */ static int copy_cstate(uint8_t *pdata, const sdp_cstate_t *cstate) { if (cstate) { *pdata++ = cstate->length; memcpy(pdata, cstate->data, cstate->length); return cstate->length + 1; } *pdata = 0; return 1; } static int sdp_send_req(struct transaction_context *ctxt, int *err) { sdp_pdu_hdr_t *reqhdr = (sdp_pdu_hdr_t *) ctxt->reqbuf; uint32_t sent = 0; uint32_t reqsize; reqhdr->tid = htons(sdp_gen_tid(ctxt->session)); reqsize = ctxt->reqsize + copy_cstate(ctxt->reqbuf + ctxt->reqsize, ctxt->cstate); reqhdr->plen = htons(reqsize - sizeof(sdp_pdu_hdr_t)); while (sent < reqsize) { int n = send(ctxt->session->sock, ctxt->reqbuf + sent, reqsize - sent, 0); if (n < 0) { *err = errno; return -1; } sent += n; } return 0; } static DBusMessage *parse_response(struct transaction_context *ctxt) { DBusMessage *reply = NULL; DBusMessageIter iter; DBusMessageIter array_iter; int scanned, seqlen; uint8_t dataType; uint8_t *pdata; const char *owner; owner = dbus_message_get_sender(ctxt->rq); reply = dbus_message_new_method_return(ctxt->rq); if ((ctxt->attr_list_len <= 0) || (ctxt->rspbuf.data_size == 0)) return dbus_message_new_error(ctxt->rq, ERROR_INTERFACE ".DoesNotExist", "Record does not exist"); dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32_AS_STRING, &array_iter); pdata = ctxt->rspbuf.data; scanned = sdp_extract_seqtype(pdata, &dataType, &seqlen); if (scanned && seqlen) { pdata += scanned; do { uint32_t id; int recsize = 0; sdp_record_t *rec = sdp_extract_pdu(pdata, &recsize); if (rec == NULL) break; if (!recsize) { sdp_record_free(rec); break; } scanned += recsize; pdata += recsize; id = sdp_cache_append(owner, &ctxt->dba, rec); dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_UINT32, &id); } while (scanned < ctxt->attr_list_len); } dbus_message_iter_close_container(&iter, &array_iter); return reply; } static gboolean svc_search_attr_req_cb(GIOChannel *chan, GIOCondition cond, struct transaction_context *ctxt) { int sk, err, n; uint32_t rsp_count; gboolean ret_val = FALSE; socklen_t len; uint8_t cstate_len; uint8_t *pdata; uint8_t *rsp = NULL; sdp_pdu_hdr_t *reqhdr = (sdp_pdu_hdr_t *) ctxt->reqbuf; sdp_pdu_hdr_t *rsphdr; 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); goto failed; } if (err != 0) { error("connect(): %s(%d)", strerror(err), err); error_connection_attempt_failed(ctxt->conn, ctxt->rq, err); goto failed; } rsp = malloc(SDP_RSP_BUFFER_SIZE); memset(rsp, 0, SDP_RSP_BUFFER_SIZE); n = recv(sk, rsp, SDP_RSP_BUFFER_SIZE, 0); if (n <= 0) { err = errno; goto failed; } rsphdr = (sdp_pdu_hdr_t *)rsp; if (n == 0 || reqhdr->tid != rsphdr->tid) { err = EPROTO; goto failed; } pdata = rsp + sizeof(sdp_pdu_hdr_t); if (rsphdr->pdu_id == SDP_ERROR_RSP) { uint16_t ecode = ntohs(bt_get_unaligned((uint16_t *) pdata)); error("Received SDP error response PDU: %s (%d)", ecode2str(ecode), ecode); err = EPROTO; goto failed; } rsp_count = ntohs(bt_get_unaligned((uint16_t *) pdata)); ctxt->attr_list_len += rsp_count; pdata += sizeof(uint16_t); // if continuation state set need to re-issue request before parsing cstate_len = *(uint8_t *) (pdata + rsp_count); if (rsp_count > 0) { uint8_t *targetPtr = NULL; ctxt->cstate = cstate_len > 0 ? (sdp_cstate_t *) (pdata + rsp_count) : 0; // build concatenated response buffer ctxt->rspbuf.data = realloc(ctxt->rspbuf.data, ctxt->rspbuf.data_size + rsp_count); ctxt->rspbuf.buf_size = ctxt->rspbuf.data_size + rsp_count; targetPtr = ctxt->rspbuf.data + ctxt->rspbuf.data_size; memcpy(targetPtr, pdata, rsp_count); ctxt->rspbuf.data_size += rsp_count; } if (ctxt->cstate) { if (!sdp_send_req(ctxt, &err)) ret_val = TRUE; } else { /* parse the response PDU */ send_reply_and_unref(ctxt->conn, parse_response(ctxt)); } failed: if (rsp) free(rsp); if (err) { error_failed(ctxt->conn, ctxt->rq, err); error("SDP transaction error: %s (%d)", strerror(err), err); } return ret_val; } static gboolean sdp_client_connect_cb(GIOChannel *chan, GIOCondition cond, struct pending_connect *c) { int err, sk = 0; socklen_t len; GIOChannel *tchan; struct transaction_context *ctxt; 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); goto failed; } if (err != 0) { error("connect(): %s(%d)", strerror(err), err); error_connection_attempt_failed(c->conn, c->rq, err); goto failed; } ctxt = transaction_context_new(c->conn, c->rq, &c->dba, c->svc, sk, 0); if (!ctxt) { error_failed(c->conn, c->rq, ENOMEM); goto failed; } tchan = g_io_channel_unix_new(sk); g_io_add_watch_full(tchan, 0, G_IO_IN, (GIOFunc)svc_search_attr_req_cb, ctxt, (GDestroyNotify)transaction_context_free); if (sdp_send_req(ctxt, &err) < 0) { error("Can't send PDU: %s (%d)", strerror(err), err); error_failed(c->conn, c->rq, err); goto failed; } failed: pending_connects = slist_remove(pending_connects, c); pending_connect_free(c); return FALSE; } static int search_request(DBusConnection *conn, DBusMessage *msg, uint16_t dev_id, const char *dst, const char *svc, int *err) { struct pending_connect *c = NULL; GIOChannel *chan = NULL; bdaddr_t sba; struct sockaddr_l2 sa; int sk, watch = 0; // create L2CAP connection sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (sk < 0) { if (err) *err = errno; return -1; } chan = g_io_channel_unix_new(sk); sa.l2_family = AF_BLUETOOTH; sa.l2_psm = 0; hci_devba(dev_id, &sba); if (bacmp(&sba, BDADDR_ANY) != 0) { bacpy(&sa.l2_bdaddr, &sba); if (bind(sk, (struct sockaddr *) &sa, sizeof(sa)) < 0) { if (err) *err = errno; goto fail; } } sa.l2_psm = htobs(SDP_PSM); str2ba(dst, &sa.l2_bdaddr); c = pending_connect_new(conn, msg, &sa.l2_bdaddr, svc); if (!c) { if (err) *err = ENOMEM; goto fail; } fcntl(sk, F_SETFL, fcntl(sk, F_GETFL, 0)|O_NONBLOCK); if (connect(sk, (struct sockaddr *) &sa, sizeof(sa)) < 0) { if ( !(errno == EAGAIN || errno == EINPROGRESS)) { if (err) *err = errno; error("connect() failed:%s (%d)", strerror(errno), errno); goto fail; } watch = g_io_add_watch(chan, G_IO_OUT, (GIOFunc)sdp_client_connect_cb, c); pending_connects = slist_append(pending_connects, c); } else { sdp_client_connect_cb(chan, G_IO_OUT, c); } return 0; fail: if (chan) g_io_channel_close(chan); if (c) pending_connect_free(c); return -1; } static DBusHandlerResult get_identifiers(DBusConnection *conn, DBusMessage *msg, void *data) { char filename[PATH_MAX + 1]; struct hci_dbus_data *dbus_data = data; struct service_provider *p; struct slist *l; const char *dst; char *str; DBusMessage *reply; DBusMessageIter iter, array_iter; bdaddr_t dba; int err = 0; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &dst, DBUS_TYPE_INVALID)) return error_invalid_arguments(conn, msg); /* FIXME: validate Bluetooth address(dst) */ str2ba(dst, &dba); reply = dbus_message_new_method_return(msg); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; p = service_provider_new(NULL, &dba); if (!p) { dbus_message_unref(reply); return DBUS_HANDLER_RESULT_NEED_MEMORY; } l = slist_find(sdp_cache, p, (cmp_func_t)service_provider_cmp); service_provider_free(p); if (l) { struct slist *lr; struct service_record *r; /* check the cache */ dbus_message_iter_init_append(reply, &iter); dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, DBUS_TYPE_UINT32_AS_STRING, &array_iter); p = l->data; for (lr = p->lrec; lr; lr = lr->next) { r = lr->data; dbus_message_iter_append_basic(&array_iter, DBUS_TYPE_UINT32, &r->identifier); } dbus_message_iter_close_container(&iter, &array_iter); return send_reply_and_unref(conn, reply); } if (find_pending_connect(&dba)) return error_service_search_in_progress(conn, msg); /* check if it is a unknown address */ create_name(filename, PATH_MAX, STORAGEDIR, dbus_data->address, "lastseen"); str = textfile_get(filename, dst); if (!str) return error_unknown_address(conn, msg); free(str); if (search_request(conn, msg, dbus_data->dev_id, dst, NULL, &err) < 0) { error("Search request failed: %s (%d)", strerror(err), err); return error_failed(conn, msg, err); } return DBUS_HANDLER_RESULT_HANDLED; } static DBusHandlerResult get_identifiers_by_service(DBusConnection *conn, DBusMessage *msg, void *data) { return DBUS_HANDLER_RESULT_NOT_YET_HANDLED; } sdp_record_t *find_record(uint32_t identifier) { struct slist *lp, *lr; struct service_provider *p; struct service_record *r; for (lp = sdp_cache; lp; lp = lp->next) { p = lp->data; for (lr = p->lrec; lr; lr = lr->next) { r = lr->data; if (r->identifier == identifier) return r->record; } } return NULL; } static DBusHandlerResult get_uuid(DBusConnection *conn, DBusMessage *msg, void *data) { char uuid_str[MAX_LEN_UUID_STR]; sdp_list_t *l; DBusMessage *reply; sdp_record_t *rec; char *ptr = uuid_str; uint32_t identifier; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &identifier, DBUS_TYPE_INVALID)) return error_invalid_arguments(conn, msg); rec = find_record(identifier); if (!rec) return error_record_does_not_exist(conn, msg); memset(uuid_str, 0, MAX_LEN_UUID_STR); reply = dbus_message_new_method_return(msg); if (sdp_get_profile_descs(rec, &l) == 0) { sdp_profile_desc_t *desc = (sdp_profile_desc_t *)l->data; sdp_uuid2strn(&desc->uuid, uuid_str, MAX_LEN_UUID_STR); sdp_list_free(l, free); } dbus_message_append_args(reply, DBUS_TYPE_STRING, &ptr, DBUS_TYPE_INVALID); return send_reply_and_unref(conn, reply); } static DBusHandlerResult get_name(DBusConnection *conn, DBusMessage *msg, void *data) { DBusMessage *reply; sdp_record_t *rec; sdp_data_t *d; char name[] = ""; char *ptr = name; uint32_t identifier; if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_UINT32, &identifier, DBUS_TYPE_INVALID)) return error_invalid_arguments(conn, msg); rec = find_record(identifier); if (!rec) return error_record_does_not_exist(conn, msg); reply = dbus_message_new_method_return(msg); d = sdp_data_get(rec, SDP_ATTR_SVCNAME_PRIMARY); if (d && d->val.str) ptr = d->val.str; dbus_message_append_args(reply, DBUS_TYPE_STRING, &ptr, DBUS_TYPE_INVALID); return send_reply_and_unref(conn, reply); } static DBusHandlerResult register_rfcomm(DBusConnection *conn, DBusMessage *msg, void *data) { struct hci_dbus_data *dbus_data = data; struct service_provider ref; DBusMessage *reply; const char *owner, *name; bdaddr_t sba; uint32_t identifier = 0, handle = 0; uint8_t channel; owner = dbus_message_get_sender(msg); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &name, DBUS_TYPE_BYTE, &channel, DBUS_TYPE_INVALID)) return error_invalid_arguments(conn, msg); /* FIXME: register the service */ hci_devba(dbus_data->dev_id, &sba); identifier = gen_next_id(&sba, handle); reply = dbus_message_new_method_return(msg); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; dbus_message_append_args(msg, DBUS_TYPE_UINT32, &identifier, DBUS_TYPE_INVALID); /* Only add a D-Bus unique name listener if there isn't one already registered */ memset(&ref, 0, sizeof(ref)); bacpy(&ref.prov, BDADDR_ANY); if (!slist_find(sdp_cache, &ref, (cmp_func_t)service_provider_cmp)) name_listener_add(conn, owner, (name_cb_t)owner_exited, dbus_data); /* FIXME: register the RFCOMM service */ return send_reply_and_unref(conn, reply); } static DBusHandlerResult unregister_rfcomm(DBusConnection *conn, DBusMessage *msg, void *data) { struct hci_dbus_data *dbus_data = data; struct service_provider *p, ref; struct slist *match; DBusMessage *reply; const char *owner, *identifier; owner = dbus_message_get_sender(msg); if (!dbus_message_get_args(msg, NULL, DBUS_TYPE_STRING, &identifier, DBUS_TYPE_INVALID)) return error_invalid_arguments(conn, msg); memset(&ref, 0, sizeof(ref)); hci_devba(dbus_data->dev_id, &ref.prov); ref.owner = (char *) owner; match = slist_find(sdp_cache, &ref, (cmp_func_t)service_provider_cmp); if (!match) return error_service_does_not_exist(conn, msg); /* FIXME: find the RFCOMM UUID in the list */ p = match->data; if (strcmp(p->owner, owner)) return error_not_authorized(conn, msg); reply = dbus_message_new_method_return(msg); if (!reply) return DBUS_HANDLER_RESULT_NEED_MEMORY; /* FIXME: unregister the service */ sdp_cache = slist_remove(sdp_cache, p); service_provider_free(p); bacpy(&ref.prov, BDADDR_ANY); /* Only remove the D-Bus unique name listener if there are no more record using this name */ if (!slist_find(sdp_cache, &ref, (cmp_func_t)service_provider_cmp)) name_listener_remove(conn, owner, (name_cb_t)owner_exited, dbus_data); return send_reply_and_unref(conn, reply); } static struct service_data sdp_services[] = { { "GetIdentifiers", get_identifiers }, { "GetIdentifiersByService", get_identifiers_by_service }, { "GetUUID", get_uuid }, { "GetName", get_name }, { "RegisterRFCOMM", register_rfcomm }, { "UnregisterRFCOMM", unregister_rfcomm }, { NULL, NULL } }; DBusHandlerResult handle_sdp_method(DBusConnection *conn, DBusMessage *msg, void *data) { service_handler_func_t handler; if (!hcid_dbus_use_experimental()) return error_unknown_method(conn, msg); handler = find_service_handler(sdp_services, msg); if (handler) return handler(conn, msg, data); return error_unknown_method(conn, msg); }