diff options
-rw-r--r-- | hcid/dbus-sdp.c | 1125 |
1 files changed, 588 insertions, 537 deletions
diff --git a/hcid/dbus-sdp.c b/hcid/dbus-sdp.c index 2cfd687c..fe3ad04a 100644 --- a/hcid/dbus-sdp.c +++ b/hcid/dbus-sdp.c @@ -50,18 +50,17 @@ #include "hcid.h" #include "textfile.h" -#define SDP_UUID_SEQ_SIZE 256 -#define SDP_MAX_ATTR_LEN 65535 - +#define MAX_IDENTIFIER_LEN 24 struct service_provider { char *owner; /* null for remote services or unique name if local */ - bdaddr_t prov; /* remote Bluetooth address or local address */ + char *prov; /* remote Bluetooth address thar provides the service */ + int ttl; /* time to live */ struct slist *lrec; }; struct service_record { - uint32_t identifier; + int id; sdp_record_t *record; }; @@ -69,59 +68,114 @@ struct pending_connect { DBusConnection *conn; DBusMessage *rq; char *svc; - bdaddr_t dba; + char *dst; }; 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: 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, NULL }, + { "map", 0, NULL }, + { "pbap", 0, NULL }, + { "sap", SAP_SVCLASS_ID, "SIM Access" }, + { "ftp", OBEX_FILETRANS_SVCLASS_ID, "OBEX File Transfer" }, + { "bpp", DIRECT_PRINTING_SVCLASS_ID, "Direct Printing" }, + { "bip", 0, NULL }, + { "synch", 0, NULL }, + { "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: store the arguments or just the pointer to the message */ +/* FIXME: move to a common file */ +uint16_t sdp_str2svclass(const char *str) +{ + sdp_service_t *s; -/* list of remote and local service records - * FIXME: free the cache when the local sock(sdpd) is closed - */ + for (s = sdp_service; s->name; s++) { + if (strcasecmp(s->name, str) == 0) + return s->class; + } -static struct slist *sdp_cache = NULL; -static struct slist *pending_connects = NULL; + return 0; +} -static const char *ecode2str(uint16_t ecode) +/* FIXME: move to a common file */ +const char* sdp_svclass2str(uint16_t class) { - 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"; + sdp_service_t *s; + + for (s = sdp_service; s->name; s++) { + if (s->class == class) + return s->name; } + + return NULL; } +/* FIXME: stub for bluez-libs changes */ +static inline sdp_session_t *sdp_session_new(int sk) +{ + errno = ENOSYS; + return NULL; +} + +/* FIXME: stub for bluez-libs changes */ +static inline int sdp_service_search_handle(sdp_session_t *session) +{ + return ENODATA; +} + +/* FIXME: stub for bluez-libs changes */ +typedef void sdp_transaction_cb_t(sdp_list_t *rsp, void *udata, int err); +static inline int sdp_service_search_attr_req_async(sdp_session_t *session, const sdp_list_t *search, + sdp_transaction_cb_t *f, void *udata) +{ + return 0; +} + +/* 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 bdaddr_t *bda, const char *svc) + const char *dst, const char *svc) { struct pending_connect *c; - if (!bda) + if (!dst) return NULL; c = malloc(sizeof(*c)); @@ -131,18 +185,26 @@ static struct pending_connect *pending_connect_new(DBusConnection *conn, DBusMes if (svc) { c->svc = strdup(svc); if (!c->svc) - goto failed; + goto fail; + } + if (dst) { + c->dst = strdup(dst); + if (!c->dst) + goto fail; } - bacpy(&c->dba, bda); c->conn = dbus_connection_ref(conn); c->rq = dbus_message_ref(msg); return c; -failed: - if (c) - free(c); +fail: + if (c->svc) + free(c->svc); + if (c->dst) + free(c->dst); + free(c); + return NULL; } @@ -163,237 +225,120 @@ static void pending_connect_free(struct pending_connect *c) free(c); } -static struct pending_connect *find_pending_connect(const bdaddr_t *bda) +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 (!bacmp(bda, &pending->dba)) + if (!strcmp(dst, pending->dst)) 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) +static int str2identifier(const char *identifier, char *address, int *id) { - 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 (!identifier || !address) + return -1; - if (ctxt->session) - free(ctxt->session); + if (strlen(identifier) < 19) + return -1; - if (ctxt->reqbuf) - free(ctxt->reqbuf); + memset(address, 0, 18); - if (ctxt->rspbuf.data) - free(ctxt->rspbuf.data); + snprintf(address, 18, "%s", identifier); - free(ctxt); + return (sscanf(identifier + 18, "%d", id) > 0? 0: -1); } -/* FIXME: generate the pseudo random id */ -static uint32_t gen_next_id(const bdaddr_t *prov, uint32_t handle) +static inline int gen_next_record_id() { - static uint32_t id; - return ++id; + static int id = 0; + return id++; } -static struct service_record *service_record_new(const bdaddr_t *prov, sdp_record_t *rec) +static struct service_record *service_record_new(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->id = gen_next_record_id(); r->record = rec; return r; } -static void service_record_free(struct service_record *r, void *data) +static void service_record_free(void *data, void *udata) { + struct service_record *r = data; + if (!r) return; - sdp_record_free(r->record); + if (r->record) + sdp_record_free(r->record); + free(r); } -static void service_provider_free(struct service_provider *p) +/* + * This function doesn't check service record pattern + */ +static int service_record_cmp(const void *data, const void *udata) { - if (p->owner) - free(p->owner); + const struct service_record *a = data; + const struct service_record *b = udata; + if (b->id >= 0) { + if (a->id != b->id) + return -1; + } - if (p->lrec) { - slist_foreach(p->lrec, (slist_func_t)service_record_free, NULL); - slist_free(p->lrec); + if (b->record) { + if (b->record->handle != 0xffffffff && + b->record->handle != a->record->handle) + return -1; } - free(p); + return 0; } -static struct service_provider *service_provider_new(const char *owner, const bdaddr_t *prov) +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; @@ -411,19 +356,25 @@ static struct service_provider *service_provider_new(const char *owner, const bd goto fail; } - bacpy(&p->prov, prov); + if (prov) { + p->prov = strdup(prov); + if (!p->prov) + goto fail; + } return p; fail: - service_provider_free(p); + service_provider_free(p, NULL); return NULL; } -static int service_provider_cmp(const struct service_provider *a, const struct service_provider *b) +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; @@ -432,10 +383,10 @@ static int service_provider_cmp(const struct service_provider *a, const struct s return ret; } - if (bacmp(&b->prov, BDADDR_ANY)) { - if (!bacmp(&a->prov, BDADDR_ANY)) + if (b->prov) { + if (!a->prov) return -1; - ret = bacmp(&a->prov, &b->prov); + ret = strcmp(a->prov, b->prov); if (ret) return ret; } @@ -443,158 +394,98 @@ static int service_provider_cmp(const struct service_provider *a, const struct s return 0; } -static uint32_t sdp_cache_append(const char *owner, const bdaddr_t *prov, sdp_record_t *rec) +static int sdp_cache_append(const char *owner, const char *prov, sdp_record_t *rec) { - struct slist *l; + struct slist *lp, *lr; struct service_provider *p; - struct service_provider *ref; + struct service_provider psearch; struct service_record *r; - - if (!prov || !rec) - return 0; - ref = service_provider_new(owner, prov); - if (!ref) - return 0; + if (!prov || !rec) + return -1; - l = slist_find(sdp_cache, (const void*)ref, (cmp_func_t)service_provider_cmp); - if (!l) { + 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 = l->data; - - r = service_record_new(prov, rec); - p->lrec = slist_append(p->lrec, r); + p = lp->data; - if (ref) - service_provider_free(ref); + /* check if there is service record already belongs to the cache */ + r = malloc(sizeof(*r)); + memset(r, 0, sizeof(*r)); + r->id = -1; + r->record = sdp_record_alloc(); + r->record->handle = rec->handle; + lr = slist_find(p->lrec, r, service_record_cmp); + service_record_free(r, NULL); + + if (lr) { + /* overwrite the record instead of compare */ + r = lr->data; + sdp_record_free(r->record); + r->record = rec; + } else { + /* create a new entry */ + r = service_record_new(rec); + p->lrec = slist_append(p->lrec, r); + } - return r->identifier; + return r->id; } static void owner_exited(const char *owner, struct hci_dbus_data *dbus_data) { - struct slist *cur, *next; - + struct slist *lp, *next, *lr; + struct service_provider *p; + bdaddr_t sba; + int err = 0; debug("SDP provider owner %s exited", owner); - for (cur = sdp_cache; cur != NULL; cur = next) { - struct service_provider *p = cur->data; - - next = cur->next; + for (lp = sdp_cache; lp; lp = next) { - if(!p->owner) - continue; - - if (strcmp(p->owner, owner)) + next = lp->next; + p = lp->data; + + if (!p->owner || 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; + /* + * 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; } - 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); + /* remove from the cache */ + sdp_cache = slist_remove(sdp_cache, p); - } while (scanned < ctxt->attr_list_len); + service_provider_free(p, NULL); } - - 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) +static gboolean search_session_handle_cb(GIOChannel *chan, GIOCondition cond, void *udata) { - int sk, err, n; - uint32_t rsp_count; - gboolean ret_val = FALSE; + struct transaction_context *ctxt = udata; + int sk, err = 0; 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; + const char *dst; + int retval = FALSE; + + dbus_message_get_args(ctxt->rq, NULL, + DBUS_TYPE_STRING, &dst, + DBUS_TYPE_INVALID); sk = g_io_channel_unix_get_fd(chan); @@ -602,84 +493,96 @@ static gboolean svc_search_attr_req_cb(GIOChannel *chan, GIOCondition cond, stru if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { error("getsockopt(): %s, (%d)", strerror(errno), errno); - goto failed; + error_failed(ctxt->conn, ctxt->rq, errno); + goto fail; } if (err != 0) { - error("connect(): %s(%d)", strerror(err), err); - error_connection_attempt_failed(ctxt->conn, ctxt->rq, err); - goto failed; + error_failed(ctxt->conn, ctxt->rq, err); + error("sock error:s(%d)", strerror(err), err); + goto fail; } + if (!sdp_service_search_handle(ctxt->session)) + return TRUE; - rsp = malloc(SDP_RSP_BUFFER_SIZE); - memset(rsp, 0, SDP_RSP_BUFFER_SIZE); +fail: + g_io_channel_close(chan); + return retval; +} - n = recv(sk, rsp, SDP_RSP_BUFFER_SIZE, 0); - if (n <= 0) { - err = errno; - goto failed; - } +static void search_transaction_completed_cb(sdp_list_t *rsp, struct transaction_context *ctxt, int err) +{ + char identifier[MAX_IDENTIFIER_LEN]; + const char *ptr = identifier; + DBusMessage *reply; + DBusMessageIter iter, array_iter; + sdp_list_t *next; + const char *dst; + int id; - rsphdr = (sdp_pdu_hdr_t *)rsp; - if (n == 0 || reqhdr->tid != rsphdr->tid) { - err = EPROTO; - goto failed; + if (!ctxt) + return; + + if (err) { + error_failed(ctxt->conn, ctxt->rq, err); + return; } - pdata = rsp + sizeof(sdp_pdu_hdr_t); + dbus_message_get_args(ctxt->rq, NULL, + DBUS_TYPE_STRING, &dst, + DBUS_TYPE_INVALID); - 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; - } + 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); - rsp_count = ntohs(bt_get_unaligned((uint16_t *) pdata)); - ctxt->attr_list_len += rsp_count; - pdata += sizeof(uint16_t); + /* add in the cache */ + for ( ;rsp; rsp = next) { + sdp_record_t *rec = (sdp_record_t *) rsp->data; - // if continuation state set need to re-issue request before parsing - cstate_len = *(uint8_t *) (pdata + rsp_count); + id = sdp_cache_append(NULL, dst, rec); + snprintf(identifier, MAX_IDENTIFIER_LEN, "%s/%d", dst, id); + dbus_message_iter_append_basic(&array_iter, + DBUS_TYPE_STRING, &ptr); - if (rsp_count > 0) { - uint8_t *targetPtr = NULL; + next = rsp->next; + free(rsp); + /* Don't free the record */ + } - ctxt->cstate = cstate_len > 0 ? (sdp_cstate_t *) (pdata + rsp_count) : 0; + dbus_message_iter_close_container(&iter, &array_iter); + send_reply_and_unref(ctxt->conn, reply); +} - // 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; - } +static void transaction_context_free(void *udata) +{ + struct transaction_context *ctxt = udata; - 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 (!ctxt) + return; - if (err) { - error_failed(ctxt->conn, ctxt->rq, err); - error("SDP transaction error: %s (%d)", strerror(err), err); - } + if (ctxt->conn) + dbus_connection_unref(ctxt->conn); + + if (ctxt->rq) + dbus_message_unref(ctxt->rq); - return ret_val; + if (ctxt->session) + sdp_close(ctxt->session); + + free(ctxt); } -static gboolean sdp_client_connect_cb(GIOChannel *chan, GIOCondition cond, struct pending_connect *c) +static gboolean sdp_client_connect_cb(GIOChannel *chan, GIOCondition cond, void *udata) { - int err, sk = 0; + struct pending_connect *c = udata; + struct transaction_context *ctxt = NULL; + sdp_list_t *search = NULL; + uint16_t class; + int err = 0, sk = 0; socklen_t len; - GIOChannel *tchan; - struct transaction_context *ctxt; + uuid_t uuid; sk = g_io_channel_unix_get_fd(chan); @@ -687,33 +590,62 @@ static gboolean sdp_client_connect_cb(GIOChannel *chan, GIOCondition cond, struc if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &err, &len) < 0) { error("getsockopt(): %s, (%d)", strerror(errno), errno); - goto failed; + error_connection_attempt_failed(c->conn, c->rq, errno); + goto fail; } if (err != 0) { error("connect(): %s(%d)", strerror(err), err); error_connection_attempt_failed(c->conn, c->rq, err); - goto failed; + goto fail; } - ctxt = transaction_context_new(c->conn, c->rq, &c->dba, c->svc, sk, 0); - + ctxt = malloc(sizeof(*ctxt)); if (!ctxt) { error_failed(c->conn, c->rq, ENOMEM); - goto failed; + goto fail; } + memset(ctxt, 0, sizeof(*ctxt)); - tchan = g_io_channel_unix_new(sk); + /* FIXME: the sdp search list pattern should be created outside */ + if (!c->svc) { + sdp_uuid16_create(&uuid, PUBLIC_BROWSE_GROUP); + search = sdp_list_append(0, &uuid); + } else { + class = sdp_str2svclass(c->svc); + sdp_uuid16_create(&uuid, class); + search = sdp_list_append(0, &uuid); + } - g_io_add_watch_full(tchan, 0, G_IO_IN, (GIOFunc)svc_search_attr_req_cb, - ctxt, (GDestroyNotify)transaction_context_free); + ctxt->conn = dbus_connection_ref(c->conn); + ctxt->rq = dbus_message_ref(c->rq); + ctxt->session = sdp_session_new(sk); + if (!ctxt->session) { + error("error: %s (%d)", strerror(errno), errno); + error_failed(c->conn, c->rq, errno); + goto fail; + } - 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; + /* Create/send the search request and set the callback to indicate the request completion */ + if (sdp_service_search_attr_req_async(ctxt->session, search, + (sdp_transaction_cb_t *)search_transaction_completed_cb, ctxt) < 0) { + error("send request failed: %s (%d)", strerror(errno), errno); + error_failed(c->conn, c->rq, errno); + goto fail; } -failed: + + /* set the callback responsible for update the transaction data */ + g_io_add_watch_full(chan, 0, G_IO_IN, search_session_handle_cb, + ctxt, transaction_context_free); + goto done; + +fail: + if (ctxt) + transaction_context_free(ctxt); +done: + if (search) + sdp_list_free(search, 0); + pending_connects = slist_remove(pending_connects, c); pending_connect_free(c); @@ -725,11 +657,11 @@ static int search_request(DBusConnection *conn, DBusMessage *msg, uint16_t dev_i { struct pending_connect *c = NULL; GIOChannel *chan = NULL; - bdaddr_t sba; struct sockaddr_l2 sa; int sk, watch = 0; // create L2CAP connection + /* FIXME: use set_nonblocking */ sk = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); if (sk < 0) { if (err) @@ -743,21 +675,18 @@ static int search_request(DBusConnection *conn, DBusMessage *msg, uint16_t dev_i sa.l2_family = AF_BLUETOOTH; sa.l2_psm = 0; - hci_devba(dev_id, &sba); + hci_devba(dev_id, &sa.l2_bdaddr); - 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; - } + 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); + c = pending_connect_new(conn, msg, dst, svc); if (!c) { if (err) *err = ENOMEM; @@ -766,7 +695,7 @@ static int search_request(DBusConnection *conn, DBusMessage *msg, uint16_t dev_i 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 (!(errno == EAGAIN || errno == EINPROGRESS)) { if (err) *err = errno; error("connect() failed:%s (%d)", strerror(errno), errno); @@ -774,7 +703,7 @@ static int search_request(DBusConnection *conn, DBusMessage *msg, uint16_t dev_i } watch = g_io_add_watch(chan, G_IO_OUT, - (GIOFunc)sdp_client_connect_cb, c); + sdp_client_connect_cb, c); pending_connects = slist_append(pending_connects, c); } else { sdp_client_connect_cb(chan, G_IO_OUT, c); @@ -796,60 +725,59 @@ static DBusHandlerResult get_identifiers(DBusConnection *conn, { 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; + if (find_pending_connect(dst)) + return error_service_search_in_progress(conn, msg); - 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); + /* check if it is a unknown address */ + snprintf(filename, PATH_MAX, "%s/%s/lastseen", STORAGEDIR, dbus_data->address); - if (l) { - struct slist *lr; - struct service_record *r; + str = textfile_get(filename, dst); + if (!str) + return error_unknown_address(conn, msg); - /* 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); + free(str); - 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); - } + 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); + } - dbus_message_iter_close_container(&iter, &array_iter); + return DBUS_HANDLER_RESULT_HANDLED; +} - return send_reply_and_unref(conn, reply); - } +static DBusHandlerResult get_identifiers_by_service(DBusConnection *conn, + DBusMessage *msg, void *data) +{ + char filename[PATH_MAX + 1]; + 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; + char *str; + int err = 0, nrec = 0; + uint32_t class; + uuid_t uuid; - if (find_pending_connect(&dba)) - return error_service_search_in_progress(conn, msg); + if (!dbus_message_get_args(msg, NULL, + DBUS_TYPE_STRING, &dst, + DBUS_TYPE_STRING, &svc, + DBUS_TYPE_INVALID)) + return error_invalid_arguments(conn, msg); /* check if it is a unknown address */ create_name(filename, PATH_MAX, STORAGEDIR, dbus_data->address, "lastseen"); @@ -860,21 +788,74 @@ static DBusHandlerResult get_identifiers(DBusConnection *conn, free(str); - if (search_request(conn, msg, dbus_data->dev_id, dst, NULL, &err) < 0) { + 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/%d", p->prov, r->id); + 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 (search_request(conn, msg, dbus_data->dev_id, dst, svc, &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) +static sdp_record_t *find_record(const char *address, int id) { struct slist *lp, *lr; struct service_provider *p; @@ -882,9 +863,12 @@ sdp_record_t *find_record(uint32_t identifier) 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->identifier == identifier) + if (r->id == id) return r->record; } } @@ -896,18 +880,23 @@ static DBusHandlerResult get_uuid(DBusConnection *conn, DBusMessage *msg, void *data) { char uuid_str[MAX_LEN_UUID_STR]; - sdp_list_t *l; + char address[18]; + sdp_list_t *ls; DBusMessage *reply; sdp_record_t *rec; char *ptr = uuid_str; - uint32_t identifier; + const char *identifier; + int id; if (!dbus_message_get_args(msg, NULL, - DBUS_TYPE_UINT32, &identifier, + DBUS_TYPE_STRING, &identifier, DBUS_TYPE_INVALID)) return error_invalid_arguments(conn, msg); - rec = find_record(identifier); + if (str2identifier(identifier, address, &id) != 0) + return error_invalid_arguments(conn, msg); + + rec = find_record(address, id); if (!rec) return error_record_does_not_exist(conn, msg); @@ -915,11 +904,16 @@ static DBusHandlerResult get_uuid(DBusConnection *conn, 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; + if (sdp_get_service_classes(rec, &ls) == 0) { + char tmp_str[MAX_LEN_UUID_STR]; + uuid_t *uuid = (uuid_t *)ls->data; - sdp_uuid2strn(&desc->uuid, uuid_str, MAX_LEN_UUID_STR); - sdp_list_free(l, free); + 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, @@ -932,27 +926,43 @@ static DBusHandlerResult get_uuid(DBusConnection *conn, static DBusHandlerResult get_name(DBusConnection *conn, DBusMessage *msg, void *data) { + char address[18]; DBusMessage *reply; sdp_record_t *rec; - sdp_data_t *d; + sdp_list_t *ls; char name[] = ""; - char *ptr = name; - uint32_t identifier; + const char *ptr = name; + const char *identifier; + uuid_t *puuid; + int id; if (!dbus_message_get_args(msg, NULL, - DBUS_TYPE_UINT32, &identifier, + DBUS_TYPE_STRING, &identifier, DBUS_TYPE_INVALID)) return error_invalid_arguments(conn, msg); - rec = find_record(identifier); + if (str2identifier(identifier, address, &id) != 0) + return error_invalid_arguments(conn, msg); + + rec = find_record(address, id); 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; + 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); @@ -964,11 +974,14 @@ static DBusHandlerResult register_rfcomm(DBusConnection *conn, DBusMessage *msg, void *data) { struct hci_dbus_data *dbus_data = data; - struct service_provider ref; + 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; - uint32_t identifier = 0, handle = 0; + int id = 0, err = 0; uint8_t channel; owner = dbus_message_get_sender(msg); @@ -979,39 +992,51 @@ static DBusHandlerResult register_rfcomm(DBusConnection *conn, 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); + 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(&ref, 0, sizeof(ref)); - bacpy(&ref.prov, BDADDR_ANY); + memset(&psearch, 0, sizeof(psearch)); + psearch.owner = (char *)owner; - if (!slist_find(sdp_cache, &ref, (cmp_func_t)service_provider_cmp)) + if (!slist_find(sdp_cache, &psearch, service_provider_cmp)) name_listener_add(conn, owner, (name_cb_t)owner_exited, dbus_data); - /* FIXME: register the RFCOMM service */ - + /* add record in the cache */ + id = sdp_cache_append(owner, dbus_data->address, rec); + + snprintf(identifier, MAX_IDENTIFIER_LEN, "%s/%d", dbus_data->address, id); + 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, ref; - struct slist *match; + struct service_provider *p, psearch; + struct service_record rsearch, *r; + struct slist *lp, *lr; DBusMessage *reply; const char *owner, *identifier; + bdaddr_t sba; + int id = 0, err = 0; owner = dbus_message_get_sender(msg); @@ -1020,34 +1045,55 @@ static DBusHandlerResult unregister_rfcomm(DBusConnection *conn, 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; + if (str2identifier(identifier, address, &id) != 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); - match = slist_find(sdp_cache, &ref, (cmp_func_t)service_provider_cmp); - if (!match) + 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); - /* FIXME: find the RFCOMM UUID in the list */ - p = match->data; - - if (strcmp(p->owner, owner)) - return error_not_authorized(conn, msg); + p = lp->data; + + memset(&rsearch, 0, sizeof(rsearch)); + rsearch.id = id; + str2ba(dbus_data->address, &sba); + 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; - /* FIXME: unregister the service */ + r = lr->data; + if (sdp_service_unregister(&sba, r->record, &err) < 0) + error("service unregister error: %s (%d)", strerror(err), err); + else + r->record = NULL; - sdp_cache = slist_remove(sdp_cache, p); - service_provider_free(p); + /* Remove the service record */ + service_record_free(r, NULL); + p->lrec = slist_remove(p->lrec, r); - bacpy(&ref.prov, BDADDR_ANY); + /* 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, &ref, (cmp_func_t)service_provider_cmp)) + 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); @@ -1077,3 +1123,8 @@ DBusHandlerResult handle_sdp_method(DBusConnection *conn, DBusMessage *msg, void return error_unknown_method(conn, msg); } + +void dbus_sdp_cache_free() +{ + slist_foreach(sdp_cache, service_provider_free, NULL); +} |