blob: ea86182351882882276e206968b55608174a655c [file] [log] [blame]
/*
*
* 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/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 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 */
snprintf(filename, PATH_MAX, "%s/%s/lastseen", STORAGEDIR, dbus_data->address);
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);
}