| /* |
| * |
| * BlueZ - Bluetooth protocol stack for Linux |
| * |
| * Copyright (C) 2004-2006 Marcel Holtmann <marcel@holtmann.org> |
| * |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License as published by |
| * the Free Software Foundation; either version 2 of the License, or |
| * (at your option) any later version. |
| * |
| * This program is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| * GNU General Public License for more details. |
| * |
| * You should have received a copy of the GNU General Public License |
| * along with this program; if not, write to the Free Software |
| * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA |
| * |
| */ |
| |
| #ifdef HAVE_CONFIG_H |
| #include <config.h> |
| #endif |
| |
| #include <stdio.h> |
| #include <errno.h> |
| #include <unistd.h> |
| |
| #include <fcntl.h> |
| |
| #include <sys/types.h> |
| #include <sys/param.h> |
| #include <sys/socket.h> |
| |
| #include <bluetooth/bluetooth.h> |
| #include <bluetooth/hci.h> |
| #include <bluetooth/hci_lib.h> |
| #include <bluetooth/l2cap.h> |
| #include <bluetooth/sdp.h> |
| #include <bluetooth/sdp_lib.h> |
| |
| #include <netinet/in.h> |
| |
| #include <dbus/dbus.h> |
| |
| #include "dbus.h" |
| #include "hcid.h" |
| #include "textfile.h" |
| |
| #define MAX_IDENTIFIER_LEN 29 /* "XX:XX:XX:XX:XX:XX/0xYYYYYYYY\0" */ |
| |
| struct service_provider { |
| char *owner; /* null for remote services or unique name if local */ |
| char *prov; /* remote Bluetooth address that provides the service */ |
| struct slist *lrec; |
| }; |
| |
| struct service_record { |
| int ttl; /* time to live */ |
| sdp_record_t *record; |
| }; |
| |
| struct transaction_context { |
| DBusConnection *conn; |
| DBusMessage *rq; |
| sdp_session_t *session; |
| |
| /* Used for internal async get remote service record implementation */ |
| void *priv; |
| }; |
| |
| typedef int connect_cb_t(struct transaction_context *t); |
| struct pending_connect { |
| DBusConnection *conn; |
| DBusMessage *rq; |
| |
| char *dst; |
| sdp_session_t *session; |
| connect_cb_t *conn_cb; |
| |
| /* Used for internal async get remote service record implementation */ |
| void *priv; |
| }; |
| |
| /* FIXME: move to a common file */ |
| typedef struct { |
| char *name; |
| uint16_t class; |
| char *info_name; |
| } sdp_service_t; |
| |
| /* FIXME: move to a common file */ |
| sdp_service_t sdp_service[] = { |
| { "vcp", VIDEO_CONF_SVCLASS_ID, "Video Conference" }, |
| { "map", 0, NULL }, |
| { "pbap", PBAP_SVCLASS_ID, "Phone Book Access" }, |
| { "sap", SAP_SVCLASS_ID, "SIM Access" }, |
| { "ftp", OBEX_FILETRANS_SVCLASS_ID, "OBEX File Transfer" }, |
| { "bpp", BASIC_PRINTING_SVCLASS_ID, "Printing" }, |
| { "bip", IMAGING_SVCLASS_ID, "Imaging" }, |
| { "synch", IRMC_SYNC_SVCLASS_ID, "Synchronization" }, |
| { "dun", DIALUP_NET_SVCLASS_ID, "Dial-Up Networking" }, |
| { "opp", OBEX_OBJPUSH_SVCLASS_ID, "OBEX Object Push" }, |
| { "fax", FAX_SVCLASS_ID, "Fax" }, |
| { "spp", SERIAL_PORT_SVCLASS_ID, "Serial Port" }, |
| { NULL } |
| }; |
| |
| /* FIXME: move to a common file */ |
| uint16_t sdp_str2svclass(const char *str) |
| { |
| sdp_service_t *s; |
| |
| for (s = sdp_service; s->name; s++) { |
| if (strcasecmp(s->name, str) == 0) |
| return s->class; |
| } |
| |
| return 0; |
| } |
| |
| /* FIXME: move to a common file */ |
| const char* sdp_svclass2str(uint16_t class) |
| { |
| sdp_service_t *s; |
| |
| for (s = sdp_service; s->name; s++) { |
| if (s->class == class) |
| return s->name; |
| } |
| |
| return NULL; |
| } |
| |
| /* FIXME: stub for service registration. Shared with sdptool */ |
| static inline sdp_record_t *sdp_service_register(const char *name, bdaddr_t *interface, |
| uint8_t channel, int *err) |
| { |
| if (err) |
| *err = ENOSYS; |
| |
| return NULL; |
| } |
| |
| /* FIXME: stub for service registration. Shared with sdptool */ |
| static inline int sdp_service_unregister(bdaddr_t *interface, sdp_record_t *rec, int *err) |
| { |
| if (err) |
| *err = ENOSYS; |
| |
| return -1; |
| } |
| |
| /* list of remote and local service records */ |
| static struct slist *sdp_cache = NULL; |
| static struct slist *pending_connects = NULL; |
| |
| static struct pending_connect *pending_connect_new(DBusConnection *conn, DBusMessage *msg, |
| const char *dst, connect_cb_t *cb) |
| { |
| struct pending_connect *c; |
| |
| if (!dst) |
| return NULL; |
| |
| c = malloc(sizeof(*c)); |
| if (!c) |
| return NULL; |
| memset(c, 0, sizeof(*c)); |
| |
| if (dst) { |
| c->dst = strdup(dst); |
| if (!c->dst) { |
| free(c); |
| return NULL; |
| } |
| } |
| |
| c->conn = dbus_connection_ref(conn); |
| c->rq = dbus_message_ref(msg); |
| c->conn_cb = cb; |
| |
| return c; |
| } |
| |
| static void pending_connect_free(struct pending_connect *c) |
| { |
| if (!c) |
| return; |
| |
| if (c->dst) |
| free(c->dst); |
| |
| 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 char *dst) |
| { |
| struct slist *l; |
| |
| for (l = pending_connects; l != NULL; l = l->next) { |
| struct pending_connect *pending = l->data; |
| if (!strcmp(dst, pending->dst)) |
| return pending; |
| } |
| |
| return NULL; |
| } |
| |
| static int str2identifier(const char *identifier, char *address, |
| uint32_t *handle) |
| { |
| if (!identifier || !address) |
| return -1; |
| |
| if (strlen(identifier) < 19) |
| return -1; |
| |
| memset(address, 0, 18); |
| snprintf(address, 18, "%s", identifier); |
| |
| return (sscanf(identifier + 18, "%x", handle) > 0 ? 0 : -1); |
| } |
| |
| static struct service_record *service_record_new(sdp_record_t *rec) |
| { |
| struct service_record *r; |
| |
| r = malloc(sizeof(*r)); |
| if (!r) |
| return NULL; |
| |
| memset(r, 0, sizeof(*r)); |
| r->record = rec; |
| |
| return r; |
| } |
| |
| static void service_record_free(void *data, void *udata) |
| { |
| struct service_record *r = data; |
| |
| if (!r) |
| return; |
| |
| if (r->record) |
| sdp_record_free(r->record); |
| |
| free(r); |
| } |
| |
| /* |
| * This function doesn't check service record pattern |
| */ |
| static int service_record_cmp(const void *data, const void *udata) |
| { |
| const struct service_record *a = data; |
| const struct service_record *b = udata; |
| |
| if (b->record) { |
| if (b->record->handle != 0xffffffff && |
| b->record->handle != a->record->handle) |
| return -1; |
| } |
| |
| return 0; |
| } |
| |
| static void service_provider_free(void *data, void *udata) |
| { |
| struct service_provider *p1 = data; |
| struct service_provider *p2 = udata; |
| |
| if (!p1) |
| return; |
| |
| /* Check if the provider match */ |
| if (p2) { |
| if (p2->owner && strcmp(p2->owner, p1->owner)) |
| return; |
| if (p2->prov && strcmp(p2->prov, p1->prov)) |
| return; |
| } |
| |
| if (p1->owner) |
| free(p1->owner); |
| |
| if (p1->prov) |
| free(p1->prov); |
| |
| if (p1->lrec) { |
| slist_foreach(p1->lrec, service_record_free, NULL); |
| slist_free(p1->lrec); |
| } |
| |
| free(p1); |
| } |
| |
| static struct service_provider *service_provider_new(const char *owner, const char *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; |
| } |
| |
| if (prov) { |
| p->prov = strdup(prov); |
| if (!p->prov) |
| goto fail; |
| } |
| |
| return p; |
| |
| fail: |
| service_provider_free(p, NULL); |
| return NULL; |
| } |
| |
| static int service_provider_cmp(const void *data, const void *udata) |
| { |
| const struct service_provider *a = data; |
| const struct service_provider *b = udata; |
| int ret; |
| |
| if (b->owner) { |
| if (!a->owner) |
| return -1; |
| ret = strcmp(a->owner, b->owner); |
| if (ret) |
| return ret; |
| } |
| |
| if (b->prov) { |
| if (!a->prov) |
| return -1; |
| ret = strcmp(a->prov, b->prov); |
| if (ret) |
| return ret; |
| } |
| |
| return 0; |
| } |
| |
| static int sdp_cache_append(const char *owner, const char *prov, sdp_record_t *rec) |
| { |
| struct slist *lp, *lr; |
| struct service_provider *p; |
| struct service_provider psearch; |
| struct service_record r, *sr; |
| |
| if (!prov || !rec) |
| return -1; |
| |
| memset(&psearch, 0, sizeof(psearch)); |
| psearch.owner = (char *) owner; |
| psearch.prov = (char *) prov; |
| |
| lp = slist_find(sdp_cache, &psearch, service_provider_cmp); |
| if (!lp) { |
| p = service_provider_new(owner, prov); |
| sdp_cache = slist_append(sdp_cache, p); |
| } else |
| p = lp->data; |
| |
| /* check if the service record already belongs to the cache */ |
| r.record = sdp_record_alloc(); |
| r.record->handle = rec->handle; |
| lr = slist_find(p->lrec, &r, service_record_cmp); |
| sdp_record_free(r.record); |
| |
| if (lr) { |
| /* overwrite the record instead of compare */ |
| sr = lr->data; |
| sdp_record_free(sr->record); |
| sr->record = rec; |
| } else { |
| /* create a new entry */ |
| sr = service_record_new(rec); |
| p->lrec = slist_append(p->lrec, sr); |
| } |
| |
| return 0; |
| } |
| |
| static void transaction_context_free(void *udata) |
| { |
| struct transaction_context *ctxt = udata; |
| |
| if (!ctxt) |
| return; |
| |
| if (ctxt->conn) |
| dbus_connection_unref(ctxt->conn); |
| |
| if (ctxt->rq) |
| dbus_message_unref(ctxt->rq); |
| |
| if (ctxt->session) |
| sdp_close(ctxt->session); |
| |
| free(ctxt); |
| } |
| |
| typedef struct { |
| uint16_t dev_id; |
| char *dst; |
| void *search_data; |
| get_record_cb_t *cb; |
| void *data; |
| } get_record_data_t; |
| |
| static get_record_data_t *get_record_data_new(uint16_t dev_id, const char *dst, |
| void *search_data, |
| get_record_cb_t *cb, void *data) |
| { |
| get_record_data_t *n; |
| |
| n = malloc(sizeof(*n)); |
| if (!n) |
| return NULL; |
| |
| n->dst = strdup(dst); |
| if (!n->dst) { |
| free(n); |
| return NULL; |
| } |
| |
| n->dev_id = dev_id; |
| n->search_data = search_data; |
| n->cb = cb; |
| n->data = data; |
| |
| return n; |
| } |
| |
| static void get_record_data_free(get_record_data_t *d) |
| { |
| free(d->search_data); |
| free(d->dst); |
| free(d); |
| } |
| |
| static inline void get_record_data_call_cb(get_record_data_t *d, |
| sdp_record_t *rec, int err) |
| { |
| d->cb(rec, d->data, err); |
| } |
| |
| static void owner_exited(const char *owner, struct hci_dbus_data *dbus_data) |
| { |
| struct slist *lp, *next, *lr; |
| struct service_provider *p; |
| bdaddr_t sba; |
| int err = 0; |
| debug("SDP provider owner %s exited", owner); |
| |
| for (lp = sdp_cache; lp; lp = next) { |
| |
| next = lp->next; |
| p = lp->data; |
| |
| if (!p->owner || strcmp(p->owner, owner)) |
| continue; |
| |
| /* |
| * Unregister all service records related to this owner. |
| * One owner can use multiple local adapter(provider) |
| */ |
| str2ba(dbus_data->address, &sba); |
| for (lr = p->lrec; lr; lr = lr->next) { |
| struct service_record *r = lr->data; |
| if (sdp_service_unregister(&sba, r->record, &err) < 0) |
| error("unregister error: %s (%d)", strerror(err), err); |
| else |
| /* free inside the library */ |
| r->record = NULL; |
| } |
| |
| /* remove from the cache */ |
| sdp_cache = slist_remove(sdp_cache, p); |
| |
| service_provider_free(p, NULL); |
| } |
| } |
| |
| static gboolean search_process_cb(GIOChannel *chan, |
| GIOCondition cond, void *udata) |
| { |
| struct transaction_context *ctxt = udata; |
| int err = 0; |
| |
| if (cond & G_IO_NVAL) { |
| g_io_channel_unref(chan); |
| return FALSE; |
| } |
| |
| if (cond & (G_IO_ERR | G_IO_HUP)) { |
| err = EIO; |
| goto failed; |
| } |
| |
| if (sdp_process(ctxt->session) < 0) |
| goto failed; |
| |
| return TRUE; |
| |
| failed: |
| if (err) { |
| if (ctxt->priv) { |
| get_record_data_call_cb(ctxt->priv, NULL, err); |
| get_record_data_free(ctxt->priv); |
| } else |
| error_failed(ctxt->conn, ctxt->rq, err); |
| |
| transaction_context_free(ctxt); |
| } |
| |
| return TRUE; |
| } |
| |
| static void remote_svc_rec_completed_cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t size, void *udata) |
| { |
| struct transaction_context *ctxt = udata; |
| char identifier[MAX_IDENTIFIER_LEN]; |
| sdp_record_t *rec = NULL; |
| DBusMessage *reply; |
| DBusMessageIter iter, array_iter; |
| const char *dst; |
| int i, scanned; |
| |
| if (!ctxt) |
| return; |
| |
| if (err == 0xffff) { |
| /* Check for protocol error or I/O error */ |
| int sdp_err = sdp_get_error(ctxt->session); |
| if (sdp_err < 0) { |
| error("search failed: Invalid session!"); |
| error_failed(ctxt->conn, ctxt->rq, EINVAL); |
| goto failed; |
| } |
| |
| error("search failed: %s (%d)", strerror(sdp_err), sdp_err); |
| error_failed(ctxt->conn, ctxt->rq, sdp_err); |
| goto failed; |
| } |
| |
| if (type == SDP_ERROR_RSP) { |
| error_sdp_failed(ctxt->conn, ctxt->rq, err); |
| goto failed; |
| } |
| |
| /* check response PDU ID */ |
| if (type != SDP_SVC_ATTR_RSP) { |
| error("SDP error: %s (%d)", strerror(EPROTO), EPROTO); |
| error_failed(ctxt->conn, ctxt->rq, EPROTO); |
| goto failed; |
| } |
| |
| dbus_message_get_args(ctxt->rq, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_INVALID); |
| |
| reply = dbus_message_new_method_return(ctxt->rq); |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_BYTE_AS_STRING, &array_iter); |
| |
| rec = sdp_extract_pdu(rsp, &scanned); |
| if (rec == NULL) { |
| error("SVC REC is null"); |
| goto done; |
| } |
| |
| sdp_cache_append(NULL, dst, rec); |
| snprintf(identifier, MAX_IDENTIFIER_LEN, "%s/0x%x", dst, rec->handle); |
| |
| /* FIXME: avoid seg fault / out of bound */ |
| for (i = 0; i < size; i++) |
| dbus_message_iter_append_basic(&array_iter, |
| DBUS_TYPE_BYTE, &rsp[i]); |
| |
| done: |
| dbus_message_iter_close_container(&iter, &array_iter); |
| send_reply_and_unref(ctxt->conn, reply); |
| failed: |
| transaction_context_free(ctxt); |
| } |
| |
| static void remote_svc_handles_completed_cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t size, void *udata) |
| { |
| struct transaction_context *ctxt = udata; |
| DBusMessage *reply; |
| DBusMessageIter iter, array_iter; |
| uint8_t *pdata; |
| int scanned, csrc, tsrc; |
| |
| if (!ctxt) |
| return; |
| |
| if (err == 0xffff) { |
| /* Check for protocol error or I/O error */ |
| int sdp_err = sdp_get_error(ctxt->session); |
| if (sdp_err < 0) { |
| error("search failed: Invalid session!"); |
| error_failed(ctxt->conn, ctxt->rq, EINVAL); |
| goto failed; |
| } |
| |
| error("search failed: %s (%d)", strerror(sdp_err), sdp_err); |
| error_failed(ctxt->conn, ctxt->rq, sdp_err); |
| goto failed; |
| } |
| |
| if (type == SDP_ERROR_RSP) { |
| error_sdp_failed(ctxt->conn, ctxt->rq, err); |
| goto failed; |
| } |
| |
| /* check response PDU ID */ |
| if (type != SDP_SVC_SEARCH_RSP) { |
| error("SDP error: %s (%d)", strerror(EPROTO), EPROTO); |
| error_failed(ctxt->conn, ctxt->rq, EPROTO); |
| goto failed; |
| } |
| |
| reply = dbus_message_new_method_return(ctxt->rq); |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_UINT32_AS_STRING, &array_iter); |
| |
| pdata = rsp; |
| |
| tsrc = ntohs(bt_get_unaligned((uint16_t *) pdata)); |
| if (tsrc <= 0) |
| goto done; |
| |
| pdata += sizeof(uint16_t); |
| scanned = sizeof(uint16_t); |
| |
| csrc = ntohs(bt_get_unaligned((uint16_t *) pdata)); |
| if (csrc <= 0) |
| goto done; |
| |
| pdata += sizeof(uint16_t); |
| scanned += sizeof(uint16_t); |
| |
| do { |
| uint32_t handle = ntohl(bt_get_unaligned((uint32_t*)pdata)); |
| scanned += sizeof(uint32_t); |
| pdata += sizeof(uint32_t); |
| |
| dbus_message_iter_append_basic(&array_iter, |
| DBUS_TYPE_UINT32, &handle); |
| } while (--tsrc); |
| |
| |
| done: |
| dbus_message_iter_close_container(&iter, &array_iter); |
| send_reply_and_unref(ctxt->conn, reply); |
| failed: |
| transaction_context_free(ctxt); |
| } |
| |
| static void search_completed_cb(uint8_t type, uint16_t err, uint8_t *rsp, size_t size, void *udata) |
| { |
| struct transaction_context *ctxt = udata; |
| char identifier[MAX_IDENTIFIER_LEN]; |
| const char *ptr = identifier; |
| DBusMessage *reply; |
| DBusMessageIter iter, array_iter; |
| const char *dst; |
| uint8_t *pdata; |
| int scanned, csrc, tsrc; |
| |
| if (!ctxt) |
| return; |
| |
| if (err == 0xffff) { |
| /* Check for protocol error or I/O error */ |
| int sdp_err = sdp_get_error(ctxt->session); |
| if (sdp_err < 0) { |
| error("search failed: Invalid session!"); |
| error_failed(ctxt->conn, ctxt->rq, EINVAL); |
| goto failed; |
| } |
| |
| error("search failed: %s (%d)", strerror(sdp_err), sdp_err); |
| error_failed(ctxt->conn, ctxt->rq, sdp_err); |
| goto failed; |
| } |
| |
| if (type == SDP_ERROR_RSP) { |
| error_sdp_failed(ctxt->conn, ctxt->rq, err); |
| goto failed; |
| } |
| |
| /* check response PDU ID */ |
| if (type != SDP_SVC_SEARCH_RSP) { |
| error("SDP error: %s (%d)", strerror(EPROTO), EPROTO); |
| error_failed(ctxt->conn, ctxt->rq, EPROTO); |
| goto failed; |
| } |
| |
| dbus_message_get_args(ctxt->rq, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_INVALID); |
| |
| reply = dbus_message_new_method_return(ctxt->rq); |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array_iter); |
| |
| pdata = rsp; |
| |
| tsrc = ntohs(bt_get_unaligned((uint16_t *) pdata)); |
| if (tsrc <= 0) |
| goto done; |
| |
| pdata += sizeof(uint16_t); |
| scanned = sizeof(uint16_t); |
| |
| csrc = ntohs(bt_get_unaligned((uint16_t *) pdata)); |
| if (csrc <= 0) |
| goto done; |
| |
| pdata += sizeof(uint16_t); |
| scanned += sizeof(uint16_t); |
| |
| do { |
| uint32_t handle = ntohl(bt_get_unaligned((uint32_t*)pdata)); |
| scanned += sizeof(uint32_t); |
| pdata += sizeof(uint32_t); |
| |
| snprintf(identifier, MAX_IDENTIFIER_LEN, "%s/0x%x", dst, handle); |
| |
| dbus_message_iter_append_basic(&array_iter, |
| DBUS_TYPE_STRING, &ptr); |
| } while (--tsrc); |
| |
| done: |
| dbus_message_iter_close_container(&iter, &array_iter); |
| send_reply_and_unref(ctxt->conn, reply); |
| failed: |
| transaction_context_free(ctxt); |
| } |
| |
| static gboolean sdp_client_connect_cb(GIOChannel *chan, |
| GIOCondition cond, void *udata) |
| { |
| struct pending_connect *c = udata; |
| struct transaction_context *ctxt = NULL; |
| int sdp_err, err = 0, sk; |
| socklen_t len; |
| |
| sk = g_io_channel_unix_get_fd(chan); |
| |
| len = sizeof(err); |
| if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { |
| error("getsockopt(): %s (%d)", strerror(errno), errno); |
| err = errno; |
| goto failed; |
| } |
| if (err != 0) { |
| error("connect(): %s (%d)", strerror(err), err); |
| goto failed; |
| } |
| |
| ctxt = malloc(sizeof(*ctxt)); |
| if (!ctxt) { |
| err = ENOMEM; |
| goto failed; |
| } |
| memset(ctxt, 0, sizeof(*ctxt)); |
| |
| ctxt->conn = dbus_connection_ref(c->conn); |
| ctxt->rq = dbus_message_ref(c->rq); |
| ctxt->session = c->session; |
| if (c->priv) |
| ctxt->priv = c->priv; |
| |
| /* set the complete transaction callback and send the search request */ |
| sdp_err = c->conn_cb(ctxt); |
| if (sdp_err < 0) { |
| err = -sdp_err; |
| error("search failed: %s (%d)", strerror(err), err); |
| goto failed; |
| } |
| |
| /* set the callback responsible for update the transaction data */ |
| g_io_add_watch(chan, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL, |
| search_process_cb, ctxt); |
| goto done; |
| |
| failed: |
| if (c->priv) |
| get_record_data_call_cb(c->priv, NULL, err); |
| else |
| error_connection_attempt_failed(c->conn, c->rq, err); |
| |
| if (c->priv) |
| get_record_data_free(c->priv); |
| |
| if (ctxt) |
| transaction_context_free(ctxt); |
| else |
| sdp_close(c->session); |
| |
| g_io_channel_unref(chan); |
| |
| done: |
| pending_connects = slist_remove(pending_connects, c); |
| pending_connect_free(c); |
| |
| return FALSE; |
| } |
| |
| static struct pending_connect *connect_request(DBusConnection *conn, |
| DBusMessage *msg, |
| uint16_t dev_id, |
| const char *dst, |
| connect_cb_t *cb, int *err) |
| { |
| struct pending_connect *c; |
| bdaddr_t srcba, dstba; |
| GIOChannel *chan; |
| |
| c = pending_connect_new(conn, msg, dst, cb); |
| if (!c) { |
| if (err) |
| *err = ENOMEM; |
| return NULL; |
| } |
| |
| hci_devba(dev_id, &srcba); |
| str2ba(dst, &dstba); |
| |
| c->session = sdp_connect(&srcba, &dstba, SDP_NON_BLOCKING); |
| if (!c->session) { |
| if (err) |
| *err = errno; |
| error("sdp_connect() failed: %s (%d)", strerror(errno), errno); |
| pending_connect_free(c); |
| return NULL; |
| } |
| |
| chan = g_io_channel_unix_new(sdp_get_socket(c->session)); |
| g_io_add_watch(chan, G_IO_OUT, sdp_client_connect_cb, c); |
| pending_connects = slist_append(pending_connects, c); |
| |
| return c; |
| } |
| |
| static int remote_svc_rec_conn_cb(struct transaction_context *ctxt) |
| { |
| sdp_list_t *attrids = NULL; |
| uint32_t range = 0x0000ffff; |
| const char *dst; |
| uint32_t handle; |
| int err = 0; |
| |
| if (sdp_set_notify(ctxt->session, remote_svc_rec_completed_cb, ctxt) < 0) { |
| err = -EINVAL; |
| goto fail; |
| } |
| |
| dbus_message_get_args(ctxt->rq, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_UINT32, &handle, |
| DBUS_TYPE_INVALID); |
| |
| attrids = sdp_list_append(NULL, &range); |
| /* Create/send the search request and set the callback to indicate the request completion */ |
| if (sdp_service_attr_async(ctxt->session, handle, SDP_ATTR_REQ_RANGE, attrids) < 0) { |
| err = -sdp_get_error(ctxt->session); |
| goto fail; |
| } |
| |
| fail: |
| if (attrids) |
| sdp_list_free(attrids, NULL); |
| |
| return err; |
| } |
| |
| DBusHandlerResult get_remote_svc_rec(DBusConnection *conn, DBusMessage *msg, void *data) |
| { |
| struct hci_dbus_data *dbus_data = data; |
| const char *dst; |
| uint32_t handle; |
| int err = 0; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_UINT32, &handle, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| if (find_pending_connect(dst)) |
| return error_service_search_in_progress(conn, msg); |
| |
| if (!connect_request(conn, msg, dbus_data->dev_id, |
| dst, remote_svc_rec_conn_cb, &err)) { |
| error("Search request failed: %s (%d)", strerror(err), err); |
| return error_failed(conn, msg, err); |
| } |
| |
| return DBUS_HANDLER_RESULT_HANDLED; |
| } |
| |
| static int remote_svc_handles_conn_cb(struct transaction_context *ctxt) |
| { |
| sdp_list_t *search = NULL; |
| const char *dst, *svc; |
| uuid_t uuid; |
| int err = 0; |
| |
| if (sdp_set_notify(ctxt->session, remote_svc_handles_completed_cb, ctxt) < 0) { |
| err = -EINVAL; |
| goto fail; |
| } |
| |
| dbus_message_get_args(ctxt->rq, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_STRING, &svc, |
| DBUS_TYPE_INVALID); |
| |
| if (strlen(svc) > 0) |
| str2uuid(&uuid, svc); |
| else |
| sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); |
| |
| search = sdp_list_append(0, &uuid); |
| |
| /* Create/send the search request and set the callback to indicate the request completion */ |
| if (sdp_service_search_async(ctxt->session, search, 64) < 0) { |
| error("send request failed: %s (%d)", strerror(errno), errno); |
| err = -sdp_get_error(ctxt->session); |
| goto fail; |
| } |
| |
| fail: |
| if (search) |
| sdp_list_free(search, NULL); |
| |
| return err; |
| } |
| |
| DBusHandlerResult get_remote_svc_handles(DBusConnection *conn, DBusMessage *msg, void *data) |
| { |
| struct hci_dbus_data *dbus_data = data; |
| const char *dst, *svc; |
| int err = 0; |
| uuid_t uuid; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_STRING, &svc, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| if (strlen(svc) > 0) { |
| /* Check if it is a service name string */ |
| if (str2uuid(&uuid, svc) < 0) { |
| error("Invalid service class name"); |
| return error_invalid_arguments(conn, msg); |
| } |
| } |
| |
| if (find_pending_connect(dst)) |
| return error_service_search_in_progress(conn, msg); |
| |
| if (!connect_request(conn, msg, dbus_data->dev_id, |
| dst, remote_svc_handles_conn_cb, &err)) { |
| error("Search request failed: %s (%d)", strerror(err), err); |
| return error_failed(conn, msg, err); |
| } |
| |
| return DBUS_HANDLER_RESULT_HANDLED; |
| } |
| |
| static int get_identifiers_conn_cb(struct transaction_context *ctxt) |
| { |
| sdp_list_t *search = NULL; |
| uuid_t uuid; |
| int err = 0; |
| |
| if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) { |
| err = -EINVAL; |
| goto fail; |
| } |
| |
| sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); |
| search = sdp_list_append(0, &uuid); |
| |
| /* Create/send the search request and set the callback to indicate the request completion */ |
| if (sdp_service_search_async(ctxt->session, search, 64) < 0) { |
| err = -sdp_get_error(ctxt->session); |
| goto fail; |
| } |
| |
| fail: |
| if (search) |
| sdp_list_free(search, NULL); |
| |
| return err; |
| } |
| |
| static DBusHandlerResult get_identifiers(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct hci_dbus_data *dbus_data = data; |
| const char *dst; |
| int err = 0; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| /* in progress is not working properly */ |
| if (find_pending_connect(dst)) |
| return error_service_search_in_progress(conn, msg); |
| |
| if (!connect_request(conn, msg, dbus_data->dev_id, |
| dst, get_identifiers_conn_cb, &err)) { |
| error("Search request failed: %s (%d)", strerror(err), err); |
| return error_failed(conn, msg, err); |
| } |
| |
| return DBUS_HANDLER_RESULT_HANDLED; |
| } |
| |
| static int get_identifiers_by_service_conn_cb(struct transaction_context *ctxt) |
| { |
| sdp_list_t *search = NULL; |
| const char *dst, *svc; |
| uuid_t uuid; |
| uint16_t class; |
| int err = 0; |
| |
| if (sdp_set_notify(ctxt->session, search_completed_cb, ctxt) < 0) { |
| err = -EINVAL; |
| goto fail; |
| } |
| |
| dbus_message_get_args(ctxt->rq, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_STRING, &svc, |
| DBUS_TYPE_INVALID); |
| |
| class = sdp_str2svclass(svc); |
| sdp_uuid16_create(&uuid, class); |
| search = sdp_list_append(0, &uuid); |
| |
| /* Create/send the search request and set the callback to indicate the request completion */ |
| if (sdp_service_search_async(ctxt->session, search, 64) < 0) { |
| err = -sdp_get_error(ctxt->session); |
| goto fail; |
| } |
| |
| fail: |
| if (search) |
| sdp_list_free(search, NULL); |
| |
| return err; |
| } |
| |
| static DBusHandlerResult get_identifiers_by_service(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| struct hci_dbus_data *dbus_data = data; |
| DBusMessage *reply; |
| DBusMessageIter iter, array_iter; |
| struct slist *lp; |
| struct slist *lr; |
| struct service_provider *p; |
| struct service_record *r; |
| char identifier[MAX_IDENTIFIER_LEN]; |
| const char *ptr = identifier; |
| const char *dst, *svc; |
| int err = 0, nrec = 0; |
| uint32_t class; |
| uuid_t uuid; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &dst, |
| DBUS_TYPE_STRING, &svc, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| class = sdp_str2svclass(svc); |
| if (!class) { |
| error("Invalid service class name"); |
| return error_invalid_arguments(conn, msg); |
| } |
| |
| sdp_uuid16_create(&uuid, class); |
| |
| p = service_provider_new(NULL, dst); |
| if (!p) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| /* FIXME: return cache entry or query again? */ |
| lp = slist_find(sdp_cache, p, service_provider_cmp); |
| service_provider_free(p, NULL); |
| |
| if (!lp) |
| goto search_request; |
| |
| reply = dbus_message_new_method_return(msg); |
| dbus_message_iter_init_append(reply, &iter); |
| dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, |
| DBUS_TYPE_STRING_AS_STRING, &array_iter); |
| |
| p = lp->data; |
| for (lr = p->lrec; lr; lr = lr->next) { |
| sdp_list_t *ls; |
| uuid_t *puuid; |
| r = lr->data; |
| /* check if the pattern match */ |
| if (sdp_get_service_classes(r->record, &ls)) |
| continue; |
| |
| puuid = (uuid_t *) ls->data; |
| |
| if (sdp_uuid16_cmp(puuid, &uuid) == 0) { |
| snprintf(identifier, MAX_IDENTIFIER_LEN, "%s/0x%x", p->prov, r->record->handle); |
| dbus_message_iter_append_basic(&array_iter, |
| DBUS_TYPE_STRING, &ptr); |
| nrec++; |
| } |
| |
| sdp_list_free(ls, free); |
| |
| } |
| |
| dbus_message_iter_close_container(&iter, &array_iter); |
| |
| if (nrec > 0) |
| return send_reply_and_unref(conn, reply); |
| |
| /* no record found: request search */ |
| dbus_message_unref(reply); |
| |
| search_request: |
| if (find_pending_connect(dst)) |
| return error_service_search_in_progress(conn, msg); |
| |
| if (!connect_request(conn, msg, dbus_data->dev_id, |
| dst, get_identifiers_by_service_conn_cb, &err)) { |
| error("Search request failed: %s (%d)", strerror(err), err); |
| return error_failed(conn, msg, err); |
| } |
| |
| return DBUS_HANDLER_RESULT_HANDLED; |
| |
| } |
| |
| #if 0 |
| static int uuid_cmp(const void *key1, const void *key2) |
| { |
| uuid_t *a, *b; |
| int ret_val; |
| |
| /* converting to uuid128 */ |
| a = sdp_uuid_to_uuid128((uuid_t *) key1); |
| b = sdp_uuid_to_uuid128((uuid_t *) key2); |
| |
| ret_val = sdp_uuid128_cmp(a, b); |
| |
| bt_free(a); |
| bt_free(b); |
| |
| return ret_val; |
| } |
| |
| static sdp_record_t *find_record_by_uuid(const char *address, uuid_t *uuid) |
| { |
| struct slist *lp, *lr; |
| struct service_provider *p; |
| struct service_record *r; |
| sdp_list_t *list = NULL; |
| |
| for (lp = sdp_cache; lp; lp = lp->next) { |
| p = lp->data; |
| if (strcmp(p->prov, address)) |
| continue; |
| |
| for (lr = p->lrec; lr; lr = lr->next) { |
| r = lr->data; |
| /* Check whether the record has the correct uuid */ |
| if (sdp_get_service_classes(r->record, &list) != 0) |
| continue; |
| |
| if (sdp_list_find(list, uuid, uuid_cmp)) |
| return r->record; |
| } |
| } |
| |
| return NULL; |
| } |
| #endif |
| |
| static sdp_record_t *find_record_by_handle(const char *address, |
| uint32_t handle) |
| { |
| struct slist *lp, *lr; |
| struct service_provider *p; |
| struct service_record *r; |
| |
| for (lp = sdp_cache; lp; lp = lp->next) { |
| p = lp->data; |
| if (strcmp(p->prov, address)) |
| continue; |
| |
| for (lr = p->lrec; lr; lr = lr->next) { |
| r = lr->data; |
| if (r->record->handle == handle) |
| return r->record; |
| } |
| } |
| |
| return NULL; |
| } |
| |
| static DBusHandlerResult get_uuid(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| char uuid_str[MAX_LEN_UUID_STR]; |
| char address[18]; |
| sdp_list_t *ls; |
| DBusMessage *reply; |
| sdp_record_t *rec; |
| char *ptr = uuid_str; |
| const char *identifier; |
| uint32_t handle; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &identifier, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| if (str2identifier(identifier, address, &handle) != 0) |
| return error_invalid_arguments(conn, msg); |
| |
| rec = find_record_by_handle(address, handle); |
| 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_service_classes(rec, &ls) == 0) { |
| char tmp_str[MAX_LEN_UUID_STR]; |
| uuid_t *uuid = (uuid_t *) ls->data; |
| |
| if (sdp_uuid2strn(uuid, tmp_str, MAX_LEN_UUID_STR) != 0) |
| error("Can't convert UUID to string!"); |
| else |
| snprintf(uuid_str, MAX_LEN_UUID_STR, "0x%s", tmp_str); |
| |
| sdp_list_free(ls, 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) |
| { |
| char address[18]; |
| DBusMessage *reply; |
| sdp_record_t *rec; |
| sdp_list_t *ls; |
| char name[] = ""; |
| const char *ptr = name; |
| const char *identifier; |
| uuid_t *puuid; |
| uint32_t handle; |
| |
| if (!dbus_message_get_args(msg, NULL, |
| DBUS_TYPE_STRING, &identifier, |
| DBUS_TYPE_INVALID)) |
| return error_invalid_arguments(conn, msg); |
| |
| if (str2identifier(identifier, address, &handle) != 0) |
| return error_invalid_arguments(conn, msg); |
| |
| rec = find_record_by_handle(address, handle); |
| if (!rec) |
| return error_record_does_not_exist(conn, msg); |
| |
| if ((sdp_get_service_classes(rec, &ls)) < 0) { |
| return error_failed(conn, msg, errno); |
| } |
| |
| puuid = (uuid_t *) ls->data; |
| |
| ptr = sdp_svclass2str(puuid->value.uuid16); |
| sdp_list_free(ls, free); |
| |
| /* return empty string for non supported services */ |
| if (!ptr) |
| ptr = name; |
| |
| /* FIXME: it should return the service name attribute instead of the short service name */ |
| reply = dbus_message_new_method_return(msg); |
| 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 psearch; |
| DBusMessage *reply; |
| sdp_record_t *rec; |
| const char *owner, *name; |
| char identifier[MAX_IDENTIFIER_LEN]; |
| const char *ptr = identifier; |
| bdaddr_t sba; |
| int err = 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); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| str2ba(dbus_data->address, &sba); |
| /* register service */ |
| if (!(rec = sdp_service_register(name, &sba, channel, &err))) { |
| dbus_message_unref(reply); |
| error("service register error: %s (%d)", strerror(err), err); |
| if (err == EINVAL) |
| return error_invalid_arguments(conn, msg); |
| else |
| return error_failed(conn, msg, err); |
| } |
| |
| /* Only add a D-Bus unique name listener if there isn't one already registered */ |
| memset(&psearch, 0, sizeof(psearch)); |
| psearch.owner = (char *) owner; |
| |
| if (!slist_find(sdp_cache, &psearch, service_provider_cmp)) |
| name_listener_add(conn, owner, (name_cb_t) owner_exited, dbus_data); |
| |
| /* add record in the cache */ |
| sdp_cache_append(owner, dbus_data->address, rec); |
| snprintf(identifier, MAX_IDENTIFIER_LEN, "%s/0x%x", dbus_data->address, rec->handle); |
| dbus_message_append_args(reply, |
| DBUS_TYPE_STRING, &ptr, |
| DBUS_TYPE_INVALID); |
| |
| return send_reply_and_unref(conn, reply); |
| } |
| |
| static DBusHandlerResult unregister_rfcomm(DBusConnection *conn, |
| DBusMessage *msg, void *data) |
| { |
| char address[18]; |
| struct hci_dbus_data *dbus_data = data; |
| struct service_provider *p, psearch; |
| struct service_record rsearch, *r; |
| sdp_record_t record; |
| struct slist *lp, *lr; |
| DBusMessage *reply; |
| const char *owner, *identifier; |
| bdaddr_t sba; |
| int err = 0; |
| uint32_t handle; |
| |
| 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); |
| |
| if (str2identifier(identifier, address, &handle) != 0) |
| return error_invalid_arguments(conn, msg); |
| |
| /* check if the local adapter match */ |
| if (strcmp(address, dbus_data->address)) |
| return error_not_authorized(conn, msg); |
| |
| memset(&psearch, 0, sizeof(psearch)); |
| |
| psearch.prov = address; |
| psearch.owner = (char *) owner; |
| |
| lp = slist_find(sdp_cache, &psearch, service_provider_cmp); |
| if (!lp) |
| return error_service_does_not_exist(conn, msg); |
| |
| p = lp->data; |
| |
| rsearch.record = &record; |
| record.handle = handle; |
| lr = slist_find(p->lrec, &rsearch, service_record_cmp); |
| if (!lr) |
| return error_service_does_not_exist(conn, msg); |
| |
| reply = dbus_message_new_method_return(msg); |
| if (!reply) |
| return DBUS_HANDLER_RESULT_NEED_MEMORY; |
| |
| r = lr->data; |
| str2ba(dbus_data->address, &sba); |
| if (sdp_service_unregister(&sba, r->record, &err) < 0) |
| error("service unregister error: %s (%d)", strerror(err), err); |
| else |
| r->record = NULL; |
| |
| /* Remove the service record */ |
| service_record_free(r, NULL); |
| p->lrec = slist_remove(p->lrec, r); |
| |
| /* if the service record is empty remove the provider */ |
| if (!p->lrec) { |
| sdp_cache = slist_remove(sdp_cache, p); |
| service_provider_free(p, NULL); |
| } |
| |
| psearch.prov = NULL; |
| |
| /* Only remove the D-Bus unique name listener if there are no more record using this name */ |
| if (!slist_find(sdp_cache, &psearch, 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) |
| { |
| const struct hci_dbus_data *pdata = data; |
| service_handler_func_t handler; |
| |
| if (!hcid_dbus_use_experimental()) |
| return error_unknown_method(conn, msg); |
| |
| if (!pdata->up) |
| return error_not_ready(conn, msg); |
| |
| handler = find_service_handler(sdp_services, msg); |
| |
| if (handler) |
| return handler(conn, msg, data); |
| |
| return error_unknown_method(conn, msg); |
| } |
| |
| void dbus_sdp_cache_free() |
| { |
| slist_foreach(sdp_cache, service_provider_free, NULL); |
| } |
| |
| /* |
| * Internal async get remote service record implementation |
| */ |
| |
| static void get_rec_with_handle_comp_cb(uint8_t type, uint16_t err, |
| uint8_t *rsp, size_t size, void *udata) |
| { |
| struct transaction_context *ctxt = udata; |
| int scanned, cb_err = 0; |
| sdp_record_t *rec = NULL; |
| |
| if (err == 0xffff) { |
| int sdp_err = sdp_get_error(ctxt->session); |
| if (sdp_err < 0) { |
| error("search failed: Invalid session!"); |
| cb_err = EINVAL; |
| goto failed; |
| } |
| error("search failed :%s (%d)", strerror(sdp_err), sdp_err); |
| cb_err = sdp_err; |
| goto failed; |
| } |
| |
| if (type == SDP_ERROR_RSP || type != SDP_SVC_ATTR_RSP) { |
| error("SDP error: %s(%d)", strerror(EPROTO), EPROTO); |
| cb_err = EPROTO; |
| goto failed; |
| } |
| |
| rec = sdp_extract_pdu(rsp, &scanned); |
| if (!rec) { |
| error("Service record is NULL"); |
| cb_err = EPROTO; |
| goto failed; |
| } |
| |
| /* FIXME: add record to the cache! */ |
| |
| failed: |
| get_record_data_call_cb(ctxt->priv, rec, cb_err); |
| |
| /* FIXME: when start using cache don't free the service record */ |
| if (rec) |
| sdp_record_free(rec); |
| get_record_data_free(ctxt->priv); |
| transaction_context_free(ctxt); |
| } |
| |
| static int get_rec_with_handle_conn_cb(struct transaction_context *ctxt) |
| { |
| get_record_data_t *d = ctxt->priv; |
| uint32_t range = 0x0000ffff; |
| sdp_list_t *attrids = NULL; |
| uint32_t handle; |
| int err = 0; |
| |
| if (sdp_set_notify(ctxt->session, |
| get_rec_with_handle_comp_cb, ctxt) < 0) { |
| error("Invalid session data!"); |
| err = -EINVAL; |
| goto failed; |
| } |
| |
| handle = *((uint32_t *)d->search_data); |
| attrids = sdp_list_append(NULL, &range); |
| |
| if (sdp_service_attr_async(ctxt->session, handle, |
| SDP_ATTR_REQ_RANGE, attrids) < 0) { |
| error("send request failed: %s (%d)", strerror(errno), errno); |
| err = -errno; |
| goto failed; |
| } |
| |
| failed: |
| if (attrids) |
| sdp_list_free(attrids, NULL); |
| |
| return err; |
| } |
| |
| int get_record_with_handle(DBusConnection *conn, DBusMessage *msg, |
| uint16_t dev_id, const char *dst, |
| uint32_t handle, get_record_cb_t *cb, void *data) |
| { |
| struct pending_connect *c; |
| get_record_data_t *d; |
| uint32_t *rec_handle; |
| int err; |
| |
| /* FIXME: search the cache first! */ |
| |
| if (find_pending_connect(dst)) { |
| error("SDP search in progress!"); |
| return -EINPROGRESS; |
| } |
| |
| rec_handle = malloc(sizeof(uint32_t)); |
| if (!rec_handle) |
| return -ENOMEM; |
| |
| *rec_handle = handle; |
| |
| d = get_record_data_new(dev_id, dst, rec_handle, cb, data); |
| if (!d) { |
| free(rec_handle); |
| return -ENOMEM; |
| } |
| |
| if (!(c = connect_request(conn, msg, dev_id, dst, |
| get_rec_with_handle_conn_cb, &err))) { |
| error("Search request failed: %s (%d)", strerror(err), err); |
| get_record_data_free(d); |
| return -err; |
| } |
| |
| c->priv = d; |
| |
| return 0; |
| } |
| |
| static void get_rec_with_uuid_comp_cb(uint8_t type, uint16_t err, |
| uint8_t *rsp, size_t size, void *udata) |
| { |
| struct transaction_context *ctxt = udata; |
| get_record_data_t *d = ctxt->priv; |
| int csrc, tsrc, cb_err = 0; |
| uint32_t *handle; |
| uint8_t *pdata; |
| |
| if (err == 0xffff) { |
| int sdp_err = sdp_get_error(ctxt->session); |
| if (sdp_err < 0) { |
| error("search failed: Invalid session!"); |
| cb_err = EINVAL; |
| goto failed; |
| } |
| error("search failed: %s (%d)", strerror(sdp_err), sdp_err); |
| cb_err = sdp_err; |
| goto failed; |
| } |
| |
| if (type == SDP_ERROR_RSP || type != SDP_SVC_SEARCH_RSP) { |
| error("SDP error: %s (%d)", strerror(EPROTO), EPROTO); |
| cb_err = EPROTO; |
| goto failed; |
| } |
| |
| pdata = rsp; |
| tsrc = ntohs(bt_get_unaligned((uint16_t *) pdata)); |
| if (tsrc <= 0) |
| goto failed; |
| pdata += sizeof(uint16_t); |
| |
| csrc = ntohs(bt_get_unaligned((uint16_t *) pdata)); |
| if (csrc <= 0) |
| goto failed; |
| pdata += sizeof(uint16_t); |
| |
| handle = malloc(sizeof(*handle)); |
| if (!handle) { |
| cb_err = ENOMEM; |
| goto failed; |
| } |
| *handle = ntohl(bt_get_unaligned((uint32_t*)pdata)); |
| |
| free(d->search_data); |
| d->search_data = handle; |
| |
| cb_err = get_rec_with_handle_conn_cb(ctxt); |
| if (cb_err) |
| goto failed; |
| |
| return; |
| |
| failed: |
| get_record_data_call_cb(d, NULL, cb_err); |
| get_record_data_free(d); |
| transaction_context_free(ctxt); |
| } |
| |
| static int get_rec_with_uuid_conn_cb(struct transaction_context *ctxt) |
| { |
| get_record_data_t *d = ctxt->priv; |
| sdp_list_t *search = NULL; |
| uuid_t *uuid; |
| int err = 0; |
| |
| if (sdp_set_notify(ctxt->session, |
| get_rec_with_uuid_comp_cb, ctxt) < 0) { |
| err = -EINVAL; |
| goto failed; |
| } |
| |
| uuid = (uuid_t *)d->search_data; |
| search = sdp_list_append(NULL, uuid); |
| |
| if (sdp_service_search_async(ctxt->session, search, 64) < 0) { |
| error("send request failed: %s (%d)", strerror(errno), errno); |
| err = -sdp_get_error(ctxt->session); |
| goto failed; |
| } |
| |
| failed: |
| if (search) |
| sdp_list_free(search, NULL); |
| |
| return err; |
| } |
| |
| int get_record_with_uuid(DBusConnection *conn, DBusMessage *msg, |
| uint16_t dev_id, const char *dst, |
| const uuid_t *uuid, get_record_cb_t *cb, void *data) |
| { |
| struct pending_connect *c; |
| get_record_data_t *d; |
| int err; |
| uuid_t *sdp_uuid; |
| |
| /* FIXME: search the cache first! */ |
| |
| if (find_pending_connect(dst)) { |
| error("SDP search in progress!"); |
| return -EINPROGRESS; |
| } |
| |
| sdp_uuid = malloc(sizeof(uuid_t)); |
| if (!sdp_uuid) |
| return -ENOMEM; |
| |
| memcpy(sdp_uuid, uuid, sizeof(uuid_t)); |
| |
| d = get_record_data_new(dev_id, dst, sdp_uuid, cb, data); |
| if (!d) { |
| free(sdp_uuid); |
| return -ENOMEM; |
| } |
| |
| if (!(c = connect_request(conn, msg, dev_id, dst, |
| get_rec_with_uuid_conn_cb, &err))) { |
| error("Search request failed: %s (%d)", strerror(err), err); |
| get_record_data_free(d); |
| return -err; |
| } |
| |
| c->priv = d; |
| |
| return 0; |
| } |