diff options
-rw-r--r-- | serial/manager.c | 138 |
1 files changed, 129 insertions, 9 deletions
diff --git a/serial/manager.c b/serial/manager.c index 0b35f6e6..795b9b36 100644 --- a/serial/manager.c +++ b/serial/manager.c @@ -30,6 +30,9 @@ #include <stdio.h> #include <stdlib.h> #include <string.h> +#include <unistd.h> +#include <sys/ioctl.h> + #include <glib.h> #include <bluetooth/bluetooth.h> @@ -37,6 +40,7 @@ #include <bluetooth/hci_lib.h> #include <bluetooth/sdp.h> #include <bluetooth/sdp_lib.h> +#include <bluetooth/rfcomm.h> #include "dbus.h" #include "logging.h" @@ -53,9 +57,10 @@ struct pending_connection { DBusConnection *conn; DBusMessage *msg; - bdaddr_t src; /* Source address */ char *addr; /* Destination address */ char *adapter_path; /* Adapter D-Bus path */ + bdaddr_t src; + uint8_t channel; }; static DBusConnection *connection = NULL; @@ -105,6 +110,108 @@ static DBusHandlerResult err_not_supported(DBusConnection *conn, "The service is not supported by the remote device")); } +static gboolean rfcomm_connect_cb(GIOChannel *chan, + GIOCondition cond, struct pending_connection *pc) +{ + DBusMessage *reply; + char node_name[16]; + const char *pname = node_name; + struct rfcomm_dev_req req; + int sk, ret, err, id; + socklen_t len; + + /* FIXME: Check if it was canceled */ + + sk = g_io_channel_unix_get_fd(chan); + + len = sizeof(ret); + if (getsockopt(sk, SOL_SOCKET, SO_ERROR, &ret, &len) < 0) { + err = errno; + error("getsockopt(SO_ERROR): %s (%d)", strerror(err), err); + goto fail; + } + + if (ret != 0) { + error("connect(): %s (%d)", strerror(ret), ret); + goto fail; + } + + debug("rfcomm_connect_cb: connected"); + + memset(&req, 0, sizeof(req)); + req.dev_id = -1; + req.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP); + bacpy(&req.src, &pc->src); + str2ba(pc->addr, &req.dst); + req.channel = pc->channel; + + id = ioctl(sk, RFCOMMCREATEDEV, &req); + if (id < 0) { + err = errno; + error("ioctl(RFCOMMCREATEDEV): %s (%d)", strerror(err), err); + err_connection_failed(pc->conn, pc->msg, strerror(err)); + goto fail; + } + + snprintf(node_name, 16, "/dev/rfcomm%d", id); + reply = dbus_message_new_method_return(pc->msg); + dbus_message_append_args(reply, + DBUS_TYPE_STRING, &pname, + DBUS_TYPE_INVALID); + send_message_and_unref(pc->conn, reply); +fail: + pending_connection_free(pc); + /* FIXME: Remote from the pending connects list */ + return FALSE; +} + +static int rfcomm_connect(struct pending_connection *pc) +{ + struct sockaddr_rc addr; + GIOChannel *io; + int sk, err = 0; + + sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sk < 0) + return -errno; + + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, &pc->src); + addr.rc_channel = 0; + + if (bind(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) + return -errno; + + if (set_nonblocking(sk) < 0) + return -errno; + + io = g_io_channel_unix_new(sk); + g_io_channel_set_close_on_unref(io, TRUE); + + addr.rc_family = AF_BLUETOOTH; + str2ba(pc->addr, &addr.rc_bdaddr); + addr.rc_channel = pc->channel; + + if (connect(sk, (struct sockaddr *) &addr, sizeof(addr)) < 0) { + /* BlueZ returns EAGAIN eventhough it should return EINPROGRESS */ + if (!(errno == EAGAIN || errno == EINPROGRESS)) { + err = errno; + error("connect() failed: %s (%d)", strerror(err), err); + goto fail; + } + + debug("Connect in progress"); + g_io_add_watch(io, G_IO_OUT, (GIOFunc) rfcomm_connect_cb, pc); + /* FIXME: Control the pending connects */ + } else { + debug("Connect succeeded with first try"); + (void) rfcomm_connect_cb(io, G_IO_OUT, pc); + } +fail: + g_io_channel_unref(io); + return -err; +} + static void record_reply(DBusPendingCall *call, void *data) { struct pending_connection *pc = data; @@ -113,7 +220,7 @@ static void record_reply(DBusPendingCall *call, void *data) const uint8_t *rec_bin; sdp_list_t *protos; DBusError derr; - int len, scanned, ch; + int len, scanned, ch, err; dbus_error_init(&derr); if (dbus_set_error_from_message(&derr, reply)) { @@ -163,10 +270,21 @@ static void record_reply(DBusPendingCall *call, void *data) error("Channel out of range: %d", ch); sdp_record_free(rec); err_not_supported(pc->conn, pc->msg); + goto fail; } - /* FIXME: Check if there is a pending connection */ + /* FIXME: Check if there is a pending connection or if it was canceled */ + + pc->channel = ch; + err = rfcomm_connect(pc); + if (err < 0) { + error("RFCOMM connection failed"); + err_connection_failed(pc->conn, pc->msg, strerror(-err)); + goto fail; + } + dbus_message_unref(reply); + return; fail: dbus_message_unref(reply); dbus_error_free(&derr); @@ -285,7 +403,7 @@ static DBusHandlerResult connect_service(DBusConnection *conn, const char *addr, *pattern; char *endptr; long val; - int dev_id; + int dev_id, err; /* FIXME: Check if it already exist or if there is pending connect */ @@ -330,7 +448,6 @@ static DBusHandlerResult connect_service(DBusConnection *conn, pending_connection_free(pc); return err_not_supported(conn, msg); } - return DBUS_HANDLER_RESULT_HANDLED; } @@ -353,7 +470,6 @@ static DBusHandlerResult connect_service(DBusConnection *conn, if (get_record(pc, val, record_reply) < 0) { pending_connection_free(pc); return err_not_supported(conn, msg); - } return DBUS_HANDLER_RESULT_HANDLED; } @@ -365,9 +481,13 @@ static DBusHandlerResult connect_service(DBusConnection *conn, "invalid RFCOMM channel"); } - /* FIXME: Connect */ - info("Connecting to channel: %d", val); - + pc->channel = val; + err = rfcomm_connect(pc); + if (err < 0) { + const char *strerr = strerror(-err); + error("RFCOMM connect failed: %s(%d)", strerr, -err); + err_connection_failed(conn, msg, strerr); + } return DBUS_HANDLER_RESULT_HANDLED; } |