diff options
Diffstat (limited to 'serial/port.c')
| -rw-r--r-- | serial/port.c | 404 | 
1 files changed, 404 insertions, 0 deletions
diff --git a/serial/port.c b/serial/port.c new file mode 100644 index 00000000..f0563244 --- /dev/null +++ b/serial/port.c @@ -0,0 +1,404 @@ +/* + * + *  BlueZ - Bluetooth protocol stack for Linux + * + *  Copyright (C) 2004-2008  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 <errno.h> +#include <stdio.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <termios.h> +#include <sys/ioctl.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/rfcomm.h> +#include <bluetooth/sdp.h> + +#include <glib.h> +#include <gdbus.h> + +#include "../hcid/dbus-common.h" + +#include "logging.h" + +#include "error.h" +#include "manager.h" +#include "storage.h" + +#define SERIAL_PORT_INTERFACE	"org.bluez.serial.Port" + +struct rfcomm_node { +	int16_t		id;		/* RFCOMM device id */ +	bdaddr_t	src;		/* Source (local) address */ +	bdaddr_t	dst;		/* Destination address */ +	char		*svcname;	/* RFCOMM service name */ +	char		*device;	/* RFCOMM device name */ +	DBusConnection	*conn;		/* for name listener handling */ +	char		*owner;		/* Bus name */ +	GIOChannel	*io;		/* Connected node IO Channel */ +	guint		io_id;		/* IO Channel ID */ +	guint		listener_id; +}; + +static GSList *connected_nodes = NULL; +static GSList *bound_nodes = NULL; + +static struct rfcomm_node *find_node_by_name(GSList *nodes, const char *dev) +{ +	GSList *l; + +	for (l = nodes; l != NULL; l = l->next) { +		struct rfcomm_node *node = l->data; +		if (!strcmp(node->device, dev)) +			return node; +	} + +	return NULL; +} + +static DBusMessage *port_get_address(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct rfcomm_node *node = data; +	DBusMessage *reply; +	char bda[18]; +	const char *pbda = bda; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	ba2str(&node->dst, bda); +	dbus_message_append_args(reply, +			DBUS_TYPE_STRING, &pbda, +			DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *port_get_device(DBusConnection *conn, +					DBusMessage *msg, void *data) +{ +	struct rfcomm_node *node = data; +	DBusMessage *reply; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, +			DBUS_TYPE_STRING, &node->device, +			DBUS_TYPE_INVALID); + +	return reply; + +} + +static DBusMessage *port_get_adapter(DBusConnection *conn, +				  DBusMessage *msg, void *data) +{ +	struct rfcomm_node *node = data; +	DBusMessage *reply; +	char addr[18]; +	const char *paddr = addr; + +	ba2str(&node->src, addr); + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, +			DBUS_TYPE_STRING, &paddr, +			DBUS_TYPE_INVALID); + +	return reply; +} + + +static DBusMessage *port_get_name(DBusConnection *conn, +			       DBusMessage *msg, void *data) +{ +	struct rfcomm_node *node = data; +	DBusMessage *reply; +	const char *pname; +	char *name = NULL; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	read_device_name(&node->src, &node->dst, &name); + +	pname = (name ? name : ""); +	dbus_message_append_args(reply, +			DBUS_TYPE_STRING, &pname, +			DBUS_TYPE_INVALID); + +	if (name) +		g_free(name); + +	return reply; +} + +static DBusMessage *port_get_service_name(DBusConnection *conn, +				       DBusMessage *msg, void *data) +{ +	struct rfcomm_node *node = data; +	DBusMessage *reply; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_append_args(reply, +			DBUS_TYPE_STRING, &node->svcname, +			DBUS_TYPE_INVALID); + +	return reply; +} + +static DBusMessage *port_get_info(DBusConnection *conn, +				DBusMessage *msg, void *data) +{ +	struct rfcomm_node *node = data; +	DBusMessage *reply; +	DBusMessageIter iter, dict; +	char bda[18]; +	const char *pbda = bda; + +	reply = dbus_message_new_method_return(msg); +	if (!reply) +		return NULL; + +	dbus_message_iter_init_append(reply, &iter); + +	dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, +			DBUS_DICT_ENTRY_BEGIN_CHAR_AS_STRING +			DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING +			DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict); + +	dbus_message_iter_append_dict_entry(&dict, "device", +			DBUS_TYPE_STRING, &node->device); + +	ba2str(&node->dst, bda); +	dbus_message_iter_append_dict_entry(&dict, "address", +			DBUS_TYPE_STRING, &pbda); + +	dbus_message_iter_close_container(&iter, &dict); + +	return reply; +} + +static GDBusMethodTable port_methods[] = { +	{ "GetAddress",		"",	"s",	port_get_address }, +	{ "GetDevice",		"",	"s",	port_get_device }, +	{ "GetAdapter",		"",	"s",	port_get_adapter }, +	{ "GetName",		"",	"s",	port_get_name }, +	{ "GetServiceName",	"",	"s",	port_get_service_name }, +	{ "GetInfo",		"",	"a{sv}",port_get_info }, +	{ NULL, NULL, NULL, NULL }, +}; + +static GDBusSignalTable port_signals[] = { +	{ NULL, NULL } +}; + +static void rfcomm_node_free(struct rfcomm_node *node) +{ +	if (node->device) +		g_free(node->device); +	if (node->conn) +		dbus_connection_unref(node->conn); +	if (node->owner) +		g_free(node->owner); +	if (node->svcname) +		g_free(node->svcname); +	if (node->io) { +		g_source_remove(node->io_id); +		g_io_channel_close(node->io); +		g_io_channel_unref(node->io); +	} +	rfcomm_release(node->id); +	g_free(node); +} + +static void connection_owner_exited(void *user_data) +{ +	struct rfcomm_node *node = user_data; + +	debug("Connect requestor exited. Releasing %s node", +						node->device); + +	g_dbus_emit_signal(node->conn, SERIAL_MANAGER_PATH, +			SERIAL_MANAGER_INTERFACE, "ServiceDisconnected" , +			DBUS_TYPE_STRING, &node->device, +			DBUS_TYPE_INVALID); + +	connected_nodes = g_slist_remove(connected_nodes, node); +	rfcomm_node_free(node); +} + +static gboolean rfcomm_disconnect_cb(GIOChannel *io, +		GIOCondition cond, struct rfcomm_node *node) +{ +	debug("RFCOMM node %s was disconnected", node->device); + +	g_dbus_remove_watch(node->conn, node->listener_id); + +	g_dbus_emit_signal(node->conn, SERIAL_MANAGER_PATH, +			SERIAL_MANAGER_INTERFACE, "ServiceDisconnected" , +			DBUS_TYPE_STRING, &node->device, +			DBUS_TYPE_INVALID); + +	connected_nodes = g_slist_remove(connected_nodes, node); +	rfcomm_node_free(node); + +	return FALSE; +} + +static void port_handler_unregister(void *data) +{ +	struct rfcomm_node *node = data; + +	debug("Unregistered serial port: %s", node->device); + +	bound_nodes = g_slist_remove(bound_nodes, node); +	rfcomm_node_free(node); +} + +void port_add_listener(DBusConnection *conn, int16_t id, bdaddr_t *dst, +			int fd, const char *dev, const char *owner) +{ +	struct rfcomm_node *node; + +	node = g_new0(struct rfcomm_node, 1); +	bacpy(&node->dst, dst); +	node->id	= id; +	node->device	= g_strdup(dev); +	node->conn	= dbus_connection_ref(conn); +	node->owner	= g_strdup(owner); +	node->io	= g_io_channel_unix_new(fd); +	node->io_id = g_io_add_watch(node->io, G_IO_ERR | G_IO_NVAL | G_IO_HUP, +					(GIOFunc) rfcomm_disconnect_cb, node); + +	connected_nodes = g_slist_append(connected_nodes, node); + +	/* Service connection listener */ +	node->listener_id = g_dbus_add_disconnect_watch(conn, owner, +						connection_owner_exited, node, +						NULL); +} + +int port_remove_listener(const char *owner, const char *dev) +{ +	struct rfcomm_node *node; + +	node = find_node_by_name(connected_nodes, dev); +	if (!node) +		return -ENOENT; +	if (strcmp(node->owner, owner) != 0) +		return -EPERM; + +	g_dbus_remove_watch(node->conn, node->listener_id); + +	connected_nodes = g_slist_remove(connected_nodes, node); +	rfcomm_node_free(node); + +	return 0; +} + +void port_release_all(void) +{ +	struct rfcomm_node *node; +	GSList *l; + +	for (l = connected_nodes; l; l = l->next) { +		node = l->data; + +		connected_nodes = g_slist_remove(connected_nodes, node); +		rfcomm_node_free(node); +	} +} + +int port_register(DBusConnection *conn, int16_t id, bdaddr_t *src, +		  bdaddr_t *dst, const char *dev, char *ppath, const char *svc) +{ +	char path[MAX_PATH_LENGTH]; +	struct rfcomm_node *node; + +	node = g_new0(struct rfcomm_node, 1); +	bacpy(&node->dst, dst); +	bacpy(&node->src, src); +	node->id	= id; +	node->device	= g_strdup(dev); +	node->conn	= dbus_connection_ref(conn); +	node->svcname	= g_strdup(svc?:"Bluetooth RFCOMM port"); + +	snprintf(path, MAX_PATH_LENGTH, "%s/rfcomm%hd", SERIAL_MANAGER_PATH, id); + + +	if (!g_dbus_register_interface(conn, path, +				SERIAL_PORT_INTERFACE, +				port_methods, port_signals, NULL, +				node, port_handler_unregister)) { +		error("D-Bus failed to register %s interface", +				SERIAL_PORT_INTERFACE); +		rfcomm_node_free(node); +		return -1; +	} + +	info("Registered RFCOMM:%s, path:%s", dev, path); + +	if (ppath) +		strcpy(ppath, path); + +	bound_nodes = g_slist_append(bound_nodes, node); + +	return 0; +} + +int port_unregister(const char *path) +{ +	struct rfcomm_node *node; +	char dev[16]; +	int16_t id; + +	if (sscanf(path, SERIAL_MANAGER_PATH"/rfcomm%hd", &id) != 1) +		return -ENOENT; + +	snprintf(dev, sizeof(dev), "/dev/rfcomm%hd", id); +	node = find_node_by_name(bound_nodes, dev); +	if (!node) +		return -ENOENT; + +	g_dbus_unregister_interface(node->conn, path, SERIAL_PORT_INTERFACE); + +	return 0; +}  | 
