From afbc604a5e0db902f57dd10ab7563af0c83ad3ab Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 26 Dec 2024 12:30:55 +0800 Subject: [PATCH 01/11] Bluetooth: L2CAP_BR: Support dynamic PSM allocation In the function `bt_l2cap_br_server_register()`, the PSM cannot be dynamic allocated. And only pre-set PSM is supported. Improve the function `bt_l2cap_br_server_register()` to support the dynamic PSM allocation if the passed PSM is zero. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/l2cap.h | 27 +++++++++- subsys/bluetooth/host/classic/l2cap_br.c | 65 ++++++++++++++++++++---- 2 files changed, 82 insertions(+), 10 deletions(-) diff --git a/include/zephyr/bluetooth/l2cap.h b/include/zephyr/bluetooth/l2cap.h index 8358d62c309f..c510ac26eebe 100644 --- a/include/zephyr/bluetooth/l2cap.h +++ b/include/zephyr/bluetooth/l2cap.h @@ -490,7 +490,7 @@ struct bt_l2cap_chan_ops { struct bt_l2cap_server { /** @brief Server PSM. * - * Possible values: + * For LE, possible values: * 0 A dynamic value will be auto-allocated when * bt_l2cap_server_register() is called. * @@ -500,6 +500,22 @@ struct bt_l2cap_server { * application before server registration (not * recommended however), or auto-allocated by the * stack if the app gave 0 as the value. + * + * For BR, possible values: + * + * The PSM field is at least two octets in length. All PSM values shall have the least + * significant bit of the most significant octet equal to 0 and the least significant bit + * of all other octets equal to 1. + * + * 0 A dynamic value will be auto-allocated when + * bt_l2cap_br_server_register() is called. + * + * 0x0001-0x0eff Standard, Bluetooth SIG-assigned fixed values. + * + * > 0x1000 Dynamically allocated. May be pre-set by the + * application before server registration (not + * recommended however), or auto-allocated by the + * stack if the app gave 0 as the value. */ uint16_t psm; @@ -556,6 +572,15 @@ int bt_l2cap_server_register(struct bt_l2cap_server *server); * the accept() callback which in case of success shall allocate the channel * structure to be used by the new connection. * + * For fixed, SIG-assigned PSMs (in the range 0x0001-0x0eff) the PSM should + * be assigned to server->psm before calling this API. For dynamic PSMs + * (in the range 0x1000-0xffff) server->psm may be pre-set to a given value + * (this is however not recommended) or be left as 0, in which case upon + * return a newly allocated value will have been assigned to it. For + * dynamically allocated values the expectation is that it's exposed through + * a SDP record, and that's how L2CAP clients discover how to connect to + * the server. + * * @param server Server structure. * * @return 0 in case of success or negative value in case of error. diff --git a/subsys/bluetooth/host/classic/l2cap_br.c b/subsys/bluetooth/host/classic/l2cap_br.c index 405a971cfef5..2fdfc2b75120 100644 --- a/subsys/bluetooth/host/classic/l2cap_br.c +++ b/subsys/bluetooth/host/classic/l2cap_br.c @@ -37,6 +37,8 @@ LOG_MODULE_REGISTER(bt_l2cap_br, CONFIG_BT_L2CAP_LOG_LEVEL); #define L2CAP_BR_PSM_START 0x0001 #define L2CAP_BR_PSM_END 0xffff +#define L2CAP_BR_PSM_DYN_START 0x1000 +#define L2CAP_BR_PSM_DYN_END L2CAP_BR_PSM_END #define L2CAP_BR_CID_DYN_START 0x0040 #define L2CAP_BR_CID_DYN_END 0xffff @@ -1151,14 +1153,46 @@ static void l2cap_br_conf_rsp(struct bt_l2cap_br *l2cap, uint8_t ident, } } -int bt_l2cap_br_server_register(struct bt_l2cap_server *server) +static int bt_l2cap_br_allocate_psm(void) { - if (server->psm < L2CAP_BR_PSM_START || !server->accept) { - return -EINVAL; + static uint16_t allocated_psm = L2CAP_BR_PSM_DYN_START; + + if (allocated_psm < L2CAP_BR_PSM_DYN_END) { + allocated_psm = allocated_psm + 1; + } else { + goto failed; } - /* PSM must be odd and lsb of upper byte must be 0 */ - if ((server->psm & 0x0101) != 0x0001) { + for (; allocated_psm <= L2CAP_BR_PSM_DYN_END; allocated_psm++) { + /* Bluetooth Core Specification Version 6.0 | Vol 3, Part A, section 4.2 + * + * The PSM field is at least two octets in length. All PSM values shall have + * the least significant bit of the most significant octet equal to 0 and the + * least significant bit of all other octets equal to 1. + */ + if ((allocated_psm & 0x0101) != 0x0001) { + continue; + } + + if (l2cap_br_server_lookup_psm(allocated_psm)) { + LOG_DBG("PSM 0x%04x has been used", allocated_psm); + continue; + } + + LOG_DBG("Allocated PSM 0x%04x for new server", allocated_psm); + return allocated_psm; + } + +failed: + LOG_WRN("No free dynamic PSMs available"); + return -EADDRNOTAVAIL; +} + +int bt_l2cap_br_server_register(struct bt_l2cap_server *server) +{ + int err; + + if (!server->accept) { return -EINVAL; } @@ -1169,10 +1203,23 @@ int bt_l2cap_br_server_register(struct bt_l2cap_server *server) server->sec_level = BT_SECURITY_L1; } - /* Check if given PSM is already in use */ - if (l2cap_br_server_lookup_psm(server->psm)) { - LOG_DBG("PSM already registered"); - return -EADDRINUSE; + if (!server->psm) { + err = bt_l2cap_br_allocate_psm(); + if (err < 0) { + return err; + } + server->psm = (uint16_t)err; + } else { + /* PSM must be odd and lsb of upper byte must be 0 */ + if ((server->psm & 0x0101) != 0x0001) { + return -EINVAL; + } + + /* Check if given PSM is already in use */ + if (l2cap_br_server_lookup_psm(server->psm)) { + LOG_DBG("PSM already registered"); + return -EADDRINUSE; + } } LOG_DBG("PSM 0x%04x", server->psm); From d455f02157d9fcf70961c72bddca8bd74ef4226b Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 26 Dec 2024 14:36:20 +0800 Subject: [PATCH 02/11] Bluetooth: rfcomm: Support dynamic channel allocation In the function `bt_rfcomm_server_register()`, the channel cannot be dynamic allocated. And only pre-set channel is supported. Improve the function `bt_rfcomm_server_register()` to support the dynamic channel allocation if the passed channel is zero. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/rfcomm.h | 12 +++++++++- subsys/bluetooth/host/classic/rfcomm.c | 29 ++++++++++++++++++----- 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/include/zephyr/bluetooth/classic/rfcomm.h b/include/zephyr/bluetooth/classic/rfcomm.h index 1d3056643bd0..8b3846617c7c 100644 --- a/include/zephyr/bluetooth/classic/rfcomm.h +++ b/include/zephyr/bluetooth/classic/rfcomm.h @@ -31,6 +31,7 @@ enum { BT_RFCOMM_CHAN_HSP_AG, BT_RFCOMM_CHAN_HSP_HS, BT_RFCOMM_CHAN_SPP, + BT_RFCOMM_CHAN_DYNAMIC_START, }; struct bt_rfcomm_dlc; @@ -109,7 +110,16 @@ struct bt_rfcomm_dlc { }; struct bt_rfcomm_server { - /** Server Channel */ + /** Server Channel + * + * Possible values: + * 0 A dynamic value will be auto-allocated when bt_rfcomm_server_register() is + * called. + * + * 0x01 - 0x1e Dynamically allocated. May be pre-set by the application before server + * registration (not recommended however), or auto-allocated by the stack + * if the 0 is passed. + */ uint8_t channel; /** Server accept callback diff --git a/subsys/bluetooth/host/classic/rfcomm.c b/subsys/bluetooth/host/classic/rfcomm.c index 45e2a50b7e19..d73c834f907f 100644 --- a/subsys/bluetooth/host/classic/rfcomm.c +++ b/subsys/bluetooth/host/classic/rfcomm.c @@ -199,15 +199,32 @@ rfcomm_sessions_lookup_bt_conn(struct bt_conn *conn) int bt_rfcomm_server_register(struct bt_rfcomm_server *server) { - if (server->channel < RFCOMM_CHANNEL_START || - server->channel > RFCOMM_CHANNEL_END || !server->accept) { + if (server->channel > RFCOMM_CHANNEL_END || !server->accept) { return -EINVAL; } - /* Check if given channel is already in use */ - if (rfcomm_server_lookup_channel(server->channel)) { - LOG_DBG("Channel already registered"); - return -EADDRINUSE; + if (!server->channel) { + uint8_t chan = (uint8_t)BT_RFCOMM_CHAN_DYNAMIC_START; + + for (; chan <= RFCOMM_CHANNEL_END; chan++) { + /* Check if given channel is already in use */ + if (!rfcomm_server_lookup_channel(chan)) { + server->channel = chan; + LOG_DBG("Allocated channel 0x%02x for new server", chan); + break; + } + } + + if (!server->channel) { + LOG_WRN("No free dynamic rfcomm channels available"); + return -EADDRNOTAVAIL; + } + } else { + /* Check if given channel is already in use */ + if (rfcomm_server_lookup_channel(server->channel)) { + LOG_DBG("Channel already registered"); + return -EADDRINUSE; + } } LOG_DBG("Channel 0x%02x", server->channel); From fc2c130492a14850dbe0a844e3441f00f61c6e92 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 27 Dec 2024 20:51:50 +0800 Subject: [PATCH 03/11] Bluetooth: RFCOMM: Add a argument `server` to `bt_rfcomm_server.accept` In current implementation, the `accept` cannot be identified if the same one `accept` callback is passed by the upper layer. Similar with `accept` of `bt_l2cap_server.accept`, add a parameter `server` to the `bt_rfcomm_server.accept` callback. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/rfcomm.h | 4 +++- subsys/bluetooth/host/classic/rfcomm.c | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/include/zephyr/bluetooth/classic/rfcomm.h b/include/zephyr/bluetooth/classic/rfcomm.h index 8b3846617c7c..5701056524e6 100644 --- a/include/zephyr/bluetooth/classic/rfcomm.h +++ b/include/zephyr/bluetooth/classic/rfcomm.h @@ -128,11 +128,13 @@ struct bt_rfcomm_server { * authorization. * * @param conn The connection that is requesting authorization + * @param server Pointer to the server structure this callback relates to * @param dlc Pointer to received the allocated dlc * * @return 0 in case of success or negative value in case of error. */ - int (*accept)(struct bt_conn *conn, struct bt_rfcomm_dlc **dlc); + int (*accept)(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc); struct bt_rfcomm_server *_next; }; diff --git a/subsys/bluetooth/host/classic/rfcomm.c b/subsys/bluetooth/host/classic/rfcomm.c index d73c834f907f..5e87942d75dc 100644 --- a/subsys/bluetooth/host/classic/rfcomm.c +++ b/subsys/bluetooth/host/classic/rfcomm.c @@ -495,7 +495,7 @@ static struct bt_rfcomm_dlc *rfcomm_dlc_accept(struct bt_rfcomm_session *session return NULL; } - if (server->accept(session->br_chan.chan.conn, &dlc) < 0) { + if (server->accept(session->br_chan.chan.conn, server, &dlc) < 0) { LOG_DBG("Incoming connection rejected"); return NULL; } From a86c2138ab326d7f275a42b3be443a8db8b402e2 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 27 Dec 2024 20:54:30 +0800 Subject: [PATCH 04/11] Bluetooth: HFP_HF: Add a argument `server` to rfcomm_accept() Since a new argument `server` is added by `bt_rfcomm_server.accept`, add a argument `server` to function rfcomm_accept() to avoid building issue. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/hfp_hf.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/hfp_hf.c b/subsys/bluetooth/host/classic/hfp_hf.c index 4bde73e9399d..54d90b876f18 100644 --- a/subsys/bluetooth/host/classic/hfp_hf.c +++ b/subsys/bluetooth/host/classic/hfp_hf.c @@ -716,7 +716,8 @@ static void hfp_hf_sent(struct bt_rfcomm_dlc *dlc, int err) LOG_DBG("DLC %p sent cb (err %d)", dlc, err); } -static int bt_hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_dlc **dlc) +static int bt_hfp_hf_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) { int i; static struct bt_rfcomm_dlc_ops ops = { From a37b8f2bb007154d6249dda1e7e318d28869430b Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 27 Dec 2024 20:55:59 +0800 Subject: [PATCH 05/11] Bluetooth: Shell: Add a argument `server` to rfcomm_accept() Since a new argument `server` is added by `bt_rfcomm_server.accept`, add a argument `server` to function rfcomm_accept() to avoid building issue. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/shell/rfcomm.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/subsys/bluetooth/host/classic/shell/rfcomm.c b/subsys/bluetooth/host/classic/shell/rfcomm.c index 61782e3da9a3..feba34ecf7d5 100644 --- a/subsys/bluetooth/host/classic/shell/rfcomm.c +++ b/subsys/bluetooth/host/classic/shell/rfcomm.c @@ -126,7 +126,8 @@ static struct bt_rfcomm_dlc rfcomm_dlc = { .mtu = 30, }; -static int rfcomm_accept(struct bt_conn *conn, struct bt_rfcomm_dlc **dlc) +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); From b2978ea2501b439e5f5c22f40496cc952a1c04dc Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 2 Jan 2025 10:07:53 +0800 Subject: [PATCH 06/11] Bluetooth: GOEP: Enable GOEP feature Add a Kconfig BT_GOEP to control the GOEP feature. Implement the GOEP protocol and transport, both for GOEP 1.1 and GOEP 2.x. For GOEP transport, OBEX over RFCOMM, and OBEX over L2CAP are supported. For GOEP protocol, `put`, `get`, `abort`, `setpath`, and `action` are supported. And only one operation can be processed at the same time. The feature `Reliable Session` is unsupported. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/goep.h | 352 ++ include/zephyr/bluetooth/classic/obex.h | 1447 ++++++++ subsys/bluetooth/Kconfig.logging | 4 + subsys/bluetooth/host/classic/CMakeLists.txt | 6 + subsys/bluetooth/host/classic/Kconfig | 16 + subsys/bluetooth/host/classic/goep.c | 604 ++++ subsys/bluetooth/host/classic/obex.c | 3210 +++++++++++++++++ subsys/bluetooth/host/classic/obex_internal.h | 62 + 8 files changed, 5701 insertions(+) create mode 100644 include/zephyr/bluetooth/classic/goep.h create mode 100644 include/zephyr/bluetooth/classic/obex.h create mode 100644 subsys/bluetooth/host/classic/goep.c create mode 100644 subsys/bluetooth/host/classic/obex.c create mode 100644 subsys/bluetooth/host/classic/obex_internal.h diff --git a/include/zephyr/bluetooth/classic/goep.h b/include/zephyr/bluetooth/classic/goep.h new file mode 100644 index 000000000000..2f189e5e90ef --- /dev/null +++ b/include/zephyr/bluetooth/classic/goep.h @@ -0,0 +1,352 @@ +/* goep.h - Bluetooth Generic Object Exchange Profile handling */ + +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_GOEP_H_ +#define ZEPHYR_INCLUDE_BLUETOOTH_GOEP_H_ + +/** + * @brief Bluetooth APIs + * @defgroup bluetooth Bluetooth APIs + * @ingroup connectivity + * @{ + */ + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Generic Object Exchange Profile (GOEP) + * @defgroup bt_goep Generic Object Exchange Profile (GOEP) + * @since 1.0 + * @version 1.0.0 + * @ingroup bluetooth + * @{ + */ + +struct bt_goep; + +/** @brief GOEP transport operations structure. + * + * The object has to stay valid and constant for the lifetime of the GOEP server and client. + */ +struct bt_goep_transport_ops { + /** @brief GOEP transport connected callback + * + * If this callback is provided it will be called whenever the GOEP transport connection + * completes. + * + * @param conn The ACL connection + * @param goep The GOEP object that has been connected + */ + void (*connected)(struct bt_conn *conn, struct bt_goep *goep); + + /** @brief GOEP transport disconnected callback + * + * If this callback is provided it will be called whenever the GOEP transport is + * disconnected, including when a connection gets rejected. + * + * @param goep The GOEP object that has been Disconnected + */ + void (*disconnected)(struct bt_goep *goep); +}; + +/** @brief Life-span states of GOEP transport. + * + * Used only by internal APIs dealing with setting GOEP to proper transport state depending on + * operational context. + * + * GOEP transport enters the @ref BT_GOEP_TRANSPORT_CONNECTING state upon + * @ref bt_goep_transport_l2cap_connect, @ref bt_goep_transport_rfcomm_connect or upon returning + * from @ref bt_goep_transport_rfcomm_server::accept and bt_goep_transport_l2cap_server::accept. + * + * When GOEP transport leaves the @ref BT_GOEP_TRANSPORT_CONNECTING state and enters the @ref + * BT_GOEP_TRANSPORT_CONNECTED, @ref bt_goep_transport_ops::connected is called. + * + * When GOEP transport enters the @ref BT_GOEP_TRANSPORT_DISCONNECTED from other states, + * @ref bt_goep_transport_ops::disconnected is called. + */ +enum __packed bt_goep_transport_state { + /** GOEP disconnected */ + BT_GOEP_TRANSPORT_DISCONNECTED, + /** GOEP in connecting state */ + BT_GOEP_TRANSPORT_CONNECTING, + /** GOEP ready for upper layer traffic on it */ + BT_GOEP_TRANSPORT_CONNECTED, + /** GOEP in disconnecting state */ + BT_GOEP_TRANSPORT_DISCONNECTING, +}; + +struct bt_goep { + /** @internal To be used for transport */ + union { + struct bt_rfcomm_dlc dlc; + struct bt_l2cap_br_chan chan; + } _transport; + + /** @internal Peer GOEP Version + * + * false - Peer supports GOEP v1.1. The GOEP transport is based on RFCOMM. + * `dlc` is used as transport. + * + * true - peer supports GOEP v2.0 or later. The GOEP transport is based on L2CAP. + * `chan` is used as transport. + */ + bool _goep_v2; + + /** @internal connection handle */ + struct bt_conn *_acl; + + /** @internal Saves the current transport state, @ref bt_goep_transport_state */ + atomic_t _state; + + /** @brief GOEP transport operations + * + * The upper layer should pass the operations to `transport_ops` when + * providing the GOEP structure. + */ + const struct bt_goep_transport_ops *transport_ops; + + /** @brief OBEX object */ + struct bt_obex obex; +}; + +/** + * @defgroup bt_goep_transport_rfcomm GOEP transport RFCOMM + * @ingroup bt_goep + * @{ + */ + +/** @brief GOEP Server structure GOEP v1.1. */ +struct bt_goep_transport_rfcomm_server { + /** @brief RFCOMM server for GOEP v1.1 + * + * The @ref bt_goep_transport_rfcomm_server::rfcomm is used to register a rfcomm server. + * + * The @ref bt_rfcomm_server::channel needs to be passed with a pre-set channel (not + * recommended however), Or give a value `0` to make the channel be auto-allocated when + * @ref bt_goep_transport_rfcomm_server_register is called. + * The @ref bt_rfcomm_server::accept should be not used by GOEP application, and instead + * the @ref bt_goep_transport_rfcomm_server::accept should be used. + */ + struct bt_rfcomm_server rfcomm; + + /** @brief Server accept callback + * + * This callback is called whenever a new incoming GOEP connection requires + * authorization. + * + * @warning It is the responsibility of the caller to zero out the parent of the GOEP + * object. + * + * @param conn The connection that is requesting authorization + * @param server Pointer to the server structure this callback relates to + * @param goep Pointer to received the allocated GOEP object + * + * @return 0 in case of success or negative value in case of error. + * @return -ENOMEM if no available space for new object. + * @return -EACCES if application did not authorize the connection. + * @return -EPERM if encryption key size is too short. + */ + int (*accept)(struct bt_conn *conn, struct bt_goep_transport_rfcomm_server *server, + struct bt_goep **goep); + + sys_snode_t node; +}; + +/** @brief Register GOEP RFCOMM server. + * + * Register GOEP server for a RFCOMM channel @ref bt_rfcomm_server::channel, each new connection + * is authorized using the @ref bt_goep_transport_rfcomm_server::accept callback which in case of + * success shall allocate the GOEP structure @ref bt_goep to be used by the new GOEP connection. + * + * @ref bt_rfcomm_server::channel may be pre-set to a given value (not recommended however). Or be + * left as 0, in which case the channel will be auto-allocated by RFCOMM. + * + * @param server Server structure + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_goep_transport_rfcomm_server_register(struct bt_goep_transport_rfcomm_server *server); + +/** @brief Connect GOEP transport over RFCOMM + * + * Connect GOEP transport over RFCOMM, once the connection is completed, the callback + * @ref bt_goep_transport_ops::connected is called. If the connection is rejected, + * @ref bt_goep_transport_ops::disconnected callback is called instead. + * The GOEP object is passed (over an address of it) as second parameter, application should + * create transport dedicated GOEP object @ref bt_goep. Then pass to this API the location + * (address). + * Before calling the API, @ref bt_obex::client_ops and @ref bt_goep::transport_ops should + * be initialized with valid address of type @ref bt_obex_client_ops object and + * @ref bt_goep_transport_ops object. The field `mtu` of @ref bt_obex::rx could be passed with + * valid value. Or set it to zero, the mtu will be calculated according to + * @kconfig{CONFIG_BT_GOEP_RFCOMM_MTU}. + * The RFCOMM channel is passed as third parameter. It is the RFCOMM channel of RFCOMM server of + * peer device. + * + * @warning It is the responsibility of the caller to zero out the parent of the GOEP object. + * + * @param conn Connection object + * @param goep GOEP object + * @param channel RFCOMM channel to connect to + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_goep_transport_rfcomm_connect(struct bt_conn *conn, struct bt_goep *goep, uint8_t channel); + +/** @brief Disconnect GOEP transport from RFCOMM + * + * Disconnect GOEP RFCOMM transport. + * + * @param goep GOEP object + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_goep_transport_rfcomm_disconnect(struct bt_goep *goep); + +/** @} */ + +/** + * @defgroup bt_goep_transport_l2cap GOEP transport L2CAP + * @ingroup bt_goep + * @{ + */ + +/** @brief GOEP Server structure for GOEP v2.0 and later. */ +struct bt_goep_transport_l2cap_server { + /** @brief L2CAP PSM for GOEP v2.0 and later + * + * The @ref bt_goep_transport_l2cap_server::l2cap is used to register a l2cap server. + * + * The @ref bt_l2cap_server::psm needs to be passed with a pre-set psm (not recommended + * however), Or give a value `0` to make the psm be auto-allocated when + * @ref bt_goep_transport_l2cap_server_register is called. + * The @ref bt_l2cap_server::sec_level is used to require minimum security level for l2cap + * server. + * The @ref bt_l2cap_server::accept should be not used by GOEP application, and instead + * the @ref bt_goep_transport_l2cap_server::accept will be used. + */ + struct bt_l2cap_server l2cap; + + /** @brief Server accept callback + * + * This callback is called whenever a new incoming GOEP connection requires + * authorization. + * + * @warning It is the responsibility of the caller to zero out the parent of the GOEP + * object. + * + * @param conn The connection that is requesting authorization + * @param server Pointer to the server structure this callback relates to + * @param goep Pointer to received the allocated GOEP object + * + * @return 0 in case of success or negative value in case of error. + * @return -ENOMEM if no available space for new object. + * @return -EACCES if application did not authorize the connection. + * @return -EPERM if encryption key size is too short. + */ + int (*accept)(struct bt_conn *conn, struct bt_goep_transport_l2cap_server *server, + struct bt_goep **goep); + + sys_snode_t node; +}; + +/** @brief Register GOEP L2CAP server. + * + * Register GOEP server for a L2CAP PSM @ref bt_l2cap_server::psm. each new connection is + * authorized using the @ref bt_goep_transport_l2cap_server::accept callback which in case of + * success shall allocate the GOEP structure @ref bt_goep to be used by the new GOEP connection. + * + * For L2CAP PSM, for fixed, SIG-assigned PSMs (in the range 0x0001-0x0eff) the PSM should not be + * used. For dynamic PSMs (in the range 0x1000-0xffff), + * @ref bt_l2cap_server::psm may be pre-set to a given value (not recommended however). And it + * shall have the least significant bit of the most significant octet equal to 0 and the least + * significant bit of all other octets equal to 1. Or be left as 0, in which case the channel + * will be auto-allocated by L2CAP. + * + * @param server Server structure + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_goep_transport_l2cap_server_register(struct bt_goep_transport_l2cap_server *server); + +/** @brief Connect GOEP transport over L2CAP + * + * Connect GOEP transport by L2CAP, once the connection is completed, the callback + * @ref bt_goep_transport_ops::connected is called. If the connection is rejected, + * @ref bt_goep_transport_ops::disconnected callback is called instead. + * The GOEP object is passed (over an address of it) as second parameter, application should + * create transport dedicated GOEP object @ref bt_goep. Then pass to this API the location + * (address). + * Before calling the API, @ref bt_obex::client_ops and @ref bt_goep::transport_ops should + * be initialized with valid address of type @ref bt_obex_client_ops object and + * @ref bt_goep_transport_ops object. The field `mtu` of @ref bt_obex::rx could be passed with + * valid value. Or set it to zero, the mtu will be calculated according to + * @kconfig{CONFIG_BT_GOEP_L2CAP_MTU}. + * The L2CAP PSM is passed as third parameter. It is the RFCOMM channel of RFCOMM server of peer + * device. + * + * @warning It is the responsibility of the caller to zero out the parent of the GOEP object. + * + * @param conn Connection object + * @param goep GOEP object + * @param psm L2CAP PSM to connect to + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_goep_transport_l2cap_connect(struct bt_conn *conn, struct bt_goep *goep, uint16_t psm); + +/** @brief Disconnect GOEP transport from L2CAP channel + * + * Disconnect GOEP L2CAP transport. + * + * @param goep GOEP object + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_goep_transport_l2cap_disconnect(struct bt_goep *goep); + +/** @} */ + +/** @brief Allocate the buffer from given pool after reserving head room for GOEP + * + * For GOEP connection over RFCOMM, the reserved head room includes OBEX, RFCOMM, L2CAP and ACL + * headers. + * For GOEP connection over L2CAP, the reserved head room includes OBEX, L2CAP and ACL headers. + * + * @param goep GOEP object + * @param pool Which pool to take the buffer from + * + * @return New buffer. + */ +struct net_buf *bt_goep_create_pdu(struct bt_goep *goep, struct net_buf_pool *pool); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_GOEP_H_ */ diff --git a/include/zephyr/bluetooth/classic/obex.h b/include/zephyr/bluetooth/classic/obex.h new file mode 100644 index 000000000000..f439aff8dcbe --- /dev/null +++ b/include/zephyr/bluetooth/classic/obex.h @@ -0,0 +1,1447 @@ +/* obex.h - IrDA Oject Exchange Protocol handling */ + +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#ifndef ZEPHYR_INCLUDE_BLUETOOTH_OBEX_H_ +#define ZEPHYR_INCLUDE_BLUETOOTH_OBEX_H_ + +/** + * @brief Bluetooth APIs + * @defgroup bluetooth Bluetooth APIs + * @ingroup connectivity + * @{ + */ + +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief IrDA Oject Exchange Protocol (OBEX) + * @defgroup bt_obex IrDA Oject Exchange Protocol (OBEX) + * @since 1.0 + * @version 1.0.0 + * @ingroup bluetooth + * @{ + */ + +/** @brief OBEX Response Code. */ +enum __packed bt_obex_rsp_code { + /** Continue */ + BT_OBEX_RSP_CODE_CONTINUE = 0x90, + /** OK */ + BT_OBEX_RSP_CODE_OK = 0xa0, + /** Success */ + BT_OBEX_RSP_CODE_SUCCESS = 0xa0, + /** Created */ + BT_OBEX_RSP_CODE_CREATED = 0xa1, + /** Accepted */ + BT_OBEX_RSP_CODE_ACCEPTED = 0xa2, + /** Non-Authoritative Information */ + BT_OBEX_RSP_CODE_NON_AUTH_INFO = 0xa3, + /** No Content */ + BT_OBEX_RSP_CODE_NO_CONTENT = 0xa4, + /** Reset Content */ + BT_OBEX_RSP_CODE_RESET_CONTENT = 0xa5, + /** Partial Content */ + BT_OBEX_RSP_CODE_PARTIAL_CONTENT = 0xa6, + /** Multiple Choices */ + BT_OBEX_RSP_CODE_MULTI_CHOICES = 0xb0, + /** Moved Permanently */ + BT_OBEX_RSP_CODE_MOVED_PERM = 0xb1, + /** Moved temporarily */ + BT_OBEX_RSP_CODE_MOVED_TEMP = 0xb2, + /** See Other */ + BT_OBEX_RSP_CODE_SEE_OTHER = 0xb3, + /** Not modified */ + BT_OBEX_RSP_CODE_NOT_MODIFIED = 0xb4, + /** Use Proxy */ + BT_OBEX_RSP_CODE_USE_PROXY = 0xb5, + /** Bad Request - server couldn’t understand request */ + BT_OBEX_RSP_CODE_BAD_REQ = 0xc0, + /** Unauthorized */ + BT_OBEX_RSP_CODE_UNAUTH = 0xc1, + /** Payment Required */ + BT_OBEX_RSP_CODE_PAY_REQ = 0xc2, + /** Forbidden - operation is understood but refused */ + BT_OBEX_RSP_CODE_FORBIDDEN = 0xc3, + /** Not Found */ + BT_OBEX_RSP_CODE_NOT_FOUND = 0xc4, + /** Method Not Allowed */ + BT_OBEX_RSP_CODE_NOT_ALLOW = 0xc5, + /** Not Acceptable */ + BT_OBEX_RSP_CODE_NOT_ACCEPT = 0xc6, + /** Proxy Authentication Required */ + BT_OBEX_RSP_CODE_PROXY_AUTH_REQ = 0xc7, + /** Request Time Out */ + BT_OBEX_RSP_CODE_REQ_TIMEOUT = 0xc8, + /** Conflict */ + BT_OBEX_RSP_CODE_CONFLICT = 0xc9, + /** Gone */ + BT_OBEX_RSP_CODE_GONE = 0xca, + /** Length Required */ + BT_OBEX_RSP_CODE_LEN_REQ = 0xcb, + /** Precondition Failed */ + BT_OBEX_RSP_CODE_PRECON_FAIL = 0xcc, + /** Requested Entity Too Large */ + BT_OBEX_RSP_CODE_ENTITY_TOO_LARGE = 0xcd, + /** Requested URL Too Large */ + BT_OBEX_RSP_CODE_URL_TOO_LARGE = 0xce, + /** Unsupported media type */ + BT_OBEX_RSP_CODE_UNSUPP_MEDIA_TYPE = 0xcf, + /** Internal serve Error */ + BT_OBEX_RSP_CODE_INTER_ERROR = 0xd0, + /** Not Implemented */ + BT_OBEX_RSP_CODE_NOT_IMPL = 0xd1, + /** Bad Gateway */ + BT_OBEX_RSP_CODE_BAD_GATEWAY = 0xd2, + /** Service Unavailable */ + BT_OBEX_RSP_CODE_UNAVAIL = 0xd3, + /** Gateway Timeout */ + BT_OBEX_RSP_CODE_GATEWAY_TIMEOUT = 0xd4, + /** HTTP Version not supported */ + BT_OBEX_RSP_CODE_VER_UNSUPP = 0xd5, + /** Database Full */ + BT_OBEX_RSP_CODE_DB_FULL = 0xe0, + /** Database Locked */ + BT_OBEX_RSP_CODE_DB_LOCK = 0xe1, +}; + +/** Converts a OBEX response code to string. + * + * @param rsp_code Response code + * + * @return The string representation of the response code. + * If @kconfig{CONFIG_BT_OEBX_RSP_CODE_TO_STR} is not enabled, + * this just returns the empty string. + */ +#if defined(CONFIG_BT_OEBX_RSP_CODE_TO_STR) +const char *bt_obex_rsp_code_to_str(enum bt_obex_rsp_code rsp_code); +#else +static inline const char *bt_obex_rsp_code_to_str(enum bt_obex_rsp_code rsp_code) +{ + ARG_UNUSED(rsp_code); + + return ""; +} +#endif /* CONFIG_BT_OEBX_RSP_CODE_TO_STR */ + +/** @brief OBEX Header ID */ +enum __packed bt_obex_header_id { + /** Number of objects (used by Connect) */ + BT_OBEX_HEADER_ID_COUNT = 0xC0, + + /** Name of the object (often a file name) */ + BT_OBEX_HEADER_ID_NAME = 0x01, + + /** Type of object - e.g. text, html, binary, manufacturer specific */ + BT_OBEX_HEADER_ID_TYPE = 0x42, + + /** The length of the object in bytes */ + BT_OBEX_HEADER_ID_LEN = 0xC3, + + /** Date/time stamp – ISO 8601 version - preferred */ + BT_OBEX_HEADER_ID_TIME_ISO_8601 = 0x44, + + /** Date/time stamp – 4 byte version (for compatibility only) */ + BT_OBEX_HEADER_ID_TIME = 0xC4, + + /** Text description of the object */ + BT_OBEX_HEADER_ID_DES = 0x05, + + /** Name of service that operation is targeted to */ + BT_OBEX_HEADER_ID_TARGET = 0x46, + + /** An HTTP 1.x header */ + BT_OBEX_HEADER_ID_HTTP = 0x47, + + /** A chunk of the object body. */ + BT_OBEX_HEADER_ID_BODY = 0x48, + + /** The final chunk of the object body. */ + BT_OBEX_HEADER_ID_END_BODY = 0x49, + + /** Identifies the OBEX application, used to tell if talking to a peer. */ + BT_OBEX_HEADER_ID_WHO = 0x4A, + + /** An identifier used for OBEX connection multiplexing. */ + BT_OBEX_HEADER_ID_CONN_ID = 0xCB, + + /** Extended application request & response information. */ + BT_OBEX_HEADER_ID_APP_PARAM = 0x4C, + + /** Authentication digest-challenge. */ + BT_OBEX_HEADER_ID_AUTH_CHALLENGE = 0x4D, + + /** Authentication digest-response. */ + BT_OBEX_HEADER_ID_AUTH_RSP = 0x4E, + + /** Indicates the creator of an object. */ + BT_OBEX_HEADER_ID_CREATE_ID = 0xCF, + + /** Uniquely identifies the network client (OBEX server). */ + BT_OBEX_HEADER_ID_WAN_UUID = 0x50, + + /** OBEX Object class of object. */ + BT_OBEX_HEADER_ID_OBJECT_CLASS = 0x51, + + /** Parameters used in session commands/responses. */ + BT_OBEX_HEADER_ID_SESSION_PARAM = 0x52, + + /** Sequence number used in each OBEX packet for reliability. */ + BT_OBEX_HEADER_ID_SESSION_SEQ_NUM = 0x93, + + /** Specifies the action to be performed (used in ACTION operation). */ + BT_OBEX_HEADER_ID_ACTION_ID = 0x94, + + /** The destination object name (used in certain ACTION operations). */ + BT_OBEX_HEADER_ID_DEST_NAME = 0x15, + + /** 4-byte bit mask for setting permissions. */ + BT_OBEX_HEADER_ID_PERM = 0xD6, + + /** 1-byte value to setup Single Response Mode (SRM). */ + BT_OBEX_HEADER_ID_SRM = 0x97, + + /** 1-byte value for setting parameters used during Single Response Mode (SRM). */ + BT_OBEX_HEADER_ID_SRM_PARAM = 0x98, +}; + +#define BT_OBEX_SEND_BUF_RESERVE 7 + +struct bt_obex; + +/** @brief OBEX server operations structure. + * + * The object has to stay valid and constant for the lifetime of the OBEX server. + */ +struct bt_obex_server_ops { + /** @brief OBEX connect request callback + * + * If this callback is provided it will be called whenever the OBEX connect request + * is received. + * + * @param obex The OBEX object + * @param version OBEX version number + * @param mopl Maximum OBEX packet length + * @param buf Sequence of headers + */ + void (*connect)(struct bt_obex *obex, uint8_t version, uint16_t mopl, struct net_buf *buf); + + /** @brief OBEX disconnect request callback + * + * If this callback is provided it will be called whenever the OBEX disconnect request + * is received. + * + * @param obex The OBEX object + * @param buf Sequence of headers + */ + void (*disconnect)(struct bt_obex *obex, struct net_buf *buf); + + /** @brief OBEX put request callback + * + * If this callback is provided it will be called whenever the OBEX put request is + * received. + * + * @param obex The OBEX object + * @param final If the final bit is set + * @param buf Sequence of headers + */ + void (*put)(struct bt_obex *obex, bool final, struct net_buf *buf); + + /** @brief OBEX get request callback + * + * If this callback is provided it will be called whenever the OBEX get request is + * received. + * + * @param obex The OBEX object + * @param final If the final bit is set + * @param buf Sequence of headers + */ + void (*get)(struct bt_obex *obex, bool final, struct net_buf *buf); + + /** @brief OBEX abort request callback + * + * If this callback is provided it will be called whenever the OBEX abort request is + * received. + * + * @param obex The OBEX object + * @param buf Optional headers + */ + void (*abort)(struct bt_obex *obex, struct net_buf *buf); + + /** @brief OBEX SetPath request callback + * + * If this callback is provided it will be called whenever the OBEX SetPath request is + * received. + * + * @param obex The OBEX object + * @param flags The flags + * @param buf Optional headers + */ + void (*setpath)(struct bt_obex *obex, uint8_t flags, struct net_buf *buf); + + /** @brief OBEX action request callback + * + * If this callback is provided it will be called whenever the OBEX action request is + * received. + * + * @param obex The OBEX object + * @param final If the final bit is set + * @param buf Sequence of headers (Including action identifier header if it exists) + */ + void (*action)(struct bt_obex *obex, bool final, struct net_buf *buf); +}; + +/** @brief OBEX client operations structure. + * + * The object has to stay valid and constant for the lifetime of the OBEX client. + */ +struct bt_obex_client_ops { + /** @brief OBEX connect response callback + * + * If this callback is provided it will be called whenever the OBEX connect response + * is received. + * + * @param obex The OBEX object + * @param rsp_code Response code + * @param version OBEX version number + * @param mopl Maximum OBEX packet length + * @param buf Sequence of headers + */ + void (*connect)(struct bt_obex *obex, uint8_t rsp_code, uint8_t version, uint16_t mopl, + struct net_buf *buf); + + /** @brief OBEX disconnect response callback + * + * If this callback is provided it will be called whenever the OBEX disconnect response + * is received. + * + * @param obex The OBEX object + * @param rsp_code Response code + * @param buf Sequence of headers + */ + void (*disconnect)(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + + /** @brief OBEX put response callback + * + * If this callback is provided it will be called whenever the OBEX put response is + * received. + * + * @param obex The OBEX object + * @param rsp_code Response code + * @param buf Optional response headers + */ + void (*put)(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + + /** @brief OBEX get response callback + * + * If this callback is provided it will be called whenever the OBEX get response is + * received. + * + * @param obex The OBEX object + * @param rsp_code Response code + * @param buf Optional response headers + */ + void (*get)(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + + /** @brief OBEX abort response callback + * + * If this callback is provided it will be called whenever the OBEX abort response is + * received. + * + * @param obex The OBEX object + * @param rsp_code Response code + * @param buf Optional response headers + */ + void (*abort)(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + + /** @brief OBEX SetPath response callback + * + * If this callback is provided it will be called whenever the OBEX SetPath response is + * received. + * + * @param obex The OBEX object + * @param rsp_code Response code + * @param buf Optional response headers + */ + void (*setpath)(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + + /** @brief OBEX action response callback + * + * If this callback is provided it will be called whenever the OBEX action response is + * received. + * + * @param obex The OBEX object + * @param rsp_code Response code + * @param buf Optional response headers + */ + void (*action)(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); +}; + +/** @brief OBEX transport operations structure. + * + * The object has to stay valid and constant for the lifetime of the OBEX client/server. + */ +struct bt_obex_transport_ops { + /** @brief allocate buf callback + * + * If this callback is provided it will be called to allocate a net buffer to store + * the outgoing data. + * + * @param obex The OBEX object + * @param pool Which pool to take the buffer from + * + * @return Allocated buffer. + */ + struct net_buf *(*alloc_buf)(struct bt_obex *obex, struct net_buf_pool *pool); + + /** @brief Send OBEX data via transport + * + * If this callback is provided it will be called to send data via OBEX transport. + * + * @param obex The OBEX object + * @param buf OBEX packet + * + * @return 0 in case of success or negative value in case of error. + * @return -EMSGSIZE if `buf` is larger than `obex`'s MOPL. + */ + int (*send)(struct bt_obex *obex, struct net_buf *buf); + + /** @brief Disconnect from transport + * + * If this callback is provided it will be called to disconnect transport. + * + * @param obex The OBEX object + * + * @return 0 in case of success or negative value in case of error. + */ + int (*disconnect)(struct bt_obex *obex); +}; + +/** @brief Life-span states of OBEX. + * + * Used only by internal APIs dealing with setting OBEX to proper state depending on operational + * context. + * + * OBEX enters the @ref BT_OBEX_CONNECTING state upon @ref bt_obex_connect, or upon returning + * from @ref bt_obex_server_ops::connect. + * + * When OBEX leaves the @ref BT_OBEX_CONNECTING state and enters the @ref BT_OBEX_CONNECTED, + * @ref bt_obex_connect_rsp or @ref bt_obex_client_ops::connect is called with the response code + * @ref BT_OBEX_RSP_CODE_SUCCESS. + * + * When OBEX enters the @ref BT_OBEX_DISCONNECTED from other states, + * @ref bt_obex_client_ops::disconnect or @ref bt_obex_connect_rsp is called with the response code + * @ref BT_OBEX_RSP_CODE_SUCCESS. Or OBEX transport enters the disconnected state from other OBEX + * transport states. + */ +enum __packed bt_obex_state { + /** OBEX disconnected */ + BT_OBEX_DISCONNECTED, + /** OBEX in connecting state */ + BT_OBEX_CONNECTING, + /** OBEX ready for upper layer traffic on it */ + BT_OBEX_CONNECTED, + /** OBEX in disconnecting state */ + BT_OBEX_DISCONNECTING, +}; + +/** @brief OBEX structure. */ +struct bt_obex { + /** @brief OBEX Server operations + * + * If it is a obex sever, the upper layer should pass the operations of server to + * `server_ops` when providing the OBEX structure. + */ + const struct bt_obex_server_ops *server_ops; + + /** @brief OBEX Client operations + * + * If it is a obex client, the upper layer should pass the operations of client to + * `client_ops` when providing the OBEX structure. + */ + const struct bt_obex_client_ops *client_ops; + + struct { + /** @brief MTU of OBEX transport */ + uint16_t mtu; + /** @brief The Maximum OBEX Packet Length (MOPL) */ + uint16_t mopl; + } rx; + + struct { + /** @brief MTU of OBEX transport */ + uint16_t mtu; + /** @brief The Maximum OBEX Packet Length (MOPL) */ + uint16_t mopl; + } tx; + + /** @internal OBEX transport operations */ + const struct bt_obex_transport_ops *_transport_ops; + + /** @internal Saves the current state, @ref bt_obex_state */ + atomic_t _state; + + /** @internal OBEX opcode */ + atomic_t _opcode; + + /** @internal OBEX previous opcode */ + atomic_t _pre_opcode; +}; + +/** @brief OBEX connect request + * + * The connect operation initiates the connection and sets up the basic expectations of each side + * of the link. The connect request must fit in a single packet. + * The third parameter `buf` saves the packet data (sequence of headers) of the connect request is + * stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `Authenticate Challenge` is packed by calling + * @ref bt_obex_add_header_auth_challenge. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param mopl Maximum OBEX packet length + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_connect(struct bt_obex *obex, uint16_t mopl, struct net_buf *buf); + +/** @brief OBEX connect response + * + * The connect response is used to acknowledged connect request packet. + * The connect response must fit in a single packet. + * + * The second parameter `rsp_code` is used to pass the response code @ref bt_obex_rsp_code. The + * typical values @ref BT_OBEX_RSP_CODE_SUCCESS for `success`. + * The 4th parameter `buf` saves the packet data (sequence of headers) of the connect response is + * stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `Authenticate Response` is packed by calling + * @ref bt_obex_add_header_auth_rsp. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param rsp_code Response code + * @param mopl Maximum OBEX packet length + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_connect_rsp(struct bt_obex *obex, uint8_t rsp_code, uint16_t mopl, struct net_buf *buf); + +/** @brief OBEX disconnect request + * + * The disconnect operation signals the end of the OBEX connection from client side. + * The disconnect request must fit in a single packet. + * + * The second parameter `buf` saves the packet data (sequence of headers) of the disconnect + * request is stored in second parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `Connection Id` is packed by calling + * @ref bt_obex_add_header_conn_id. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_disconnect(struct bt_obex *obex, struct net_buf *buf); + +/** @brief OBEX disconnect response + * + * The disconnect response is used to acknowledged disconnect request packet. + * The disconnect response must fit in a single packet. + * + * The second parameter `rsp_code` is used to pass the response code @ref bt_obex_rsp_code. The + * typical values, @ref BT_OBEX_RSP_CODE_SUCCESS for `success`, @ref BT_OBEX_RSP_CODE_UNAVAIL for + * `service unavailable` if the header `Connection Id` is invalid. + * The third parameter `buf` saves the packet data (sequence of headers) of the connect response + * is stored in third parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param rsp_code Response code + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_disconnect_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + +/** @brief OBEX put request + * + * The put operation sends one object from the client to the server. + * The put operation consists of one or more request packets, the last of which should have the + * second parameter `final` set. + * The third parameter `buf` saves the packet data (sequence of headers) of the put request is + * stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `Name` is packed by calling @ref bt_obex_add_header_name. + * + * @note A put operation with NO Body or End-of-Body headers whatsoever should be treated as a + * delete request. Similarly, a put operation with an empty End-of-Body header requests the + * recipient to create an empty object. This definition may not make sense or apply to every + * implementation (in other words devices are not required to support delete operations or + * empty objects). + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param final The final bit of opcode + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_put(struct bt_obex *obex, bool final, struct net_buf *buf); + +/** @brief OBEX put response + * + * The put response is used to acknowledged each put request packet. It is sent from the server + * to client. + * The second parameter `rsp_code` is used to pass the response code @ref bt_obex_rsp_code. The + * typical values, @ref BT_OBEX_RSP_CODE_CONTINUE for `continue`, @ref BT_OBEX_RSP_CODE_SUCCESS + * for `success`. + * The third parameter `buf` saves the packet data (sequence of headers) of the put response is + * stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `srm` is packed by calling @ref bt_obex_add_header_srm. + * Or, the `buf` could be NULL if there is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param rsp_code Response code + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_put_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + +/** @brief OBEX get request + * + * The get operation requests that the server return an object to the client. + * The get operation consists of one or more request packets, the last of which should have the + * second parameter `final` set, and the request phase of the get is complete. Once a get request + * is sent with the final bit, all subsequent get request packets must set the final bit until + * the operation is complete. + * The third parameter `buf` saves the packet data (sequence of headers) of the get request is + * stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `Name` is packed by calling @ref bt_obex_add_header_name. + * Or, the `buf` could be NULL if there is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param final The final bit of opcode + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get(struct bt_obex *obex, bool final, struct net_buf *buf); + +/** @brief OBEX get response + * + * The get response is used to acknowledged get request packets. It is sent from the server + * to client. + * If the server has more than one object that fits the request, the behavior is system + * dependent. But the server device must not begin sending the object body chunks until the + * request phase is complete. + * + * The second parameter `rsp_code` is used to pass the response code @ref bt_obex_rsp_code. The + * typical values, @ref BT_OBEX_RSP_CODE_CONTINUE for `continue`, @ref BT_OBEX_RSP_CODE_SUCCESS + * for `success`. + * A successful response for an object that fits entirely in one response packet is + * @ref BT_OBEX_RSP_CODE_SUCCESS (Success, with Final bit set) in the response code, followed + * by the object body. If the response is large enough to require multiple GET responses, only + * the last response is @ref BT_OBEX_RSP_CODE_SUCCESS, and the others are all + * @ref BT_OBEX_RSP_CODE_CONTINUE (Continue). The object is returned as a sequence of headers + * just as with put operation. Any other response code @ref bt_obex_rsp_code indicates failure. + * The typical failure response codes, @ref BT_OBEX_RSP_CODE_BAD_REQ for `Bad request - server + * couldn’t understand request`, @ref BT_OBEX_RSP_CODE_FORBIDDEN for `Forbidden - operation is + * understood but refused`. + * The third parameter `buf` saves the packet data (sequence of headers) of the get response is + * stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `srm` is packed by calling @ref bt_obex_add_header_srm. + * Or, the `buf` could be NULL if there is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param rsp_code Response code + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + +/** @brief OBEX abort request + * + * The abort request is used when the client decides to terminate a multi-packet operation (such + * as put) before it would be normally end. The abort request always fits in one OBEX packet and + * have the Final bit set. + * The second parameter `buf` saves the packet data (sequence of headers) of the abort request is + * stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `description` is packed by calling + * @ref bt_obex_add_header_description. Or, the `buf` could be NULL if there is not any data + * needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_abort(struct bt_obex *obex, struct net_buf *buf); + +/** @brief OBEX abort response + * + * The abort response is used to acknowledged abort request packet. It is sent from the server + * to client. + * The abort response always fits in one OBEX packet and have the Final bit set. + * + * The second parameter `rsp_code` is used to pass the response code @ref bt_obex_rsp_code. The + * typical value @ref BT_OBEX_RSP_CODE_SUCCESS for `success`. + * The third parameter `buf` saves the packet data (sequence of headers) of the abort response is + * stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Or, the `buf` could be NULL if there is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param rsp_code Response code + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_abort_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + +/** @brief OBEX setpath request + * + * The setpath request is used to set the "current folder" on the receiving side in order to + * enable transfers that need additional path information. The Path name is contained in a Name + * header. + * The setpath request always fits in one OBEX packet and have the Final bit set. + * + * The second parameter `flags` is the setpath operation flag bit maps. Bit 0 means `backup a + * level before applying name (equivalent to ../ on many systems)`. Bit 1 means `Don’t create + * folder if it does not exist, return an error instead.` Other bits must be set to zero by + * sender and ignored by receiver. + * The third parameter `buf` saves the packet data (sequence of headers) of the setpath request is + * stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `Name` is packed by calling @ref bt_obex_add_header_name. + * If the header `Name` is added, it means the client wants to go one level down relative to the + * current folder into the folder `name`. Or, the client wants to go back to the default folder + * when no bit of flags set. + * + * @note The Name header may be omitted when flags or constants indicate the entire operation + * being requested. For example, back up one level (bit 0 of `flags` is set), equivalent to + * "cd .." on some systems. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param flags Flags for setpath request + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_setpath(struct bt_obex *obex, uint8_t flags, struct net_buf *buf); + +/** @brief OBEX setpath response + * + * The setpath response is used to acknowledged setpath request packet. It is sent from the + * server to client. + * The setpath response always fits in one OBEX packet and have the Final bit set. + * + * The second parameter `rsp_code` is used to pass the response code @ref bt_obex_rsp_code. The + * typical value @ref BT_OBEX_RSP_CODE_SUCCESS for `success`. Any other response code + * @ref bt_obex_rsp_code indicates failure. The typical failure response codes, + * @ref BT_OBEX_RSP_CODE_BAD_REQ for `Bad request - server couldn’t understand request`, + * @ref BT_OBEX_RSP_CODE_FORBIDDEN for `Forbidden - operation is understood but refused`. + * The third parameter `buf` saves the packet data (sequence of headers) of the setpath response + * is stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Or, the `buf` could be NULL if there is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param rsp_code Response code + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_setpath_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + +/** @brief OBEX Actions. */ +enum __packed bt_obex_action_id { + /** Copy Object Action */ + BT_OBEX_ACTION_COPY = 0x00, + /** Move/Rename Object Action */ + BT_OBEX_ACTION_MOVE_RENAME = 0x01, + /** Set Object Permissions Action */ + BT_OBEX_ACTION_SET_PERM = 0x02, +}; + +/** @brief OBEX action request + * + * The action operation is defined to cover the needs of common actions. The `Action Id` header + * @ref BT_OBEX_HEADER_ID_ACTION_ID is used in the action operation and contains an action + * identifier which defines what action is to be performed. All actions are optional and depends + * on the implementation of server. + * + * There are three actions defined @ref bt_obex_action_id, + * + * @ref BT_OBEX_ACTION_COPY is used to copy an object from one location to another. The header + * `Name` @ref BT_OBEX_HEADER_ID_NAME specifies the source file name and the header `Dest Name` + * @ref BT_OBEX_HEADER_ID_DEST_NAME specifies the destination file name. These two headers are + * mandatory for this action. + * + * @ref BT_OBEX_ACTION_MOVE_RENAME is used to move an object from one location to another. It + * can also be used to rename an object. The header `Name` @ref BT_OBEX_HEADER_ID_NAME specifies + * the source file name and the header `Dest Name` @ref BT_OBEX_HEADER_ID_DEST_NAME specifies the + * destination file name. These two headers are mandatory for this action. + * + * @ref BT_OBEX_ACTION_SET_PERM is used to set the access permissions of an object or folder. + * The header `Name` @ref BT_OBEX_HEADER_ID_NAME specifies the source file name and the header + * `Permissions` @ref BT_OBEX_HEADER_ID_PERM specifies the new permissions for this object. These + * two headers are mandatory for this action. + * + * The action operation consists of one or more request packets, the last of which should have + * the second parameter `final` set. + * The third parameter `buf` saves the packet data (sequence of headers) of the action request is + * stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Such as header `Name` is packed by calling @ref bt_obex_add_header_name. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param final The final bit of opcode + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_action(struct bt_obex *obex, bool final, struct net_buf *buf); + +/** @brief OBEX action response + * + * The action response is used to acknowledged action request packets. It is sent from the server + * to client. + * The second parameter `rsp_code` is used to pass the response code @ref bt_obex_rsp_code. The + * typical value @ref BT_OBEX_RSP_CODE_SUCCESS for `success`. + * + * There are three actions defined @ref bt_obex_action_id. For each action request, there are + * failure response codes corresponding to it. + * + * For action @ref BT_OBEX_ACTION_COPY, + * @ref BT_OBEX_RSP_CODE_NOT_FOUND - `Source object or destination folder does not exist`. + * @ref BT_OBEX_RSP_CODE_FORBIDDEN - `Cannot modify the permissions of the destination + * object/folder, permission denied`. + * @ref BT_OBEX_RSP_CODE_DB_FULL - `Cannot create object in destination folder, out of memory` + * @ref BT_OBEX_RSP_CODE_CONFLICT - `Cannot modify the permissions, sharing violation, object or + * Set Object Permissions Action busy`. + * @ref BT_OBEX_RSP_CODE_NOT_IMPL - `Set Object Permissions Action not supported`. + * @ref BT_OBEX_RSP_CODE_NOT_MODIFIED - `Cannot create folder/file, destination folder/file + * already exits`. + * + * For action @ref BT_OBEX_ACTION_MOVE_RENAME, + * @ref BT_OBEX_RSP_CODE_NOT_FOUND - `Source object or destination folder does not exist`. + * @ref BT_OBEX_RSP_CODE_FORBIDDEN - `Cannot modify the permissions of the destination + * object/folder, permission denied`. + * @ref BT_OBEX_RSP_CODE_DB_FULL - `Cannot create object in destination folder, out of memory` + * @ref BT_OBEX_RSP_CODE_CONFLICT - `Cannot modify the permissions, sharing violation, object or + * Set Object Permissions Action busy`. + * @ref BT_OBEX_RSP_CODE_NOT_IMPL - `Set Object Permissions Action not supported`. + * @ref BT_OBEX_RSP_CODE_NOT_MODIFIED - `Cannot create folder/file, destination folder/file + * already exits`. + * + * For action @ref BT_OBEX_ACTION_SET_PERM, + * @ref BT_OBEX_RSP_CODE_NOT_FOUND - `Source object or destination folder does not exist`. + * @ref BT_OBEX_RSP_CODE_FORBIDDEN - `Cannot modify the permissions of the destination + * object/folder, permission denied`. + * @ref BT_OBEX_RSP_CODE_NOT_IMPL - `Set Object Permissions Action not supported`. + * @ref BT_OBEX_RSP_CODE_CONFLICT - `Cannot modify the permissions, sharing violation, object or + * Set Object Permissions Action busy`. + * + * The third parameter `buf` saves the packet data (sequence of headers) of the action response + * is stored in thrid parameter `buf`. All headers are packed by calling function + * bt_obex_add_header_*. Or, the `buf` could be NULL if there is not any data needs to be sent. + * + * @note Buffer ownership is transferred to the stack in case of success, in case of an error + * the caller retains the ownership of the buffer. + * + * @param obex OBEX object + * @param rsp_code Response code + * @param buf Sequence of headers to be sent out + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_action_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf); + +/** @brief Add Header: number of objects (used by Connect) + * + * @param buf Buffer needs to be sent + * @param count Number of objects + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_count(struct net_buf *buf, uint32_t count); + +/** @brief Add Header: name of the object (often a file name) + * + * @param buf Buffer needs to be sent + * @param len Length of name + * @param name Name of the object + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_name(struct net_buf *buf, uint16_t len, const uint8_t *name); + +/** @brief Add Header: type of object - e.g. text, html, binary, manufacturer specific + * + * @param buf Buffer needs to be sent + * @param len Length of type + * @param type Type of the object + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_type(struct net_buf *buf, uint16_t len, const uint8_t *type); + +/** @brief Add Header: the length of the object in bytes + * + * @param buf Buffer needs to be sent + * @param len The length of the object in bytes + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_len(struct net_buf *buf, uint32_t len); + +/** @brief Add Header: date/time stamp – ISO 8601 version - preferred + * + * @param buf Buffer needs to be sent + * @param len Length of data/time stamp + * @param t Data/time stamp + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_time_iso_8601(struct net_buf *buf, uint16_t len, const uint8_t *t); + +/** @brief Add Header: date/time stamp – 4 byte version (for compatibility only) + * + * @param buf Buffer needs to be sent + * @param t Data/time stamp + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_time(struct net_buf *buf, uint32_t t); + +/** @brief Add Header: text description of the object + * + * @param buf Buffer needs to be sent + * @param len Length of description + * @param dec Description of the object + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_description(struct net_buf *buf, uint16_t len, const uint8_t *dec); + +/** @brief Add Header: name of service that operation is targeted to + * + * @param buf Buffer needs to be sent + * @param len Length of target name + * @param target Target name + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_target(struct net_buf *buf, uint16_t len, const uint8_t *target); + +/** @brief Add Header: an HTTP 1.x header + * + * @param buf Buffer needs to be sent + * @param len Length of http 1.x header + * @param http Http 1.x header + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_http(struct net_buf *buf, uint16_t len, const uint8_t *http); + +/** @brief Add Header: a chunk of the object body + * + * @param buf Buffer needs to be sent + * @param len Length of body + * @param body Object Body + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_body(struct net_buf *buf, uint16_t len, const uint8_t *body); + +/** @brief Add Header: the final chunk of the object body. + * + * @param buf Buffer needs to be sent + * @param len Length of body + * @param body Object Body + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_end_body(struct net_buf *buf, uint16_t len, const uint8_t *body); + +/** @brief Add Header: identifies the OBEX application, used to tell if talking to a peer. + * + * @param buf Buffer needs to be sent + * @param len Length of who + * @param who Who + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_who(struct net_buf *buf, uint16_t len, const uint8_t *who); + +/** @brief Add Header: an identifier used for OBEX connection multiplexing. + * + * @param buf Buffer needs to be sent + * @param conn_id Connection Id + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_conn_id(struct net_buf *buf, uint32_t conn_id); + +/** @brief Add Header: extended application request & response information. + * + * @param buf Buffer needs to be sent + * @param len Length of app_param + * @param app_param Application parameter + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_app_param(struct net_buf *buf, uint16_t len, const uint8_t *app_param); + +/** @brief Add Header: authentication digest-challenge. + * + * @param buf Buffer needs to be sent + * @param len Length of auth_challenge + * @param auth Authentication challenge + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_auth_challenge(struct net_buf *buf, uint16_t len, const uint8_t *auth); + +/** @brief Add Header: authentication digest-response. + * + * @param buf Buffer needs to be sent + * @param len Length of authentication response + * @param auth Authentication response + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_auth_rsp(struct net_buf *buf, uint16_t len, const uint8_t *auth); + +/** @brief Add Header: indicates the creator of an object. + * + * @param buf Buffer needs to be sent + * @param creator_id Creator Id + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_creator_id(struct net_buf *buf, uint32_t creator_id); + +/** @brief Add Header: uniquely identifies the network client (OBEX server). + * + * @param buf Buffer needs to be sent + * @param len Length of UUID + * @param uuid UUID + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_wan_uuid(struct net_buf *buf, uint16_t len, const uint8_t *uuid); + +/** @brief Add Header: oBEX Object class of object. + * + * @param buf Buffer needs to be sent + * @param len Length of oject class + * @param obj_class Class of object + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_obj_class(struct net_buf *buf, uint16_t len, const uint8_t *obj_class); + +/** @brief Add Header: parameters used in session commands/responses. + * + * @param buf Buffer needs to be sent + * @param len Length of session parameter + * @param session_param Session parameter + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_session_param(struct net_buf *buf, uint16_t len, + const uint8_t *session_param); + +/** @brief Add Header: sequence number used in each OBEX packet for reliability. + * + * @param buf Buffer needs to be sent + * @param session_seq_number Session sequence parameter + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_session_seq_number(struct net_buf *buf, uint32_t session_seq_number); + +/** @brief Add Header: specifies the action to be performed (used in ACTION operation). + * + * @param buf Buffer needs to be sent + * @param action_id Action Id @ref bt_obex_action_id + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_action_id(struct net_buf *buf, uint32_t action_id); + +/** @brief Add Header: the destination object name (used in certain ACTION operations). + * + * @param buf Buffer needs to be sent + * @param len Length of destination name + * @param dest_name Destination name + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_dest_name(struct net_buf *buf, uint16_t len, const uint8_t *dest_name); + +/** @brief Add Header: 4-byte bit mask for setting permissions. + * + * @param buf Buffer needs to be sent + * @param perm Permissions + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_perm(struct net_buf *buf, uint32_t perm); + +/** @brief Add Header: 1-byte value to setup Single Response Mode (SRM). + * + * @param buf Buffer needs to be sent + * @param srm SRM + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_srm(struct net_buf *buf, uint8_t srm); + +/** @brief Add Header: Single Response Mode (SRM) Parameter. + * + * @param buf Buffer needs to be sent + * @param srm_param SRM parameter + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_srm_param(struct net_buf *buf, uint8_t srm_param); + +/** @brief OBEX Header structure */ +struct bt_obex_hdr { + /** Header ID @ref bt_obex_header_id */ + uint8_t id; + /** The length of header value */ + uint16_t len; + /** Header value */ + const uint8_t *data; +}; + +/** @brief Helper for parsing OBEX header. + * + * A helper for parsing the header structure for OBEX packets. The most common scenario is to + * call this helper on the in the callback of OBEX server and client. + * + * @warning This helper function will consume `buf` when parsing. The user should make a copy + * if the original data is to be used afterwards. + * + * @param buf OBEX packet as given to the callback of OBEX server and client + * @param func Callback function which will be called for each element that's found in the data + * The callback should return true to continue parsing, or false to stop parsing. + * @param user_data User data to be passed to the callback + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_header_parse(struct net_buf *buf, + bool (*func)(struct bt_obex_hdr *hdr, void *user_data), void *user_data); + +/** @brief Get header value: number of objects (used by Connect) + * + * @param buf Buffer needs to be sent + * @param count Number of objects + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_count(struct net_buf *buf, uint32_t *count); + +/** @brief Get header value: name of the object (often a file name) + * + * @param buf Buffer needs to be sent + * @param len Length of name + * @param name Name of the object + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_name(struct net_buf *buf, uint16_t *len, const uint8_t **name); + +/** @brief Get header value: type of object - e.g. text, html, binary, manufacturer specific + * + * @param buf Buffer needs to be sent + * @param len Length of type + * @param type Type of the object + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_type(struct net_buf *buf, uint16_t *len, const uint8_t **type); + +/** @brief Get header value: the length of the object in bytes + * + * @param buf Buffer needs to be sent + * @param len The length of the object in bytes + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_len(struct net_buf *buf, uint32_t *len); + +/** @brief Get header value: date/time stamp – ISO 8601 version - preferred + * + * @param buf Buffer needs to be sent + * @param len Length of data/time stamp + * @param t Data/time stamp + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_time_iso_8601(struct net_buf *buf, uint16_t *len, const uint8_t **t); + +/** @brief Get header value: date/time stamp – 4 byte version (for compatibility only) + * + * @param buf Buffer needs to be sent + * @param t Data/time stamp + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_time(struct net_buf *buf, uint32_t *t); + +/** @brief Get header value: text description of the object + * + * @param buf Buffer needs to be sent + * @param len Length of description + * @param dec Description of the object + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_description(struct net_buf *buf, uint16_t *len, const uint8_t **dec); + +/** @brief Get header value: name of service that operation is targeted to + * + * @param buf Buffer needs to be sent + * @param len Length of target name + * @param target Target name + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_target(struct net_buf *buf, uint16_t *len, const uint8_t **target); + +/** @brief Get header value: an HTTP 1.x header + * + * @param buf Buffer needs to be sent + * @param len Length of http 1.x header + * @param http Http 1.x header + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_http(struct net_buf *buf, uint16_t *len, const uint8_t **http); + +/** @brief Get header value: a chunk of the object body. + * + * @param buf Buffer needs to be sent + * @param len Length of body + * @param body Object Body + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_body(struct net_buf *buf, uint16_t *len, const uint8_t **body); + +/** @brief Get header value: the final chunk of the object body. + * + * @param buf Buffer needs to be sent + * @param len Length of body + * @param body Object Body + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_end_body(struct net_buf *buf, uint16_t *len, const uint8_t **body); + +/** @brief Get header value: identifies the OBEX application, used to tell if talking to a peer. + * + * @param buf Buffer needs to be sent + * @param len Length of who + * @param who Who + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_who(struct net_buf *buf, uint16_t *len, const uint8_t **who); + +/** @brief Get header value: an identifier used for OBEX connection multiplexing. + * + * @param buf Buffer needs to be sent + * @param conn_id Connection Id + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_conn_id(struct net_buf *buf, uint32_t *conn_id); + +/** @brief Get header value: extended application request & response information. + * + * @param buf Buffer needs to be sent + * @param len Length of app_param + * @param app_param Application parameter + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_app_param(struct net_buf *buf, uint16_t *len, const uint8_t **app_param); + +/** @brief Get header value: authentication digest-challenge. + * + * @param buf Buffer needs to be sent + * @param len Length of auth_challenge + * @param auth Authentication challenge + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_auth_challenge(struct net_buf *buf, uint16_t *len, const uint8_t **auth); + +/** @brief Get header value: authentication digest-response. + * + * @param buf Buffer needs to be sent + * @param len Length of authentication response + * @param auth Authentication response + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_auth_rsp(struct net_buf *buf, uint16_t *len, const uint8_t **auth); + +/** @brief Get header value: indicates the creator of an object. + * + * @param buf Buffer needs to be sent + * @param creator_id Creator Id + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_creator_id(struct net_buf *buf, uint32_t *creator_id); + +/** @brief Get header value: uniquely identifies the network client (OBEX server). + * + * @param buf Buffer needs to be sent + * @param len Length of UUID + * @param uuid UUID + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_wan_uuid(struct net_buf *buf, uint16_t *len, const uint8_t **uuid); + +/** @brief Get header value: oBEX Object class of object. + * + * @param buf Buffer needs to be sent + * @param len Length of oject class + * @param obj_class Class of object + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_obj_class(struct net_buf *buf, uint16_t *len, const uint8_t **obj_class); + +/** @brief Get header value: parameters used in session commands/responses. + * + * @param buf Buffer needs to be sent + * @param len Length of session parameter + * @param session_param Session parameter + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_session_param(struct net_buf *buf, uint16_t *len, + const uint8_t **session_param); + +/** @brief Get header value: sequence number used in each OBEX packet for reliability. + * + * @param buf Buffer needs to be sent + * @param session_seq_number Session sequence parameter + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_session_seq_number(struct net_buf *buf, uint32_t *session_seq_number); + +/** @brief Get header value: specifies the action to be performed (used in ACTION operation). + * + * @param buf Buffer needs to be sent + * @param action_id Action Id @ref bt_obex_action_id + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_action_id(struct net_buf *buf, uint32_t *action_id); + +/** @brief Get header value: the destination object name (used in certain ACTION operations). + * + * @param buf Buffer needs to be sent + * @param len Length of destination name + * @param dest_name Destination name + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_dest_name(struct net_buf *buf, uint16_t *len, const uint8_t **dest_name); + +/** @brief Get header value: 4-byte bit mask for setting permissions. + * + * @param buf Buffer needs to be sent + * @param perm Permissions + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_perm(struct net_buf *buf, uint32_t *perm); + +/** @brief Get header value: 1-byte value to setup Single Response Mode (SRM). + * + * @param buf Buffer needs to be sent + * @param srm SRM + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_srm(struct net_buf *buf, uint8_t *srm); + +/** @brief Get header value: Single Response Mode (SRM) Parameter. + * + * @param buf Buffer needs to be sent + * @param srm_param SRM parameter + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_get_header_srm_param(struct net_buf *buf, uint8_t *srm_param); + +/** + * @} + */ + +#ifdef __cplusplus +} +#endif +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_BLUETOOTH_OBEX_H_ */ diff --git a/subsys/bluetooth/Kconfig.logging b/subsys/bluetooth/Kconfig.logging index 7ccaf55c2d6b..8e47a24e8f0b 100644 --- a/subsys/bluetooth/Kconfig.logging +++ b/subsys/bluetooth/Kconfig.logging @@ -399,6 +399,10 @@ module = BT_SDP module-str = "Bluetooth Service Discovery Protocol (SDP)" source "subsys/logging/Kconfig.template.log_config_inherit" +module = BT_GOEP +module-str = "Bluetooth Generic Object Exchange Profile (GOEP)" +source "subsys/logging/Kconfig.template.log_config_inherit" + endmenu # Bluetooth Classic endif # BT_CLASSIC diff --git a/subsys/bluetooth/host/classic/CMakeLists.txt b/subsys/bluetooth/host/classic/CMakeLists.txt index 0f7f01d5c00b..1fc28a2c74a6 100644 --- a/subsys/bluetooth/host/classic/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/CMakeLists.txt @@ -31,3 +31,9 @@ zephyr_library_sources_ifdef( CONFIG_BT_HFP_AG hfp_ag.c ) + +zephyr_library_sources_ifdef( + CONFIG_BT_GOEP + obex.c + goep.c + ) diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index a967cfa65ae5..488c657e498f 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -229,6 +229,22 @@ config BT_COD consult the following link: https://www.bluetooth.com/specifications/assigned-numbers +config BT_GOEP + bool "Bluetooth GOEP Profile [EXPERIMENTAL]" + select EXPERIMENTAL + help + This option enables the GOEP profile + +if BT_GOEP +config BT_OEBX_RSP_CODE_TO_STR + bool "Print OBEX response code as strings" + help + This configuration enables printing of OBEX response + codes represented as strings. + See bt_obex_rsp_code_to_str() for more details. + +endif # BT_GOEP + endif # BT_CLASSIC endmenu diff --git a/subsys/bluetooth/host/classic/goep.c b/subsys/bluetooth/host/classic/goep.c new file mode 100644 index 000000000000..46fb2c67709a --- /dev/null +++ b/subsys/bluetooth/host/classic/goep.c @@ -0,0 +1,604 @@ +/* goep.c - Bluetooth Generic Object Exchange Profile handling */ + +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "host/conn_internal.h" +#include "l2cap_br_internal.h" +#include "obex_internal.h" + +#define LOG_LEVEL CONFIG_BT_GOEP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_goep); + +#define GOEP_MIN_MTU BT_OBEX_MIN_MTU + +/* RFCOMM Server list */ +static sys_slist_t goep_rfcomm_server; + +static void goep_rfcomm_recv(struct bt_rfcomm_dlc *dlc, struct net_buf *buf) +{ + struct bt_goep *goep = CONTAINER_OF(dlc, struct bt_goep, _transport.dlc); + int err; + + err = bt_obex_recv(&goep->obex, buf); + if (err) { + LOG_WRN("Fail to handle OBEX packet (err %d)", err); + } +} + +static void goep_rfcomm_connected(struct bt_rfcomm_dlc *dlc) +{ + struct bt_goep *goep = CONTAINER_OF(dlc, struct bt_goep, _transport.dlc); + int err; + + LOG_DBG("RFCOMM %p connected", dlc); + + if (dlc->mtu < GOEP_MIN_MTU) { + LOG_WRN("Invalid MTU %d", dlc->mtu); + bt_rfcomm_dlc_disconnect(dlc); + return; + } + goep->obex.rx.mtu = dlc->mtu; + goep->obex.tx.mtu = dlc->mtu; + + /* Set MOPL of RX to MTU by default */ + goep->obex.rx.mopl = dlc->mtu; + /* Set MOPL of TX to MTU by default to avoid the OBEX connect req cannot be sent. */ + goep->obex.tx.mopl = dlc->mtu; + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTED); + + err = bt_obex_transport_connected(&goep->obex); + if (err) { + LOG_WRN("Notify OBEX (err %d). Disconnecting transport...", err); + bt_rfcomm_dlc_disconnect(dlc); + return; + } + + if (goep->transport_ops->connected) { + goep->transport_ops->connected(goep->_acl, goep); + } +} + +static void goep_rfcomm_disconnected(struct bt_rfcomm_dlc *dlc) +{ + struct bt_goep *goep = CONTAINER_OF(dlc, struct bt_goep, _transport.dlc); + int err; + + LOG_DBG("RFCOMM %p disconnected", dlc); + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_DISCONNECTED); + + err = bt_obex_transport_disconnected(&goep->obex); + if (err) { + LOG_WRN("Notify OBEX (err %d).", err); + } + + if (goep->transport_ops->disconnected) { + goep->transport_ops->disconnected(goep); + } +} + +static struct bt_rfcomm_dlc_ops goep_rfcomm_ops = { + .recv = goep_rfcomm_recv, + .connected = goep_rfcomm_connected, + .disconnected = goep_rfcomm_disconnected, +}; + +static int goep_rfcomm_send(struct bt_obex *obex, struct net_buf *buf) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + + if (goep->_goep_v2) { + LOG_WRN("Invalid transport"); + return -EINVAL; + } + + if (buf->len > obex->tx.mtu) { + LOG_WRN("Packet size exceeds MTU"); + return -EMSGSIZE; + } + + if (net_buf_tailroom(buf) < BT_RFCOMM_FCS_SIZE) { + LOG_WRN("No tailroom for RFCOMM FCS field"); + return -EMSGSIZE; + } + + return bt_rfcomm_dlc_send(&goep->_transport.dlc, buf); +} + +static struct net_buf *goep_rfcomm_alloc_buf(struct bt_obex *obex, struct net_buf_pool *pool) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + + if (goep->_goep_v2) { + LOG_WRN("Invalid transport"); + return NULL; + } + + return bt_goep_create_pdu(goep, pool); +} + +static int geop_rfcomm_disconnect(struct bt_obex *obex) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + + if (goep->_goep_v2) { + LOG_WRN("Invalid transport"); + return -EINVAL; + } + + return bt_rfcomm_dlc_disconnect(&goep->_transport.dlc); +} + +static const struct bt_obex_transport_ops goep_rfcomm_transport_ops = { + .alloc_buf = goep_rfcomm_alloc_buf, + .send = goep_rfcomm_send, + .disconnect = geop_rfcomm_disconnect, +}; + +static bool goep_find_rfcomm_server(sys_slist_t *servers, + struct bt_goep_transport_rfcomm_server *server) +{ + struct bt_goep_transport_rfcomm_server *rfcomm_server, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(servers, rfcomm_server, next, node) { + if (rfcomm_server == server) { + return true; + } + } + return false; +} + +static int goep_rfcomm_accept(struct bt_conn *conn, struct bt_rfcomm_server *server, + struct bt_rfcomm_dlc **dlc) +{ + struct bt_goep_transport_rfcomm_server *rfcomm_server; + struct bt_goep *goep; + int err; + + rfcomm_server = CONTAINER_OF(server, struct bt_goep_transport_rfcomm_server, rfcomm); + + if (!goep_find_rfcomm_server(&goep_rfcomm_server, rfcomm_server)) { + LOG_WRN("Invalid rfcomm server"); + return -ENOMEM; + } + + err = rfcomm_server->accept(conn, rfcomm_server, &goep); + if (err) { + LOG_DBG("Incoming connection rejected"); + return err; + } + + if (!goep || !goep->transport_ops || (goep->obex.rx.mtu < GOEP_MIN_MTU) || + !goep->obex.server_ops || !goep->obex.server_ops->connect || + !goep->obex.server_ops->disconnect) { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + err = bt_obex_reg_transport(&goep->obex, &goep_rfcomm_transport_ops); + if (err) { + LOG_WRN("Fail to reg transport ops"); + return err; + } + + goep->_goep_v2 = false; + goep->_acl = conn; + *dlc = &goep->_transport.dlc; + goep->_transport.dlc.mtu = goep->obex.rx.mtu; + goep->_transport.dlc.ops = &goep_rfcomm_ops; + goep->_transport.dlc.required_sec_level = BT_SECURITY_L2; + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTING); + + return 0; +} + +int bt_goep_transport_rfcomm_server_register(struct bt_goep_transport_rfcomm_server *server) +{ + int err; + + if (!server || !server->accept) { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + if (goep_find_rfcomm_server(&goep_rfcomm_server, server)) { + LOG_WRN("RFCOMM server has been registered"); + return -EEXIST; + } + + server->rfcomm.accept = goep_rfcomm_accept; + err = bt_rfcomm_server_register(&server->rfcomm); + if (err) { + LOG_WRN("Fail to register RFCOMM Server %p", server); + return err; + } + + LOG_DBG("Register RFCOMM server %p", server); + sys_slist_append(&goep_rfcomm_server, &server->node); + + return 0; +} + +int bt_goep_transport_rfcomm_connect(struct bt_conn *conn, struct bt_goep *goep, uint8_t channel) +{ + int err; + + if (!conn || !goep || !goep->transport_ops || (goep->obex.rx.mtu < GOEP_MIN_MTU) || + !goep->obex.client_ops || !goep->obex.client_ops->connect || + !goep->obex.client_ops->disconnect) { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + err = bt_obex_reg_transport(&goep->obex, &goep_rfcomm_transport_ops); + if (err) { + LOG_WRN("Fail to reg transport ops"); + return err; + } + + goep->_goep_v2 = false; + goep->_acl = conn; + goep->_transport.dlc.mtu = goep->obex.rx.mtu; + goep->_transport.dlc.ops = &goep_rfcomm_ops; + goep->_transport.dlc.required_sec_level = BT_SECURITY_L2; + + err = bt_rfcomm_dlc_connect(conn, &goep->_transport.dlc, channel); + if (err) { + LOG_WRN("Fail to create RFCOMM connection"); + return err; + } + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTING); + + return 0; +} + +int bt_goep_transport_rfcomm_disconnect(struct bt_goep *goep) +{ + int err; + uint32_t state; + + if (!goep || goep->_goep_v2) { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + state = atomic_get(&goep->_state); + if (state != BT_GOEP_TRANSPORT_CONNECTED) { + LOG_DBG("Invalid stats %d", state); + return -ENOTCONN; + } + + err = bt_rfcomm_dlc_disconnect(&goep->_transport.dlc); + if (err) { + LOG_WRN("Fail to disconnect RFCOMM DLC"); + return err; + } + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_DISCONNECTING); + + return 0; +} + +/* L2CAP Server list */ +static sys_slist_t goep_l2cap_server; + +static int goep_l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf) +{ + struct bt_goep *goep = CONTAINER_OF(chan, struct bt_goep, _transport.chan.chan); + int err; + + err = bt_obex_recv(&goep->obex, buf); + if (err) { + LOG_WRN("Fail to handle OBEX packet (err %d)", err); + } + return err; +} + +static void goep_l2cap_connected(struct bt_l2cap_chan *chan) +{ + struct bt_goep *goep = CONTAINER_OF(chan, struct bt_goep, _transport.chan.chan); + int err; + + LOG_DBG("L2CAP channel %p connected", chan); + + if ((goep->_transport.chan.tx.mtu < GOEP_MIN_MTU) || + (goep->_transport.chan.rx.mtu < GOEP_MIN_MTU)) { + LOG_WRN("Invalid MTU (local %d, peer %d", goep->_transport.chan.rx.mtu, + goep->_transport.chan.tx.mtu); + bt_l2cap_chan_disconnect(chan); + return; + } + + goep->obex.rx.mtu = goep->_transport.chan.rx.mtu; + goep->obex.tx.mtu = goep->_transport.chan.tx.mtu; + + /* Set MOPL of RX to MTU by default */ + goep->obex.rx.mopl = goep->_transport.chan.rx.mtu; + /* Set MOPL of TX to MTU by default to avoid the OBEX connect req cannot be sent. */ + goep->obex.tx.mopl = goep->_transport.chan.tx.mtu; + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTED); + + err = bt_obex_transport_connected(&goep->obex); + if (err) { + LOG_WRN("Notify OBEX (err %d). Disconnecting transport...", err); + bt_l2cap_chan_disconnect(chan); + return; + } + + if (goep->transport_ops->connected) { + goep->transport_ops->connected(goep->_acl, goep); + } +} + +static void goep_l2cap_disconnected(struct bt_l2cap_chan *chan) +{ + struct bt_goep *goep = CONTAINER_OF(chan, struct bt_goep, _transport.chan.chan); + int err; + + LOG_DBG("L2CAP channel %p disconnected", chan); + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_DISCONNECTED); + + err = bt_obex_transport_disconnected(&goep->obex); + if (err) { + LOG_WRN("Notify OBEX (err %d).", err); + } + + if (goep->transport_ops->disconnected) { + goep->transport_ops->disconnected(goep); + } +} + +static const struct bt_l2cap_chan_ops goep_l2cap_ops = { + .recv = goep_l2cap_recv, + .connected = goep_l2cap_connected, + .disconnected = goep_l2cap_disconnected, +}; + +static int goep_l2cap_send(struct bt_obex *obex, struct net_buf *buf) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + + if (!goep->_goep_v2) { + LOG_WRN("Invalid transport"); + return -EINVAL; + } + + if (buf->len > obex->tx.mtu) { + LOG_WRN("Packet size exceeds MTU"); + return -EMSGSIZE; + } + + return bt_l2cap_chan_send(&goep->_transport.chan.chan, buf); +} + +static struct net_buf *goep_l2cap_alloc_buf(struct bt_obex *obex, struct net_buf_pool *pool) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + + if (goep->_goep_v2) { + LOG_WRN("Invalid transport"); + return NULL; + } + + return bt_goep_create_pdu(goep, pool); +} + +static int geop_l2cap_disconnect(struct bt_obex *obex) +{ + struct bt_goep *goep = CONTAINER_OF(obex, struct bt_goep, obex); + + if (goep->_goep_v2) { + LOG_WRN("Invalid transport"); + return -EINVAL; + } + + return bt_l2cap_chan_disconnect(&goep->_transport.chan.chan); +} + +static const struct bt_obex_transport_ops goep_l2cap_transport_ops = { + .alloc_buf = goep_l2cap_alloc_buf, + .send = goep_l2cap_send, + .disconnect = geop_l2cap_disconnect, +}; + +static bool goep_find_l2cap_server(sys_slist_t *servers, + struct bt_goep_transport_l2cap_server *server) +{ + struct bt_goep_transport_l2cap_server *l2cap_server, *next; + + SYS_SLIST_FOR_EACH_CONTAINER_SAFE(servers, l2cap_server, next, node) { + if (l2cap_server == server) { + return true; + } + } + return false; +} + +static int goep_l2cap_accept(struct bt_conn *conn, struct bt_l2cap_server *server, + struct bt_l2cap_chan **chan) +{ + struct bt_goep_transport_l2cap_server *l2cap_server; + struct bt_goep *goep; + int err; + + l2cap_server = CONTAINER_OF(server, struct bt_goep_transport_l2cap_server, l2cap); + + if (!goep_find_l2cap_server(&goep_l2cap_server, l2cap_server)) { + LOG_WRN("Invalid l2cap server"); + return -ENOMEM; + } + + err = l2cap_server->accept(conn, l2cap_server, &goep); + if (err) { + LOG_DBG("Incoming connection rejected"); + return err; + } + + if (!goep || !goep->transport_ops || (goep->obex.rx.mtu < GOEP_MIN_MTU) || + !goep->obex.server_ops || !goep->obex.server_ops->connect || + !goep->obex.server_ops->disconnect) { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + err = bt_obex_reg_transport(&goep->obex, &goep_l2cap_transport_ops); + if (err) { + LOG_WRN("Fail to reg transport ops"); + return err; + } + + goep->_goep_v2 = true; + goep->_acl = conn; + *chan = &goep->_transport.chan.chan; + goep->_transport.chan.rx.mtu = goep->obex.rx.mtu; + goep->_transport.chan.chan.ops = &goep_l2cap_ops; + goep->_transport.chan.required_sec_level = BT_SECURITY_L2; + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTING); + + return 0; +} + +int bt_goep_transport_l2cap_server_register(struct bt_goep_transport_l2cap_server *server) +{ + int err; + + if (!server || !server->accept) { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + if (goep_find_l2cap_server(&goep_l2cap_server, server)) { + LOG_WRN("L2CAP server has been registered"); + return -EEXIST; + } + + server->l2cap.accept = goep_l2cap_accept; + err = bt_l2cap_br_server_register(&server->l2cap); + if (err) { + LOG_WRN("Fail to register L2CAP Server %p", server); + return err; + } + + LOG_DBG("Register L2CAP server %p", server); + sys_slist_append(&goep_l2cap_server, &server->node); + + return 0; +} + +int bt_goep_transport_l2cap_connect(struct bt_conn *conn, struct bt_goep *goep, uint16_t psm) +{ + int err; + uint32_t state; + + if (!conn || !goep || !goep->transport_ops || (goep->obex.rx.mtu < GOEP_MIN_MTU) || + !goep->obex.client_ops || !goep->obex.client_ops->connect || + !goep->obex.client_ops->disconnect) { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + state = atomic_get(&goep->_state); + if (state != BT_GOEP_TRANSPORT_DISCONNECTED) { + LOG_DBG("Invalid stats %d", state); + return -EBUSY; + } + + err = bt_obex_reg_transport(&goep->obex, &goep_l2cap_transport_ops); + if (err) { + LOG_WRN("Fail to reg transport ops"); + return err; + } + + goep->_goep_v2 = true; + goep->_acl = conn; + goep->_transport.chan.rx.mtu = goep->obex.rx.mtu; + goep->_transport.chan.chan.ops = &goep_l2cap_ops; + goep->_transport.chan.required_sec_level = BT_SECURITY_L2; + + err = bt_l2cap_chan_connect(conn, &goep->_transport.chan.chan, psm); + if (err) { + LOG_WRN("Fail to create L2CAP connection"); + return err; + } + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_CONNECTING); + + return 0; +} + +int bt_goep_transport_l2cap_disconnect(struct bt_goep *goep) +{ + int err; + uint32_t state; + + if (!goep || !goep->_goep_v2) { + LOG_DBG("Invalid parameter"); + return -EINVAL; + } + + state = atomic_get(&goep->_state); + if (state != BT_GOEP_TRANSPORT_CONNECTED) { + LOG_DBG("Invalid stats %d", state); + return -ENOTCONN; + } + + err = bt_l2cap_chan_disconnect(&goep->_transport.chan.chan); + if (err) { + LOG_WRN("Fail to disconnect L2CAP channel"); + return err; + } + + atomic_set(&goep->_state, BT_GOEP_TRANSPORT_DISCONNECTING); + + return 0; +} + +struct net_buf *bt_goep_create_pdu(struct bt_goep *goep, struct net_buf_pool *pool) +{ + struct net_buf *buf; + size_t len; + + if (!goep) { + LOG_WRN("Invalid parameter"); + return NULL; + } + + if (!goep->_goep_v2) { + buf = bt_rfcomm_create_pdu(pool); + } else { + buf = bt_conn_create_pdu(pool, sizeof(struct bt_l2cap_hdr)); + } + + if (buf) { + len = net_buf_headroom(buf); + net_buf_reserve(buf, len + BT_OBEX_SEND_BUF_RESERVE); + } + return buf; +} diff --git a/subsys/bluetooth/host/classic/obex.c b/subsys/bluetooth/host/classic/obex.c new file mode 100644 index 000000000000..e21ee241da40 --- /dev/null +++ b/subsys/bluetooth/host/classic/obex.c @@ -0,0 +1,3210 @@ +/* obex.c - IrDA Oject Exchange Protocol handling */ + +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "obex_internal.h" + +#define LOG_LEVEL CONFIG_BT_GOEP_LOG_LEVEL +#include +LOG_MODULE_REGISTER(bt_obex); + +static struct net_buf *obex_alloc_buf(struct bt_obex *obex) +{ + struct net_buf *buf; + + if (!obex->_transport_ops || !obex->_transport_ops->alloc_buf) { + LOG_WRN("Buffer allocation is unsupported"); + return NULL; + } + + buf = obex->_transport_ops->alloc_buf(obex, NULL); + if (!buf) { + LOG_WRN("Fail to alloc buffer"); + } + return buf; +} + +static int obex_send(struct bt_obex *obex, struct net_buf *buf) +{ + if (!obex->_transport_ops || !obex->_transport_ops->send) { + LOG_WRN("OBEX sending is unsupported"); + return -EIO; + } + + if (buf->len > obex->tx.mopl) { + LOG_WRN("Data length too long (%d > %d)", buf->len, obex->tx.mopl); + return -EMSGSIZE; + } + + return obex->_transport_ops->send(obex, buf); +} + +static int obex_transport_disconn(struct bt_obex *obex) +{ + int err; + + if (!obex->_transport_ops || !obex->_transport_ops->disconnect) { + LOG_WRN("OBEX transport disconn is unsupported"); + return -EIO; + } + + err = obex->_transport_ops->disconnect(obex); + if (err) { + LOG_ERR("Fail to disconnect transport (err %d)", err); + } + return -EINVAL; +} + +struct server_handler { + uint8_t opcode; + int (*handler)(struct bt_obex *obex, uint16_t len, struct net_buf *buf); +}; + +static int obex_server_connect(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + struct bt_obex_conn_req_hdr *hdr; + struct bt_obex_conn_rsp_hdr *rsp_conn_hdr; + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t version; + uint8_t flags; + uint16_t mopl; + uint8_t rsp_code; + + LOG_DBG(""); + + if ((len != buf->len) || (buf->len < sizeof(*hdr))) { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_get(&obex->_state) != BT_OBEX_DISCONNECTED) { + LOG_WRN("Invalid state, connect refused"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!obex->server_ops->connect) { + LOG_WRN("Conn req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + if (!atomic_cas(&obex->_opcode, 0, BT_OBEX_OPCODE_CONNECT)) { + LOG_WRN("Unexpected conn request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + version = net_buf_pull_u8(buf); + flags = net_buf_pull_u8(buf); + mopl = net_buf_pull_be16(buf); + + if (mopl < BT_OBEX_MIN_MTU) { + LOG_WRN("Invalid MTU length (%d < %d)", mopl, BT_OBEX_MIN_MTU); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + obex->tx.mopl = mopl; + + atomic_set(&obex->_state, BT_OBEX_CONNECTING); + obex->server_ops->connect(obex, version, mopl, buf); + return 0; + +failed: + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_conn_hdr = net_buf_add(buf, sizeof(*rsp_conn_hdr)); + rsp_conn_hdr->flags = 0; + rsp_conn_hdr->mopl = 0; + rsp_conn_hdr->version = BT_OBEX_VERSION; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + return obex_send(obex, buf); +} + +static int obex_server_disconn(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!obex->server_ops->disconnect) { + LOG_WRN("Conn req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + if (!atomic_cas(&obex->_opcode, 0, BT_OBEX_OPCODE_DISCONN)) { + LOG_WRN("Unexpected disconn request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + atomic_set(&obex->_state, BT_OBEX_DISCONNECTING); + obex->server_ops->disconnect(obex, buf); + return 0; + +failed: + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + return obex_send(obex, buf); +} + +static int obex_server_put_common(struct bt_obex *obex, bool final, uint16_t len, + struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + uint8_t req_code; + uint8_t opcode; + + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!obex->server_ops->put) { + LOG_WRN("Put req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + req_code = final ? BT_OBEX_OPCODE_PUT_F : BT_OBEX_OPCODE_PUT; + if (!atomic_cas(&obex->_opcode, 0, req_code)) { + opcode = atomic_get(&obex->_opcode); + if ((opcode != BT_OBEX_OPCODE_PUT_F) && (opcode != BT_OBEX_OPCODE_PUT)) { + LOG_WRN("Unexpected put request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!final && (opcode == BT_OBEX_OPCODE_PUT_F)) { + LOG_WRN("Unexpected put request without final bit"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (opcode != req_code) { + atomic_cas(&obex->_opcode, opcode, req_code); + } + } + + obex->server_ops->put(obex, final, buf); + return 0; + +failed: + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + return obex_send(obex, buf); +} + +static int obex_server_put(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + return obex_server_put_common(obex, false, len, buf); +} + +static int obex_server_put_final(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + return obex_server_put_common(obex, true, len, buf); +} + +static int obex_server_get_common(struct bt_obex *obex, bool final, uint16_t len, + struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + uint8_t req_code; + uint8_t opcode; + + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!obex->server_ops->get) { + LOG_WRN("Get req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + req_code = final ? BT_OBEX_OPCODE_GET_F : BT_OBEX_OPCODE_GET; + if (!atomic_cas(&obex->_opcode, 0, req_code)) { + opcode = atomic_get(&obex->_opcode); + if ((opcode != BT_OBEX_OPCODE_GET_F) && (opcode != BT_OBEX_OPCODE_GET)) { + LOG_WRN("Unexpected get request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!final && (opcode == BT_OBEX_OPCODE_GET_F)) { + LOG_WRN("Unexpected get request without final bit"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (opcode != req_code) { + atomic_cas(&obex->_opcode, opcode, req_code); + } + } + + obex->server_ops->get(obex, final, buf); + return 0; + +failed: + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + return obex_send(obex, buf); +} + +static int obex_server_get(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + return obex_server_get_common(obex, false, len, buf); +} + +static int obex_server_get_final(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + return obex_server_get_common(obex, true, len, buf); +} + +static int obex_server_setpath(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + struct bt_obex_setpath_req_hdr *req_hdr; + uint8_t rsp_code; + + LOG_DBG(""); + + if ((len != buf->len) || (buf->len < sizeof(*req_hdr))) { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!obex->server_ops->setpath) { + LOG_WRN("Setpath req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + if (!atomic_cas(&obex->_opcode, 0, BT_OBEX_OPCODE_SETPATH)) { + LOG_WRN("Unexpected setpath request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + req_hdr = net_buf_pull_mem(buf, sizeof(*req_hdr)); + obex->server_ops->setpath(obex, req_hdr->flags, buf); + return 0; + +failed: + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + return obex_send(obex, buf); +} + +static int obex_server_action_common(struct bt_obex *obex, bool final, uint16_t len, + struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + uint8_t req_code; + uint8_t opcode; + + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!obex->server_ops->action) { + LOG_WRN("Action req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + req_code = final ? BT_OBEX_OPCODE_ACTION_F : BT_OBEX_OPCODE_ACTION; + if (!atomic_cas(&obex->_opcode, 0, req_code)) { + opcode = atomic_get(&obex->_opcode); + if ((opcode != BT_OBEX_OPCODE_ACTION_F) && (opcode != BT_OBEX_OPCODE_ACTION)) { + LOG_WRN("Unexpected action request"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!final && (opcode == BT_OBEX_OPCODE_ACTION_F)) { + LOG_WRN("Unexpected action request without final bit"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (opcode != req_code) { + atomic_cas(&obex->_opcode, opcode, req_code); + } + } + + obex->server_ops->action(obex, final, buf); + return 0; + +failed: + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + return obex_send(obex, buf); +} + +static int obex_server_action(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + return obex_server_action_common(obex, false, len, buf); +} + +static int obex_server_action_final(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + return obex_server_action_common(obex, true, len, buf); +} + +static int obex_server_session(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + LOG_WRN("Action req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + +failed: + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + return obex_send(obex, buf); +} + +static int obex_server_abort(struct bt_obex *obex, uint16_t len, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *rsp_hdr; + uint8_t rsp_code; + + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + rsp_code = BT_OBEX_RSP_CODE_PRECON_FAIL; + goto failed; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + rsp_code = BT_OBEX_RSP_CODE_FORBIDDEN; + goto failed; + } + + if (!obex->server_ops->abort) { + LOG_WRN("Abort req handling not implemented"); + rsp_code = BT_OBEX_RSP_CODE_NOT_IMPL; + goto failed; + } + + atomic_set(&obex->_opcode, BT_OBEX_OPCODE_ABORT); + + obex->server_ops->abort(obex, buf); + return 0; + +failed: + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("Cannot allocate buffer"); + return -ENOBUFS; + } + + rsp_hdr = net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = rsp_code; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + return obex_send(obex, buf); +} + +struct server_handler server_handler[] = { + {BT_OBEX_OPCODE_CONNECT, obex_server_connect}, + {BT_OBEX_OPCODE_DISCONN, obex_server_disconn}, + {BT_OBEX_OPCODE_PUT, obex_server_put}, + {BT_OBEX_OPCODE_PUT_F, obex_server_put_final}, + {BT_OBEX_OPCODE_GET, obex_server_get}, + {BT_OBEX_OPCODE_GET_F, obex_server_get_final}, + {BT_OBEX_OPCODE_SETPATH, obex_server_setpath}, + {BT_OBEX_OPCODE_ACTION, obex_server_action}, + {BT_OBEX_OPCODE_ACTION_F, obex_server_action_final}, + {BT_OBEX_OPCODE_SESSION, obex_server_session}, + {BT_OBEX_OPCODE_ABORT, obex_server_abort}, +}; + +static int obex_server_recv(struct bt_obex *obex, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + struct bt_obex_rsp_hdr *rsp_hdr; + uint16_t len; + + if (buf->len < sizeof(*hdr)) { + LOG_WRN("Too small header size (%d < %d)", buf->len, sizeof(*hdr)); + goto failed; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + len = sys_be16_to_cpu(hdr->len); + + for (size_t i = 0; i < ARRAY_SIZE(server_handler); i++) { + if (server_handler[i].opcode == hdr->code) { + int err; + + err = server_handler[i].handler(obex, len - sizeof(*hdr), buf); + if (err) { + LOG_WRN("Handler err %d", err); + } + return err; + } + } + +failed: + LOG_WRN("Unsupported request"); + buf = obex_alloc_buf(obex); + if (!buf) { + return -ENOBUFS; + } + + rsp_hdr = (void *)net_buf_add(buf, sizeof(*rsp_hdr)); + rsp_hdr->code = BT_OBEX_RSP_CODE_BAD_REQ; + rsp_hdr->len = sys_cpu_to_be16(buf->len); + + return obex_send(obex, buf); +} + +struct client_handler { + uint8_t opcode; + int (*handler)(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, struct net_buf *buf); +}; + +static int obex_client_connect(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + struct bt_obex_conn_rsp_hdr *rsp_conn_hdr; + uint8_t version = 0; + uint8_t flags = 0; + uint16_t mopl = 0; + + LOG_DBG(""); + + atomic_clear(&obex->_opcode); + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTING) { + return -EINVAL; + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) { + if ((len != buf->len) || (buf->len < sizeof(*rsp_conn_hdr))) { + LOG_WRN("Invalid packet size"); + goto failed; + } else { + version = net_buf_pull_u8(buf); + flags = net_buf_pull_u8(buf); + mopl = net_buf_pull_be16(buf); + if (mopl < BT_OBEX_MIN_MTU) { + LOG_WRN("Invalid MTU length (%d < %d)", mopl, BT_OBEX_MIN_MTU); + goto failed; + } + obex->tx.mopl = mopl; + } + } + + atomic_set(&obex->_state, + rsp_code == BT_OBEX_RSP_CODE_SUCCESS ? BT_OBEX_CONNECTED : BT_OBEX_DISCONNECTED); + + if (obex->client_ops->connect) { + obex->client_ops->connect(obex, rsp_code, version, mopl, buf); + } + return 0; + +failed: + LOG_WRN("Disconnect transport"); + + return obex_transport_disconn(obex); +} + +static int obex_client_disconn(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + LOG_DBG(""); + + atomic_clear(&obex->_opcode); + + if (atomic_get(&obex->_state) != BT_OBEX_DISCONNECTING) { + return -EINVAL; + } + + atomic_set(&obex->_state, + rsp_code == BT_OBEX_RSP_CODE_SUCCESS ? BT_OBEX_DISCONNECTED : BT_OBEX_CONNECTED); + + if (obex->client_ops->disconnect) { + obex->client_ops->disconnect(obex, rsp_code, buf); + } + return 0; +} + +static int obex_client_put_common(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (!obex->client_ops->put) { + LOG_WRN("Put rsp handling not implemented"); + return -ENOTSUP; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) { + atomic_clear(&obex->_opcode); + } + + obex->client_ops->put(obex, rsp_code, buf); + return 0; +} + +static int obex_client_put(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_put_common(obex, rsp_code, len, buf); +} + +static int obex_client_put_final(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_put_common(obex, rsp_code, len, buf); +} + +static int obex_client_get_common(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (!obex->client_ops->get) { + LOG_WRN("Get rsp handling not implemented"); + return -ENOTSUP; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) { + atomic_clear(&obex->_opcode); + } + + obex->client_ops->get(obex, rsp_code, buf); + return 0; +} + +static int obex_client_get(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_get_common(obex, rsp_code, len, buf); +} + +static int obex_client_get_final(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_get_common(obex, rsp_code, len, buf); +} + +static int obex_client_setpath(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (!obex->client_ops->setpath) { + LOG_WRN("Setpath rsp handling not implemented"); + return -ENOTSUP; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) { + atomic_clear(&obex->_opcode); + } + + obex->client_ops->setpath(obex, rsp_code, buf); + return 0; +} + +static int obex_client_action_common(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (!obex->client_ops->action) { + LOG_WRN("Setpath rsp handling not implemented"); + return -ENOTSUP; + } + + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) { + atomic_clear(&obex->_opcode); + } + + obex->client_ops->action(obex, rsp_code, buf); + return 0; +} + +static int obex_client_action(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_action_common(obex, rsp_code, len, buf); +} + +static int obex_client_action_final(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return obex_client_action_common(obex, rsp_code, len, buf); +} + +static int obex_client_session(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + return -ENOTSUP; +} + +static struct client_handler *obex_client_find_handler(uint8_t opcode); + +static int obex_client_abort(struct bt_obex *obex, uint8_t rsp_code, uint16_t len, + struct net_buf *buf) +{ + LOG_DBG(""); + + if (len != buf->len) { + LOG_WRN("Invalid packet size"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (len != 0) { + struct client_handler *handler; + int err = -EINVAL; + + handler = obex_client_find_handler(atomic_get(&obex->_pre_opcode)); + if (handler) { + err = handler->handler(obex, rsp_code, len, buf); + if (err) { + LOG_WRN("Handler err %d", err); + } + } + return err; + } + + if (!obex->client_ops->abort) { + LOG_WRN("Abort rsp handling not implemented"); + return -ENOTSUP; + } + + atomic_clear(&obex->_opcode); + + if (rsp_code != BT_OBEX_RSP_CODE_SUCCESS) { + LOG_WRN("Disconnect transport"); + + return obex_transport_disconn(obex); + } + + obex->client_ops->abort(obex, rsp_code, buf); + return 0; +} + +struct client_handler client_handler[] = { + {BT_OBEX_OPCODE_CONNECT, obex_client_connect}, + {BT_OBEX_OPCODE_DISCONN, obex_client_disconn}, + {BT_OBEX_OPCODE_PUT, obex_client_put}, + {BT_OBEX_OPCODE_PUT_F, obex_client_put_final}, + {BT_OBEX_OPCODE_GET, obex_client_get}, + {BT_OBEX_OPCODE_GET_F, obex_client_get_final}, + {BT_OBEX_OPCODE_SETPATH, obex_client_setpath}, + {BT_OBEX_OPCODE_ACTION, obex_client_action}, + {BT_OBEX_OPCODE_ACTION_F, obex_client_action_final}, + {BT_OBEX_OPCODE_SESSION, obex_client_session}, + {BT_OBEX_OPCODE_ABORT, obex_client_abort}, +}; + +static struct client_handler *obex_client_find_handler(uint8_t opcode) +{ + for (size_t i = 0; i < ARRAY_SIZE(client_handler); i++) { + if (client_handler[i].opcode == opcode) { + return &client_handler[i]; + } + } + return NULL; +} + +static int obex_client_recv(struct bt_obex *obex, struct net_buf *buf) +{ + struct client_handler *handler; + struct bt_obex_rsp_hdr *hdr; + uint16_t len; + int err; + + if (buf->len < sizeof(*hdr)) { + LOG_WRN("Too small header size (%d < %d)", buf->len, sizeof(*hdr)); + return -EINVAL; + } + + hdr = net_buf_pull_mem(buf, sizeof(*hdr)); + len = sys_be16_to_cpu(hdr->len); + handler = obex_client_find_handler(atomic_get(&obex->_opcode)); + if (handler) { + err = handler->handler(obex, hdr->code, len - sizeof(*hdr), buf); + if (err) { + LOG_WRN("Handler err %d", err); + } + return err; + } + + LOG_WRN("Unknown OBEX rsp (rsp_code %02x, len %d)", hdr->code, len); + return -EINVAL; +} + +int bt_obex_transport_connected(struct bt_obex *obex) +{ + if (!obex) { + return -EINVAL; + } + + if (obex->rx.mtu < BT_OBEX_MIN_MTU) { + LOG_WRN("Invalid MTU (%d < %d)", obex->rx.mtu, BT_OBEX_MIN_MTU); + return -EINVAL; + } + + if (obex->tx.mtu < BT_OBEX_MIN_MTU) { + LOG_WRN("Invalid MTU (%d < %d)", obex->tx.mtu, BT_OBEX_MIN_MTU); + return -EINVAL; + } + + atomic_clear(&obex->_opcode); + atomic_clear(&obex->_pre_opcode); + atomic_set(&obex->_state, BT_OBEX_DISCONNECTED); + + return 0; +} + +int bt_obex_transport_disconnected(struct bt_obex *obex) +{ + if (obex) { + atomic_clear(&obex->_opcode); + atomic_clear(&obex->_pre_opcode); + atomic_set(&obex->_state, BT_OBEX_DISCONNECTED); + obex->_transport_ops = NULL; + return 0; + } + + return -EINVAL; +} + +int bt_obex_reg_transport(struct bt_obex *obex, const struct bt_obex_transport_ops *ops) +{ + if (!obex || !ops) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + obex->_transport_ops = ops; + return 0; +} + +int bt_obex_recv(struct bt_obex *obex, struct net_buf *buf) +{ + if (!obex || !buf) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (obex->server_ops) { + return obex_server_recv(obex, buf); + } else if (obex->client_ops) { + return obex_client_recv(obex, buf); + } + + LOG_WRN("Unknown role of OBEX %p", obex); + return -EINVAL; +} + +int bt_obex_connect(struct bt_obex *obex, uint16_t mopl, struct net_buf *buf) +{ + struct bt_obex_conn_req_hdr *req_hdr; + struct bt_obex_req_hdr *hdr; + int err; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->client_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (mopl > obex->rx.mtu) { + mopl = obex->rx.mtu; + } + + if (mopl < BT_OBEX_MIN_MTU) { + LOG_WRN("Invalid MOPL"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_DISCONNECTED) { + LOG_WRN("Invalid state, connect is %s", + atomic_get(&obex->_state) == BT_OBEX_CONNECTING ? "ongoing" + : "established"); + return -EINPROGRESS; + } + + if (!atomic_cas(&obex->_opcode, 0, BT_OBEX_OPCODE_CONNECT)) { + LOG_WRN("Operation inprogress"); + return -EBUSY; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + obex->rx.mopl = mopl; + atomic_set(&obex->_state, BT_OBEX_CONNECTING); + + req_hdr = net_buf_push(buf, sizeof(*req_hdr)); + req_hdr->flags = 0; + req_hdr->mopl = sys_cpu_to_be16(mopl); + req_hdr->version = BT_OBEX_VERSION; + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = BT_OBEX_OPCODE_CONNECT; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (err) { + atomic_set(&obex->_state, BT_OBEX_DISCONNECTED); + atomic_clear(&obex->_opcode); + } + return err; +} + +int bt_obex_connect_rsp(struct bt_obex *obex, uint8_t rsp_code, uint16_t mopl, struct net_buf *buf) +{ + struct bt_obex_conn_rsp_hdr *rsp_hdr; + struct bt_obex_rsp_hdr *hdr; + int err; + atomic_val_t old_state; + + if (!obex || (rsp_code == BT_OBEX_RSP_CODE_CONTINUE)) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->server_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (mopl > obex->rx.mtu) { + mopl = obex->rx.mtu; + } + + if (mopl < BT_OBEX_MIN_MTU) { + LOG_WRN("Invalid MOPL"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTING) { + LOG_WRN("Invalid state"); + return -ENOTCONN; + } + + if (atomic_get(&obex->_opcode) != BT_OBEX_OPCODE_CONNECT) { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + obex->rx.mopl = mopl; + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) { + old_state = atomic_set(&obex->_state, BT_OBEX_CONNECTED); + } else { + old_state = atomic_set(&obex->_state, BT_OBEX_DISCONNECTED); + } + + rsp_hdr = net_buf_push(buf, sizeof(*rsp_hdr)); + rsp_hdr->flags = 0; + rsp_hdr->mopl = sys_cpu_to_be16(mopl); + rsp_hdr->version = BT_OBEX_VERSION; + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (err) { + atomic_set(&obex->_state, old_state); + } else { + atomic_clear(&obex->_opcode); + } + return err; +} + +int bt_obex_disconnect(struct bt_obex *obex, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + int err; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->client_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!atomic_cas(&obex->_opcode, 0, BT_OBEX_OPCODE_DISCONN)) { + LOG_WRN("Operation inprogress"); + return -EBUSY; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = BT_OBEX_OPCODE_DISCONN; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (err) { + atomic_clear(&obex->_opcode); + } else { + atomic_set(&obex->_state, BT_OBEX_DISCONNECTING); + } + return err; +} + +int bt_obex_disconnect_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + atomic_val_t old_state; + + if (!obex || (rsp_code == BT_OBEX_RSP_CODE_CONTINUE)) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->server_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_DISCONNECTING) { + LOG_WRN("Invalid state"); + return -EINVAL; + } + + if (atomic_get(&obex->_opcode) != BT_OBEX_OPCODE_DISCONN) { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + if (rsp_code == BT_OBEX_RSP_CODE_SUCCESS) { + old_state = atomic_set(&obex->_state, BT_OBEX_DISCONNECTED); + } else { + old_state = atomic_set(&obex->_state, BT_OBEX_CONNECTED); + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (err) { + atomic_set(&obex->_state, old_state); + } else { + atomic_clear(&obex->_opcode); + } + return err; +} + +int bt_obex_put(struct bt_obex *obex, bool final, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + int err; + uint8_t req_code; + uint8_t opcode; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->client_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + opcode = atomic_get(&obex->_opcode); + + req_code = final ? BT_OBEX_OPCODE_PUT_F : BT_OBEX_OPCODE_PUT; + if (!atomic_cas(&obex->_opcode, 0, req_code)) { + if ((opcode != BT_OBEX_OPCODE_PUT_F) && (opcode != BT_OBEX_OPCODE_PUT)) { + LOG_WRN("Operation inprogress"); + return -EBUSY; + } + + if (!final && (opcode == BT_OBEX_OPCODE_PUT_F)) { + LOG_WRN("Unexpected put request without final bit"); + return -EBUSY; + } + + if (opcode != req_code) { + atomic_cas(&obex->_opcode, opcode, req_code); + } + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = req_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (err) { + atomic_set(&obex->_opcode, opcode); + } + return err; +} + +int bt_obex_put_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + uint8_t opcode; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->server_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + opcode = atomic_get(&obex->_opcode); + if ((opcode != BT_OBEX_OPCODE_PUT_F) && (opcode != BT_OBEX_OPCODE_PUT)) { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if ((opcode != BT_OBEX_OPCODE_PUT_F) && (rsp_code == BT_OBEX_RSP_CODE_SUCCESS)) { + LOG_WRN("Success cannot be responded without final"); + return -EINVAL; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (!err) { + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) { + atomic_clear(&obex->_opcode); + } + } + return err; +} + +int bt_obex_get(struct bt_obex *obex, bool final, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + int err; + uint8_t req_code; + uint8_t opcode; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->client_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + opcode = atomic_get(&obex->_opcode); + + req_code = final ? BT_OBEX_OPCODE_GET_F : BT_OBEX_OPCODE_GET; + if (!atomic_cas(&obex->_opcode, 0, req_code)) { + if ((opcode != BT_OBEX_OPCODE_GET_F) && (opcode != BT_OBEX_OPCODE_GET)) { + LOG_WRN("Operation inprogress"); + return -EBUSY; + } + + if (!final && (opcode == BT_OBEX_OPCODE_GET_F)) { + LOG_WRN("Unexpected get request without final bit"); + return -EBUSY; + } + + if (opcode != req_code) { + atomic_cas(&obex->_opcode, opcode, req_code); + } + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = req_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (err) { + atomic_set(&obex->_opcode, opcode); + } + return err; +} + +int bt_obex_get_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + uint8_t opcode; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->server_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + opcode = atomic_get(&obex->_opcode); + if ((opcode != BT_OBEX_OPCODE_GET_F) && (opcode != BT_OBEX_OPCODE_GET)) { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if ((opcode != BT_OBEX_OPCODE_GET_F) && (rsp_code == BT_OBEX_RSP_CODE_SUCCESS)) { + LOG_WRN("Success cannot be responded without final"); + return -EINVAL; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (!err) { + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) { + atomic_clear(&obex->_opcode); + } + } + return err; +} + +static const uint8_t abort_opcode[] = { + BT_OBEX_OPCODE_PUT, + BT_OBEX_OPCODE_PUT_F, + BT_OBEX_OPCODE_GET, + BT_OBEX_OPCODE_GET_F, + BT_OBEX_OPCODE_ACTION, + BT_OBEX_OPCODE_ACTION_F +}; + +static bool obex_op_support_abort(uint8_t opcode) +{ + for (size_t index = 0; index < ARRAY_SIZE(abort_opcode); index++) { + if (opcode == abort_opcode[index]) { + return true; + } + } + return false; +} + +int bt_obex_abort(struct bt_obex *obex, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + int err; + uint8_t opcode; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->client_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!atomic_get(&obex->_opcode)) { + LOG_WRN("No operation is inprogress"); + return -EINVAL; + } else if (atomic_get(&obex->_opcode) == BT_OBEX_OPCODE_ABORT) { + LOG_WRN("Abort is inprogress"); + return -EINPROGRESS; + } else if (!obex_op_support_abort((uint8_t)atomic_get(&obex->_opcode))) { + LOG_WRN("Opcode %02x cannot be aborted", (uint8_t)atomic_get(&obex->_opcode)); + return -ENOTSUP; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + opcode = atomic_set(&obex->_opcode, BT_OBEX_OPCODE_ABORT); + atomic_clear(&obex->_pre_opcode); + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = BT_OBEX_OPCODE_ABORT; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (err) { + atomic_set(&obex->_opcode, opcode); + } else { + atomic_set(&obex->_pre_opcode, opcode); + } + return err; +} + +int bt_obex_abort_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->server_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (atomic_get(&obex->_opcode) != BT_OBEX_OPCODE_ABORT) { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (!err) { + atomic_clear(&obex->_opcode); + } + return err; +} + +int bt_obex_setpath(struct bt_obex *obex, uint8_t flags, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + struct bt_obex_setpath_req_hdr *req_hdr; + int err; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->client_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!atomic_cas(&obex->_opcode, 0, BT_OBEX_OPCODE_SETPATH)) { + LOG_WRN("Operation inprogress"); + return -EINPROGRESS; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + req_hdr = net_buf_push(buf, sizeof(*req_hdr)); + req_hdr->flags = flags; + req_hdr->constants = 0; + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = BT_OBEX_OPCODE_SETPATH; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (err) { + atomic_clear(&obex->_opcode); + } + return err; +} + +int bt_obex_setpath_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->server_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (atomic_get(&obex->_opcode) != BT_OBEX_OPCODE_SETPATH) { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (!err) { + atomic_clear(&obex->_opcode); + } + return err; +} + +int bt_obex_action(struct bt_obex *obex, bool final, struct net_buf *buf) +{ + struct bt_obex_req_hdr *hdr; + int err; + uint8_t req_code; + uint8_t opcode; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->client_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -ENOTCONN; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + opcode = atomic_get(&obex->_opcode); + + req_code = final ? BT_OBEX_OPCODE_ACTION_F : BT_OBEX_OPCODE_ACTION; + if (!atomic_cas(&obex->_opcode, 0, req_code)) { + if ((opcode != BT_OBEX_OPCODE_ACTION_F) && (opcode != BT_OBEX_OPCODE_ACTION)) { + LOG_WRN("Operation inprogress"); + return -EBUSY; + } + + if (!final && (opcode == BT_OBEX_OPCODE_ACTION_F)) { + LOG_WRN("Unexpected get request without final bit"); + return -EBUSY; + } + + if (opcode != req_code) { + atomic_cas(&obex->_opcode, opcode, req_code); + } + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = req_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (err) { + atomic_set(&obex->_opcode, opcode); + } + return err; +} + +int bt_obex_action_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_obex_rsp_hdr *hdr; + int err; + uint8_t opcode; + + if (!obex) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + if (!obex->server_ops) { + LOG_WRN("Invalid OBEX role"); + return -EINVAL; + } + + if (atomic_get(&obex->_state) != BT_OBEX_CONNECTED) { + LOG_WRN("Invalid state, connect is not established"); + return -EINVAL; + } + + if (!buf) { + buf = obex_alloc_buf(obex); + if (!buf) { + LOG_WRN("No buffers"); + return -ENOBUFS; + } + } + + opcode = atomic_get(&obex->_opcode); + if ((opcode != BT_OBEX_OPCODE_ACTION_F) && (opcode != BT_OBEX_OPCODE_ACTION)) { + LOG_WRN("Invalid response"); + return -EINVAL; + } + + if ((opcode != BT_OBEX_OPCODE_ACTION_F) && (rsp_code == BT_OBEX_RSP_CODE_SUCCESS)) { + LOG_WRN("Success cannot be responded without final"); + return -EINVAL; + } + + hdr = net_buf_push(buf, sizeof(*hdr)); + hdr->code = rsp_code; + hdr->len = sys_cpu_to_be16(buf->len); + + err = obex_send(obex, buf); + if (!err) { + if (rsp_code != BT_OBEX_RSP_CODE_CONTINUE) { + atomic_clear(&obex->_opcode); + } + } + return err; +} + +int bt_obex_add_header_count(struct net_buf *buf, uint32_t count) +{ + size_t total; + + if (!buf) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(count); + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_COUNT); + net_buf_add_be32(buf, count); + return 0; +} + +int bt_obex_add_header_name(struct net_buf *buf, uint16_t len, const uint8_t *name) +{ + size_t total; + + if (!buf || !name) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_NAME); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, name, len); + return 0; +} + +int bt_obex_add_header_type(struct net_buf *buf, uint16_t len, const uint8_t *type) +{ + size_t total; + + if (!buf || !type) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TYPE); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, type, len); + return 0; +} + +int bt_obex_add_header_len(struct net_buf *buf, uint32_t len) +{ + size_t total; + + if (!buf) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len); + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_LEN); + net_buf_add_be32(buf, len); + return 0; +} + +int bt_obex_add_header_time_iso_8601(struct net_buf *buf, uint16_t len, const uint8_t *t) +{ + size_t total; + + if (!buf || !t) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TIME_ISO_8601); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, t, len); + return 0; +} + +int bt_obex_add_header_time(struct net_buf *buf, uint32_t t) +{ + size_t total; + + if (!buf) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(t); + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TIME); + net_buf_add_be32(buf, t); + return 0; +} + +int bt_obex_add_header_description(struct net_buf *buf, uint16_t len, const uint8_t *dec) +{ + size_t total; + + if (!buf || !dec) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_DES); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, dec, len); + return 0; +} + +int bt_obex_add_header_target(struct net_buf *buf, uint16_t len, const uint8_t *target) +{ + size_t total; + + if (!buf || !target) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TARGET); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, target, len); + return 0; +} + +int bt_obex_add_header_http(struct net_buf *buf, uint16_t len, const uint8_t *http) +{ + size_t total; + + if (!buf || !http) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_HTTP); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, http, len); + return 0; +} + +int bt_obex_add_header_body(struct net_buf *buf, uint16_t len, const uint8_t *body) +{ + size_t total; + + if (!buf || !body) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_BODY); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, body, len); + return 0; +} + +int bt_obex_add_header_end_body(struct net_buf *buf, uint16_t len, const uint8_t *body) +{ + size_t total; + + if (!buf || !body) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_END_BODY); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, body, len); + return 0; +} + +int bt_obex_add_header_who(struct net_buf *buf, uint16_t len, const uint8_t *who) +{ + size_t total; + + if (!buf || !who) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_WHO); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, who, len); + return 0; +} + +int bt_obex_add_header_conn_id(struct net_buf *buf, uint32_t conn_id) +{ + size_t total; + + if (!buf) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(conn_id); + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_CONN_ID); + net_buf_add_be32(buf, conn_id); + return 0; +} + +int bt_obex_add_header_app_param(struct net_buf *buf, uint16_t len, const uint8_t *app_param) +{ + size_t total; + + if (!buf || !app_param) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_APP_PARAM); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, app_param, len); + return 0; +} + +int bt_obex_add_header_auth_challenge(struct net_buf *buf, uint16_t len, const uint8_t *auth) +{ + size_t total; + + if (!buf || !auth) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_AUTH_CHALLENGE); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, auth, len); + return 0; +} + +int bt_obex_add_header_auth_rsp(struct net_buf *buf, uint16_t len, const uint8_t *auth) +{ + size_t total; + + if (!buf || !auth) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_AUTH_RSP); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, auth, len); + return 0; +} + +int bt_obex_add_header_creator_id(struct net_buf *buf, uint32_t creator_id) +{ + size_t total; + + if (!buf) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(creator_id); + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_CREATE_ID); + net_buf_add_be32(buf, creator_id); + return 0; +} + +int bt_obex_add_header_wan_uuid(struct net_buf *buf, uint16_t len, const uint8_t *uuid) +{ + size_t total; + + if (!buf || !uuid) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_WAN_UUID); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, uuid, len); + return 0; +} + +int bt_obex_add_header_obj_class(struct net_buf *buf, uint16_t len, const uint8_t *obj_class) +{ + size_t total; + + if (!buf || !obj_class) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_OBJECT_CLASS); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, obj_class, len); + return 0; +} + +int bt_obex_add_header_session_param(struct net_buf *buf, uint16_t len, + const uint8_t *session_param) +{ + size_t total; + + if (!buf || !session_param) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_SESSION_PARAM); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, session_param, len); + return 0; +} + +int bt_obex_add_header_session_seq_number(struct net_buf *buf, uint32_t session_seq_number) +{ + size_t total; + + if (!buf) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(session_seq_number); + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_SESSION_SEQ_NUM); + net_buf_add_be32(buf, session_seq_number); + return 0; +} + +int bt_obex_add_header_action_id(struct net_buf *buf, uint32_t action_id) +{ + size_t total; + + if (!buf) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(action_id); + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_ACTION_ID); + net_buf_add_be32(buf, action_id); + return 0; +} + +int bt_obex_add_header_dest_name(struct net_buf *buf, uint16_t len, const uint8_t *dest_name) +{ + size_t total; + + if (!buf || !dest_name) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_DEST_NAME); + net_buf_add_be16(buf, (uint16_t)total); + net_buf_add_mem(buf, dest_name, len); + return 0; +} + +int bt_obex_add_header_perm(struct net_buf *buf, uint32_t perm) +{ + size_t total; + + if (!buf) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(perm); + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_PERM); + net_buf_add_be32(buf, perm); + return 0; +} + +int bt_obex_add_header_srm(struct net_buf *buf, uint8_t srm) +{ + size_t total; + + if (!buf) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(srm); + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_SRM); + net_buf_add_u8(buf, srm); + return 0; +} + +int bt_obex_add_header_srm_param(struct net_buf *buf, uint8_t srm_param) +{ + size_t total; + + if (!buf) { + LOG_WRN("Invalid buf"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(srm_param); + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_SRM_PARAM); + net_buf_add_u8(buf, srm_param); + return 0; +} + +#define BT_OBEX_HEADER_ENCODING(header_id) (0xc0 & (header_id)) +#define BT_OBEX_HEADER_ENCODING_UNICODE 0x00 +#define BT_OBEX_HEADER_ENCODING_BYTE_SEQ 0x40 +#define BT_OBEX_HEADER_ENCODING_1_BYTE 0x80 +#define BT_OBEX_HEADER_ENCODING_4_BYTES 0xc0 + +int bt_obex_header_parse(struct net_buf *buf, + bool (*func)(struct bt_obex_hdr *hdr, void *user_data), void *user_data) +{ + uint16_t len = 0; + struct bt_obex_hdr hdr; + uint8_t header_id; + uint16_t header_value_len; + + if (!buf || !func) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + while (len < buf->len) { + header_id = buf->data[len]; + len++; + + switch (BT_OBEX_HEADER_ENCODING(header_id)) { + case BT_OBEX_HEADER_ENCODING_1_BYTE: + header_value_len = sizeof(header_id); + break; + case BT_OBEX_HEADER_ENCODING_4_BYTES: + header_value_len = 4; + break; + case BT_OBEX_HEADER_ENCODING_UNICODE: + case BT_OBEX_HEADER_ENCODING_BYTE_SEQ: + default: + if ((len + sizeof(header_value_len)) > buf->len) { + return -EINVAL; + } + + header_value_len = sys_get_be16(&buf->data[len]); + if (header_value_len < (sizeof(header_id) + sizeof(header_value_len))) { + return -EINVAL; + } + + header_value_len -= sizeof(header_id) + sizeof(header_value_len); + len += sizeof(header_value_len); + break; + } + + if ((len + header_value_len) > buf->len) { + return -EINVAL; + } + + hdr.id = header_id; + hdr.data = &buf->data[len]; + hdr.len = header_value_len; + len += header_value_len; + + if (!func(&hdr, user_data)) { + return 0; + } + } + return 0; +} + +struct bt_obex_find_header_data { + struct bt_obex_hdr hdr; +}; + +static bool bt_obex_find_header_cb(struct bt_obex_hdr *hdr, void *user_data) +{ + struct bt_obex_find_header_data *data; + + data = user_data; + + if (hdr->id == data->hdr.id) { + data->hdr.data = hdr->data; + data->hdr.len = hdr->len; + return false; + } + return true; +} + +int bt_obex_get_header_count(struct net_buf *buf, uint32_t *count) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !count) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_COUNT; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len != sizeof(*count)) || !data.hdr.data) { + return -ENODATA; + } + + *count = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_name(struct net_buf *buf, uint16_t *len, const uint8_t **name) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !name) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_NAME; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len == 0) || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *name = data.hdr.data; + return 0; +} + +int bt_obex_get_header_type(struct net_buf *buf, uint16_t *len, const uint8_t **type) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !type) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_TYPE; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *type = data.hdr.data; + return 0; +} + +int bt_obex_get_header_len(struct net_buf *buf, uint32_t *len) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_LEN; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len != sizeof(*len)) || !data.hdr.data) { + return -ENODATA; + } + + *len = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_time_iso_8601(struct net_buf *buf, uint16_t *len, const uint8_t **t) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !t) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_TIME_ISO_8601; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *t = data.hdr.data; + return 0; +} + +int bt_obex_get_header_time(struct net_buf *buf, uint32_t *t) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !t) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_TIME; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len != sizeof(*t)) || !data.hdr.data) { + return -ENODATA; + } + + *t = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_description(struct net_buf *buf, uint16_t *len, const uint8_t **dec) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !dec) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_DES; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *dec = data.hdr.data; + return 0; +} + +int bt_obex_get_header_target(struct net_buf *buf, uint16_t *len, const uint8_t **target) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !target) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_TARGET; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *target = data.hdr.data; + return 0; +} + +int bt_obex_get_header_http(struct net_buf *buf, uint16_t *len, const uint8_t **http) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !http) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_HTTP; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *http = data.hdr.data; + return 0; +} + +int bt_obex_get_header_body(struct net_buf *buf, uint16_t *len, const uint8_t **body) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !body) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_BODY; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *body = data.hdr.data; + return 0; +} + +int bt_obex_get_header_end_body(struct net_buf *buf, uint16_t *len, const uint8_t **body) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !body) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_END_BODY; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *body = data.hdr.data; + return 0; +} + +int bt_obex_get_header_who(struct net_buf *buf, uint16_t *len, const uint8_t **who) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !who) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_WHO; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *who = data.hdr.data; + return 0; +} + +int bt_obex_get_header_conn_id(struct net_buf *buf, uint32_t *conn_id) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !conn_id) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_CONN_ID; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len != sizeof(*conn_id)) || !data.hdr.data) { + return -ENODATA; + } + + *conn_id = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_app_param(struct net_buf *buf, uint16_t *len, const uint8_t **app_param) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !app_param) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_APP_PARAM; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *app_param = data.hdr.data; + return 0; +} + +int bt_obex_get_header_auth_challenge(struct net_buf *buf, uint16_t *len, const uint8_t **auth) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !auth) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_AUTH_CHALLENGE; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *auth = data.hdr.data; + return 0; +} + +int bt_obex_get_header_auth_rsp(struct net_buf *buf, uint16_t *len, const uint8_t **auth) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !auth) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_AUTH_RSP; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *auth = data.hdr.data; + return 0; +} + +int bt_obex_get_header_creator_id(struct net_buf *buf, uint32_t *creator_id) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !creator_id) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_CREATE_ID; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len != sizeof(*creator_id)) || !data.hdr.data) { + return -ENODATA; + } + + *creator_id = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_wan_uuid(struct net_buf *buf, uint16_t *len, const uint8_t **uuid) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !uuid) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_WAN_UUID; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *uuid = data.hdr.data; + return 0; +} + +int bt_obex_get_header_obj_class(struct net_buf *buf, uint16_t *len, const uint8_t **obj_class) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !obj_class) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_OBJECT_CLASS; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *obj_class = data.hdr.data; + return 0; +} + +int bt_obex_get_header_session_param(struct net_buf *buf, uint16_t *len, + const uint8_t **session_param) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !session_param) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_SESSION_PARAM; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *session_param = data.hdr.data; + return 0; +} + +int bt_obex_get_header_session_seq_number(struct net_buf *buf, uint32_t *session_seq_number) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !session_seq_number) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_SESSION_SEQ_NUM; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len != sizeof(*session_seq_number)) || !data.hdr.data) { + return -ENODATA; + } + + *session_seq_number = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_action_id(struct net_buf *buf, uint32_t *action_id) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !action_id) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_ACTION_ID; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len != sizeof(*action_id)) || !data.hdr.data) { + return -ENODATA; + } + + *action_id = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_dest_name(struct net_buf *buf, uint16_t *len, const uint8_t **dest_name) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !len || !dest_name) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_DEST_NAME; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.hdr.len || !data.hdr.data) { + return -ENODATA; + } + + *len = data.hdr.len; + *dest_name = data.hdr.data; + return 0; +} + +int bt_obex_get_header_perm(struct net_buf *buf, uint32_t *perm) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !perm) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_PERM; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len != sizeof(*perm)) || !data.hdr.data) { + return -ENODATA; + } + + *perm = sys_get_be32(data.hdr.data); + return 0; +} + +int bt_obex_get_header_srm(struct net_buf *buf, uint8_t *srm) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !srm) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_SRM; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len != sizeof(*srm)) || !data.hdr.data) { + return -ENODATA; + } + + *srm = data.hdr.data[0]; + return 0; +} + +int bt_obex_get_header_srm_param(struct net_buf *buf, uint8_t *srm_param) +{ + struct bt_obex_find_header_data data; + int err; + + if (!buf || !srm_param) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + data.hdr.id = BT_OBEX_HEADER_ID_SRM_PARAM; + data.hdr.len = 0; + data.hdr.data = NULL; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if ((data.hdr.len != sizeof(*srm_param)) || !data.hdr.data) { + return -ENODATA; + } + + *srm_param = data.hdr.data[0]; + return 0; +} + +#if defined(CONFIG_BT_OEBX_RSP_CODE_TO_STR) +const char *bt_obex_rsp_code_to_str(enum bt_obex_rsp_code rsp_code) +{ + const char *rsp_code_str; + + switch (rsp_code) { + case BT_OBEX_RSP_CODE_CONTINUE: + rsp_code_str = "Continue"; + break; + case BT_OBEX_RSP_CODE_SUCCESS: + rsp_code_str = "Success"; + break; + case BT_OBEX_RSP_CODE_CREATED: + rsp_code_str = "Created"; + break; + case BT_OBEX_RSP_CODE_ACCEPTED: + rsp_code_str = "Accepted"; + break; + case BT_OBEX_RSP_CODE_NON_AUTH_INFO: + rsp_code_str = "Non-Authoritative Information"; + break; + case BT_OBEX_RSP_CODE_NO_CONTENT: + rsp_code_str = "No Content"; + break; + case BT_OBEX_RSP_CODE_RESET_CONTENT: + rsp_code_str = "Reset Content"; + break; + case BT_OBEX_RSP_CODE_PARTIAL_CONTENT: + rsp_code_str = "Partial Content"; + break; + case BT_OBEX_RSP_CODE_MULTI_CHOICES: + rsp_code_str = "Multiple Choices"; + break; + case BT_OBEX_RSP_CODE_MOVED_PERM: + rsp_code_str = "Moved Permanently"; + break; + case BT_OBEX_RSP_CODE_MOVED_TEMP: + rsp_code_str = "Moved temporarily"; + break; + case BT_OBEX_RSP_CODE_SEE_OTHER: + rsp_code_str = "See Other"; + break; + case BT_OBEX_RSP_CODE_NOT_MODIFIED: + rsp_code_str = "Not modified"; + break; + case BT_OBEX_RSP_CODE_USE_PROXY: + rsp_code_str = "Use Proxy"; + break; + case BT_OBEX_RSP_CODE_BAD_REQ: + rsp_code_str = "Bad Request - server couldn't understand request"; + break; + case BT_OBEX_RSP_CODE_UNAUTH: + rsp_code_str = "Unauthorized"; + break; + case BT_OBEX_RSP_CODE_PAY_REQ: + rsp_code_str = "Payment Required"; + break; + case BT_OBEX_RSP_CODE_FORBIDDEN: + rsp_code_str = "Forbidden - operation is understood but refused"; + break; + case BT_OBEX_RSP_CODE_NOT_FOUND: + rsp_code_str = "Not Found"; + break; + case BT_OBEX_RSP_CODE_NOT_ALLOW: + rsp_code_str = "Method Not Allowed"; + break; + case BT_OBEX_RSP_CODE_NOT_ACCEPT: + rsp_code_str = "Not Acceptable"; + break; + case BT_OBEX_RSP_CODE_PROXY_AUTH_REQ: + rsp_code_str = "Proxy Authentication Required"; + break; + case BT_OBEX_RSP_CODE_REQ_TIMEOUT: + rsp_code_str = "Request Time Out"; + break; + case BT_OBEX_RSP_CODE_CONFLICT: + rsp_code_str = "Conflict"; + break; + case BT_OBEX_RSP_CODE_GONE: + rsp_code_str = "Gone"; + break; + case BT_OBEX_RSP_CODE_LEN_REQ: + rsp_code_str = "Length Required"; + break; + case BT_OBEX_RSP_CODE_PRECON_FAIL: + rsp_code_str = "Precondition Failed"; + break; + case BT_OBEX_RSP_CODE_ENTITY_TOO_LARGE: + rsp_code_str = "Requested Entity Too Large"; + break; + case BT_OBEX_RSP_CODE_URL_TOO_LARGE: + rsp_code_str = "Requested URL Too Large"; + break; + case BT_OBEX_RSP_CODE_UNSUPP_MEDIA_TYPE: + rsp_code_str = "Unsupported media type"; + break; + case BT_OBEX_RSP_CODE_INTER_ERROR: + rsp_code_str = "Internal serve Error"; + break; + case BT_OBEX_RSP_CODE_NOT_IMPL: + rsp_code_str = "Not Implemented"; + break; + case BT_OBEX_RSP_CODE_BAD_GATEWAY: + rsp_code_str = "Bad Gateway"; + break; + case BT_OBEX_RSP_CODE_UNAVAIL: + rsp_code_str = "Service Unavailable"; + break; + case BT_OBEX_RSP_CODE_GATEWAY_TIMEOUT: + rsp_code_str = "Gateway Timeout"; + break; + case BT_OBEX_RSP_CODE_VER_UNSUPP: + rsp_code_str = "HTTP Version not supported"; + break; + case BT_OBEX_RSP_CODE_DB_FULL: + rsp_code_str = "Database Full"; + break; + case BT_OBEX_RSP_CODE_DB_LOCK: + rsp_code_str = "Database Locked"; + break; + default: + rsp_code_str = "Unknown"; + break; + } + + return rsp_code_str; +} +#endif /* CONFIG_BT_OEBX_RSP_CODE_TO_STR */ diff --git a/subsys/bluetooth/host/classic/obex_internal.h b/subsys/bluetooth/host/classic/obex_internal.h new file mode 100644 index 000000000000..e90b317fe6a5 --- /dev/null +++ b/subsys/bluetooth/host/classic/obex_internal.h @@ -0,0 +1,62 @@ +/* obex.c - Internal for IrDA Oject Exchange Protocol handling */ + +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#define BT_OBEX_VERSION 0x10 + +#define BT_OBEX_MIN_MTU 255 + +#define BT_OBEX_OPCODE_CONNECT 0x80 +#define BT_OBEX_OPCODE_DISCONN 0x81 +#define BT_OBEX_OPCODE_PUT 0x02 +#define BT_OBEX_OPCODE_PUT_F 0x82 +#define BT_OBEX_OPCODE_GET 0x03 +#define BT_OBEX_OPCODE_GET_F 0x83 +#define BT_OBEX_OPCODE_SETPATH 0x85 +#define BT_OBEX_OPCODE_ACTION 0x06 +#define BT_OBEX_OPCODE_ACTION_F 0x86 +#define BT_OBEX_OPCODE_SESSION 0x87 +#define BT_OBEX_OPCODE_ABORT 0xFF + +struct bt_obex_req_hdr { + uint8_t code; + uint16_t len; +} __packed; + +struct bt_obex_rsp_hdr { + uint8_t code; + uint16_t len; +} __packed; + +struct bt_obex_conn_req_hdr { + uint8_t version; + uint8_t flags; + uint16_t mopl; +} __packed; + +struct bt_obex_conn_rsp_hdr { + uint8_t version; + uint8_t flags; + uint16_t mopl; +} __packed; + +struct bt_obex_setpath_req_hdr { + uint8_t flags; + uint8_t constants; +} __packed; + +/* OBEX initialization */ +int bt_obex_reg_transport(struct bt_obex *obex, const struct bt_obex_transport_ops *ops); + +/* Process the received OBEX packet */ +int bt_obex_recv(struct bt_obex *obex, struct net_buf *buf); + +/* Notify OBEX that the transport has been connected */ +int bt_obex_transport_connected(struct bt_obex *obex); + +/* Notify OBEX that the transport has been disconnected */ +int bt_obex_transport_disconnected(struct bt_obex *obex); From 9ba8cc79475ee311f62288e0ab5c7293b92bba06 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 9 Jan 2025 18:45:45 +0800 Subject: [PATCH 07/11] Bluetooth: GOEP: Improve the MTU configuration The MTU of the GOEP should be not less than 255. So, if the transport is RFCOMM, the CONFIG_BT_BUF_ACL_RX_SIZE should be not less than 264. It includes, - 255 bytes for the minimum MTU of GOEP, - 4 bytes for L2CAP Header, - 5 bytes for RFCOMM header and FCS. And if the transport is L2CAP, the CONFIG_BT_BUF_ACL_RX_SIZE should be not less than 259. It includes, - 255 bytes for the minimum MTU of GOEP, - 4 bytes for L2CAP Header. Add Kconfig `BT_GOEP_RFCOMM_MTU` to configure the maximum size for RFCOMM transport. The range of `BT_GOEP_RFCOMM_MTU` is `[264, BT_RFCOMM_L2CAP_MTU]`. And the GOEP MTU via RFCOMM transport should be in the range `[255, (BT_GOEP_RFCOMM_MTU-9)]`. Add Kconfig `BT_GOEP_L2CAP_MTU` to configure the maximum size for L2CAP transport. The range of `BT_GOEP_L2CAP_MTU` is `[259, BT_BUF_ACL_RX_SIZE]`. And the GOEP MTU via L2CAP transport should be in the range `[255, (BT_GOEP_L2CAP_MTU-4)]`. Signed-off-by: Lyle Zhu --- subsys/bluetooth/common/Kconfig | 6 ++ subsys/bluetooth/host/classic/Kconfig | 26 +++++++ subsys/bluetooth/host/classic/goep.c | 107 +++++++++++++++++++++++--- 3 files changed, 127 insertions(+), 12 deletions(-) diff --git a/subsys/bluetooth/common/Kconfig b/subsys/bluetooth/common/Kconfig index a9c17ebceec5..f50fd1db16df 100644 --- a/subsys/bluetooth/common/Kconfig +++ b/subsys/bluetooth/common/Kconfig @@ -50,11 +50,17 @@ config BT_BUF_ACL_TX_COUNT config BT_BUF_ACL_RX_SIZE int "Maximum supported ACL size for incoming data" + # For GOEP over RFCOMM, including, + # 255 bytes - the minimum MTU of GOEP, + # 4 bytes - L2CAP Header, + # 5 bytes - 4 bytes for RFCOMM header with extended length, 1 byte for FCS. + default 264 if BT_CLASSIC && BT_GOEP default 200 if BT_CLASSIC default 70 if BT_EATT default 69 if BT_SMP default 37 if BT_MESH_GATT default 27 + range 264 $(UINT16_MAX) if BT_CLASSIC && BT_GOEP range 70 $(UINT16_MAX) if BT_EATT range 69 $(UINT16_MAX) if BT_SMP range 27 $(UINT16_MAX) diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 488c657e498f..997748846a85 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -231,11 +231,37 @@ config BT_COD config BT_GOEP bool "Bluetooth GOEP Profile [EXPERIMENTAL]" + select BT_RFCOMM select EXPERIMENTAL help This option enables the GOEP profile if BT_GOEP +config BT_GOEP_RFCOMM_MTU + int "RFCOMM MTU for GOEP" + default BT_RFCOMM_L2CAP_MTU + # For GOEP over RFCOMM, including, + # 255 bytes - the minimum MTU of GOEP, + # 4 bytes - L2CAP Header, + # 5 bytes - 4 bytes for RFCOMM header with extended length, 1 byte for FCS. + range 264 BT_RFCOMM_L2CAP_MTU + help + Maximum size of RFCOMM MTU for GOEP. + RX MTU will be truncated to account for ACL data and type overhead, the L2CAP PDU header, + and the RFCOMM header. + +config BT_GOEP_L2CAP_MTU + int "L2CAP MTU for GOEP" + default BT_BUF_ACL_RX_SIZE + # For GOEP over L2CAP, including, + # 255 bytes - the minimum MTU of GOEP, + # 4 bytes - L2CAP Header, + range 259 BT_BUF_ACL_RX_SIZE + help + Maximum size of L2CAP MTU for GOEP. + RX MTU will be truncated to account for ACL data and type overhead, and the L2CAP PDU + header. + config BT_OEBX_RSP_CODE_TO_STR bool "Print OBEX response code as strings" help diff --git a/subsys/bluetooth/host/classic/goep.c b/subsys/bluetooth/host/classic/goep.c index 46fb2c67709a..be709c9da10d 100644 --- a/subsys/bluetooth/host/classic/goep.c +++ b/subsys/bluetooth/host/classic/goep.c @@ -24,6 +24,7 @@ #include "host/conn_internal.h" #include "l2cap_br_internal.h" +#include "rfcomm_internal.h" #include "obex_internal.h" #define LOG_LEVEL CONFIG_BT_GOEP_LOG_LEVEL @@ -175,6 +176,8 @@ static int goep_rfcomm_accept(struct bt_conn *conn, struct bt_rfcomm_server *ser { struct bt_goep_transport_rfcomm_server *rfcomm_server; struct bt_goep *goep; + uint32_t mtu; + uint32_t hdr_size; int err; rfcomm_server = CONTAINER_OF(server, struct bt_goep_transport_rfcomm_server, rfcomm); @@ -190,13 +193,32 @@ static int goep_rfcomm_accept(struct bt_conn *conn, struct bt_rfcomm_server *ser return err; } - if (!goep || !goep->transport_ops || (goep->obex.rx.mtu < GOEP_MIN_MTU) || - !goep->obex.server_ops || !goep->obex.server_ops->connect || - !goep->obex.server_ops->disconnect) { + if (!goep || !goep->transport_ops || !goep->obex.server_ops || + !goep->obex.server_ops->connect || !goep->obex.server_ops->disconnect) { LOG_DBG("Invalid parameter"); return -EINVAL; } + hdr_size = sizeof(struct bt_l2cap_hdr); + hdr_size += BT_RFCOMM_HDR_SIZE + BT_RFCOMM_FCS_SIZE; + + mtu = CONFIG_BT_GOEP_RFCOMM_MTU - hdr_size; + /* Use default MTU if it is not given */ + if (!goep->obex.rx.mtu) { + goep->obex.rx.mtu = mtu; + } + + if (goep->obex.rx.mtu < GOEP_MIN_MTU) { + LOG_WRN("GOEP RFCOMM MTU less than minimum size (%d < %d)", goep->obex.rx.mtu, + GOEP_MIN_MTU); + goep->obex.rx.mtu = GOEP_MIN_MTU; + } + + if (goep->obex.rx.mtu > mtu) { + LOG_WRN("GOEP RFCOMM MTU exceeds maximum size (%d > %d)", goep->obex.rx.mtu, mtu); + goep->obex.rx.mtu = mtu; + } + err = bt_obex_reg_transport(&goep->obex, &goep_rfcomm_transport_ops); if (err) { LOG_WRN("Fail to reg transport ops"); @@ -245,14 +267,35 @@ int bt_goep_transport_rfcomm_server_register(struct bt_goep_transport_rfcomm_ser int bt_goep_transport_rfcomm_connect(struct bt_conn *conn, struct bt_goep *goep, uint8_t channel) { int err; + uint32_t mtu; + uint32_t hdr_size; - if (!conn || !goep || !goep->transport_ops || (goep->obex.rx.mtu < GOEP_MIN_MTU) || - !goep->obex.client_ops || !goep->obex.client_ops->connect || - !goep->obex.client_ops->disconnect) { + if (!conn || !goep || !goep->transport_ops || !goep->obex.client_ops || + !goep->obex.client_ops->connect || !goep->obex.client_ops->disconnect) { LOG_DBG("Invalid parameter"); return -EINVAL; } + hdr_size = sizeof(struct bt_l2cap_hdr); + hdr_size += BT_RFCOMM_HDR_SIZE + BT_RFCOMM_FCS_SIZE; + + mtu = CONFIG_BT_GOEP_RFCOMM_MTU - hdr_size; + /* Use default MTU if it is not given */ + if (!goep->obex.rx.mtu) { + goep->obex.rx.mtu = mtu; + } + + if (goep->obex.rx.mtu < GOEP_MIN_MTU) { + LOG_WRN("GOEP RFCOMM MTU less than minimum size (%d < %d)", goep->obex.rx.mtu, + GOEP_MIN_MTU); + goep->obex.rx.mtu = GOEP_MIN_MTU; + } + + if (goep->obex.rx.mtu > mtu) { + LOG_WRN("GOEP RFCOMM MTU exceeds maximum size (%d > %d)", goep->obex.rx.mtu, mtu); + goep->obex.rx.mtu = mtu; + } + err = bt_obex_reg_transport(&goep->obex, &goep_rfcomm_transport_ops); if (err) { LOG_WRN("Fail to reg transport ops"); @@ -445,6 +488,8 @@ static int goep_l2cap_accept(struct bt_conn *conn, struct bt_l2cap_server *serve { struct bt_goep_transport_l2cap_server *l2cap_server; struct bt_goep *goep; + uint32_t mtu; + uint32_t hdr_size; int err; l2cap_server = CONTAINER_OF(server, struct bt_goep_transport_l2cap_server, l2cap); @@ -460,13 +505,31 @@ static int goep_l2cap_accept(struct bt_conn *conn, struct bt_l2cap_server *serve return err; } - if (!goep || !goep->transport_ops || (goep->obex.rx.mtu < GOEP_MIN_MTU) || - !goep->obex.server_ops || !goep->obex.server_ops->connect || - !goep->obex.server_ops->disconnect) { + if (!goep || !goep->transport_ops || !goep->obex.server_ops || + !goep->obex.server_ops->connect || !goep->obex.server_ops->disconnect) { LOG_DBG("Invalid parameter"); return -EINVAL; } + hdr_size = sizeof(struct bt_l2cap_hdr); + + mtu = CONFIG_BT_GOEP_L2CAP_MTU - hdr_size; + /* Use default MTU if it is not given */ + if (!goep->obex.rx.mtu) { + goep->obex.rx.mtu = mtu; + } + + if (goep->obex.rx.mtu < GOEP_MIN_MTU) { + LOG_WRN("GOEP RFCOMM MTU less than minimum size (%d < %d)", goep->obex.rx.mtu, + GOEP_MIN_MTU); + goep->obex.rx.mtu = GOEP_MIN_MTU; + } + + if (goep->obex.rx.mtu > mtu) { + LOG_WRN("GOEP RFCOMM MTU exceeds maximum size (%d > %d)", goep->obex.rx.mtu, mtu); + goep->obex.rx.mtu = mtu; + } + err = bt_obex_reg_transport(&goep->obex, &goep_l2cap_transport_ops); if (err) { LOG_WRN("Fail to reg transport ops"); @@ -516,10 +579,11 @@ int bt_goep_transport_l2cap_connect(struct bt_conn *conn, struct bt_goep *goep, { int err; uint32_t state; + uint32_t mtu; + uint32_t hdr_size; - if (!conn || !goep || !goep->transport_ops || (goep->obex.rx.mtu < GOEP_MIN_MTU) || - !goep->obex.client_ops || !goep->obex.client_ops->connect || - !goep->obex.client_ops->disconnect) { + if (!conn || !goep || !goep->transport_ops || !goep->obex.client_ops || + !goep->obex.client_ops->connect || !goep->obex.client_ops->disconnect) { LOG_DBG("Invalid parameter"); return -EINVAL; } @@ -530,6 +594,25 @@ int bt_goep_transport_l2cap_connect(struct bt_conn *conn, struct bt_goep *goep, return -EBUSY; } + hdr_size = sizeof(struct bt_l2cap_hdr); + + mtu = CONFIG_BT_GOEP_L2CAP_MTU - hdr_size; + /* Use default MTU if it is not given */ + if (!goep->obex.rx.mtu) { + goep->obex.rx.mtu = mtu; + } + + if (goep->obex.rx.mtu < GOEP_MIN_MTU) { + LOG_WRN("GOEP RFCOMM MTU less than minimum size (%d < %d)", goep->obex.rx.mtu, + GOEP_MIN_MTU); + goep->obex.rx.mtu = GOEP_MIN_MTU; + } + + if (goep->obex.rx.mtu > mtu) { + LOG_WRN("GOEP RFCOMM MTU exceeds maximum size (%d > %d)", goep->obex.rx.mtu, mtu); + goep->obex.rx.mtu = mtu; + } + err = bt_obex_reg_transport(&goep->obex, &goep_l2cap_transport_ops); if (err) { LOG_WRN("Fail to reg transport ops"); From a1abc8abcf9c600c184592649483030340b9c377 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Fri, 10 Jan 2025 10:36:18 +0800 Subject: [PATCH 08/11] Bluetooth: RFCOMM: Move BT_RFCOMM_BUF_SIZE to rfcomm.h The buffer `BT_RFCOMM_BUF_SIZE` is used to define the TX buffer size of TX pool. In current implementation, the TX buffer size of RFCOMM cannot be calculated due to the macro `BT_RFCOMM_BUF_SIZE` is defined in internal header file `rfcomm_internal.h`. Move the macro `BT_RFCOMM_BUF_SIZE` form internal header file `rfcomm_internal.h` to interface `rfcomm.h`. Signed-off-by: Lyle Zhu --- include/zephyr/bluetooth/classic/rfcomm.h | 16 ++++++++++++++++ subsys/bluetooth/host/classic/rfcomm_internal.h | 10 ---------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/include/zephyr/bluetooth/classic/rfcomm.h b/include/zephyr/bluetooth/classic/rfcomm.h index 5701056524e6..be2cf02b4192 100644 --- a/include/zephyr/bluetooth/classic/rfcomm.h +++ b/include/zephyr/bluetooth/classic/rfcomm.h @@ -19,11 +19,27 @@ #include #include +#include #ifdef __cplusplus extern "C" { #endif +/** RFCOMM Maximum Header Size. The length could be 2 bytes, it depends on information length. */ +#define BT_RFCOMM_HDR_MAX_SIZE 4 +/** RFCOMM FCS Size */ +#define BT_RFCOMM_FCS_SIZE 1 + +/** @brief Helper to calculate needed buffer size for RFCOMM PDUs. + * Useful for creating buffer pools. + * + * @param mtu Needed RFCOMM PDU MTU. + * + * @return Needed buffer size to match the requested RFCOMM PDU MTU. + */ +#define BT_RFCOMM_BUF_SIZE(mtu) \ + BT_L2CAP_BUF_SIZE(BT_RFCOMM_HDR_MAX_SIZE + BT_RFCOMM_FCS_SIZE + (mtu)) + /* RFCOMM channels (1-30): pre-allocated for profiles to avoid conflicts */ enum { BT_RFCOMM_CHAN_HFP_HF = 1, diff --git a/subsys/bluetooth/host/classic/rfcomm_internal.h b/subsys/bluetooth/host/classic/rfcomm_internal.h index 07ff13bd0e9b..afc7b366d153 100644 --- a/subsys/bluetooth/host/classic/rfcomm_internal.h +++ b/subsys/bluetooth/host/classic/rfcomm_internal.h @@ -133,15 +133,6 @@ struct bt_rfcomm_rpn { #define BT_RFCOMM_CHECK_MTU(mtu) (!!((mtu) >= BT_RFCOMM_SIG_MIN_MTU && \ (mtu) <= BT_RFCOMM_SIG_MAX_MTU)) -/* Helper to calculate needed outgoing buffer size. - * Length in rfcomm header can be two bytes depending on user data length. - * One byte in the tail should be reserved for FCS. - */ -#define BT_RFCOMM_BUF_SIZE(mtu) (BT_BUF_RESERVE + \ - BT_HCI_ACL_HDR_SIZE + BT_L2CAP_HDR_SIZE + \ - sizeof(struct bt_rfcomm_hdr) + 1 + (mtu) + \ - BT_RFCOMM_FCS_SIZE) - #define BT_RFCOMM_GET_DLCI(addr) (((addr) & 0xfc) >> 2) #define BT_RFCOMM_GET_FRAME_TYPE(ctrl) ((ctrl) & 0xef) #define BT_RFCOMM_GET_MSG_TYPE(type) (((type) & 0xfc) >> 2) @@ -192,7 +183,6 @@ struct bt_rfcomm_rpn { /* Length can be 2 bytes depending on data size */ #define BT_RFCOMM_HDR_SIZE (sizeof(struct bt_rfcomm_hdr) + 1) -#define BT_RFCOMM_FCS_SIZE 1 #define BT_RFCOMM_FCS_LEN_UIH 2 #define BT_RFCOMM_FCS_LEN_NON_UIH 3 From 091a563f853c40b6202b3d44c080be02b037127b Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 2 Jan 2025 16:09:52 +0800 Subject: [PATCH 09/11] Bluetooth: shell: Add transport commands for GOEP Add commands for GOEP to test transport features. Add commands for transport over RFCOMM, including `register-rfcomm`, `connect-rfcomm`, and `disconnect-rfcomm`. Add commands for transport over L2CAP, including `register-l2cap`, `connect-l2cap`, and `disconnect-l2cap`. Signed-off-by: Lyle Zhu --- .../host/classic/shell/CMakeLists.txt | 1 + subsys/bluetooth/host/classic/shell/goep.c | 374 ++++++++++++++++++ tests/bluetooth/shell/prj_br.conf | 1 + 3 files changed, 376 insertions(+) create mode 100644 subsys/bluetooth/host/classic/shell/goep.c diff --git a/subsys/bluetooth/host/classic/shell/CMakeLists.txt b/subsys/bluetooth/host/classic/shell/CMakeLists.txt index dbf42a44cb1a..81f572c0d3ea 100644 --- a/subsys/bluetooth/host/classic/shell/CMakeLists.txt +++ b/subsys/bluetooth/host/classic/shell/CMakeLists.txt @@ -5,3 +5,4 @@ zephyr_library_sources(bredr.c) zephyr_library_sources_ifdef(CONFIG_BT_RFCOMM rfcomm.c) zephyr_library_sources_ifdef(CONFIG_BT_A2DP a2dp.c) zephyr_library_sources_ifdef(CONFIG_BT_AVRCP avrcp.c) +zephyr_library_sources_ifdef(CONFIG_BT_GOEP goep.c) diff --git a/subsys/bluetooth/host/classic/shell/goep.c b/subsys/bluetooth/host/classic/shell/goep.c new file mode 100644 index 000000000000..68597bdf2b60 --- /dev/null +++ b/subsys/bluetooth/host/classic/shell/goep.c @@ -0,0 +1,374 @@ +/** @file + * @brief Bluetooth GOEP shell module + * + * Provide some Bluetooth shell commands that can be useful to applications. + */ +/* + * Copyright 2024 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#include "host/shell/bt.h" +#include "common/bt_shell_private.h" + +struct bt_goep_app { + struct bt_goep goep; + struct bt_conn *conn; +}; + +static struct bt_goep_app goep_app; + +static struct bt_goep_transport_rfcomm_server rfcomm_server; +static struct bt_goep_transport_l2cap_server l2cap_server; + +static struct bt_goep_app *goep_alloc(struct bt_conn *conn) +{ + if (goep_app.conn) { + return NULL; + } + + goep_app.conn = conn; + return &goep_app; +} + +static void goep_free(struct bt_goep_app *goep) +{ + goep->conn = NULL; +} + +void goep_transport_connected(struct bt_conn *conn, struct bt_goep *goep) +{ + bt_shell_print("GOEP %p transport connected on %p", goep, conn); +} + +void goep_transport_disconnected(struct bt_goep *goep) +{ + struct bt_goep_app *g_app = CONTAINER_OF(goep, struct bt_goep_app, goep); + + goep_free(g_app); + bt_shell_print("GOEP %p transport disconnected", goep); +} + +struct bt_goep_transport_ops goep_transport_ops = { + .connected = goep_transport_connected, + .disconnected = goep_transport_disconnected, +}; + +static void goep_server_connect(struct bt_obex *obex, uint8_t version, uint16_t mopl, + struct net_buf *buf) +{ +} + +static void goep_server_disconnect(struct bt_obex *obex, struct net_buf *buf) +{ +} + +static void goep_server_put(struct bt_obex *obex, bool final, struct net_buf *buf) +{ +} + +static void goep_server_get(struct bt_obex *obex, bool final, struct net_buf *buf) +{ +} + +static void goep_server_abort(struct bt_obex *obex, struct net_buf *buf) +{ +} + +static void goep_server_setpath(struct bt_obex *obex, uint8_t flags, struct net_buf *buf) +{ +} + +static void goep_server_action(struct bt_obex *obex, bool final, struct net_buf *buf) +{ +} + +struct bt_obex_server_ops goep_server_ops = { + .connect = goep_server_connect, + .disconnect = goep_server_disconnect, + .put = goep_server_put, + .get = goep_server_get, + .abort = goep_server_abort, + .setpath = goep_server_setpath, + .action = goep_server_action, +}; + +static void goep_client_connect(struct bt_obex *obex, uint8_t rsp_code, uint8_t version, + uint16_t mopl, struct net_buf *buf) +{ +} + +static void goep_client_disconnect(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ +} + +static void goep_client_put(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ +} + +static void goep_client_get(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ +} + +static void goep_client_abort(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ +} + +static void goep_client_setpath(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ +} + +static void goep_client_action(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) +{ +} + +struct bt_obex_client_ops goep_client_ops = { + .connect = goep_client_connect, + .disconnect = goep_client_disconnect, + .put = goep_client_put, + .get = goep_client_get, + .abort = goep_client_abort, + .setpath = goep_client_setpath, + .action = goep_client_action, +}; + +static int rfcomm_accept(struct bt_conn *conn, struct bt_goep_transport_rfcomm_server *server, + struct bt_goep **goep) +{ + struct bt_goep_app *g_app; + + g_app = goep_alloc(conn); + if (!g_app) { + bt_shell_print("Cannot allocate goep instance"); + return -ENOMEM; + } + + g_app->goep.transport_ops = &goep_transport_ops; + g_app->goep.obex.server_ops = &goep_server_ops; + *goep = &g_app->goep; + return 0; +} + +static int cmd_register_rfcomm(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + uint8_t channel; + + if (rfcomm_server.rfcomm.channel) { + shell_error(sh, "RFCOMM has been registered"); + return -EBUSY; + } + + channel = (uint8_t)strtoul(argv[1], NULL, 16); + + rfcomm_server.rfcomm.channel = channel; + rfcomm_server.accept = rfcomm_accept; + err = bt_goep_transport_rfcomm_server_register(&rfcomm_server); + if (err) { + shell_error(sh, "Fail to register RFCOMM server (error %d)", err); + rfcomm_server.rfcomm.channel = 0; + return -ENOEXEC; + } + shell_print(sh, "RFCOMM server (channel %02x) is registered", rfcomm_server.rfcomm.channel); + return 0; +} + +static int cmd_connect_rfcomm(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + struct bt_goep_app *g_app; + uint8_t channel; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + channel = (uint8_t)strtoul(argv[1], NULL, 16); + if (!channel) { + shell_error(sh, "Invalid channel"); + return -ENOEXEC; + } + + g_app = goep_alloc(default_conn); + if (!g_app) { + shell_error(sh, "Cannot allocate goep instance"); + return -ENOMEM; + } + + g_app->goep.transport_ops = &goep_transport_ops; + g_app->goep.obex.client_ops = &goep_client_ops; + + err = bt_goep_transport_rfcomm_connect(default_conn, &g_app->goep, channel); + if (err) { + goep_free(g_app); + shell_error(sh, "Fail to connect to channel %d (err %d)", channel, err); + } else { + shell_print(sh, "GOEP RFCOMM connection pending"); + } + + return err; +} + +static int cmd_disconnect_rfcomm(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + err = bt_goep_transport_rfcomm_disconnect(&goep_app.goep); + if (err) { + shell_error(sh, "Fail to disconnect to channel (err %d)", err); + } else { + shell_print(sh, "GOEP RFCOMM disconnection pending"); + } + return err; +} + +static int l2cap_accept(struct bt_conn *conn, struct bt_goep_transport_l2cap_server *server, + struct bt_goep **goep) +{ + struct bt_goep_app *g_app; + + g_app = goep_alloc(conn); + if (!g_app) { + bt_shell_print("Cannot allocate goep instance"); + return -ENOMEM; + } + + g_app->goep.transport_ops = &goep_transport_ops; + g_app->goep.obex.server_ops = &goep_server_ops; + *goep = &g_app->goep; + return 0; +} + +static int cmd_register_l2cap(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + uint16_t psm; + + if (l2cap_server.l2cap.psm) { + shell_error(sh, "L2CAP server has been registered"); + return -EBUSY; + } + + psm = (uint16_t)strtoul(argv[1], NULL, 16); + + l2cap_server.l2cap.psm = psm; + l2cap_server.accept = l2cap_accept; + err = bt_goep_transport_l2cap_server_register(&l2cap_server); + if (err) { + shell_error(sh, "Fail to register L2CAP server (error %d)", err); + l2cap_server.l2cap.psm = 0; + return -ENOEXEC; + } + shell_print(sh, "L2CAP server (psm %04x) is registered", l2cap_server.l2cap.psm); + return 0; +} + +static int cmd_connect_l2cap(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + struct bt_goep_app *g_app; + uint16_t psm; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + psm = (uint16_t)strtoul(argv[1], NULL, 16); + if (!psm) { + shell_error(sh, "Invalid psm"); + return -ENOEXEC; + } + + g_app = goep_alloc(default_conn); + if (!g_app) { + shell_error(sh, "Cannot allocate goep instance"); + return -ENOMEM; + } + + g_app->goep.transport_ops = &goep_transport_ops; + g_app->goep.obex.client_ops = &goep_client_ops; + + err = bt_goep_transport_l2cap_connect(default_conn, &g_app->goep, psm); + if (err) { + goep_free(g_app); + shell_error(sh, "Fail to connect to PSM %d (err %d)", psm, err); + } else { + shell_print(sh, "GOEP L2CAP connection pending"); + } + + return err; +} + +static int cmd_disconnect_l2cap(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + err = bt_goep_transport_l2cap_disconnect(&goep_app.goep); + if (err) { + shell_error(sh, "Fail to disconnect L2CAP conn (err %d)", err); + } else { + shell_print(sh, "GOEP L2CAP disconnection pending"); + } + return err; +} + +#define HELP_NONE "" + +SHELL_STATIC_SUBCMD_SET_CREATE(goep_cmds, + SHELL_CMD_ARG(register-rfcomm, NULL, "", cmd_register_rfcomm, 2, 0), + SHELL_CMD_ARG(connect-rfcomm, NULL, "", cmd_connect_rfcomm, 2, 0), + SHELL_CMD_ARG(disconnect-rfcomm, NULL, HELP_NONE, cmd_disconnect_rfcomm, 1, 0), + SHELL_CMD_ARG(register-l2cap, NULL, "", cmd_register_l2cap, 2, 0), + SHELL_CMD_ARG(connect-l2cap, NULL, "", cmd_connect_l2cap, 2, 0), + SHELL_CMD_ARG(disconnect-l2cap, NULL, HELP_NONE, cmd_disconnect_l2cap, 1, 0), + SHELL_SUBCMD_SET_END +); + +static int cmd_goep(const struct shell *sh, size_t argc, char **argv) +{ + if (argc == 1) { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + + return -ENOEXEC; +} + +SHELL_CMD_ARG_REGISTER(goep, &goep_cmds, "Bluetooth GOEP shell commands", cmd_goep, 1, 1); diff --git a/tests/bluetooth/shell/prj_br.conf b/tests/bluetooth/shell/prj_br.conf index 45d3806473ff..d3d3406a4805 100644 --- a/tests/bluetooth/shell/prj_br.conf +++ b/tests/bluetooth/shell/prj_br.conf @@ -17,3 +17,4 @@ CONFIG_BT_GATT_CLIENT=y CONFIG_BT_HRS=y CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y CONFIG_BT_DEVICE_NAME="test shell" +CONFIG_BT_GOEP=y From 0b1334b8a04376df265a24c05a049652076f8452 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Thu, 9 Jan 2025 19:18:30 +0800 Subject: [PATCH 10/11] Bluetooth: shell: Add OBEX commands for GOEP Add commands for GOEP to test OBEX features. Add command `alloc-buf` and `release-buf` to allocate and release TX buffer. Add command set `add-header` to add the OBEX headers to allocated TX buffer. Add command set `client` to send OBEX client requests with allocated TX buffer. Add command set `server` to send OBEX responses with allocated TX buffer. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/shell/goep.c | 1197 +++++++++++++++++++- tests/bluetooth/shell/prj_br.conf | 1 + 2 files changed, 1181 insertions(+), 17 deletions(-) diff --git a/subsys/bluetooth/host/classic/shell/goep.c b/subsys/bluetooth/host/classic/shell/goep.c index 68597bdf2b60..2425f0c297d1 100644 --- a/subsys/bluetooth/host/classic/shell/goep.c +++ b/subsys/bluetooth/host/classic/shell/goep.c @@ -11,6 +11,7 @@ #include #include +#include #include #include @@ -24,9 +25,17 @@ #include "host/shell/bt.h" #include "common/bt_shell_private.h" +#define GOEP_MOPL CONFIG_BT_GOEP_RFCOMM_MTU + +NET_BUF_POOL_FIXED_DEFINE(tx_pool, CONFIG_BT_MAX_CONN, BT_RFCOMM_BUF_SIZE(GOEP_MOPL), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +static uint8_t add_head_buffer[GOEP_MOPL]; + struct bt_goep_app { struct bt_goep goep; struct bt_conn *conn; + struct net_buf *tx_buf; }; static struct bt_goep_app goep_app; @@ -67,33 +76,75 @@ struct bt_goep_transport_ops goep_transport_ops = { .disconnected = goep_transport_disconnected, }; +static bool goep_parse_headers_cb(struct bt_obex_hdr *hdr, void *user_data) +{ + bt_shell_print("HI %02x Len %d", hdr->id, hdr->len); + bt_shell_hexdump(hdr->data, hdr->len); + + return true; +} + +static int goep_parse_headers(struct net_buf *buf) +{ + int err; + + if (!buf) { + return 0; + } + + err = bt_obex_header_parse(buf, goep_parse_headers_cb, NULL); + if (err) { + bt_shell_print("Fail to parse OBEX Headers"); + } + + return err; +} + static void goep_server_connect(struct bt_obex *obex, uint8_t version, uint16_t mopl, struct net_buf *buf) { + bt_shell_print("OBEX %p conn req, version %02x, mopl %04x", obex, version, mopl); + goep_parse_headers(buf); } static void goep_server_disconnect(struct bt_obex *obex, struct net_buf *buf) { + bt_shell_print("OBEX %p disconn req", obex); + goep_parse_headers(buf); } static void goep_server_put(struct bt_obex *obex, bool final, struct net_buf *buf) { + bt_shell_print("OBEX %p put req, final %s, data len %d", obex, + final ? "true" : "false", buf->len); + goep_parse_headers(buf); } static void goep_server_get(struct bt_obex *obex, bool final, struct net_buf *buf) { + bt_shell_print("OBEX %p get req, final %s, data len %d", obex, + final ? "true" : "false", buf->len); + goep_parse_headers(buf); } static void goep_server_abort(struct bt_obex *obex, struct net_buf *buf) { + bt_shell_print("OBEX %p abort req", obex); + goep_parse_headers(buf); } static void goep_server_setpath(struct bt_obex *obex, uint8_t flags, struct net_buf *buf) { + bt_shell_print("OBEX %p setpath req, flags %02x, data len %d", obex, flags, + buf->len); + goep_parse_headers(buf); } static void goep_server_action(struct bt_obex *obex, bool final, struct net_buf *buf) { + bt_shell_print("OBEX %p action req, final %s, data len %d", obex, + final ? "true" : "false", buf->len); + goep_parse_headers(buf); } struct bt_obex_server_ops goep_server_ops = { @@ -109,30 +160,51 @@ struct bt_obex_server_ops goep_server_ops = { static void goep_client_connect(struct bt_obex *obex, uint8_t rsp_code, uint8_t version, uint16_t mopl, struct net_buf *buf) { + bt_shell_print("OBEX %p conn rsq, rsp_code %s, version %02x, mopl %04x", obex, + bt_obex_rsp_code_to_str(rsp_code), version, mopl); + goep_parse_headers(buf); } static void goep_client_disconnect(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) { + bt_shell_print("OBEX %p disconn rsq, rsp_code %s", obex, + bt_obex_rsp_code_to_str(rsp_code)); + goep_parse_headers(buf); } static void goep_client_put(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) { + bt_shell_print("OBEX %p put rsq, rsp_code %s, data len %d", obex, + bt_obex_rsp_code_to_str(rsp_code), buf->len); + goep_parse_headers(buf); } static void goep_client_get(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) { + bt_shell_print("OBEX %p get rsq, rsp_code %s, data len %d", obex, + bt_obex_rsp_code_to_str(rsp_code), buf->len); + goep_parse_headers(buf); } static void goep_client_abort(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) { + bt_shell_print("OBEX %p abort rsq, rsp_code %s", obex, + bt_obex_rsp_code_to_str(rsp_code)); + goep_parse_headers(buf); } static void goep_client_setpath(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) { + bt_shell_print("OBEX %p setpath rsq, rsp_code %s", obex, + bt_obex_rsp_code_to_str(rsp_code)); + goep_parse_headers(buf); } static void goep_client_action(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *buf) { + bt_shell_print("OBEX %p action rsq, rsp_code %s, data len %d", obex, + bt_obex_rsp_code_to_str(rsp_code), buf->len); + goep_parse_headers(buf); } struct bt_obex_client_ops goep_client_ops = { @@ -347,28 +419,1119 @@ static int cmd_disconnect_l2cap(const struct shell *sh, size_t argc, char *argv[ return err; } -#define HELP_NONE "" +static int cmd_add_header_count(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t count; + int err; -SHELL_STATIC_SUBCMD_SET_CREATE(goep_cmds, - SHELL_CMD_ARG(register-rfcomm, NULL, "", cmd_register_rfcomm, 2, 0), - SHELL_CMD_ARG(connect-rfcomm, NULL, "", cmd_connect_rfcomm, 2, 0), - SHELL_CMD_ARG(disconnect-rfcomm, NULL, HELP_NONE, cmd_disconnect_rfcomm, 1, 0), - SHELL_CMD_ARG(register-l2cap, NULL, "", cmd_register_l2cap, 2, 0), - SHELL_CMD_ARG(connect-l2cap, NULL, "", cmd_connect_l2cap, 2, 0), - SHELL_CMD_ARG(disconnect-l2cap, NULL, HELP_NONE, cmd_disconnect_l2cap, 1, 0), - SHELL_SUBCMD_SET_END -); + count = strtoul(argv[1], NULL, 16); + + err = bt_obex_add_header_count(goep_app.tx_buf, count); + if (err) { + shell_error(sh, "Fail to add header count"); + } + return err; +} -static int cmd_goep(const struct shell *sh, size_t argc, char **argv) +static int cmd_add_header_name(const struct shell *sh, size_t argc, char *argv[]) { - if (argc == 1) { - shell_help(sh); - return SHELL_CMD_HELP_PRINTED; + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; } - shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + err = bt_obex_add_header_name(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header name"); + } + return err; +} - return -ENOEXEC; +static int cmd_add_header_type(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_type(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header type"); + } + return err; +} + +static int cmd_add_header_len(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t len; + int err; + + len = strtoul(argv[1], NULL, 16); + + err = bt_obex_add_header_len(goep_app.tx_buf, len); + if (err) { + shell_error(sh, "Fail to add header len"); + } + return err; +} + +static int cmd_add_header_time_iso_8601(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_time_iso_8601(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header time_iso_8601"); + } + return err; +} + +static int cmd_add_header_time(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t t; + int err; + + t = strtoul(argv[1], NULL, 16); + + err = bt_obex_add_header_time(goep_app.tx_buf, t); + if (err) { + shell_error(sh, "Fail to add header time"); + } + return err; +} + +static int cmd_add_header_description(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_description(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header description"); + } + return err; +} + +static int cmd_add_header_target(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_target(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header target"); + } + return err; +} + +static int cmd_add_header_http(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_http(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header http"); + } + return err; +} + +static int cmd_add_header_body(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_body(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header body"); + } + return err; +} + +static int cmd_add_header_end_body(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_end_body(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header end_body"); + } + return err; +} + +static int cmd_add_header_who(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_who(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header who"); + } + return err; +} + +static int cmd_add_header_conn_id(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t conn_id; + int err; + + conn_id = strtoul(argv[1], NULL, 16); + + err = bt_obex_add_header_conn_id(goep_app.tx_buf, conn_id); + if (err) { + shell_error(sh, "Fail to add header conn_id"); + } + return err; +} + +static int cmd_add_header_app_param(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_app_param(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header app_param"); + } + return err; +} + +static int cmd_add_header_auth_challenge(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_auth_challenge(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header auth_challenge"); + } + return err; +} + +static int cmd_add_header_auth_rsp(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_auth_rsp(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header auth_rsp"); + } + return err; +} + +static int cmd_add_header_creator_id(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t creator_id; + int err; + + creator_id = strtoul(argv[1], NULL, 16); + + err = bt_obex_add_header_creator_id(goep_app.tx_buf, creator_id); + if (err) { + shell_error(sh, "Fail to add header creator_id"); + } + return err; +} + +static int cmd_add_header_wan_uuid(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_wan_uuid(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header wan_uuid"); + } + return err; +} + +static int cmd_add_header_obj_class(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_obj_class(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header obj_class"); + } + return err; } -SHELL_CMD_ARG_REGISTER(goep, &goep_cmds, "Bluetooth GOEP shell commands", cmd_goep, 1, 1); +static int cmd_add_header_session_param(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_session_param(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header session_param"); + } + return err; +} + +static int cmd_add_header_session_seq_number(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t session_seq_number; + int err; + + session_seq_number = strtoul(argv[1], NULL, 16); + + err = bt_obex_add_header_session_seq_number(goep_app.tx_buf, session_seq_number); + if (err) { + shell_error(sh, "Fail to add header session_seq_number"); + } + return err; +} + +static int cmd_add_header_action_id(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t action_id; + int err; + + action_id = strtoul(argv[1], NULL, 16); + + err = bt_obex_add_header_action_id(goep_app.tx_buf, action_id); + if (err) { + shell_error(sh, "Fail to add header action_id"); + } + return err; +} + +static int cmd_add_header_dest_name(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload = argv[1]; + size_t hex_payload_size = strlen(hex_payload); + + len = hex2bin(hex_payload, hex_payload_size, add_head_buffer, sizeof(add_head_buffer)); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_dest_name(goep_app.tx_buf, (uint16_t)len, add_head_buffer); + if (err) { + shell_error(sh, "Fail to add header dest_name"); + } + return err; +} + +static int cmd_add_header_perm(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t perm; + int err; + + perm = strtoul(argv[1], NULL, 16); + + err = bt_obex_add_header_perm(goep_app.tx_buf, perm); + if (err) { + shell_error(sh, "Fail to add header perm"); + } + return err; +} + +static int cmd_add_header_srm(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t srm; + int err; + + srm = strtoul(argv[1], NULL, 16); + + if (srm > UINT8_MAX) { + shell_error(sh, "Value exceeds max value (%x > %x)", srm, UINT8_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_srm(goep_app.tx_buf, (uint8_t)srm); + if (err) { + shell_error(sh, "Fail to add header srm"); + } + return err; +} + +static int cmd_add_header_srm_param(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t srm_param; + int err; + + srm_param = strtoul(argv[1], NULL, 16); + + if (srm_param > UINT8_MAX) { + shell_error(sh, "Value exceeds max value (%x > %x)", srm_param, UINT8_MAX); + return -ENOEXEC; + } + + err = bt_obex_add_header_srm_param(goep_app.tx_buf, (uint8_t)srm_param); + if (err) { + shell_error(sh, "Fail to add header srm_param"); + } + return err; +} + +static int cmd_goep_client_conn(const struct shell *sh, size_t argc, char *argv[]) +{ + uint16_t mopl; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + mopl = (uint16_t)strtoul(argv[1], NULL, 16); + + err = bt_obex_connect(&goep_app.goep.obex, mopl, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send conn req %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_client_disconn(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + err = bt_obex_disconnect(&goep_app.goep.obex, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send disconn req %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_client_put(const struct shell *sh, size_t argc, char *argv[]) +{ + bool final; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + if (!strcmp(argv[1], "yes")) { + final = true; + } else if (!strcmp(argv[1], "no")) { + final = false; + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + err = bt_obex_put(&goep_app.goep.obex, final, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send put req %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_client_get(const struct shell *sh, size_t argc, char *argv[]) +{ + bool final; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + if (!strcmp(argv[1], "yes")) { + final = true; + } else if (!strcmp(argv[1], "no")) { + final = false; + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + err = bt_obex_get(&goep_app.goep.obex, final, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send get req %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_client_abort(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + err = bt_obex_abort(&goep_app.goep.obex, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send abort req %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_client_setpath(const struct shell *sh, size_t argc, char *argv[]) +{ + int err; + uint8_t flags = BIT(1); + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + for (size_t index = 1; index < argc; index++) { + if (!strcmp(argv[index], "parent")) { + flags |= BIT(0); + } else if (!strcmp(argv[index], "create")) { + flags &= ~BIT(1); + } + } + + err = bt_obex_setpath(&goep_app.goep.obex, flags, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send setpath req %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_client_action(const struct shell *sh, size_t argc, char *argv[]) +{ + bool final; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + if (!strcmp(argv[1], "yes")) { + final = true; + } else if (!strcmp(argv[1], "no")) { + final = false; + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + err = bt_obex_action(&goep_app.goep.obex, final, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send action req %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_server_conn(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t rsp_code; + uint16_t mopl; + const char *rsp; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + rsp = argv[1]; + if (!strcmp(rsp, "continue")) { + rsp_code = BT_OBEX_RSP_CODE_CONTINUE; + } else if (!strcmp(rsp, "success")) { + rsp_code = BT_OBEX_RSP_CODE_SUCCESS; + } else if (!strcmp(rsp, "error")) { + if (argc < 4) { + shell_error(sh, "[rsp_code] is needed if the rsp is %s", rsp); + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + rsp_code = (uint8_t)strtoul(argv[3], NULL, 16); + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + mopl = (uint16_t)strtoul(argv[2], NULL, 16); + + err = bt_obex_connect_rsp(&goep_app.goep.obex, rsp_code, mopl, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send conn rsp %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_server_disconn(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t rsp_code; + const char *rsp; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + rsp = argv[1]; + if (!strcmp(rsp, "continue")) { + rsp_code = BT_OBEX_RSP_CODE_CONTINUE; + } else if (!strcmp(rsp, "success")) { + rsp_code = BT_OBEX_RSP_CODE_SUCCESS; + } else if (!strcmp(rsp, "error")) { + if (argc < 3) { + shell_error(sh, "[rsp_code] is needed if the rsp is %s", rsp); + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + rsp_code = (uint8_t)strtoul(argv[2], NULL, 16); + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + err = bt_obex_disconnect_rsp(&goep_app.goep.obex, rsp_code, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send disconn rsp %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_server_put(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t rsp_code; + const char *rsp; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + rsp = argv[1]; + if (!strcmp(rsp, "continue")) { + rsp_code = BT_OBEX_RSP_CODE_CONTINUE; + } else if (!strcmp(rsp, "success")) { + rsp_code = BT_OBEX_RSP_CODE_SUCCESS; + } else if (!strcmp(rsp, "error")) { + if (argc < 3) { + shell_error(sh, "[rsp_code] is needed if the rsp is %s", rsp); + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + rsp_code = (uint8_t)strtoul(argv[2], NULL, 16); + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + err = bt_obex_put_rsp(&goep_app.goep.obex, rsp_code, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send put rsp %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_server_get(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t rsp_code; + const char *rsp; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + rsp = argv[1]; + if (!strcmp(rsp, "continue")) { + rsp_code = BT_OBEX_RSP_CODE_CONTINUE; + } else if (!strcmp(rsp, "success")) { + rsp_code = BT_OBEX_RSP_CODE_SUCCESS; + } else if (!strcmp(rsp, "error")) { + if (argc < 3) { + shell_error(sh, "[rsp_code] is needed if the rsp is %s", rsp); + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + rsp_code = (uint8_t)strtoul(argv[2], NULL, 16); + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + err = bt_obex_get_rsp(&goep_app.goep.obex, rsp_code, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send get rsp %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_server_abort(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t rsp_code; + const char *rsp; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + rsp = argv[1]; + if (!strcmp(rsp, "continue")) { + rsp_code = BT_OBEX_RSP_CODE_CONTINUE; + } else if (!strcmp(rsp, "success")) { + rsp_code = BT_OBEX_RSP_CODE_SUCCESS; + } else if (!strcmp(rsp, "error")) { + if (argc < 3) { + shell_error(sh, "[rsp_code] is needed if the rsp is %s", rsp); + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + rsp_code = (uint8_t)strtoul(argv[2], NULL, 16); + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + err = bt_obex_abort_rsp(&goep_app.goep.obex, rsp_code, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send abort rsp %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_server_setpath(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t rsp_code; + const char *rsp; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + rsp = argv[1]; + if (!strcmp(rsp, "continue")) { + rsp_code = BT_OBEX_RSP_CODE_CONTINUE; + } else if (!strcmp(rsp, "success")) { + rsp_code = BT_OBEX_RSP_CODE_SUCCESS; + } else if (!strcmp(rsp, "error")) { + if (argc < 3) { + shell_error(sh, "[rsp_code] is needed if the rsp is %s", rsp); + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + rsp_code = (uint8_t)strtoul(argv[2], NULL, 16); + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + err = bt_obex_setpath_rsp(&goep_app.goep.obex, rsp_code, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send setpath rsp %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +static int cmd_goep_server_action(const struct shell *sh, size_t argc, char *argv[]) +{ + uint8_t rsp_code; + const char *rsp; + int err; + + if (!default_conn) { + shell_error(sh, "Not connected"); + return -ENOEXEC; + } + + if (!goep_app.conn) { + shell_error(sh, "No goep transport connection"); + return -ENOEXEC; + } + + rsp = argv[1]; + if (!strcmp(rsp, "continue")) { + rsp_code = BT_OBEX_RSP_CODE_CONTINUE; + } else if (!strcmp(rsp, "success")) { + rsp_code = BT_OBEX_RSP_CODE_SUCCESS; + } else if (!strcmp(rsp, "error")) { + if (argc < 3) { + shell_error(sh, "[rsp_code] is needed if the rsp is %s", rsp); + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + rsp_code = (uint8_t)strtoul(argv[2], NULL, 16); + } else { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + err = bt_obex_action_rsp(&goep_app.goep.obex, rsp_code, goep_app.tx_buf); + if (err) { + shell_error(sh, "Fail to send action rsp %d", err); + } else { + goep_app.tx_buf = NULL; + } + return err; +} + +#define HELP_NONE "" + +SHELL_STATIC_SUBCMD_SET_CREATE(obex_add_header_cmds, + SHELL_CMD_ARG(count, NULL, "", cmd_add_header_count, 2, + 0), + SHELL_CMD_ARG(name, NULL, "", cmd_add_header_name, + 2, 0), + SHELL_CMD_ARG(type, NULL, + "", + cmd_add_header_type, 2, 0), + SHELL_CMD_ARG(len, NULL, "", cmd_add_header_len, 2, 0), + SHELL_CMD_ARG(time_iso_8601, NULL, "", + cmd_add_header_time_iso_8601, 2, 0), + SHELL_CMD_ARG(time, NULL, "", + cmd_add_header_time, 2, 0), + SHELL_CMD_ARG(description, NULL, "", + cmd_add_header_description, 2, 0), + SHELL_CMD_ARG(target, NULL, "", + cmd_add_header_target, 2, 0), + SHELL_CMD_ARG(http, NULL, "", cmd_add_header_http, 2, 0), + SHELL_CMD_ARG(body, NULL, "", cmd_add_header_body, 2, 0), + SHELL_CMD_ARG(end_body, NULL, "", + cmd_add_header_end_body, 2, 0), + SHELL_CMD_ARG(who, NULL, + "", + cmd_add_header_who, 2, 0), + SHELL_CMD_ARG(conn_id, NULL, "", + cmd_add_header_conn_id, 2, 0), + SHELL_CMD_ARG(app_param, NULL, "", + cmd_add_header_app_param, 2, 0), + SHELL_CMD_ARG(auth_challenge, NULL, "", + cmd_add_header_auth_challenge, 2, 0), + SHELL_CMD_ARG(auth_rsp, NULL, "", cmd_add_header_auth_rsp, + 2, 0), + SHELL_CMD_ARG(creator_id, NULL, "", + cmd_add_header_creator_id, 2, 0), + SHELL_CMD_ARG(wan_uuid, NULL, "", + cmd_add_header_wan_uuid, 2, 0), + SHELL_CMD_ARG(obj_class, NULL, "", cmd_add_header_obj_class, 2, + 0), + SHELL_CMD_ARG(session_param, NULL, "", + cmd_add_header_session_param, 2, 0), + SHELL_CMD_ARG(session_seq_number, NULL, + "", + cmd_add_header_session_seq_number, 2, 0), + SHELL_CMD_ARG(action_id, NULL, + "", + cmd_add_header_action_id, 2, 0), + SHELL_CMD_ARG(dest_name, NULL, + "", + cmd_add_header_dest_name, 2, 0), + SHELL_CMD_ARG(perm, NULL, "<4-byte bit mask for setting permissions>", cmd_add_header_perm, + 2, 0), + SHELL_CMD_ARG(srm, NULL, "<1-byte value to setup Single Response Mode (SRM)>", + cmd_add_header_srm, 2, 0), + SHELL_CMD_ARG(srm_param, NULL, "", + cmd_add_header_srm_param, 2, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(obex_client_cmds, + SHELL_CMD_ARG(conn, NULL, "", cmd_goep_client_conn, 2, 0), + SHELL_CMD_ARG(disconn, NULL, HELP_NONE, cmd_goep_client_disconn, 1, 0), + SHELL_CMD_ARG(put, NULL, "", cmd_goep_client_put, 2, 0), + SHELL_CMD_ARG(get, NULL, "", cmd_goep_client_get, 2, 0), + SHELL_CMD_ARG(abort, NULL, HELP_NONE, cmd_goep_client_abort, 1, 0), + SHELL_CMD_ARG(setpath, NULL, "[parent] [create]", cmd_goep_client_setpath, 1, 2), + SHELL_CMD_ARG(action, NULL, "", cmd_goep_client_action, 2, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_STATIC_SUBCMD_SET_CREATE(obex_server_cmds, + SHELL_CMD_ARG(conn, NULL, " [rsp_code]", + cmd_goep_server_conn, 3, 1), + SHELL_CMD_ARG(disconn, NULL, " [rsp_code]", + cmd_goep_server_disconn, 2, 1), + SHELL_CMD_ARG(put, NULL, " [rsp_code]", cmd_goep_server_put, + 2, 1), + SHELL_CMD_ARG(get, NULL, " [rsp_code]", cmd_goep_server_get, + 2, 1), + SHELL_CMD_ARG(abort, NULL, " [rsp_code]", + cmd_goep_server_abort, 2, 1), + SHELL_CMD_ARG(setpath, NULL, " [rsp_code]", + cmd_goep_server_setpath, 2, 1), + SHELL_CMD_ARG(action, NULL, " [rsp_code]", + cmd_goep_server_action, 2, 1), + SHELL_SUBCMD_SET_END +); + +static int cmd_alloc_buf(const struct shell *sh, size_t argc, char **argv) +{ + if (goep_app.tx_buf) { + shell_error(sh, "Buf %p is using", goep_app.tx_buf); + return -EBUSY; + } + + goep_app.tx_buf = bt_goep_create_pdu(&goep_app.goep, &tx_pool); + if (!goep_app.tx_buf) { + shell_error(sh, "Fail to allocate tx buffer"); + return -ENOBUFS; + } + + return 0; +} + +static int cmd_release_buf(const struct shell *sh, size_t argc, char **argv) +{ + if (!goep_app.tx_buf) { + shell_error(sh, "No buf is using"); + return -EINVAL; + } + + net_buf_unref(goep_app.tx_buf); + goep_app.tx_buf = NULL; + + return 0; +} + +static int cmd_common(const struct shell *sh, size_t argc, char **argv) +{ + if (argc == 1) { + shell_help(sh); + return SHELL_CMD_HELP_PRINTED; + } + + shell_error(sh, "%s unknown parameter: %s", argv[0], argv[1]); + + return -ENOEXEC; +} + +SHELL_STATIC_SUBCMD_SET_CREATE(goep_cmds, + SHELL_CMD_ARG(register-rfcomm, NULL, "", cmd_register_rfcomm, 2, 0), + SHELL_CMD_ARG(connect-rfcomm, NULL, "", cmd_connect_rfcomm, 2, 0), + SHELL_CMD_ARG(disconnect-rfcomm, NULL, HELP_NONE, cmd_disconnect_rfcomm, 1, 0), + SHELL_CMD_ARG(register-l2cap, NULL, "", cmd_register_l2cap, 2, 0), + SHELL_CMD_ARG(connect-l2cap, NULL, "", cmd_connect_l2cap, 2, 0), + SHELL_CMD_ARG(disconnect-l2cap, NULL, HELP_NONE, cmd_disconnect_l2cap, 1, 0), + SHELL_CMD_ARG(alloc-buf, NULL, "Alloc tx buffer", cmd_alloc_buf, 1, 0), + SHELL_CMD_ARG(release-buf, NULL, "Free allocated tx buffer", cmd_release_buf, 1, 0), + SHELL_CMD_ARG(add-header, &obex_add_header_cmds, "Adding header sets", cmd_common, 1, 0), + SHELL_CMD_ARG(client, &obex_client_cmds, "Client sets", cmd_common, 1, 0), + SHELL_CMD_ARG(server, &obex_server_cmds, "Server sets", cmd_common, 1, 0), + SHELL_SUBCMD_SET_END +); + +SHELL_CMD_ARG_REGISTER(goep, &goep_cmds, "Bluetooth GOEP shell commands", cmd_common, 1, 1); diff --git a/tests/bluetooth/shell/prj_br.conf b/tests/bluetooth/shell/prj_br.conf index d3d3406a4805..4f778f5f58c9 100644 --- a/tests/bluetooth/shell/prj_br.conf +++ b/tests/bluetooth/shell/prj_br.conf @@ -18,3 +18,4 @@ CONFIG_BT_HRS=y CONFIG_BT_L2CAP_DYNAMIC_CHANNEL=y CONFIG_BT_DEVICE_NAME="test shell" CONFIG_BT_GOEP=y +CONFIG_BT_OEBX_RSP_CODE_TO_STR=y From 2177c21c0605dfe74fb22a4075004513b8eee491 Mon Sep 17 00:00:00 2001 From: Lyle Zhu Date: Tue, 14 Jan 2025 11:28:05 +0800 Subject: [PATCH 11/11] Bluetooth: OBEX: Check if the added string is valid There are types of string can be added by using OBEX adding header function bt_obex_add_header_*(). One is byte sequence. Another is null terminated Unicode text. Add a function bt_obex_string_is_valid() to check if the added string is valid. And add a function bt_obex_unicode_is_valid() dedicated to check Unicode string. Signed-off-by: Lyle Zhu --- subsys/bluetooth/host/classic/obex.c | 216 ++++++++++++++++++++++----- 1 file changed, 178 insertions(+), 38 deletions(-) diff --git a/subsys/bluetooth/host/classic/obex.c b/subsys/bluetooth/host/classic/obex.c index e21ee241da40..0907ace71401 100644 --- a/subsys/bluetooth/host/classic/obex.c +++ b/subsys/bluetooth/host/classic/obex.c @@ -1769,6 +1769,72 @@ int bt_obex_action_rsp(struct bt_obex *obex, uint8_t rsp_code, struct net_buf *b return err; } +#define BT_OBEX_HEADER_ENCODING(header_id) (0xc0 & (header_id)) +#define BT_OBEX_HEADER_ENCODING_UNICODE 0x00 +#define BT_OBEX_HEADER_ENCODING_BYTE_SEQ 0x40 +#define BT_OBEX_HEADER_ENCODING_1_BYTE 0x80 +#define BT_OBEX_HEADER_ENCODING_4_BYTES 0xc0 + +#define BT_OBEX_UFT16_LEAD_SURROGATE_START 0xd800 +#define BT_OBEX_UFT16_LEAD_SURROGATE_END 0xdbff +#define BT_OBEX_UFT16_TRAIL_SURROGATE_START 0xdc00 +#define BT_OBEX_UFT16_TRAIL_SURROGATE_END 0xdfff + +#define BT_OBEX_UFT16_NULL_TERMINATED 0x0000 + +static bool bt_obex_unicode_is_valid(uint16_t len, const uint8_t *str) +{ + uint16_t index = 0; + uint16_t code; + + if ((len < 2) || (len % 2)) { + LOG_WRN("Invalid string length %d", len); + return false; + } + + code = sys_get_be16(&str[len - 2]); + if (code != BT_OBEX_UFT16_NULL_TERMINATED) { + LOG_WRN("Invalid terminated unicode %04x", code); + return false; + } + + while ((index + 1) < len) { + code = sys_get_be16(&str[index]); + if ((code >= BT_OBEX_UFT16_LEAD_SURROGATE_START) && + (code <= BT_OBEX_UFT16_LEAD_SURROGATE_END)) { + /* Find the trail surrogate */ + index += 2; + if ((index + 1) >= len) { + LOG_WRN("Invalid length, trail surrogate missing"); + return false; + } + + code = sys_get_be16(&str[index]); + if ((code < BT_OBEX_UFT16_TRAIL_SURROGATE_START) || + (code > BT_OBEX_UFT16_TRAIL_SURROGATE_END)) { + LOG_WRN("Invalid trail surrogate %04x at %d", code, index); + return false; + } + } else if ((code >= BT_OBEX_UFT16_TRAIL_SURROGATE_START) && + (code <= BT_OBEX_UFT16_TRAIL_SURROGATE_END)) { + LOG_WRN("Abnormal trail surrogate %04x at %d", code, index); + return false; + } + index += 2; + } + + return true; +} + +static bool bt_obex_string_is_valid(uint8_t id, uint16_t len, const uint8_t *str) +{ + if (BT_OBEX_HEADER_ENCODING(id) == BT_OBEX_HEADER_ENCODING_UNICODE) { + return bt_obex_unicode_is_valid(len, str); + } + + return true; +} + int bt_obex_add_header_count(struct net_buf *buf, uint32_t count) { size_t total; @@ -1792,8 +1858,8 @@ int bt_obex_add_header_name(struct net_buf *buf, uint16_t len, const uint8_t *na { size_t total; - if (!buf || !name) { - LOG_WRN("Invalid buf"); + if (!buf || !name || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -1802,6 +1868,11 @@ int bt_obex_add_header_name(struct net_buf *buf, uint16_t len, const uint8_t *na return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_NAME, len, name)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_NAME); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, name, len); @@ -1812,8 +1883,8 @@ int bt_obex_add_header_type(struct net_buf *buf, uint16_t len, const uint8_t *ty { size_t total; - if (!buf || !type) { - LOG_WRN("Invalid buf"); + if (!buf || !type || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -1822,6 +1893,11 @@ int bt_obex_add_header_type(struct net_buf *buf, uint16_t len, const uint8_t *ty return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_TYPE, len, type)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TYPE); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, type, len); @@ -1851,8 +1927,8 @@ int bt_obex_add_header_time_iso_8601(struct net_buf *buf, uint16_t len, const ui { size_t total; - if (!buf || !t) { - LOG_WRN("Invalid buf"); + if (!buf || !t || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -1861,6 +1937,11 @@ int bt_obex_add_header_time_iso_8601(struct net_buf *buf, uint16_t len, const ui return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_TIME_ISO_8601, len, t)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TIME_ISO_8601); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, t, len); @@ -1890,8 +1971,8 @@ int bt_obex_add_header_description(struct net_buf *buf, uint16_t len, const uint { size_t total; - if (!buf || !dec) { - LOG_WRN("Invalid buf"); + if (!buf || !dec || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -1900,6 +1981,11 @@ int bt_obex_add_header_description(struct net_buf *buf, uint16_t len, const uint return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_DES, len, dec)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_DES); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, dec, len); @@ -1910,8 +1996,8 @@ int bt_obex_add_header_target(struct net_buf *buf, uint16_t len, const uint8_t * { size_t total; - if (!buf || !target) { - LOG_WRN("Invalid buf"); + if (!buf || !target || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -1920,6 +2006,11 @@ int bt_obex_add_header_target(struct net_buf *buf, uint16_t len, const uint8_t * return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_TARGET, len, target)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_TARGET); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, target, len); @@ -1930,8 +2021,8 @@ int bt_obex_add_header_http(struct net_buf *buf, uint16_t len, const uint8_t *ht { size_t total; - if (!buf || !http) { - LOG_WRN("Invalid buf"); + if (!buf || !http || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -1940,6 +2031,11 @@ int bt_obex_add_header_http(struct net_buf *buf, uint16_t len, const uint8_t *ht return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_HTTP, len, http)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_HTTP); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, http, len); @@ -1950,8 +2046,8 @@ int bt_obex_add_header_body(struct net_buf *buf, uint16_t len, const uint8_t *bo { size_t total; - if (!buf || !body) { - LOG_WRN("Invalid buf"); + if (!buf || !body || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -1960,6 +2056,11 @@ int bt_obex_add_header_body(struct net_buf *buf, uint16_t len, const uint8_t *bo return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_BODY, len, body)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_BODY); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, body, len); @@ -1970,8 +2071,8 @@ int bt_obex_add_header_end_body(struct net_buf *buf, uint16_t len, const uint8_t { size_t total; - if (!buf || !body) { - LOG_WRN("Invalid buf"); + if (!buf || !body || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -1980,6 +2081,11 @@ int bt_obex_add_header_end_body(struct net_buf *buf, uint16_t len, const uint8_t return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_END_BODY, len, body)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_END_BODY); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, body, len); @@ -1990,8 +2096,8 @@ int bt_obex_add_header_who(struct net_buf *buf, uint16_t len, const uint8_t *who { size_t total; - if (!buf || !who) { - LOG_WRN("Invalid buf"); + if (!buf || !who || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -2000,6 +2106,11 @@ int bt_obex_add_header_who(struct net_buf *buf, uint16_t len, const uint8_t *who return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_WHO, len, who)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_WHO); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, who, len); @@ -2029,8 +2140,8 @@ int bt_obex_add_header_app_param(struct net_buf *buf, uint16_t len, const uint8_ { size_t total; - if (!buf || !app_param) { - LOG_WRN("Invalid buf"); + if (!buf || !app_param || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -2039,6 +2150,11 @@ int bt_obex_add_header_app_param(struct net_buf *buf, uint16_t len, const uint8_ return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_APP_PARAM, len, app_param)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_APP_PARAM); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, app_param, len); @@ -2049,8 +2165,8 @@ int bt_obex_add_header_auth_challenge(struct net_buf *buf, uint16_t len, const u { size_t total; - if (!buf || !auth) { - LOG_WRN("Invalid buf"); + if (!buf || !auth || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -2059,6 +2175,11 @@ int bt_obex_add_header_auth_challenge(struct net_buf *buf, uint16_t len, const u return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_AUTH_CHALLENGE, len, auth)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_AUTH_CHALLENGE); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, auth, len); @@ -2069,8 +2190,8 @@ int bt_obex_add_header_auth_rsp(struct net_buf *buf, uint16_t len, const uint8_t { size_t total; - if (!buf || !auth) { - LOG_WRN("Invalid buf"); + if (!buf || !auth || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -2079,6 +2200,11 @@ int bt_obex_add_header_auth_rsp(struct net_buf *buf, uint16_t len, const uint8_t return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_AUTH_RSP, len, auth)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_AUTH_RSP); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, auth, len); @@ -2108,8 +2234,8 @@ int bt_obex_add_header_wan_uuid(struct net_buf *buf, uint16_t len, const uint8_t { size_t total; - if (!buf || !uuid) { - LOG_WRN("Invalid buf"); + if (!buf || !uuid || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -2118,6 +2244,11 @@ int bt_obex_add_header_wan_uuid(struct net_buf *buf, uint16_t len, const uint8_t return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_WAN_UUID, len, uuid)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_WAN_UUID); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, uuid, len); @@ -2128,8 +2259,8 @@ int bt_obex_add_header_obj_class(struct net_buf *buf, uint16_t len, const uint8_ { size_t total; - if (!buf || !obj_class) { - LOG_WRN("Invalid buf"); + if (!buf || !obj_class || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -2138,6 +2269,11 @@ int bt_obex_add_header_obj_class(struct net_buf *buf, uint16_t len, const uint8_ return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_OBJECT_CLASS, len, obj_class)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_OBJECT_CLASS); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, obj_class, len); @@ -2149,8 +2285,8 @@ int bt_obex_add_header_session_param(struct net_buf *buf, uint16_t len, { size_t total; - if (!buf || !session_param) { - LOG_WRN("Invalid buf"); + if (!buf || !session_param || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -2159,6 +2295,11 @@ int bt_obex_add_header_session_param(struct net_buf *buf, uint16_t len, return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_SESSION_PARAM, len, session_param)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_SESSION_PARAM); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, session_param, len); @@ -2207,8 +2348,8 @@ int bt_obex_add_header_dest_name(struct net_buf *buf, uint16_t len, const uint8_ { size_t total; - if (!buf || !dest_name) { - LOG_WRN("Invalid buf"); + if (!buf || !dest_name || !len) { + LOG_WRN("Invalid parameter"); return -EINVAL; } @@ -2217,6 +2358,11 @@ int bt_obex_add_header_dest_name(struct net_buf *buf, uint16_t len, const uint8_ return -ENOMEM; } + if (!bt_obex_string_is_valid(BT_OBEX_HEADER_ID_DEST_NAME, len, dest_name)) { + LOG_WRN("Invalid string"); + return -EINVAL; + } + net_buf_add_u8(buf, BT_OBEX_HEADER_ID_DEST_NAME); net_buf_add_be16(buf, (uint16_t)total); net_buf_add_mem(buf, dest_name, len); @@ -2280,12 +2426,6 @@ int bt_obex_add_header_srm_param(struct net_buf *buf, uint8_t srm_param) return 0; } -#define BT_OBEX_HEADER_ENCODING(header_id) (0xc0 & (header_id)) -#define BT_OBEX_HEADER_ENCODING_UNICODE 0x00 -#define BT_OBEX_HEADER_ENCODING_BYTE_SEQ 0x40 -#define BT_OBEX_HEADER_ENCODING_1_BYTE 0x80 -#define BT_OBEX_HEADER_ENCODING_4_BYTES 0xc0 - int bt_obex_header_parse(struct net_buf *buf, bool (*func)(struct bt_obex_hdr *hdr, void *user_data), void *user_data) {