diff options
author | Marcel Holtmann <marcel@holtmann.org> | 2012-12-09 20:54:33 +0100 |
---|---|---|
committer | Marcel Holtmann <marcel@holtmann.org> | 2012-12-09 20:54:33 +0100 |
commit | 9f169828802c79e2f09afed58b7a55ae67085ca8 (patch) | |
tree | 11043b19a2530442a087af3c1b635e12bfca9966 /tools/btiotest.c | |
parent | a18bd79ae13bbeb50a0488f3c0c68d190ed46b9c (diff) | |
download | bluez-9f169828802c79e2f09afed58b7a55ae67085ca8.tar.gz |
test: Move btiotest utility into tools directory
Diffstat (limited to 'tools/btiotest.c')
-rw-r--r-- | tools/btiotest.c | 618 |
1 files changed, 618 insertions, 0 deletions
diff --git a/tools/btiotest.c b/tools/btiotest.c new file mode 100644 index 000000000..4b170ace5 --- /dev/null +++ b/tools/btiotest.c @@ -0,0 +1,618 @@ +/* + * + * BlueZ - Bluetooth protocol stack for Linux + * + * Copyright (C) 2009-2010 Marcel Holtmann <marcel@holtmann.org> + * Copyright (C) 2009-2010 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 <stdio.h> +#include <stdlib.h> +#include <stdint.h> +#include <errno.h> +#include <string.h> +#include <signal.h> + +#include <glib.h> + +#include <btio/btio.h> + +#define DEFAULT_ACCEPT_TIMEOUT 2 +static gint opt_update_sec = 0; + +struct io_data { + guint ref; + GIOChannel *io; + gint reject; + gint disconn; + gint accept; +}; + +static void io_data_unref(struct io_data *data) +{ + data->ref--; + + if (data->ref) + return; + + if (data->io) + g_io_channel_unref(data->io); + + g_free(data); +} + +static struct io_data *io_data_ref(struct io_data *data) +{ + data->ref++; + return data; +} + +static struct io_data *io_data_new(GIOChannel *io, int reject, gint disconn, + gint accept) +{ + struct io_data *data; + + data = g_new0(struct io_data, 1); + data->io = io; + data->reject = reject; + data->disconn = disconn; + data->accept = accept; + + return io_data_ref(data); +} + +static gboolean io_watch(GIOChannel *io, GIOCondition cond, gpointer user_data) +{ + printf("Disconnected\n"); + return FALSE; +} + +static gboolean disconn_timeout(gpointer user_data) +{ + struct io_data *data = user_data; + + printf("Disconnecting\n"); + + g_io_channel_shutdown(data->io, TRUE, NULL); + + return FALSE; +} + +static void update_sec_level(struct io_data *data) +{ + GError *err = NULL; + int sec_level; + + if (!bt_io_get(data->io, &err, BT_IO_OPT_SEC_LEVEL, &sec_level, + BT_IO_OPT_INVALID)) { + printf("bt_io_get(OPT_SEC_LEVEL): %s\n", err->message); + g_clear_error(&err); + return; + } + + printf("sec_level=%d\n", sec_level); + + if (opt_update_sec == sec_level) + return; + + if (!bt_io_set(data->io, &err, BT_IO_OPT_SEC_LEVEL, opt_update_sec, + BT_IO_OPT_INVALID)) { + printf("bt_io_set(OPT_SEC_LEVEL): %s\n", err->message); + g_clear_error(&err); + } +} + +static void connect_cb(GIOChannel *io, GError *err, gpointer user_data) +{ + struct io_data *data = user_data; + GIOCondition cond; + char addr[18]; + uint16_t handle, omtu, imtu; + uint8_t cls[3], key_size; + + if (err) { + printf("Connecting failed: %s\n", err->message); + return; + } + + if (!bt_io_get(io, &err, + BT_IO_OPT_DEST, addr, + BT_IO_OPT_HANDLE, &handle, + BT_IO_OPT_CLASS, cls, + BT_IO_OPT_INVALID)) { + printf("Unable to get destination address: %s\n", + err->message); + g_clear_error(&err); + strcpy(addr, "(unknown)"); + } + + printf("Successfully connected to %s. handle=%u, class=%02x%02x%02x\n", + addr, handle, cls[0], cls[1], cls[2]); + + if (!bt_io_get(io, &err, BT_IO_OPT_OMTU, &omtu, + BT_IO_OPT_IMTU, &imtu, + BT_IO_OPT_INVALID)) { + printf("Unable to get MTU sizes: %s\n", err->message); + g_clear_error(&err); + } else + printf("imtu=%u, omtu=%u\n", imtu, omtu); + + if (!bt_io_get(io, &err, BT_IO_OPT_KEY_SIZE, &key_size, + BT_IO_OPT_INVALID)) { + printf("Unable to get Key size: %s\n", err->message); + g_clear_error(&err); + } else + printf("key_size=%u\n", key_size); + + if (data->disconn == 0) { + g_io_channel_shutdown(io, TRUE, NULL); + printf("Disconnected\n"); + return; + } + + if (data->io == NULL) + data->io = g_io_channel_ref(io); + + if (data->disconn > 0) { + io_data_ref(data); + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, data->disconn, + disconn_timeout, data, + (GDestroyNotify) io_data_unref); + } + + + io_data_ref(data); + + if (opt_update_sec > 0) + update_sec_level(data); + + cond = G_IO_NVAL | G_IO_HUP | G_IO_ERR; + g_io_add_watch_full(io, G_PRIORITY_DEFAULT, cond, io_watch, data, + (GDestroyNotify) io_data_unref); +} + +static gboolean confirm_timeout(gpointer user_data) +{ + struct io_data *data = user_data; + + if (data->reject >= 0) { + printf("Rejecting connection\n"); + g_io_channel_shutdown(data->io, TRUE, NULL); + return FALSE; + } + + printf("Accepting connection\n"); + + io_data_ref(data); + + if (opt_update_sec > 0) + update_sec_level(data); + + if (!bt_io_accept(data->io, connect_cb, data, + (GDestroyNotify) io_data_unref, NULL)) { + printf("bt_io_accept() failed\n"); + io_data_unref(data); + } + + return FALSE; +} + +static void confirm_cb(GIOChannel *io, gpointer user_data) +{ + char addr[18]; + struct io_data *data = user_data; + GError *err = NULL; + + if (!bt_io_get(io, &err, BT_IO_OPT_DEST, addr, BT_IO_OPT_INVALID)) { + printf("bt_io_get(OPT_DEST): %s\n", err->message); + g_clear_error(&err); + } else + printf("Got confirmation request for %s\n", addr); + + if (data->accept < 0 && data->reject < 0) + return; + + if (data->reject == 0) { + printf("Rejecting connection\n"); + g_io_channel_shutdown(io, TRUE, NULL); + return; + } + + data->io = g_io_channel_ref(io); + io_data_ref(data); + + if (data->accept == 0) { + if (!bt_io_accept(io, connect_cb, data, + (GDestroyNotify) io_data_unref, + &err)) { + printf("bt_io_accept() failed: %s\n", err->message); + g_clear_error(&err); + io_data_unref(data); + return; + } + } else { + gint seconds = (data->reject > 0) ? + data->reject : data->accept; + g_timeout_add_seconds_full(G_PRIORITY_DEFAULT, seconds, + confirm_timeout, data, + (GDestroyNotify) io_data_unref); + } +} + +static void l2cap_connect(const char *src, const char *dst, uint8_t addr_type, + uint16_t psm, uint16_t cid, gint disconn, + gint sec, gint prio) +{ + struct io_data *data; + GError *err = NULL; + + printf("Connecting to %s L2CAP PSM %u\n", dst, psm); + + data = io_data_new(NULL, -1, disconn, -1); + + if (src) + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_DEST_TYPE, addr_type, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_CID, cid, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_PRIORITY, prio, + BT_IO_OPT_INVALID); + else + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_DEST_TYPE, addr_type, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_CID, cid, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_PRIORITY, prio, + BT_IO_OPT_INVALID); + + if (!data->io) { + printf("Connecting to %s failed: %s\n", dst, err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } +} + +static void l2cap_listen(const char *src, uint16_t psm, gint defer, + gint reject, gint disconn, gint accept, + gint sec, gboolean master) +{ + struct io_data *data; + BtIOConnect conn; + BtIOConfirm cfm; + GIOChannel *l2_srv; + GError *err = NULL; + + if (defer) { + conn = NULL; + cfm = confirm_cb; + } else { + conn = connect_cb; + cfm = NULL; + } + + printf("Listening on L2CAP PSM %u\n", psm); + + data = io_data_new(NULL, reject, disconn, accept); + + if (src) + l2_srv = bt_io_listen(conn, cfm, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + else + l2_srv = bt_io_listen(conn, cfm, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_PSM, psm, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + + if (!l2_srv) { + printf("Listening failed: %s\n", err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } + + g_io_channel_unref(l2_srv); +} + +static void rfcomm_connect(const char *src, const char *dst, uint8_t ch, + gint disconn, gint sec) +{ + struct io_data *data; + GError *err = NULL; + + printf("Connecting to %s RFCOMM channel %u\n", dst, ch); + + data = io_data_new(NULL, -1, disconn, -1); + + if (src) + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_INVALID); + else + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_INVALID); + + if (!data->io) { + printf("Connecting to %s failed: %s\n", dst, err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } +} + +static void rfcomm_listen(const char *src, uint8_t ch, gboolean defer, + gint reject, gint disconn, gint accept, + gint sec, gboolean master) +{ + struct io_data *data; + BtIOConnect conn; + BtIOConfirm cfm; + GIOChannel *rc_srv; + GError *err = NULL; + + if (defer) { + conn = NULL; + cfm = confirm_cb; + } else { + conn = connect_cb; + cfm = NULL; + } + + data = io_data_new(NULL, reject, disconn, accept); + + if (src) + rc_srv = bt_io_listen(conn, cfm, + data, (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + else + rc_srv = bt_io_listen(conn, cfm, + data, (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_CHANNEL, ch, + BT_IO_OPT_SEC_LEVEL, sec, + BT_IO_OPT_MASTER, master, + BT_IO_OPT_INVALID); + + if (!rc_srv) { + printf("Listening failed: %s\n", err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } + + bt_io_get(rc_srv, &err, BT_IO_OPT_CHANNEL, &ch, BT_IO_OPT_INVALID); + + printf("Listening on RFCOMM channel %u\n", ch); + + g_io_channel_unref(rc_srv); +} + +static void sco_connect(const char *src, const char *dst, gint disconn) +{ + struct io_data *data; + GError *err = NULL; + + printf("Connecting SCO to %s\n", dst); + + data = io_data_new(NULL, -1, disconn, -1); + + if (src) + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_INVALID); + else + data->io = bt_io_connect(connect_cb, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_DEST, dst, + BT_IO_OPT_INVALID); + + if (!data->io) { + printf("Connecting to %s failed: %s\n", dst, err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } +} + +static void sco_listen(const char *src, gboolean defer, gint reject, + gint disconn, gint accept) +{ + struct io_data *data; + BtIOConnect conn; + BtIOConfirm cfm; + GIOChannel *sco_srv; + GError *err = NULL; + + printf("Listening for SCO connections\n"); + + if (defer) { + conn = NULL; + cfm = confirm_cb; + } else { + conn = connect_cb; + cfm = NULL; + } + + data = io_data_new(NULL, reject, disconn, accept); + + if (src) + sco_srv = bt_io_listen(conn, cfm, data, + (GDestroyNotify) io_data_unref, + &err, + BT_IO_OPT_SOURCE, src, + BT_IO_OPT_INVALID); + else + sco_srv = bt_io_listen(conn, cfm, data, + (GDestroyNotify) io_data_unref, + &err, BT_IO_OPT_INVALID); + + if (!sco_srv) { + printf("Listening failed: %s\n", err->message); + g_error_free(err); + exit(EXIT_FAILURE); + } + + g_io_channel_unref(sco_srv); +} + +static gint opt_channel = -1; +static gint opt_psm = 0; +static gboolean opt_sco = FALSE; +static gboolean opt_defer = FALSE; +static char *opt_dev = NULL; +static gint opt_reject = -1; +static gint opt_disconn = -1; +static gint opt_accept = DEFAULT_ACCEPT_TIMEOUT; +static gint opt_sec = 0; +static gboolean opt_master = FALSE; +static gint opt_priority = 0; +static gint opt_cid = 0; +static guint8 opt_addr_type = 0; + +static GMainLoop *main_loop; + +static GOptionEntry options[] = { + { "channel", 'c', 0, G_OPTION_ARG_INT, &opt_channel, + "RFCOMM channel" }, + { "psm", 'p', 0, G_OPTION_ARG_INT, &opt_psm, + "L2CAP PSM" }, + { "cid", 'j', 0, G_OPTION_ARG_INT, &opt_cid, + "L2CAP CID" }, + { "addr-type", 't', 0, G_OPTION_ARG_INT, &opt_addr_type, + "Address type " + "(0 BR/EDR 1 LE Public 2 LE Random" }, + { "sco", 's', 0, G_OPTION_ARG_NONE, &opt_sco, + "Use SCO" }, + { "defer", 'd', 0, G_OPTION_ARG_NONE, &opt_defer, + "Use DEFER_SETUP for incoming connections" }, + { "sec-level", 'S', 0, G_OPTION_ARG_INT, &opt_sec, + "Security level" }, + { "update-sec-level", 'U', 0, G_OPTION_ARG_INT, &opt_update_sec, + "Update security level" }, + { "dev", 'i', 0, G_OPTION_ARG_STRING, &opt_dev, + "Which HCI device to use" }, + { "reject", 'r', 0, G_OPTION_ARG_INT, &opt_reject, + "Reject connection after N seconds" }, + { "disconnect", 'D', 0, G_OPTION_ARG_INT, &opt_disconn, + "Disconnect connection after N seconds" }, + { "accept", 'a', 0, G_OPTION_ARG_INT, &opt_accept, + "Accept connection after N seconds" }, + { "master", 'm', 0, G_OPTION_ARG_NONE, &opt_master, + "Master role switch (incoming connections)" }, + { "priority", 'P', 0, G_OPTION_ARG_INT, &opt_priority, + "Transmission priority: Setting a priority " + "outside the range 0 to 6 requires the" + "CAP_NET_ADMIN capability." }, + { NULL }, +}; + +static void sig_term(int sig) +{ + g_main_loop_quit(main_loop); +} + +int main(int argc, char *argv[]) +{ + GOptionContext *context; + + context = g_option_context_new(NULL); + g_option_context_add_main_entries(context, options, NULL); + + if (!g_option_context_parse(context, &argc, &argv, NULL)) + exit(EXIT_FAILURE); + + g_option_context_free(context); + + printf("accept=%d, reject=%d, discon=%d, defer=%d, sec=%d," + " update_sec=%d, prio=%d\n", opt_accept, opt_reject, + opt_disconn, opt_defer, opt_sec, opt_update_sec, opt_priority); + + if (opt_psm || opt_cid) { + if (argc > 1) + l2cap_connect(opt_dev, argv[1], opt_addr_type, + opt_psm, opt_cid, opt_disconn, + opt_sec, opt_priority); + else + l2cap_listen(opt_dev, opt_psm, opt_defer, opt_reject, + opt_disconn, opt_accept, opt_sec, + opt_master); + } + + if (opt_channel != -1) { + if (argc > 1) + rfcomm_connect(opt_dev, argv[1], opt_channel, + opt_disconn, opt_sec); + else + rfcomm_listen(opt_dev, opt_channel, opt_defer, + opt_reject, opt_disconn, opt_accept, + opt_sec, opt_master); + } + + if (opt_sco) { + if (argc > 1) + sco_connect(opt_dev, argv[1], opt_disconn); + else + sco_listen(opt_dev, opt_defer, opt_reject, + opt_disconn, opt_accept); + } + + signal(SIGTERM, sig_term); + signal(SIGINT, sig_term); + + main_loop = g_main_loop_new(NULL, FALSE); + + g_main_loop_run(main_loop); + + g_main_loop_unref(main_loop); + + printf("Exiting\n"); + + exit(EXIT_SUCCESS); +} |