mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-12-11 13:04:10 +08:00
1035 lines
24 KiB
C
1035 lines
24 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2004-2007 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 <stdio.h>
|
|
#include <errno.h>
|
|
#include <fcntl.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <sys/stat.h>
|
|
#include <sys/ioctl.h>
|
|
#include <sys/socket.h>
|
|
#include <arpa/inet.h>
|
|
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/rfcomm.h>
|
|
#include <bluetooth/hci.h>
|
|
#include <bluetooth/hci_lib.h>
|
|
#include <bluetooth/sdp.h>
|
|
#include <bluetooth/sdp_lib.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include <dbus/dbus.h>
|
|
|
|
#include "hcid.h"
|
|
#include "dbus.h"
|
|
#include "dbus-common.h"
|
|
#include "dbus-hci.h"
|
|
#include "dbus-adapter.h"
|
|
#include "dbus-error.h"
|
|
#include "dbus-sdp.h"
|
|
#include "dbus-rfcomm.h"
|
|
|
|
/* Waiting for udev to create the device node */
|
|
#define MAX_OPEN_TRIES 5
|
|
#define OPEN_WAIT 300 /* ms */
|
|
|
|
static int rfcomm_ctl = -1;
|
|
|
|
struct rfcomm_node {
|
|
int16_t id; /* Device id */
|
|
char name[16]; /* Node filename */
|
|
|
|
DBusConnection *conn; /* for name listener handling */
|
|
|
|
/* The following members are only valid for connected nodes */
|
|
GIOChannel *io; /* IO Channel for the connection */
|
|
guint io_id; /* ID for IO channel */
|
|
char *owner; /* D-Bus name that created the node */
|
|
};
|
|
|
|
struct pending_connect {
|
|
DBusConnection *conn;
|
|
DBusMessage *msg;
|
|
GIOChannel *io;
|
|
char *svc;
|
|
int canceled;
|
|
struct sockaddr_rc laddr;
|
|
struct sockaddr_rc raddr;
|
|
|
|
/* Used only when we wait for udev to create the device node */
|
|
struct rfcomm_node *node;
|
|
int ntries;
|
|
};
|
|
|
|
static GSList *pending_connects = NULL;
|
|
static GSList *connected_nodes = NULL;
|
|
static GSList *bound_nodes = NULL;
|
|
|
|
static char *rfcomm_node_name_from_id(int16_t id, char *dev, size_t len)
|
|
{
|
|
snprintf(dev, len, "/dev/rfcomm%d", id);
|
|
return dev;
|
|
}
|
|
|
|
static void rfcomm_node_free(struct rfcomm_node *node)
|
|
{
|
|
g_free(node->owner);
|
|
if (node->io) {
|
|
g_source_remove(node->io_id);
|
|
g_io_channel_unref(node->io);
|
|
}
|
|
if (node->conn)
|
|
dbus_connection_unref(node->conn);
|
|
g_free(node);
|
|
}
|
|
|
|
static struct rfcomm_node *find_node_by_name(GSList *nodes, const char *name)
|
|
{
|
|
GSList *l;
|
|
|
|
for (l = nodes; l != NULL; l = l->next) {
|
|
struct rfcomm_node *node = l->data;
|
|
if (!strcmp(node->name, name))
|
|
return node;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct pending_connect *find_pending_connect_by_channel(const char *bda,
|
|
uint8_t ch)
|
|
{
|
|
GSList *l;
|
|
bdaddr_t dba;
|
|
|
|
str2ba(bda, &dba);
|
|
|
|
for (l = pending_connects; l != NULL; l = l->next) {
|
|
struct pending_connect *pending = l->data;
|
|
if (!bacmp(&dba, &pending->raddr.rc_bdaddr) &&
|
|
pending->raddr.rc_channel == ch)
|
|
return pending;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct pending_connect *find_pending_connect_by_service(const char *bda,
|
|
const char *svc)
|
|
{
|
|
GSList *l;
|
|
bdaddr_t dba;
|
|
|
|
str2ba(bda, &dba);
|
|
|
|
for (l = pending_connects; l != NULL; l = l->next) {
|
|
struct pending_connect *pending = l->data;
|
|
if (!bacmp(&dba, &pending->raddr.rc_bdaddr) &&
|
|
!strcmp(pending->svc, svc))
|
|
return pending;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void pending_connect_free(struct pending_connect *c)
|
|
{
|
|
g_free(c->svc);
|
|
if (c->io)
|
|
g_io_channel_unref(c->io);
|
|
if (c->msg)
|
|
dbus_message_unref(c->msg);
|
|
if (c->conn)
|
|
dbus_connection_unref(c->conn);
|
|
g_free(c);
|
|
}
|
|
|
|
static int rfcomm_release(struct rfcomm_node *node, int *err)
|
|
{
|
|
struct rfcomm_dev_req req;
|
|
|
|
debug("rfcomm_release(%s)", node->name);
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.dev_id = node->id;
|
|
|
|
#if 0
|
|
/*
|
|
* We are hitting a kernel bug inside RFCOMM code when
|
|
* RFCOMM_HANGUP_NOW bit is set on request's flags passed to
|
|
* ioctl(RFCOMMRELEASEDEV)!
|
|
*/
|
|
req.flags = (1 << RFCOMM_HANGUP_NOW);
|
|
#endif
|
|
|
|
if (ioctl(rfcomm_ctl, RFCOMMRELEASEDEV, &req) < 0) {
|
|
if (err)
|
|
*err = errno;
|
|
error("Can't release device %d: %s (%d)", node->id,
|
|
strerror(errno), errno);
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void rfcomm_connect_req_exit(const char *name, void *data)
|
|
{
|
|
struct rfcomm_node *node = data;
|
|
debug("Connect requestor %s exited. Releasing %s node",
|
|
name, node->name);
|
|
rfcomm_release(node, NULL);
|
|
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->name);
|
|
name_listener_remove(node->conn, node->owner,
|
|
rfcomm_connect_req_exit, node);
|
|
connected_nodes = g_slist_remove(connected_nodes, node);
|
|
rfcomm_node_free(node);
|
|
return FALSE;
|
|
}
|
|
|
|
static void rfcomm_connect_cb_devnode_opened(int fd, struct pending_connect *c,
|
|
struct rfcomm_node *node)
|
|
{
|
|
DBusMessage *reply = NULL;
|
|
char *ptr;
|
|
|
|
reply = dbus_message_new_method_return(c->msg);
|
|
if (!reply) {
|
|
error_failed(c->conn, c->msg, ENOMEM);
|
|
goto failed;
|
|
}
|
|
|
|
ptr = node->name;
|
|
if (!dbus_message_append_args(reply,
|
|
DBUS_TYPE_STRING, &ptr,
|
|
DBUS_TYPE_INVALID)) {
|
|
error_failed(c->conn, c->msg, ENOMEM);
|
|
goto failed;
|
|
}
|
|
|
|
node->owner = g_strdup(dbus_message_get_sender(c->msg));
|
|
|
|
/* Check if the caller is still present */
|
|
if (!dbus_bus_name_has_owner(c->conn, node->owner, NULL)) {
|
|
error("RFCOMM.Connect requestor %s exited", node->owner);
|
|
goto failed;
|
|
}
|
|
|
|
node->io = g_io_channel_unix_new(fd);
|
|
g_io_channel_set_close_on_unref(node->io, TRUE);
|
|
node->io_id = g_io_add_watch(node->io, G_IO_ERR | G_IO_HUP,
|
|
(GIOFunc) rfcomm_disconnect_cb, node);
|
|
|
|
send_message_and_unref(c->conn, reply);
|
|
|
|
connected_nodes = g_slist_append(connected_nodes, node);
|
|
|
|
node->conn = dbus_connection_ref(c->conn);
|
|
name_listener_add(node->conn, node->owner,
|
|
rfcomm_connect_req_exit, node);
|
|
|
|
goto done;
|
|
|
|
failed:
|
|
close(fd);
|
|
rfcomm_release(node, NULL);
|
|
rfcomm_node_free(node);
|
|
if (reply)
|
|
dbus_message_unref(reply);
|
|
done:
|
|
pending_connects = g_slist_remove(pending_connects, c);
|
|
pending_connect_free(c);
|
|
}
|
|
|
|
static gboolean rfcomm_connect_cb_continue(void *data)
|
|
{
|
|
struct pending_connect *c = data;
|
|
struct rfcomm_node *node = c->node;
|
|
int fd;
|
|
|
|
if (c->canceled) {
|
|
error_connect_canceled(c->conn, c->msg);
|
|
goto failed;
|
|
}
|
|
|
|
fd = open(node->name, O_RDONLY | O_NOCTTY);
|
|
if (fd < 0) {
|
|
if (++c->ntries >= MAX_OPEN_TRIES) {
|
|
int err = errno;
|
|
error("Could not open %s: %s (%d)",
|
|
node->name, strerror(err), err);
|
|
error_connection_attempt_failed(c->conn, c->msg, err);
|
|
goto failed;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
rfcomm_connect_cb_devnode_opened(fd, c, node);
|
|
|
|
return FALSE;
|
|
|
|
failed:
|
|
rfcomm_release(node, NULL);
|
|
rfcomm_node_free(node);
|
|
|
|
pending_connects = g_slist_remove(pending_connects, c);
|
|
pending_connect_free(c);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static gboolean rfcomm_connect_cb(GIOChannel *chan, GIOCondition cond,
|
|
struct pending_connect *c)
|
|
{
|
|
struct rfcomm_node *node = NULL;
|
|
struct rfcomm_dev_req req;
|
|
int sk, ret, err, fd = -1;
|
|
socklen_t len;
|
|
|
|
if (c->canceled) {
|
|
error_connect_canceled(c->conn, c->msg);
|
|
goto failed;
|
|
}
|
|
|
|
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);
|
|
error_failed(c->conn, c->msg, err);
|
|
goto failed;
|
|
}
|
|
if (ret != 0) {
|
|
error("connect(): %s (%d)", strerror(ret), ret);
|
|
error_connection_attempt_failed(c->conn, c->msg, ret);
|
|
goto failed;
|
|
}
|
|
|
|
debug("rfcomm_connect_cb: connected");
|
|
|
|
len = sizeof(c->laddr);
|
|
if (getsockname(sk, (struct sockaddr *) &c->laddr, &len) < 0) {
|
|
err = errno;
|
|
error_failed(c->conn, c->msg, err);
|
|
error("getsockname: %s (%d)", strerror(err), err);
|
|
goto failed;
|
|
}
|
|
|
|
node = g_new0(struct rfcomm_node, 1);
|
|
|
|
/* Create the rfcomm device node */
|
|
memset(&req, 0, sizeof(req));
|
|
|
|
req.dev_id = -1;
|
|
req.flags = (1 << RFCOMM_REUSE_DLC) | (1 << RFCOMM_RELEASE_ONHUP);
|
|
|
|
bacpy(&req.src, &c->laddr.rc_bdaddr);
|
|
bacpy(&req.dst, &c->raddr.rc_bdaddr);
|
|
req.channel = c->raddr.rc_channel;
|
|
|
|
node->id = ioctl(sk, RFCOMMCREATEDEV, &req);
|
|
if (node->id < 0) {
|
|
err = errno;
|
|
error("ioctl(RFCOMMCREATEDEV): %s (%d)", strerror(errno), err);
|
|
error_failed(c->conn, c->msg, err);
|
|
goto failed;
|
|
}
|
|
|
|
rfcomm_node_name_from_id(node->id, node->name, sizeof(node->name));
|
|
|
|
fd = open(node->name, O_RDONLY | O_NOCTTY);
|
|
if (fd < 0) {
|
|
c->node = node;
|
|
c->ntries = 0;
|
|
g_timeout_add(OPEN_WAIT, rfcomm_connect_cb_continue, c);
|
|
return FALSE;
|
|
}
|
|
|
|
rfcomm_connect_cb_devnode_opened(fd, c, node);
|
|
|
|
return FALSE;
|
|
|
|
failed:
|
|
if (node)
|
|
rfcomm_node_free(node);
|
|
|
|
pending_connects = g_slist_remove(pending_connects, c);
|
|
pending_connect_free(c);
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
static int rfcomm_connect(DBusConnection *conn, DBusMessage *msg, bdaddr_t *src,
|
|
const char *bda, const char *svc, uint8_t ch, int *err)
|
|
{
|
|
int sk = -1;
|
|
struct pending_connect *c = NULL;
|
|
|
|
c = g_new0(struct pending_connect, 1);
|
|
|
|
if (svc)
|
|
c->svc = g_strdup(svc);
|
|
|
|
c->laddr.rc_family = AF_BLUETOOTH;
|
|
bacpy(&c->laddr.rc_bdaddr, src);
|
|
c->laddr.rc_channel = 0;
|
|
|
|
c->raddr.rc_family = AF_BLUETOOTH;
|
|
str2ba(bda, &c->raddr.rc_bdaddr);
|
|
c->raddr.rc_channel = ch;
|
|
|
|
sk = socket(AF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM);
|
|
if (sk < 0) {
|
|
if (err)
|
|
*err = errno;
|
|
goto failed;
|
|
}
|
|
|
|
if (bind(sk, (struct sockaddr *) &c->laddr, sizeof(c->laddr)) < 0) {
|
|
if (err)
|
|
*err = errno;
|
|
goto failed;
|
|
}
|
|
|
|
if (set_nonblocking(sk) < 0) {
|
|
*err = errno;
|
|
goto failed;
|
|
}
|
|
|
|
/* So we can reply to the message later */
|
|
c->msg = dbus_message_ref(msg);
|
|
c->conn = dbus_connection_ref(conn);
|
|
|
|
c->io = g_io_channel_unix_new(sk);
|
|
g_io_channel_set_close_on_unref(c->io, TRUE);
|
|
|
|
if (connect(sk, (struct sockaddr *) &c->raddr, sizeof(c->raddr)) < 0) {
|
|
/* BlueZ returns EAGAIN eventhough it should return EINPROGRESS */
|
|
if (!(errno == EAGAIN || errno == EINPROGRESS)) {
|
|
if (err)
|
|
*err = errno;
|
|
error("connect() failed: %s (%d)", strerror(errno), errno);
|
|
goto failed;
|
|
}
|
|
|
|
debug("Connect in progress");
|
|
g_io_add_watch(c->io, G_IO_OUT, (GIOFunc) rfcomm_connect_cb, c);
|
|
pending_connects = g_slist_append(pending_connects, c);
|
|
} else {
|
|
debug("Connect succeeded with first try");
|
|
(void) rfcomm_connect_cb(c->io, G_IO_OUT, c);
|
|
}
|
|
|
|
return 0;
|
|
|
|
failed:
|
|
if (c)
|
|
pending_connect_free(c);
|
|
if (sk >= 0)
|
|
close(sk);
|
|
return -1;
|
|
}
|
|
|
|
static void rfcomm_bind_req_exit(const char *name, void *data)
|
|
{
|
|
struct rfcomm_node *node = data;
|
|
debug("Bind requestor %s exited. Releasing %s node", name, node->name);
|
|
rfcomm_release(node, NULL);
|
|
bound_nodes = g_slist_remove(bound_nodes, node);
|
|
rfcomm_node_free(node);
|
|
}
|
|
|
|
static struct rfcomm_node *rfcomm_bind(bdaddr_t *src, const char *bda,
|
|
uint8_t ch, DBusConnection *conn, const char *owner, int *err)
|
|
{
|
|
struct rfcomm_dev_req req;
|
|
struct rfcomm_node *node;
|
|
|
|
debug("rfcomm_bind(%s, %d)", bda, ch);
|
|
|
|
memset(&req, 0, sizeof(req));
|
|
req.dev_id = -1;
|
|
req.flags = 0;
|
|
bacpy(&req.src, src);
|
|
|
|
str2ba(bda, &req.dst);
|
|
req.channel = ch;
|
|
|
|
node = g_new0(struct rfcomm_node, 1);
|
|
|
|
node->owner = g_strdup(owner);
|
|
|
|
node->id = ioctl(rfcomm_ctl, RFCOMMCREATEDEV, &req);
|
|
if (node->id < 0) {
|
|
if (err)
|
|
*err = errno;
|
|
error("RFCOMMCREATEDEV failed: %s (%d)", strerror(errno), errno);
|
|
rfcomm_node_free(node);
|
|
return NULL;
|
|
}
|
|
|
|
rfcomm_node_name_from_id(node->id, node->name, sizeof(node->name));
|
|
bound_nodes = g_slist_append(bound_nodes, node);
|
|
|
|
node->conn = dbus_connection_ref(conn);
|
|
name_listener_add(node->conn, node->owner, rfcomm_bind_req_exit, node);
|
|
|
|
return node;
|
|
}
|
|
|
|
typedef struct {
|
|
DBusConnection *conn;
|
|
DBusMessage *msg;
|
|
char *dst;
|
|
char *svc;
|
|
struct adapter *adapter;
|
|
} rfcomm_continue_data_t;
|
|
|
|
static rfcomm_continue_data_t *rfcomm_continue_data_new(DBusConnection *conn,
|
|
DBusMessage *msg,
|
|
const char *dst,
|
|
const char *svc,
|
|
struct adapter *adapter)
|
|
{
|
|
rfcomm_continue_data_t *new;
|
|
|
|
new = g_new(rfcomm_continue_data_t, 1);
|
|
|
|
new->dst = g_strdup(dst);
|
|
if (!new->dst) {
|
|
g_free(new);
|
|
return NULL;
|
|
}
|
|
|
|
new->svc = g_strdup(svc);
|
|
new->conn = dbus_connection_ref(conn);
|
|
new->msg = dbus_message_ref(msg);
|
|
new->adapter = adapter;
|
|
|
|
return new;
|
|
}
|
|
|
|
static void rfcomm_continue_data_free(rfcomm_continue_data_t *d)
|
|
{
|
|
dbus_connection_unref(d->conn);
|
|
dbus_message_unref(d->msg);
|
|
g_free(d->svc);
|
|
g_free(d->dst);
|
|
g_free(d);
|
|
}
|
|
|
|
static void rfcomm_conn_req_continue(sdp_record_t *rec, void *data, int err)
|
|
{
|
|
rfcomm_continue_data_t *cdata = data;
|
|
int ch = -1, conn_err;
|
|
sdp_list_t *protos;
|
|
bdaddr_t bdaddr;
|
|
|
|
if (err || !rec) {
|
|
error_record_does_not_exist(cdata->conn, cdata->msg);
|
|
goto failed;
|
|
}
|
|
|
|
if (!sdp_get_access_protos(rec, &protos)) {
|
|
ch = sdp_get_proto_port(protos, RFCOMM_UUID);
|
|
sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
|
|
sdp_list_free(protos, NULL);
|
|
}
|
|
if (ch == -1) {
|
|
error_record_does_not_exist(cdata->conn, cdata->msg);
|
|
goto failed;
|
|
}
|
|
|
|
if (find_pending_connect_by_channel(cdata->dst, ch)) {
|
|
error_connect_in_progress(cdata->conn, cdata->msg);
|
|
goto failed;
|
|
}
|
|
|
|
hci_devba(cdata->adapter->dev_id, &bdaddr);
|
|
if (rfcomm_connect(cdata->conn, cdata->msg, &bdaddr,
|
|
cdata->dst, cdata->svc, ch, &conn_err) < 0)
|
|
error_failed(cdata->conn, cdata->msg, conn_err);
|
|
|
|
failed:
|
|
rfcomm_continue_data_free(cdata);
|
|
}
|
|
|
|
static DBusHandlerResult rfcomm_connect_req(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct adapter *adapter = data;
|
|
rfcomm_continue_data_t *cdata;
|
|
uint32_t handle;
|
|
uuid_t uuid;
|
|
const char *string;
|
|
const char *dst;
|
|
int err;
|
|
|
|
if (!dbus_message_get_args(msg, NULL,
|
|
DBUS_TYPE_STRING, &dst,
|
|
DBUS_TYPE_STRING, &string,
|
|
DBUS_TYPE_INVALID))
|
|
return error_invalid_arguments(conn, msg);
|
|
|
|
cdata = rfcomm_continue_data_new(conn, msg, dst, string, adapter);
|
|
if (!cdata)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
if (str2uuid(&uuid, string) == 0)
|
|
err = get_record_with_uuid(conn, msg, adapter->dev_id, dst,
|
|
&uuid, rfcomm_conn_req_continue, cdata);
|
|
else if ((handle = strtol(string, NULL, 0)))
|
|
err = get_record_with_handle(conn, msg, adapter->dev_id, dst,
|
|
handle, rfcomm_conn_req_continue, cdata);
|
|
else {
|
|
rfcomm_continue_data_free(cdata);
|
|
return error_invalid_arguments(conn, msg);
|
|
}
|
|
|
|
if (!err)
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
|
|
rfcomm_continue_data_free(cdata);
|
|
|
|
if (err == -ENOMEM)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
return error_failed(conn, msg, err);
|
|
}
|
|
|
|
static DBusHandlerResult rfcomm_cancel_connect_req(DBusConnection *conn,
|
|
DBusMessage *msg,
|
|
void *data)
|
|
{
|
|
struct pending_connect *pending;
|
|
DBusMessage *reply;
|
|
const char *string;
|
|
const char *dst;
|
|
|
|
if (!dbus_message_get_args(msg, NULL,
|
|
DBUS_TYPE_STRING, &dst,
|
|
DBUS_TYPE_STRING, &string,
|
|
DBUS_TYPE_INVALID))
|
|
return error_invalid_arguments(conn, msg);
|
|
|
|
pending = find_pending_connect_by_service(dst, string);
|
|
if (!pending)
|
|
return error_connect_not_in_progress(conn, msg);
|
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
if (!reply)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
pending->canceled = 1;
|
|
|
|
return send_message_and_unref(conn, reply);
|
|
}
|
|
|
|
static DBusHandlerResult rfcomm_connect_by_ch_req(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
bdaddr_t bdaddr;
|
|
const char *dst;
|
|
uint8_t ch;
|
|
int err;
|
|
struct adapter *adapter = data;
|
|
|
|
hci_devba(adapter->dev_id, &bdaddr);
|
|
|
|
if (!dbus_message_get_args(msg, NULL,
|
|
DBUS_TYPE_STRING, &dst,
|
|
DBUS_TYPE_BYTE, &ch,
|
|
DBUS_TYPE_INVALID))
|
|
return error_invalid_arguments(conn, msg);
|
|
|
|
if (find_pending_connect_by_channel(dst, ch))
|
|
return error_connect_in_progress(conn, msg);
|
|
|
|
if (rfcomm_connect(conn, msg, &bdaddr, dst, NULL, ch, &err) < 0)
|
|
return error_failed(conn, msg, err);
|
|
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
}
|
|
|
|
static DBusHandlerResult rfcomm_cancel_connect_by_ch_req(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
const char *dst;
|
|
uint8_t ch;
|
|
DBusMessage *reply;
|
|
struct pending_connect *pending;
|
|
|
|
if (!dbus_message_get_args(msg, NULL,
|
|
DBUS_TYPE_STRING, &dst,
|
|
DBUS_TYPE_BYTE, &ch,
|
|
DBUS_TYPE_INVALID))
|
|
return error_invalid_arguments(conn, msg);
|
|
|
|
pending = find_pending_connect_by_channel(dst, ch);
|
|
if (!pending)
|
|
return error_connect_not_in_progress(conn, msg);
|
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
if (!reply)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
pending->canceled = 1;
|
|
|
|
return send_message_and_unref(conn, reply);
|
|
}
|
|
|
|
static DBusHandlerResult rfcomm_disconnect_req(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct rfcomm_node *node;
|
|
DBusMessage *reply;
|
|
const char *name;
|
|
int err;
|
|
|
|
if (!dbus_message_get_args(msg, NULL,
|
|
DBUS_TYPE_STRING, &name,
|
|
DBUS_TYPE_INVALID))
|
|
return error_invalid_arguments(conn, msg);
|
|
|
|
node = find_node_by_name(connected_nodes, name);
|
|
if (!node)
|
|
return error_not_connected(conn, msg);
|
|
|
|
if (strcmp(node->owner, dbus_message_get_sender(msg)))
|
|
return error_not_authorized(conn, msg);
|
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
if (!reply)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
if (rfcomm_release(node, &err) < 0) {
|
|
dbus_message_unref(reply);
|
|
return error_failed(conn, msg, err);
|
|
}
|
|
|
|
name_listener_remove(node->conn, node->owner,
|
|
rfcomm_connect_req_exit, node);
|
|
connected_nodes = g_slist_remove(connected_nodes, node);
|
|
rfcomm_node_free(node);
|
|
|
|
return send_message_and_unref(conn, reply);
|
|
}
|
|
|
|
static void rfcomm_bind_req_continue(sdp_record_t *rec, void *data, int err)
|
|
{
|
|
rfcomm_continue_data_t *cdata = data;
|
|
struct rfcomm_node *node = NULL;
|
|
DBusMessage *reply = NULL;
|
|
int ch = -1, bind_err;
|
|
sdp_list_t *protos;
|
|
const char *name;
|
|
bdaddr_t bdaddr;
|
|
|
|
if (err || !rec) {
|
|
error_record_does_not_exist(cdata->conn, cdata->msg);
|
|
goto failed;
|
|
}
|
|
|
|
if (!sdp_get_access_protos(rec, &protos)) {
|
|
ch = sdp_get_proto_port(protos, RFCOMM_UUID);
|
|
sdp_list_foreach(protos, (sdp_list_func_t) sdp_list_free, NULL);
|
|
sdp_list_free(protos, NULL);
|
|
}
|
|
if (ch == -1) {
|
|
error_record_does_not_exist(cdata->conn, cdata->msg);
|
|
goto failed;
|
|
}
|
|
|
|
hci_devba(cdata->adapter->dev_id, &bdaddr);
|
|
|
|
node = rfcomm_bind(&bdaddr, cdata->dst, ch, cdata->conn,
|
|
dbus_message_get_sender(cdata->msg), &bind_err);
|
|
if (!node) {
|
|
error_failed(cdata->conn, cdata->msg, bind_err);
|
|
goto failed;
|
|
}
|
|
|
|
reply = dbus_message_new_method_return(cdata->msg);
|
|
if (!reply) {
|
|
error_failed(cdata->conn, cdata->msg, ENOMEM);
|
|
goto failed;
|
|
}
|
|
|
|
name = node->name;
|
|
if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &name,
|
|
DBUS_TYPE_INVALID)) {
|
|
error_failed(cdata->conn, cdata->msg, ENOMEM);
|
|
goto failed;
|
|
}
|
|
|
|
send_message_and_unref(cdata->conn, reply);
|
|
|
|
rfcomm_continue_data_free(cdata);
|
|
|
|
return;
|
|
|
|
failed:
|
|
if (reply)
|
|
dbus_message_unref(reply);
|
|
if (node) {
|
|
bound_nodes = g_slist_remove(bound_nodes, node);
|
|
rfcomm_release(node, NULL);
|
|
rfcomm_node_free(node);
|
|
}
|
|
|
|
rfcomm_continue_data_free(cdata);
|
|
}
|
|
|
|
static DBusHandlerResult rfcomm_bind_req(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
struct adapter *adapter = data;
|
|
rfcomm_continue_data_t *cdata;
|
|
uint32_t handle;
|
|
uuid_t uuid;
|
|
const char *string;
|
|
const char *dst;
|
|
int err;
|
|
|
|
if (!dbus_message_get_args(msg, NULL,
|
|
DBUS_TYPE_STRING, &dst,
|
|
DBUS_TYPE_STRING, &string,
|
|
DBUS_TYPE_INVALID))
|
|
return error_invalid_arguments(conn, msg);
|
|
|
|
cdata = rfcomm_continue_data_new(conn, msg, dst, string, adapter);
|
|
if (!cdata)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
if (str2uuid(&uuid, string) == 0)
|
|
err = get_record_with_uuid(conn, msg, adapter->dev_id, dst,
|
|
&uuid, rfcomm_bind_req_continue, cdata);
|
|
else if ((handle = strtol(string, NULL, 0)))
|
|
err = get_record_with_handle(conn, msg, adapter->dev_id, dst,
|
|
handle, rfcomm_bind_req_continue, cdata);
|
|
else {
|
|
rfcomm_continue_data_free(cdata);
|
|
return error_invalid_arguments(conn, msg);
|
|
}
|
|
|
|
if (!err)
|
|
return DBUS_HANDLER_RESULT_HANDLED;
|
|
|
|
rfcomm_continue_data_free(cdata);
|
|
|
|
if (err == -ENOMEM)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
return error_failed(conn, msg, err);
|
|
}
|
|
|
|
static DBusHandlerResult rfcomm_bind_by_ch_req(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
bdaddr_t bdaddr;
|
|
DBusMessage *reply = NULL;
|
|
uint8_t ch;
|
|
int err;
|
|
const char *dst, *name;
|
|
struct adapter *adapter = data;
|
|
struct rfcomm_node *node = NULL;
|
|
|
|
hci_devba(adapter->dev_id, &bdaddr);
|
|
|
|
if (!dbus_message_get_args(msg, NULL,
|
|
DBUS_TYPE_STRING, &dst,
|
|
DBUS_TYPE_BYTE, &ch,
|
|
DBUS_TYPE_INVALID))
|
|
return error_invalid_arguments(conn, msg);
|
|
|
|
node = rfcomm_bind(&bdaddr, dst, ch, conn,
|
|
dbus_message_get_sender(msg), &err);
|
|
if (!node)
|
|
return error_failed(conn, msg, err);
|
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
if (!reply)
|
|
goto need_memory;
|
|
|
|
name = node->name;
|
|
if (!dbus_message_append_args(reply, DBUS_TYPE_STRING, &name,
|
|
DBUS_TYPE_INVALID))
|
|
goto need_memory;
|
|
|
|
return send_message_and_unref(conn, reply);
|
|
|
|
need_memory:
|
|
if (reply)
|
|
dbus_message_unref(reply);
|
|
if (node) {
|
|
bound_nodes = g_slist_remove(bound_nodes, node);
|
|
rfcomm_release(node, NULL);
|
|
rfcomm_node_free(node);
|
|
}
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
}
|
|
|
|
static DBusHandlerResult rfcomm_release_req(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
DBusMessage *reply;
|
|
const char *name;
|
|
struct rfcomm_node *node;
|
|
int err;
|
|
|
|
if (!dbus_message_get_args(msg, NULL,
|
|
DBUS_TYPE_STRING, &name,
|
|
DBUS_TYPE_INVALID))
|
|
return error_invalid_arguments(conn, msg);
|
|
|
|
node = find_node_by_name(bound_nodes, name);
|
|
if (!node)
|
|
return error_binding_does_not_exist(conn, msg);
|
|
|
|
if (strcmp(node->owner, dbus_message_get_sender(msg)))
|
|
return error_not_authorized(conn, msg);
|
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
if (!reply)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
if (rfcomm_release(node, &err) < 0) {
|
|
dbus_message_unref(reply);
|
|
return error_failed(conn, msg, err);
|
|
}
|
|
|
|
name_listener_remove(node->conn, node->owner,
|
|
rfcomm_bind_req_exit, node);
|
|
bound_nodes = g_slist_remove(bound_nodes, node);
|
|
rfcomm_node_free(node);
|
|
|
|
return send_message_and_unref(conn, reply);
|
|
}
|
|
|
|
static DBusHandlerResult rfcomm_list_bindings_req(DBusConnection *conn,
|
|
DBusMessage *msg, void *data)
|
|
{
|
|
bdaddr_t bdaddr;
|
|
DBusMessage *reply;
|
|
DBusMessageIter iter, sub;
|
|
struct adapter *adapter = data;
|
|
GSList *l;
|
|
|
|
hci_devba(adapter->dev_id, &bdaddr);
|
|
|
|
reply = dbus_message_new_method_return(msg);
|
|
if (!reply)
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
|
|
dbus_message_iter_init_append(reply, &iter);
|
|
|
|
if (!dbus_message_iter_open_container(&iter, DBUS_TYPE_ARRAY, "s", &sub)) {
|
|
dbus_message_unref(reply);
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
}
|
|
|
|
for (l = bound_nodes; l != NULL; l = l->next) {
|
|
struct rfcomm_node *node = l->data;
|
|
struct rfcomm_dev_info di = { id: node->id };
|
|
char *name = node->name;
|
|
|
|
if (ioctl(rfcomm_ctl, RFCOMMGETDEVINFO, &di) < 0) {
|
|
error("RFCOMMGETDEVINFO(%d): %s (%d)",
|
|
node->id, strerror(errno), errno);
|
|
continue;
|
|
}
|
|
|
|
/* Ignore nodes not specific to this adapter */
|
|
if (bacmp(&di.src, &bdaddr))
|
|
continue;
|
|
|
|
dbus_message_iter_append_basic(&sub, DBUS_TYPE_STRING, &name);
|
|
}
|
|
|
|
if (!dbus_message_iter_close_container(&iter, &sub)) {
|
|
dbus_message_unref(reply);
|
|
return DBUS_HANDLER_RESULT_NEED_MEMORY;
|
|
}
|
|
|
|
return send_message_and_unref(conn, reply);
|
|
}
|
|
|
|
static struct service_data rfcomm_services[] = {
|
|
{ "Connect", rfcomm_connect_req, },
|
|
{ "CancelConnect", rfcomm_cancel_connect_req, },
|
|
{ "ConnectByChannel", rfcomm_connect_by_ch_req, },
|
|
{ "CancelConnectByChannel", rfcomm_cancel_connect_by_ch_req, },
|
|
{ "Disconnect", rfcomm_disconnect_req, },
|
|
{ "Bind", rfcomm_bind_req, },
|
|
{ "BindByChannel", rfcomm_bind_by_ch_req, },
|
|
{ "Release", rfcomm_release_req, },
|
|
{ "ListBindings", rfcomm_list_bindings_req, },
|
|
{ NULL, NULL, }
|
|
};
|
|
|
|
DBusHandlerResult handle_rfcomm_method(DBusConnection *conn, DBusMessage *msg,
|
|
void *data)
|
|
{
|
|
const struct adapter *adapter = data;
|
|
service_handler_func_t handler;
|
|
|
|
if (!hcid_dbus_use_experimental())
|
|
return error_unknown_method(conn, msg);
|
|
|
|
if (!data) {
|
|
error("RFCOMM method called with NULL data pointer!");
|
|
return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
|
|
}
|
|
|
|
if (!adapter->up)
|
|
return error_not_ready(conn, msg);
|
|
|
|
/* Initialize the RFCOMM control socket if has not yet been done */
|
|
if (rfcomm_ctl < 0) {
|
|
rfcomm_ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_RFCOMM);
|
|
if (rfcomm_ctl < 0)
|
|
return error_failed(conn, msg, errno);
|
|
}
|
|
|
|
handler = find_service_handler(rfcomm_services, msg);
|
|
|
|
if (handler)
|
|
return handler(conn, msg, data);
|
|
|
|
return error_unknown_method(conn, msg);
|
|
}
|