| /** @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(¶m, 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, ¶m); | 
 | 		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, ¶m); | 
 | 		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); |