| /* |
| * |
| * 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.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); |
| } |