mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-12-05 01:54:22 +08:00
619 lines
14 KiB
C
619 lines
14 KiB
C
/*
|
|
*
|
|
* 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);
|
|
}
|