/*
*
* BlueZ - Bluetooth protocol stack for Linux
*
* Copyright (C) 2002-2010 Marcel Holtmann <marcel@holtmann.org>
*
*
* 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
*
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#define _GNU_SOURCE
#include <stdio.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <getopt.h>
#include <signal.h>
#include <sys/poll.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <bluetooth/bluetooth.h>
#include <bluetooth/hci.h>
#include <bluetooth/hci_lib.h>
#include <bluetooth/l2cap.h>
#include <bluetooth/sdp.h>
#include <bluetooth/sdp_lib.h>
#include <bluetooth/cmtp.h>
#ifdef NEED_PPOLL
#include "ppoll.h"
#endif
static volatile sig_atomic_t __io_canceled = 0;
static void sig_hup(int sig)
{
return;
}
static void sig_term(int sig)
{
__io_canceled = 1;
}
static char *cmtp_state[] = {
"unknown",
"connected",
"open",
"bound",
"listening",
"connecting",
"connecting",
"config",
"disconnecting",
"closed"
};
static char *cmtp_flagstostr(uint32_t flags)
{
static char str[100] = "";
strcat(str, "[");
if (flags & (1 << CMTP_LOOPBACK))
strcat(str, "loopback");
strcat(str, "]");
return str;
}
static int get_psm(bdaddr_t *src, bdaddr_t *dst, unsigned short *psm)
{
sdp_session_t *s;
sdp_list_t *srch, *attrs, *rsp;
uuid_t svclass;
uint16_t attr;
int err;
if (!(s = sdp_connect(src, dst, 0)))
return -1;
sdp_uuid16_create(&svclass, CIP_SVCLASS_ID);
srch = sdp_list_append(NULL, &svclass);
attr = SDP_ATTR_PROTO_DESC_LIST;
attrs = sdp_list_append(NULL, &attr);
err = sdp_service_search_attr_req(s, srch, SDP_ATTR_REQ_INDIVIDUAL, attrs, &rsp);
sdp_close(s);
if (err)
return 0;
for (; rsp; rsp = rsp->next) {
sdp_record_t *rec = (sdp_record_t *) rsp->data;
sdp_list_t *protos;
if (!sdp_get_access_protos(rec, &protos)) {
unsigned short p = sdp_get_proto_port(protos, L2CAP_UUID);
if (p > 0) {
*psm = p;
return 1;
}
}
}
return 0;
}
static int do_connect(int ctl, int dev_id, bdaddr_t *src, bdaddr_t *dst, unsigned short psm, uint32_t flags)
{
struct cmtp_connadd_req req;
struct hci_dev_info di;
struct sockaddr_l2 addr;
struct l2cap_options opts;
socklen_t size;
int sk;
hci_devinfo(dev_id, &di);
if (!(di.link_policy & HCI_LP_RSWITCH)) {
printf("Local device is not accepting role switch\n");
}
if ((sk = socket(AF_BLUETOOTH, SOCK_SEQPACKET, BTPROTO_L2CAP)) < 0) {
perror("Can't create L2CAP socket");
exit(1);
}
memset(&addr, 0, sizeof(addr));
addr.l2_family = AF_BLUETOOTH;
bacpy(&addr.l2_bdaddr, src);
if (bind(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Can't bind L2CAP socket");
close(sk);
exit(1);
}
memset(&opts, 0, sizeof(opts));
size = sizeof(opts);
if (getsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, &size) < 0) {
perror("Can't get L2CAP options");
close(sk);
exit(1);
}
opts.imtu = CMTP_DEFAULT_MTU;
opts.omtu = CMTP_DEFAULT_MTU;
opts.flush_to = 0xffff;
if (setsockopt(sk, SOL_L2CAP, L2CAP_OPTIONS, &opts, sizeof(opts)) < 0) {
perror("Can't set L2CAP options");
close(sk);
exit(1);
}
memset(&addr, 0, sizeof(addr));
addr.l2_family = AF_BLUETOOTH;
bacpy(&addr.l2_bdaddr, dst);
addr.l2_psm = htobs(psm);
if (connect(sk, (struct sockaddr *)&addr, sizeof(addr)) < 0) {
perror("Can't connect L2CAP socket");
close(sk);
exit(1);
}
req.sock = sk;
req.flags = flags;
if (ioctl(ctl, CMTPCONNADD, &req) < 0) {
perror("Can't create connection");
exit(1);
}
return sk;
}
static void cmd_show(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
{
struct cmtp_connlist_req req;
struct cmtp_conninfo ci[16];
char addr[18];
unsigned int i;
req.cnum = 16;
req.ci = ci;
if (ioctl(ctl, CMTPGETCONNLIST, &req) < 0) {
perror("Can't get connection list");
exit(1);
}
for (i = 0; i < req.cnum; i++) {
ba2str(&ci[i].bdaddr, addr);
printf("%d %s %s %s\n", ci[i].num, addr,
cmtp_state[ci[i].state],
ci[i].flags ? cmtp_flagstostr(ci[i].flags) : "");
}
}
static void cmd_search(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
{
inquiry_info *info = NULL;
bdaddr_t src, dst;
unsigned short psm;
int i, dev_id, num_rsp, length, flags;
char addr[18];
uint8_t class[3];
ba2str(bdaddr, addr);
dev_id = hci_devid(addr);
if (dev_id < 0) {
dev_id = hci_get_route(NULL);
hci_devba(dev_id, &src);
} else
bacpy(&src, bdaddr);
length = 8; /* ~10 seconds */
num_rsp = 0;
flags = 0;
printf("Searching ...\n");
num_rsp = hci_inquiry(dev_id, length, num_rsp, NULL, &info, flags);
for (i = 0; i < num_rsp; i++) {
memcpy(class, (info+i)->dev_class, 3);
if ((class[1] == 2) && ((class[0] / 4) == 5)) {
bacpy(&dst, &(info+i)->bdaddr);
ba2str(&dst, addr);
printf("\tChecking service for %s\n", addr);
if (!get_psm(&src, &dst, &psm))
continue;
bt_free(info);
printf("\tConnecting to device %s\n", addr);
do_connect(ctl, dev_id, &src, &dst, psm, 0);
return;
}
}
bt_free(info);
fprintf(stderr, "\tNo devices in range or visible\n");
exit(1);
}
static void cmd_create(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
{
bdaddr_t src, dst;
unsigned short psm;
int dev_id;
char addr[18];
if (argc < 2)
return;
str2ba(argv[1], &dst);
ba2str(bdaddr, addr);
dev_id = hci_devid(addr);
if (dev_id < 0) {
dev_id = hci_get_route(&dst);
hci_devba(dev_id, &src);
} else
bacpy(&src, bdaddr);
if (argc < 3) {
if (!get_psm(&src, &dst, &psm))
psm = 4099;
} else
psm = atoi(argv[2]);
do_connect(ctl, dev_id, &src, &dst, psm, 0);
}
static void cmd_release(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
{
struct cmtp_conndel_req req;
struct cmtp_connlist_req cl;
struct cmtp_conninfo ci[16];
if (argc < 2) {
cl.cnum = 16;
cl.ci = ci;
if (ioctl(ctl, CMTPGETCONNLIST, &cl) < 0) {
perror("Can't get connection list");
exit(1);
}
if (cl.cnum == 0)
return;
if (cl.cnum != 1) {
fprintf(stderr, "You have to specifiy the device address.\n");
exit(1);
}
bacpy(&req.bdaddr, &ci[0].bdaddr);
} else
str2ba(argv[1], &req.bdaddr);
if (ioctl(ctl, CMTPCONNDEL, &req) < 0) {
perror("Can't release connection");
exit(1);
}
}
static void cmd_loopback(int ctl, bdaddr_t *bdaddr, int argc, char **argv)
{
struct cmtp_conndel_req req;
struct sigaction sa;
struct pollfd p;
sigset_t sigs;
bdaddr_t src, dst;
unsigned short psm;
int dev_id, sk;
char addr[18];
if (argc < 2)
return;
str2ba(argv[1], &dst);
ba2str(bdaddr, addr);
dev_id = hci_devid(addr);
if (dev_id < 0) {
dev_id = hci_get_route(&dst);
hci_devba(dev_id, &src);
} else
bacpy(&src, bdaddr);
ba2str(&dst, addr);
printf("Connecting to %s in loopback mode\n", addr);
if (argc < 3) {
if (!get_psm(&src, &dst, &psm))
psm = 4099;
} else
psm = atoi(argv[2]);
sk = do_connect(ctl, dev_id, &src, &dst, psm, (1 << CMTP_LOOPBACK));
printf("Press CTRL-C for hangup\n");
memset(&sa, 0, sizeof(sa));
sa.sa_flags = SA_NOCLDSTOP;
sa.sa_handler = SIG_IGN;
sigaction(SIGCHLD, &sa, NULL);
sigaction(SIGPIPE, &sa, NULL);
sa.sa_handler = sig_term;
sigaction(SIGTERM, &sa, NULL);
sigaction(SIGINT, &sa, NULL);
sa.sa_handler = sig_hup;
sigaction(SIGHUP, &sa, NULL);
sigfillset(&sigs);
sigdelset(&sigs, SIGCHLD);
sigdelset(&sigs, SIGPIPE);
sigdelset(&sigs, SIGTERM);
sigdelset(&sigs, SIGINT);
sigdelset(&sigs, SIGHUP);
p.fd = sk;
p.events = POLLERR | POLLHUP;
while (!__io_canceled) {
p.revents = 0;
if (ppoll(&p, 1, NULL, &sigs) > 0)
break;
}
bacpy(&req.bdaddr, &dst);
ioctl(ctl, CMTPCONNDEL, &req);
}
static struct {
char *cmd;
char *alt;
void (*func)(int ctl, bdaddr_t *bdaddr, int argc, char **argv);
char *opt;
char *doc;
} command[] = {
{ "show", "list", cmd_show, 0, "Show remote connections" },
{ "search", "scan", cmd_search, 0, "Search for a remote device" },
{ "connect", "create", cmd_create, "<bdaddr>", "Connect a remote device" },
{ "release", "disconnect", cmd_release, "[bdaddr]", "Disconnect the remote device" },
{ "loopback", "test", cmd_loopback, "<bdaddr>", "Loopback test of a device" },
{ NULL, NULL, NULL, 0, 0 }
};
static void usage(void)
{
int i;
printf("ciptool - Bluetooth Common ISDN Access Profile (CIP)\n\n");
printf("Usage:\n"
"\tciptool [options] [command]\n"
"\n");
printf("Options:\n"
"\t-i [hciX|bdaddr] Local HCI device or BD Address\n"
"\t-h, --help Display help\n"
"\n");
printf("Commands:\n");
for (i = 0; command[i].cmd; i++)
printf("\t%-8s %-10s\t%s\n", command[i].cmd,
command[i].opt ? command[i].opt : " ",
command[i].doc);
printf("\n");
}
static struct option main_options[] = {
{ "help", 0, 0, 'h' },
{ "device", 1, 0, 'i' },
{ 0, 0, 0, 0 }
};
int main(int argc, char *argv[])
{
bdaddr_t bdaddr;
int i, opt, ctl;
bacpy(&bdaddr, BDADDR_ANY);
while ((opt = getopt_long(argc, argv, "+i:h", main_options, NULL)) != -1) {
switch(opt) {
case 'i':
if (!strncmp(optarg, "hci", 3))
hci_devba(atoi(optarg + 3), &bdaddr);
else
str2ba(optarg, &bdaddr);
break;
case 'h':
usage();
exit(0);
default:
exit(0);
}
}
argc -= optind;
argv += optind;
optind = 0;
if (argc < 1) {
usage();
return 0;
}
if ((ctl = socket(AF_BLUETOOTH, SOCK_RAW, BTPROTO_CMTP)) < 0 ) {
perror("Can't open CMTP control socket");
exit(1);
}
for (i = 0; command[i].cmd; i++) {
if (strncmp(command[i].cmd, argv[0], 4) && strncmp(command[i].alt, argv[0], 4))
continue;
command[i].func(ctl, &bdaddr, argc, argv);
close(ctl);
exit(0);
}
usage();
close(ctl);
return 0;
}