diff --git a/include/zephyr/bluetooth/classic/goep.h b/include/zephyr/bluetooth/classic/goep.h new file mode 100644 index 000000000000..0dc147e45284 --- /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..eeee460880e1 --- /dev/null +++ b/include/zephyr/bluetooth/classic/obex.h @@ -0,0 +1,1534 @@ +/* obex.h - IrDA Oject Exchange Protocol handling */ + +/* + * Copyright 2024-2025 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 Bluetooth OBEX TLV triplet. + * + * Description of different data types that can be encoded into + * TLV triplet. Used to form arrays that are passed to the + * bt_obex_add_header_app_param(), bt_obex_add_header_auth_challenge(), + * and bt_obex_add_header_auth_rsp() function. + */ +struct bt_obex_tlv { + uint8_t type; + uint8_t data_len; + const uint8_t *data; +}; + +/** @brief Add Header: extended application request & response information. + * + * @param buf Buffer needs to be sent. + * @param count Number of @ref bt_obex_tlv structures in @p data. + * @param data Array of @ref bt_obex_tlv structures. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_app_param(struct net_buf *buf, size_t count, + const struct bt_obex_tlv data[]); + +/** + * OBEX digest-challenge tag: Nonce + * String of bytes representing the nonce. + */ +#define BT_OBEX_CHALLENGE_TAG_NONCE 0x00 + +/** + * OBEX digest-challenge tag: Options + * Optional Challenge Information. + */ +#define BT_OBEX_CHALLENGE_TAG_OPTIONS 0x01 + +/** Option BIT0: When set, the User Id must be sent in the authenticate response. */ +#define BT_OBEX_CHALLENGE_TAG_OPTION_REQ_USER_ID BIT(0) +/** Option BIT1: Access mode: Read Only when set, otherwise Full access is permitted. */ +#define BT_OBEX_CHALLENGE_TAG_OPTION_ACCESS_MODE BIT(1) + +/** + * OBEX digest-challenge tag: Realm + * A displayable string indicating which userid and/or password to use. The first byte of the + * string is the character set to use. The character set uses the same values as those defined + * in IrLMP for the nickname. + */ +#define BT_OBEX_CHALLENGE_TAG_REALM 0x02 + +/** @brief Add Header: authentication digest-challenge. + * + * @param buf Buffer needs to be sent. + * @param count Number of @ref bt_obex_tlv structures in @p data. + * @param data Array of @ref bt_obex_tlv structures. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_auth_challenge(struct net_buf *buf, size_t count, + const struct bt_obex_tlv data[]); + +/** + * OBEX digest-Response tag: Request-digest + * String of bytes representing the request digest. + */ +#define BT_OBEX_RESPONSE_TAG_REQ_DIGEST 0x00 + +/** + * OBEX digest-Response tag: User Id + * User ID string of length n. Max size is 20 bytes. + */ +#define BT_OBEX_RESPONSE_TAG_USER_ID 0x01 + +/** + * OBEX digest-Response tag: Nonce + * The nonce sent in the digest challenge string. + */ +#define BT_OBEX_RESPONSE_TAG_NONCE 0x02 + +/** @brief Add Header: authentication digest-response. + * + * @param buf Buffer needs to be sent. + * @param count Number of @ref bt_obex_tlv structures in @p data. + * @param data Array of @ref bt_obex_tlv structures. + * + * @return 0 in case of success or negative value in case of error. + */ +int bt_obex_add_header_auth_rsp(struct net_buf *buf, size_t count, const struct bt_obex_tlv data[]); + +/** @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 Helper for parsing OBEX TLV triplet. + * + * A helper for parsing the TLV triplet structure for OBEX packets. The most common scenario is to + * call this helper on the in the callback of OBEX server and client. + * The @p data is encoded by using A Tag-Length-Value encoding scheme. Usually, it is the header + * value of the header `Application Request-Response Parameters`, `Authenticate Challenge`, and + * `Authenticate Response`. It means the @p data is the output of the + * @ref bt_obex_get_header_app_param, @ref bt_obex_get_header_auth_challenge, or + * @ref bt_obex_get_header_auth_rsp. + * + * @param len The length of the @p data. + * @param data The data as given to the callback of OBEX server and client. + * @param func Callback function which will be called for each TLV triplet that's found from the + * @p 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_tlv_parse(uint16_t len, const uint8_t *data, + bool (*func)(struct bt_obex_tlv *tlv, void *user_data), void *user_data); + +/** @brief Get header value: extended application request & response information. + * + * The parameter can be parsed by calling @ref bt_obex_tlv_parse. + * + * @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. + * + * The options can be parsed by calling @ref bt_obex_tlv_parse. + * + * @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. + * + * The options can be parsed by calling @ref bt_obex_tlv_parse. + * + * @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/common/Kconfig b/subsys/bluetooth/common/Kconfig index 56dd33c73fed..773f18050ca6 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/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..997748846a85 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -229,6 +229,48 @@ config BT_COD consult the following link: https://www.bluetooth.com/specifications/assigned-numbers +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 + 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..f140199556e1 --- /dev/null +++ b/subsys/bluetooth/host/classic/goep.c @@ -0,0 +1,693 @@ +/* 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 "rfcomm_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); + int err; + + 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; + } + + err = bt_rfcomm_dlc_send(&goep->_transport.dlc, buf); + if (err < 0) { + return err; + } + + return 0; +} + +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; + uint32_t mtu; + uint32_t hdr_size; + 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.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"); + 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; + uint32_t mtu; + uint32_t hdr_size; + + 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"); + 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; + uint32_t mtu; + uint32_t hdr_size; + 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.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"); + 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; + uint32_t mtu; + uint32_t hdr_size; + + 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; + } + + state = atomic_get(&goep->_state); + if (state != BT_GOEP_TRANSPORT_DISCONNECTED) { + LOG_DBG("Invalid stats %d", state); + 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"); + 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..d7128d0db5d5 --- /dev/null +++ b/subsys/bluetooth/host/classic/obex.c @@ -0,0 +1,3584 @@ +/* obex.c - IrDA Oject Exchange Protocol handling */ + +/* + * Copyright 2024-2025 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; + int err; + + 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); + + err = obex_send(obex, buf); + if (err) { + net_buf_unref(buf); + } + + return err; +} + +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; + int err; + + 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); + + err = obex_send(obex, buf); + if (err) { + net_buf_unref(buf); + } + + return err; +} + +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; + int err; + + 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); + + err = obex_send(obex, buf); + if (err) { + net_buf_unref(buf); + } + + return err; +} + +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; + int err; + + 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); + + err = obex_send(obex, buf); + if (err) { + net_buf_unref(buf); + } + + return err; +} + +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; + int err; + + 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); + + err = obex_send(obex, buf); + if (err) { + net_buf_unref(buf); + } + + return err; +} + +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; + int err; + + 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); + + err = obex_send(obex, buf); + if (err) { + net_buf_unref(buf); + } + + return err; +} + +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; + int err; + + 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); + + err = obex_send(obex, buf); + if (err) { + net_buf_unref(buf); + } + + return err; +} + +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; + int err; + + 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); + + err = obex_send(obex, buf); + if (err) { + net_buf_unref(buf); + } + + return err; +} + +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; + int err; + + 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) { + 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); + + err = obex_send(obex, buf); + if (err) { + net_buf_unref(buf); + } + + return err; +} + +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 ((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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + + if (allocated) { + net_buf_unref(buf); + } + } + 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + + if (allocated) { + net_buf_unref(buf); + } + } 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + + if (allocated) { + net_buf_unref(buf); + } + } 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + + if (allocated) { + net_buf_unref(buf); + } + } 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + + if (allocated) { + net_buf_unref(buf); + } + } + 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + } + } else { + if (allocated) { + net_buf_unref(buf); + } + } + 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + + if (allocated) { + net_buf_unref(buf); + } + } + 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + } + } else { + if (allocated) { + net_buf_unref(buf); + } + } + 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + + if (allocated) { + net_buf_unref(buf); + } + } 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + } else { + if (allocated) { + net_buf_unref(buf); + } + } + 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + + if (allocated) { + net_buf_unref(buf); + } + } + 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + } else { + if (allocated) { + net_buf_unref(buf); + } + } + 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + + if (allocated) { + net_buf_unref(buf); + } + } + 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; + bool allocated = false; + + 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; + } + allocated = true; + } + + 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); + } + } else { + if (allocated) { + net_buf_unref(buf); + } + } + 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; + + 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; + + /* + * OBEX Version 1.5, section 2.2.2 Name + * The `name` could be a NULL, so the `len` of the name could 0. + * In some cases an empty Name header is used to specify a particular behavior; + * see the GET and SETPATH Operations. An empty Name header is defined as a Name + * header of length 3 (one byte opcode + two byte length). + */ + if (!buf || (len && !name)) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + return -ENOMEM; + } + + if (len && !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); + if (len) { + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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, size_t count, const struct bt_obex_tlv data[]) +{ + size_t total; + uint16_t len = 0; + + if (!buf || !data || !count) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + for (size_t i = 0; i < count; i++) { + if (data[i].data_len && !data[i].data) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + len += data[i].data_len + sizeof(data[i].type) + sizeof(data[i].data_len); + } + + 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); + for (size_t i = 0; i < count; i++) { + len += data[i].data_len + sizeof(data[i].type) + sizeof(data[i].data_len); + net_buf_add_u8(buf, data[i].type); + net_buf_add_u8(buf, data[i].data_len); + net_buf_add_mem(buf, data[i].data, (size_t)data[i].data_len); + } + return 0; +} + +int bt_obex_add_header_auth_challenge(struct net_buf *buf, size_t count, + const struct bt_obex_tlv data[]) +{ + size_t total; + uint16_t len = 0; + + if (!buf || !data || !count) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + for (size_t i = 0; i < count; i++) { + if (data[i].data_len && !data[i].data) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + len += data[i].data_len + sizeof(data[i].type) + sizeof(data[i].data_len); + } + + 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); + for (size_t i = 0; i < count; i++) { + len += data[i].data_len + sizeof(data[i].type) + sizeof(data[i].data_len); + net_buf_add_u8(buf, data[i].type); + net_buf_add_u8(buf, data[i].data_len); + net_buf_add_mem(buf, data[i].data, (size_t)data[i].data_len); + } + return 0; +} + +int bt_obex_add_header_auth_rsp(struct net_buf *buf, size_t count, const struct bt_obex_tlv data[]) +{ + size_t total; + uint16_t len = 0; + + if (!buf || !data || !count) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + for (size_t i = 0; i < count; i++) { + if (data[i].data_len && !data[i].data) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + len += data[i].data_len + sizeof(data[i].type) + sizeof(data[i].data_len); + } + + 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); + for (size_t i = 0; i < count; i++) { + len += data[i].data_len + sizeof(data[i].type) + sizeof(data[i].data_len); + net_buf_add_u8(buf, data[i].type); + net_buf_add_u8(buf, data[i].data_len); + net_buf_add_mem(buf, data[i].data, (size_t)data[i].data_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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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 || !len) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + total = sizeof(uint8_t) + sizeof(len) + len; + if (net_buf_tailroom(buf) < total) { + 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); + 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; +} + +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; + bool found; +}; + +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; + data.found = false; + + 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; + data.found = false; + + err = bt_obex_header_parse(buf, bt_obex_find_header_cb, &data); + if (err) { + return err; + } + + if (!data.found) { + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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_tlv_parse(uint16_t len, const uint8_t *data, + bool (*func)(struct bt_obex_tlv *tlv, void *user_data), void *user_data) +{ + uint16_t index = 0; + struct bt_obex_tlv tlv; + + if (!len || !data || !func) { + LOG_WRN("Invalid parameter"); + return -EINVAL; + } + + while ((index + 2) <= len) { + tlv.type = data[index]; + tlv.data_len = data[index + 1]; + index += 2; + if ((index + tlv.data_len) > len) { + break; + } + + tlv.data = &data[index]; + index += tlv.data_len; + if (!func(&tlv, user_data)) { + return 0; + } + } + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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; + data.found = false; + + 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); 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..0d68bb14c295 --- /dev/null +++ b/subsys/bluetooth/host/classic/shell/goep.c @@ -0,0 +1,1662 @@ +/** @file + * @brief Bluetooth GOEP shell module + * + * Provide some Bluetooth shell commands that can be useful to applications. + */ +/* + * Copyright 2024-2025 NXP + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +#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; + +static struct bt_goep_transport_rfcomm_server rfcomm_server; +static struct bt_goep_transport_l2cap_server l2cap_server; + +#define TLV_COUNT 3 +#define TLV_BUFFER_SIZE 64 + +static struct bt_obex_tlv tlvs[TLV_COUNT]; +static uint8_t tlv_buffers[TLV_COUNT][TLV_BUFFER_SIZE]; +static uint8_t tlv_count; + +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 bool goep_parse_tlvs_cb(struct bt_obex_tlv *tlv, void *user_data) +{ + bt_shell_print("T %02x L %d", tlv->type, tlv->data_len); + bt_shell_hexdump(tlv->data, tlv->data_len); + + return true; +} + +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); + + switch (hdr->id) { + case BT_OBEX_HEADER_ID_APP_PARAM: + case BT_OBEX_HEADER_ID_AUTH_CHALLENGE: + case BT_OBEX_HEADER_ID_AUTH_RSP: + int err; + + err = bt_obex_tlv_parse(hdr->len, hdr->data, goep_parse_tlvs_cb, NULL); + if (err) { + bt_shell_error("Fail to parse OBEX TLV triplet"); + } + break; + default: + bt_shell_hexdump(hdr->data, hdr->len); + break; + } + + 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 = { + .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) +{ + 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 = { + .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; +} + +static int cmd_add_header_count(const struct shell *sh, size_t argc, char *argv[]) +{ + uint32_t count; + int err; + + 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_add_header_name(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + const char *hex_payload; + size_t hex_payload_size; + + if (argc > 1) { + hex_payload = argv[1]; + 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; + } + } else { + len = 0; + } + + 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; +} + +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; + uint8_t tag; + bool last = false; + + if (tlv_count >= TLV_COUNT) { + shell_warn(sh, "No space of TLV array, add app_param and clear tlvs"); + goto add_header; + } + + len = hex2bin(argv[1], strlen(argv[1]), &tag, sizeof(tag)); + if (len < 1) { + shell_error(sh, "Length should not be zero"); + return -ENOEXEC; + } + + len = hex2bin(argv[2], strlen(argv[2]), &tlv_buffers[tlv_count][0], TLV_BUFFER_SIZE); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + if (argc > 3) { + if (!strcmp(argv[3], "last")) { + last = true; + } + } + + tlvs[tlv_count].type = tag; + tlvs[tlv_count].data_len = len; + tlvs[tlv_count].data = &tlv_buffers[tlv_count][0]; + + tlv_count++; + + if (!last) { + return 0; + } + +add_header: + err = bt_obex_add_header_app_param(goep_app.tx_buf, (size_t)tlv_count, tlvs); + if (err) { + shell_error(sh, "Fail to add header app_param"); + } + tlv_count = 0; + return err; +} + +static int cmd_add_header_auth_challenge(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + uint8_t tag; + bool last = false; + + if (tlv_count >= TLV_COUNT) { + shell_warn(sh, "No space of TLV array, add auth_challenge and clear tlvs"); + goto add_header; + } + + len = hex2bin(argv[1], strlen(argv[1]), &tag, sizeof(tag)); + if (len < 1) { + shell_error(sh, "Length should not be zero"); + return -ENOEXEC; + } + + len = hex2bin(argv[2], strlen(argv[2]), &tlv_buffers[tlv_count][0], TLV_BUFFER_SIZE); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + if (argc > 3) { + if (!strcmp(argv[3], "last")) { + last = true; + } + } + + tlvs[tlv_count].type = tag; + tlvs[tlv_count].data_len = len; + tlvs[tlv_count].data = &tlv_buffers[tlv_count][0]; + + tlv_count++; + + if (!last) { + return 0; + } + +add_header: + err = bt_obex_add_header_auth_challenge(goep_app.tx_buf, (size_t)tlv_count, tlvs); + if (err) { + shell_error(sh, "Fail to add header auth_challenge"); + } + tlv_count = 0; + return err; +} + +static int cmd_add_header_auth_rsp(const struct shell *sh, size_t argc, char *argv[]) +{ + size_t len; + int err; + uint8_t tag; + bool last = false; + + if (tlv_count >= TLV_COUNT) { + shell_warn(sh, "No space of TLV array, add auth_rsp and clear tlvs"); + goto add_header; + } + + len = hex2bin(argv[1], strlen(argv[1]), &tag, sizeof(tag)); + if (len < 1) { + shell_error(sh, "Length should not be zero"); + return -ENOEXEC; + } + + len = hex2bin(argv[2], strlen(argv[2]), &tlv_buffers[tlv_count][0], TLV_BUFFER_SIZE); + if (len > UINT16_MAX) { + shell_error(sh, "Length exceeds max length (%x > %x)", len, UINT16_MAX); + return -ENOEXEC; + } + + if (argc > 3) { + if (!strcmp(argv[3], "last")) { + last = true; + } + } + + tlvs[tlv_count].type = tag; + tlvs[tlv_count].data_len = len; + tlvs[tlv_count].data = &tlv_buffers[tlv_count][0]; + + tlv_count++; + + if (!last) { + return 0; + } + +add_header: + err = bt_obex_add_header_auth_rsp(goep_app.tx_buf, (size_t)tlv_count, tlvs); + if (err) { + shell_error(sh, "Fail to add header auth_rsp"); + } + tlv_count = 0; + 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; +} + +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, "[name of the object (often a file name)]", cmd_add_header_name, + 1, 1), + 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, "application parameter: [last]", + cmd_add_header_app_param, 3, 1), + SHELL_CMD_ARG(auth_challenge, NULL, "authentication digest-challenge: [last]", + cmd_add_header_auth_challenge, 3, 1), + SHELL_CMD_ARG(auth_rsp, NULL, "authentication digest-response: [last]", + cmd_add_header_auth_rsp, 3, 1), + 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 45d3806473ff..4f778f5f58c9 100644 --- a/tests/bluetooth/shell/prj_br.conf +++ b/tests/bluetooth/shell/prj_br.conf @@ -17,3 +17,5 @@ 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 +CONFIG_BT_OEBX_RSP_CODE_TO_STR=y