mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-24 04:34:17 +08:00
498 lines
9.7 KiB
C
498 lines
9.7 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2014 Intel 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
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#define _GNU_SOURCE
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <getopt.h>
|
|
#include <unistd.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include "lib/bluetooth.h"
|
|
#include "lib/hci.h"
|
|
#include "lib/hci_lib.h"
|
|
|
|
#include "btio/btio.h"
|
|
#include "lib/l2cap.h"
|
|
#include "profiles/health/mcap.h"
|
|
|
|
enum {
|
|
MODE_NONE,
|
|
MODE_CONNECT,
|
|
MODE_LISTEN,
|
|
};
|
|
|
|
static GMainLoop *mloop;
|
|
|
|
static int ccpsm = 0x1003, dcpsm = 0x1005;
|
|
|
|
static struct mcap_instance *mcap = NULL;
|
|
static struct mcap_mdl *mdl = NULL;
|
|
static uint16_t mdlid;
|
|
|
|
static int control_mode = MODE_LISTEN;
|
|
static int data_mode = MODE_LISTEN;
|
|
|
|
static int mdl_conn_req_result = MCAP_SUCCESS;
|
|
|
|
static gboolean send_synccap_req = FALSE;
|
|
static gboolean mcl_disconnect = FALSE;
|
|
static gboolean mdl_disconnect = FALSE;
|
|
static int mcl_disconnect_timeout = -1;
|
|
static int mdl_disconnect_timeout = -1;
|
|
|
|
static struct mcap_mcl *mcl = NULL;
|
|
|
|
static gboolean no_close = FALSE;
|
|
|
|
#define REQ_CLOCK_ACC 0x1400
|
|
|
|
static void mdl_close(struct mcap_mdl *mdl)
|
|
{
|
|
int fd = -1;
|
|
|
|
printf("%s\n", __func__);
|
|
|
|
if (mdl_disconnect_timeout >= 0)
|
|
sleep(mdl_disconnect_timeout);
|
|
|
|
fd = mcap_mdl_get_fd(mdl);
|
|
|
|
if (fd > 0)
|
|
close(fd);
|
|
}
|
|
|
|
static void mdl_connected_cb(struct mcap_mdl *mdl, void *data)
|
|
{
|
|
printf("%s\n", __func__);
|
|
|
|
if (mdl_disconnect)
|
|
mdl_close(mdl);
|
|
}
|
|
|
|
static void mdl_closed_cb(struct mcap_mdl *mdl, void *data)
|
|
{
|
|
printf("%s\n", __func__);
|
|
|
|
if (mcl_disconnect && mcl_disconnect_timeout >= 0) {
|
|
sleep(mcl_disconnect_timeout);
|
|
|
|
printf("Closing MCAP communication link\n");
|
|
mcap_close_mcl(mcl, TRUE);
|
|
|
|
if (no_close)
|
|
return;
|
|
|
|
g_main_loop_quit(mloop);
|
|
}
|
|
}
|
|
|
|
static void mdl_deleted_cb(struct mcap_mdl *mdl, void *data)
|
|
{
|
|
/* TODO */
|
|
printf("%s\n", __func__);
|
|
|
|
/* Disconnecting MDL latency timeout */
|
|
if (mdl_disconnect_timeout >= 0)
|
|
sleep(mdl_disconnect_timeout);
|
|
}
|
|
|
|
static void mdl_aborted_cb(struct mcap_mdl *mdl, void *data)
|
|
{
|
|
/* TODO */
|
|
printf("%s\n", __func__);
|
|
}
|
|
|
|
static uint8_t mdl_conn_req_cb(struct mcap_mcl *mcl, uint8_t mdepid,
|
|
uint16_t mdlid, uint8_t *conf, void *data)
|
|
{
|
|
int ret;
|
|
|
|
printf("%s\n", __func__);
|
|
|
|
ret = mdl_conn_req_result;
|
|
|
|
mdl_conn_req_result = MCAP_SUCCESS;
|
|
|
|
return ret;
|
|
}
|
|
|
|
static uint8_t mdl_reconn_req_cb(struct mcap_mdl *mdl, void *data)
|
|
{
|
|
printf("%s\n", __func__);
|
|
|
|
return MCAP_SUCCESS;
|
|
}
|
|
|
|
static void create_mdl_cb(struct mcap_mdl *mcap_mdl, uint8_t type, GError *gerr,
|
|
gpointer data);
|
|
|
|
static void mcl_reconnected(struct mcap_mcl *mcl, gpointer data)
|
|
{
|
|
GError *gerr = NULL;
|
|
|
|
printf("%s\n", __func__);
|
|
|
|
if (data_mode == MODE_CONNECT) {
|
|
mcap_create_mdl(mcl, 1, 0, create_mdl_cb, NULL, NULL, &gerr);
|
|
if (gerr) {
|
|
printf("Could not connect MDL: %s\n", gerr->message);
|
|
g_error_free(gerr);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mcl_disconnected(struct mcap_mcl *mcl, gpointer data)
|
|
{
|
|
/* TODO */
|
|
printf("%s\n", __func__);
|
|
|
|
if (no_close)
|
|
return;
|
|
|
|
g_main_loop_quit(mloop);
|
|
}
|
|
|
|
static void mcl_uncached(struct mcap_mcl *mcl, gpointer data)
|
|
{
|
|
/* TODO */
|
|
printf("%s\n", __func__);
|
|
}
|
|
|
|
static void connect_mdl_cb(struct mcap_mdl *mdl, GError *gerr, gpointer data)
|
|
{
|
|
mdlid = mcap_mdl_get_mdlid(mdl);
|
|
|
|
printf("%s\n", __func__);
|
|
|
|
if (mdlid == MCAP_MDLID_RESERVED)
|
|
printf("MCAP mdlid is reserved");
|
|
else
|
|
printf("MDL %d connected\n", mdlid);
|
|
}
|
|
|
|
static void create_mdl_cb(struct mcap_mdl *mcap_mdl, uint8_t type, GError *gerr,
|
|
gpointer data)
|
|
{
|
|
GError *err = NULL;
|
|
|
|
printf("%s\n", __func__);
|
|
|
|
if (gerr) {
|
|
printf("MDL error: %s\n", gerr->message);
|
|
|
|
if (!no_close)
|
|
g_main_loop_quit(mloop);
|
|
|
|
return;
|
|
}
|
|
|
|
if (mdl)
|
|
mcap_mdl_unref(mdl);
|
|
|
|
mdl = mcap_mdl_ref(mcap_mdl);
|
|
|
|
if (!mcap_connect_mdl(mdl, L2CAP_MODE_ERTM, dcpsm, connect_mdl_cb, NULL,
|
|
NULL, &err)) {
|
|
printf("Error connecting to mdl: %s\n", err->message);
|
|
g_error_free(err);
|
|
|
|
if (no_close)
|
|
return;
|
|
|
|
g_main_loop_quit(mloop);
|
|
}
|
|
}
|
|
|
|
static void sync_cap_cb(struct mcap_mcl *mcl, uint8_t mcap_err,
|
|
uint8_t btclockres, uint16_t synclead,
|
|
uint16_t tmstampres, uint16_t tmstampacc, GError *err,
|
|
gpointer data)
|
|
{
|
|
/* TODO */
|
|
printf("%s\n", __func__);
|
|
}
|
|
|
|
static void trigger_mdl_action(int mode)
|
|
{
|
|
GError *gerr = NULL;
|
|
gboolean ret;
|
|
|
|
ret = mcap_mcl_set_cb(mcl, NULL, &gerr,
|
|
MCAP_MDL_CB_CONNECTED, mdl_connected_cb,
|
|
MCAP_MDL_CB_CLOSED, mdl_closed_cb,
|
|
MCAP_MDL_CB_DELETED, mdl_deleted_cb,
|
|
MCAP_MDL_CB_ABORTED, mdl_aborted_cb,
|
|
MCAP_MDL_CB_REMOTE_CONN_REQ, mdl_conn_req_cb,
|
|
MCAP_MDL_CB_REMOTE_RECONN_REQ, mdl_reconn_req_cb,
|
|
MCAP_MDL_CB_INVALID);
|
|
|
|
if (!ret && gerr) {
|
|
printf("MCL cannot handle connection %s\n",
|
|
gerr->message);
|
|
g_error_free(gerr);
|
|
}
|
|
|
|
if (mode == MODE_CONNECT) {
|
|
printf("Creating MCAP Data End Point\n");
|
|
mcap_create_mdl(mcl, 1, 0, create_mdl_cb, NULL, NULL, &gerr);
|
|
if (gerr) {
|
|
printf("Could not connect MDL: %s\n", gerr->message);
|
|
g_error_free(gerr);
|
|
}
|
|
}
|
|
|
|
if (send_synccap_req && mcap->csp_enabled) {
|
|
mcap_sync_init(mcl);
|
|
|
|
mcap_sync_cap_req(mcl, REQ_CLOCK_ACC, sync_cap_cb, NULL, &gerr);
|
|
if (gerr) {
|
|
printf("MCAP Sync req error: %s\n", gerr->message);
|
|
g_error_free(gerr);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void mcl_connected(struct mcap_mcl *mcap_mcl, gpointer data)
|
|
{
|
|
printf("%s\n", __func__);
|
|
|
|
if (mcl) {
|
|
mcap_sync_stop(mcl);
|
|
mcap_mcl_unref(mcl);
|
|
}
|
|
|
|
mcl = mcap_mcl_ref(mcap_mcl);
|
|
trigger_mdl_action(data_mode);
|
|
}
|
|
|
|
static void create_mcl_cb(struct mcap_mcl *mcap_mcl, GError *err, gpointer data)
|
|
{
|
|
printf("%s\n", __func__);
|
|
|
|
if (err) {
|
|
printf("Could not connect MCL: %s\n", err->message);
|
|
|
|
if (!no_close)
|
|
g_main_loop_quit(mloop);
|
|
|
|
return;
|
|
}
|
|
|
|
if (mcl) {
|
|
mcap_sync_stop(mcl);
|
|
mcap_mcl_unref(mcl);
|
|
}
|
|
|
|
mcl = mcap_mcl_ref(mcap_mcl);
|
|
trigger_mdl_action(data_mode);
|
|
}
|
|
|
|
static void usage(void)
|
|
{
|
|
printf("mcaptest - MCAP testing ver %s\n", VERSION);
|
|
printf("Usage:\n"
|
|
"\tmcaptest <control_mode> <data_mode> [options]\n");
|
|
printf("Control Link Mode:\n"
|
|
"\t-c connect <dst_addr>\n"
|
|
"\t-b close control link after closing data link\n"
|
|
"\t-e <timeout> disconnect MCL and quit after MDL is closed\n"
|
|
"\t-g send clock sync capability request if MCL connected\n");
|
|
printf("Data Link Mode:\n"
|
|
"\t-d connect\n"
|
|
"\t-a close data link immediately after being connected"
|
|
"\t-f <timeout> disconnect MDL after it's connected\n"
|
|
"\t-u send \'Unavailable\' on first MDL connection request\n");
|
|
printf("Options:\n"
|
|
"\t-n don't exit after mcl disconnect/err receive\n"
|
|
"\t-i <hcidev> HCI device\n"
|
|
"\t-C <control_ch> Control channel PSM\n"
|
|
"\t-D <data_ch> Data channel PSM\n");
|
|
}
|
|
|
|
static struct option main_options[] = {
|
|
{ "help", 0, 0, 'h' },
|
|
{ "device", 1, 0, 'i' },
|
|
{ "connect_cl", 1, 0, 'c' },
|
|
{ "disconnect_cl", 1, 0, 'e' },
|
|
{ "synccap_req", 0, 0, 'g' },
|
|
{ "connect_dl", 0, 0, 'd' },
|
|
{ "disconnect_da", 0, 0, 'a' },
|
|
{ "disconnect_ca", 0, 0, 'b' },
|
|
{ "disconnect_dl", 1, 0, 'f' },
|
|
{ "unavailable_dl", 0, 0, 'u' },
|
|
{ "no exit mcl dis/err",0, 0, 'n' },
|
|
{ "control_ch", 1, 0, 'C' },
|
|
{ "data_ch", 1, 0, 'D' },
|
|
{ 0, 0, 0, 0 }
|
|
};
|
|
|
|
int main(int argc, char *argv[])
|
|
{
|
|
GError *err = NULL;
|
|
bdaddr_t src, dst;
|
|
int opt;
|
|
char bdastr[18];
|
|
|
|
hci_devba(0, &src);
|
|
bacpy(&dst, BDADDR_ANY);
|
|
|
|
mloop = g_main_loop_new(NULL, FALSE);
|
|
if (!mloop) {
|
|
printf("Cannot create main loop\n");
|
|
|
|
exit(1);
|
|
}
|
|
|
|
while ((opt = getopt_long(argc, argv, "+i:c:C:D:e:f:dghunab",
|
|
main_options, NULL)) != EOF) {
|
|
switch (opt) {
|
|
case 'i':
|
|
if (!strncmp(optarg, "hci", 3))
|
|
hci_devba(atoi(optarg + 3), &src);
|
|
else
|
|
str2ba(optarg, &src);
|
|
|
|
break;
|
|
|
|
case 'c':
|
|
control_mode = MODE_CONNECT;
|
|
str2ba(optarg, &dst);
|
|
|
|
break;
|
|
|
|
case 'd':
|
|
data_mode = MODE_CONNECT;
|
|
|
|
break;
|
|
|
|
case 'a':
|
|
mdl_disconnect = TRUE;
|
|
|
|
break;
|
|
|
|
case 'b':
|
|
mcl_disconnect = TRUE;
|
|
|
|
break;
|
|
|
|
case 'e':
|
|
mcl_disconnect_timeout = atoi(optarg);
|
|
|
|
break;
|
|
|
|
case 'f':
|
|
mdl_disconnect_timeout = atoi(optarg);
|
|
|
|
break;
|
|
|
|
case 'g':
|
|
send_synccap_req = TRUE;
|
|
|
|
break;
|
|
|
|
case 'u':
|
|
mdl_conn_req_result = MCAP_RESOURCE_UNAVAILABLE;
|
|
|
|
break;
|
|
|
|
case 'n':
|
|
no_close = TRUE;
|
|
|
|
break;
|
|
|
|
case 'C':
|
|
ccpsm = atoi(optarg);
|
|
|
|
break;
|
|
|
|
case 'D':
|
|
dcpsm = atoi(optarg);
|
|
|
|
break;
|
|
|
|
case 'h':
|
|
default:
|
|
usage();
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
mcap = mcap_create_instance(&src, BT_IO_SEC_MEDIUM, ccpsm, dcpsm,
|
|
mcl_connected, mcl_reconnected,
|
|
mcl_disconnected, mcl_uncached,
|
|
NULL, /* CSP is not used right now */
|
|
NULL, &err);
|
|
|
|
if (!mcap) {
|
|
printf("MCAP instance creation failed %s\n", err->message);
|
|
g_error_free(err);
|
|
|
|
exit(1);
|
|
}
|
|
|
|
mcap_enable_csp(mcap);
|
|
|
|
switch (control_mode) {
|
|
case MODE_CONNECT:
|
|
ba2str(&dst, bdastr);
|
|
printf("Connecting to %s\n", bdastr);
|
|
|
|
mcap_create_mcl(mcap, &dst, ccpsm, create_mcl_cb, NULL, NULL,
|
|
&err);
|
|
|
|
if (err) {
|
|
printf("MCAP create error %s\n", err->message);
|
|
g_error_free(err);
|
|
|
|
exit(1);
|
|
}
|
|
|
|
break;
|
|
case MODE_LISTEN:
|
|
printf("Listening for control channel connection\n");
|
|
|
|
break;
|
|
case MODE_NONE:
|
|
default:
|
|
goto done;
|
|
}
|
|
|
|
g_main_loop_run(mloop);
|
|
|
|
done:
|
|
printf("Done\n");
|
|
|
|
if (mcap)
|
|
mcap_instance_unref(mcap);
|
|
|
|
g_main_loop_unref(mloop);
|
|
|
|
return 0;
|
|
}
|