Blame obexd/client/bluetooth.c

Packit 34410b
/*
Packit 34410b
 *
Packit 34410b
 *  OBEX Client
Packit 34410b
 *
Packit 34410b
 *  Copyright (C) 2012 Intel Corporation
Packit 34410b
 *
Packit 34410b
 *
Packit 34410b
 *  This program is free software; you can redistribute it and/or modify
Packit 34410b
 *  it under the terms of the GNU General Public License as published by
Packit 34410b
 *  the Free Software Foundation; either version 2 of the License, or
Packit 34410b
 *  (at your option) any later version.
Packit 34410b
 *
Packit 34410b
 *  This program is distributed in the hope that it will be useful,
Packit 34410b
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
Packit 34410b
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
Packit 34410b
 *  GNU General Public License for more details.
Packit 34410b
 *
Packit 34410b
 *  You should have received a copy of the GNU General Public License
Packit 34410b
 *  along with this program; if not, write to the Free Software
Packit 34410b
 *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
Packit 34410b
 *
Packit 34410b
 */
Packit 34410b
Packit 34410b
#ifdef HAVE_CONFIG_H
Packit 34410b
#include <config.h>
Packit 34410b
#endif
Packit 34410b
Packit 34410b
#include <stdlib.h>
Packit 34410b
#include <errno.h>
Packit 34410b
#include <inttypes.h>
Packit 34410b
Packit 34410b
#include <glib.h>
Packit 34410b
Packit 34410b
#include "lib/bluetooth.h"
Packit 34410b
#include "lib/rfcomm.h"
Packit 34410b
#include "lib/sdp.h"
Packit 34410b
#include "lib/sdp_lib.h"
Packit 34410b
Packit 34410b
#include "gdbus/gdbus.h"
Packit 34410b
#include "btio/btio.h"
Packit 34410b
Packit 34410b
#include "obexd/src/log.h"
Packit 34410b
#include "transport.h"
Packit 34410b
#include "bluetooth.h"
Packit 34410b
Packit 34410b
#define BT_RX_MTU 32767
Packit 34410b
#define BT_TX_MTU 32767
Packit 34410b
Packit 34410b
#define OBC_BT_ERROR obc_bt_error_quark()
Packit 34410b
Packit 34410b
struct bluetooth_session {
Packit 34410b
	guint id;
Packit 34410b
	bdaddr_t src;
Packit 34410b
	bdaddr_t dst;
Packit 34410b
	uint16_t port;
Packit 34410b
	sdp_session_t *sdp;
Packit 34410b
	sdp_record_t *sdp_record;
Packit 34410b
	GIOChannel *io;
Packit 34410b
	char *service;
Packit 34410b
	obc_transport_func func;
Packit 34410b
	void *user_data;
Packit 34410b
};
Packit 34410b
Packit 34410b
static GSList *sessions = NULL;
Packit 34410b
Packit 34410b
static GQuark obc_bt_error_quark(void)
Packit 34410b
{
Packit 34410b
	return g_quark_from_static_string("obc-bluetooth-error-quark");
Packit 34410b
}
Packit 34410b
Packit 34410b
static void session_destroy(struct bluetooth_session *session)
Packit 34410b
{
Packit 34410b
	DBG("%p", session);
Packit 34410b
Packit 34410b
	if (g_slist_find(sessions, session) == NULL)
Packit 34410b
		return;
Packit 34410b
Packit 34410b
	sessions = g_slist_remove(sessions, session);
Packit 34410b
Packit 34410b
	if (session->io != NULL) {
Packit 34410b
		g_io_channel_shutdown(session->io, TRUE, NULL);
Packit 34410b
		g_io_channel_unref(session->io);
Packit 34410b
	}
Packit 34410b
Packit 34410b
	if (session->sdp)
Packit 34410b
		sdp_close(session->sdp);
Packit 34410b
Packit 34410b
	if (session->sdp_record)
Packit 34410b
		sdp_record_free(session->sdp_record);
Packit 34410b
Packit 34410b
	g_free(session->service);
Packit 34410b
	g_free(session);
Packit 34410b
}
Packit 34410b
Packit 34410b
static void transport_callback(GIOChannel *io, GError *err, gpointer user_data)
Packit 34410b
{
Packit 34410b
	struct bluetooth_session *session = user_data;
Packit 34410b
Packit 34410b
	DBG("");
Packit 34410b
Packit 34410b
	if (session->func)
Packit 34410b
		session->func(io, err, session->user_data);
Packit 34410b
Packit 34410b
	if (err != NULL)
Packit 34410b
		session_destroy(session);
Packit 34410b
}
Packit 34410b
Packit 34410b
static GIOChannel *transport_connect(const bdaddr_t *src, const bdaddr_t *dst,
Packit 34410b
					uint16_t port, BtIOConnect function,
Packit 34410b
					gpointer user_data)
Packit 34410b
{
Packit 34410b
	GIOChannel *io;
Packit 34410b
	GError *err = NULL;
Packit 34410b
Packit 34410b
	DBG("port %u", port);
Packit 34410b
Packit 34410b
	if (port > 31) {
Packit 34410b
		io = bt_io_connect(function, user_data,
Packit 34410b
				NULL, &err,
Packit 34410b
				BT_IO_OPT_SOURCE_BDADDR, src,
Packit 34410b
				BT_IO_OPT_DEST_BDADDR, dst,
Packit 34410b
				BT_IO_OPT_PSM, port,
Packit 34410b
				BT_IO_OPT_MODE, BT_IO_MODE_ERTM,
Packit 34410b
				BT_IO_OPT_OMTU, BT_TX_MTU,
Packit 34410b
				BT_IO_OPT_IMTU, BT_RX_MTU,
Packit 34410b
				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
Packit 34410b
				BT_IO_OPT_INVALID);
Packit 34410b
	} else {
Packit 34410b
		io = bt_io_connect(function, user_data,
Packit 34410b
				NULL, &err,
Packit 34410b
				BT_IO_OPT_SOURCE_BDADDR, src,
Packit 34410b
				BT_IO_OPT_DEST_BDADDR, dst,
Packit 34410b
				BT_IO_OPT_CHANNEL, port,
Packit 34410b
				BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_LOW,
Packit 34410b
				BT_IO_OPT_INVALID);
Packit 34410b
	}
Packit 34410b
Packit 34410b
	if (io != NULL)
Packit 34410b
		return io;
Packit 34410b
Packit 34410b
	error("%s", err->message);
Packit 34410b
	g_error_free(err);
Packit 34410b
	return NULL;
Packit 34410b
}
Packit 34410b
Packit 34410b
static void search_callback(uint8_t type, uint16_t status,
Packit 34410b
			uint8_t *rsp, size_t size, void *user_data)
Packit 34410b
{
Packit 34410b
	struct bluetooth_session *session = user_data;
Packit 34410b
	unsigned int scanned, bytesleft = size;
Packit 34410b
	int seqlen = 0;
Packit 34410b
	uint8_t dataType;
Packit 34410b
	uint16_t port = 0;
Packit 34410b
	GError *gerr = NULL;
Packit 34410b
Packit 34410b
	if (status || type != SDP_SVC_SEARCH_ATTR_RSP)
Packit 34410b
		goto failed;
Packit 34410b
Packit 34410b
	scanned = sdp_extract_seqtype(rsp, bytesleft, &dataType, &seqlen);
Packit 34410b
	if (!scanned || !seqlen)
Packit 34410b
		goto failed;
Packit 34410b
Packit 34410b
	rsp += scanned;
Packit 34410b
	bytesleft -= scanned;
Packit 34410b
	do {
Packit 34410b
		sdp_record_t *rec;
Packit 34410b
		sdp_list_t *protos;
Packit 34410b
		sdp_data_t *data;
Packit 34410b
		int recsize, ch = -1;
Packit 34410b
Packit 34410b
		recsize = 0;
Packit 34410b
		rec = sdp_extract_pdu(rsp, bytesleft, &recsize);
Packit 34410b
		if (!rec)
Packit 34410b
			break;
Packit 34410b
Packit 34410b
		if (!recsize) {
Packit 34410b
			sdp_record_free(rec);
Packit 34410b
			break;
Packit 34410b
		}
Packit 34410b
Packit 34410b
		if (!sdp_get_access_protos(rec, &protos)) {
Packit 34410b
			ch = sdp_get_proto_port(protos, RFCOMM_UUID);
Packit 34410b
			sdp_list_foreach(protos,
Packit 34410b
					(sdp_list_func_t) sdp_list_free, NULL);
Packit 34410b
			sdp_list_free(protos, NULL);
Packit 34410b
			protos = NULL;
Packit 34410b
		}
Packit 34410b
Packit 34410b
		data = sdp_data_get(rec, 0x0200);
Packit 34410b
		/* PSM must be odd and lsb of upper byte must be 0 */
Packit 34410b
		if (data != NULL && (data->val.uint16 & 0x0101) == 0x0001)
Packit 34410b
			ch = data->val.uint16;
Packit 34410b
Packit 34410b
		/* Cache the sdp record associated with the service that we
Packit 34410b
		 * attempt to connect. This allows reading its application
Packit 34410b
		 * specific service attributes. */
Packit 34410b
		if (ch > 0) {
Packit 34410b
			port = ch;
Packit 34410b
			session->sdp_record = rec;
Packit 34410b
			break;
Packit 34410b
		}
Packit 34410b
Packit 34410b
		sdp_record_free(rec);
Packit 34410b
Packit 34410b
		scanned += recsize;
Packit 34410b
		rsp += recsize;
Packit 34410b
		bytesleft -= recsize;
Packit 34410b
	} while (scanned < size && bytesleft > 0);
Packit 34410b
Packit 34410b
	if (port == 0)
Packit 34410b
		goto failed;
Packit 34410b
Packit 34410b
	session->port = port;
Packit 34410b
Packit 34410b
	g_io_channel_set_close_on_unref(session->io, FALSE);
Packit 34410b
	g_io_channel_unref(session->io);
Packit 34410b
Packit 34410b
	session->io = transport_connect(&session->src, &session->dst, port,
Packit 34410b
						transport_callback, session);
Packit 34410b
	if (session->io != NULL) {
Packit 34410b
		sdp_close(session->sdp);
Packit 34410b
		session->sdp = NULL;
Packit 34410b
		return;
Packit 34410b
	}
Packit 34410b
Packit 34410b
failed:
Packit 34410b
	if (session->io != NULL) {
Packit 34410b
		g_io_channel_shutdown(session->io, TRUE, NULL);
Packit 34410b
		g_io_channel_unref(session->io);
Packit 34410b
		session->io = NULL;
Packit 34410b
	}
Packit 34410b
Packit 34410b
	g_set_error(&gerr, OBC_BT_ERROR, -EIO,
Packit 34410b
					"Unable to find service record");
Packit 34410b
	if (session->func)
Packit 34410b
		session->func(session->io, gerr, session->user_data);
Packit 34410b
Packit 34410b
	g_clear_error(&gerr);
Packit 34410b
Packit 34410b
	session_destroy(session);
Packit 34410b
}
Packit 34410b
Packit 34410b
static gboolean process_callback(GIOChannel *io, GIOCondition cond,
Packit 34410b
							gpointer user_data)
Packit 34410b
{
Packit 34410b
	struct bluetooth_session *session = user_data;
Packit 34410b
Packit 34410b
	if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
Packit 34410b
		return FALSE;
Packit 34410b
Packit 34410b
	if (sdp_process(session->sdp) < 0)
Packit 34410b
		return FALSE;
Packit 34410b
Packit 34410b
	return TRUE;
Packit 34410b
}
Packit 34410b
Packit 34410b
static int bt_string2uuid(uuid_t *uuid, const char *string)
Packit 34410b
{
Packit 34410b
	uint32_t data0, data4;
Packit 34410b
	uint16_t data1, data2, data3, data5;
Packit 34410b
Packit 34410b
	if (sscanf(string, "%08x-%04hx-%04hx-%04hx-%08x%04hx",
Packit 34410b
				&data0, &data1, &data2, &data3, &data4, &data5) == 6) {
Packit 34410b
		uint8_t val[16];
Packit 34410b
Packit 34410b
		data0 = g_htonl(data0);
Packit 34410b
		data1 = g_htons(data1);
Packit 34410b
		data2 = g_htons(data2);
Packit 34410b
		data3 = g_htons(data3);
Packit 34410b
		data4 = g_htonl(data4);
Packit 34410b
		data5 = g_htons(data5);
Packit 34410b
Packit 34410b
		memcpy(&val[0], &data0, 4);
Packit 34410b
		memcpy(&val[4], &data1, 2);
Packit 34410b
		memcpy(&val[6], &data2, 2);
Packit 34410b
		memcpy(&val[8], &data3, 2);
Packit 34410b
		memcpy(&val[10], &data4, 4);
Packit 34410b
		memcpy(&val[14], &data5, 2);
Packit 34410b
Packit 34410b
		sdp_uuid128_create(uuid, val);
Packit 34410b
Packit 34410b
		return 0;
Packit 34410b
	}
Packit 34410b
Packit 34410b
	return -EINVAL;
Packit 34410b
}
Packit 34410b
Packit 34410b
static gboolean service_callback(GIOChannel *io, GIOCondition cond,
Packit 34410b
							gpointer user_data)
Packit 34410b
{
Packit 34410b
	struct bluetooth_session *session = user_data;
Packit 34410b
	sdp_list_t *search, *attrid;
Packit 34410b
	uint32_t range = 0x0000ffff;
Packit 34410b
	GError *gerr = NULL;
Packit 34410b
	uuid_t uuid;
Packit 34410b
Packit 34410b
	if (cond & G_IO_NVAL)
Packit 34410b
		return FALSE;
Packit 34410b
Packit 34410b
	if (cond & G_IO_ERR)
Packit 34410b
		goto failed;
Packit 34410b
Packit 34410b
	if (sdp_set_notify(session->sdp, search_callback, session) < 0)
Packit 34410b
		goto failed;
Packit 34410b
Packit 34410b
	if (bt_string2uuid(&uuid, session->service) < 0)
Packit 34410b
		goto failed;
Packit 34410b
Packit 34410b
	sdp_uuid128_to_uuid(&uuid);
Packit 34410b
Packit 34410b
	search = sdp_list_append(NULL, &uuid);
Packit 34410b
	attrid = sdp_list_append(NULL, &range);
Packit 34410b
Packit 34410b
	if (sdp_service_search_attr_async(session->sdp,
Packit 34410b
				search, SDP_ATTR_REQ_RANGE, attrid) < 0) {
Packit 34410b
		sdp_list_free(attrid, NULL);
Packit 34410b
		sdp_list_free(search, NULL);
Packit 34410b
		goto failed;
Packit 34410b
	}
Packit 34410b
Packit 34410b
	sdp_list_free(attrid, NULL);
Packit 34410b
	sdp_list_free(search, NULL);
Packit 34410b
Packit 34410b
	g_io_add_watch(io, G_IO_IN | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
Packit 34410b
						process_callback, session);
Packit 34410b
Packit 34410b
	return FALSE;
Packit 34410b
Packit 34410b
failed:
Packit 34410b
	g_io_channel_shutdown(session->io, TRUE, NULL);
Packit 34410b
	g_io_channel_unref(session->io);
Packit 34410b
	session->io = NULL;
Packit 34410b
Packit 34410b
	g_set_error(&gerr, OBC_BT_ERROR, -EIO,
Packit 34410b
					"Unable to find service record");
Packit 34410b
	if (session->func)
Packit 34410b
		session->func(session->io, gerr, session->user_data);
Packit 34410b
	g_clear_error(&gerr);
Packit 34410b
Packit 34410b
	session_destroy(session);
Packit 34410b
	return FALSE;
Packit 34410b
}
Packit 34410b
Packit 34410b
static sdp_session_t *service_connect(const bdaddr_t *src, const bdaddr_t *dst,
Packit 34410b
					GIOFunc function, gpointer user_data)
Packit 34410b
{
Packit 34410b
	struct bluetooth_session *session = user_data;
Packit 34410b
	sdp_session_t *sdp;
Packit 34410b
	GIOChannel *io;
Packit 34410b
Packit 34410b
	DBG("");
Packit 34410b
Packit 34410b
	sdp = sdp_connect(src, dst, SDP_NON_BLOCKING);
Packit 34410b
	if (sdp == NULL)
Packit 34410b
		return NULL;
Packit 34410b
Packit 34410b
	io = g_io_channel_unix_new(sdp_get_socket(sdp));
Packit 34410b
	if (io == NULL) {
Packit 34410b
		sdp_close(sdp);
Packit 34410b
		return NULL;
Packit 34410b
	}
Packit 34410b
Packit 34410b
	g_io_add_watch(io, G_IO_OUT | G_IO_HUP | G_IO_ERR | G_IO_NVAL,
Packit 34410b
							function, user_data);
Packit 34410b
Packit 34410b
	session->io = io;
Packit 34410b
Packit 34410b
	return sdp;
Packit 34410b
}
Packit 34410b
Packit 34410b
static int session_connect(struct bluetooth_session *session)
Packit 34410b
{
Packit 34410b
	int err;
Packit 34410b
Packit 34410b
	DBG("session %p", session);
Packit 34410b
Packit 34410b
	if (session->port > 0) {
Packit 34410b
		session->io = transport_connect(&session->src, &session->dst,
Packit 34410b
							session->port,
Packit 34410b
							transport_callback,
Packit 34410b
							session);
Packit 34410b
		err = (session->io == NULL) ? -EINVAL : 0;
Packit 34410b
	} else {
Packit 34410b
		session->sdp = service_connect(&session->src, &session->dst,
Packit 34410b
						service_callback, session);
Packit 34410b
		err = (session->sdp == NULL) ? -ENOMEM : 0;
Packit 34410b
	}
Packit 34410b
Packit 34410b
	return err;
Packit 34410b
}
Packit 34410b
Packit 34410b
static guint bluetooth_connect(const char *source, const char *destination,
Packit 34410b
				const char *service, uint16_t port,
Packit 34410b
				obc_transport_func func, void *user_data)
Packit 34410b
{
Packit 34410b
	struct bluetooth_session *session;
Packit 34410b
	static guint id = 0;
Packit 34410b
Packit 34410b
	DBG("src %s dest %s service %s port %u",
Packit 34410b
				source, destination, service, port);
Packit 34410b
Packit 34410b
	if (destination == NULL)
Packit 34410b
		return 0;
Packit 34410b
Packit 34410b
	session = g_try_malloc0(sizeof(*session));
Packit 34410b
	if (session == NULL)
Packit 34410b
		return 0;
Packit 34410b
Packit 34410b
	session->id = ++id;
Packit 34410b
	session->func = func;
Packit 34410b
	session->port = port;
Packit 34410b
	session->user_data = user_data;
Packit 34410b
Packit 34410b
	str2ba(destination, &session->dst);
Packit 34410b
	str2ba(source, &session->src);
Packit 34410b
Packit 34410b
	if (session_connect(session) < 0) {
Packit 34410b
		g_free(session);
Packit 34410b
		return 0;
Packit 34410b
	}
Packit 34410b
Packit 34410b
	session->service = g_strdup(service);
Packit 34410b
	sessions = g_slist_prepend(sessions, session);
Packit 34410b
Packit 34410b
	return session->id;
Packit 34410b
}
Packit 34410b
Packit 34410b
static void bluetooth_disconnect(guint id)
Packit 34410b
{
Packit 34410b
	GSList *l;
Packit 34410b
Packit 34410b
	DBG("");
Packit 34410b
Packit 34410b
	for (l = sessions; l; l = l->next) {
Packit 34410b
		struct bluetooth_session *session = l->data;
Packit 34410b
Packit 34410b
		if (session->id == id) {
Packit 34410b
			session_destroy(session);
Packit 34410b
			return;
Packit 34410b
		}
Packit 34410b
	}
Packit 34410b
}
Packit 34410b
Packit 34410b
static int bluetooth_getpacketopt(GIOChannel *io, int *tx_mtu, int *rx_mtu)
Packit 34410b
{
Packit 34410b
	int sk = g_io_channel_unix_get_fd(io);
Packit 34410b
	int type;
Packit 34410b
	uint16_t omtu = BT_TX_MTU;
Packit 34410b
	uint16_t imtu = BT_RX_MTU;
Packit 34410b
	socklen_t len = sizeof(int);
Packit 34410b
Packit 34410b
	DBG("");
Packit 34410b
Packit 34410b
	if (getsockopt(sk, SOL_SOCKET, SO_TYPE, &type, &len) < 0)
Packit 34410b
		return -errno;
Packit 34410b
Packit 34410b
	if (type != SOCK_SEQPACKET)
Packit 34410b
		return -EINVAL;
Packit 34410b
Packit 34410b
	if (!bt_io_get(io, NULL, BT_IO_OPT_OMTU, &omtu,
Packit 34410b
						BT_IO_OPT_IMTU, &imtu,
Packit 34410b
						BT_IO_OPT_INVALID))
Packit 34410b
		return -EINVAL;
Packit 34410b
Packit 34410b
	if (tx_mtu)
Packit 34410b
		*tx_mtu = omtu;
Packit 34410b
Packit 34410b
	if (rx_mtu)
Packit 34410b
		*rx_mtu = imtu;
Packit 34410b
Packit 34410b
	return 0;
Packit 34410b
}
Packit 34410b
Packit 34410b
static const void *bluetooth_getattribute(guint id, int attribute_id)
Packit 34410b
{
Packit 34410b
	GSList *l;
Packit 34410b
	sdp_data_t *data;
Packit 34410b
Packit 34410b
	for (l = sessions; l; l = l->next) {
Packit 34410b
		struct bluetooth_session *session = l->data;
Packit 34410b
Packit 34410b
		if (session->id != id)
Packit 34410b
			continue;
Packit 34410b
Packit 34410b
		if (session->sdp_record == NULL)
Packit 34410b
			break;
Packit 34410b
Packit 34410b
		/* Read version since UUID is already known */
Packit 34410b
		if (attribute_id == SDP_ATTR_PFILE_DESC_LIST) {
Packit 34410b
			sdp_list_t *descs;
Packit 34410b
			void *ret = NULL;
Packit 34410b
Packit 34410b
			if (sdp_get_profile_descs(session->sdp_record,
Packit 34410b
								&descs) < 0)
Packit 34410b
				return NULL;
Packit 34410b
Packit 34410b
			if (descs && descs->data) {
Packit 34410b
				sdp_profile_desc_t *desc = descs->data;
Packit 34410b
				ret = GINT_TO_POINTER(desc->version);
Packit 34410b
			}
Packit 34410b
Packit 34410b
			sdp_list_free(descs, free);
Packit 34410b
Packit 34410b
			return ret;
Packit 34410b
		}
Packit 34410b
Packit 34410b
		data = sdp_data_get(session->sdp_record, attribute_id);
Packit 34410b
		if (!data)
Packit 34410b
			break;
Packit 34410b
Packit 34410b
		return &data->val;
Packit 34410b
	}
Packit 34410b
	return NULL;
Packit 34410b
}
Packit 34410b
Packit 34410b
static struct obc_transport bluetooth = {
Packit 34410b
	.name = "Bluetooth",
Packit 34410b
	.connect = bluetooth_connect,
Packit 34410b
	.getpacketopt = bluetooth_getpacketopt,
Packit 34410b
	.disconnect = bluetooth_disconnect,
Packit 34410b
	.getattribute = bluetooth_getattribute,
Packit 34410b
};
Packit 34410b
Packit 34410b
int bluetooth_init(void)
Packit 34410b
{
Packit 34410b
	DBG("");
Packit 34410b
Packit 34410b
	return obc_transport_register(&bluetooth);
Packit 34410b
}
Packit 34410b
Packit 34410b
void bluetooth_exit(void)
Packit 34410b
{
Packit 34410b
	DBG("");
Packit 34410b
Packit 34410b
	obc_transport_unregister(&bluetooth);
Packit 34410b
}