blob: 3e96676a7fe1c8385002eb792bcd2d328362b8ed [file] [log] [blame]
/** @file
* @brief Bluetooth BR/EDR shell module
*
* Provide some Bluetooth shell commands that can be useful to applications.
*/
/*
* Copyright (c) 2018 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <errno.h>
#include <zephyr/types.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/kernel.h>
#include <zephyr/settings/settings.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/conn.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/bluetooth/rfcomm.h>
#include <zephyr/bluetooth/sdp.h>
#include <zephyr/shell/shell.h>
#include "bt.h"
#if defined(CONFIG_BT_CONN)
/* Connection context for BR/EDR legacy pairing in sec mode 3 */
static struct bt_conn *pairing_conn;
#endif /* CONFIG_BT_CONN */
#define DATA_BREDR_MTU 48
NET_BUF_POOL_FIXED_DEFINE(data_pool, 1, DATA_BREDR_MTU, 8, NULL);
#define SDP_CLIENT_USER_BUF_LEN 512
NET_BUF_POOL_FIXED_DEFINE(sdp_client_pool, CONFIG_BT_MAX_CONN,
SDP_CLIENT_USER_BUF_LEN, 8, NULL);
static int cmd_auth_pincode(const struct shell *sh,
size_t argc, char *argv[])
{
struct bt_conn *conn;
uint8_t max = 16U;
if (default_conn) {
conn = default_conn;
} else if (pairing_conn) {
conn = pairing_conn;
} else {
conn = NULL;
}
if (!conn) {
shell_print(sh, "Not connected");
return 0;
}
if (strlen(argv[1]) > max) {
shell_print(sh, "PIN code value invalid - enter max %u "
"digits", max);
return 0;
}
shell_print(sh, "PIN code \"%s\" applied", argv[1]);
bt_conn_auth_pincode_entry(conn, argv[1]);
return 0;
}
static int cmd_connect(const struct shell *sh, size_t argc, char *argv[])
{
struct bt_conn *conn;
bt_addr_t addr;
int err;
err = bt_addr_from_str(argv[1], &addr);
if (err) {
shell_print(sh, "Invalid peer address (err %d)", err);
return -ENOEXEC;
}
conn = bt_conn_create_br(&addr, BT_BR_CONN_PARAM_DEFAULT);
if (!conn) {
shell_print(sh, "Connection failed");
} else {
shell_print(sh, "Connection pending");
/* unref connection obj in advance as app user */
bt_conn_unref(conn);
}
return 0;
}
static void br_device_found(const bt_addr_t *addr, int8_t rssi,
const uint8_t cod[3], const uint8_t eir[240])
{
char br_addr[BT_ADDR_STR_LEN];
char name[239];
int len = 240;
(void)memset(name, 0, sizeof(name));
while (len) {
if (len < 2) {
break;
}
/* Look for early termination */
if (!eir[0]) {
break;
}
/* Check if field length is correct */
if (eir[0] > len - 1) {
break;
}
switch (eir[1]) {
case BT_DATA_NAME_SHORTENED:
case BT_DATA_NAME_COMPLETE:
if (eir[0] > sizeof(name) - 1) {
memcpy(name, &eir[2], sizeof(name) - 1);
} else {
memcpy(name, &eir[2], eir[0] - 1);
}
break;
default:
break;
}
/* Parse next AD Structure */
len -= eir[0] + 1;
eir += eir[0] + 1;
}
bt_addr_to_str(addr, br_addr, sizeof(br_addr));
shell_print(ctx_shell, "[DEVICE]: %s, RSSI %i %s", br_addr, rssi, name);
}
static struct bt_br_discovery_result br_discovery_results[5];
static void br_discovery_complete(struct bt_br_discovery_result *results,
size_t count)
{
size_t i;
shell_print(ctx_shell, "BR/EDR discovery complete");
for (i = 0; i < count; i++) {
br_device_found(&results[i].addr, results[i].rssi,
results[i].cod, results[i].eir);
}
}
static int cmd_discovery(const struct shell *sh, size_t argc, char *argv[])
{
const char *action;
action = argv[1];
if (!strcmp(action, "on")) {
struct bt_br_discovery_param param;
param.limited = false;
param.length = 8U;
if (argc > 2) {
param.length = atoi(argv[2]);
}
if (argc > 3 && !strcmp(argv[3], "limited")) {
param.limited = true;
}
if (bt_br_discovery_start(&param, br_discovery_results,
ARRAY_SIZE(br_discovery_results),
br_discovery_complete) < 0) {
shell_print(sh, "Failed to start discovery");
return 0;
}
shell_print(sh, "Discovery started");
} else if (!strcmp(action, "off")) {
if (bt_br_discovery_stop()) {
shell_print(sh, "Failed to stop discovery");
return 0;
}
shell_print(sh, "Discovery stopped");
} else {
shell_help(sh);
}
return 0;
}
static int l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
shell_print(ctx_shell, "Incoming data channel %p len %u", chan,
buf->len);
return 0;
}
static void l2cap_connected(struct bt_l2cap_chan *chan)
{
shell_print(ctx_shell, "Channel %p connected", chan);
}
static void l2cap_disconnected(struct bt_l2cap_chan *chan)
{
shell_print(ctx_shell, "Channel %p disconnected", chan);
}
static struct net_buf *l2cap_alloc_buf(struct bt_l2cap_chan *chan)
{
shell_print(ctx_shell, "Channel %p requires buffer", chan);
return net_buf_alloc(&data_pool, K_FOREVER);
}
static const struct bt_l2cap_chan_ops l2cap_ops = {
.alloc_buf = l2cap_alloc_buf,
.recv = l2cap_recv,
.connected = l2cap_connected,
.disconnected = l2cap_disconnected,
};
static struct bt_l2cap_br_chan l2cap_chan = {
.chan.ops = &l2cap_ops,
/* Set for now min. MTU */
.rx.mtu = 48,
};
static int l2cap_accept(struct bt_conn *conn, struct bt_l2cap_chan **chan)
{
shell_print(ctx_shell, "Incoming BR/EDR conn %p", conn);
if (l2cap_chan.chan.conn) {
shell_error(ctx_shell, "No channels available");
return -ENOMEM;
}
*chan = &l2cap_chan.chan;
return 0;
}
static struct bt_l2cap_server br_server = {
.accept = l2cap_accept,
};
static int cmd_l2cap_register(const struct shell *sh,
size_t argc, char *argv[])
{
if (br_server.psm) {
shell_print(sh, "Already registered");
return 0;
}
br_server.psm = strtoul(argv[1], NULL, 16);
if (bt_l2cap_br_server_register(&br_server) < 0) {
shell_error(sh, "Unable to register psm");
br_server.psm = 0U;
return -ENOEXEC;
} else {
shell_print(sh, "L2CAP psm %u registered", br_server.psm);
}
return 0;
}
static int cmd_discoverable(const struct shell *sh,
size_t argc, char *argv[])
{
int err;
const char *action;
action = argv[1];
if (!strcmp(action, "on")) {
err = bt_br_set_discoverable(true);
} else if (!strcmp(action, "off")) {
err = bt_br_set_discoverable(false);
} else {
shell_help(sh);
return 0;
}
if (err) {
shell_print(sh, "BR/EDR set/reset discoverable failed "
"(err %d)", err);
return -ENOEXEC;
} else {
shell_print(sh, "BR/EDR set/reset discoverable done");
}
return 0;
}
static int cmd_connectable(const struct shell *sh,
size_t argc, char *argv[])
{
int err;
const char *action;
action = argv[1];
if (!strcmp(action, "on")) {
err = bt_br_set_connectable(true);
} else if (!strcmp(action, "off")) {
err = bt_br_set_connectable(false);
} else {
shell_help(sh);
return 0;
}
if (err) {
shell_print(sh, "BR/EDR set/rest connectable failed "
"(err %d)", err);
return -ENOEXEC;
} else {
shell_print(sh, "BR/EDR set/reset connectable done");
}
return 0;
}
static int cmd_oob(const struct shell *sh, size_t argc, char *argv[])
{
char addr[BT_ADDR_STR_LEN];
struct bt_br_oob oob;
int err;
err = bt_br_oob_get_local(&oob);
if (err) {
shell_print(sh, "BR/EDR OOB data failed");
return -ENOEXEC;
}
bt_addr_to_str(&oob.addr, addr, sizeof(addr));
shell_print(sh, "BR/EDR OOB data:");
shell_print(sh, " addr %s", addr);
return 0;
}
static uint8_t sdp_hfp_ag_user(struct bt_conn *conn,
struct bt_sdp_client_result *result)
{
char addr[BT_ADDR_STR_LEN];
uint16_t param, version;
uint16_t features;
int res;
conn_addr_str(conn, addr, sizeof(addr));
if (result) {
shell_print(ctx_shell, "SDP HFPAG data@%p (len %u) hint %u from"
" remote %s", result->resp_buf,
result->resp_buf->len, result->next_record_hint,
addr);
/*
* Focus to get BT_SDP_ATTR_PROTO_DESC_LIST attribute item to
* get HFPAG Server Channel Number operating on RFCOMM protocol.
*/
res = bt_sdp_get_proto_param(result->resp_buf,
BT_SDP_PROTO_RFCOMM, &param);
if (res < 0) {
shell_error(ctx_shell, "Error getting Server CN, "
"err %d", res);
goto done;
}
shell_print(ctx_shell, "HFPAG Server CN param 0x%04x", param);
res = bt_sdp_get_profile_version(result->resp_buf,
BT_SDP_HANDSFREE_SVCLASS,
&version);
if (res < 0) {
shell_error(ctx_shell, "Error getting profile version, "
"err %d", res);
goto done;
}
shell_print(ctx_shell, "HFP version param 0x%04x", version);
/*
* Focus to get BT_SDP_ATTR_SUPPORTED_FEATURES attribute item to
* get profile Supported Features mask.
*/
res = bt_sdp_get_features(result->resp_buf, &features);
if (res < 0) {
shell_error(ctx_shell, "Error getting HFPAG Features, "
"err %d", res);
goto done;
}
shell_print(ctx_shell, "HFPAG Supported Features param 0x%04x",
features);
} else {
shell_print(ctx_shell, "No SDP HFPAG data from remote %s",
addr);
}
done:
return BT_SDP_DISCOVER_UUID_CONTINUE;
}
static uint8_t sdp_a2src_user(struct bt_conn *conn,
struct bt_sdp_client_result *result)
{
char addr[BT_ADDR_STR_LEN];
uint16_t param, version;
uint16_t features;
int res;
conn_addr_str(conn, addr, sizeof(addr));
if (result) {
shell_print(ctx_shell, "SDP A2SRC data@%p (len %u) hint %u from"
" remote %s", result->resp_buf,
result->resp_buf->len, result->next_record_hint,
addr);
/*
* Focus to get BT_SDP_ATTR_PROTO_DESC_LIST attribute item to
* get A2SRC Server PSM Number.
*/
res = bt_sdp_get_proto_param(result->resp_buf,
BT_SDP_PROTO_L2CAP, &param);
if (res < 0) {
shell_error(ctx_shell, "A2SRC PSM Number not found, "
"err %d", res);
goto done;
}
shell_print(ctx_shell, "A2SRC Server PSM Number param 0x%04x",
param);
/*
* Focus to get BT_SDP_ATTR_PROFILE_DESC_LIST attribute item to
* get profile version number.
*/
res = bt_sdp_get_profile_version(result->resp_buf,
BT_SDP_ADVANCED_AUDIO_SVCLASS,
&version);
if (res < 0) {
shell_error(ctx_shell, "A2SRC version not found, "
"err %d", res);
goto done;
}
shell_print(ctx_shell, "A2SRC version param 0x%04x", version);
/*
* Focus to get BT_SDP_ATTR_SUPPORTED_FEATURES attribute item to
* get profile supported features mask.
*/
res = bt_sdp_get_features(result->resp_buf, &features);
if (res < 0) {
shell_error(ctx_shell, "A2SRC Features not found, "
"err %d", res);
goto done;
}
shell_print(ctx_shell, "A2SRC Supported Features param 0x%04x",
features);
} else {
shell_print(ctx_shell, "No SDP A2SRC data from remote %s",
addr);
}
done:
return BT_SDP_DISCOVER_UUID_CONTINUE;
}
static struct bt_sdp_discover_params discov_hfpag = {
.uuid = BT_UUID_DECLARE_16(BT_SDP_HANDSFREE_AGW_SVCLASS),
.func = sdp_hfp_ag_user,
.pool = &sdp_client_pool,
};
static struct bt_sdp_discover_params discov_a2src = {
.uuid = BT_UUID_DECLARE_16(BT_SDP_AUDIO_SOURCE_SVCLASS),
.func = sdp_a2src_user,
.pool = &sdp_client_pool,
};
static struct bt_sdp_discover_params discov;
static int cmd_sdp_find_record(const struct shell *sh,
size_t argc, char *argv[])
{
int res;
const char *action;
if (!default_conn) {
shell_print(sh, "Not connected");
return 0;
}
action = argv[1];
if (!strcmp(action, "HFPAG")) {
discov = discov_hfpag;
} else if (!strcmp(action, "A2SRC")) {
discov = discov_a2src;
} else {
shell_help(sh);
return 0;
}
shell_print(sh, "SDP UUID \'%s\' gets applied", action);
res = bt_sdp_discover(default_conn, &discov);
if (res) {
shell_error(sh, "SDP discovery failed: result %d", res);
return -ENOEXEC;
} else {
shell_print(sh, "SDP discovery started");
}
return 0;
}
#define HELP_NONE "[none]"
#define HELP_ADDR_LE "<address: XX:XX:XX:XX:XX:XX> <type: (public|random)>"
SHELL_STATIC_SUBCMD_SET_CREATE(br_cmds,
SHELL_CMD_ARG(auth-pincode, NULL, "<pincode>", cmd_auth_pincode, 2, 0),
SHELL_CMD_ARG(connect, NULL, "<address>", cmd_connect, 2, 0),
SHELL_CMD_ARG(discovery, NULL,
"<value: on, off> [length: 1-48] [mode: limited]",
cmd_discovery, 2, 2),
SHELL_CMD_ARG(iscan, NULL, "<value: on, off>", cmd_discoverable, 2, 0),
SHELL_CMD_ARG(l2cap-register, NULL, "<psm>", cmd_l2cap_register, 2, 0),
SHELL_CMD_ARG(oob, NULL, NULL, cmd_oob, 1, 0),
SHELL_CMD_ARG(pscan, NULL, "<value: on, off>", cmd_connectable, 2, 0),
SHELL_CMD_ARG(sdp-find, NULL, "<HFPAG>", cmd_sdp_find_record, 2, 0),
SHELL_SUBCMD_SET_END
);
static int cmd_br(const struct shell *sh, size_t argc, char **argv)
{
if (argc == 1) {
shell_help(sh);
/* shell returns 1 when help is printed */
return 1;
}
shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]);
return -ENOEXEC;
}
SHELL_CMD_ARG_REGISTER(br, &br_cmds, "Bluetooth BR/EDR shell commands", cmd_br,
1, 1);