summaryrefslogtreecommitdiffstats
path: root/hcid/dbus-sdp.c
diff options
context:
space:
mode:
Diffstat (limited to 'hcid/dbus-sdp.c')
-rw-r--r--hcid/dbus-sdp.c501
1 files changed, 501 insertions, 0 deletions
diff --git a/hcid/dbus-sdp.c b/hcid/dbus-sdp.c
index f3bf47c4..e2a46fb8 100644
--- a/hcid/dbus-sdp.c
+++ b/hcid/dbus-sdp.c
@@ -27,3 +27,504 @@
#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 <dbus/dbus.h>
+
+#include "dbus.h"
+#include "hcid.h"
+#include "textfile.h"
+
+struct dbus_sdp_record {
+ char *owner; /* null for remote services or unique name if local */
+ bdaddr_t provider; /* remote Bluetooth address or local address */
+ char *name; /* service name */
+ uint32_t identifier;
+ uint16_t uuid;
+ uint8_t channel;
+};
+
+/* list of remote and local service records */
+static struct slist *sdp_records = NULL;
+
+struct search_request_info *search_request_info_new(bdaddr_t *dba, DBusMessage *msg)
+{
+ struct search_request_info *search = malloc(sizeof(struct search_request_info));
+ if (!search)
+ return NULL;
+
+ memset(search, 0, sizeof(*search));
+
+ bacpy(&search->bdaddr, dba);
+ search->rq = dbus_message_ref(msg);
+
+ return search;
+}
+void search_request_info_free(struct search_request_info *search)
+{
+ if (search->rq)
+ dbus_message_unref(search->rq);
+ if (search->session)
+ free(search->session);
+ if (search->io)
+ free(search->io);
+
+ free(search);
+}
+
+void dbus_sdp_record_free(struct dbus_sdp_record *rec)
+{
+ if (rec->owner)
+ free(rec->owner);
+ if (rec->name)
+ free(rec->name);
+
+ free(rec);
+}
+
+static void id2str(uint32_t id, char *dst)
+{
+ snprintf(dst, 9, "%2.2X%2.2X%2.2X%2.2X", (id >> 24) & 0xFF,
+ (id >> 16) & 0xFF, (id >> 8) & 0xFF, id & 0xFF);
+ debug ("%s, identifier:%s", __PRETTY_FUNCTION__, dst);
+}
+
+static uint32_t str2id(const char *id)
+{
+ return atoi(id);
+}
+
+
+static uint32_t generate_new_id(const bdaddr_t *provider, uint8_t channel)
+{
+ /* FIXME: generate the pseudo random id */
+ return 1;
+}
+
+struct dbus_sdp_record *dbus_sdp_record_new(const char *owner, bdaddr_t *provider,
+ const char *name, uint32_t uuid, uint8_t channel)
+{
+ struct dbus_sdp_record *rec;
+
+ /* FIXME: validate the arguments */
+
+ rec = malloc(sizeof(struct dbus_sdp_record));
+ if (!rec)
+ return NULL;
+
+ memset(rec, 0, sizeof(*rec));
+ if (owner) {
+ rec->owner = strdup(owner);
+ if (!rec->owner)
+ goto mem_fail;
+ }
+
+ bacpy(&rec->provider, provider);
+
+ rec->name = strdup(name);
+ if(!rec->name)
+ goto mem_fail;
+
+ rec->uuid = uuid;
+ rec->channel = channel;
+ rec->identifier = generate_new_id(provider, channel);
+
+ return rec;
+
+mem_fail:
+ dbus_sdp_record_free(rec);
+ return NULL;
+}
+
+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_records; cur != NULL; cur = next) {
+ struct dbus_sdp_record *rec = cur->data;
+
+ next = cur->next;
+
+ if(!rec->owner)
+ continue;
+
+ if (strcmp(rec->owner, owner))
+ continue;
+
+ sdp_records = slist_remove(sdp_records, rec);
+ dbus_sdp_record_free(rec);
+ }
+}
+
+static int record_cmp(const struct dbus_sdp_record *a, const struct dbus_sdp_record *b)
+{
+ int ret;
+
+ if (b->owner) {
+ if (!a->owner)
+ return -1;
+ ret = strcmp(a->owner, b->owner);
+ if (ret)
+ return ret;
+ }
+
+ if (bacmp(&b->provider, BDADDR_ANY)) {
+ if (!bacmp(&a->provider, BDADDR_ANY))
+ return -1;
+ ret = bacmp(&a->provider, &b->provider);
+ if (ret)
+ return ret;
+ }
+
+ if (b->uuid) {
+ ret = (a->uuid - b->uuid);
+ if (ret)
+ return ret;
+ }
+
+ if (b->channel) {
+ ret = (a->channel - b->channel);
+ if (ret)
+ return ret;
+ }
+
+ return 0;
+}
+
+static gboolean sdp_client_connection_cb(GIOChannel *chan, GIOCondition cond, struct hci_dbus_data *dbus_data)
+{
+ debug("%s, line:%d condition:%d", __PRETTY_FUNCTION__, __LINE__, cond);
+ /* FIXME: send the request */
+ return FALSE;
+}
+
+int dbus_sdp_connect(struct hci_dbus_data *dbus_data, const bdaddr_t *sba,
+ const bdaddr_t *dba, uint32_t flags, int *err)
+{
+ struct sockaddr_l2 sa;
+ sdp_session_t *session = malloc(sizeof(sdp_session_t));
+ if (!session) {
+ if (err)
+ *err = ENOMEM;
+ return -1;
+ }
+
+ memset(session, 0, sizeof(*session));
+ session->flags = flags;
+
+ // create L2CAP connection
+ session->sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP);
+ session->local = 0;
+ if (session->sock >= 0) {
+ sa.l2_family = AF_BLUETOOTH;
+ sa.l2_psm = 0;
+ if (bacmp(sba, BDADDR_ANY) != 0) {
+ sa.l2_bdaddr = *sba;
+ if (bind(session->sock, (struct sockaddr *) &sa, sizeof(sa)) < 0)
+ goto fail;
+ }
+ if (flags & SDP_WAIT_ON_CLOSE) {
+ struct linger l = { .l_onoff = 1, .l_linger = 1 };
+ setsockopt(session->sock, SOL_SOCKET, SO_LINGER, &l, sizeof(l));
+ }
+ sa.l2_psm = htobs(SDP_PSM);
+ sa.l2_bdaddr = *dba;
+
+ debug("%s, line:%d connecting ...", __PRETTY_FUNCTION__, __LINE__);
+
+ dbus_data->search->io = g_io_channel_unix_new(session->sock);
+
+ fcntl(session->sock, F_SETFL, fcntl(session->sock, F_GETFL, 0)|O_NONBLOCK);
+ if (connect(session->sock, (struct sockaddr *) &sa, sizeof(sa)) < 0) {
+ if ( !(errno == EAGAIN || errno == EINPROGRESS)) {
+ error("connect() failed:%s (%d)", strerror(errno), errno);
+ goto fail;
+ }
+ g_io_add_watch(dbus_data->search->io, G_IO_OUT,
+ (GIOFunc)sdp_client_connection_cb, dbus_data);
+ } else {
+ debug("Connect completed in the first attempt");
+ sdp_client_connection_cb(dbus_data->search->io, G_IO_OUT, dbus_data);
+ }
+
+ dbus_data->search->session = session;
+ return 0;
+ }
+fail:
+ if (err)
+ *err = errno;
+
+ if (session->sock >= 0)
+ close(session->sock);
+ free(session);
+ errno = *err;
+
+ 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 dbus_sdp_record *rec;
+ struct slist *l;
+ const char *peer;
+ char *str;
+ DBusMessage *reply;
+ DBusMessageIter iter, array_iter;
+ DBusError err;
+ bdaddr_t sba, dba;
+ uint32_t flags = 0;
+ int conn_err, found = 0;
+
+ dbus_error_init(&err);
+
+ dbus_message_get_args(msg, &err,
+ DBUS_TYPE_STRING, &peer,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_error_is_set(&err)) {
+ error("Can't extract message arguments:%s", err.message);
+ dbus_error_free(&err);
+ return error_invalid_arguments(conn, msg);
+ }
+
+ str2ba(peer, &dba);
+
+ reply = dbus_message_new_method_return(msg);
+ if (!reply)
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+
+ /* check the cache */
+ dbus_message_iter_init_append(reply, &iter);
+ dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY,
+ DBUS_TYPE_STRING_AS_STRING, &array_iter);
+
+ for (l = sdp_records; l; l = l->next) {
+ char id_str[9];
+ char *id_ptr = id_str;
+
+ rec = l->data;
+ if (bacmp(&rec->provider, &dba))
+ continue;
+ id2str(rec->identifier, id_ptr);
+ dbus_message_iter_append_basic(&array_iter,
+ DBUS_TYPE_STRING, &id_ptr);
+ found = 1;
+ }
+
+ dbus_message_iter_close_container(&iter, &array_iter);
+
+ if (found)
+ return send_reply_and_unref(conn, reply);
+
+ dbus_message_unref(reply);
+
+ if (dbus_data->search)
+ 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, peer);
+ if (!str)
+ return error_unknown_address(conn, msg);
+
+ free(str);
+
+ /* FIXME: if found, when it is invalid/expired? */
+
+ /* FIXME: check if there is an active connection */
+
+ /* Check if there is an inquiry/bonding in progress */
+
+ /* Background search */
+ dbus_data->search = search_request_info_new(&dba, msg);
+ if (!dbus_data->search)
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+ hci_devba(dbus_data->dev_id, &sba);
+
+ if (dbus_sdp_connect(dbus_data, &sba, &dba, flags, &conn_err) < 0) {
+ search_request_info_free(dbus_data->search);
+ dbus_data->search = NULL;
+ return error_failed(conn, msg, conn_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;
+}
+
+static DBusHandlerResult get_uuid(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+static DBusHandlerResult get_name(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}
+
+
+static DBusHandlerResult register_rfcomm(DBusConnection *conn,
+ DBusMessage *msg, void *data)
+{
+ struct hci_dbus_data *dbus_data = data;
+ struct dbus_sdp_record *rec, ref;
+ DBusMessage *reply;
+ DBusError err;
+ const char *owner, *name;
+ bdaddr_t provider;
+ char id_str[9];
+ char *id_ptr = id_str;
+ uint8_t channel;
+
+ owner = dbus_message_get_sender(msg);
+
+ dbus_error_init(&err);
+ dbus_message_get_args(msg, &err,
+ DBUS_TYPE_STRING, &name,
+ DBUS_TYPE_BYTE, &channel,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_error_is_set(&err)) {
+ error("Can't extract message arguments:%s", err.message);
+ dbus_error_free(&err);
+ return error_invalid_arguments(conn, msg);
+ }
+
+ hci_devba(dbus_data->dev_id, &provider);
+
+ rec = dbus_sdp_record_new(owner, &provider, name, RFCOMM_UUID, channel);
+ if (!rec)
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+
+ if (slist_find(sdp_records, &rec, (cmp_func_t)record_cmp)) {
+ dbus_sdp_record_free(rec);
+ return error_service_already_exists(conn, msg);
+ }
+
+ id2str(rec->identifier, id_ptr);
+ reply = dbus_message_new_method_return(msg);
+ dbus_message_append_args(msg,
+ DBUS_TYPE_STRING, &id_ptr,
+ DBUS_TYPE_INVALID);
+ if (!reply) {
+ dbus_sdp_record_free(rec);
+ return DBUS_HANDLER_RESULT_NEED_MEMORY;
+ }
+
+ /* FIXME: register the service */
+
+ /* Only add a D-Bus unique name listener if there isn't one already registered */
+ memset(&ref, 0, sizeof(ref));
+ bacpy(&ref.provider, BDADDR_ANY);
+
+ if (!slist_find(sdp_records, &ref, (cmp_func_t)record_cmp))
+ name_listener_add(conn, rec->owner, (name_cb_t)owner_exited, dbus_data);
+
+ sdp_records = slist_append(sdp_records, rec);
+
+ 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 dbus_sdp_record *rec, ref;
+ struct slist *match;
+ DBusMessage *reply;
+ DBusError err;
+ const char *owner, *identifier;
+
+ owner = dbus_message_get_sender(msg);
+
+ dbus_error_init(&err);
+ dbus_message_get_args(msg, &err,
+ DBUS_TYPE_STRING, &identifier,
+ DBUS_TYPE_INVALID);
+
+ if (dbus_error_is_set(&err)) {
+ error("Can't extract message arguments:%s", err.message);
+ dbus_error_free(&err);
+ return error_invalid_arguments(conn, msg);
+ }
+
+ memset(&ref, 0, sizeof(ref));
+
+ ref.uuid = RFCOMM_UUID;
+ ref.identifier = str2id(identifier);
+ hci_devba(dbus_data->dev_id, &ref.provider);
+
+ match = slist_find(sdp_records, &ref, (cmp_func_t)record_cmp);
+ if (!match)
+ return error_service_does_not_exist(conn, msg);
+
+ rec = match->data;
+
+ if (strcmp(rec->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_records = slist_remove(sdp_records, rec);
+ dbus_sdp_record_free(rec);
+
+ bacpy(&ref.provider, BDADDR_ANY);
+ ref.uuid = 0x0000;
+
+ /* Only remove the D-Bus unique name listener if there are no more record using this name */
+ if (!slist_find(sdp_records, &ref, (cmp_func_t)record_cmp))
+ name_listener_remove(conn, ref.name, (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;
+
+ handler = find_service_handler(sdp_services, msg);
+
+ if (handler)
+ return handler(conn, msg, data);
+
+ return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
+}