// SPDX-License-Identifier: GPL-2.0-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include #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 [options]\n"); printf("Control Link Mode:\n" "\t-c connect \n" "\t-b close control link after closing data link\n" "\t-e 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 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 HCI device\n" "\t-C Control channel PSM\n" "\t-D 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; }