diff --git a/include/zephyr/bluetooth/uuid.h b/include/zephyr/bluetooth/uuid.h index eeee5470b9e18..8cab49851e1fe 100644 --- a/include/zephyr/bluetooth/uuid.h +++ b/include/zephyr/bluetooth/uuid.h @@ -5249,6 +5249,18 @@ bool bt_uuid_create(struct bt_uuid *uuid, const uint8_t *data, uint8_t data_len) */ void bt_uuid_to_str(const struct bt_uuid *uuid, char *str, size_t len); +/** @brief Convert string to Bluetooth UUID. + * + * Converts string to Bluetooth UUID. + * UUID can be in any format, 16-bit, 32-bit or 128-bit. + * + * @param str pointer to string to convert + * @param uuid pointer where to put converted UUID + * + * @return true if the string was valid and the UUID was successfully created. + */ +bool bt_str_to_uuid(const char *str, struct bt_uuid *uuid); + #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/shell/CMakeLists.txt b/subsys/bluetooth/host/classic/shell/CMakeLists.txt index 3ace702c49af6..f137f6a79adfb 100644 --- a/subsys/bluetooth/host/classic/shell/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/shell/CMakeLists.txt @@ -2,7 +2,7 @@ zephyr_library() zephyr_library_sources(bredr.c) -zephyr_library_sources_ifdef(CONFIG_BT_RFCOMM rfcomm.c) +zephyr_library_sources_ifdef(CONFIG_BT_RFCOMM spp.c) zephyr_library_sources_ifdef(CONFIG_BT_A2DP a2dp.c) zephyr_library_sources_ifdef(CONFIG_BT_AVRCP avrcp.c) if(CONFIG_BT_HFP_HF OR CONFIG_BT_HFP_AG) diff --git a/subsys/bluetooth/host/classic/shell/bredr.c b/subsys/bluetooth/host/classic/shell/bredr.c index a3f57f25fe3b9..d1d38da342228 100644 --- a/subsys/bluetooth/host/classic/shell/bredr.c +++ b/subsys/bluetooth/host/classic/shell/bredr.c @@ -47,6 +47,72 @@ NET_BUF_POOL_FIXED_DEFINE(data_rx_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); +#ifdef CONFIG_BT_RFCOMM +#define RFCOMM_DATA_MTU 48 + +static struct bt_sdp_attribute spp_attrs[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_SERIAL_PORT_SVCLASS) + }, + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + ) + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + BT_SDP_ARRAY_8(BT_RFCOMM_CHAN_SPP) + }, + ) + }, + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_SERIAL_PORT_SVCLASS) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0102) + }, + ) + }, + ) + ), + BT_SDP_SERVICE_NAME("Serial Port"), +}; +#endif /* CONFIG_BT_RFCOMM */ + static int cmd_auth_pincode(const struct shell *sh, size_t argc, char *argv[]) { struct bt_conn *conn; @@ -1511,6 +1577,179 @@ static int cmd_default_handler(const struct shell *sh, size_t argc, char **argv) return -EINVAL; } +#if defined(CONFIG_BT_RFCOMM) +static struct bt_sdp_record spp_rec = BT_SDP_RECORD(spp_attrs); + +static void rfcomm_recv(struct bt_rfcomm_dlc *dlci, struct net_buf *buf) +{ + bt_shell_print("Incoming data dlc %p len %u", dlci, buf->len); +} + +static void rfcomm_connected(struct bt_rfcomm_dlc *dlci) +{ + bt_shell_print("Dlc %p connected", dlci); +} + +static void rfcomm_disconnected(struct bt_rfcomm_dlc *dlci) +{ + bt_shell_print("Dlc %p disconnected", dlci); +} + +static struct bt_rfcomm_dlc_ops rfcomm_ops = { + .recv = rfcomm_recv, + .connected = rfcomm_connected, + .disconnected = rfcomm_disconnected, +}; + +static struct bt_rfcomm_dlc rfcomm_dlc = { + .ops = &rfcomm_ops, + .mtu = 30, +}; + +static int rfcomm_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) +{ + bt_shell_print("Incoming RFCOMM conn %p", conn); + + if (rfcomm_dlc.session) { + bt_shell_error("No channels available"); + return -ENOMEM; + } + + *dlc = &rfcomm_dlc; + + return 0; +} + +struct bt_rfcomm_server rfcomm_server = { + .accept = &rfcomm_accept, +}; + +static int cmd_register(const struct shell *sh, size_t argc, char *argv[]) +{ + int ret; + + if (rfcomm_server.channel) { + shell_error(sh, "Already registered"); + return -ENOEXEC; + } + + rfcomm_server.channel = BT_RFCOMM_CHAN_SPP; + + ret = bt_rfcomm_server_register(&rfcomm_server); + if (ret < 0) { + shell_error(sh, "Unable to register channel %x", ret); + rfcomm_server.channel = 0U; + return -ENOEXEC; + } else { + shell_print(sh, "RFCOMM channel %u registered", + rfcomm_server.channel); + bt_sdp_register_service(&spp_rec); + } + + return 0; +} + +static int cmd_connect(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t channel; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + channel = strtoul(argv[1], NULL, 16); + + err = bt_rfcomm_dlc_connect(default_conn, &rfcomm_dlc, channel); + if (err < 0) { + shell_error(sh, "Unable to connect to channel %d (err %u)", + channel, err); + } else { + shell_print(sh, "RFCOMM connection pending"); + } + + return err; +} + +static int cmd_send(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t buf_data[RFCOMM_DATA_MTU] = { [0 ... (RFCOMM_DATA_MTU - 1)] = 0xff }; + int ret, len, count = 1; + struct net_buf *buf; + + if (argc > 1) { + count = strtoul(argv[1], NULL, 10); + } + + while (count--) { + buf = bt_rfcomm_create_pdu(&data_tx_pool); + /* Should reserve one byte in tail for FCS */ + len = MIN(rfcomm_dlc.mtu, net_buf_tailroom(buf) - 1); + + net_buf_add_mem(buf, buf_data, len); + ret = bt_rfcomm_dlc_send(&rfcomm_dlc, buf); + if (ret < 0) { + shell_error(sh, "Unable to send: %d", -ret); + net_buf_unref(buf); + return -ENOEXEC; + } + } + + return 0; +} + +static int cmd_disconnect(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + err = bt_rfcomm_dlc_disconnect(&rfcomm_dlc); + if (err) { + shell_error(sh, "Unable to disconnect: %u", -err); + return -ENOEXEC; + } + + return 0; +} + +static int cmd_send_rpn(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + struct bt_rfcomm_rpn rpn; + + if (!rfcomm_dlc.session) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + /* Initialize RPN with default values */ + memset(&rpn, 0, sizeof(rpn)); + + /* Set default values for RPN parameters */ + rpn.baud_rate = BT_RFCOMM_RPN_BAUD_RATE_9600; + rpn.line_settings = BT_RFCOMM_SET_LINE_SETTINGS( + BT_RFCOMM_RPN_DATA_BITS_8, + BT_RFCOMM_RPN_STOP_BITS_1, + BT_RFCOMM_RPN_PARITY_NONE); + rpn.flow_control = BT_RFCOMM_RPN_FLOW_NONE; + rpn.xon_char = BT_RFCOMM_RPN_XON_CHAR; + rpn.xoff_char = BT_RFCOMM_RPN_XOFF_CHAR; + rpn.param_mask = BT_RFCOMM_RPN_PARAM_MASK_ALL; + + shell_print(sh, "Sending RFCOMM RPN command with default settings"); + + err = bt_rfcomm_send_rpn_cmd(&rfcomm_dlc, &rpn); + if (err < 0) { + shell_error(sh, "Unable to send RPN command: %d", err); + return -ENOEXEC; + } + + shell_print(sh, "RFCOMM RPN command sent successfully"); + return 0; +} +#endif /* CONFIG_BT_RFCOMM */ + #define HELP_NONE "[none]" #define HELP_ADDR "" #define HELP_REG \ @@ -1559,6 +1798,17 @@ SHELL_STATIC_SUBCMD_SET_CREATE(l2cap_cmds, SHELL_SUBCMD_SET_END ); +#if defined(CONFIG_BT_RFCOMM) +SHELL_STATIC_SUBCMD_SET_CREATE(rfcomm_cmds, + SHELL_CMD_ARG(register, NULL, HELP_NONE, cmd_register, 1, 0), + SHELL_CMD_ARG(connect, NULL, "", cmd_connect, 2, 0), + SHELL_CMD_ARG(disconnect, NULL, HELP_NONE, cmd_disconnect, 1, 0), + SHELL_CMD_ARG(send, NULL, "", cmd_send, 2, 0), + SHELL_CMD_ARG(rpn, NULL, "Send RPN command with default settings", cmd_send_rpn, 1, 0), + SHELL_SUBCMD_SET_END +); +#endif /* CONFIG_BT_RFCOMM */ + SHELL_STATIC_SUBCMD_SET_CREATE(br_cmds, SHELL_CMD_ARG(auth-pincode, NULL, "", cmd_auth_pincode, 2, 0), SHELL_CMD_ARG(connect, NULL, "
", cmd_connect, 2, 0), @@ -1571,6 +1821,10 @@ SHELL_STATIC_SUBCMD_SET_CREATE(br_cmds, SHELL_CMD_ARG(iscan, NULL, " [mode: limited]", cmd_discoverable, 2, 1), SHELL_CMD(l2cap, &l2cap_cmds, HELP_NONE, cmd_default_handler), +#if defined(CONFIG_BT_RFCOMM) + SHELL_CMD(rfcomm, &rfcomm_cmds, HELP_NONE, cmd_default_handler), +#endif /* CONFIG_BT_RFCOMM */ + SHELL_CMD(rfcomm, &rfcomm_cmds, "Bluetooth RFCOMM shell commands", cmd_default_handler), SHELL_CMD_ARG(oob, NULL, NULL, cmd_oob, 1, 0), SHELL_CMD_ARG(pscan, NULL, "", cmd_connectable, 2, 0), SHELL_CMD_ARG(sdp-find, NULL, "", diff --git a/subsys/bluetooth/host/classic/shell/rfcomm.c b/subsys/bluetooth/host/classic/shell/rfcomm.c deleted file mode 100644 index 380011728cb10..0000000000000 --- a/subsys/bluetooth/host/classic/shell/rfcomm.c +++ /dev/null @@ -1,297 +0,0 @@ -/** @file - * @brief Bluetooth RFCOMM 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 -#include -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include -#include - -#include - -#include "host/shell/bt.h" -#include "common/bt_shell_private.h" - -#define DATA_MTU 48 - -NET_BUF_POOL_FIXED_DEFINE(pool, 1, DATA_MTU, CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); - -static struct bt_sdp_attribute spp_attrs[] = { - BT_SDP_NEW_SERVICE, - BT_SDP_LIST( - BT_SDP_ATTR_SVCLASS_ID_LIST, - BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), - BT_SDP_DATA_ELEM_LIST( - { - BT_SDP_TYPE_SIZE(BT_SDP_UUID16), - BT_SDP_ARRAY_16(BT_SDP_SERIAL_PORT_SVCLASS) - }, - ) - ), - BT_SDP_LIST( - BT_SDP_ATTR_PROTO_DESC_LIST, - BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12), - BT_SDP_DATA_ELEM_LIST( - { - BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), - BT_SDP_DATA_ELEM_LIST( - { - BT_SDP_TYPE_SIZE(BT_SDP_UUID16), - BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) - }, - ) - }, - { - BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), - BT_SDP_DATA_ELEM_LIST( - { - BT_SDP_TYPE_SIZE(BT_SDP_UUID16), - BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) - }, - { - BT_SDP_TYPE_SIZE(BT_SDP_UINT8), - BT_SDP_ARRAY_8(BT_RFCOMM_CHAN_SPP) - }, - ) - }, - ) - ), - BT_SDP_LIST( - BT_SDP_ATTR_PROFILE_DESC_LIST, - BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), - BT_SDP_DATA_ELEM_LIST( - { - BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), - BT_SDP_DATA_ELEM_LIST( - { - BT_SDP_TYPE_SIZE(BT_SDP_UUID16), - BT_SDP_ARRAY_16(BT_SDP_SERIAL_PORT_SVCLASS) - }, - { - BT_SDP_TYPE_SIZE(BT_SDP_UINT16), - BT_SDP_ARRAY_16(0x0102) - }, - ) - }, - ) - ), - BT_SDP_SERVICE_NAME("Serial Port"), -}; - -static struct bt_sdp_record spp_rec = BT_SDP_RECORD(spp_attrs); - -static void rfcomm_recv(struct bt_rfcomm_dlc *dlci, struct net_buf *buf) -{ - bt_shell_print("Incoming data dlc %p len %u", dlci, buf->len); -} - -static void rfcomm_connected(struct bt_rfcomm_dlc *dlci) -{ - bt_shell_print("Dlc %p connected", dlci); -} - -static void rfcomm_disconnected(struct bt_rfcomm_dlc *dlci) -{ - bt_shell_print("Dlc %p disconnected", dlci); -} - -static struct bt_rfcomm_dlc_ops rfcomm_ops = { - .recv = rfcomm_recv, - .connected = rfcomm_connected, - .disconnected = rfcomm_disconnected, -}; - -static struct bt_rfcomm_dlc rfcomm_dlc = { - .ops = &rfcomm_ops, - .mtu = 30, -}; - -static int rfcomm_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, - struct bt_rfcomm_dlc **dlc) -{ - bt_shell_print("Incoming RFCOMM conn %p", conn); - - if (rfcomm_dlc.session) { - bt_shell_error("No channels available"); - return -ENOMEM; - } - - *dlc = &rfcomm_dlc; - - return 0; -} - -struct bt_rfcomm_server rfcomm_server = { - .accept = &rfcomm_accept, -}; - -static int cmd_register(const struct shell *sh, size_t argc, char *argv[]) -{ - int ret; - - if (rfcomm_server.channel) { - shell_error(sh, "Already registered"); - return -ENOEXEC; - } - - rfcomm_server.channel = BT_RFCOMM_CHAN_SPP; - - ret = bt_rfcomm_server_register(&rfcomm_server); - if (ret < 0) { - shell_error(sh, "Unable to register channel %x", ret); - rfcomm_server.channel = 0U; - return -ENOEXEC; - } else { - shell_print(sh, "RFCOMM channel %u registered", - rfcomm_server.channel); - bt_sdp_register_service(&spp_rec); - } - - return 0; -} - -static int cmd_connect(const struct shell *sh, size_t argc, char *argv[]) -{ - uint8_t channel; - int err; - - if (!default_conn) { - shell_error(sh, "Not connected"); - return -ENOEXEC; - } - - channel = strtoul(argv[1], NULL, 16); - - err = bt_rfcomm_dlc_connect(default_conn, &rfcomm_dlc, channel); - if (err < 0) { - shell_error(sh, "Unable to connect to channel %d (err %u)", - channel, err); - } else { - shell_print(sh, "RFCOMM connection pending"); - } - - return err; -} - -static int cmd_send(const struct shell *sh, size_t argc, char *argv[]) -{ - uint8_t buf_data[DATA_MTU] = { [0 ... (DATA_MTU - 1)] = 0xff }; - int ret, len, count = 1; - struct net_buf *buf; - - if (argc > 1) { - count = strtoul(argv[1], NULL, 10); - } - - while (count--) { - buf = bt_rfcomm_create_pdu(&pool); - /* Should reserve one byte in tail for FCS */ - len = MIN(rfcomm_dlc.mtu, net_buf_tailroom(buf) - 1); - - net_buf_add_mem(buf, buf_data, len); - ret = bt_rfcomm_dlc_send(&rfcomm_dlc, buf); - if (ret < 0) { - shell_error(sh, "Unable to send: %d", -ret); - net_buf_unref(buf); - return -ENOEXEC; - } - } - - return 0; -} - -static int cmd_disconnect(const struct shell *sh, size_t argc, char *argv[]) -{ - int err; - - err = bt_rfcomm_dlc_disconnect(&rfcomm_dlc); - if (err) { - shell_error(sh, "Unable to disconnect: %u", -err); - } - - return err; -} - -static int cmd_send_rpn(const struct shell *sh, size_t argc, char *argv[]) -{ - int err; - struct bt_rfcomm_rpn rpn; - - if (!rfcomm_dlc.session) { - shell_error(sh, "Not connected"); - return -ENOEXEC; - } - - /* Initialize RPN with default values */ - memset(&rpn, 0, sizeof(rpn)); - - /* Set default values for RPN parameters */ - rpn.baud_rate = BT_RFCOMM_RPN_BAUD_RATE_9600; - rpn.line_settings = BT_RFCOMM_SET_LINE_SETTINGS( - BT_RFCOMM_RPN_DATA_BITS_8, - BT_RFCOMM_RPN_STOP_BITS_1, - BT_RFCOMM_RPN_PARITY_NONE); - rpn.flow_control = BT_RFCOMM_RPN_FLOW_NONE; - rpn.xon_char = BT_RFCOMM_RPN_XON_CHAR; - rpn.xoff_char = BT_RFCOMM_RPN_XOFF_CHAR; - rpn.param_mask = BT_RFCOMM_RPN_PARAM_MASK_ALL; - - shell_print(sh, "Sending RFCOMM RPN command with default settings"); - - err = bt_rfcomm_send_rpn_cmd(&rfcomm_dlc, &rpn); - if (err < 0) { - shell_error(sh, "Unable to send RPN command: %d", err); - return -ENOEXEC; - } - - shell_print(sh, "RFCOMM RPN command sent successfully"); - return 0; -} - -#define HELP_NONE "[none]" -#define HELP_ADDR_LE " " - -SHELL_STATIC_SUBCMD_SET_CREATE(rfcomm_cmds, - SHELL_CMD_ARG(register, NULL, HELP_NONE, cmd_register, 1, 0), - SHELL_CMD_ARG(connect, NULL, "", cmd_connect, 2, 0), - SHELL_CMD_ARG(disconnect, NULL, HELP_NONE, cmd_disconnect, 1, 0), - SHELL_CMD_ARG(send, NULL, "", cmd_send, 2, 0), - SHELL_CMD_ARG(rpn, NULL, "Send RPN command with default settings", cmd_send_rpn, 1, 0), - SHELL_SUBCMD_SET_END -); - -static int cmd_rfcomm(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(rfcomm, &rfcomm_cmds, "Bluetooth RFCOMM shell commands", - cmd_rfcomm, 1, 1); diff --git a/subsys/bluetooth/host/classic/shell/spp.c b/subsys/bluetooth/host/classic/shell/spp.c new file mode 100644 index 0000000000000..7f3e5aebdcb85 --- /dev/null +++ b/subsys/bluetooth/host/classic/shell/spp.c @@ -0,0 +1,781 @@ +/* + * Copyright 2025 Xiaomi Corporation + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "host/shell/bt.h" +#include "common/bt_shell_private.h" +#include "common/bt_str.h" + +#define HELP_NONE "[none]" +#define SPP_RFCOMM_MTU CONFIG_BT_RFCOMM_L2CAP_MTU +#define SDP_CLIENT_BUF_LEN 512 + +enum __packed spp_state { + /* SPP_STATE_DISCONNECTED: + * No active SPP connection. Resources are released or not allocated. + */ + SPP_STATE_DISCONNECTED, + /* SPP_STATE_CONNECTING: + * Connection is in progress (e.g., SDP discovery or RFCOMM setup). + * Not yet available for data transfer. + */ + SPP_STATE_CONNECTING, + /* SPP_STATE_CONNECTED: + * RFCOMM DLC is established and SPP is ready for data transfer. + */ + SPP_STATE_CONNECTED, + /* SPP_STATE_DISCONNECTING: + * Disconnection is in progress; cleanup and resource release pending. + */ + SPP_STATE_DISCONNECTING, +}; + +struct bt_spp; + +enum bt_spp_mode { + BT_SPP_MODE_UNKNOWN, + BT_SPP_MODE_UUID, + BT_SPP_MODE_CHANNEL, +}; + +/** @brief SPP profile application callback structure */ +struct bt_spp_ops { + /** SPP connection established callback + * + * If this callback is provided, it will be called when the Serial Port + * Profile (SPP) connection has been fully established and is ready for + * data transfer operations. + * + * At this point: + * - The RFCOMM channel is active + * - The SPP object is fully initialized + * - Data transmission via bt_spp_send() can begin + * + * @param conn ACL connection for the SPP link + * @param spp Valid SPP connection object + */ + void (*connected)(struct bt_conn *conn, struct bt_spp *spp); + + /** SPP connection terminated callback + * + * If this callback is provided, it will be called when the SPP connection + * has been terminated. This includes both graceful disconnections and any + * error conditions that caused the link to break. + * + * Important notes: + * - The SPP object will be freed immediately after this callback returns + * - After this callback, the SPP object becomes invalid + * - All references to this SPP object should be cleared + * + * @param spp SPP connection object (will be freed after callback) + */ + void (*disconnected)(struct bt_spp *spp); + + /** SPP data received callback + * + * If this callback is provided, it will be called when data has been + * received over the SPP connection. + * + * Important considerations: + * - The data buffer is only valid during the callback execution + * - If the data needs to be retained, it must be copied by the application + * - The implementation should process data quickly to avoid flow control issues + * + * @param spp SPP connection object that received the data + * @param buf Pointer to the received data buffer + */ + void (*recv)(struct bt_spp *spp, struct net_buf *buf); +}; + +struct bt_spp_client { + /** @internal SPP SDP discover params */ + struct bt_sdp_discover_params sdp_discover; +}; + +struct bt_spp_server { + /** Pointer to the SDP record exposed for this server. + * + * If NULL when registering, the registration will fail. The record must + * remain valid for the lifetime of the registration. + */ + struct bt_sdp_record *sdp_record; + + union { + /** UUID (service) to discovery remote SPP service */ + const struct bt_uuid *uuid; + /** Rfcomm channel to connect remote SPP server */ + uint8_t channel; + }; + + /* SPP mode: UUID or channel */ + enum bt_spp_mode mode; + + /** RFCOMM server instance. + * + * Populated/used by the stack to accept incoming RFCOMM connections + * for this SPP service. Do not modify directly from application code. + */ + struct bt_rfcomm_server rfcomm_server; +}; + +struct bt_spp { + /** @internal RFCOMM Data Link Connection (DLC) */ + struct bt_rfcomm_dlc rfcomm_dlc; + + union { + /** UUID (service) to discovery remote SPP service */ + const struct bt_uuid *uuid; + /** Rfcomm channel to connect remote SPP server */ + uint8_t channel; + }; + + enum bt_spp_mode mode; + + /** @internal ACL connection reference */ + struct bt_conn *acl_conn; + + /** @internal Connection state (atomic) */ + atomic_t state; + + /** @internal Role-specific context + * + * Union containing context specific to the role of this SPP instance: + * - client: SDP discovery parameters and UUID used when initiating a + * connection to a remote SPP service. + * - server: server registration and accept callback context for + * accepted incoming connections. + * + * Access the appropriate member only when the instance role is known. + */ + union { + struct bt_spp_client client; + struct bt_spp_server server; + }; +}; + +NET_BUF_POOL_FIXED_DEFINE(sdp_pool, CONFIG_BT_MAX_CONN, SDP_CLIENT_BUF_LEN, 8, NULL); +NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_MAX_CONN, + BT_L2CAP_BUF_SIZE(SPP_RFCOMM_MTU), 16, NULL); + +static uint8_t spp_channel; + +static union { + struct bt_uuid_16 u16; + struct bt_uuid_32 u32; + struct bt_uuid_128 u128; +} spp_uuid; + +static struct bt_sdp_attribute spp_attrs_channel[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_SERIAL_PORT_SVCLASS) + }, + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + ) + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + &spp_channel + }, + ) + }, + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_SERIAL_PORT_SVCLASS) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0102) + }, + ) + }, + ) + ), + BT_SDP_SERVICE_NAME("Serial Port"), +}; + +static struct bt_sdp_record spp_rec_channel = BT_SDP_RECORD(spp_attrs_channel); + +static uint8_t spp_uuid_buf[BT_UUID_SIZE_128]; + +static struct bt_sdp_attribute spp_attrs_uuid128[] = { + BT_SDP_NEW_SERVICE, + BT_SDP_LIST( + BT_SDP_ATTR_SVCLASS_ID_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 17), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID128), + spp_uuid_buf + }, + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROTO_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 12), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 3), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_L2CAP) + }, + ) + }, + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 5), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_PROTO_RFCOMM) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT8), + &spp_channel + }, + ) + }, + ) + ), + BT_SDP_LIST( + BT_SDP_ATTR_PROFILE_DESC_LIST, + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 8), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE_VAR(BT_SDP_SEQ8, 6), + BT_SDP_DATA_ELEM_LIST( + { + BT_SDP_TYPE_SIZE(BT_SDP_UUID16), + BT_SDP_ARRAY_16(BT_SDP_SERIAL_PORT_SVCLASS) + }, + { + BT_SDP_TYPE_SIZE(BT_SDP_UINT16), + BT_SDP_ARRAY_16(0x0102) + }, + ) + }, + ) + ), + BT_SDP_SERVICE_NAME("Serial Port"), +}; + +static struct bt_sdp_record spp_rec_uuid128 = BT_SDP_RECORD(spp_attrs_uuid128); + +static void bt_spp_connected(struct bt_rfcomm_dlc *dlci) +{ + struct bt_spp *spp = CONTAINER_OF(dlci, struct bt_spp, rfcomm_dlc); + + atomic_set(&spp->state, SPP_STATE_CONNECTED); + + bt_shell_print("SPP:%p, conn:%p, channel:%d connected ", spp, spp->acl_conn, spp->channel); +} + +static void bt_spp_disconnected(struct bt_rfcomm_dlc *dlci) +{ + struct bt_spp *spp = CONTAINER_OF(dlci, struct bt_spp, rfcomm_dlc); + + atomic_set(&spp->state, SPP_STATE_DISCONNECTED); + + bt_shell_print("SPP:%p disconnected", spp); +} + +static void bt_spp_recv(struct bt_rfcomm_dlc *dlci, struct net_buf *buf) +{ + struct bt_spp *spp = CONTAINER_OF(dlci, struct bt_spp, rfcomm_dlc); + + bt_shell_print("SPP:%p, data len:%d", spp, buf->len); + + bt_shell_hexdump(buf->data, buf->len); +} + +static struct bt_spp default_spp; + +static struct bt_rfcomm_dlc_ops spp_rfcomm_ops = { + .recv = bt_spp_recv, + .connected = bt_spp_connected, + .disconnected = bt_spp_disconnected, +}; + +static int bt_spp_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) +{ + struct bt_spp_server *spp_srv = + CONTAINER_OF(server, struct bt_spp_server, rfcomm_server); + + if (atomic_get(&default_spp.state) != SPP_STATE_DISCONNECTED) { + bt_shell_print("SPP already connected or connecting"); + return -EALREADY; + } + + default_spp.acl_conn = bt_conn_ref(conn); + default_spp.rfcomm_dlc.ops = &spp_rfcomm_ops; + default_spp.rfcomm_dlc.mtu = SPP_RFCOMM_MTU; + default_spp.rfcomm_dlc.required_sec_level = BT_SECURITY_L2; + *dlc = &default_spp.rfcomm_dlc; + + bt_shell_print("SPP accept a conn %p, mode:%d", conn, spp_srv->mode); + return 0; +} + +static int bt_spp_server_register(struct bt_spp_server *server) +{ + int err; + + server->rfcomm_server.accept = bt_spp_accept; + + err = bt_rfcomm_server_register(&server->rfcomm_server); + if (err < 0) { + bt_shell_print("fail to register rfcomm service, err:%d", err); + return err; + } + + err = bt_sdp_register_service(server->sdp_record); + if (err < 0) { + bt_shell_print("fail to register sdp service, err:%d", err); + return err; + } + + return 0; +} + +static int spp_uuid_register(const struct shell *sh, int32_t argc, char *argv[]) +{ + int err; + const char *uuid; + struct bt_uuid_128 uuid128; + + uuid = argv[2]; + + if (!bt_str_to_uuid(uuid, (struct bt_uuid *)&uuid128)) { + bt_shell_print("fail to parse uuid"); + return -ENOEXEC; + } + + if (uuid128.uuid.type != BT_UUID_TYPE_128) { + bt_shell_print("only support 128bit uuid"); + return -ENOEXEC; + } + + sys_memcpy_swap(spp_uuid_buf, uuid128.val, BT_UUID_SIZE_128); + + default_spp.server.sdp_record = &spp_rec_uuid128; + default_spp.server.rfcomm_server.channel = 0U; + + err = bt_spp_server_register(&default_spp.server); + if (err < 0) { + shell_print(sh, "fail to register SPP service, err:%d", err); + default_spp.server.sdp_record = NULL; + default_spp.server.rfcomm_server.channel = 0U; + return -ENOEXEC; + } + + spp_channel = default_spp.server.rfcomm_server.channel; + + shell_print(sh, "register SPP srv uuid:%s(channel:%d) success", uuid, spp_channel); + + return 0; +} + +static int spp_channel_register(const struct shell *sh, int32_t argc, char *argv[]) +{ + int err = -1; + + spp_channel = strtoul(argv[2], NULL, 16); + + default_spp.server.sdp_record = &spp_rec_channel; + default_spp.server.rfcomm_server.channel = spp_channel; + + err = bt_spp_server_register(&default_spp.server); + if (err < 0) { + shell_print(sh, "fail to register SPP service, err:%d", err); + default_spp.server.sdp_record = NULL; + default_spp.server.rfcomm_server.channel = 0U; + return -ENOEXEC; + } + + shell_print(sh, "register SPP srv channel:%d success", spp_channel); + + return 0; +} + +static int cmd_register_server(const struct shell *sh, int32_t argc, char *argv[]) +{ + int err = -1; + const char *type; + + if (argc < 3) { + return -ENOEXEC; + } + + type = argv[1]; + + if (!strcmp(type, "channel")) { + err = spp_channel_register(sh, argc, argv); + } else if (!strcmp(type, "uuid")) { + err = spp_uuid_register(sh, argc, argv); + } else { + shell_print(sh, "invalid type:%s", type); + return -ENOEXEC; + } + + return err; +} + +static int bt_spp_connect_rfcomm(struct bt_conn *conn, struct bt_spp *spp) +{ + int err; + + spp->acl_conn = bt_conn_ref(conn); + spp->rfcomm_dlc.ops = &spp_rfcomm_ops; + spp->rfcomm_dlc.mtu = SPP_RFCOMM_MTU; + spp->rfcomm_dlc.required_sec_level = BT_SECURITY_L2; + + err = bt_rfcomm_dlc_connect(conn, &spp->rfcomm_dlc, spp->channel); + if (err < 0) { + bt_shell_print("SPP rfcomm dlc connect fail, err:%d", err); + return err; + } + + return 0; +} + + +static uint8_t sdp_discover_cb(struct bt_conn *conn, struct bt_sdp_client_result *response, + const struct bt_sdp_discover_params *params) +{ + uint16_t channel; + int err; + struct bt_spp *spp = CONTAINER_OF(params, struct bt_spp, client.sdp_discover); + + if (response == NULL) { + bt_shell_print("SPP response is null"); + return BT_SDP_DISCOVER_UUID_CONTINUE; + } + + if (response->resp_buf == NULL) { + bt_shell_print("SPP sdp resp_buf is null"); + goto exit; + } + + err = bt_sdp_get_proto_param(response->resp_buf, BT_SDP_PROTO_RFCOMM, &channel); + if (err < 0) { + bt_shell_print("SPP sdp get proto fail"); + goto exit; + } + + bt_shell_print("SPP SDP record channel:%d found for uuid:%s", channel, + bt_uuid_str(params->uuid)); + + spp->channel = channel; + err = bt_spp_connect_rfcomm(conn, spp); + if (err < 0) { + bt_shell_print("SPP connect fail, err:%d", err); + goto exit; + } + + return BT_SDP_DISCOVER_UUID_STOP; + +exit: + return BT_SDP_DISCOVER_UUID_STOP; +} + +static int bt_spp_connect(struct bt_conn *conn, struct bt_spp *spp) +{ + int err; + + if (atomic_get(&spp->state) != SPP_STATE_DISCONNECTED) { + bt_shell_print("SPP was not in disconnected state"); + return -EALREADY; + } + + if (spp->mode == BT_SPP_MODE_CHANNEL) { + err = bt_spp_connect_rfcomm(conn, spp); + if (err < 0) { + bt_shell_print("SPP connect fail, err:%d", err); + return err; + } + } else if (spp->mode == BT_SPP_MODE_UUID) { + spp->client.sdp_discover.type = BT_SDP_DISCOVER_SERVICE_SEARCH_ATTR; + spp->client.sdp_discover.func = sdp_discover_cb; + spp->client.sdp_discover.pool = &sdp_pool; + spp->client.sdp_discover.uuid = spp->uuid; + + err = bt_sdp_discover(conn, &spp->client.sdp_discover); + if (err < 0) { + bt_shell_print("SPP sdp discover fail, err:%d", err); + return err; + } + } + + atomic_set(&spp->state, SPP_STATE_CONNECTING); + return 0; +} + +static int spp_uuid_connect(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + size_t len; + + len = strlen(argv[2]); + + if (len == (BT_UUID_SIZE_16 * 2)) { + uint16_t val; + + spp_uuid.u16.uuid.type = BT_UUID_TYPE_16; + hex2bin(argv[2], len, (uint8_t *)&val, sizeof(val)); + spp_uuid.u16.val = sys_be16_to_cpu(val); + } else if (len == (BT_UUID_SIZE_32 * 2)) { + uint32_t val; + + spp_uuid.u32.uuid.type = BT_UUID_TYPE_32; + hex2bin(argv[2], len, (uint8_t *)&val, sizeof(val)); + spp_uuid.u32.val = sys_be32_to_cpu(val); + } else if (len == (BT_UUID_SIZE_128 * 2)) { + uint8_t uuid128[BT_UUID_SIZE_128]; + + spp_uuid.u128.uuid.type = BT_UUID_TYPE_128; + hex2bin(argv[2], len, &uuid128[0], sizeof(uuid128)); + sys_memcpy_swap(spp_uuid.u128.val, uuid128, sizeof(uuid128)); + } else { + shell_error(sh, "Invalid UUID"); + return -ENOEXEC; + } + + default_spp.mode = BT_SPP_MODE_UUID; + default_spp.uuid = (const struct bt_uuid *)&spp_uuid; + + err = bt_spp_connect(default_conn, &default_spp); + if (err < 0) { + shell_error(sh, "fail to connect SPP device, err:%d", err); + default_spp.mode = BT_SPP_MODE_UNKNOWN; + default_spp.uuid = NULL; + return -ENOEXEC; + } + + shell_print(sh, "SPP connect uuid:%s ", bt_uuid_str((const struct bt_uuid *)&spp_uuid)); + return 0; +} + +static int spp_channel_connect(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + uint8_t channel; + + channel = strtoul(argv[2], NULL, 16); + + default_spp.mode = BT_SPP_MODE_CHANNEL; + default_spp.channel = channel; + + err = bt_spp_connect(default_conn, &default_spp); + if (err < 0) { + shell_error(sh, "fail to connect SPP device, err:%d", err); + default_spp.mode = BT_SPP_MODE_UNKNOWN; + default_spp.uuid = NULL; + return -ENOEXEC; + } + + shell_print(sh, "SPP connect channel:%d", channel); + return 0; +} + +static int cmd_spp_connect(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + const char *type; + + if (argc < 3) { + return -ENOEXEC; + } + + if (default_conn == NULL) { + shell_error(sh, "please connect bt first"); + return -ENOEXEC; + } + + type = argv[1]; + + if (!strcmp(type, "channel")) { + err = spp_channel_connect(sh, argc, argv); + } else if (!strcmp(type, "uuid")) { + err = spp_uuid_connect(sh, argc, argv); + } else { + shell_print(sh, "invalid type:%s", type); + return -ENOEXEC; + } + + return err; +} + +static int bt_spp_send(struct bt_spp *spp, struct net_buf *buf) +{ + int err; + + if (atomic_get(&spp->state) != SPP_STATE_CONNECTED) { + bt_shell_print("Cannot send data while not connected"); + return -ENOTCONN; + } + + err = bt_rfcomm_dlc_send(&spp->rfcomm_dlc, buf); + if (err < 0) { + bt_shell_print("rfcomm unable to send: %d", err); + return err; + } + + return 0; +} + +static int cmd_spp_send(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t data[SPP_RFCOMM_MTU]; + int len = SPP_RFCOMM_MTU; + int err; + static struct net_buf *tx_buf; + + memset(data, 0xff, sizeof(data)); + + tx_buf = bt_rfcomm_create_pdu(&tx_pool); + if (tx_buf == NULL) { + shell_error(sh, "SPP tx_buf is NULL"); + return -ENOEXEC; + } + + if (argc > 1) { + len = strtoul(argv[1], NULL, 10); + } + + len = MIN(SPP_RFCOMM_MTU, len); + shell_print(sh, "Send data len:%d", len); + + net_buf_add_mem(tx_buf, data, len); + + err = bt_spp_send(&default_spp, tx_buf); + if (err < 0) { + shell_error(sh, "fail to send data, err:%d", err); + net_buf_unref(tx_buf); + return -ENOEXEC; + } + + return 0; +} + +static int bt_spp_disconnect(struct bt_spp *spp) +{ + int err; + + if (spp->acl_conn == NULL) { + bt_shell_print("SPP conn invalid"); + return -EINVAL; + } + + if (atomic_get(&spp->state) != SPP_STATE_CONNECTED) { + bt_shell_print("Cannot disconnect SPP connection while not connected"); + return -ENOTCONN; + } + + err = bt_rfcomm_dlc_disconnect(&spp->rfcomm_dlc); + if (err < 0) { + bt_shell_print("bt rfcomm disconnect err:%d", err); + return err; + } + + atomic_set(&spp->state, SPP_STATE_DISCONNECTING); + return 0; +} + +static int cmd_spp_disconnect(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + err = bt_spp_disconnect(&default_spp); + if (err < 0) { + shell_error(sh, "fail to disconnect SPP, err:%d", err); + return -ENOEXEC; + } + + shell_print(sh, "SPP disconnecting"); + + return 0; +} + +SHELL_STATIC_SUBCMD_SET_CREATE( + spp_cmds, + SHELL_CMD_ARG(register, NULL, + "register server [value]" + "[uuid:uuid 16bit or 32bit or 128bit]", + cmd_register_server, 3, 0), + SHELL_CMD_ARG(connect, NULL, + "connect [channel:value] " + "[uuid128:00001101-0000-1000-8000-00805F9B34FB]", + cmd_spp_connect, 3, 0), + SHELL_CMD_ARG(send, NULL, "send [length of packet(s)]", cmd_spp_send, 2, 1), + SHELL_CMD_ARG(disconnect, NULL, HELP_NONE, cmd_spp_disconnect, 1, 0), SHELL_SUBCMD_SET_END); + +static int cmd_spp(const struct shell *sh, size_t argc, char **argv) +{ + if (argc == 1) { + shell_help(sh); + /* sh returns 1 when help is printed */ + return SHELL_CMD_HELP_PRINTED; + } + + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + + return -ENOEXEC; +} + +SHELL_CMD_ARG_REGISTER(spp, &spp_cmds, "Bluetooth SPP sh commands", cmd_spp, 1, 1); diff --git a/subsys/bluetooth/host/uuid.c b/subsys/bluetooth/host/uuid.c index 2d48f2a2a9f68..439b31cf23eca 100644 --- a/subsys/bluetooth/host/uuid.c +++ b/subsys/bluetooth/host/uuid.c @@ -9,6 +9,7 @@ #include #include #include +#include #include #include @@ -130,3 +131,45 @@ void bt_uuid_to_str(const struct bt_uuid *uuid, char *str, size_t len) return; } } + +bool bt_str_to_uuid(const char *str, struct bt_uuid *uuid) +{ + switch (strlen(str)) { + case BT_UUID_SIZE_16 * 2: { + uint16_t *p = &BT_UUID_16(uuid)->val; + + if (sscanf(str, "%04hx", p) != 1) { + return false; + } + + uuid->type = BT_UUID_TYPE_16; + return true; + } + case BT_UUID_SIZE_32 * 2: { + uint32_t *p = &BT_UUID_32(uuid)->val; + + if (sscanf(str, "%08x", p) != 1) { + return false; + } + + uuid->type = BT_UUID_TYPE_32; + return true; + } + case BT_UUID_STR_LEN - 1: { + uint8_t *p = BT_UUID_128(uuid)->val; + + if (sscanf(str, + "%02hhx%02hhx%02hhx%02hhx-%02hhx%02hhx-%02hhx%02hhx" + "-%02hhx%02hhx-%02hhx%02hhx%02hhx%02hhx%02hhx%02hhx", + &p[0], &p[1], &p[2], &p[3], &p[4], &p[5], &p[6], &p[7], &p[8], &p[9], + &p[10], &p[11], &p[12], &p[13], &p[14], &p[15]) != BT_UUID_SIZE_128) { + return false; + } + + uuid->type = BT_UUID_TYPE_128; + return true; + } + default: + return false; + } +}