From d3351f1b45477451d28e9e65996946fa6291f319 Mon Sep 17 00:00:00 2001 From: Johan Hedberg Date: Mon, 16 Feb 2009 09:01:29 +0200 Subject: Merge external btio rework --- common/btio.c | 968 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 968 insertions(+) create mode 100644 common/btio.c (limited to 'common/btio.c') diff --git a/common/btio.c b/common/btio.c new file mode 100644 index 00000000..601a5fd0 --- /dev/null +++ b/common/btio.c @@ -0,0 +1,968 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009 Marcel Holtmann + * Copyright (C) 2009 Nokia Corporation + * + * + * 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 + * + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "logging.h" +#include "btio.h" + +#define DEFAULT_DEFER_TIMEOUT 30 + +struct set_opts { + bdaddr_t src; + bdaddr_t dst; + int defer; + int lm_flags; + int sec_level; + uint8_t channel; + uint16_t psm; + uint16_t mtu; + uint16_t imtu; + uint16_t omtu; +}; + +struct connect { + BtIOConnect connect; + gpointer user_data; + GDestroyNotify destroy; +}; + +struct accept { + BtIOConnect connect; + gpointer user_data; + GDestroyNotify destroy; +}; + +struct server { + BtIOConnect connect; + BtIOConfirm confirm; + gpointer user_data; + GDestroyNotify destroy; +}; + +static void server_remove(struct server *server) +{ + if (server->destroy) + server->destroy(server->user_data); + g_free(server); +} + +static void connect_remove(struct connect *conn) +{ + if (conn->destroy) + conn->destroy(conn->user_data); + g_free(conn); +} + +static void accept_remove(struct accept *accept) +{ + if (accept->destroy) + accept->destroy(accept->user_data); + g_free(accept); +} + +static gboolean accept_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct accept *accept = user_data; + + /* If the user aborted this accept attempt */ + if (cond & G_IO_NVAL) + return FALSE; + + if (cond & (G_IO_HUP | G_IO_ERR)) + accept->connect(io, EIO, accept->user_data); + else + accept->connect(io, 0, accept->user_data); + + return FALSE; +} + +static gboolean connect_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct connect *conn = user_data; + int err; + + /* If the user aborted this connect attempt */ + if (cond & G_IO_NVAL) + return FALSE; + + err = 0; + + if (cond & G_IO_OUT) { + socklen_t len = sizeof(err); + int sock = g_io_channel_unix_get_fd(io); + if (getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, &len) < 0) + err = errno; + + } else if (cond & (G_IO_HUP | G_IO_ERR)) + err = EIO; + + conn->connect(io, err, conn->user_data); + + return FALSE; +} + +static gboolean server_cb(GIOChannel *io, GIOCondition cond, + gpointer user_data) +{ + struct server *server = user_data; + int srv_sock, cli_sock; + GIOChannel *cli_io; + + /* If the user closed the server */ + if (cond & G_IO_NVAL) + return FALSE; + + srv_sock = g_io_channel_unix_get_fd(io); + + cli_sock = accept(srv_sock, NULL, NULL); + if (cli_sock < 0) { + error("accept: %s (%d)", strerror(errno), errno); + return TRUE; + } + + cli_io = g_io_channel_unix_new(cli_sock); + + if (server->confirm) + server->confirm(cli_io, server->user_data); + else + server->connect(cli_io, 0, server->user_data); + + g_io_channel_unref(cli_io); + + return TRUE; +} + +static void server_add(GIOChannel *io, BtIOConnect connect, + BtIOConfirm confirm, gpointer user_data, + GDestroyNotify destroy) +{ + struct server *server; + GIOCondition cond; + + server = g_new0(struct server, 1); + server->connect = connect; + server->confirm = confirm; + server->user_data = user_data; + server->destroy = destroy; + + cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, server_cb, server, + (GDestroyNotify) server_remove); +} + +static void connect_add(GIOChannel *io, BtIOConnect connect, + gpointer user_data, GDestroyNotify destroy) +{ + struct connect *conn; + GIOCondition cond; + + conn = g_new0(struct connect, 1); + conn->connect = connect; + conn->user_data = user_data; + conn->destroy = destroy; + + cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, connect_cb, conn, + (GDestroyNotify) connect_remove); +} + +static void accept_add(GIOChannel *io, BtIOConnect connect, gpointer user_data, + GDestroyNotify destroy) +{ + struct accept *accept; + GIOCondition cond; + + accept = g_new0(struct accept, 1); + accept->connect = connect; + accept->user_data = user_data; + accept->destroy = destroy; + + cond = G_IO_OUT | G_IO_ERR | G_IO_HUP | G_IO_NVAL; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, accept_cb, accept, + (GDestroyNotify) accept_remove); +} + +static int l2cap_bind(int sock, bdaddr_t *src, uint16_t psm) +{ + struct sockaddr_l2 addr; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, src); + addr.l2_psm = htobs(psm); + + return bind(sock, (struct sockaddr *) &addr, sizeof(addr)); +} + +static int l2cap_connect(int sock, bdaddr_t *dst, uint16_t psm) +{ + int err; + struct sockaddr_l2 addr; + + memset(&addr, 0, sizeof(addr)); + addr.l2_family = AF_BLUETOOTH; + bacpy(&addr.l2_bdaddr, dst); + addr.l2_psm = htobs(psm); + + err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + return err; + + return 0; +} + +static int l2cap_set(int sock, int lm_flags, int sec_level, uint16_t imtu, + uint16_t omtu) +{ + int err; + + if (lm_flags) { + err = setsockopt(sock, SOL_L2CAP, L2CAP_LM, &lm_flags, + sizeof(lm_flags)); + if (err < 0) + return err; + } + + if (imtu || omtu) { + struct l2cap_options l2o; + socklen_t len; + + memset(&l2o, 0, sizeof(l2o)); + len = sizeof(l2o); + err = getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len); + if (err < 0) + return err; + + if (imtu) + l2o.imtu = imtu; + if (omtu) + l2o.omtu = omtu; + + err = setsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, + sizeof(l2o)); + if (err < 0) + return err; + } + + if (sec_level) { + struct bt_security sec; + + memset(&sec, 0, sizeof(sec)); + sec.level = sec_level; + + err = setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, + sizeof(sec)); + if (err < 0) + return err; + } + + return 0; +} + +static int rfcomm_bind(int sock, bdaddr_t *src, uint8_t channel) +{ + struct sockaddr_rc addr; + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, src); + addr.rc_channel = channel; + + return bind(sock, (struct sockaddr *) &addr, sizeof(addr)); +} + +static int rfcomm_connect(int sock, bdaddr_t *dst, uint8_t channel) +{ + int err; + struct sockaddr_rc addr; + char str[18]; + + ba2str(dst, str); + debug("Connecting to %s", str); + + memset(&addr, 0, sizeof(addr)); + addr.rc_family = AF_BLUETOOTH; + bacpy(&addr.rc_bdaddr, dst); + addr.rc_channel = channel; + + err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + return err; + + return 0; +} + +static int rfcomm_set(int sock, int lm_flags, int sec_level) +{ + int err; + + if (lm_flags) { + err = setsockopt(sock, SOL_RFCOMM, RFCOMM_LM, &lm_flags, + sizeof(lm_flags)); + if (err < 0) + return err; + } + + if (sec_level) { + struct bt_security sec; + + memset(&sec, 0, sizeof(sec)); + sec.level = sec_level; + + err = setsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, + sizeof(sec)); + if (err < 0) + return err; + } + + return 0; +} + +static int sco_bind(int sock, bdaddr_t *src) +{ + struct sockaddr_sco addr; + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, src); + + return bind(sock, (struct sockaddr *) &addr, sizeof(addr)); +} + +static int sco_connect(int sock, bdaddr_t *dst) +{ + struct sockaddr_sco addr; + int err; + + memset(&addr, 0, sizeof(addr)); + addr.sco_family = AF_BLUETOOTH; + bacpy(&addr.sco_bdaddr, dst); + + err = connect(sock, (struct sockaddr *) &addr, sizeof(addr)); + if (err < 0 && !(errno == EAGAIN || errno == EINPROGRESS)) + return err; + + return 0; +} + +static int sco_set(int sock, uint16_t mtu) +{ + int err; + struct sco_options sco_opt; + + if (mtu) { + socklen_t len; + + memset(&sco_opt, 0, len); + len = sizeof(sco_opt); + err = getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len); + if (err < 0) + return err; + + sco_opt.mtu = mtu; + err = setsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, + sizeof(sco_opt)); + if (err < 0) + return err; + } + + return 0; +} + +static gboolean set_valist(GIOChannel *io, struct set_opts *opts, + BtIOOption opt1, va_list args) +{ + BtIOOption opt = opt1; + const char *str; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + str = va_arg(args, const char *); + if (strncasecmp(str, "hci", 3) == 0) + hci_devba(atoi(str + 3), &opts->src); + else + str2ba(str, &opts->src); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(&opts->src, va_arg(args, bdaddr_t *)); + break; + case BT_IO_OPT_DEST: + str2ba(va_arg(args, const char *), &opts->dst); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(&opts->dst, va_arg(args, bdaddr_t *)); + break; + case BT_IO_OPT_DEFER_TIMEOUT: + opts->defer = va_arg(args, int); + break; + case BT_IO_OPT_LM_FLAGS: + opts->lm_flags = va_arg(args, int); + break; + case BT_IO_OPT_SEC_LEVEL: + opts->sec_level = va_arg(args, int); + break; + case BT_IO_OPT_CHANNEL: + opts->channel = va_arg(args, int); + break; + case BT_IO_OPT_PSM: + opts->psm = va_arg(args, int); + break; + case BT_IO_OPT_MTU: + opts->mtu = va_arg(args, int); + opts->imtu = opts->mtu; + opts->omtu = opts->mtu; + break; + case BT_IO_OPT_OMTU: + opts->omtu = va_arg(args, int); + if (!opts->mtu) + opts->mtu = opts->omtu; + break; + case BT_IO_OPT_IMTU: + opts->imtu = va_arg(args, int); + if (!opts->mtu) + opts->mtu = opts->imtu; + break; + default: + error("set_valist: unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static gboolean get_sec_level(int sock, int *level) +{ + struct bt_security sec; + socklen_t len; + + memset(&sec, 0, sizeof(sec)); + len = sizeof(sec); + if (getsockopt(sock, SOL_BLUETOOTH, BT_SECURITY, &sec, &len) < 0) { + error("getsockopt(BT_SECURITY): %s (%d)", strerror(errno), + errno); + return FALSE; + } + + *level = sec.level; + + return TRUE; +} + +static gboolean get_peers(int sock, struct sockaddr *src, struct sockaddr *dst, + socklen_t len) +{ + socklen_t olen; + + memset(src, 0, len); + olen = len; + if (getsockname(sock, src, &olen) < 0) { + error("getsockname: %s (%d)", strerror(errno), errno); + return FALSE; + } + + memset(dst, 0, len); + olen = len; + if (getpeername(sock, dst, &olen) < 0) { + error("getpeername: %s (%d)", strerror(errno), errno); + return FALSE; + } + + return TRUE; +} + +static gboolean l2cap_get(int sock, BtIOOption opt1, va_list args) +{ + BtIOOption opt = opt1; + struct sockaddr_l2 src, dst; + struct l2cap_options l2o; + socklen_t len; + + len = sizeof(l2o); + memset(&l2o, 0, len); + if (getsockopt(sock, SOL_L2CAP, L2CAP_OPTIONS, &l2o, &len) < 0) { + error("getsockopt: %s (%d)", strerror(errno), errno); + return FALSE; + } + + if (!get_peers(sock, (struct sockaddr *) &src, + (struct sockaddr *) &dst, sizeof(src))) + return FALSE; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + ba2str(&src.l2_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &src.l2_bdaddr); + break; + case BT_IO_OPT_DEST: + ba2str(&dst.l2_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &dst.l2_bdaddr); + break; + case BT_IO_OPT_DEFER_TIMEOUT: + len = sizeof(int); + if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, + va_arg(args, int *), &len) < 0) + return FALSE; + break; + case BT_IO_OPT_LM_FLAGS: + len = sizeof(int); + if (getsockopt(sock, SOL_L2CAP, L2CAP_LM, + va_arg(args, int *), &len) < 0) + return FALSE; + break; + case BT_IO_OPT_SEC_LEVEL: + if (!get_sec_level(sock, va_arg(args, int *))) + return FALSE; + break; + case BT_IO_OPT_PSM: + *(va_arg(args, int *)) = src.l2_psm ? + src.l2_psm : dst.l2_psm; + break; + case BT_IO_OPT_OMTU: + *(va_arg(args, int *)) = l2o.omtu; + break; + case BT_IO_OPT_IMTU: + *(va_arg(args, int *)) = l2o.imtu; + break; + default: + error("l2cap_get: unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static gboolean rfcomm_get(int sock, BtIOOption opt1, va_list args) +{ + BtIOOption opt = opt1; + struct sockaddr_rc src, dst; + socklen_t len; + + if (!get_peers(sock, (struct sockaddr *) &src, + (struct sockaddr *) &dst, sizeof(src))) + return FALSE; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + ba2str(&src.rc_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &src.rc_bdaddr); + break; + case BT_IO_OPT_DEST: + ba2str(&dst.rc_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &dst.rc_bdaddr); + break; + case BT_IO_OPT_DEFER_TIMEOUT: + len = sizeof(int); + if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, + va_arg(args, int *), &len) < 0) + return FALSE; + break; + case BT_IO_OPT_LM_FLAGS: + len = sizeof(int); + if (getsockopt(sock, SOL_RFCOMM, RFCOMM_LM, + va_arg(args, int *), &len) < 0) + return FALSE; + break; + case BT_IO_OPT_SEC_LEVEL: + if (!get_sec_level(sock, va_arg(args, int *))) + return FALSE; + break; + case BT_IO_OPT_CHANNEL: + *(va_arg(args, int *)) = src.rc_channel ? + src.rc_channel : dst.rc_channel; + break; + default: + error("rfcomm_get: unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static gboolean sco_get(int sock, BtIOOption opt1, va_list args) +{ + BtIOOption opt = opt1; + struct sockaddr_sco src, dst; + struct sco_options sco_opt; + socklen_t len; + + len = sizeof(sco_opt); + memset(&sco_opt, 0, len); + if (getsockopt(sock, SOL_SCO, SCO_OPTIONS, &sco_opt, &len) < 0) { + error("getsockopt: %s (%d)", strerror(errno), errno); + return FALSE; + } + + if (!get_peers(sock, (struct sockaddr *) &src, + (struct sockaddr *) &dst, sizeof(src))) + return FALSE; + + while (opt != BT_IO_OPT_INVALID) { + switch (opt) { + case BT_IO_OPT_SOURCE: + ba2str(&src.sco_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_SOURCE_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &src.sco_bdaddr); + break; + case BT_IO_OPT_DEST: + ba2str(&dst.sco_bdaddr, va_arg(args, char *)); + break; + case BT_IO_OPT_DEST_BDADDR: + bacpy(va_arg(args, bdaddr_t *), &dst.sco_bdaddr); + break; + case BT_IO_OPT_DEFER_TIMEOUT: + len = sizeof(int); + if (getsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, + va_arg(args, int *), &len) < 0) + return FALSE; + break; + case BT_IO_OPT_SEC_LEVEL: + if (!get_sec_level(sock, va_arg(args, int *))) + return FALSE; + break; + case BT_IO_OPT_MTU: + case BT_IO_OPT_IMTU: + case BT_IO_OPT_OMTU: + *(va_arg(args, int *)) = sco_opt.mtu; + break; + default: + error("sco_get: unknown option %d", opt); + return FALSE; + } + + opt = va_arg(args, int); + } + + return TRUE; +} + +static gboolean get_valist(GIOChannel *io, BtIOType type, BtIOOption opt1, + va_list args) +{ + int sock; + + sock = g_io_channel_unix_get_fd(io); + + switch (type) { + case BT_IO_L2RAW: + case BT_IO_L2CAP: + return l2cap_get(sock, opt1, args); + case BT_IO_RFCOMM: + return rfcomm_get(sock, opt1, args); + case BT_IO_SCO: + return sco_get(sock, opt1, args); + } + + return FALSE; +} + +gboolean bt_io_accept(GIOChannel *io, BtIOConnect connect, gpointer user_data, + GDestroyNotify destroy) +{ + int sock; + char c; + struct pollfd pfd; + + sock = g_io_channel_unix_get_fd(io); + + memset(&pfd, 0, sizeof(pfd)); + pfd.fd = sock; + pfd.events = POLLOUT; + + if (poll(&pfd, 1, 0) < 0) + error("poll: %s (%d)", strerror(errno), errno); + else if (!(pfd.revents & POLLOUT)) + read(sock, &c, 1); + + accept_add(io, connect, user_data, destroy); + + return TRUE; +} + +gboolean bt_io_set(GIOChannel *io, BtIOType type, BtIOOption opt1, ...) +{ + va_list args; + gboolean ret; + struct set_opts opts; + int sock; + + memset(&opts, 0, sizeof(opts)); + + va_start(args, opt1); + ret = set_valist(io, &opts, opt1, args); + va_end(args); + + if (!ret) + return ret; + + sock = g_io_channel_unix_get_fd(io); + + switch (type) { + case BT_IO_L2RAW: + case BT_IO_L2CAP: + if (l2cap_set(sock, opts.lm_flags, opts.sec_level, + opts.imtu, opts.omtu) < 0) + return FALSE; + break; + case BT_IO_RFCOMM: + if (rfcomm_set(sock, opts.lm_flags, opts.sec_level) < 0) + return FALSE; + break; + case BT_IO_SCO: + if (sco_set(sock, opts.mtu) < 0) + return FALSE; + break; + } + + return ret; +} + +gboolean bt_io_get(GIOChannel *io, BtIOType type, BtIOOption opt1, ...) +{ + va_list args; + gboolean ret; + + va_start(args, opt1); + ret = get_valist(io, type, opt1, args); + va_end(args); + + return ret; +} + +static GIOChannel *create_io(BtIOType type, gboolean server, struct set_opts *opts) +{ + int sock, err; + GIOChannel *io; + + switch (type) { + case BT_IO_L2RAW: + sock = socket(PF_BLUETOOTH, SOCK_RAW, BTPROTO_L2CAP); + if (sock < 0) { + error("socket: %s (%d)", strerror(errno), errno); + return NULL; + } + err = l2cap_bind(sock, &opts->src, server ? opts->psm : 0); + if (err < 0) { + error("l2cap_bind: %s (%d)", strerror(-err), -err); + return NULL; + } + err = l2cap_set(sock, 0, opts->sec_level, 0, 0); + if (err < 0) { + error("l2cap_set: %s (%d)", strerror(-err), -err); + return NULL; + } + break; + case BT_IO_L2CAP: + sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP); + if (sock < 0) { + error("socket: %s (%d)", strerror(errno), errno); + return NULL; + } + err = l2cap_bind(sock, &opts->src, server ? opts->psm : 0); + if (err < 0) { + error("l2cap_bind: %s (%d)", strerror(-err), -err); + return NULL; + } + err = l2cap_set(sock, opts->lm_flags, opts->sec_level, + opts->imtu, opts->omtu); + if (err < 0) { + error("l2cap_set: %s (%d)", strerror(-err), -err); + return NULL; + } + break; + case BT_IO_RFCOMM: + sock = socket(PF_BLUETOOTH, SOCK_STREAM, BTPROTO_RFCOMM); + if (sock < 0) { + error("socket: %s (%d)", strerror(errno), errno); + return NULL; + } + err = rfcomm_bind(sock, &opts->src, server ? opts->channel : 0); + if (err < 0) { + error("rfcomm_bind: %s (%d)", strerror(-err), -err); + return NULL; + } + err = rfcomm_set(sock, opts->lm_flags, opts->sec_level); + if (err < 0) { + error("rfcomm_set: %s (%d)", strerror(-err), -err); + return NULL; + } + break; + case BT_IO_SCO: + sock = socket(PF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_SCO); + if (sock < 0) { + error("socket: %s (%d)", strerror(errno), errno); + return NULL; + } + err = sco_bind(sock, &opts->src); + if (err < 0) { + error("sco_bind: %s (%d)", strerror(-err), -err); + return NULL; + } + err = sco_set(sock, opts->mtu); + if (err < 0) { + error("sco_set: %s (%d)", strerror(-err), -err); + return NULL; + } + break; + } + + io = g_io_channel_unix_new(sock); + + g_io_channel_set_flags(io, G_IO_FLAG_NONBLOCK, NULL); + + return io; +} + +GIOChannel *bt_io_connect(BtIOType type, BtIOConnect connect, + gpointer user_data, GDestroyNotify destroy, + BtIOOption opt1, ...) +{ + GIOChannel *io; + va_list args; + struct set_opts opts; + int err, sock; + gboolean ret; + + memset(&opts, 0, sizeof(opts)); + + va_start(args, opt1); + ret = set_valist(io, &opts, opt1, args); + va_end(args); + + if (ret == FALSE) + return NULL; + + io = create_io(type, FALSE, &opts); + if (io == NULL) + return NULL; + + sock = g_io_channel_unix_get_fd(io); + + switch (type) { + case BT_IO_L2RAW: + err = l2cap_connect(sock, &opts.dst, 0); + break; + case BT_IO_L2CAP: + err = l2cap_connect(sock, &opts.dst, opts.psm); + break; + case BT_IO_RFCOMM: + err = rfcomm_connect(sock, &opts.dst, opts.channel); + break; + case BT_IO_SCO: + err = sco_connect(sock, &opts.dst); + break; + } + + if (ret < 0) { + error("connect: %s (%d)", strerror(-err), -err); + g_io_channel_unref(io); + return NULL; + } + + connect_add(io, connect, user_data, destroy); + + return io; +} + +GIOChannel *bt_io_listen(BtIOType type, BtIOConnect connect, + BtIOConfirm confirm, gpointer user_data, + GDestroyNotify destroy, BtIOOption opt1, ...) +{ + GIOChannel *io; + va_list args; + struct set_opts opts; + int sock; + gboolean ret; + + if (type == BT_IO_L2RAW) + return NULL; + + memset(&opts, 0, sizeof(opts)); + + opts.defer = DEFAULT_DEFER_TIMEOUT; + + va_start(args, opt1); + ret = set_valist(io, &opts, opt1, args); + va_end(args); + + if (ret == FALSE) + return NULL; + + io = create_io(type, TRUE, &opts); + if (io == NULL) + return NULL; + + sock = g_io_channel_unix_get_fd(io); + + if (confirm) + setsockopt(sock, SOL_BLUETOOTH, BT_DEFER_SETUP, &opts.defer, + sizeof(opts.defer)); + + if (listen(sock, 5) < 0) { + error("listen: %s (%d)", strerror(errno), errno); + g_io_channel_unref(io); + return NULL; + } + + server_add(io, connect, confirm, user_data, destroy); + + return io; +} -- cgit