diff options
| author | Johan Hedberg <johan.hedberg@nokia.com> | 2009-02-16 09:01:29 +0200 | 
|---|---|---|
| committer | Johan Hedberg <johan.hedberg@nokia.com> | 2009-02-16 09:01:29 +0200 | 
| commit | d3351f1b45477451d28e9e65996946fa6291f319 (patch) | |
| tree | a53e990e1bb3fe6252a74fb2c662c02dd06bfe8c /common/btio.c | |
| parent | 87e1eceda35d2e17ddfff0d099aaac57392e8bf1 (diff) | |
Merge external btio rework
Diffstat (limited to 'common/btio.c')
| -rw-r--r-- | common/btio.c | 968 | 
1 files changed, 968 insertions, 0 deletions
| 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 <marcel@holtmann.org> + *  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 <stdarg.h> +#include <stdlib.h> +#include <unistd.h> +#include <errno.h> +#include <poll.h> +#include <sys/types.h> +#include <sys/socket.h> + +#include <bluetooth/bluetooth.h> +#include <bluetooth/l2cap.h> +#include <bluetooth/rfcomm.h> +#include <bluetooth/sco.h> +#include <bluetooth/hci.h> +#include <bluetooth/hci_lib.h> + +#include <glib.h> + +#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; +} | 
