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; +} |