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