diff --git a/include/zephyr/bluetooth/classic/avrcp.h b/include/zephyr/bluetooth/classic/avrcp.h index 82405e28d6e5a..98049a4079331 100644 --- a/include/zephyr/bluetooth/classic/avrcp.h +++ b/include/zephyr/bluetooth/classic/avrcp.h @@ -257,6 +257,26 @@ typedef enum __packed { * Valid for Commands: All Register Notification commands */ BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED = 0x16, + + /** In transition response. + * The target is currently changing state (e.g., between play/pause). + * Note: This status is not used for browsing commands. + */ + BT_AVRCP_STATUS_IN_TRANSITION = 0xfd, + + /** Not implemented response. + * The command/PDU is not supported by the target device. + * Note: This status is not used for browsing commands. + */ + BT_AVRCP_STATUS_NOT_IMPLEMENTED = 0xfe, + + /** Successful response. + * The requested command or PDU was processed successfully by the target device. + * + * For control commands, it means the request was accepted. + * For status commands, it means the state is stable and reported successfully. + */ + BT_AVRCP_STATUS_SUCCESS = BT_AVRCP_STATUS_OPERATION_COMPLETED, } bt_avrcp_status_t; /** @brief AVRCP CT structure */ @@ -300,7 +320,68 @@ struct bt_avrcp_passthrough_rsp { struct bt_avrcp_passthrough_opvu_data data[0]; /**< opvu data */ } __packed; -struct bt_avrcp_get_cap_rsp { +typedef enum __packed { + /** Capabilities */ + BT_AVRCP_PDU_ID_GET_CAPS = 0x10, + + /** Player Application Settings */ + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS = 0x11, + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS = 0x12, + BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL = 0x13, + BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL = 0x14, + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT = 0x15, + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT = 0x16, + BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET = 0x17, + BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT = 0x18, + + /** Metadata Attributes for Current Media Item */ + BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS = 0x20, + + /** Notifications */ + BT_AVRCP_PDU_ID_GET_PLAY_STATUS = 0x30, + BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION = 0x31, + BT_AVRCP_PDU_ID_EVT_PLAYBACK_STATUS_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_TRACK_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_TRACK_REACHED_END = 0x31, + BT_AVRCP_PDU_ID_EVT_TRACK_REACHED_START = 0x31, + BT_AVRCP_PDU_ID_EVT_PLAYBACK_POS_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_BATT_STATUS_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_SYSTEM_STATUS_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_PLAYER_APP_SETTING_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_VOLUME_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_ADDRESSED_PLAYER_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_AVAILABLE_PLAYERS_CHANGED = 0x31, + BT_AVRCP_PDU_ID_EVT_UIDS_CHANGED = 0x31, + + /** Continuation */ + BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP = 0x40, + BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP = 0x41, + + /** Absolute Volume */ + BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME = 0x50, + + /** Media Player Selection */ + BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER = 0x60, + + /** Browsing */ + BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER = 0x70, + BT_AVRCP_PDU_ID_GET_FOLDER_ITEMS = 0x71, + BT_AVRCP_PDU_ID_CHANGE_PATH = 0x72, + BT_AVRCP_PDU_ID_GET_ITEM_ATTRS = 0x73, + BT_AVRCP_PDU_ID_PLAY_ITEM = 0x74, + BT_AVRCP_PDU_ID_GET_TOTAL_NUMBER_OF_ITEMS = 0x75, + + /** Search */ + BT_AVRCP_PDU_ID_SEARCH = 0x80, + + /** Now Playing */ + BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING = 0x90, + + /** Error Response */ + BT_AVRCP_PDU_ID_GENERAL_REJECT = 0xa0, +} bt_avrcp_pdu_id_t; + +struct bt_avrcp_get_caps_rsp { uint8_t cap_id; /**< bt_avrcp_cap_t */ uint8_t cap_cnt; /**< number of items contained in *cap */ uint8_t cap[]; /**< 1 or 3 octets each depends on cap_id */ @@ -327,6 +408,308 @@ struct bt_avrcp_set_browsed_player_rsp { struct bt_avrcp_folder_name folder_names[0]; /**< Folder names data */ } __packed; +/** @brief AVRCP Playback Status */ +typedef enum __packed { + BT_AVRCP_PLAYBACK_STATUS_STOPPED = 0x00, + BT_AVRCP_PLAYBACK_STATUS_PLAYING = 0x01, + BT_AVRCP_PLAYBACK_STATUS_PAUSED = 0x02, + BT_AVRCP_PLAYBACK_STATUS_FWD_SEEK = 0x03, + BT_AVRCP_PLAYBACK_STATUS_REV_SEEK = 0x04, + BT_AVRCP_PLAYBACK_STATUS_ERROR = 0xFF, +} bt_avrcp_playback_status_t; + +/** @brief AVRCP System Status Code. */ +typedef enum __packed { + BT_AVRCP_SYSTEM_STATUS_POWER_ON = 0x00, + BT_AVRCP_SYSTEM_STATUS_POWER_OFF = 0x01, + BT_AVRCP_SYSTEM_STATUS_UNPLUGGED = 0x02, +} bt_avrcp_system_status_t; + +/** @brief AVRCP Battery Status Code. */ +typedef enum __packed { + BT_AVRCP_BATTERY_STATUS_NORMAL = 0x00, + BT_AVRCP_BATTERY_STATUS_WARNING = 0x01, + BT_AVRCP_BATTERY_STATUS_CRITICAL = 0x02, + BT_AVRCP_BATTERY_STATUS_EXTERNAL = 0x03, + BT_AVRCP_BATTERY_STATUS_FULL = 0x04, +} bt_avrcp_battery_status_t; + +/** AVRCP MAX absolute volume. */ +#define BT_AVRCP_MAX_ABSOLUTE_VOLUME 0x7F + +/** @brief AVRCP Media Attribute IDs */ +typedef enum __packed { + BT_AVRCP_MEDIA_ATTR_TITLE = 0x01, + BT_AVRCP_MEDIA_ATTR_ARTIST = 0x02, + BT_AVRCP_MEDIA_ATTR_ALBUM = 0x03, + BT_AVRCP_MEDIA_ATTR_TRACK_NUMBER = 0x04, + BT_AVRCP_MEDIA_ATTR_TOTAL_TRACKS = 0x05, + BT_AVRCP_MEDIA_ATTR_GENRE = 0x06, + BT_AVRCP_MEDIA_ATTR_PLAYING_TIME = 0x07, +} bt_avrcp_media_attr_t; + +/** @brief GetElementAttributes command request structure */ +struct bt_avrcp_get_element_attrs_cmd { + uint8_t identifier[8]; /**< Element identifier (0x0 for currently playing) */ + uint8_t num_attrs; /**< Number of attributes requested (0 = all) */ + uint32_t attr_ids[]; /**< Array of requested attribute IDs */ +} __packed; + +/** @brief AVRCP Media Attribute structure */ +struct bt_avrcp_media_attr { + uint32_t attr_id; /**< Media attribute ID, see @ref bt_avrcp_media_attr_t */ + uint16_t charset_id; /**< Character set ID, see @ref bt_avrcp_charset_t */ + uint16_t attr_len; /**< Length of attribute value */ + uint8_t attr_val[]; /**< Attribute value data */ +} __packed; + +/** @brief GetElementAttributes response structure */ +struct bt_avrcp_get_element_attrs_rsp { + uint8_t num_attrs; /**< Number of attributes in response */ + struct bt_avrcp_media_attr attrs[]; /**< Array of media attributes */ +} __packed; + +/** @brief AVRCP Player Application Setting Attribute IDs */ +typedef enum __packed { + BT_AVRCP_PLAYER_ATTR_EQUALIZER = 0x01U, + BT_AVRCP_PLAYER_ATTR_REPEAT_MODE = 0x02U, + BT_AVRCP_PLAYER_ATTR_SHUFFLE = 0x03U, + BT_AVRCP_PLAYER_ATTR_SCAN = 0x04U, +} bt_avrcp_player_attr_id_t; + +/** @brief AVRCP Player Application Setting Values for Equalizer */ +typedef enum __packed { + BT_AVRCP_EQUALIZER_OFF = 0x01U, + BT_AVRCP_EQUALIZER_ON = 0x02U, +} bt_avrcp_equalizer_value_t; + +/** @brief AVRCP Player Application Setting Values for Repeat Mode */ +typedef enum __packed { + BT_AVRCP_REPEAT_MODE_OFF = 0x01U, + BT_AVRCP_REPEAT_MODE_SINGLE_TRACK = 0x02U, + BT_AVRCP_REPEAT_MODE_ALL_TRACKS = 0x03U, + BT_AVRCP_REPEAT_MODE_GROUP = 0x04U, +} bt_avrcp_repeat_mode_value_t; + +/** @brief AVRCP Player Application Setting Values for Shuffle */ +typedef enum __packed { + BT_AVRCP_SHUFFLE_OFF = 0x01U, + BT_AVRCP_SHUFFLE_ALL_TRACKS = 0x02U, + BT_AVRCP_SHUFFLE_GROUP = 0x03U, +} bt_avrcp_shuffle_value_t; + +/** @brief AVRCP Player Application Setting Values for Scan */ +typedef enum __packed { + BT_AVRCP_SCAN_OFF = 0x01U, + BT_AVRCP_SCAN_ALL_TRACKS = 0x02U, + BT_AVRCP_SCAN_GROUP = 0x03U, +} bt_avrcp_scan_value_t; + +/** @brief AVRCP Scope Values + * 0x00 = Media Player List + * 0x01 = Filesystem + * 0x02 = Search + * 0x03 = Now Playing + */ +typedef enum __packed { + BT_AVRCP_SCOPE_MEDIA_PLAYER_LIST = 0x00U, + BT_AVRCP_SCOPE_FILESYSTEM = 0x01U, + BT_AVRCP_SCOPE_SEARCH = 0x02U, + BT_AVRCP_SCOPE_NOW_PLAYING = 0x03U, +} bt_avrcp_scope_t; + +/** @brief ListPlayerApplicationSettingAttributes response */ +struct bt_avrcp_list_app_setting_attr_rsp { + uint8_t num_attrs; /**< Number of application setting attributes */ + uint8_t attr_ids[]; /**< Array of attribute IDs @ref bt_avrcp_player_attr_id_t */ +} __packed; + +/** @brief ListPlayerApplicationSettingValues command request */ +struct bt_avrcp_list_player_app_setting_vals_cmd { + uint8_t attr_id; /**< Attribute ID to query values for */ +} __packed; + +/** @brief ListPlayerApplicationSettingValues response */ +struct bt_avrcp_list_player_app_setting_vals_rsp { + uint8_t num_values; /**< Number of values for the attribute */ + uint8_t values[]; /**< Array of possible values */ +} __packed; + +/** @brief GetCurrentPlayerApplicationSettingValue command request */ +struct bt_avrcp_get_curr_player_app_setting_val_cmd { + uint8_t num_attrs; /**< Number of attributes to query */ + uint8_t attr_ids[]; /**< Array of attribute IDs */ +} __packed; + +/** @brief AVRCP Attribute-Value Pair */ +struct bt_avrcp_app_setting_attr_val { + uint8_t attr_id; /**< Attribute ID */ + uint8_t value_id; /**< Value ID */ +} __packed; + +/** @brief GetCurrentPlayerApplicationSettingValue response */ +struct bt_avrcp_get_curr_player_app_setting_val_rsp { + uint8_t num_attrs; /**< Number of attributes returned */ + struct bt_avrcp_app_setting_attr_val attr_vals[]; /**< Array of attribute-value pairs */ +} __packed; + +/** @brief SetPlayerApplicationSettingValue command request */ +struct bt_avrcp_set_player_app_setting_val_cmd { + uint8_t num_attrs; /**< Number of attributes to set */ + struct bt_avrcp_app_setting_attr_val attr_vals[]; /**< Array of attribute-value pairs */ +} __packed; + +/** @brief GetPlayerApplicationSettingAttributeText command request */ +struct bt_avrcp_get_player_app_setting_attr_text_cmd { + uint8_t num_attrs; /**< Number of attributes to get text for */ + uint8_t attr_ids[]; /**< Array of attribute IDs */ +} __packed; + +/** @brief AVRCP Attribute Text Entry */ +struct bt_avrcp_app_setting_attr_text { + uint8_t attr_id; /**< Attribute ID */ + uint16_t charset_id; /**< Charset ID */ + uint8_t text_len; /**< Length of text */ + uint8_t text[]; /**< Text string */ +} __packed; + +/** @brief GetPlayerApplicationSettingAttributeText response */ +struct bt_avrcp_get_player_app_setting_attr_text_rsp { + uint8_t num_attrs; /**< Number of attributes returned */ + struct bt_avrcp_app_setting_attr_text attr_text[]; +} __packed; + +/** @brief GetPlayerApplicationSettingValueText command request */ +struct bt_avrcp_get_player_app_setting_val_text_cmd { + uint8_t attr_id; /**< Attribute ID */ + uint8_t num_values; /**< Number of values to get text for */ + uint8_t value_ids[]; /**< Array of value IDs */ +} __packed; + +/** @brief AVRCP Attribute Text Entry */ +struct bt_avrcp_app_setting_val_text { + uint8_t value_id; /**< Value ID */ + uint16_t charset_id; /**< Charset ID */ + uint8_t text_len; /**< Length of text */ + uint8_t text[]; /**< Text string */ +} __packed; + +/** @brief GetPlayerApplicationSettingValueText response */ +struct bt_avrcp_get_player_app_setting_val_text_rsp { + uint8_t num_values; /**< Number of values returned */ + struct bt_avrcp_app_setting_val_text value_text[]; +} __packed; + +/** @brief InformDisplayableCharacterSet command request */ +struct bt_avrcp_inform_displayable_char_set_cmd { + uint8_t num_charsets; /**< Number of character sets supported */ + uint16_t charset_ids[]; /**< Array of character set IDs */ +} __packed; + +/** @brief InformBatteryStatusOfCT command request */ +struct bt_avrcp_inform_batt_status_of_ct_cmd { + uint8_t battery_status; /**< Battery status value @ref bt_avrcp_battery_status_t */ +} __packed; + +/** @brief GetPlayStatus response */ +struct bt_avrcp_get_play_status_rsp { + uint32_t song_length; /**< Total length of the song in milliseconds */ + uint32_t song_position; /**< Current position in the song in milliseconds */ + uint8_t play_status; /**< Play status: @ref bt_avrcp_playback_status_t */ +} __packed; + +/** @brief RegisterNotification command request */ +struct bt_avrcp_register_notification_cmd { + uint8_t event_id; /**< Event ID to register for */ + uint32_t interval; /**< Playback interval (used only for event_id = 0x05) */ +} __packed; + +/** @brief SetAbsoluteVolume command request */ +struct bt_avrcp_set_absolute_volume_cmd { + uint8_t absolute_volume; /**< Volume level (0x00 to 0x7F) */ +} __packed; + +/** @brief SetAbsoluteVolume response */ +struct bt_avrcp_set_absolute_volume_rsp { + uint8_t absolute_volume; /**< Volume level acknowledged */ +} __packed; + +/** @brief SetAddressedPlayer command request */ +struct bt_avrcp_set_addressed_player_cmd { + uint16_t player_id; /**< Player ID to be addressed */ +} __packed; + +/** @brief PlayItem command request */ +struct bt_avrcp_play_item_cmd { + uint8_t scope; /**< Scope: @ref bt_avrcp_scope_t */ + uint8_t uid[8]; /**< UID of the item */ + uint16_t uid_counter; /**< UID counter */ +} __packed; + +/** @brief AddToNowPlaying command request */ +struct bt_avrcp_add_to_now_playing_cmd { + uint8_t scope; /**< Scope: @ref bt_avrcp_scope_t */ + uint8_t uid[8]; /**< UID of the item */ + uint16_t uid_counter; /**< UID counter */ +} __packed; + +struct bt_avrcp_event_data { + union { + /* EVENT_PLAYBACK_STATUS_CHANGED */ + uint8_t play_status; + + /* EVENT_TRACK_CHANGED */ + uint8_t identifier[8]; + + /* EVENT_PLAYBACK_POS_CHANGED */ + uint32_t playback_pos; + + /* EVENT_BATT_STATUS_CHANGED */ + uint8_t battery_status; + + /* EVENT_SYSTEM_STATUS_CHANGED */ + uint8_t system_status; + + /* EVENT_PLAYER_APPLICATION_SETTING_CHANGED */ + struct __packed { + uint8_t num_of_attr; + struct bt_avrcp_app_setting_attr_val *attr_vals; + } setting_changed; + + /* EVENT_ADDRESSED_PLAYER_CHANGED */ + struct __packed { + uint16_t player_id; + uint16_t uid_counter; + } addressed_player_changed; + + /* EVENT_UIDS_CHANGED */ + uint16_t uid_counter; + + /* EVENT_VOLUME_CHANGED */ + uint8_t absolute_volume; + }; +}; + +/** @brief Callback function type for AVRCP event notifications for event changed. + * + * This callback is invoked by the AVRCP Target (TG) when a registered event + * occurs and a "changed" notification needs to be sent to the Controller (CT). + * + * For interim and rejected error cases, the callback will trigger the + * `notification_rsp` function registered by the Controller + + * @param event_id The AVRCP event identifier. @ref bt_avrcp_event_data + * This corresponds to one of the AVRCP event types such as + * EVENT_PLAYBACK_STATUS_CHANGED, EVENT_TRACK_CHANGED, etc. + * + * @param data Pointer to @ref bt_avrcp_event_data structure containing the event-specific + * data. The content of the union depends on the event_id. + * + * @note The callback implementation should not block or perform heavy operations. + * If needed, defer processing to another thread or task. + */ +typedef void(*bt_avrcp_notification_cb_t)(uint8_t event_id, struct bt_avrcp_event_data *data); + struct bt_avrcp_ct_cb { /** @brief An AVRCP CT connection has been established. * @@ -366,16 +749,27 @@ struct bt_avrcp_ct_cb { */ void (*browsing_disconnected)(struct bt_avrcp_ct *ct); - /** @brief Callback function for bt_avrcp_get_cap(). + /** @brief Callback function for bt_avrcp_get_caps(). * * Called when the get capabilities process is completed. * * @param ct AVRCP CT connection object. * @param tid The transaction label of the response. - * @param rsp The response for Get Capabilities command. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the BT_AVRCP_PDU_ID_GET_CAPS + * payload returned by the TG. The application can parse this + * payload according to the format, defined in @ref bt_avrcp_get_caps_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP response + * code is an AV/C REJECTED response, and buf is NULL. + * Note that all multi-octet fields are encoded in big-endian format. */ - void (*get_cap_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, - const struct bt_avrcp_get_cap_rsp *rsp); + void (*get_caps_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + struct net_buf *buf); /** @brief Callback function for bt_avrcp_get_unit_info(). * @@ -423,6 +817,286 @@ struct bt_avrcp_ct_cb { * big-endian format. */ void (*browsed_player_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + + /** @brief Callback function for Set Absolute Volume response (CT). + * + * Called when the Set Absolute Volume response is received from the TG. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param absolute_volume The absolute volume value (0x00-0x7F). + */ + void (*set_absolute_volume_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + uint8_t absolute_volume); + + /** @brief Callback function for Event Notification response (CT). + * + * Called when the AVRCP Target (TG) sends a response to a previously + * registered event (Register Notification). This callback reports the + * event type, the response phase (e.g., Interim), and the event-specific + * payload. + * + * @param ct AVRCP Controller (CT) connection context. + * @param tid Transaction label that correlates this notification + * with the original Register Notification request. + * @param status TG status/phase code (BT_AVRCP_STATUS_*). Typically + * BT_AVRCP_STATUS_SUCCESS for an interim notification. + * Error codes may be returned for invalid parameters or + * unsupported events. + * @param event_id The AVRCP event identifier. @ref bt_avrcp_event_data + * This corresponds to one of the AVRCP event types such as + * EVENT_PLAYBACK_STATUS_CHANGED, EVENT_TRACK_CHANGED, etc. + * + * @param data Pointer to @ref bt_avrcp_event_data structure containing the + * event-specific data. The content of the union depends on the event_id. + * + * @note This callback is only invoked for interim notifications and error + * statuses from the TG. For CHANGED event notifications, the event must + * first be registered using @ref bt_avrcp_notification_cb_t. + */ + void (*notification_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + uint8_t event_id, struct bt_avrcp_event_data *data); + + /** @brief Callback for PDU ID LIST_PLAYER_APP_SETTING_ATTRS. + * + * Called when the response for LIST_PLAYER_APP_SETTING_ATTRS is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the LIST_PLAYER_APP_SETTING_ATTRS + * payload returned by the TG. + * The application can parse this payload according to the format + * defined in @ref bt_avrcp_list_app_setting_attr_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP response + * code is an AV/C REJECTED response, and buf is NULL. + * Note that all multi-octet fields are encoded in big-endian format. + */ + void (*list_player_app_setting_attrs_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf); + + /** @brief Callback for PDU ID LIST_PLAYER_APP_SETTING_VALS. + * + * Called when the response for LIST_PLAYER_APP_SETTING_VALS is received.` + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the LIST_PLAYER_APP_SETTING_VALS + * payload returned by the TG. + * The application can parse this payload according to the format + * defined in @ref bt_avrcp_list_player_app_setting_vals_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP response + * code is an AV/C REJECTED response, and buf is NULL. + * Note that all multi-octet fields are encoded in big-endian format. + */ + void (*list_player_app_setting_vals_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf); + + /** @brief Callback for PDU ID GET_CURR_PLAYER_APP_SETTING_VAL. + * + * Called when the response for GET_CURR_PLAYER_APP_SETTING_VAL is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the GET_CURR_PLAYER_APP_SETTING_VAL + * payload returned by the TG. + * The application can parse this payload according to the format + * defined in @ref bt_avrcp_get_curr_player_app_setting_val_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP response + * code is an AV/C REJECTED response, and buf is NULL. + * Note that all multi-octet fields are encoded in big-endian format. + */ + void (*get_curr_player_app_setting_val_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf); + + /** @brief Callback for PDU ID SET_PLAYER_APP_SETTING_VAL. + * + * Called when the response for SET_PLAYER_APP_SETTING_VAL is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + void (*set_player_app_setting_val_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status); + + /** @brief Callback for PDU ID GET_PLAYER_APP_SETTING_ATTR_TEXT. + * + * Called when the response for GET_PLAYER_APP_SETTING_ATTR_TEXT is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the GET_PLAYER_APP_SETTING_ATTR_TEXT + * payload returned by the TG, formatted as + * @ref bt_avrcp_get_player_app_setting_attr_text_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP response + * code is an AV/C REJECTED response, and buf is NULL. + * Note that all multi-octet fields are encoded in big-endian format. + */ + void (*get_player_app_setting_attr_text_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf); + + /** @brief Callback for PDU ID GET_PLAYER_APP_SETTING_VAL_TEXT. + * + * Called when the response for GET_PLAYER_APP_SETTING_VAL_TEXT is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the GET_PLAYER_APP_SETTING_VAL_TEXT + * payload returned by the TG, formatted as + * @ref bt_avrcp_get_player_app_setting_val_text_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP response + * code is an AV/C REJECTED response, and buf is NULL. + * Note that all multi-octet fields are encoded in big-endian format. + */ + void (*get_player_app_setting_val_text_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf); + + /** @brief Callback for PDU ID INFORM_DISPLAYABLE_CHAR_SET. + * + * Called when the response for INFORM_DISPLAYABLE_CHAR_SET is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + void (*inform_displayable_char_set_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status); + + /** @brief Callback for PDU ID INFORM_BATT_STATUS_OF_CT. + * + * Called when the response for INFORM_BATT_STATUS_OF_CT is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + void (*inform_batt_status_of_ct_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status); + + /** @brief Callback for PDU ID GET_ELEMENT_ATTRS. + * + * Called when the response for GET_ELEMENT_ATTRS is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the GET_ELEMENT_ATTRS payload + * returned by the TG, formatted as + * @ref bt_avrcp_get_element_attrs_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP response + * code is an AV/C REJECTED response, and buf is NULL. + * Note that all multi-octet fields are encoded in big-endian format. + */ + void (*get_element_attrs_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + struct net_buf *buf); + + /** @brief Callback for PDU ID GET_PLAY_STATUS. + * + * Called when the response for GET_PLAY_STATUS is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation, @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + * @param buf The response buffer containing the GET_PLAY_STATUS payload + * returned by the TG, formatted as + * @ref bt_avrcp_get_play_status_rsp. + * If status is in the range BT_AVRCP_STATUS_INVALID_COMMAND to + * BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED, and is not equal to + * BT_AVRCP_STATUS_OPERATION_COMPLETED, it indicates that the AVRCP response + * code is an AV/C REJECTED response, and buf is NULL. + * Note that all multi-octet fields are encoded in big-endian format. + */ + void (*get_play_status_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + struct net_buf *buf); + + /** @brief Callback for PDU ID SET_ADDRESSED_PLAYER. + * + * Called when the response for SET_ADDRESSED_PLAYER is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation. @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + void (*set_addressed_player_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status); + + /** @brief Callback for PDU ID PLAY_ITEM. + * + * Called when the response for PLAY_ITEM is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation. @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + void (*play_item_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status); + + /** @brief Callback for PDU ID ADD_TO_NOW_PLAYING. + * + * Called when the response for ADD_TO_NOW_PLAYING is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param status The status code returned by the TG, indicating the result of the + * operation. @ref bt_avrcp_status_t. Typically corresponds to + * BT_AVRCP_STATUS_* values such as BT_AVRCP_STATUS_SUCCESS + * or BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + void (*add_to_now_playing_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status); }; /** @brief Connect AVRCP. @@ -461,6 +1135,21 @@ int bt_avrcp_disconnect(struct bt_conn *conn); */ struct net_buf *bt_avrcp_create_pdu(struct net_buf_pool *pool); +/** + * @brief Allocate a net_buf for AVRCP Vendor-Dependent PDU transmission, reserving + * headroom for the Vendor PDU header in addition to AVRCP, AVCTP, L2CAP, + * and ACL headers. + * + * This function allocates a buffer from the specified pool and reserves + * sufficient headroom for protocol headers required by AVRCP Vendor-Dependent + * PDUs over Bluetooth. + * + * @param pool The buffer pool to allocate from. + * + * @return A newly allocated net_buf with reserved headroom. + */ +struct net_buf *bt_avrcp_create_vendor_pdu(struct net_buf_pool *pool); + /** @brief Connect AVRCP browsing channel. * * This function is to be called after the AVRCP control channel is established. @@ -502,7 +1191,7 @@ int bt_avrcp_ct_register_cb(const struct bt_avrcp_ct_cb *cb); * * @return 0 in case of success or error code in case of error. */ -int bt_avrcp_ct_get_cap(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t cap_id); +int bt_avrcp_ct_get_caps(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t cap_id); /** @brief Get AVRCP Unit Info. * @@ -556,6 +1245,194 @@ int bt_avrcp_ct_passthrough(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t opid, u */ int bt_avrcp_ct_set_browsed_player(struct bt_avrcp_ct *ct, uint8_t tid, uint16_t player_id); +/** @brief Register for AVRCP changed notifications with callback. + * + * This function registers for notifications from the target device. + * The notification response will be received through the provided callback function. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param event_id The event ID to register for, see @ref bt_avrcp_evt_t. + * @param interval The playback interval for position changed events. + * Other events will have this value set to 0 to ignore. + * @param cb The callback function to handle the changed notification response. + * + * @return 0 in case of success or error code in case of error. + */ +int bt_avrcp_ct_register_notification(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t event_id, + uint32_t interval, bt_avrcp_notification_cb_t cb); + +/** @brief Send Set Absolute Volume command (CT). + * + * This function sends the Set Absolute Volume command to the TG. + * + * @param ct The AVRCP CT instance. + * @param tid The transaction label of the command, valid from 0 to 15. + * @param absolute_volume The absolute volume value (0x00-0x7F). + * + * @return 0 in case of success or error code in case of error. + */ +int bt_avrcp_ct_set_absolute_volume(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t absolute_volume); + +/** @brief Send AVRCP vendor dependent command for LIST_PLAYER_APP_SETTING_ATTRS. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_list_player_app_setting_attrs(struct bt_avrcp_ct *ct, uint8_t tid); + +/** @brief Send AVRCP vendor dependent command for LIST_PLAYER_APP_SETTING_VALS. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param attr_id Player application setting attribute ID for which the possible + * values should be listed (e.g., Equalizer, Repeat Mode, Shuffle, Scan). + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_list_player_app_setting_vals(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t attr_id); + +/** @brief Send AVRCP vendor dependent command for GET_CURR_PLAYER_APP_SETTING_VAL. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the GET_CURR_PLAYER_APP_SETTING_VAL + * request payload, formatted as + * @ref bt_avrcp_get_curr_player_app_setting_val_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_get_curr_player_app_setting_val(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for SET_PLAYER_APP_SETTING_VAL. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the SET_PLAYER_APP_SETTING_VAL + * request payload, formatted as + * @ref bt_avrcp_set_player_app_setting_val_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_set_player_app_setting_val(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for GET_PLAYER_APP_SETTING_ATTR_TEXT. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the GET_PLAYER_APP_SETTING_ATTR_TEXT + * request payload, formatted as + * @ref bt_avrcp_get_player_app_setting_attr_text_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_get_player_app_setting_attr_text(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for GET_PLAYER_APP_SETTING_VAL_TEXT. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the GET_PLAYER_APP_SETTING_VAL_TEXT + * request payload, formatted as + * @ref bt_avrcp_get_player_app_setting_val_text_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_get_player_app_setting_val_text(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for INFORM_DISPLAYABLE_CHAR_SET. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the INFORM_DISPLAYABLE_CHAR_SET + * request payload, formatted as + * @ref bt_avrcp_inform_displayable_char_set_cmd. + * Note that all multi-octet fields are encoded in big-endian format + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_inform_displayable_char_set(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for INFORM_BATT_STATUS_OF_CT. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param battery_status Battery status value @ref bt_avrcp_battery_status_t + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_inform_batt_status_of_ct(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t battery_status); + +/** @brief Send AVRCP vendor dependent command for GET_ELEMENT_ATTRS. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the GET_ELEMENT_ATTRS + * request payload, formatted as + * @ref bt_avrcp_get_element_attrs_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_get_element_attrs(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for GET_PLAY_STATUS. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_get_play_status(struct bt_avrcp_ct *ct, uint8_t tid); + +/** @brief Send AVRCP vendor dependent command for SET_ADDRESSED_PLAYER. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param player_id The player ID to be set as addressed player. + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_set_addressed_player(struct bt_avrcp_ct *ct, uint8_t tid, uint16_t player_id); + +/** @brief Send AVRCP vendor dependent command for PLAY_ITEM. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the PLAY_ITEM + * request payload, formatted as + * @ref bt_avrcp_play_item_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_play_item(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for ADD_TO_NOW_PLAYING. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param buf The command buffer containing the ADD_TO_NOW_PLAYING + * request payload, formatted as + * @ref bt_avrcp_add_to_now_playing_cmd. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_ct_add_to_now_playing(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + struct bt_avrcp_tg_cb { /** @brief An AVRCP TG connection has been established. * @@ -585,6 +1462,19 @@ struct bt_avrcp_tg_cb { */ void (*unit_info_req)(struct bt_avrcp_tg *tg, uint8_t tid); + /** @brief Register notification request callback. + * + * This callback is called whenever an AVRCP register notification is requested. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param event_id The event ID that the CT wants to register for @ref bt_avrcp_evt_t. + * @param interval The playback interval for position changed event. + * other events will have this value set to 0 for ingnoring. + */ + void (*register_notification_req)(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t event_id, + uint32_t interval); + /** @brief Subunit Info Request callback. * * This callback is called whenever an AVRCP subunit info is requested. @@ -594,6 +1484,16 @@ struct bt_avrcp_tg_cb { */ void (*subunit_info_req)(struct bt_avrcp_tg *tg, uint8_t tid); + /** @brief Get capabilities request callback. + * + * This callback is called whenever an AVRCP get capabilities command is received. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param cap_id The capability ID requested. + */ + void (*get_caps_req)(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t cap_id); + /** @brief An AVRCP TG browsing connection has been established. * * This callback notifies the application of an avrcp browsing connection, @@ -635,6 +1535,169 @@ struct bt_avrcp_tg_cb { * in big-endian format. */ void (*passthrough_req)(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf); + + /** @brief Callback function for Set Absolute Volume command (TG). + * + * Called when the Set Absolute Volume command is received from the CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param absolute_volume The absolute volume value (0x00-0x7F). + */ + void (*set_absolute_volume_req)(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t absolute_volume); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS. + * + * Called when the TG receives a vendor dependent command LIST_PLAYER_APP_SETTING_ATTRS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + */ + void (*list_player_app_setting_attrs_req)(struct bt_avrcp_tg *tg, uint8_t tid); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS. + * + * Called when the TG receives a vendor dependent command for LIST_PLAYER_APP_SETTING_VALS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param attr_id Player application setting attribute ID for which the possible + * values should be listed (e.g., Equalizer, Repeat Mode, Shuffle, Scan). + */ + void (*list_player_app_setting_vals_req)(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t attr_id); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL. + * + * Called when the TG receives a vendor dependent command GET_CURR_PLAYER_APP_SETTING_VAL. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the GET_CURR_PLAYER_APP_SETTING_VAL command payload, + * formatted as @ref bt_avrcp_get_curr_player_app_setting_val_cmd. + * The application should parse fields in big-endian order. + */ + void (*get_curr_player_app_setting_val_req)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL. + * + * Called when the TG receives a vendor dependent command for SET_PLAYER_APP_SETTING_VAL. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the SET_PLAYER_APP_SETTING_VAL command payload, + * formatted as @ref bt_avrcp_set_player_app_setting_val_cmd. + * The application should parse fields in big-endian order. + */ + void (*set_player_app_setting_val_req)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT. + * + * Called when the TG receives a vendor dependent command GET_PLAYER_APP_SETTING_ATTR_TEXT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the GET_PLAYER_APP_SETTING_VAL_TEXT command payload, + * formatted as @ref bt_avrcp_get_player_app_setting_val_text_cmd. + * The application should parse fields in big-endian order. + */ + void (*get_player_app_setting_attr_text_req)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT. + * + * Called when the TG receives a vendor dependent command GET_PLAYER_APP_SETTING_VAL_TEXT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the GET_PLAYER_APP_SETTING_VAL_TEXT command payload, + * formatted as @ref bt_avrcp_get_player_app_setting_val_text_cmd. + * The application should parse fields in big-endian order. + */ + void (*get_player_app_setting_val_text_req)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET. + * + * Called when the TG receives a vendor dependent command for INFORM_DISPLAYABLE_CHAR_SET. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the INFORM_DISPLAYABLE_CHAR_SET command payload, + * formatted as @ref bt_avrcp_inform_displayable_char_set_cmd. + * The application should parse fields in big-endian order. + */ + void (*inform_displayable_char_set_req)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT. + * + * Called when the TG receives a vendor dependent command for INFORM_BATT_STATUS_OF_CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param battery_status Battery status value @ref bt_avrcp_battery_status_t + */ + void (*inform_batt_status_of_ct_req)(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t battery_status); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS. + * + * Called when the TG receives a vendor dependent command for GET_ELEMENT_ATTRS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the GET_ELEMENT_ATTRS command payload, + * formatted as @ref bt_avrcp_get_element_attrs_cmd. + * The application should parse fields in big-endian order. + */ + void (*get_element_attrs_req)(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_GET_PLAY_STATUS. + * + * Called when the TG receives a vendor dependent command for GET_PLAY_STATUS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + */ + void (*get_play_status_req)(struct bt_avrcp_tg *tg, uint8_t tid); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER. + * + * Called when the TG receives a vendor dependent command for SET_ADDRESSED_PLAYER. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param player_id The player ID to be set as addressed player. + */ + void (*set_addressed_player_req)(struct bt_avrcp_tg *tg, uint8_t tid, uint16_t player_id); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_PLAY_ITEM. + * + * Called when the TG receives a vendor dependent command for PLAY_ITEM. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the PLAY_ITEM command payload, + * formatted as @ref bt_avrcp_play_item_cmd. + * The application should parse fields in big-endian order. + */ + void (*play_item_req)(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING. + * + * Called when the TG receives a vendor dependent command for ADD_TO_NOW_PLAYING. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the command. + * @param buf The buffer containing the ADD_TO_NOW_PLAYING command payload, + * formatted as @ref bt_avrcp_add_to_now_playing_cmd. + * The application should parse fields in big-endian order. + */ + void (*add_to_now_playing_req)(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf); }; /** @brief Register callback. @@ -671,6 +1734,49 @@ int bt_avrcp_tg_send_unit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid, */ int bt_avrcp_tg_send_subunit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid); +/** @brief Send GET_CAPABILITIES response. + * + * This function is called by the application to send the GET_CAPABILITIES response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the GET_CAPS payload, + * formatted as @ref bt_avrcp_get_caps_rsp. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 in case of success or error code in case of error. + */ +int bt_avrcp_tg_send_get_caps_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, + struct net_buf *buf); + +/** @brief Send notification response. + * + * This function sends a notification response from the AVRCP Target (TG) to + * the Controller (CT) for a previously registered event. The response can be: + * + * - **INTERIM**: Sent on the first call for the given @p event_id to indicate + * that the event is being monitored. + * - **CHANGED**: Sent on the next call for the same @p event_id when the event + * state has changed. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param event_id The AVRCP event ID for which the notification is sent, @ref bt_avrcp_evt_t. + * @param data Pointer to an bt_avrcp_event_data structure containing the event-specific + * data. The content of the union depends on the event_id. + * + * @return 0 in case of success or error code in case of error. + * + * @note + * - The first successful call for a given @p event_id sends an INTERIM response. + * - The next successful call for the same @p event_id sends a CHANGED response. + * - If @p status is not SUCCESS, a REJECTED or NOT_IMPLEMENTED response is sent. + */ +int bt_avrcp_tg_send_notification_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, + uint8_t event_id, struct bt_avrcp_event_data *data); + /** @brief Send the set browsed player response. * * This function is called by the application to send the set browsed player response. @@ -684,6 +1790,20 @@ int bt_avrcp_tg_send_subunit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid); int bt_avrcp_tg_send_set_browsed_player_rsp(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf); +/** @brief Send Set Absolute Volume response (TG). + * + * This function sends the Set Absolute Volume response to the CT. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param absolute_volume The absolute volume value (0x00-0x7F). + * + * @return 0 in case of success or error code in case of error. + */ +int bt_avrcp_tg_send_absolute_volume_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, + uint8_t absolute_volume); + /** @brief Send AVRCP Pass Through response. * * This function is called by the application to send the Pass Through response. @@ -701,6 +1821,167 @@ int bt_avrcp_tg_send_set_browsed_player_rsp(struct bt_avrcp_tg *tg, uint8_t tid, */ int bt_avrcp_tg_send_passthrough_rsp(struct bt_avrcp_tg *tg, uint8_t tid, bt_avrcp_rsp_t result, struct net_buf *buf); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the LIST_PLAYER_APP_SETTING_ATTRS payload, + * formatted as @ref bt_avrcp_list_app_setting_attr_rsp. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_list_player_app_setting_attrs_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the LIST_PLAYER_APP_SETTING_VALS payload, + * formatted as @ref bt_avrcp_list_player_app_setting_vals_rsp. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_list_player_app_setting_vals_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the GET_CURR_PLAYER_APP_SETTING_VAL payload, + * formatted as @ref bt_avrcp_get_curr_player_app_setting_val_rsp. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_get_curr_player_app_setting_val_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_set_player_app_setting_val_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the GET_PLAYER_APP_SETTING_ATTR_TEXT payload, + * formatted as @ref bt_avrcp_get_player_app_setting_attr_text_rsp. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_get_player_app_setting_attr_text_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the GET_PLAYER_APP_SETTING_VAL_TEXT payload, + * formatted as @ref bt_avrcp_get_player_app_setting_val_text_rsp. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_get_player_app_setting_val_text_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_inform_displayable_char_set_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_inform_batt_status_of_ct_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the GET_ELEMENT_ATTRS payload, + * formatted as @ref bt_avrcp_get_element_attrs_rsp. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_get_element_attrs_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_GET_PLAY_STATUS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * @param buf The response buffer containing the GET_PLAY_STATUS payload, + * formatted as @ref bt_avrcp_get_play_status_rsp. + * Note that all multi-octet fields are encoded in big-endian format. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_get_play_status_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, + struct net_buf *buf); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_set_addressed_player_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_PLAY_ITEM. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_play_item_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param status Status code of the operation @ref bt_avrcp_status_t. + * + * @return 0 on success or error code. + */ +int bt_avrcp_tg_send_add_to_now_playing_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status); #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 60252cbcecc84..d1fdf2f0ddb77 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -505,6 +505,17 @@ config BT_AVRCP_CONTROLLER help This option enables the AVRCP profile controller function +config BT_AVRCP_VENDOR_RX_DATA_BUF_SIZE + int "AVRCP Vendor Dependent RX data buffer Size" + default 1024 + range 1024 $(INT16_MAX) + help + Size of the AVRCP Vendor Dependent RX data buffer in bytes. + This buffer is used for reassembling fragmented AVRCP Vendor Dependent + commands and responses received from the remote device. If the remote + sends large Vendor-specific PDUs or frequent fragmentation occurs, + consider increasing this value to avoid incomplete message handling. + config BT_AVRCP_BROWSING bool "Bluetooth AVRCP Browsing channel support [EXPERIMENTAL]" select EXPERIMENTAL diff --git a/subsys/bluetooth/host/classic/avctp.c b/subsys/bluetooth/host/classic/avctp.c index 5f921f4a422e5..75dfe0bd65202 100644 --- a/subsys/bluetooth/host/classic/avctp.c +++ b/subsys/bluetooth/host/classic/avctp.c @@ -330,8 +330,8 @@ static void avctp_tx_processor(struct k_work *item) goto failed; } - if (net_buf_tailroom(buf) < chunk_size) { - LOG_WRN("Not enough tailroom for AVCTP payload (len: %d)", chunk_size); + if (buf->len < chunk_size) { + LOG_WRN("AVCTP payload too short: have %d, need %d", buf->len, chunk_size); goto failed; } net_buf_pull_mem(buf, chunk_size); diff --git a/subsys/bluetooth/host/classic/avrcp.c b/subsys/bluetooth/host/classic/avrcp.c index 2789da0b318da..168d1c5cc5f14 100644 --- a/subsys/bluetooth/host/classic/avrcp.c +++ b/subsys/bluetooth/host/classic/avrcp.c @@ -41,10 +41,25 @@ struct bt_avrcp { struct bt_avrcp_ct { struct bt_avrcp *avrcp; + + struct net_buf *reassembly_buf; /**< Buffer for reassembling fragments */ + + struct bt_avrcp_notify_registration ct_notify[BT_AVRCP_EVT_VOLUME_CHANGED + 1]; }; struct bt_avrcp_tg { struct bt_avrcp *avrcp; + + /* AVRCP vendor dependent response TX pending */ + sys_slist_t vd_rsp_tx_pending; + + /* Critical locker */ + struct k_sem lock; + + /* AVRCP vendor dependent response TX work */ + struct k_work_delayable vd_rsp_tx_work; + + bool interim_sent[BT_AVRCP_EVT_VOLUME_CHANGED + 1]; }; struct avrcp_handler { @@ -52,6 +67,36 @@ struct avrcp_handler { void (*func)(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf); }; +struct avrcp_pdu_vendor_handler { + bt_avrcp_pdu_id_t pdu_id; + uint8_t min_len; + bt_avrcp_ctype_t cmd_type; + void (*func)(struct bt_avrcp *avrcp, uint8_t tid, uint8_t result, struct net_buf *buf); +}; + +static void avrcp_tx_buf_destroy(struct net_buf *buf) +{ + struct bt_avrcp_tg_vd_rsp_tx *tx = net_buf_user_data(buf); + + __ASSERT(tx != NULL, "user_data is NULL in destroy callback"); + + if ((tx != NULL) && (tx->tg != NULL)) { + (void)k_work_reschedule(&(tx->tg->vd_rsp_tx_work), K_NO_WAIT); + memset(tx, 0, sizeof(*tx)); + } + + net_buf_destroy(buf); +} + +NET_BUF_POOL_FIXED_DEFINE(avrcp_vd_rx_pool, CONFIG_BT_MAX_CONN, + CONFIG_BT_AVRCP_VENDOR_RX_DATA_BUF_SIZE, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + +NET_BUF_POOL_FIXED_DEFINE(avrcp_vd_tx_pool, CONFIG_BT_MAX_CONN, + BT_L2CAP_BUF_SIZE(BT_AVRCP_FRAGMENT_SIZE) + + sizeof(struct bt_avctp_header_start), + CONFIG_BT_CONN_TX_USER_DATA_SIZE, avrcp_tx_buf_destroy); + struct avrcp_pdu_handler { bt_avrcp_pdu_id_t pdu_id; uint8_t min_len; @@ -76,6 +121,13 @@ NET_BUF_POOL_FIXED_DEFINE(avctp_ctrl_tx_pool, CONFIG_BT_MAX_CONN, #define IS_CT_ROLE_SUPPORTED() (avrcp_ct_cb != NULL) #define IS_TG_ROLE_SUPPORTED() (avrcp_tg_cb != NULL) +#define AVRCP_TX_RETRY_DELAY_MS (100) + +#define AVRCP_STATUS_IS_REJECTED(status) (\ + ((uint8_t)(status) < BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED) && \ + ((uint8_t)(status) != BT_AVRCP_STATUS_OPERATION_COMPLETED) \ +) + static const struct bt_avrcp_ct_cb *avrcp_ct_cb; static const struct bt_avrcp_tg_cb *avrcp_tg_cb; static struct bt_avrcp avrcp_connection[CONFIG_BT_MAX_CONN]; @@ -304,6 +356,16 @@ static struct bt_sdp_attribute avrcp_ct_attrs[] = { static struct bt_sdp_record avrcp_ct_rec = BT_SDP_RECORD(avrcp_ct_attrs); #endif /* CONFIG_BT_AVRCP_CONTROLLER */ +static void avrcp_tg_lock(struct bt_avrcp_tg *tg) +{ + k_sem_take(&tg->lock, K_FOREVER); +} + +static void avrcp_tg_unlock(struct bt_avrcp_tg *tg) +{ + k_sem_give(&tg->lock); +} + static struct bt_avrcp *avrcp_get_connection(struct bt_conn *conn) { size_t index; @@ -349,6 +411,44 @@ static inline struct bt_avrcp_tg *get_avrcp_tg(struct bt_avrcp *avrcp) return &bt_avrcp_tg_pool[index]; } +static int bt_avrcp_rsp_to_status(uint8_t rsp, struct net_buf *buf, uint8_t *out_status) +{ + uint8_t status; + + if ((out_status == NULL) || (buf == NULL)) { + return -EINVAL; + } + + switch (rsp) { + case BT_AVRCP_RSP_STABLE: + status = BT_AVRCP_STATUS_SUCCESS; + break; + case BT_AVRCP_RSP_IN_TRANSITION: + status = BT_AVRCP_STATUS_IN_TRANSITION; + break; + case BT_AVRCP_RSP_ACCEPTED: + status = BT_AVRCP_STATUS_SUCCESS; + break; + case BT_AVRCP_RSP_NOT_IMPLEMENTED: + status = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + break; + case BT_AVRCP_RSP_REJECTED: + if (buf->len < sizeof(status)) { + LOG_ERR("AVRCP rejected response missing error code"); + *out_status = BT_AVRCP_STATUS_INTERNAL_ERROR; + return -EINVAL; + } + status = net_buf_pull_u8(buf); + break; + default: + status = BT_AVRCP_STATUS_INTERNAL_ERROR; + break; + } + + *out_status = status; + return 0; +} + /* The AVCTP L2CAP channel established */ static void avrcp_connected(struct bt_avctp *session) { @@ -402,7 +502,7 @@ static struct net_buf *avrcp_create_unit_pdu(struct bt_avrcp *avrcp, uint8_t cty return buf; } -static struct net_buf *avrcp_create_subunit_pdu(struct bt_avrcp *avrcp, uint8_t ctype_or_rsp) +static struct net_buf *avrcp_create_subunit_pdu(struct bt_avrcp *avrcp, uint8_t ctype_or_rsp) { struct net_buf *buf; struct bt_avrcp_frame *cmd; @@ -452,8 +552,8 @@ static struct net_buf *avrcp_create_vendor_pdu(struct bt_avrcp *avrcp, uint8_t c struct net_buf *buf; struct bt_avrcp_frame *cmd; - buf = bt_avctp_create_pdu(NULL); - if (!buf) { + buf = bt_avctp_create_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { return NULL; } @@ -494,8 +594,8 @@ static int avrcp_browsing_send(struct bt_avrcp *avrcp, struct net_buf *buf, bt_a uint8_t tid) { int err; - struct bt_avrcp_avc_brow_pdu *hdr = - (struct bt_avrcp_avc_brow_pdu *)(buf->data); + struct bt_avrcp_brow_pdu *hdr = + (struct bt_avrcp_brow_pdu *)(buf->data); LOG_DBG("AVRCP browsing send cr:0x%X, tid:0x%X, pdu_id:0x%02X\n", cr, tid, hdr->pdu_id); @@ -571,1081 +671,3675 @@ static int bt_avrcp_send_subunit_info(struct bt_avrcp *avrcp, uint8_t tid, uint8 return err; } -static void process_get_cap_rsp(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +static int init_fragmentation_context(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t rsp) { - struct bt_avrcp_avc_pdu *pdu; - struct bt_avrcp_get_cap_rsp *rsp; - uint16_t len; - uint16_t expected_len; + struct bt_avrcp_ct_frag_reassembly_ctx *ctx; - if (buf->len < sizeof(*pdu)) { - LOG_ERR("Invalid vendor payload length: %d", buf->len); - return; + if (ct == NULL) { + return -EINVAL; } - pdu = net_buf_pull_mem(buf, sizeof(*pdu)); + /* Clean up any existing reassembly buffer */ + if (ct->reassembly_buf != NULL) { + LOG_WRN("Interleaving fragments not allowed (tid=%u, rsp=%u)", tid, rsp); + net_buf_unref(ct->reassembly_buf); + ct->reassembly_buf = NULL; + } - if (BT_AVRCP_AVC_PDU_GET_PACKET_TYPE(pdu) != BT_AVRVP_PKT_TYPE_SINGLE) { - LOG_ERR("Invalid packet type"); - return; + /* Allocate reassembly buffer */ + ct->reassembly_buf = net_buf_alloc(&avrcp_vd_rx_pool, K_NO_WAIT); + if (ct->reassembly_buf == NULL) { + LOG_ERR("Failed to allocate reassembly buffer"); + return -ENOMEM; } - len = sys_be16_to_cpu(pdu->param_len); + __ASSERT_NO_MSG(ct->reassembly_buf->user_data_size >= sizeof(*ctx)); + ctx = net_buf_user_data(ct->reassembly_buf); - if (len < sizeof(*rsp) || buf->len < len) { - LOG_ERR("Invalid capability length: %d, buf length = %d", len, buf->len); - return; - } + ctx->tid = tid; + ctx->rsp = rsp; + return 0; +} - if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_cap_rsp == NULL)) { - return; +static struct bt_avrcp_ct_frag_reassembly_ctx *get_fragmentation_context(struct bt_avrcp_ct *ct) +{ + struct bt_avrcp_ct_frag_reassembly_ctx *ctx; + + if (ct == NULL) { + return NULL; } - rsp = (struct bt_avrcp_get_cap_rsp *)buf->data; + ctx = net_buf_user_data(ct->reassembly_buf); - switch (rsp->cap_id) { - case BT_AVRCP_CAP_COMPANY_ID: - expected_len = rsp->cap_cnt * BT_AVRCP_COMPANY_ID_SIZE; - break; - case BT_AVRCP_CAP_EVENTS_SUPPORTED: - expected_len = rsp->cap_cnt; - break; - default: - LOG_ERR("Unrecognized capability = 0x%x", rsp->cap_id); - return; + return ctx; +} + +static int add_fragment_data(struct bt_avrcp_ct *ct, const uint8_t *data, uint16_t data_len) +{ + if ((ct == NULL) || (data == NULL) || (ct->reassembly_buf == NULL)) { + return -EINVAL; } - if (buf->len < sizeof(*rsp) + expected_len) { - LOG_ERR("Invalid capability payload length: %d", buf->len); - return; + if (net_buf_tailroom(ct->reassembly_buf) < data_len) { + LOG_ERR("Insufficient space in reassembly buffer"); + return -ENOMEM; } - avrcp_ct_cb->get_cap_rsp(get_avrcp_ct(avrcp), tid, rsp); + /* Add fragment data to reassembly buffer */ + net_buf_add_mem(ct->reassembly_buf, data, data_len); + return 0; } -static void avrcp_vendor_dependent_rsp_handler(struct bt_avrcp *avrcp, uint8_t tid, - struct net_buf *buf) +static void cleanup_fragmentation_context(struct bt_avrcp_ct *ct) { - struct bt_avrcp_avc_pdu *pdu; - struct bt_avrcp_header *avrcp_hdr; - uint32_t company_id; - uint8_t pdu_id; - - if (buf->len < (sizeof(*avrcp_hdr) + BT_AVRCP_COMPANY_ID_SIZE + sizeof(pdu_id))) { - LOG_ERR("Invalid vendor frame length: %d", buf->len); + if (ct == NULL) { return; } - avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); - company_id = net_buf_pull_be24(buf); - if (company_id != BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG) { - LOG_ERR("Invalid company id: 0x%06x", company_id); - return; + if (ct->reassembly_buf != NULL) { + net_buf_unref(ct->reassembly_buf); + ct->reassembly_buf = NULL; } +} - pdu = (struct bt_avrcp_avc_pdu *)buf->data; +static struct net_buf *avrcp_prepare_vendor_pdu(void *avrcp, bt_avrcp_pkt_type_t pkt_type, + uint8_t avrcp_type, uint8_t pdu_id, + uint16_t param_len) +{ + struct net_buf *buf; + struct bt_avrcp_avc_vendor_pdu *pdu; - switch (pdu->pdu_id) { - case BT_AVRCP_PDU_ID_GET_CAPS: - process_get_cap_rsp(avrcp, tid, buf); - break; - default: - LOG_DBG("Unhandled response: 0x%02x", pdu->pdu_id); - break; + if (avrcp == NULL) { + return NULL; } + buf = avrcp_create_vendor_pdu(avrcp, avrcp_type); + if (buf == NULL) { + LOG_WRN("Insufficient buffer"); + return NULL; + } + + if (net_buf_tailroom(buf) < sizeof(*pdu)) { + LOG_WRN("Not enough tailroom: required"); + net_buf_unref(buf); + return NULL; + } + + pdu = net_buf_add(buf, sizeof(*pdu)); + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, pdu->company_id); + pdu->pdu_id = pdu_id; + BT_AVRCP_AVC_PDU_SET_PACKET_TYPE(pdu, pkt_type); + pdu->param_len = sys_cpu_to_be16(param_len); + + return buf; } -static void avrcp_unit_info_rsp_handler(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +static int send_single_vendor_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t rsp, uint8_t pdu_id, + struct net_buf *buf) { - struct bt_avrcp_header *avrcp_hdr; - struct bt_avrcp_unit_info_rsp rsp; + struct bt_avrcp_header *hdr; + struct bt_avrcp_avc_vendor_pdu *pdu; + uint16_t param_len; - avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + if ((tg == NULL) || (tg->avrcp == NULL)) { + return -EINVAL; + } + param_len = buf->len; - if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->unit_info_rsp != NULL)) { - if (buf->len != BT_AVRCP_UNIT_INFO_RSP_SIZE) { - LOG_ERR("Invalid unit info length: %d", buf->len); - return; - } - net_buf_pull_u8(buf); /* Always 0x07 */ - rsp.unit_type = FIELD_GET(GENMASK(7, 3), net_buf_pull_u8(buf)); - rsp.company_id = net_buf_pull_be24(buf); - avrcp_ct_cb->unit_info_rsp(get_avrcp_ct(avrcp), tid, &rsp); + if (net_buf_headroom(buf) < (sizeof(*pdu) + sizeof(*hdr))) { + LOG_WRN("Not enough headroom: for vendor dependent PDU"); + net_buf_unref(buf); + return -ENOMEM; } + + pdu = net_buf_push(buf, sizeof(*pdu)); + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, pdu->company_id); + pdu->pdu_id = pdu_id; + BT_AVRCP_AVC_PDU_SET_PACKET_TYPE(pdu, BT_AVRCP_PKT_TYPE_SINGLE); + pdu->param_len = sys_cpu_to_be16(param_len); + + hdr = net_buf_push(buf, sizeof(struct bt_avrcp_header)); + memset(hdr, 0, sizeof(struct bt_avrcp_header)); + BT_AVRCP_HDR_SET_CTYPE_OR_RSP(hdr, rsp); + BT_AVRCP_HDR_SET_SUBUNIT_ID(hdr, BT_AVRCP_SUBUNIT_ID_ZERO); + BT_AVRCP_HDR_SET_SUBUNIT_TYPE(hdr, BT_AVRCP_SUBUNIT_TYPE_PANEL); + hdr->opcode = BT_AVRCP_OPC_VENDOR_DEPENDENT; + + return avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); } -static void avrcp_subunit_info_rsp_handler(struct bt_avrcp *avrcp, uint8_t tid, - struct net_buf *buf) +static int send_fragmented_vendor_rsp(struct bt_avrcp_tg_vd_rsp_tx *tx, + bt_avrcp_pkt_type_t pkt_type, + uint8_t *data, uint16_t data_len) { - struct bt_avrcp_header *avrcp_hdr; - struct bt_avrcp_subunit_info_rsp rsp; - uint8_t tmp; + struct bt_avrcp_tg *tg = tx->tg; + struct net_buf *buf; - avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + if ((tg == NULL) || (tg->avrcp == NULL)) { + return -EINVAL; + } - if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->subunit_info_rsp != NULL)) { - if (buf->len < BT_AVRCP_SUBUNIT_INFO_RSP_SIZE) { - LOG_ERR("Invalid subunit info length: %d", buf->len); - return; - } - net_buf_pull_u8(buf); /* Always 0x07 */ - tmp = net_buf_pull_u8(buf); - rsp.subunit_type = FIELD_GET(GENMASK(7, 3), tmp); - rsp.max_subunit_id = FIELD_GET(GENMASK(2, 0), tmp); - if (buf->len < (rsp.max_subunit_id << 1)) { - LOG_ERR("Invalid subunit info response"); - return; + buf = avrcp_prepare_vendor_pdu(tg->avrcp, pkt_type, tx->rsp, tx->pdu_id, data_len); + if (buf == NULL) { + return -ENOBUFS; + } + + if (data_len > 0U) { + if (net_buf_tailroom(buf) < data_len) { + LOG_WRN("Not enough tailroom: %u < %u", net_buf_tailroom(buf), data_len); + net_buf_unref(buf); + return -ENOMEM; } - rsp.extended_subunit_type = buf->data; - rsp.extended_subunit_id = rsp.extended_subunit_type + rsp.max_subunit_id; - avrcp_ct_cb->subunit_info_rsp(get_avrcp_ct(avrcp), tid, &rsp); + net_buf_add_mem(buf, data, data_len); } + return avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tx->tid); } -static void avrcp_pass_through_rsp_handler(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +static int bt_avrcp_ct_continuing_rsp_cmd_send(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t op_pdu_id, uint8_t target_pdu_id) { - struct bt_avrcp_header *avrcp_hdr; - struct bt_avrcp_passthrough_rsp *rsp; - bt_avrcp_rsp_t result; - - avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + struct net_buf *buf; - if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->subunit_info_rsp != NULL)) { - if (buf->len < sizeof(*rsp)) { - LOG_ERR("Invalid passthrough length: %d", buf->len); - return; - } + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } - result = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); - rsp = (struct bt_avrcp_passthrough_rsp *)buf->data; + buf = avrcp_prepare_vendor_pdu(ct->avrcp, BT_AVRCP_PKT_TYPE_SINGLE, + BT_AVRCP_CTYPE_CONTROL, op_pdu_id, + sizeof(target_pdu_id)); + if (buf == NULL) { + return -ENOMEM; + } - avrcp_ct_cb->passthrough_rsp(get_avrcp_ct(avrcp), tid, result, rsp); + /* Add target PDU ID to payload */ + if (net_buf_tailroom(buf) < sizeof(target_pdu_id)) { + LOG_WRN("Not enough tailroom for pdu_id"); + net_buf_unref(buf); + return -ENOMEM; } + net_buf_add_u8(buf, target_pdu_id); + + return avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); } -static const struct avrcp_handler rsp_handlers[] = { - {BT_AVRCP_OPC_VENDOR_DEPENDENT, avrcp_vendor_dependent_rsp_handler}, - {BT_AVRCP_OPC_UNIT_INFO, avrcp_unit_info_rsp_handler}, - {BT_AVRCP_OPC_SUBUNIT_INFO, avrcp_subunit_info_rsp_handler}, - {BT_AVRCP_OPC_PASS_THROUGH, avrcp_pass_through_rsp_handler}, -}; +static int bt_avrcp_ct_send_req_continuing_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t pdu_id) +{ + return bt_avrcp_ct_continuing_rsp_cmd_send(ct, tid, BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP, + pdu_id); +} -static void avrcp_unit_info_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +static int bt_avrcp_ct_send_abort_continuing_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t pdu_id) { - struct bt_avrcp_header *avrcp_hdr; - bt_avrcp_subunit_type_t subunit_type; - bt_avrcp_subunit_id_t subunit_id; - bt_avrcp_ctype_t ctype; - int err; + tid++; + tid &= 0x0F; + return bt_avrcp_ct_continuing_rsp_cmd_send(ct, tid, BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP, + pdu_id); +} - if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->unit_info_req == NULL)) { - goto err_rsp; - } +static void bt_avrcp_tg_set_tx_state(struct bt_avrcp_tg *tg, avrcp_tg_rsp_state_t state, + uint8_t tid, struct net_buf *buf) +{ + sys_snode_t *node; + struct bt_avrcp_tg_vd_rsp_tx *tx; + struct net_buf *tx_buf; + uint8_t pdu_id; - if (buf->len < sizeof(*avrcp_hdr)) { - goto err_rsp; - } + avrcp_tg_lock(tg); - avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); - if (buf->len != BT_AVRCP_UNIT_INFO_CMD_SIZE) { - LOG_ERR("Invalid unit info length"); - goto err_rsp; + node = sys_slist_peek_head(&tg->vd_rsp_tx_pending); + if (!node) { + LOG_WRN("No pending tx"); + avrcp_tg_unlock(tg); + return; } - subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); - subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); - ctype = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); - if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_UNIT) || (ctype != BT_AVRCP_CTYPE_STATUS) || - (subunit_id != BT_AVRCP_SUBUNIT_ID_IGNORE) || - (avrcp_hdr->opcode != BT_AVRCP_OPC_UNIT_INFO)) { - LOG_ERR("Invalid unit info command"); - goto err_rsp; + tx_buf = CONTAINER_OF(node, struct net_buf, node); + + __ASSERT_NO_MSG(tx_buf->user_data_size >= sizeof(*tx)); + tx = net_buf_user_data(tx_buf); + + if (buf->len < sizeof(pdu_id)) { + LOG_WRN("Invalid AVRCP buffer: no PDU id"); + avrcp_tg_unlock(tg); + return; } + pdu_id = net_buf_pull_u8(buf); - return avrcp_tg_cb->unit_info_req(get_avrcp_tg(avrcp), tid); + tx->state = state; + tx->tid = tid; -err_rsp: - err = bt_avrcp_send_unit_info_err_rsp(avrcp, tid); - if (err) { - LOG_ERR("Failed to send unit info error response"); - if (bt_avrcp_disconnect(avrcp->acl_conn)) { - LOG_ERR("Failed to disconnect AVRCP connection"); + switch (state) { + case AVRCP_STATE_ABORT_CONTINUING: + tx->rsp = (pdu_id == tx->pdu_id) ? BT_AVRCP_RSP_ACCEPTED : BT_AVRCP_RSP_REJECTED; + tx->pdu_id = BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP; + break; + + case AVRCP_STATE_SENDING_CONTINUING: + if (pdu_id != tx->pdu_id) { + tx->rsp = BT_AVRCP_RSP_REJECTED; + tx->pdu_id = BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP; } + break; + + default: + break; } + + avrcp_tg_unlock(tg); } -static void avrcp_vendor_dependent_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, - struct net_buf *buf) +static void avrcp_tg_tx_remove(struct bt_avrcp_tg *tg, struct net_buf *buf) { -/* ToDo */ + avrcp_tg_lock(tg); + sys_slist_find_and_remove(&tg->vd_rsp_tx_pending, &buf->node); + avrcp_tg_unlock(tg); } -static void avrcp_subunit_info_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, - struct net_buf *buf) +static void bt_avrcp_tg_vendor_tx_work(struct k_work *work) { - struct bt_avrcp_header *avrcp_hdr; - bt_avrcp_subunit_type_t subunit_type; - bt_avrcp_subunit_id_t subunit_id; - bt_avrcp_ctype_t ctype; - uint8_t page; - uint8_t extension_code; + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_avrcp_tg *tg = CONTAINER_OF(dwork, struct bt_avrcp_tg, vd_rsp_tx_work); + sys_snode_t *node; + struct net_buf *buf; + struct bt_avrcp_tg_vd_rsp_tx *tx; + uint16_t max_payload_size; int err; - if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->subunit_info_req == NULL)) { - goto err_rsp; - } + avrcp_tg_lock(tg); - if (buf->len < sizeof(*avrcp_hdr)) { - goto err_rsp; + node = sys_slist_peek_head(&tg->vd_rsp_tx_pending); + if (node == NULL) { + LOG_WRN("No pending tx"); + avrcp_tg_unlock(tg); + return; } + avrcp_tg_unlock(tg); - avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); - if (buf->len != BT_AVRCP_SUBUNIT_INFO_CMD_SIZE) { - LOG_ERR("Invalid subunit info length"); - goto err_rsp; - } + buf = CONTAINER_OF(node, struct net_buf, node); - subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); - subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); - ctype = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + __ASSERT_NO_MSG(buf->user_data_size >= sizeof(*tx)); + tx = net_buf_user_data(buf); - /* First byte contains page and extension code */ - page = FIELD_GET(GENMASK(6, 4), buf->data[0]); - extension_code = FIELD_GET(GENMASK(2, 0), buf->data[0]); + /* Calculate maximum payload size per fragment */ + max_payload_size = BT_AVRCP_FRAGMENT_SIZE - sizeof(struct bt_avrcp_header) + - sizeof(struct bt_avrcp_avc_vendor_pdu); - if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_UNIT) || (ctype != BT_AVRCP_CTYPE_STATUS) || - (subunit_id != BT_AVRCP_SUBUNIT_ID_IGNORE) || (page != AVRCP_SUBUNIT_PAGE) || - (avrcp_hdr->opcode != BT_AVRCP_OPC_SUBUNIT_INFO) || - (extension_code != AVRCP_SUBUNIT_EXTENSION_CODE)) { - LOG_ERR("Invalid subunit info command"); - goto err_rsp; + /* Check if fragmentation is needed */ + if ((tx->sent_len == 0) && (buf->len <= max_payload_size)) { + /* Single packet response */ + err = send_single_vendor_rsp(tg, tx->tid, tx->rsp, tx->pdu_id, buf); + if (err < 0) { + LOG_ERR("Failed to send single len: %u", buf->len); + goto done; + } + avrcp_tg_tx_remove(tg, buf); + k_work_reschedule(&tg->vd_rsp_tx_work, K_NO_WAIT); + return; } + /* Multi-packet fragmented response */ + bt_avrcp_pkt_type_t pkt_type = BT_AVRCP_PKT_TYPE_SINGLE; + uint16_t chunk_size = MIN(max_payload_size, buf->len); + + if ((tx->state == AVRCP_STATE_ABORT_CONTINUING) || + (tx->rsp == BT_AVRCP_RSP_REJECTED)) { + LOG_WRN("Abort to continuing send OR REJECT"); + struct net_buf *rsp_buf; + uint8_t error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + + rsp_buf = bt_avrcp_create_vendor_pdu(NULL); + if (rsp_buf == NULL) { + LOG_ERR("Failed to allocate response buffer"); + goto done; + } + if (tx->rsp == BT_AVRCP_RSP_REJECTED) { + if (net_buf_tailroom(rsp_buf) < sizeof(uint8_t)) { + LOG_ERR("Insufficient space in response buffer"); + goto done; + } + net_buf_add_u8(rsp_buf, error_code); + } - return avrcp_tg_cb->subunit_info_req(get_avrcp_tg(avrcp), tid); + err = send_single_vendor_rsp(tg, tx->tid, tx->rsp, tx->pdu_id, rsp_buf); + if (err < 0) { + LOG_ERR("Failed to send rsp_buf"); + net_buf_unref(rsp_buf); + } + goto done; + } -err_rsp: - err = bt_avrcp_send_subunit_info(avrcp, tid, BT_AVRCP_RSP_REJECTED); + /* Determine packet type */ + if (tx->sent_len == 0U) { + pkt_type = BT_AVRCP_PKT_TYPE_START; + } else if (tx->state == AVRCP_STATE_SENDING_CONTINUING) { + /* Last fragment */ + if (chunk_size >= buf->len) { + pkt_type = BT_AVRCP_PKT_TYPE_END; + } else { + pkt_type = BT_AVRCP_PKT_TYPE_CONTINUE; + } + } else { + /* Unexpected state */ + __ASSERT(false, "Unexpected AVRCP TX state: %d", tx->state); + } + + /* Send fragment */ + err = send_fragmented_vendor_rsp(tx, pkt_type, buf->data, chunk_size); if (err < 0) { - LOG_ERR("Failed to send subunit info error response"); - if (bt_avrcp_disconnect(avrcp->acl_conn)) { - LOG_ERR("Failed to disconnect AVRCP connection"); + if (err == -ENOBUFS) { + LOG_WRN("Retry send fragment %p", buf); + k_work_reschedule(&tg->vd_rsp_tx_work, K_MSEC(AVRCP_TX_RETRY_DELAY_MS)); + return; } + LOG_ERR("Failed to send fragment offset %u len: %u", tx->sent_len, chunk_size); + goto done; + } + + net_buf_pull_mem(buf, chunk_size); + + tx->sent_len += chunk_size; + if (buf->len == 0) { + avrcp_tg_tx_remove(tg, buf); + net_buf_unref(buf); } + return; + +done: + avrcp_tg_tx_remove(tg, buf); + if (buf != NULL) { + net_buf_unref(buf); + } + /* restart the tx work */ + k_work_reschedule(&tg->vd_rsp_tx_work, K_NO_WAIT); } -static void avrcp_pass_through_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, - struct net_buf *buf) +static int bt_avrcp_tg_send_vendor_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t pdu_id, + uint8_t result, struct net_buf *buf) { - struct bt_avrcp_header *avrcp_hdr; - struct net_buf *rsp_buf; + struct bt_avrcp_tg_vd_rsp_tx *tx; + + if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; + } + + __ASSERT_NO_MSG(buf->user_data_size >= sizeof(*tx)); + tx = net_buf_user_data(buf); + + tx->tg = tg; + tx->tid = tid; + tx->rsp = result; + tx->pdu_id = pdu_id; + tx->sent_len = 0U; + tx->state = AVRCP_STATE_IDLE; + + LOG_DBG("Sending vendor dependent response: tid=%u, total_len=%u", tid, buf->len); + avrcp_tg_lock(tg); + sys_slist_append(&tg->vd_rsp_tx_pending, &buf->node); + avrcp_tg_unlock(tg); + + k_work_reschedule(&tg->vd_rsp_tx_work, K_NO_WAIT); + + return 0; +} + +static void process_get_caps_rsp(struct bt_avrcp *avrcp, uint8_t tid, uint8_t rsp_code, + struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; int err; - if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->passthrough_req == NULL)) { - goto err_rsp; + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_caps_rsp == NULL)) { + return; } - if (buf->len < (sizeof(*avrcp_hdr) + sizeof(struct bt_avrcp_passthrough_cmd))) { - LOG_ERR("Invalid passthrough command length: %d", buf->len); - goto err_rsp; + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (rsp_code == BT_AVRCP_RSP_REJECTED)) { + avrcp_ct_cb->get_caps_rsp(get_avrcp_ct(avrcp), tid, status, NULL); + return; } - avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); - if (BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr) != BT_AVRCP_SUBUNIT_TYPE_PANEL || - BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr) != BT_AVRCP_SUBUNIT_ID_ZERO || - BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr) != BT_AVRCP_CTYPE_CONTROL) { - LOG_ERR("Invalid passthrough command "); - goto err_rsp; + avrcp_ct_cb->get_caps_rsp(get_avrcp_ct(avrcp), tid, status, buf); +} + +static void process_register_notification_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + struct bt_avrcp_event_data *event_data; + uint8_t event_id; + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + uint16_t expected_len; + struct bt_avrcp_ct *ct = get_avrcp_ct(avrcp); + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->notification_rsp == NULL)) { + return; } - return avrcp_tg_cb->passthrough_req(get_avrcp_tg(avrcp), tid, buf); + if (rsp_code == BT_AVRCP_RSP_REJECTED) { + if (buf->len < sizeof(status)) { + LOG_ERR("Invalid notification response length"); + return; + } -err_rsp: - rsp_buf = bt_avrcp_create_pdu(NULL); - if (rsp_buf == NULL) { - LOG_ERR("Failed to allocate response buffer"); + status = net_buf_pull_u8(buf); + avrcp_ct_cb->notification_rsp(get_avrcp_ct(avrcp), tid, status, 0, NULL); return; } - err = bt_avrcp_tg_send_passthrough_rsp(get_avrcp_tg(avrcp), tid, BT_AVRCP_RSP_REJECTED, - rsp_buf); - if (err < 0) { - LOG_ERR("Failed to send passthrough error response"); - net_buf_unref(rsp_buf); - if (bt_avrcp_disconnect(avrcp->acl_conn)) { - LOG_ERR("Failed to disconnect AVRCP connection"); + if (rsp_code == BT_AVRCP_RSP_NOT_IMPLEMENTED) { + avrcp_ct_cb->notification_rsp(get_avrcp_ct(avrcp), tid, + BT_AVRCP_STATUS_NOT_IMPLEMENTED, 0, NULL); + return; + } + + /* The first byte is the event_id */ + if (buf->len < sizeof(event_id)) { + LOG_ERR("Invalid notification response length"); + return; + } + + event_id = net_buf_pull_u8(buf); + if (event_id > BT_AVRCP_EVT_VOLUME_CHANGED) { + LOG_ERR("Invalid event_id"); + return; + } + + event_data = (struct bt_avrcp_event_data *)buf->data; + + /* Parse event-specific data */ + switch (event_id) { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + if (buf->len < sizeof(event_data->play_status)) { + LOG_ERR("Invalid PLAYBACK_STATUS_CHANGED response length"); + return; + } + + if (event_data->play_status > BT_AVRCP_PLAYBACK_STATUS_REV_SEEK && + event_data->play_status != BT_AVRCP_PLAYBACK_STATUS_ERROR) { + LOG_ERR("Invalid playback status: %d", event_data->play_status); + return; + } + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + if (buf->len < sizeof(event_data->identifier)) { + LOG_ERR("Invalid TRACK_CHANGED response length"); + return; + } + uint64_t identifier = sys_get_be64((const uint8_t *)event_data->identifier); + + memcpy(event_data->identifier, &identifier, sizeof(uint64_t)); + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + if (buf->len < sizeof(event_data->playback_pos)) { + LOG_ERR("Invalid PLAYBACK_POS_CHANGED response length"); + return; + } + event_data->playback_pos = sys_be32_to_cpu(event_data->playback_pos); + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + if (buf->len < sizeof(event_data->battery_status)) { + LOG_ERR("Invalid BATT_STATUS_CHANGED response length"); + return; + } + break; + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + if (buf->len < sizeof(event_data->system_status)) { + LOG_ERR("Invalid SYSTEM_STATUS_CHANGED response length"); + return; + } + if (event_data->system_status > BT_AVRCP_SYSTEM_STATUS_UNPLUGGED) { + LOG_ERR("Invalid system status: %d", event_data->system_status); + return; + } + break; + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + expected_len = sizeof(event_data->setting_changed.num_of_attr) + + event_data->setting_changed.num_of_attr * + sizeof(struct bt_avrcp_app_setting_attr_val); + if (buf->len < expected_len) { + LOG_ERR("Invalid PLAYER_APP_SETTING_CHANGED attribute length"); + return; + } + break; + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + if (buf->len < sizeof(event_data->addressed_player_changed)) { + LOG_ERR("Invalid ADDRESSED_PLAYER_CHANGED response length"); + return; } + event_data->addressed_player_changed.player_id = + sys_be16_to_cpu(event_data->addressed_player_changed.player_id); + event_data->addressed_player_changed.uid_counter = + sys_be16_to_cpu(event_data->addressed_player_changed.uid_counter); + break; + case BT_AVRCP_EVT_UIDS_CHANGED: + if (buf->len < sizeof(event_data->uid_counter)) { + LOG_ERR("Invalid UIDS_CHANGED response length"); + return; + } + event_data->uid_counter = sys_be16_to_cpu(event_data->uid_counter); + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + if (buf->len < sizeof(event_data->absolute_volume)) { + LOG_ERR("Invalid VOLUME_CHANGED response length"); + return; + } + + if (event_data->absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) { + LOG_ERR("Invalid absolute volume: %d", event_data->absolute_volume); + return; + } + break; + case BT_AVRCP_EVT_TRACK_REACHED_END: + case BT_AVRCP_EVT_TRACK_REACHED_START: + case BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED: + case BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED: + if (buf->len != 0) { + LOG_WRN("Zero-parameter event (0x%02X) with %u trailing bytes", + event_id, buf->len); + } + event_data = NULL; + break; + default: + LOG_WRN("Unknown notification event_id: 0x%02X", event_id); + return; + } + + if (tid != ct->ct_notify[event_id].tid) { + LOG_WRN("Mismatched transaction ID: received %u, expected %u", tid, + ct->ct_notify[event_id].tid); + } + + if (rsp_code != BT_AVRCP_RSP_INTERIM && rsp_code != BT_AVRCP_RSP_CHANGED) { + LOG_WRN("Unexpected rsp_code: 0x%02X", rsp_code); + } + + if (rsp_code == BT_AVRCP_RSP_INTERIM) { + /* Mark as interim_received flag on interim response */ + ct->ct_notify[event_id].interim_received = 1; + avrcp_ct_cb->notification_rsp(get_avrcp_ct(avrcp), tid, BT_AVRCP_STATUS_SUCCESS, + event_id, (struct bt_avrcp_event_data *)event_data); + return; + + } + + if ((ct->ct_notify[event_id].cb != NULL) && + (ct->ct_notify[event_id].interim_received == 1) && + (rsp_code == BT_AVRCP_RSP_CHANGED)) { + ct->ct_notify[event_id].cb(event_id, (struct bt_avrcp_event_data *)event_data); + ct->ct_notify[event_id].interim_received = 0; + } +} + + +static void process_set_absolute_volume_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + uint8_t absolute_volume; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->set_absolute_volume_rsp == NULL)) { + return; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (rsp_code == BT_AVRCP_RSP_REJECTED)) { + avrcp_ct_cb->set_absolute_volume_rsp(get_avrcp_ct(avrcp), tid, status, 0); + return; + } + + if (buf->len < sizeof(absolute_volume)) { + LOG_ERR("Invalid absolute volume response length"); + return; + } + absolute_volume = net_buf_pull_u8(buf); + if (absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) { + LOG_ERR("Invalid absolute volume: %d", absolute_volume); + return; + } + + avrcp_ct_cb->set_absolute_volume_rsp(get_avrcp_ct(avrcp), tid, status, absolute_volume); +} + +static void process_list_player_app_setting_attrs_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->list_player_app_setting_attrs_rsp == NULL)) { + return; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (rsp_code == BT_AVRCP_RSP_REJECTED)) { + avrcp_ct_cb->list_player_app_setting_attrs_rsp(get_avrcp_ct(avrcp), tid, status, + NULL); + return; + } + + avrcp_ct_cb->list_player_app_setting_attrs_rsp(get_avrcp_ct(avrcp), tid, status, buf); +} + +static void process_list_player_app_setting_vals_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->list_player_app_setting_vals_rsp == NULL)) { + return; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (rsp_code == BT_AVRCP_RSP_REJECTED)) { + avrcp_ct_cb->list_player_app_setting_vals_rsp(get_avrcp_ct(avrcp), tid, status, + NULL); + return; + } + + avrcp_ct_cb->list_player_app_setting_vals_rsp(get_avrcp_ct(avrcp), tid, status, buf); +} + +static void process_get_curr_player_app_setting_val_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_curr_player_app_setting_val_rsp == NULL)) { + return; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (rsp_code == BT_AVRCP_RSP_REJECTED)) { + avrcp_ct_cb->get_curr_player_app_setting_val_rsp(get_avrcp_ct(avrcp), tid, status, + NULL); + return; + } + + avrcp_ct_cb->get_curr_player_app_setting_val_rsp(get_avrcp_ct(avrcp), tid, status, buf); +} + +static void process_set_player_app_setting_val_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->set_player_app_setting_val_rsp == NULL)) { + return; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + + avrcp_ct_cb->set_player_app_setting_val_rsp(get_avrcp_ct(avrcp), tid, status); +} + +static void process_get_player_app_setting_attr_text_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_player_app_setting_attr_text_rsp == NULL)) { + return; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (rsp_code == BT_AVRCP_RSP_REJECTED)) { + avrcp_ct_cb->get_player_app_setting_attr_text_rsp(get_avrcp_ct(avrcp), tid, status, + NULL); + return; + } + + avrcp_ct_cb->get_player_app_setting_attr_text_rsp(get_avrcp_ct(avrcp), tid, status, buf); +} + +static void process_get_player_app_setting_val_text_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_player_app_setting_val_text_rsp == NULL)) { + return; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (rsp_code == BT_AVRCP_RSP_REJECTED)) { + avrcp_ct_cb->get_player_app_setting_val_text_rsp(get_avrcp_ct(avrcp), tid, status, + NULL); + return; + } + + avrcp_ct_cb->get_player_app_setting_val_text_rsp(get_avrcp_ct(avrcp), tid, status, buf); +} + +static void process_inform_displayable_char_set_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->inform_displayable_char_set_rsp == NULL)) { + return; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + + avrcp_ct_cb->inform_displayable_char_set_rsp(get_avrcp_ct(avrcp), tid, status); +} + +static void process_inform_batt_status_of_ct_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->inform_batt_status_of_ct_rsp == NULL)) { + return; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + + avrcp_ct_cb->inform_batt_status_of_ct_rsp(get_avrcp_ct(avrcp), tid, status); +} + +static void process_get_element_attrs_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_element_attrs_rsp == NULL)) { + return; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (rsp_code == BT_AVRCP_RSP_REJECTED)) { + avrcp_ct_cb->get_element_attrs_rsp(get_avrcp_ct(avrcp), tid, status, NULL); + return; + } + + avrcp_ct_cb->get_element_attrs_rsp(get_avrcp_ct(avrcp), tid, status, buf); +} + +static void process_get_play_status_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + int err; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_play_status_rsp == NULL)) { + return; + } + + err = bt_avrcp_rsp_to_status(rsp_code, buf, &status); + if ((err < 0) || (rsp_code == BT_AVRCP_RSP_REJECTED)) { + avrcp_ct_cb->get_play_status_rsp(get_avrcp_ct(avrcp), tid, status, NULL); + return; + } + + avrcp_ct_cb->get_play_status_rsp(get_avrcp_ct(avrcp), tid, status, buf); +} + +static void process_set_addressed_player_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->set_addressed_player_rsp == NULL)) { + return; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + + avrcp_ct_cb->set_addressed_player_rsp(get_avrcp_ct(avrcp), tid, status); +} + +static void process_play_item_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->play_item_rsp == NULL)) { + return; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + avrcp_ct_cb->play_item_rsp(get_avrcp_ct(avrcp), tid, status); +} + +static void process_add_to_now_playing_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t status = BT_AVRCP_STATUS_INTERNAL_ERROR; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->add_to_now_playing_rsp == NULL)) { + return; + } + + bt_avrcp_rsp_to_status(rsp_code, buf, &status); + avrcp_ct_cb->add_to_now_playing_rsp(get_avrcp_ct(avrcp), tid, status); +} + +static const struct avrcp_pdu_vendor_handler rsp_vendor_handlers[] = { + { BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP, sizeof(uint8_t), BT_AVRCP_CTYPE_CONTROL, + NULL }, + { BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP, sizeof(uint8_t), BT_AVRCP_CTYPE_CONTROL, + NULL }, + { BT_AVRCP_PDU_ID_GET_CAPS, sizeof(uint8_t), BT_AVRCP_CTYPE_STATUS, process_get_caps_rsp }, + { BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, sizeof(uint8_t), BT_AVRCP_CTYPE_NOTIFY, + process_register_notification_rsp }, + { BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, sizeof(uint8_t), BT_AVRCP_CTYPE_CONTROL, + process_set_absolute_volume_rsp }, + { BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, sizeof(uint8_t), + BT_AVRCP_CTYPE_STATUS, process_list_player_app_setting_attrs_rsp }, + { BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, sizeof(uint8_t), + BT_AVRCP_CTYPE_STATUS, process_list_player_app_setting_vals_rsp }, + { BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL, sizeof(uint8_t), BT_AVRCP_CTYPE_STATUS, + process_get_curr_player_app_setting_val_rsp }, + { BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, 0, BT_AVRCP_CTYPE_CONTROL, + process_set_player_app_setting_val_rsp }, + { BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT, sizeof(uint8_t), BT_AVRCP_CTYPE_STATUS, + process_get_player_app_setting_attr_text_rsp }, + { BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT, sizeof(uint8_t), BT_AVRCP_CTYPE_STATUS, + process_get_player_app_setting_val_text_rsp }, + { BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, 0, BT_AVRCP_CTYPE_CONTROL, + process_inform_displayable_char_set_rsp }, + { BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, 0, BT_AVRCP_CTYPE_CONTROL, + process_inform_batt_status_of_ct_rsp }, + { BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, sizeof(uint8_t), BT_AVRCP_CTYPE_STATUS, + process_get_element_attrs_rsp }, + { BT_AVRCP_PDU_ID_GET_PLAY_STATUS, sizeof(uint8_t), BT_AVRCP_CTYPE_STATUS, + process_get_play_status_rsp }, + { BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, sizeof(uint8_t), BT_AVRCP_CTYPE_CONTROL, + process_set_addressed_player_rsp }, + { BT_AVRCP_PDU_ID_PLAY_ITEM, sizeof(uint8_t), BT_AVRCP_CTYPE_CONTROL, + process_play_item_rsp }, + { BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, sizeof(uint8_t), BT_AVRCP_CTYPE_CONTROL, + process_add_to_now_playing_rsp }, +}; + +static inline bt_avrcp_ctype_t get_cmd_type_by_pdu(uint8_t pdu_id) +{ + ARRAY_FOR_EACH(rsp_vendor_handlers, i) { + if (rsp_vendor_handlers[i].pdu_id == pdu_id) { + return rsp_vendor_handlers[i].cmd_type; + } + } + __ASSERT(false, "Unknown PDU ID: 0x%02X", pdu_id); + return (bt_avrcp_ctype_t)-1; +} + +static inline uint8_t bt_avrcp_status_to_rsp(uint8_t pdu_id, uint8_t status) +{ + switch (status) { + case BT_AVRCP_STATUS_SUCCESS: + if (get_cmd_type_by_pdu(pdu_id) == BT_AVRCP_CTYPE_CONTROL) { + return BT_AVRCP_RSP_ACCEPTED; + } else if (get_cmd_type_by_pdu(pdu_id) == BT_AVRCP_CTYPE_STATUS) { + return BT_AVRCP_RSP_STABLE; + } + __ASSERT(false, "Unknown PDU ID: 0x%02X", pdu_id); + return BT_AVRCP_RSP_REJECTED; + case BT_AVRCP_STATUS_IN_TRANSITION: return BT_AVRCP_RSP_IN_TRANSITION; + case BT_AVRCP_STATUS_NOT_IMPLEMENTED: return BT_AVRCP_RSP_NOT_IMPLEMENTED; + + default: + return BT_AVRCP_RSP_REJECTED; + } +} + +static int bt_avrcp_tg_send_vendor_err_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t pdu_id, uint8_t status) +{ + struct net_buf *buf; + uint8_t rsp_code = bt_avrcp_status_to_rsp(pdu_id, status); + int err; + + if ((tg == NULL) || (tg->avrcp == NULL)) { + return -EINVAL; + } + + buf = avrcp_prepare_vendor_pdu(tg->avrcp, BT_AVRCP_PKT_TYPE_SINGLE, rsp_code, + pdu_id, sizeof(status)); + if (buf == NULL) { + return -ENOMEM; + } + + if (rsp_code == BT_AVRCP_RSP_REJECTED) { + if (net_buf_tailroom(buf) < sizeof(status)) { + LOG_WRN("Not enough tailroom for err_code"); + net_buf_unref(buf); + return -ENOMEM; + } + net_buf_add_u8(buf, status); + } + + err = avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); + if (err < 0) { + net_buf_unref(buf); + if (bt_avrcp_disconnect(tg->avrcp->acl_conn)) { + LOG_ERR("Failed to disconnect AVRCP connection"); + } + } + return err; +} + +static int handle_vendor_pdu(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf, + uint8_t ctype, uint8_t pdu_id, + const struct avrcp_pdu_vendor_handler *handlers, size_t num_handlers) +{ + for (size_t i = 0; i < num_handlers; i++) { + const struct avrcp_pdu_vendor_handler *handler = &handlers[i]; + + if (handler->pdu_id != pdu_id) { + continue; + } + + if (buf->len < handler->min_len) { + LOG_ERR("Too small (%u bytes) pdu_id 0x%02x", buf->len, pdu_id); + return BT_AVRCP_STATUS_PARAMETER_CONTENT_ERROR; + } + + handler->func(avrcp, tid, ctype, buf); + return BT_AVRCP_STATUS_OPERATION_COMPLETED; + } + + return BT_AVRCP_STATUS_INVALID_COMMAND; +} + +static void process_common_vendor_rsp(struct bt_avrcp *avrcp, uint8_t pdu_id, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + if (buf == NULL) { + LOG_ERR("Invalid parameters"); + return; + } + + handle_vendor_pdu(avrcp, tid, buf, rsp_code, pdu_id, rsp_vendor_handlers, + ARRAY_SIZE(rsp_vendor_handlers)); +} + +static void avrcp_vendor_dependent_rsp_handler(struct bt_avrcp *avrcp, uint8_t tid, + struct net_buf *buf) +{ + struct bt_avrcp_avc_vendor_pdu *pdu; + struct bt_avrcp_header *avrcp_hdr; + bt_avrcp_subunit_type_t subunit_type; + bt_avrcp_subunit_id_t subunit_id; + bt_avrcp_rsp_t rsp; + uint16_t len; + int err; + + if (buf->len < (sizeof(*avrcp_hdr) + sizeof(*pdu))) { + LOG_ERR("Invalid vendor frame length: %d", buf->len); + return; + } + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + rsp = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_PANEL) || + (subunit_id != BT_AVRCP_SUBUNIT_ID_ZERO)) { + LOG_ERR("Invalid vendor dependent command"); + return; + } + + pdu = net_buf_pull_mem(buf, sizeof(*pdu)); + if (sys_get_be24(pdu->company_id) != BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG) { + LOG_ERR("Invalid company id: 0x%06x", sys_get_be24(pdu->company_id)); + return; + } + + len = sys_be16_to_cpu(pdu->param_len); + if (buf->len != len) { + LOG_ERR("Invalid length: %d, buf length = %d", len, buf->len); + return; + } + + switch (pdu->pkt_type) { + case BT_AVRCP_PKT_TYPE_SINGLE: + /* Single packet should not have incomplete fragment */ + if (NULL != get_avrcp_ct(avrcp)->reassembly_buf) { + LOG_ERR("Single packet should not have incomplete fragment"); + goto failure; + } + process_common_vendor_rsp(avrcp, pdu->pdu_id, tid, rsp, buf); + break; + + case BT_AVRCP_PKT_TYPE_START: + err = init_fragmentation_context(get_avrcp_ct(avrcp), tid, rsp); + if (err < 0) { + LOG_ERR("init fragmentation context: %d", err); + goto failure; + } + /* Add first fragment data */ + err = add_fragment_data(get_avrcp_ct(avrcp), buf->data, buf->len); + if (err < 0) { + LOG_ERR("Failed to add first fragment: %d", err); + goto failure; + } + + LOG_DBG("First fragment added: %u", buf->len); + bt_avrcp_ct_send_req_continuing_rsp(get_avrcp_ct(avrcp), tid, pdu->pdu_id); + break; + + case BT_AVRCP_PKT_TYPE_CONTINUE: + /* Continuation fragment */ + if ((NULL == get_avrcp_ct(avrcp)->reassembly_buf) || + (get_fragmentation_context(get_avrcp_ct(avrcp))->tid != tid) || + (get_fragmentation_context(get_avrcp_ct(avrcp))->rsp != rsp)) { + LOG_ERR("Unexpected continue packet"); + goto failure; + } + + /* Add fragment data */ + err = add_fragment_data(get_avrcp_ct(avrcp), buf->data, buf->len); + if (err < 0) { + LOG_ERR("Failed to add continue fragment: %d", err); + goto failure; + } + + LOG_DBG("Continue frag added: %u ", buf->len); + bt_avrcp_ct_send_req_continuing_rsp(get_avrcp_ct(avrcp), tid, pdu->pdu_id); + break; + + case BT_AVRCP_PKT_TYPE_END: + /* Final fragment */ + if ((NULL == get_avrcp_ct(avrcp)->reassembly_buf) || + (get_fragmentation_context(get_avrcp_ct(avrcp))->tid != tid) || + (get_fragmentation_context(get_avrcp_ct(avrcp))->rsp != rsp)) { + LOG_ERR("Unexpected END packet"); + goto failure; + } + + err = add_fragment_data(get_avrcp_ct(avrcp), buf->data, buf->len); + if (err < 0) { + LOG_ERR("Failed to add end fragment: %d", err); + goto failure; + } + + LOG_DBG("Continue frag added: %u ", buf->len); + + /* Parse complete reassembled response */ + process_common_vendor_rsp(avrcp, pdu->pdu_id, tid, rsp, + get_avrcp_ct(avrcp)->reassembly_buf); + + /* Clean up fragmentation context */ + cleanup_fragmentation_context(get_avrcp_ct(avrcp)); + break; + + default: + LOG_DBG("Unhandled response: 0x%02x", pdu->pdu_id); + break; + } + return; + +failure: + LOG_ERR("Failed to handle vendor dependent response"); + cleanup_fragmentation_context(get_avrcp_ct(avrcp)); + if (bt_avrcp_ct_send_abort_continuing_rsp(get_avrcp_ct(avrcp), tid, pdu->pdu_id) < 0) { + LOG_ERR("Failed to send abort continuing response"); + if (bt_avrcp_disconnect(avrcp->acl_conn)) { + LOG_ERR("Failed to disconnect AVRCP connection"); + } + } +} + +static void avrcp_unit_info_rsp_handler(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + struct bt_avrcp_unit_info_rsp rsp; + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->unit_info_rsp != NULL)) { + if (buf->len != BT_AVRCP_UNIT_INFO_RSP_SIZE) { + LOG_ERR("Invalid unit info length: %d", buf->len); + return; + } + net_buf_pull_u8(buf); /* Always 0x07 */ + rsp.unit_type = FIELD_GET(GENMASK(7, 3), net_buf_pull_u8(buf)); + rsp.company_id = net_buf_pull_be24(buf); + avrcp_ct_cb->unit_info_rsp(get_avrcp_ct(avrcp), tid, &rsp); + } +} + +static void avrcp_subunit_info_rsp_handler(struct bt_avrcp *avrcp, uint8_t tid, + struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + struct bt_avrcp_subunit_info_rsp rsp; + uint8_t tmp; + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->subunit_info_rsp != NULL)) { + if (buf->len < BT_AVRCP_SUBUNIT_INFO_RSP_SIZE) { + LOG_ERR("Invalid subunit info length: %d", buf->len); + return; + } + net_buf_pull_u8(buf); /* Always 0x07 */ + tmp = net_buf_pull_u8(buf); + rsp.subunit_type = FIELD_GET(GENMASK(7, 3), tmp); + rsp.max_subunit_id = FIELD_GET(GENMASK(2, 0), tmp); + if (buf->len < (rsp.max_subunit_id << 1)) { + LOG_ERR("Invalid subunit info response"); + return; + } + rsp.extended_subunit_type = buf->data; + rsp.extended_subunit_id = rsp.extended_subunit_type + rsp.max_subunit_id; + avrcp_ct_cb->subunit_info_rsp(get_avrcp_ct(avrcp), tid, &rsp); + } +} + +static void avrcp_pass_through_rsp_handler(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + struct bt_avrcp_passthrough_rsp *rsp; + bt_avrcp_rsp_t result; + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->subunit_info_rsp != NULL)) { + if (buf->len < sizeof(*rsp)) { + LOG_ERR("Invalid passthrough length: %d", buf->len); + return; + } + + result = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + rsp = (struct bt_avrcp_passthrough_rsp *)buf->data; + + avrcp_ct_cb->passthrough_rsp(get_avrcp_ct(avrcp), tid, result, rsp); + } +} + +static const struct avrcp_handler rsp_handlers[] = { + {BT_AVRCP_OPC_VENDOR_DEPENDENT, avrcp_vendor_dependent_rsp_handler}, + {BT_AVRCP_OPC_UNIT_INFO, avrcp_unit_info_rsp_handler}, + {BT_AVRCP_OPC_SUBUNIT_INFO, avrcp_subunit_info_rsp_handler}, + {BT_AVRCP_OPC_PASS_THROUGH, avrcp_pass_through_rsp_handler}, +}; + +static void avrcp_unit_info_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + bt_avrcp_subunit_type_t subunit_type; + bt_avrcp_subunit_id_t subunit_id; + bt_avrcp_ctype_t ctype; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->unit_info_req == NULL)) { + goto err_rsp; + } + + if (buf->len < sizeof(*avrcp_hdr)) { + goto err_rsp; + } + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + if (buf->len != BT_AVRCP_UNIT_INFO_CMD_SIZE) { + LOG_ERR("Invalid unit info length"); + goto err_rsp; + } + + subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + ctype = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_UNIT) || (ctype != BT_AVRCP_CTYPE_STATUS) || + (subunit_id != BT_AVRCP_SUBUNIT_ID_IGNORE) || + (avrcp_hdr->opcode != BT_AVRCP_OPC_UNIT_INFO)) { + LOG_ERR("Invalid unit info command"); + goto err_rsp; + } + + return avrcp_tg_cb->unit_info_req(get_avrcp_tg(avrcp), tid); + +err_rsp: + err = bt_avrcp_send_unit_info_err_rsp(avrcp, tid); + if (err) { + LOG_ERR("Failed to send unit info error response"); + if (bt_avrcp_disconnect(avrcp->acl_conn)) { + LOG_ERR("Failed to disconnect AVRCP connection"); + } + } +} + +static void process_get_caps_cmd(struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + uint8_t cap_id; + uint8_t error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_caps_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + cap_id = net_buf_pull_u8(buf); + + /* Validate capability ID */ + if ((cap_id != BT_AVRCP_CAP_COMPANY_ID) && + (cap_id != BT_AVRCP_CAP_EVENTS_SUPPORTED)) { + LOG_ERR("Invalid capability ID: 0x%02x", cap_id); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + return avrcp_tg_cb->get_caps_req(get_avrcp_tg(avrcp), tid, cap_id); + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_GET_CAPS, error_code); + if (err < 0) { + LOG_ERR("Failed to send get_caps error response"); + } +} + +static void avrcp_register_notification_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + struct bt_avrcp_register_notification_cmd *cmd; + uint8_t error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->register_notification_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + if (cmd->event_id > BT_AVRCP_EVT_VOLUME_CHANGED) { + LOG_ERR("Invalid event_id"); + return; + } + + avrcp_tg_cb->register_notification_req(get_avrcp_tg(avrcp), tid, cmd->event_id, + cmd->interval); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, error_code); + if (err < 0) { + LOG_ERR("Failed to send register notification error response"); + } +} + +static void process_set_absolute_volume_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + uint8_t absolute_volume; + uint8_t error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->set_absolute_volume_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + absolute_volume = net_buf_pull_u8(buf); + if (absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) { + LOG_ERR("Invalid absolute volume: %d", absolute_volume); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + avrcp_tg_cb->set_absolute_volume_req(get_avrcp_tg(avrcp), tid, absolute_volume); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, error_code); + if (err < 0) { + LOG_ERR("Failed to send Set Absolute Volume error response"); + } +} + +static void process_list_player_app_setting_attrs_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->list_player_app_setting_attrs_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->list_player_app_setting_attrs_req(get_avrcp_tg(avrcp), tid); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, + error_code); + if (err < 0) { + LOG_ERR("Failed to send LIST_PLAYER_APP_SETTING_ATTRS error response"); + } +} + +static void process_list_player_app_setting_vals_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + uint8_t attr_id; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->list_player_app_setting_vals_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + attr_id = net_buf_pull_u8(buf); + if (attr_id > BT_AVRCP_PLAYER_ATTR_SCAN) { + LOG_ERR("Invalid attr_id: %d", attr_id); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + avrcp_tg_cb->list_player_app_setting_vals_req(get_avrcp_tg(avrcp), tid, attr_id); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, + error_code); + if (err < 0) { + LOG_ERR("Failed to send LIST_PLAYER_APP_SETTING_VALS error response"); + } +} + +static void process_get_curr_player_app_setting_val_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_curr_player_app_setting_val_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->get_curr_player_app_setting_val_req(get_avrcp_tg(avrcp), tid, buf); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL, + error_code); + if (err < 0) { + LOG_ERR("Failed to send GET_CURR_PLAYER_APP_SETTING_VAL error response"); + } +} + +static void process_set_player_app_setting_val_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->set_player_app_setting_val_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->set_player_app_setting_val_req(get_avrcp_tg(avrcp), tid, buf); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, + error_code); + if (err < 0) { + LOG_ERR("Failed to send SET_PLAYER_APP_SETTING_VAL error response"); + } +} + +static void process_get_player_app_setting_attr_text_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_player_app_setting_attr_text_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->get_player_app_setting_attr_text_req(get_avrcp_tg(avrcp), tid, buf); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT, + error_code); + if (err < 0) { + LOG_ERR("Failed to send GET_PLAYER_APP_SETTING_ATTR_TEXT error response"); + } +} + +static void process_get_player_app_setting_val_text_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_player_app_setting_val_text_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->get_player_app_setting_val_text_req(get_avrcp_tg(avrcp), tid, buf); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT, + error_code); + if (err < 0) { + LOG_ERR("Failed to send GET_PLAYER_APP_SETTING_VAL_TEXT error response"); + } +} + +static void process_inform_displayable_char_set_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->inform_displayable_char_set_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->inform_displayable_char_set_req(get_avrcp_tg(avrcp), tid, buf); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, + error_code); + if (err < 0) { + LOG_ERR("Failed to send INFORM_DISPLAYABLE_CHAR_SET error response"); + } +} + +static void process_inform_batt_status_of_ct_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + uint8_t battery_status; + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->inform_batt_status_of_ct_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + battery_status = net_buf_pull_u8(buf); + if (battery_status > BT_AVRCP_BATTERY_STATUS_FULL) { + LOG_ERR("Invalid battery_status: %d", battery_status); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + avrcp_tg_cb->inform_batt_status_of_ct_req(get_avrcp_tg(avrcp), tid, battery_status); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, + error_code); + if (err < 0) { + LOG_ERR("Failed to send INFORM_BATT_STATUS_OF_CT error response"); + } +} + +static void process_get_element_attrs_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_element_attrs_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->get_element_attrs_req(get_avrcp_tg(avrcp), tid, buf); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, error_code); + if (err < 0) { + LOG_ERR("Failed to send GET_ELEMENT_ATTRS error response"); + } +} + +static void process_get_play_status_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_play_status_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->get_play_status_req(get_avrcp_tg(avrcp), tid); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_GET_PLAY_STATUS, error_code); + if (err < 0) { + LOG_ERR("Failed to send GET_PLAY_STATUS error response"); + } +} + +static void process_set_addressed_player_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + uint16_t player_id; + int error_code; + int err; + + player_id = net_buf_pull_be16(buf); + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->set_addressed_player_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->set_addressed_player_req(get_avrcp_tg(avrcp), tid, player_id); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, error_code); + if (err < 0) { + LOG_ERR("Failed to send SET_ADDRESSED_PLAYER error response"); + } +} + +static void process_play_item_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->play_item_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->play_item_req(get_avrcp_tg(avrcp), tid, buf); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_PLAY_ITEM, error_code); + if (err < 0) { + LOG_ERR("Failed to send PLAY_ITEMS error response"); + } +} + +static void process_add_to_now_playing_cmd(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + int error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->add_to_now_playing_req == NULL)) { + error_code = BT_AVRCP_STATUS_NOT_IMPLEMENTED; + goto err_rsp; + } + + avrcp_tg_cb->add_to_now_playing_req(get_avrcp_tg(avrcp), tid, buf); + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, error_code); + if (err < 0) { + LOG_ERR("Failed to send ADD_TO_NOW_PLAYING error response"); + } +} + +static void handle_avrcp_continuing_rsp(struct bt_avrcp *avrcp, uint8_t tid, uint8_t ctype_or_rsp, + struct net_buf *buf) +{ + LOG_DBG("Received Continuing Response"); + bt_avrcp_tg_set_tx_state(get_avrcp_tg(avrcp), AVRCP_STATE_SENDING_CONTINUING, tid, buf); + k_work_reschedule(&get_avrcp_tg(avrcp)->vd_rsp_tx_work, K_NO_WAIT); +} + +static void handle_avrcp_abort_continuing_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t ctype_or_rsp, struct net_buf *buf) +{ + LOG_DBG("Received Abort Continuing Response"); + bt_avrcp_tg_set_tx_state(get_avrcp_tg(avrcp), AVRCP_STATE_ABORT_CONTINUING, tid, buf); + k_work_reschedule(&get_avrcp_tg(avrcp)->vd_rsp_tx_work, K_NO_WAIT); +} + +static const struct avrcp_pdu_vendor_handler cmd_vendor_handlers[] = { + { BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP, sizeof(uint8_t), BT_AVRCP_CTYPE_CONTROL, + handle_avrcp_continuing_rsp }, + { BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP, sizeof(uint8_t), BT_AVRCP_CTYPE_CONTROL, + handle_avrcp_abort_continuing_rsp }, + { BT_AVRCP_PDU_ID_GET_CAPS, sizeof(uint8_t), BT_AVRCP_CTYPE_STATUS, process_get_caps_cmd }, + { BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, sizeof(struct bt_avrcp_register_notification_cmd), + BT_AVRCP_CTYPE_NOTIFY, avrcp_register_notification_cmd_handler }, + { BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, sizeof(uint8_t), BT_AVRCP_CTYPE_CONTROL, + process_set_absolute_volume_cmd }, + { BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, 0, BT_AVRCP_CTYPE_STATUS, + process_list_player_app_setting_attrs_cmd }, + { BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, + sizeof(struct bt_avrcp_list_player_app_setting_vals_cmd), BT_AVRCP_CTYPE_STATUS, + process_list_player_app_setting_vals_cmd }, + { BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL, + sizeof(struct bt_avrcp_get_curr_player_app_setting_val_cmd), BT_AVRCP_CTYPE_STATUS, + process_get_curr_player_app_setting_val_cmd }, + { BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, + sizeof(struct bt_avrcp_set_player_app_setting_val_cmd), BT_AVRCP_CTYPE_CONTROL, + process_set_player_app_setting_val_cmd }, + { BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT, + sizeof(struct bt_avrcp_get_player_app_setting_attr_text_cmd), BT_AVRCP_CTYPE_STATUS, + process_get_player_app_setting_attr_text_cmd }, + { BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT, + sizeof(struct bt_avrcp_get_player_app_setting_val_text_cmd), BT_AVRCP_CTYPE_STATUS, + process_get_player_app_setting_val_text_cmd }, + { BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, + sizeof(struct bt_avrcp_inform_displayable_char_set_cmd), BT_AVRCP_CTYPE_CONTROL, + process_inform_displayable_char_set_cmd }, + { BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, + sizeof(struct bt_avrcp_inform_batt_status_of_ct_cmd), BT_AVRCP_CTYPE_CONTROL, + process_inform_batt_status_of_ct_cmd }, + { BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, sizeof(struct bt_avrcp_get_element_attrs_cmd), + BT_AVRCP_CTYPE_STATUS, process_get_element_attrs_cmd }, + { BT_AVRCP_PDU_ID_GET_PLAY_STATUS, 0, BT_AVRCP_CTYPE_STATUS, process_get_play_status_cmd }, + { BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, sizeof(struct bt_avrcp_set_addressed_player_cmd), + BT_AVRCP_CTYPE_CONTROL, process_set_addressed_player_cmd }, + { BT_AVRCP_PDU_ID_PLAY_ITEM, sizeof(struct bt_avrcp_play_item_cmd), BT_AVRCP_CTYPE_CONTROL, + process_play_item_cmd }, + { BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, sizeof(struct bt_avrcp_add_to_now_playing_cmd), + BT_AVRCP_CTYPE_CONTROL, process_add_to_now_playing_cmd }, +}; + +static void avrcp_vendor_dependent_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, + struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + bt_avrcp_subunit_type_t subunit_type; + bt_avrcp_subunit_id_t subunit_id; + struct bt_avrcp_avc_vendor_pdu *pdu; + uint8_t pdu_id = 0; + int error_code = BT_AVRCP_STATUS_OPERATION_COMPLETED; + uint8_t ctype_or_rsp; + uint16_t len; + int err; + + if (buf->len < (sizeof(*avrcp_hdr) + sizeof(*pdu))) { + LOG_ERR("Invalid vendor frame length: %d", buf->len); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + ctype_or_rsp = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_PANEL) || + (subunit_id != BT_AVRCP_SUBUNIT_ID_ZERO)) { + LOG_ERR("Invalid vendor dependent command"); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + pdu = net_buf_pull_mem(buf, sizeof(*pdu)); + pdu_id = pdu->pdu_id; + if (sys_get_be24(pdu->company_id) != BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG) { + LOG_ERR("Invalid company id: 0x%06x", sys_get_be24(pdu->company_id)); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + if (BT_AVRCP_AVC_PDU_GET_PACKET_TYPE(pdu) != BT_AVRCP_PKT_TYPE_SINGLE) { + LOG_ERR("Invalid packet type"); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + len = sys_be16_to_cpu(pdu->param_len); + + if (buf->len != len) { + LOG_ERR("Invalid length: %d, buf length = %d", len, buf->len); + error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto err_rsp; + } + + if (ctype_or_rsp != get_cmd_type_by_pdu(pdu->pdu_id)) { + LOG_ERR("Invalid ctype 0x%02x for pdu_id 0x%02x", ctype_or_rsp, pdu->pdu_id); + error_code = BT_AVRCP_STATUS_INVALID_COMMAND; + goto err_rsp; + } + + error_code = handle_vendor_pdu(avrcp, tid, buf, ctype_or_rsp, pdu->pdu_id, + cmd_vendor_handlers, ARRAY_SIZE(cmd_vendor_handlers)); + if (error_code != BT_AVRCP_STATUS_OPERATION_COMPLETED) { + goto err_rsp; + } + return; + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, pdu_id, error_code); + if (err < 0) { + LOG_ERR("Failed to send vendor dependent error response %d", err); + } +} + +static void avrcp_subunit_info_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, + struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + bt_avrcp_subunit_type_t subunit_type; + bt_avrcp_subunit_id_t subunit_id; + bt_avrcp_ctype_t ctype; + uint8_t page; + uint8_t extension_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->subunit_info_req == NULL)) { + goto err_rsp; + } + + if (buf->len < sizeof(*avrcp_hdr)) { + goto err_rsp; + } + + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + if (buf->len != BT_AVRCP_SUBUNIT_INFO_CMD_SIZE) { + LOG_ERR("Invalid subunit info length"); + goto err_rsp; + } + + subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + ctype = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + + /* First byte contains page and extension code */ + page = FIELD_GET(GENMASK(6, 4), buf->data[0]); + extension_code = FIELD_GET(GENMASK(2, 0), buf->data[0]); + + if ((subunit_type != BT_AVRCP_SUBUNIT_TYPE_UNIT) || (ctype != BT_AVRCP_CTYPE_STATUS) || + (subunit_id != BT_AVRCP_SUBUNIT_ID_IGNORE) || (page != AVRCP_SUBUNIT_PAGE) || + (avrcp_hdr->opcode != BT_AVRCP_OPC_SUBUNIT_INFO) || + (extension_code != AVRCP_SUBUNIT_EXTENSION_CODE)) { + LOG_ERR("Invalid subunit info command"); + goto err_rsp; + } + + return avrcp_tg_cb->subunit_info_req(get_avrcp_tg(avrcp), tid); + +err_rsp: + err = bt_avrcp_send_subunit_info(avrcp, tid, BT_AVRCP_RSP_REJECTED); + if (err < 0) { + LOG_ERR("Failed to send subunit info error response"); + if (bt_avrcp_disconnect(avrcp->acl_conn)) { + LOG_ERR("Failed to disconnect AVRCP connection"); + } + } +} + +static void avrcp_pass_through_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, + struct net_buf *buf) +{ + struct bt_avrcp_header *avrcp_hdr; + struct net_buf *rsp_buf; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->passthrough_req == NULL)) { + goto err_rsp; + } + + if (buf->len < (sizeof(*avrcp_hdr) + sizeof(struct bt_avrcp_passthrough_cmd))) { + LOG_ERR("Invalid passthrough command length: %d", buf->len); + goto err_rsp; + } + avrcp_hdr = net_buf_pull_mem(buf, sizeof(*avrcp_hdr)); + + if (BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr) != BT_AVRCP_SUBUNIT_TYPE_PANEL || + BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr) != BT_AVRCP_SUBUNIT_ID_ZERO || + BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr) != BT_AVRCP_CTYPE_CONTROL) { + LOG_ERR("Invalid passthrough command "); + goto err_rsp; + } + + return avrcp_tg_cb->passthrough_req(get_avrcp_tg(avrcp), tid, buf); + +err_rsp: + rsp_buf = bt_avrcp_create_pdu(NULL); + if (rsp_buf == NULL) { + LOG_ERR("Failed to allocate response buffer"); + return; + } + + err = bt_avrcp_tg_send_passthrough_rsp(get_avrcp_tg(avrcp), tid, BT_AVRCP_RSP_REJECTED, + rsp_buf); + if (err < 0) { + LOG_ERR("Failed to send passthrough error response"); + net_buf_unref(rsp_buf); + if (bt_avrcp_disconnect(avrcp->acl_conn)) { + LOG_ERR("Failed to disconnect AVRCP connection"); + } + } +} + +static const struct avrcp_handler cmd_handlers[] = { + { BT_AVRCP_OPC_VENDOR_DEPENDENT, avrcp_vendor_dependent_cmd_handler}, + { BT_AVRCP_OPC_UNIT_INFO, avrcp_unit_info_cmd_handler}, + { BT_AVRCP_OPC_SUBUNIT_INFO, avrcp_subunit_info_cmd_handler}, + { BT_AVRCP_OPC_PASS_THROUGH, avrcp_pass_through_cmd_handler}, +}; + +/* An AVRCP message received */ +static int avrcp_recv(struct bt_avctp *session, struct net_buf *buf, bt_avctp_cr_t cr, uint8_t tid) +{ + struct bt_avrcp *avrcp = AVRCP_AVCTP(session); + struct bt_avrcp_header *avrcp_hdr; + bt_avrcp_rsp_t rsp; + bt_avrcp_subunit_id_t subunit_id; + bt_avrcp_subunit_type_t subunit_type; + + if (buf->len < sizeof(*avrcp_hdr)) { + LOG_ERR("invalid AVRCP header received"); + return -EINVAL; + } + + avrcp_hdr = (void *)buf->data; + rsp = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); + subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); + subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + + LOG_DBG("AVRCP msg received, cr:0x%X, tid:0x%X, rsp: 0x%X, opc:0x%02X,", cr, tid, rsp, + avrcp_hdr->opcode); + if (cr == BT_AVCTP_RESPONSE) { + ARRAY_FOR_EACH(rsp_handlers, i) { + if (avrcp_hdr->opcode == rsp_handlers[i].opcode) { + rsp_handlers[i].func(avrcp, tid, buf); + return 0; + } + } + } else { + ARRAY_FOR_EACH(cmd_handlers, i) { + if (avrcp_hdr->opcode == cmd_handlers[i].opcode) { + cmd_handlers[i].func(avrcp, tid, buf); + return 0; + } + } + } + + LOG_WRN("received unknown opcode : 0x%02X", avrcp_hdr->opcode); + return 0; +} + +static void init_avctp_control_channel(struct bt_avctp *session) +{ + LOG_DBG("session %p", session); + + session->br_chan.rx.mtu = BT_L2CAP_RX_MTU; + session->br_chan.required_sec_level = BT_SECURITY_L2; + session->pid = BT_SDP_AV_REMOTE_SVCLASS; + session->tx_pool = &avctp_ctrl_tx_pool; + session->max_tx_payload_size = CONFIG_BT_L2CAP_TX_MTU; + session->rx_pool = &avctp_ctrl_rx_pool; +} + +static const struct bt_avctp_ops_cb avctp_ops = { + .connected = avrcp_connected, + .disconnected = avrcp_disconnected, + .recv = avrcp_recv, +}; + +static int avrcp_accept(struct bt_conn *conn, struct bt_avctp **session) +{ + struct bt_avrcp *avrcp; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) { + return -ENOMEM; + } + + if (avrcp->acl_conn != NULL) { + return -EALREADY; + } + + init_avctp_control_channel(&(avrcp->session)); + *session = &(avrcp->session); + avrcp->session.ops = &avctp_ops; + avrcp->acl_conn = bt_conn_ref(conn); + + LOG_DBG("session: %p", &(avrcp->session)); + + return 0; +} + +#if defined(CONFIG_BT_AVRCP_BROWSING) +static void init_avctp_browsing_channel(struct bt_avctp *session) +{ + LOG_DBG("session %p", session); + + session->br_chan.rx.mtu = BT_L2CAP_RX_MTU; + session->br_chan.required_sec_level = BT_SECURITY_L2; + session->br_chan.rx.optional = false; + session->br_chan.rx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; + session->br_chan.rx.max_transmit = 3; + session->br_chan.rx.mode = BT_L2CAP_BR_LINK_MODE_ERET; + session->br_chan.tx.monitor_timeout = CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT; + session->pid = BT_SDP_AV_REMOTE_SVCLASS; + session->tx_pool = NULL; + session->max_tx_payload_size = 0; + session->rx_pool = NULL; +} + +/* The AVCTP L2CAP channel established */ +static void browsing_avrcp_connected(struct bt_avctp *session) +{ + struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->browsing_connected != NULL)) { + avrcp_ct_cb->browsing_connected(session->br_chan.chan.conn, get_avrcp_ct(avrcp)); + } + + if ((avrcp_tg_cb != NULL) && (avrcp_tg_cb->browsing_connected != NULL)) { + avrcp_tg_cb->browsing_connected(session->br_chan.chan.conn, get_avrcp_tg(avrcp)); + } +} + +/* The AVCTP L2CAP channel released */ +static void browsing_avrcp_disconnected(struct bt_avctp *session) +{ + struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); + + if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->disconnected != NULL)) { + avrcp_ct_cb->browsing_disconnected(get_avrcp_ct(avrcp)); + } + if ((avrcp_tg_cb != NULL) && (avrcp_tg_cb->disconnected != NULL)) { + avrcp_tg_cb->browsing_disconnected(get_avrcp_tg(avrcp)); + } +} + +static int avrcp_ct_handle_set_browsed_player(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->browsed_player_rsp == NULL)) { + return -EINVAL; + } + + avrcp_ct_cb->browsed_player_rsp(get_avrcp_ct(avrcp), tid, buf); + + return 0; +} + +static const struct avrcp_pdu_handler rsp_brow_handlers[] = { + {BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER, sizeof(struct bt_avrcp_set_browsed_player_rsp), + avrcp_ct_handle_set_browsed_player}, +}; + +static int avrcp_tg_handle_set_browsed_player_req(struct bt_avrcp *avrcp, + uint8_t tid, struct net_buf *buf) +{ + uint16_t player_id; + struct net_buf *rsp_buf; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->set_browsed_player_req == NULL)) { + goto error_rsp; + } + + player_id = net_buf_pull_be16(buf); + + LOG_DBG("Set browsed player request: player_id=0x%04x", player_id); + + avrcp_tg_cb->set_browsed_player_req(get_avrcp_tg(avrcp), tid, player_id); + return 0; + +error_rsp: + rsp_buf = bt_avrcp_create_pdu(NULL); + __ASSERT(rsp_buf != NULL, "Failed to allocate response buffer"); + + if (net_buf_tailroom(rsp_buf) < sizeof(uint8_t)) { + LOG_ERR("Insufficient space in response buffer"); + net_buf_unref(rsp_buf); + return -ENOMEM; + } + net_buf_add_u8(rsp_buf, BT_AVRCP_STATUS_INTERNAL_ERROR); + + err = bt_avrcp_tg_send_set_browsed_player_rsp(get_avrcp_tg(avrcp), tid, rsp_buf); + if (err < 0) { + LOG_ERR("Failed to send browsed player error response (err: %d)", err); + net_buf_unref(rsp_buf); + } + return err; +} + +static const struct avrcp_pdu_handler cmd_brow_handlers[] = { + {BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER, sizeof(uint16_t), + avrcp_tg_handle_set_browsed_player_req}, +}; + +static int handle_pdu(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf, + uint8_t pdu_id, const struct avrcp_pdu_handler *handlers, size_t num_handlers) +{ + for (size_t i = 0; i < num_handlers; i++) { + const struct avrcp_pdu_handler *handler = &handlers[i]; + + if (handler->pdu_id != pdu_id) { + continue; + } + + if (buf->len < handler->min_len) { + LOG_ERR("Too small (%u bytes) pdu_id 0x%02x", buf->len, pdu_id); + return -EINVAL; + } + + return handler->func(avrcp, tid, buf); + } + + return -EOPNOTSUPP; +} + +static int browsing_avrcp_recv(struct bt_avctp *session, struct net_buf *buf, bt_avctp_cr_t cr, + uint8_t tid) +{ + struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); + struct bt_avrcp_brow_pdu *brow; + + if (buf->len < sizeof(struct bt_avrcp_brow_pdu)) { + LOG_ERR("Invalid AVRCP browsing header received: buffer too short (%u)", buf->len); + return -EMSGSIZE; + } + + brow = net_buf_pull_mem(buf, sizeof(struct bt_avrcp_brow_pdu)); + + if (buf->len != sys_be16_to_cpu(brow->param_len)) { + LOG_ERR("Invalid AVRCP browsing PDU length: expected %u, got %u", + sys_be16_to_cpu(brow->param_len), buf->len); + return -EMSGSIZE; + } + + LOG_DBG("AVRCP browsing msg received, cr:0x%X, tid:0x%X, pdu_id:0x%02X", cr, + tid, brow->pdu_id); + + if (cr == BT_AVCTP_RESPONSE) { + return handle_pdu(avrcp, tid, buf, brow->pdu_id, rsp_brow_handlers, + ARRAY_SIZE(rsp_brow_handlers)); + } + + return handle_pdu(avrcp, tid, buf, brow->pdu_id, cmd_brow_handlers, + ARRAY_SIZE(cmd_brow_handlers)); +} + +static const struct bt_avctp_ops_cb browsing_avctp_ops = { + .connected = browsing_avrcp_connected, + .disconnected = browsing_avrcp_disconnected, + .recv = browsing_avrcp_recv, +}; + +static int avrcp_browsing_accept(struct bt_conn *conn, struct bt_avctp **session) +{ + struct bt_avrcp *avrcp; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) { + LOG_ERR("Cannot allocate memory"); + return -ENOTCONN; + } + + if (avrcp->acl_conn == NULL) { + LOG_ERR("The control channel not established"); + return -ENOTCONN; + } + + if (avrcp->browsing_session.br_chan.chan.conn != NULL) { + LOG_ERR("Browsing session already connected"); + return -EALREADY; + } + + init_avctp_browsing_channel(&(avrcp->browsing_session)); + avrcp->browsing_session.ops = &browsing_avctp_ops; + *session = &(avrcp->browsing_session); + + LOG_DBG("browsing_session: %p", &(avrcp->browsing_session)); + + return 0; +} +#endif /* CONFIG_BT_AVRCP_BROWSING */ + +int bt_avrcp_init(void) +{ + int err; + + /* Register event handlers with AVCTP */ + avctp_server.l2cap.psm = BT_L2CAP_PSM_AVRCP; + avctp_server.accept = avrcp_accept; + err = bt_avctp_server_register(&avctp_server); + if (err < 0) { + LOG_ERR("AVRCP registration failed"); + return err; + } + +#if defined(CONFIG_BT_AVRCP_BROWSING) + avctp_browsing_server.l2cap.psm = BT_L2CAP_PSM_AVRCP_BROWSING; + avctp_browsing_server.accept = avrcp_browsing_accept; + err = bt_avctp_server_register(&avctp_browsing_server); + if (err < 0) { + LOG_ERR("AVRCP browsing registration failed"); + return err; + } +#endif /* CONFIG_BT_AVRCP_BROWSING */ + +#if defined(CONFIG_BT_AVRCP_TARGET) + bt_sdp_register_service(&avrcp_tg_rec); +#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + +#if defined(CONFIG_BT_AVRCP_CONTROLLER) + bt_sdp_register_service(&avrcp_ct_rec); +#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + + /* Init CT and TG connection pool*/ + __ASSERT(ARRAY_SIZE(bt_avrcp_ct_pool) == ARRAY_SIZE(avrcp_connection), "CT size mismatch"); + __ASSERT(ARRAY_SIZE(bt_avrcp_tg_pool) == ARRAY_SIZE(avrcp_connection), "TG size mismatch"); + + ARRAY_FOR_EACH(avrcp_connection, i) { + bt_avrcp_ct_pool[i].avrcp = &avrcp_connection[i]; + bt_avrcp_tg_pool[i].avrcp = &avrcp_connection[i]; + /* Init delay work */ + k_work_init_delayable(&bt_avrcp_tg_pool[i].vd_rsp_tx_work, + bt_avrcp_tg_vendor_tx_work); + sys_slist_init(&bt_avrcp_tg_pool[i].vd_rsp_tx_pending); + + k_sem_init(&bt_avrcp_tg_pool[i].lock, 1, 1); + + memset(bt_avrcp_tg_pool[i].interim_sent, 0, + sizeof(bt_avrcp_tg_pool[i].interim_sent)); + } + LOG_DBG("AVRCP Initialized successfully."); + return 0; +} + +int bt_avrcp_connect(struct bt_conn *conn) +{ + struct bt_avrcp *avrcp; + int err; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) { + LOG_ERR("Cannot allocate memory"); + return -ENOTCONN; + } + + if (avrcp->acl_conn != NULL) { + return -EALREADY; + } + + avrcp->session.ops = &avctp_ops; + init_avctp_control_channel(&(avrcp->session)); + err = bt_avctp_connect(conn, BT_L2CAP_PSM_AVRCP, &(avrcp->session)); + if (err < 0) { + /* If error occurs, undo the saving and return the error */ + memset(avrcp, 0, sizeof(struct bt_avrcp)); + LOG_DBG("AVCTP Connect failed"); + return err; + } + avrcp->acl_conn = bt_conn_ref(conn); + + LOG_DBG("Connection request sent"); + return err; +} + +int bt_avrcp_disconnect(struct bt_conn *conn) +{ + int err; + struct bt_avrcp *avrcp; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) { + LOG_ERR("Get avrcp connection failure"); + return -ENOTCONN; + } + + if (avrcp->browsing_session.br_chan.chan.conn != NULL) { + /* If browsing session is still active, disconnect it first */ + err = bt_avrcp_browsing_disconnect(conn); + if (err < 0) { + LOG_ERR("Browsing session disconnect failed: %d", err); + return err; + } + } + + err = bt_avctp_disconnect(&(avrcp->session)); + if (err < 0) { + LOG_DBG("AVCTP Disconnect failed"); + return err; + } + + return err; +} + +struct net_buf *bt_avrcp_create_pdu(struct net_buf_pool *pool) +{ + return bt_conn_create_pdu(pool, BT_AVRCP_HEADROOM); +} + +struct net_buf *bt_avrcp_create_vendor_pdu(struct net_buf_pool *pool) +{ + return bt_conn_create_pdu(pool, BT_AVRCP_HEADROOM + + sizeof(struct bt_avrcp_avc_vendor_pdu)); +} + +#if defined(CONFIG_BT_AVRCP_BROWSING) +int bt_avrcp_browsing_connect(struct bt_conn *conn) +{ + struct bt_avrcp *avrcp; + int err; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) { + LOG_ERR("Cannot allocate memory"); + return -ENOTCONN; + } + + if (avrcp->acl_conn == NULL) { + LOG_ERR("The control channel not established"); + return -ENOTCONN; + } + + if (avrcp->browsing_session.br_chan.chan.conn != NULL) { + return -EALREADY; + } + + avrcp->browsing_session.ops = &browsing_avctp_ops; + init_avctp_browsing_channel(&(avrcp->browsing_session)); + err = bt_avctp_connect(conn, BT_L2CAP_PSM_AVRCP_BROWSING, &(avrcp->browsing_session)); + if (err < 0) { + LOG_ERR("AVCTP browsing connect failed"); + return err; + } + + LOG_DBG("Browsing connection request sent"); + + return 0; +} + +int bt_avrcp_browsing_disconnect(struct bt_conn *conn) +{ + int err; + struct bt_avrcp *avrcp; + + avrcp = avrcp_get_connection(conn); + if (avrcp == NULL) { + LOG_ERR("Get avrcp connection failure"); + return -ENOTCONN; + } + + err = bt_avctp_disconnect(&(avrcp->browsing_session)); + if (err < 0) { + LOG_ERR("AVCTP browsing disconnect failed"); + return err; + } + + return err; +} +#endif /* CONFIG_BT_AVRCP_BROWSING */ + +int bt_avrcp_ct_get_unit_info(struct bt_avrcp_ct *ct, uint8_t tid) +{ + struct net_buf *buf; + int err; + uint8_t param[5]; + + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + buf = avrcp_create_unit_pdu(ct->avrcp, BT_AVRCP_CTYPE_STATUS); + if (!buf) { + return -ENOMEM; + } + + memset(param, 0xFF, ARRAY_SIZE(param)); + net_buf_add_mem(buf, param, sizeof(param)); + + err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + return err; +} + +int bt_avrcp_ct_get_subunit_info(struct bt_avrcp_ct *ct, uint8_t tid) +{ + struct net_buf *buf; + uint8_t param[5]; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + buf = avrcp_create_subunit_pdu(ct->avrcp, BT_AVRCP_CTYPE_STATUS); + if (!buf) { + return -ENOMEM; + } + + memset(param, 0xFF, ARRAY_SIZE(param)); + param[0] = FIELD_PREP(GENMASK(6, 4), AVRCP_SUBUNIT_PAGE) | + FIELD_PREP(GENMASK(2, 0), AVRCP_SUBUNIT_EXTENSION_CODE); + net_buf_add_mem(buf, param, sizeof(param)); + + err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + return err; +} + +int bt_avrcp_ct_passthrough(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t opid, uint8_t state, + const uint8_t *payload, uint8_t len) +{ + struct net_buf *buf; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + buf = avrcp_create_passthrough_pdu(ct->avrcp, BT_AVRCP_CTYPE_CONTROL); + if (!buf) { + return -ENOMEM; + } + + net_buf_add_u8(buf, FIELD_PREP(BIT(7), state) | FIELD_PREP(GENMASK(6, 0), opid)); + net_buf_add_u8(buf, len); + if (len) { + net_buf_add_mem(buf, payload, len); + } + + err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + return err; +} + +#if defined(CONFIG_BT_AVRCP_BROWSING) +int bt_avrcp_ct_set_browsed_player(struct bt_avrcp_ct *ct, uint8_t tid, uint16_t player_id) +{ + struct net_buf *buf; + struct bt_avrcp_brow_pdu *pdu; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + if (ct->avrcp->browsing_session.br_chan.chan.conn == NULL) { + LOG_ERR("Browsing session not connected"); + return -ENOTCONN; + } + + buf = avrcp_create_browsing_pdu(ct->avrcp); + if (buf == NULL) { + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < sizeof(*pdu) + sizeof(player_id)) { + LOG_ERR("Not enough tailroom in buffer for browsing PDU"); + net_buf_unref(buf); + return -ENOMEM; + } + + pdu = net_buf_add(buf, sizeof(*pdu)); + pdu->pdu_id = BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER; + pdu->param_len = sys_cpu_to_be16(sizeof(player_id)); + net_buf_add_be16(buf, player_id); + + err = avrcp_browsing_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (err < 0) { + LOG_ERR("Failed to send AVRCP browsing PDU (err: %d)", err); + net_buf_unref(buf); + } + return err; +} +#endif /* CONFIG_BT_AVRCP_BROWSING */ + +int bt_avrcp_ct_register_notification(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t event_id, + uint32_t interval, bt_avrcp_notification_cb_t cb) +{ + struct net_buf *buf; + uint16_t param_len = sizeof(event_id) + sizeof(interval); + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + if (avrcp_ct_cb->notification_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + if (event_id > BT_AVRCP_EVT_VOLUME_CHANGED) { + return -EINVAL; + } + + memset(&ct->ct_notify[event_id], 0, sizeof(ct->ct_notify[0])); + ct->ct_notify[event_id].cb = cb; + ct->ct_notify[event_id].interim_received = 0; + ct->ct_notify[event_id].tid = tid; + + buf = avrcp_prepare_vendor_pdu(ct->avrcp, BT_AVRCP_PKT_TYPE_SINGLE, BT_AVRCP_CTYPE_NOTIFY, + BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, param_len); + if (buf == NULL) { + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < param_len) { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); + return -ENOMEM; + } + /* Add event ID */ + net_buf_add_u8(buf, event_id); + + /* Add playback interval */ + net_buf_add_be32(buf, interval); + + err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, ct->ct_notify[event_id].tid); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + return err; +} + +static int bt_avrcp_ct_vendor_dependent(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t pdu_id, + struct net_buf *buf) +{ + struct bt_avrcp_header *hdr; + struct bt_avrcp_avc_vendor_pdu *pdu; + uint16_t param_len; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + param_len = buf->len; + + if (net_buf_headroom(buf) < (sizeof(*pdu) + sizeof(*hdr))) { + LOG_WRN("Not enough headroom: for vendor dependent PDU"); + net_buf_unref(buf); + return -ENOMEM; + } + + pdu = net_buf_push(buf, sizeof(*pdu)); + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, pdu->company_id); + pdu->pdu_id = pdu_id; + BT_AVRCP_AVC_PDU_SET_PACKET_TYPE(pdu, BT_AVRCP_PKT_TYPE_SINGLE); + pdu->param_len = sys_cpu_to_be16(param_len); + + hdr = net_buf_push(buf, sizeof(struct bt_avrcp_header)); + memset(hdr, 0, sizeof(struct bt_avrcp_header)); + BT_AVRCP_HDR_SET_CTYPE_OR_RSP(hdr, get_cmd_type_by_pdu(pdu_id)); + BT_AVRCP_HDR_SET_SUBUNIT_ID(hdr, BT_AVRCP_SUBUNIT_ID_ZERO); + BT_AVRCP_HDR_SET_SUBUNIT_TYPE(hdr, BT_AVRCP_SUBUNIT_TYPE_PANEL); + hdr->opcode = BT_AVRCP_OPC_VENDOR_DEPENDENT; + + err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (err < 0) { + LOG_ERR("Failed to send vendor PDU (err: %d)", err); + } + return err; +} + +int bt_avrcp_ct_get_caps(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t cap_id) +{ + struct net_buf *buf; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + if (avrcp_ct_cb->get_caps_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(cap_id)) { + LOG_WRN("Not enough tailroom: for cap_id"); + net_buf_unref(buf); + return -ENOMEM; + } + net_buf_add_u8(buf, cap_id); + + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_GET_CAPS, buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + return err; +} + +int bt_avrcp_ct_set_absolute_volume(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t absolute_volume) +{ + struct net_buf *buf; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + if (avrcp_ct_cb->set_absolute_volume_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + if (absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) { + LOG_ERR("Invalid absolute volume: %d", absolute_volume); + return -EINVAL; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(absolute_volume)) { + LOG_WRN("Not enough tailroom: for absolute_volume"); + net_buf_unref(buf); + return -ENOMEM; + } + net_buf_add_u8(buf, absolute_volume); + + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + return err; +} + +int bt_avrcp_ct_list_player_app_setting_attrs(struct bt_avrcp_ct *ct, uint8_t tid) +{ + struct net_buf *buf; + int err; + + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + if (avrcp_ct_cb->list_player_app_setting_attrs_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + return -ENOBUFS; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, + buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + return err; +} + +int bt_avrcp_ct_list_player_app_setting_vals(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t attr_id) +{ + int err; + struct net_buf *buf; + + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + if (avrcp_ct_cb->list_player_app_setting_vals_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + return -ENOBUFS; + } + + if (net_buf_tailroom(buf) < sizeof(attr_id)) { + LOG_WRN("Not enough tailroom: for attr_id"); + net_buf_unref(buf); + return -ENOMEM; + } + + net_buf_add_u8(buf, attr_id); + + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, + buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + return err; +} + +int bt_avrcp_ct_get_curr_player_app_setting_val(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf) +{ + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + if (avrcp_ct_cb->get_curr_player_app_setting_val_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL, + buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); } + return err; } -static const struct avrcp_handler cmd_handlers[] = { - { BT_AVRCP_OPC_VENDOR_DEPENDENT, avrcp_vendor_dependent_cmd_handler}, - { BT_AVRCP_OPC_UNIT_INFO, avrcp_unit_info_cmd_handler}, - { BT_AVRCP_OPC_SUBUNIT_INFO, avrcp_subunit_info_cmd_handler}, - { BT_AVRCP_OPC_PASS_THROUGH, avrcp_pass_through_cmd_handler}, -}; - -/* An AVRCP message received */ -static int avrcp_recv(struct bt_avctp *session, struct net_buf *buf, bt_avctp_cr_t cr, uint8_t tid) +int bt_avrcp_ct_set_player_app_setting_val(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) { - struct bt_avrcp *avrcp = AVRCP_AVCTP(session); - struct bt_avrcp_header *avrcp_hdr; - bt_avrcp_rsp_t rsp; - bt_avrcp_subunit_id_t subunit_id; - bt_avrcp_subunit_type_t subunit_type; + int err; - if (buf->len < sizeof(*avrcp_hdr)) { - LOG_ERR("invalid AVRCP header received"); + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { return -EINVAL; } - avrcp_hdr = (void *)buf->data; - rsp = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(avrcp_hdr); - subunit_id = BT_AVRCP_HDR_GET_SUBUNIT_ID(avrcp_hdr); - subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } - LOG_DBG("AVRCP msg received, cr:0x%X, tid:0x%X, rsp: 0x%X, opc:0x%02X,", cr, tid, rsp, - avrcp_hdr->opcode); - if (cr == BT_AVCTP_RESPONSE) { - ARRAY_FOR_EACH(rsp_handlers, i) { - if (avrcp_hdr->opcode == rsp_handlers[i].opcode) { - rsp_handlers[i].func(avrcp, tid, buf); - return 0; - } - } - } else { - ARRAY_FOR_EACH(cmd_handlers, i) { - if (avrcp_hdr->opcode == cmd_handlers[i].opcode) { - cmd_handlers[i].func(avrcp, tid, buf); - return 0; - } - } + if (avrcp_ct_cb->set_player_app_setting_val_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; } - LOG_WRN("received unknown opcode : 0x%02X", avrcp_hdr->opcode); - return 0; + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, + buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + return err; } -static void init_avctp_control_channel(struct bt_avctp *session) +int bt_avrcp_ct_get_player_app_setting_attr_text(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf) { - LOG_DBG("session %p", session); + int err; - session->br_chan.rx.mtu = BT_L2CAP_RX_MTU; - session->br_chan.required_sec_level = BT_SECURITY_L2; - session->pid = BT_SDP_AV_REMOTE_SVCLASS; - session->tx_pool = &avctp_ctrl_tx_pool; - session->max_tx_payload_size = CONFIG_BT_L2CAP_TX_MTU; - session->rx_pool = &avctp_ctrl_rx_pool; -} + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; + } -static const struct bt_avctp_ops_cb avctp_ops = { - .connected = avrcp_connected, - .disconnected = avrcp_disconnected, - .recv = avrcp_recv, -}; + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } -static int avrcp_accept(struct bt_conn *conn, struct bt_avctp **session) -{ - struct bt_avrcp *avrcp; + if (avrcp_ct_cb->get_player_app_setting_attr_text_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } - avrcp = avrcp_get_connection(conn); - if (avrcp == NULL) { - return -ENOMEM; + err = bt_avrcp_ct_vendor_dependent(ct, tid, + BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT, buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); } + return err; +} - if (avrcp->acl_conn != NULL) { - return -EALREADY; +int bt_avrcp_ct_get_player_app_setting_val_text(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf) +{ + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; } - init_avctp_control_channel(&(avrcp->session)); - *session = &(avrcp->session); - avrcp->session.ops = &avctp_ops; - avrcp->acl_conn = bt_conn_ref(conn); + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } - LOG_DBG("session: %p", &(avrcp->session)); + if (avrcp_ct_cb->get_player_app_setting_val_text_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } - return 0; + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT, + buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + return err; } -#if defined(CONFIG_BT_AVRCP_BROWSING) -static void init_avctp_browsing_channel(struct bt_avctp *session) +int bt_avrcp_ct_inform_displayable_char_set(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf) { - LOG_DBG("session %p", session); + int err; - session->br_chan.rx.mtu = BT_L2CAP_RX_MTU; - session->br_chan.required_sec_level = BT_SECURITY_L2; - session->br_chan.rx.optional = false; - session->br_chan.rx.max_window = CONFIG_BT_L2CAP_MAX_WINDOW_SIZE; - session->br_chan.rx.max_transmit = 3; - session->br_chan.rx.mode = BT_L2CAP_BR_LINK_MODE_ERET; - session->br_chan.tx.monitor_timeout = CONFIG_BT_L2CAP_BR_MONITOR_TIMEOUT; - session->pid = BT_SDP_AV_REMOTE_SVCLASS; - session->tx_pool = NULL; - session->max_tx_payload_size = 0; - session->rx_pool = NULL; -} + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; + } -/* The AVCTP L2CAP channel established */ -static void browsing_avrcp_connected(struct bt_avctp *session) -{ - struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } - if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->browsing_connected != NULL)) { - avrcp_ct_cb->browsing_connected(session->br_chan.chan.conn, get_avrcp_ct(avrcp)); + if (avrcp_ct_cb->inform_displayable_char_set_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; } - if ((avrcp_tg_cb != NULL) && (avrcp_tg_cb->browsing_connected != NULL)) { - avrcp_tg_cb->browsing_connected(session->br_chan.chan.conn, get_avrcp_tg(avrcp)); + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, + buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); } + return err; } -/* The AVCTP L2CAP channel released */ -static void browsing_avrcp_disconnected(struct bt_avctp *session) +int bt_avrcp_ct_inform_batt_status_of_ct(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t battery_status) { - struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); + int err; + struct net_buf *buf; - if ((avrcp_ct_cb != NULL) && (avrcp_ct_cb->disconnected != NULL)) { - avrcp_ct_cb->browsing_disconnected(get_avrcp_ct(avrcp)); + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; } - if ((avrcp_tg_cb != NULL) && (avrcp_tg_cb->disconnected != NULL)) { - avrcp_tg_cb->browsing_disconnected(get_avrcp_tg(avrcp)); + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; } -} -static int avrcp_ct_handle_set_browsed_player(struct bt_avrcp *avrcp, - uint8_t tid, struct net_buf *buf) -{ - if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->browsed_player_rsp == NULL)) { + if (avrcp_ct_cb->inform_batt_status_of_ct_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } + + if (battery_status > BT_AVRCP_BATTERY_STATUS_FULL) { + LOG_ERR("Invalid battery status: %d", battery_status); return -EINVAL; } - avrcp_ct_cb->browsed_player_rsp(get_avrcp_ct(avrcp), tid, buf); + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + return -ENOBUFS; + } - return 0; -} + if (net_buf_tailroom(buf) < sizeof(battery_status)) { + LOG_WRN("Not enough tailroom: for battery_status"); + net_buf_unref(buf); + return -ENOMEM; + } -static const struct avrcp_pdu_handler rsp_brow_handlers[] = { - {BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER, sizeof(struct bt_avrcp_set_browsed_player_rsp), - avrcp_ct_handle_set_browsed_player}, -}; + net_buf_add_u8(buf, battery_status); -static int avrcp_tg_handle_set_browsed_player_req(struct bt_avrcp *avrcp, - uint8_t tid, struct net_buf *buf) + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, + buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + return err; +} + +int bt_avrcp_ct_get_element_attrs(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf) { - uint16_t player_id; - struct net_buf *rsp_buf; int err; - if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->set_browsed_player_req == NULL)) { - goto error_rsp; + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; } - player_id = net_buf_pull_be16(buf); - - LOG_DBG("Set browsed player request: player_id=0x%04x", player_id); - - avrcp_tg_cb->set_browsed_player_req(get_avrcp_tg(avrcp), tid, player_id); - return 0; - -error_rsp: - rsp_buf = bt_avrcp_create_pdu(NULL); - __ASSERT(rsp_buf != NULL, "Failed to allocate response buffer"); + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } - if (net_buf_tailroom(rsp_buf) < sizeof(uint8_t)) { - LOG_ERR("Insufficient space in response buffer"); - net_buf_unref(rsp_buf); - return -ENOMEM; + if (avrcp_ct_cb->get_element_attrs_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; } - net_buf_add_u8(rsp_buf, BT_AVRCP_STATUS_INTERNAL_ERROR); - err = bt_avrcp_tg_send_set_browsed_player_rsp(get_avrcp_tg(avrcp), tid, rsp_buf); + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, buf); if (err < 0) { - LOG_ERR("Failed to send browsed player error response (err: %d)", err); - net_buf_unref(rsp_buf); + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); } return err; } -static const struct avrcp_pdu_handler cmd_brow_handlers[] = { - {BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER, sizeof(uint16_t), - avrcp_tg_handle_set_browsed_player_req}, -}; - -static int handle_pdu(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf, - uint8_t pdu_id, const struct avrcp_pdu_handler *handlers, size_t num_handlers) +int bt_avrcp_ct_get_play_status(struct bt_avrcp_ct *ct, uint8_t tid) { - for (size_t i = 0; i < num_handlers; i++) { - const struct avrcp_pdu_handler *handler = &handlers[i]; + struct net_buf *buf; + int err; - if (handler->pdu_id != pdu_id) { - continue; - } + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } - if (buf->len < handler->min_len) { - LOG_ERR("Too small (%u bytes) pdu_id 0x%02x", buf->len, pdu_id); - return -EINVAL; - } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } - return handler->func(avrcp, tid, buf); + if (avrcp_ct_cb->get_play_status_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; } - return -EOPNOTSUPP; + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + return -ENOBUFS; + } + + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_GET_PLAY_STATUS, buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + return err; } -static int browsing_avrcp_recv(struct bt_avctp *session, struct net_buf *buf, bt_avctp_cr_t cr, - uint8_t tid) +int bt_avrcp_ct_set_addressed_player(struct bt_avrcp_ct *ct, uint8_t tid, uint16_t player_id) { - struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); - struct bt_avrcp_avc_brow_pdu *brow; + int err; + struct net_buf *buf; - if (buf->len < sizeof(struct bt_avrcp_avc_brow_pdu)) { - LOG_ERR("Invalid AVRCP browsing header received: buffer too short (%u)", buf->len); - return -EMSGSIZE; + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; } - brow = net_buf_pull_mem(buf, sizeof(struct bt_avrcp_avc_brow_pdu)); + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } - if (buf->len != sys_be16_to_cpu(brow->param_len)) { - LOG_ERR("Invalid AVRCP browsing PDU length: expected %u, got %u", - sys_be16_to_cpu(brow->param_len), buf->len); - return -EMSGSIZE; + if (avrcp_ct_cb->set_addressed_player_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; } - LOG_DBG("AVRCP browsing msg received, cr:0x%X, tid:0x%X, pdu_id:0x%02X", cr, - tid, brow->pdu_id); + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + return -ENOBUFS; + } - if (cr == BT_AVCTP_RESPONSE) { - return handle_pdu(avrcp, tid, buf, brow->pdu_id, rsp_brow_handlers, - ARRAY_SIZE(rsp_brow_handlers)); + if (net_buf_tailroom(buf) < sizeof(player_id)) { + LOG_WRN("Not enough tailroom: for player_id"); + net_buf_unref(buf); + return -ENOMEM; } - return handle_pdu(avrcp, tid, buf, brow->pdu_id, cmd_brow_handlers, - ARRAY_SIZE(cmd_brow_handlers)); -} + net_buf_add_be16(buf, player_id); -static const struct bt_avctp_ops_cb browsing_avctp_ops = { - .connected = browsing_avrcp_connected, - .disconnected = browsing_avrcp_disconnected, - .recv = browsing_avrcp_recv, -}; + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } + return err; +} -static int avrcp_browsing_accept(struct bt_conn *conn, struct bt_avctp **session) +int bt_avrcp_ct_play_item(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) { - struct bt_avrcp *avrcp; + int err; - avrcp = avrcp_get_connection(conn); - if (avrcp == NULL) { - LOG_ERR("Cannot allocate memory"); - return -ENOTCONN; + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; } - if (avrcp->acl_conn == NULL) { - LOG_ERR("The control channel not established"); - return -ENOTCONN; + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; } - if (avrcp->browsing_session.br_chan.chan.conn != NULL) { - LOG_ERR("Browsing session already connected"); - return -EALREADY; + if (avrcp_ct_cb->play_item_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; } - init_avctp_browsing_channel(&(avrcp->browsing_session)); - avrcp->browsing_session.ops = &browsing_avctp_ops; - *session = &(avrcp->browsing_session); - - LOG_DBG("browsing_session: %p", &(avrcp->browsing_session)); - - return 0; + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_PLAY_ITEM, buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + return err; } -#endif /* CONFIG_BT_AVRCP_BROWSING */ -int bt_avrcp_init(void) +int bt_avrcp_ct_add_to_now_playing(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) { int err; - /* Register event handlers with AVCTP */ - avctp_server.l2cap.psm = BT_L2CAP_PSM_AVRCP; - avctp_server.accept = avrcp_accept; - err = bt_avctp_server_register(&avctp_server); - if (err < 0) { - LOG_ERR("AVRCP registration failed"); - return err; + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; } -#if defined(CONFIG_BT_AVRCP_BROWSING) - avctp_browsing_server.l2cap.psm = BT_L2CAP_PSM_AVRCP_BROWSING; - avctp_browsing_server.accept = avrcp_browsing_accept; - err = bt_avctp_server_register(&avctp_browsing_server); - if (err < 0) { - LOG_ERR("AVRCP browsing registration failed"); - return err; + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; } -#endif /* CONFIG_BT_AVRCP_BROWSING */ -#if defined(CONFIG_BT_AVRCP_TARGET) - bt_sdp_register_service(&avrcp_tg_rec); -#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + if (avrcp_ct_cb->add_to_now_playing_rsp == NULL) { + LOG_WRN("Rsp callback not registered"); + return -EOPNOTSUPP; + } -#if defined(CONFIG_BT_AVRCP_CONTROLLER) - bt_sdp_register_service(&avrcp_ct_rec); -#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + return err; +} - /* Init CT and TG connection pool*/ - __ASSERT(ARRAY_SIZE(bt_avrcp_ct_pool) == ARRAY_SIZE(avrcp_connection), "CT size mismatch"); - __ASSERT(ARRAY_SIZE(bt_avrcp_tg_pool) == ARRAY_SIZE(avrcp_connection), "TG size mismatch"); +int bt_avrcp_ct_register_cb(const struct bt_avrcp_ct_cb *cb) +{ + if (!cb) { + return -EINVAL; + } - ARRAY_FOR_EACH(avrcp_connection, i) { - bt_avrcp_ct_pool[i].avrcp = &avrcp_connection[i]; - bt_avrcp_tg_pool[i].avrcp = &avrcp_connection[i]; + if (avrcp_ct_cb) { + return -EALREADY; } - LOG_DBG("AVRCP Initialized successfully."); + avrcp_ct_cb = cb; + return 0; } -int bt_avrcp_connect(struct bt_conn *conn) +int bt_avrcp_tg_register_cb(const struct bt_avrcp_tg_cb *cb) { - struct bt_avrcp *avrcp; - int err; - - avrcp = avrcp_get_connection(conn); - if (avrcp == NULL) { - LOG_ERR("Cannot allocate memory"); - return -ENOTCONN; + if (!cb) { + return -EINVAL; } - if (avrcp->acl_conn != NULL) { + if (avrcp_tg_cb) { return -EALREADY; } - avrcp->session.ops = &avctp_ops; - init_avctp_control_channel(&(avrcp->session)); - err = bt_avctp_connect(conn, BT_L2CAP_PSM_AVRCP, &(avrcp->session)); - if (err < 0) { - /* If error occurs, undo the saving and return the error */ - memset(avrcp, 0, sizeof(struct bt_avrcp)); - LOG_DBG("AVCTP Connect failed"); - return err; - } - avrcp->acl_conn = bt_conn_ref(conn); + avrcp_tg_cb = cb; - LOG_DBG("Connection request sent"); - return err; + return 0; } -int bt_avrcp_disconnect(struct bt_conn *conn) +int bt_avrcp_tg_send_unit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + struct bt_avrcp_unit_info_rsp *rsp) { + struct net_buf *buf; int err; - struct bt_avrcp *avrcp; - avrcp = avrcp_get_connection(conn); - if (avrcp == NULL) { - LOG_ERR("Get avrcp connection failure"); - return -ENOTCONN; + if ((tg == NULL) || (tg->avrcp == NULL) || (rsp == NULL)) { + return -EINVAL; } - if (avrcp->browsing_session.br_chan.chan.conn != NULL) { - /* If browsing session is still active, disconnect it first */ - err = bt_avrcp_browsing_disconnect(conn); - if (err < 0) { - LOG_ERR("Browsing session disconnect failed: %d", err); - return err; - } + if (!IS_TG_ROLE_SUPPORTED()) { + return -ENOTSUP; } - err = bt_avctp_disconnect(&(avrcp->session)); - if (err < 0) { - LOG_DBG("AVCTP Disconnect failed"); - return err; + buf = avrcp_create_unit_pdu(tg->avrcp, BT_AVRCP_RSP_STABLE); + if (!buf) { + LOG_WRN("Insufficient buffer"); + return -ENOMEM; } + /* The 0x7 is hard-coded in the spec. */ + net_buf_add_u8(buf, 0x07); + /* Add Unit Type info */ + net_buf_add_u8(buf, FIELD_PREP(GENMASK(7, 3), (rsp->unit_type))); + /* Company ID */ + net_buf_add_be24(buf, (rsp->company_id)); + + err = avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); + } return err; } -struct net_buf *bt_avrcp_create_pdu(struct net_buf_pool *pool) +int bt_avrcp_tg_send_subunit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid) { - return bt_conn_create_pdu(pool, - sizeof(struct bt_l2cap_hdr) + - sizeof(struct bt_avctp_header_start) + - sizeof(struct bt_avrcp_header)); + if ((tg == NULL) || (tg->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_TG_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + return bt_avrcp_send_subunit_info(tg->avrcp, tid, BT_AVRCP_RSP_STABLE); } #if defined(CONFIG_BT_AVRCP_BROWSING) -int bt_avrcp_browsing_connect(struct bt_conn *conn) +int bt_avrcp_tg_send_set_browsed_player_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf) { - struct bt_avrcp *avrcp; + struct bt_avrcp_brow_pdu *hdr; + uint16_t param_len; int err; - avrcp = avrcp_get_connection(conn); - if (avrcp == NULL) { - LOG_ERR("Cannot allocate memory"); - return -ENOTCONN; + if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) { + LOG_ERR("Invalid AVRCP target"); + return -EINVAL; } - if (avrcp->acl_conn == NULL) { - LOG_ERR("The control channel not established"); + if (!IS_TG_ROLE_SUPPORTED()) { + LOG_ERR("Target role not supported"); + return -ENOTSUP; + } + + if (tg->avrcp->browsing_session.br_chan.chan.conn == NULL) { + LOG_ERR("Browsing session not connected"); return -ENOTCONN; } - if (avrcp->browsing_session.br_chan.chan.conn != NULL) { - return -EALREADY; + param_len = buf->len; + + if (net_buf_headroom(buf) < sizeof(struct bt_avrcp_brow_pdu)) { + LOG_ERR("Not enough headroom in buffer for bt_avrcp_brow_pdu"); + return -ENOMEM; } + hdr = net_buf_push(buf, sizeof(struct bt_avrcp_brow_pdu)); + memset(hdr, 0, sizeof(struct bt_avrcp_brow_pdu)); + hdr->pdu_id = BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER; + hdr->param_len = sys_cpu_to_be16(param_len); - avrcp->browsing_session.ops = &browsing_avctp_ops; - init_avctp_browsing_channel(&(avrcp->browsing_session)); - err = bt_avctp_connect(conn, BT_L2CAP_PSM_AVRCP_BROWSING, &(avrcp->browsing_session)); + err = avrcp_browsing_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); if (err < 0) { - LOG_ERR("AVCTP browsing connect failed"); - return err; + LOG_ERR("Failed to send AVRCP browsing PDU (err: %d)", err); } - - LOG_DBG("Browsing connection request sent"); - - return 0; + return err; } +#endif /* CONFIG_BT_AVRCP_BROWSING */ -int bt_avrcp_browsing_disconnect(struct bt_conn *conn) +static int build_notification_rsp_data(uint8_t event_id, struct bt_avrcp_event_data *data, + struct net_buf *buf) { - int err; - struct bt_avrcp *avrcp; + uint16_t param_len = sizeof(event_id); - avrcp = avrcp_get_connection(conn); - if (avrcp == NULL) { - LOG_ERR("Get avrcp connection failure"); - return -ENOTCONN; + /* Calculate parameter length based on event type */ + switch (event_id) { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + param_len += sizeof(data->play_status); + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + param_len += sizeof(data->identifier); + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + param_len += sizeof(data->playback_pos); + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + param_len += sizeof(data->battery_status); + break; + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + param_len += sizeof(data->system_status); + break; + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + param_len += sizeof(data->setting_changed.num_of_attr) + + data->setting_changed.num_of_attr * sizeof(struct bt_avrcp_app_setting_attr_val); + break; + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + param_len += sizeof(data->addressed_player_changed); + break; + case BT_AVRCP_EVT_UIDS_CHANGED: + param_len += sizeof(data->uid_counter); + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + param_len += sizeof(data->absolute_volume); + break; + case BT_AVRCP_EVT_TRACK_REACHED_END: + case BT_AVRCP_EVT_TRACK_REACHED_START: + case BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED: + case BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED: + break; + default: + return -EINVAL; } - err = bt_avctp_disconnect(&(avrcp->browsing_session)); - if (err < 0) { - LOG_ERR("AVCTP browsing disconnect failed"); - return err; + if (net_buf_tailroom(buf) < param_len) { + LOG_ERR("Not enough space in net_buf"); + return -ENOMEM; + } + + net_buf_add_u8(buf, event_id); + switch (event_id) { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + if (data->play_status > BT_AVRCP_PLAYBACK_STATUS_REV_SEEK && + data->play_status != BT_AVRCP_PLAYBACK_STATUS_ERROR) { + LOG_ERR("Invalid playback status: %d", data->play_status); + return -EINVAL; + } + net_buf_add_u8(buf, data->play_status); + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + uint64_t identifier = sys_get_be64(data->identifier); + + net_buf_add_be64(buf, identifier); + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + net_buf_add_be32(buf, data->playback_pos); + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + if (data->battery_status > BT_AVRCP_BATTERY_STATUS_FULL) { + LOG_ERR("Invalid battery status: %d", data->battery_status); + return -EINVAL; + } + net_buf_add_u8(buf, data->battery_status); + break; + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + if (data->system_status > BT_AVRCP_SYSTEM_STATUS_UNPLUGGED) { + LOG_ERR("Invalid system status: %d", data->system_status); + return -EINVAL; + } + net_buf_add_u8(buf, data->system_status); + break; + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + net_buf_add_u8(buf, data->setting_changed.num_of_attr); + net_buf_add_mem(buf, data->setting_changed.attr_vals, + data->setting_changed.num_of_attr * sizeof(struct bt_avrcp_app_setting_attr_val)); + break; + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + net_buf_add_be16(buf, data->addressed_player_changed.player_id); + net_buf_add_be16(buf, data->addressed_player_changed.uid_counter); + break; + case BT_AVRCP_EVT_UIDS_CHANGED: + net_buf_add_be16(buf, data->uid_counter); + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + if (data->absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) { + LOG_ERR("Invalid absolute volume: %d", data->absolute_volume); + return -EINVAL; + } + net_buf_add_u8(buf, data->absolute_volume); + break; + case BT_AVRCP_EVT_TRACK_REACHED_END: + case BT_AVRCP_EVT_TRACK_REACHED_START: + case BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED: + case BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED: + break; + default: + return -EINVAL; } - return err; + return 0; } -#endif /* CONFIG_BT_AVRCP_BROWSING */ -int bt_avrcp_ct_get_cap(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t cap_id) +int bt_avrcp_tg_send_notification_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, + uint8_t event_id, struct bt_avrcp_event_data *data) { + uint8_t rsp_code = BT_AVRCP_RSP_INTERIM; struct net_buf *buf; - struct bt_avrcp_avc_pdu *pdu; int err; - if ((ct == NULL) || (ct->avrcp == NULL)) { + if ((tg == NULL) || (tg->avrcp == NULL)) { return -EINVAL; } - if (!IS_CT_ROLE_SUPPORTED()) { + if (!IS_TG_ROLE_SUPPORTED()) { return -ENOTSUP; } - buf = avrcp_create_vendor_pdu(ct->avrcp, BT_AVRCP_CTYPE_STATUS); - if (!buf) { - return -ENOMEM; + switch (status) { + case BT_AVRCP_STATUS_SUCCESS: + if (tg->interim_sent[event_id] == false) { + rsp_code = BT_AVRCP_RSP_INTERIM; + tg->interim_sent[event_id] = true; + } else { + rsp_code = BT_AVRCP_RSP_CHANGED; + tg->interim_sent[event_id] = false; + } + break; + case BT_AVRCP_STATUS_NOT_IMPLEMENTED: + rsp_code = BT_AVRCP_RSP_NOT_IMPLEMENTED; + break; + case BT_AVRCP_STATUS_IN_TRANSITION: + LOG_ERR("Not support IN_TRANSITION"); + return -EINVAL; + + default: + rsp_code = BT_AVRCP_RSP_REJECTED; + break; } - net_buf_add_be24(buf, BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG); - pdu = net_buf_add(buf, sizeof(*pdu)); - pdu->pdu_id = BT_AVRCP_PDU_ID_GET_CAPS; - BT_AVRCP_AVC_PDU_SET_PACKET_TYPE(pdu, BT_AVRVP_PKT_TYPE_SINGLE); - pdu->param_len = sys_cpu_to_be16(sizeof(cap_id)); - net_buf_add_u8(buf, cap_id); + buf = bt_avrcp_create_vendor_pdu(NULL); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } - err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + if (rsp_code == BT_AVRCP_RSP_REJECTED) { + if (net_buf_tailroom(buf) < sizeof(status)) { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); + return -ENOMEM; + } + net_buf_add_u8(buf, status); + } else { + err = build_notification_rsp_data(event_id, data, buf); + if (err < 0) { + net_buf_unref(buf); + return err; + } + } + + err = bt_avrcp_tg_send_vendor_rsp(tg, tid, BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, + rsp_code, buf); if (err < 0) { - LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + LOG_ERR("Failed to send notification response (err: %d)", err); net_buf_unref(buf); } return err; } -int bt_avrcp_ct_get_unit_info(struct bt_avrcp_ct *ct, uint8_t tid) +int bt_avrcp_tg_send_passthrough_rsp(struct bt_avrcp_tg *tg, uint8_t tid, bt_avrcp_rsp_t result, + struct net_buf *buf) { - struct net_buf *buf; + struct bt_avrcp_header *avrcp_hdr; int err; - uint8_t param[5]; - if ((ct == NULL) || (ct->avrcp == NULL)) { + if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) { return -EINVAL; } - if (!IS_CT_ROLE_SUPPORTED()) { + if (!IS_TG_ROLE_SUPPORTED()) { return -ENOTSUP; } - buf = avrcp_create_unit_pdu(ct->avrcp, BT_AVRCP_CTYPE_STATUS); - if (!buf) { + if (net_buf_headroom(buf) < sizeof(struct bt_avrcp_header)) { + LOG_ERR("Not enough headroom in buffer for bt_avrcp_header"); return -ENOMEM; } + avrcp_hdr = net_buf_push(buf, sizeof(struct bt_avrcp_header)); + memset(avrcp_hdr, 0, sizeof(struct bt_avrcp_header)); - memset(param, 0xFF, ARRAY_SIZE(param)); - net_buf_add_mem(buf, param, sizeof(param)); + avrcp_set_passthrough_header(avrcp_hdr, result); - err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + err = avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); if (err < 0) { LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); - net_buf_unref(buf); } return err; } -int bt_avrcp_ct_get_subunit_info(struct bt_avrcp_ct *ct, uint8_t tid) +static int bt_avrcp_tg_send_vendor_dependent_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t pdu_id + , uint8_t status, struct net_buf *buf) { - struct net_buf *buf; - uint8_t param[5]; + uint8_t rsp_code; + struct net_buf *status_buf; int err; - if ((ct == NULL) || (ct->avrcp == NULL)) { + if ((tg == NULL) || (tg->avrcp == NULL)) { return -EINVAL; } - if (!IS_CT_ROLE_SUPPORTED()) { + if (!IS_TG_ROLE_SUPPORTED()) { return -ENOTSUP; } - buf = avrcp_create_subunit_pdu(ct->avrcp, BT_AVRCP_CTYPE_STATUS); - if (!buf) { - return -ENOMEM; - } + rsp_code = bt_avrcp_status_to_rsp(pdu_id, status); + if (rsp_code == BT_AVRCP_RSP_REJECTED) { + status_buf = bt_avrcp_create_vendor_pdu(NULL); + if (status_buf == NULL) { + LOG_ERR("Failed to allocate status buffer"); + return -ENOBUFS; + } - memset(param, 0xFF, ARRAY_SIZE(param)); - param[0] = FIELD_PREP(GENMASK(6, 4), AVRCP_SUBUNIT_PAGE) | - FIELD_PREP(GENMASK(2, 0), AVRCP_SUBUNIT_EXTENSION_CODE); - net_buf_add_mem(buf, param, sizeof(param)); + if (AVRCP_STATUS_IS_REJECTED(status) == true) { + if (net_buf_tailroom(status_buf) < sizeof(status)) { + LOG_ERR("Not enough space in status net_buf"); + net_buf_unref(status_buf); + return -ENOMEM; + } + net_buf_add_u8(status_buf, status); + err = bt_avrcp_tg_send_vendor_rsp(tg, tid, pdu_id, rsp_code, status_buf); + if (err < 0) { + LOG_ERR("Failed to send vendor status PDU (err: %d)", err); + net_buf_unref(status_buf); + return err; + } + net_buf_unref(buf); + return 0; + } + } - err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + err = bt_avrcp_tg_send_vendor_rsp(tg, tid, pdu_id, rsp_code, buf); if (err < 0) { - LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); - net_buf_unref(buf); + LOG_ERR("Failed to send vendor PDU (err: %d)", err); } return err; } -int bt_avrcp_ct_passthrough(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t opid, uint8_t state, - const uint8_t *payload, uint8_t len) +int bt_avrcp_tg_send_get_caps_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, + struct net_buf *buf) { - struct net_buf *buf; int err; - if ((ct == NULL) || (ct->avrcp == NULL)) { - return -EINVAL; - } - - if (!IS_CT_ROLE_SUPPORTED()) { - return -ENOTSUP; - } - - buf = avrcp_create_passthrough_pdu(ct->avrcp, BT_AVRCP_CTYPE_CONTROL); - if (!buf) { - return -ENOMEM; - } - - net_buf_add_u8(buf, FIELD_PREP(BIT(7), state) | FIELD_PREP(GENMASK(6, 0), opid)); - net_buf_add_u8(buf, len); - if (len) { - net_buf_add_mem(buf, payload, len); - } - - err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, BT_AVRCP_PDU_ID_GET_CAPS, status, + buf); if (err < 0) { - LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); - net_buf_unref(buf); + LOG_ERR("Failed to send Get Capabilities response (err: %d)", err); } return err; } -#if defined(CONFIG_BT_AVRCP_BROWSING) -int bt_avrcp_ct_set_browsed_player(struct bt_avrcp_ct *ct, uint8_t tid, uint16_t player_id) +int bt_avrcp_tg_send_absolute_volume_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status, + uint8_t absolute_volume) { struct net_buf *buf; - struct bt_avrcp_avc_brow_pdu *pdu; int err; - if ((ct == NULL) || (ct->avrcp == NULL)) { + if (absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) { return -EINVAL; } - if (!IS_CT_ROLE_SUPPORTED()) { - return -ENOTSUP; - } - - if (ct->avrcp->browsing_session.br_chan.chan.conn == NULL) { - LOG_ERR("Browsing session not connected"); - return -ENOTCONN; - } - - buf = avrcp_create_browsing_pdu(ct->avrcp); + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); if (buf == NULL) { - return -ENOMEM; + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; } - if (net_buf_tailroom(buf) < sizeof(*pdu) + sizeof(player_id)) { - LOG_ERR("Not enough tailroom in buffer for browsing PDU"); + if (net_buf_tailroom(buf) < sizeof(absolute_volume)) { + LOG_ERR("Not enough space in net_buf"); net_buf_unref(buf); return -ENOMEM; } + net_buf_add_u8(buf, absolute_volume); - pdu = net_buf_add(buf, sizeof(*pdu)); - pdu->pdu_id = BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER; - pdu->param_len = sys_cpu_to_be16(sizeof(player_id)); - net_buf_add_be16(buf, player_id); - - err = avrcp_browsing_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, + status, buf); if (err < 0) { - LOG_ERR("Failed to send AVRCP browsing PDU (err: %d)", err); + LOG_ERR("Failed to absolute volume (err: %d)", err); net_buf_unref(buf); } return err; } -#endif /* CONFIG_BT_AVRCP_BROWSING */ -int bt_avrcp_ct_register_cb(const struct bt_avrcp_ct_cb *cb) +int bt_avrcp_tg_send_list_player_app_setting_attrs_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf) { - if (!cb) { - return -EINVAL; - } - if (avrcp_ct_cb) { - return -EALREADY; - } + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, + status, buf); +} - avrcp_ct_cb = cb; +int bt_avrcp_tg_send_list_player_app_setting_vals_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, + status, buf); +} - return 0; +int bt_avrcp_tg_send_get_curr_player_app_setting_val_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL + , status, buf); } -int bt_avrcp_tg_register_cb(const struct bt_avrcp_tg_cb *cb) +int bt_avrcp_tg_send_set_player_app_setting_val_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status) { - if (!cb) { - return -EINVAL; + struct net_buf *buf; + int err; + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; } - if (avrcp_tg_cb) { - return -EALREADY; + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, + status, buf); + if (err < 0) { + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); } + return err; +} - avrcp_tg_cb = cb; +int bt_avrcp_tg_send_get_player_app_setting_attr_text_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf) +{ + uint8_t pdu_id = BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT; - return 0; + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, pdu_id, status, buf); } -int bt_avrcp_tg_send_unit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid, - struct bt_avrcp_unit_info_rsp *rsp) +int bt_avrcp_tg_send_get_player_app_setting_val_text_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf) +{ + uint8_t pdu_id = BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT; + + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, pdu_id, status, buf); +} + +int bt_avrcp_tg_send_inform_displayable_char_set_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status) { struct net_buf *buf; int err; - if ((tg == NULL) || (tg->avrcp == NULL) || (rsp == NULL)) { - return -EINVAL; + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; } - if (!IS_TG_ROLE_SUPPORTED()) { - return -ENOTSUP; + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, + status, buf); + if (err < 0) { + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); } + return err; +} - buf = avrcp_create_unit_pdu(tg->avrcp, BT_AVRCP_RSP_STABLE); - if (!buf) { - LOG_WRN("Insufficient buffer"); - return -ENOMEM; - } +int bt_avrcp_tg_send_inform_batt_status_of_ct_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status) +{ + struct net_buf *buf; + int err; - /* The 0x7 is hard-coded in the spec. */ - net_buf_add_u8(buf, 0x07); - /* Add Unit Type info */ - net_buf_add_u8(buf, FIELD_PREP(GENMASK(7, 3), (rsp->unit_type))); - /* Company ID */ - net_buf_add_be24(buf, (rsp->company_id)); + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } - err = avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, + status, buf); if (err < 0) { - LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + LOG_ERR("Failed to send vendor dependent (err: %d)", err); net_buf_unref(buf); } return err; } -int bt_avrcp_tg_send_subunit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid) +int bt_avrcp_tg_send_get_element_attrs_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf) { - if ((tg == NULL) || (tg->avrcp == NULL)) { - return -EINVAL; - } - - if (!IS_TG_ROLE_SUPPORTED()) { - return -ENOTSUP; - } + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, + status, buf); +} - return bt_avrcp_send_subunit_info(tg->avrcp, tid, BT_AVRCP_RSP_STABLE); +int bt_avrcp_tg_send_get_play_status_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_GET_PLAY_STATUS, + status, buf); } -#if defined(CONFIG_BT_AVRCP_BROWSING) -int bt_avrcp_tg_send_set_browsed_player_rsp(struct bt_avrcp_tg *tg, uint8_t tid, - struct net_buf *buf) +int bt_avrcp_tg_send_set_addressed_player_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status) { - struct bt_avrcp_avc_brow_pdu *hdr; - uint16_t param_len; + struct net_buf *buf; int err; - if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) { - LOG_ERR("Invalid AVRCP target"); - return -EINVAL; + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; } - if (!IS_TG_ROLE_SUPPORTED()) { - LOG_ERR("Target role not supported"); - return -ENOTSUP; + if (AVRCP_STATUS_IS_REJECTED(status) == false) { + status = BT_AVRCP_STATUS_OPERATION_COMPLETED; } - if (tg->avrcp->browsing_session.br_chan.chan.conn == NULL) { - LOG_ERR("Browsing session not connected"); - return -ENOTCONN; + if (net_buf_tailroom(buf) < sizeof(status)) { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); + return -ENOMEM; } + net_buf_add_u8(buf, status); - param_len = buf->len; + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, + status, buf); + if (err < 0) { + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); + } + return err; +} + +int bt_avrcp_tg_send_play_item_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t status) +{ + struct net_buf *buf; + int err; + + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; + } - if (net_buf_headroom(buf) < sizeof(struct bt_avrcp_avc_brow_pdu)) { - LOG_ERR("Not enough headroom in buffer for bt_avrcp_avc_brow_pdu"); + if (AVRCP_STATUS_IS_REJECTED(status) == false) { + status = BT_AVRCP_STATUS_OPERATION_COMPLETED; + } + + if (net_buf_tailroom(buf) < sizeof(status)) { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); return -ENOMEM; } - hdr = net_buf_push(buf, sizeof(struct bt_avrcp_avc_brow_pdu)); - memset(hdr, 0, sizeof(struct bt_avrcp_avc_brow_pdu)); - hdr->pdu_id = BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER; - hdr->param_len = sys_cpu_to_be16(param_len); + net_buf_add_u8(buf, status); - err = avrcp_browsing_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, BT_AVRCP_PDU_ID_PLAY_ITEM, + status, buf); if (err < 0) { - LOG_ERR("Failed to send AVRCP browsing PDU (err: %d)", err); + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); } return err; } -#endif /* CONFIG_BT_AVRCP_BROWSING */ -int bt_avrcp_tg_send_passthrough_rsp(struct bt_avrcp_tg *tg, uint8_t tid, bt_avrcp_rsp_t result, - struct net_buf *buf) +int bt_avrcp_tg_send_add_to_now_playing_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t status) { - struct bt_avrcp_header *avrcp_hdr; + struct net_buf *buf; int err; - if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) { - return -EINVAL; + buf = bt_avrcp_create_vendor_pdu(&avrcp_vd_tx_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOBUFS; } - if (!IS_TG_ROLE_SUPPORTED()) { - return -ENOTSUP; + if (AVRCP_STATUS_IS_REJECTED(status) == false) { + status = BT_AVRCP_STATUS_OPERATION_COMPLETED; } - if (net_buf_headroom(buf) < sizeof(struct bt_avrcp_header)) { - LOG_ERR("Not enough headroom in buffer for bt_avrcp_header"); + if (net_buf_tailroom(buf) < sizeof(status)) { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); return -ENOMEM; } - avrcp_hdr = net_buf_push(buf, sizeof(struct bt_avrcp_header)); - memset(avrcp_hdr, 0, sizeof(struct bt_avrcp_header)); - - avrcp_set_passthrough_header(avrcp_hdr, result); + net_buf_add_u8(buf, status); - err = avrcp_send(tg->avrcp, buf, BT_AVCTP_RESPONSE, tid); + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, + status, buf); if (err < 0) { - LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + LOG_ERR("Failed to send vendor dependent (err: %d)", err); + net_buf_unref(buf); } return err; } diff --git a/subsys/bluetooth/host/classic/avrcp_internal.h b/subsys/bluetooth/host/classic/avrcp_internal.h index 295203b5eb577..d1d608dd74ebf 100644 --- a/subsys/bluetooth/host/classic/avrcp_internal.h +++ b/subsys/bluetooth/host/classic/avrcp_internal.h @@ -33,6 +33,11 @@ #define BT_L2CAP_PSM_AVRCP 0x0017 #define BT_L2CAP_PSM_AVRCP_BROWSING 0x001b +#define BT_AVRCP_HEADROOM \ + (sizeof(struct bt_l2cap_hdr) + \ + sizeof(struct bt_avctp_header_start) + \ + sizeof(struct bt_avrcp_header)) + #if defined(CONFIG_BT_AVRCP_BROWSING) #define AVRCP_BROWSING_ENABLE AVRCP_BROWSING_SUPPORT #else @@ -52,72 +57,37 @@ typedef enum __packed { } bt_avrcp_opcode_t; typedef enum __packed { - BT_AVRVP_PKT_TYPE_SINGLE = 0b00, - BT_AVRVP_PKT_TYPE_START = 0b01, - BT_AVRVP_PKT_TYPE_CONTINUE = 0b10, - BT_AVRVP_PKT_TYPE_END = 0b11, + BT_AVRCP_PKT_TYPE_SINGLE = 0b00, + BT_AVRCP_PKT_TYPE_START = 0b01, + BT_AVRCP_PKT_TYPE_CONTINUE = 0b10, + BT_AVRCP_PKT_TYPE_END = 0b11, } bt_avrcp_pkt_type_t; -typedef enum __packed { - /** Capabilities */ - BT_AVRCP_PDU_ID_GET_CAPS = 0x10, - - /** Player Application Settings */ - BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS = 0x11, - BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS = 0x12, - BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL = 0x13, - BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL = 0x14, - BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT = 0x15, - BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT = 0x16, - BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET = 0x17, - BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT = 0x18, - - /** Metadata Attributes for Current Media Item */ - BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS = 0x20, - - /** Notifications */ - BT_AVRCP_PDU_ID_GET_PLAY_STATUS = 0x30, - BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION = 0x31, - BT_AVRCP_PDU_ID_EVT_PLAYBACK_STATUS_CHANGED = 0x31, - BT_AVRCP_PDU_ID_EVT_TRACK_CHANGED = 0x31, - BT_AVRCP_PDU_ID_EVT_TRACK_REACHED_END = 0x31, - BT_AVRCP_PDU_ID_EVT_TRACK_REACHED_START = 0x31, - BT_AVRCP_PDU_ID_EVT_PLAYBACK_POS_CHANGED = 0x31, - BT_AVRCP_PDU_ID_EVT_BATT_STATUS_CHANGED = 0x31, - BT_AVRCP_PDU_ID_EVT_SYSTEM_STATUS_CHANGED = 0x31, - BT_AVRCP_PDU_ID_EVT_PLAYER_APP_SETTING_CHANGED = 0x31, - BT_AVRCP_PDU_ID_EVT_VOLUME_CHANGED = 0x31, - BT_AVRCP_PDU_ID_EVT_ADDRESSED_PLAYER_CHANGED = 0x31, - BT_AVRCP_PDU_ID_EVT_AVAILABLE_PLAYERS_CHANGED = 0x31, - BT_AVRCP_PDU_ID_EVT_UIDS_CHANGED = 0x31, - - /** Continuation */ - BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP = 0x40, - BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP = 0x41, - - /** Absolute Volume */ - BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME = 0x50, - - /** Media Player Selection */ - BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER = 0x60, - - /** Browsing */ - BT_AVRCP_PDU_ID_SET_BROWSED_PLAYER = 0x70, - BT_AVRCP_PDU_ID_GET_FOLDER_ITEMS = 0x71, - BT_AVRCP_PDU_ID_CHANGE_PATH = 0x72, - BT_AVRCP_PDU_ID_GET_ITEM_ATTRS = 0x73, - BT_AVRCP_PDU_ID_PLAY_ITEM = 0x74, - BT_AVRCP_PDU_ID_GET_TOTAL_NUMBER_OF_ITEMS = 0x75, - - /** Search */ - BT_AVRCP_PDU_ID_SEARCH = 0x80, - - /** Now Playing */ - BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING = 0x90, - - /** Error Response */ - BT_AVRCP_PDU_ID_GENERAL_REJECT = 0xa0, -} bt_avrcp_pdu_id_t; +typedef enum { + AVRCP_STATE_IDLE, + AVRCP_STATE_SENDING_CONTINUING, + AVRCP_STATE_ABORT_CONTINUING, +} avrcp_tg_rsp_state_t; + +struct bt_avrcp_ct_frag_reassembly_ctx { + uint8_t tid; + uint8_t rsp; +}; + +struct bt_avrcp_tg_vd_rsp_tx { + struct bt_avrcp_tg *tg; + uint16_t sent_len; + uint8_t tid; + uint8_t pdu_id; + uint8_t rsp; + avrcp_tg_rsp_state_t state; +} __packed; + +struct bt_avrcp_notify_registration { + uint8_t tid; + bool interim_received; + bt_avrcp_notification_cb_t cb; +}; struct bt_avrcp_req { uint8_t tid; @@ -131,14 +101,15 @@ struct bt_avrcp_header { uint8_t opcode; /**< Unit Info, Subunit Info, Vendor Dependent, or Pass Through */ } __packed; -struct bt_avrcp_avc_pdu { +struct bt_avrcp_avc_vendor_pdu { + uint8_t company_id[BT_AVRCP_COMPANY_ID_SIZE]; uint8_t pdu_id; uint8_t pkt_type; /**< [7:2]: Reserved, [1:0]: Packet Type */ uint16_t param_len; uint8_t param[]; } __packed; -struct bt_avrcp_avc_brow_pdu { +struct bt_avrcp_brow_pdu { uint8_t pdu_id; uint16_t param_len; uint8_t param[]; diff --git a/subsys/bluetooth/host/classic/l2cap_br.c b/subsys/bluetooth/host/classic/l2cap_br.c index d0929c8d2b1a8..bbbf546313b43 100644 --- a/subsys/bluetooth/host/classic/l2cap_br.c +++ b/subsys/bluetooth/host/classic/l2cap_br.c @@ -19,6 +19,7 @@ #include #include #include +#include #include #include "host/buf_view.h" diff --git a/subsys/bluetooth/host/classic/shell/avrcp.c b/subsys/bluetooth/host/classic/shell/avrcp.c index 9cf1e47b26797..27c2d039d1005 100644 --- a/subsys/bluetooth/host/classic/shell/avrcp.c +++ b/subsys/bluetooth/host/classic/shell/avrcp.c @@ -33,6 +33,9 @@ NET_BUF_POOL_DEFINE(avrcp_tx_pool, CONFIG_BT_MAX_CONN, BT_L2CAP_BUF_SIZE(CONFIG_BT_L2CAP_TX_MTU), CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); +NET_BUF_POOL_DEFINE(avrcp_big_tx_pool, CONFIG_BT_MAX_CONN, + 1024, CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + #define FOLDER_NAME_HEX_BUF_LEN 80 struct bt_avrcp_ct *default_ct; @@ -41,6 +44,91 @@ static bool avrcp_ct_registered; static bool avrcp_tg_registered; static uint8_t local_tid; static uint8_t tg_tid; +static uint8_t tg_cap_id; + +static const uint8_t supported_avrcp_events[] = { + BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED, + BT_AVRCP_EVT_TRACK_CHANGED, + BT_AVRCP_EVT_TRACK_REACHED_END, + BT_AVRCP_EVT_TRACK_REACHED_START, + BT_AVRCP_EVT_VOLUME_CHANGED, +}; + +struct bt_avrcp_media_attr_rsp { + uint32_t attr_id; + uint16_t charset_id; + uint16_t attr_len; + const uint8_t *attr_val; +} __packed; + +static struct bt_avrcp_media_attr_rsp test_media_attrs[] = { + { + .attr_id = BT_AVRCP_MEDIA_ATTR_TITLE, + .charset_id = BT_AVRCP_CHARSET_UTF8, + .attr_len = 11, + .attr_val = (const uint8_t *)"Test Title", + }, + { + .attr_id = BT_AVRCP_MEDIA_ATTR_ARTIST, + .charset_id = BT_AVRCP_CHARSET_UTF8, + .attr_len = 11, + .attr_val = "Test Artist", + }, + { + .attr_id = BT_AVRCP_MEDIA_ATTR_ALBUM, + .charset_id = BT_AVRCP_CHARSET_UTF8, + .attr_len = 10U, + .attr_val = (const uint8_t *)"Test Album", + }, + { + .attr_id = BT_AVRCP_MEDIA_ATTR_TRACK_NUMBER, + .charset_id = BT_AVRCP_CHARSET_UTF8, + .attr_len = 1, + .attr_val = (const uint8_t *)"1", + }, + { + .attr_id = BT_AVRCP_MEDIA_ATTR_TOTAL_TRACKS, + .charset_id = BT_AVRCP_CHARSET_UTF8, + .attr_len = 2U, + .attr_val = (const uint8_t *)"10", + }, + { + .attr_id = BT_AVRCP_MEDIA_ATTR_GENRE, + .charset_id = BT_AVRCP_CHARSET_UTF8, + .attr_len = 4U, + .attr_val = (const uint8_t *)"Rock", + }, + { + .attr_id = BT_AVRCP_MEDIA_ATTR_PLAYING_TIME, + .charset_id = BT_AVRCP_CHARSET_UTF8, + .attr_len = 6U, + .attr_val = (const uint8_t *)"240000", /* 4 minutes in milliseconds */ + }, +}; + +static struct bt_avrcp_media_attr_rsp large_media_attrs[] = { + { + .attr_id = BT_AVRCP_MEDIA_ATTR_TITLE, + .charset_id = BT_AVRCP_CHARSET_UTF8, + .attr_len = 200U, + .attr_val = (const uint8_t *) + "This is a long title that is designed to test the fragmentation of the AVRCP.", + }, + { + .attr_id = BT_AVRCP_MEDIA_ATTR_ARTIST, + .charset_id = BT_AVRCP_CHARSET_UTF8, + .attr_len = 250U, + .attr_val = (const uint8_t *) + "This is a very long artist name that is also designed to test fragmentation.", + }, + { + .attr_id = BT_AVRCP_MEDIA_ATTR_ALBUM, + .charset_id = BT_AVRCP_CHARSET_UTF8, + .attr_len = 100U, + .attr_val = (const uint8_t *) + "This is a long album name for testing fragmentation of AVRCP responses.", + }, +}; static uint8_t get_next_tid(void) { @@ -76,19 +164,40 @@ static void avrcp_ct_browsing_disconnected(struct bt_avrcp_ct *ct) bt_shell_print("AVRCP CT browsing disconnected"); } -static void avrcp_get_cap_rsp(struct bt_avrcp_ct *ct, uint8_t tid, - const struct bt_avrcp_get_cap_rsp *rsp) +static void avrcp_get_caps_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + struct net_buf *buf) { uint8_t i; + struct bt_avrcp_get_caps_rsp *rsp; + + bt_shell_print("GetCapabilities : status=0x%02x", status); + if (buf == NULL) { + return; + } + if (buf->len < sizeof(*rsp)) { + bt_shell_print("Invalid response data length"); + return; + } + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); switch (rsp->cap_id) { case BT_AVRCP_CAP_COMPANY_ID: for (i = 0; i < rsp->cap_cnt; i++) { + if (buf->len < BT_AVRCP_COMPANY_ID_SIZE) { + bt_shell_print("incompleted message for CAP COMPANY ID "); + break; + } + net_buf_pull_mem(buf, BT_AVRCP_COMPANY_ID_SIZE); bt_shell_print("Remote CompanyID = 0x%06x", sys_get_be24(&rsp->cap[BT_AVRCP_COMPANY_ID_SIZE * i])); } break; case BT_AVRCP_CAP_EVENTS_SUPPORTED: + if (buf->len < rsp->cap_cnt) { + bt_shell_print("incompleted message for supported EventID "); + break; + } + net_buf_pull_mem(buf, rsp->cap_cnt); for (i = 0; i < rsp->cap_cnt; i++) { bt_shell_print("Remote supported EventID = 0x%02x", rsp->cap[i]); } @@ -131,6 +240,293 @@ static void avrcp_passthrough_rsp(struct bt_avrcp_ct *ct, uint8_t tid, bt_avrcp_ } } +static void avrcp_get_element_attrs_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + struct net_buf *buf) +{ + const struct bt_avrcp_get_element_attrs_rsp *rsp; + struct bt_avrcp_media_attr *attr; + uint8_t i = 0; + const char *attr_name; + + bt_shell_print("GetElementAttributes : status=0x%02x", status); + if (buf == NULL) { + return; + } + + if (buf->len < sizeof(*rsp)) { + bt_shell_print("Invalid GetElementAttributes response length: %d", buf->len); + return; + } + + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + + bt_shell_print("AVRCP GetElementAttributes response received, tid=0x%02x, num_attrs=%u", + tid, rsp->num_attrs); + + while (buf->len > 0) { + if (buf->len < sizeof(struct bt_avrcp_media_attr)) { + bt_shell_print("incompleted message"); + break; + } + attr = net_buf_pull_mem(buf, sizeof(struct bt_avrcp_media_attr)); + + attr->attr_id = sys_be32_to_cpu(attr->attr_id); + attr->charset_id = sys_be16_to_cpu(attr->charset_id); + attr->attr_len = sys_be16_to_cpu(attr->attr_len); + if (buf->len < attr->attr_len) { + bt_shell_print("incompleted message for attr_len"); + break; + } + net_buf_pull_mem(buf, attr->attr_len); + switch (attr->attr_id) { + case BT_AVRCP_MEDIA_ATTR_TITLE: + attr_name = "TITLE"; + break; + case BT_AVRCP_MEDIA_ATTR_ARTIST: + attr_name = "ARTIST"; + break; + case BT_AVRCP_MEDIA_ATTR_ALBUM: + attr_name = "ALBUM"; + break; + case BT_AVRCP_MEDIA_ATTR_TRACK_NUMBER: + attr_name = "TRACK_NUMBER"; + break; + case BT_AVRCP_MEDIA_ATTR_TOTAL_TRACKS: + attr_name = "TOTAL_TRACKS"; + break; + case BT_AVRCP_MEDIA_ATTR_GENRE: + attr_name = "GENRE"; + break; + case BT_AVRCP_MEDIA_ATTR_PLAYING_TIME: + attr_name = "PLAYING_TIME"; + break; + default: + attr_name = "UNKNOWN"; + break; + } + bt_shell_print(" Attr[%u]: ID=0x%08x (%s), charset=0x%04x, len=%u", + i, attr->attr_id, attr_name, attr->charset_id, attr->attr_len); + + /* Print attribute value (truncate if too long for display) */ + if (attr->attr_len > 0) { + uint16_t print_len = (attr->attr_len > 64) ? 64 : attr->attr_len; + char value_str[65]; + + memcpy(value_str, attr->attr_val, print_len); + value_str[print_len] = '\0'; + bt_shell_print(" Value: \"%s\"%s", value_str, + (attr->attr_len > 64) ? "..." : ""); + } + i++; + } +} + +static void avrcp_get_element_attrs_req(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_get_element_attrs_cmd *cmd; + uint16_t expected_len = 0; + uint64_t identifier; + + tg_tid = tid; + if (buf->len < sizeof(*cmd)) { + bt_shell_print("Invalid GetElementAttributes command length: %d", buf->len); + goto err_rsp; + } + + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + + expected_len = cmd->num_attrs * sizeof(uint32_t); + if (buf->len < expected_len) { + bt_shell_print("Invalid GetElementAttributes command attribute IDs length: %d, " + "expected %d", + buf->len, expected_len); + goto err_rsp; + } + net_buf_pull_mem(buf, expected_len); + identifier = sys_get_be64(cmd->identifier); + bt_shell_print("AVRCP GetElementAttributes command received, tid=0x%02x", tid); + bt_shell_print(" Identifier: 0x%016llx", identifier); + bt_shell_print(" Num attrs requested: %u %s", cmd->num_attrs, + (cmd->num_attrs == 0U) ? "(all attributes)" : ""); + + if (cmd->num_attrs > 0U) { + bt_shell_print(" Requested attribute IDs:"); + for (uint8_t i = 0U; i < cmd->num_attrs; i++) { + const char *attr_name; + + cmd->attr_ids[i] = sys_be32_to_cpu(cmd->attr_ids[i]); + switch (cmd->attr_ids[i]) { + case BT_AVRCP_MEDIA_ATTR_TITLE: + attr_name = "TITLE"; + break; + case BT_AVRCP_MEDIA_ATTR_ARTIST: + attr_name = "ARTIST"; + break; + case BT_AVRCP_MEDIA_ATTR_ALBUM: + attr_name = "ALBUM"; + break; + case BT_AVRCP_MEDIA_ATTR_TRACK_NUMBER: + attr_name = "TRACK_NUMBER"; + break; + case BT_AVRCP_MEDIA_ATTR_TOTAL_TRACKS: + attr_name = "TOTAL_TRACKS"; + break; + case BT_AVRCP_MEDIA_ATTR_GENRE: + attr_name = "GENRE"; + break; + case BT_AVRCP_MEDIA_ATTR_PLAYING_TIME: + attr_name = "PLAYING_TIME"; + break; + default: + attr_name = "UNKNOWN"; + break; + } + bt_shell_print(" [%u]: 0x%08x (%s)", i, cmd->attr_ids[i], attr_name); + } + } + +err_rsp: + return; +} + +static void avrcp_notification_cb_rsp(uint8_t event_id, struct bt_avrcp_event_data *data) +{ + const char *type_str = "CHANGED"; + + bt_shell_print("AVRCP notification_rsp: type=%s, event_id=0x%02x", type_str, event_id); + + switch (event_id) { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + bt_shell_print(" PLAYBACK_STATUS_CHANGED: status=0x%02x", data->play_status); + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + uint64_t identifier; + + memcpy(&identifier, data->identifier, sizeof(identifier)); + bt_shell_print("TRACK_CHANGED: identifier value: %llx\n", identifier); + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + bt_shell_print(" PLAYBACK_POS_CHANGED: pos=%u", data->playback_pos); + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + bt_shell_print(" BATT_STATUS_CHANGED: battery_status=0x%02x", data->battery_status); + break; + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + bt_shell_print(" SYSTEM_STATUS_CHANGED: system_status=0x%02x", data->system_status); + break; + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + bt_shell_print(" PLAYER_APP_SETTING_CHANGED: num_of_attr=%u", + data->setting_changed.num_of_attr); + break; + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + bt_shell_print(" ADDRESSED_PLAYER_CHANGED: player_id=0x%04x, uid_counter=0x%04x", + data->addressed_player_changed.player_id, + data->addressed_player_changed.uid_counter); + break; + case BT_AVRCP_EVT_UIDS_CHANGED: + bt_shell_print(" UIDS_CHANGED: uid_counter=0x%04x", data->uid_counter); + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + bt_shell_print(" VOLUME_CHANGED: absolute_volume=0x%02x", data->absolute_volume); + break; + case BT_AVRCP_EVT_TRACK_REACHED_END: + bt_shell_print(" TRACK_REACHED_END"); + break; + case BT_AVRCP_EVT_TRACK_REACHED_START: + bt_shell_print(" TRACK_REACHED_START"); + break; + case BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED: + bt_shell_print(" AVAILABLE_PLAYERS_CHANGED"); + break; + case BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED: + bt_shell_print(" NOW_PLAYING_CONTENT_CHANGED"); + break; + default: + bt_shell_print(" Unknown event_id: 0x%02x", event_id); + break; + } +} + +static void avrcp_register_notification_req(struct bt_avrcp_tg *tg, uint8_t tid, + bt_avrcp_evt_t event_id, + uint32_t interval) +{ + tg_tid = tid; + bt_shell_print("AVRCP register_notification_req: tid=0x%02x, event_id=0x%02x, interval=%u", + tid, event_id, interval); +} + +static void avrcp_set_absolute_volume_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + uint8_t absolute_volume) +{ + bt_shell_print("AVRCP set absolute volume rsp: tid=0x%02x, rsp=0x%02x, volume=0x%02x", + tid, status, absolute_volume); +} +static void avrcp_set_absolute_volume_req(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t absolute_volume) +{ + bt_shell_print("AVRCP set_absolute_volume_req: tid=0x%02x, absolute_volume=0x%02x", + tid, absolute_volume); + tg_tid = tid; +} + +static void avrcp_notification_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status, + uint8_t event_id, struct bt_avrcp_event_data *data) +{ + bt_shell_print("AVRCP notification rsp: tid=0x%02x, status=0x%02x, event_id=0x%02x", + tid, status, event_id); + switch (event_id) { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + bt_shell_print(" PLAYBACK_STATUS_CHANGED: status=0x%02x", data->play_status); + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + uint64_t identifier; + + memcpy(&identifier, data->identifier, sizeof(identifier)); + bt_shell_print("TRACK_CHANGED: identifier value: %llx\n", identifier); + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + bt_shell_print(" PLAYBACK_POS_CHANGED: pos=%u", data->playback_pos); + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + bt_shell_print(" BATT_STATUS_CHANGED: battery_status=0x%02x", data->battery_status); + break; + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + bt_shell_print(" SYSTEM_STATUS_CHANGED: system_status=0x%02x", data->system_status); + break; + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + bt_shell_print(" PLAYER_APP_SETTING_CHANGED: num_of_attr=%u", + data->setting_changed.num_of_attr); + break; + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + bt_shell_print(" ADDRESSED_PLAYER_CHANGED: player_id=0x%04x, uid_counter=0x%04x", + data->addressed_player_changed.player_id, + data->addressed_player_changed.uid_counter); + break; + case BT_AVRCP_EVT_UIDS_CHANGED: + bt_shell_print(" UIDS_CHANGED: uid_counter=0x%04x", data->uid_counter); + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + bt_shell_print(" VOLUME_CHANGED: absolute_volume=0x%02x", data->absolute_volume); + break; + case BT_AVRCP_EVT_TRACK_REACHED_END: + bt_shell_print(" TRACK_REACHED_END"); + break; + case BT_AVRCP_EVT_TRACK_REACHED_START: + bt_shell_print(" TRACK_REACHED_START"); + break; + case BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED: + bt_shell_print(" AVAILABLE_PLAYERS_CHANGED"); + break; + case BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED: + bt_shell_print(" NOW_PLAYING_CONTENT_CHANGED"); + break; + default: + bt_shell_print(" Unknown event_id: 0x%02x", event_id); + break; + } +} + static void avrcp_browsed_player_rsp(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) { @@ -185,317 +581,2228 @@ static void avrcp_browsed_player_rsp(struct bt_avrcp_ct *ct, uint8_t tid, } } -static struct bt_avrcp_ct_cb app_avrcp_ct_cb = { - .connected = avrcp_ct_connected, - .disconnected = avrcp_ct_disconnected, - .browsing_connected = avrcp_ct_browsing_connected, - .browsing_disconnected = avrcp_ct_browsing_disconnected, - .get_cap_rsp = avrcp_get_cap_rsp, - .unit_info_rsp = avrcp_unit_info_rsp, - .subunit_info_rsp = avrcp_subunit_info_rsp, - .passthrough_rsp = avrcp_passthrough_rsp, - .browsed_player_rsp = avrcp_browsed_player_rsp, -}; - -static void avrcp_tg_connected(struct bt_conn *conn, struct bt_avrcp_tg *tg) +static const char *player_app_attr_name(uint8_t id) { - bt_shell_print("AVRCP TG connected"); - default_tg = tg; + switch (id) { + case 0x01: return "EQUALIZER"; + case 0x02: return "REPEAT_MODE"; + case 0x03: return "SHUFFLE"; + case 0x04: return "SCAN"; + default: return "UNKNOWN"; + } } -static void avrcp_tg_disconnected(struct bt_avrcp_tg *tg) +static void avrcp_list_player_app_setting_attrs_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf) { - bt_shell_print("AVRCP TG disconnected"); - default_tg = NULL; -} + struct bt_avrcp_list_app_setting_attr_rsp *rsp; -static void avrcp_tg_browsing_connected(struct bt_conn *conn, struct bt_avrcp_tg *tg) -{ - bt_shell_print("AVRCP TG browsing connected"); -} + bt_shell_print("listplayerappsettingattrs : status=0x%02x", status); + if (buf == NULL) { + return; + } -static void avrcp_unit_info_req(struct bt_avrcp_tg *tg, uint8_t tid) -{ - bt_shell_print("AVRCP unit info request received"); - tg_tid = tid; -} + if (buf->len < sizeof(*rsp)) { + bt_shell_print("Invalid response data length"); + return; + } + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); -static void avrcp_subunit_info_req(struct bt_avrcp_tg *tg, uint8_t tid) -{ - bt_shell_print("AVRCP subunit info request received"); - tg_tid = tid; -} + while (buf->len > 0) { + uint8_t attr = net_buf_pull_u8(buf); -static void avrcp_tg_browsing_disconnected(struct bt_avrcp_tg *tg) -{ - bt_shell_print("AVRCP TG browsing disconnected"); -} + bt_shell_print(" attr =0x%02x (%s)", attr, player_app_attr_name(attr)); + if (rsp->num_attrs > 0) { + rsp->num_attrs--; + } else { + bt_shell_warn("num_attrs is mismatched with received data"); + break; + } + } -static void avrcp_set_browsed_player_req(struct bt_avrcp_tg *tg, uint8_t tid, - uint16_t player_id) -{ - bt_shell_print("AVRCP set browsed player request received, player_id = %u", player_id); - tg_tid = tid; + if (rsp->num_attrs > 0) { + bt_shell_print("num_attrs mismatch: expected 0, got %u", rsp->num_attrs); + } } -static void avrcp_passthrough_req(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf) +static void avrcp_list_player_app_setting_vals_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf) { - struct bt_avrcp_passthrough_cmd *cmd; - struct bt_avrcp_passthrough_opvu_data *opvu = NULL; - const char *state_str; - bt_avrcp_opid_t opid; - bt_avrcp_button_state_t state; + struct bt_avrcp_list_player_app_setting_vals_rsp *rsp; - tg_tid = tid; - cmd = net_buf_pull_mem(buf, sizeof(*cmd)); - opid = BT_AVRCP_PASSTHROUGH_GET_STATE(cmd); - state = BT_AVRCP_PASSTHROUGH_GET_OPID(cmd); + bt_shell_print("listplayerappsettingvals : status=0x%02x", status); + if (buf == NULL) { + return; + } - if (cmd->data_len > 0U) { - if (buf->len < sizeof(struct bt_avrcp_passthrough_opvu_data)) { - bt_shell_print("Invalid passthrough data: buf length = %u, need >= %zu", - buf->len, sizeof(struct bt_avrcp_passthrough_opvu_data)); - return; - } + if (buf->len < sizeof(*rsp)) { + bt_shell_print("Invalid response data length"); + return; + } + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + while (buf->len > 0) { + uint8_t val = net_buf_pull_u8(buf); - if (buf->len < cmd->data_len) { - bt_shell_print("Invalid passthrough cmd data length: %u, buf length = %u", - cmd->data_len, buf->len); + bt_shell_print(" val : %u", val); + if (rsp->num_values > 0) { + rsp->num_values--; + } else { + bt_shell_warn("num_values is mismatched with received data"); + break; } - opvu = net_buf_pull_mem(buf, sizeof(*opvu)); } - /* Convert button state to string */ - state_str = (state == BT_AVRCP_BUTTON_PRESSED) ? "PRESSED" : "RELEASED"; - - bt_shell_print("AVRCP passthrough command received: opid = 0x%02x (%s), tid=0x%02x, len=%u", - opid, state_str, tid, cmd->data_len); - - if (cmd->data_len > 0U && opvu != NULL) { - bt_shell_print("company_id: 0x%06x", sys_get_be24(opvu->company_id)); - bt_shell_print("opid_vu: 0x%04x", sys_be16_to_cpu(opvu->opid_vu)); + if (rsp->num_values > 0) { + bt_shell_print("num_values mismatch: expected 0, got %u", rsp->num_values); } - } -static struct bt_avrcp_tg_cb app_avrcp_tg_cb = { - .connected = avrcp_tg_connected, - .disconnected = avrcp_tg_disconnected, - .browsing_connected = avrcp_tg_browsing_connected, - .browsing_disconnected = avrcp_tg_browsing_disconnected, - .unit_info_req = avrcp_unit_info_req, - .subunit_info_req = avrcp_subunit_info_req, - .set_browsed_player_req = avrcp_set_browsed_player_req, - .passthrough_req = avrcp_passthrough_req, -}; - -static int register_ct_cb(const struct shell *sh) +static void avrcp_get_curr_player_app_setting_val_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf) { - int err; + struct bt_avrcp_get_curr_player_app_setting_val_rsp *rsp; + struct bt_avrcp_app_setting_attr_val attr_vals = {0}; - if (avrcp_ct_registered) { - return 0; + bt_shell_print("getcurrplayerappsettingval : status=0x%02x", status); + if (buf == NULL) { + return; } - err = bt_avrcp_ct_register_cb(&app_avrcp_ct_cb); - if (!err) { - avrcp_ct_registered = true; - shell_print(sh, "AVRCP CT callbacks registered"); - } else { - shell_print(sh, "failed to register AVRCP CT callbacks"); + if (buf->len < sizeof(*rsp)) { + bt_shell_print("Invalid response data length"); + return; } + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + while (buf->len > 0) { - return err; -} + if (buf->len < sizeof(struct bt_avrcp_app_setting_attr_val)) { + bt_shell_print("incompleted message"); + break; + } + attr_vals.attr_id = net_buf_pull_u8(buf); + attr_vals.value_id = net_buf_pull_u8(buf); -static int cmd_register_ct_cb(const struct shell *sh, int32_t argc, char *argv[]) -{ - if (avrcp_ct_registered) { - shell_print(sh, "already registered"); - return 0; + bt_shell_print(" attr_id :%u val %u", attr_vals.attr_id, attr_vals.value_id); + if (rsp->num_attrs > 0) { + rsp->num_attrs--; + } else { + bt_shell_warn("num_attrs %d is mismatched with received", rsp->num_attrs); + break; + } } - register_ct_cb(sh); + if (rsp->num_attrs > 0) { + bt_shell_print("num_attrs mismatch: expected 0, got %u", rsp->num_attrs); + } +} - return 0; +static void avrcp_set_player_app_setting_val_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status) +{ + bt_shell_print("SetPlayerAppSettingValue rsp: tid=0x%02x, status=%02x", tid, status); } -static int register_tg_cb(const struct shell *sh) +static void avrcp_get_player_app_setting_attr_text_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf) { - int err; + struct bt_avrcp_get_player_app_setting_attr_text_rsp *rsp; + struct bt_avrcp_app_setting_attr_text *attr_text; - if (avrcp_tg_registered) { - return 0; + bt_shell_print("getplayerappsettingattrtext : status=0x%02x", status); + if (buf == NULL) { + return; } - err = bt_avrcp_tg_register_cb(&app_avrcp_tg_cb); - if (!err) { - avrcp_tg_registered = true; + if (buf->len < sizeof(*rsp)) { + bt_shell_print("Invalid response data length"); + return; + } + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + while (buf->len > 0) { + + if (buf->len < sizeof(struct bt_avrcp_app_setting_attr_text)) { + bt_shell_print("incompleted message"); + break; + } + attr_text = net_buf_pull_mem(buf, sizeof(struct bt_avrcp_app_setting_attr_text)); + attr_text->charset_id = sys_be16_to_cpu(attr_text->charset_id); + + bt_shell_print("attr=0x%02x, charset=0x%04x, text_len=%u", attr_text->attr_id, + attr_text->charset_id, attr_text->text_len); + + if (buf->len < attr_text->text_len) { + bt_shell_print("incompleted message for attr_text"); + break; + } + net_buf_pull_mem(buf, attr_text->text_len); + + if (attr_text->charset_id == BT_AVRCP_CHARSET_UTF8) { + bt_shell_print("Raw attr_text:"); + for (int i = 0; i < attr_text->text_len; i++) { + bt_shell_print("%c", attr_text->text[i]); + } + } else { + bt_shell_print(" Get attr_text : "); + bt_shell_hexdump(attr_text->text, attr_text->text_len); + } + + if (rsp->num_attrs > 0) { + rsp->num_attrs--; + } else { + bt_shell_warn("num_attrs %d is mismatched with received", rsp->num_attrs); + break; + } + } + + if (rsp->num_attrs > 0) { + bt_shell_print("num_attrs mismatch: expected 0, got %u", rsp->num_attrs); + } +} + +static void avrcp_get_player_app_setting_val_text_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf) +{ + struct bt_avrcp_get_player_app_setting_val_text_rsp *rsp; + struct bt_avrcp_app_setting_val_text *val_text; + + bt_shell_print("getplayerappsettingvaltext : status=0x%02x", status); + if (buf == NULL) { + return; + } + + if (buf->len < sizeof(*rsp)) { + bt_shell_print("Invalid response data length"); + return; + } + + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + + while (buf->len > 0) { + if (buf->len < sizeof(struct bt_avrcp_app_setting_val_text)) { + bt_shell_print("incompleted message"); + break; + } + + val_text = net_buf_pull_mem(buf, sizeof(struct bt_avrcp_app_setting_val_text)); + val_text->charset_id = sys_be16_to_cpu(val_text->charset_id); + + bt_shell_print("val=0x%02x, charset=0x%04x, text_len=%u", + val_text->value_id, val_text->charset_id, val_text->text_len); + + if (buf->len < val_text->text_len) { + bt_shell_print("incompleted message for val_text"); + break; + } + + /* Take a pointer to the text bytes before advancing the buffer */ + uint8_t *text = net_buf_pull_mem(buf, val_text->text_len); + + if (val_text->charset_id == BT_AVRCP_CHARSET_UTF8) { + bt_shell_print("Raw val_text:"); + for (uint16_t i = 0; i < val_text->text_len; i++) { + bt_shell_print("%c", text[i]); + } + } else { + bt_shell_print(" Get val_text : "); + bt_shell_hexdump(text, val_text->text_len); + } + + if (rsp->num_values > 0) { + rsp->num_values--; + } else { + bt_shell_warn("num_values %d is mismatched with received", rsp->num_values); + break; + } + } + + if (rsp->num_values > 0) { + bt_shell_print("num_values mismatch: expected 0, got %u", rsp->num_values); + } +} + +static void avrcp_set_addressed_player_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status) +{ + bt_shell_print("SetAddressedPlayer rsp: tid=0x%02x, status=%02x", tid, status); +} + +static void avrcp_play_item_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status) +{ + bt_shell_print("Play item rsp: tid=0x%02x, status=%02x", tid, status); +} + +static void avrcp_add_to_now_playing_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status) +{ + bt_shell_print("Add to now playing rsp: tid=0x%02x, status=%02x", tid, status); +} + +static void avrcp_inform_batt_status_of_ct_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t status) +{ + bt_shell_print("Inform battstatus of ct rsp: tid=0x%02x, status=%02x", tid, status); +} + +static void avrcp_get_play_status_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t status, struct net_buf *buf) +{ + struct bt_avrcp_get_play_status_rsp *rsp; + + bt_shell_print("getplaystatus : status=0x%02x", status); + if (buf == NULL) { + return; + } + if (buf->len < sizeof(*rsp)) { + bt_shell_print("Invalid response data length"); + return; + } + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + + uint32_t song_len = sys_be32_to_cpu(rsp->song_length); + uint32_t song_pos = sys_be32_to_cpu(rsp->song_position); + uint8_t play_status = rsp->play_status; + + bt_shell_print("GetPlayStatus: len=%u ms, pos=%u ms, status=0x%02x", + song_len, song_pos, play_status); + + switch (play_status) { + case BT_AVRCP_PLAYBACK_STATUS_STOPPED: + bt_shell_print(" status: STOPPED"); + break; + case BT_AVRCP_PLAYBACK_STATUS_PLAYING: + bt_shell_print(" status: PLAYING"); + break; + case BT_AVRCP_PLAYBACK_STATUS_PAUSED: + bt_shell_print(" status: PAUSED"); + break; + case BT_AVRCP_PLAYBACK_STATUS_FWD_SEEK: + bt_shell_print(" status: FWD_SEEK"); + break; + case BT_AVRCP_PLAYBACK_STATUS_REV_SEEK: + bt_shell_print(" status: REV_SEEK"); + break; + case BT_AVRCP_PLAYBACK_STATUS_ERROR: + bt_shell_print(" status: ERROR"); + break; + default: + break; + } + + if (song_len != 0xFFFFFFFFU && song_pos > song_len) { + bt_shell_warn("song_pos %u > song_len %u", song_pos, song_len); + } + + if (buf->len > 0U) { + bt_shell_warn("trailing bytes in GetPlayStatus rsp: %u", buf->len); + } +} + +static struct bt_avrcp_ct_cb app_avrcp_ct_cb = { + .connected = avrcp_ct_connected, + .disconnected = avrcp_ct_disconnected, + .browsing_connected = avrcp_ct_browsing_connected, + .browsing_disconnected = avrcp_ct_browsing_disconnected, + .get_caps_rsp = avrcp_get_caps_rsp, + .unit_info_rsp = avrcp_unit_info_rsp, + .subunit_info_rsp = avrcp_subunit_info_rsp, + .passthrough_rsp = avrcp_passthrough_rsp, + .browsed_player_rsp = avrcp_browsed_player_rsp, + .notification_rsp = avrcp_notification_rsp, + .set_absolute_volume_rsp = avrcp_set_absolute_volume_rsp, + .get_element_attrs_rsp = avrcp_get_element_attrs_rsp, + .list_player_app_setting_attrs_rsp = avrcp_list_player_app_setting_attrs_rsp, + .list_player_app_setting_vals_rsp = avrcp_list_player_app_setting_vals_rsp, + .get_curr_player_app_setting_val_rsp = avrcp_get_curr_player_app_setting_val_rsp, + .set_player_app_setting_val_rsp = avrcp_set_player_app_setting_val_rsp, + .get_player_app_setting_attr_text_rsp = avrcp_get_player_app_setting_attr_text_rsp, + .get_player_app_setting_val_text_rsp = avrcp_get_player_app_setting_val_text_rsp, + .get_play_status_rsp = avrcp_get_play_status_rsp, + .inform_batt_status_of_ct_rsp = avrcp_inform_batt_status_of_ct_rsp, + .set_addressed_player_rsp = avrcp_set_addressed_player_rsp, + .play_item_rsp = avrcp_play_item_rsp, + .add_to_now_playing_rsp = avrcp_add_to_now_playing_rsp, +}; + +static void avrcp_tg_connected(struct bt_conn *conn, struct bt_avrcp_tg *tg) +{ + bt_shell_print("AVRCP TG connected"); + default_tg = tg; +} + +static void avrcp_tg_disconnected(struct bt_avrcp_tg *tg) +{ + bt_shell_print("AVRCP TG disconnected"); + default_tg = NULL; +} + +static void avrcp_tg_browsing_connected(struct bt_conn *conn, struct bt_avrcp_tg *tg) +{ + bt_shell_print("AVRCP TG browsing connected"); +} + +static void avrcp_unit_info_req(struct bt_avrcp_tg *tg, uint8_t tid) +{ + bt_shell_print("AVRCP unit info request received"); + tg_tid = tid; +} + +static void avrcp_subunit_info_req(struct bt_avrcp_tg *tg, uint8_t tid) +{ + bt_shell_print("AVRCP subunit info request received"); + tg_tid = tid; +} + +static void avrcp_get_caps_req(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t cap_id) +{ + const char *cap_type_str; + + /* Convert capability ID to string for display */ + switch (cap_id) { + case BT_AVRCP_CAP_COMPANY_ID: + cap_type_str = "COMPANY_ID"; + break; + case BT_AVRCP_CAP_EVENTS_SUPPORTED: + cap_type_str = "EVENTS_SUPPORTED"; + break; + default: + cap_type_str = "UNKNOWN"; + break; + } + + bt_shell_print("AVRCP get capabilities command received: cap_id 0x%02x (%s), tid = 0x%02x", + cap_id, cap_type_str, tid); + + /* Store the transaction ID and capability ID for manual response testing */ + tg_tid = tid; + tg_cap_id = cap_id; +} + +static void avrcp_tg_browsing_disconnected(struct bt_avrcp_tg *tg) +{ + bt_shell_print("AVRCP TG browsing disconnected"); +} + +static void avrcp_set_browsed_player_req(struct bt_avrcp_tg *tg, uint8_t tid, + uint16_t player_id) +{ + bt_shell_print("AVRCP set browsed player request received, player_id = %u", player_id); + tg_tid = tid; +} + +static void avrcp_passthrough_req(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_passthrough_cmd *cmd; + struct bt_avrcp_passthrough_opvu_data *opvu = NULL; + const char *state_str; + bt_avrcp_opid_t opid; + bt_avrcp_button_state_t state; + + tg_tid = tid; + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + opid = BT_AVRCP_PASSTHROUGH_GET_STATE(cmd); + state = BT_AVRCP_PASSTHROUGH_GET_OPID(cmd); + + if (cmd->data_len > 0U) { + if (buf->len < sizeof(struct bt_avrcp_passthrough_opvu_data)) { + bt_shell_print("Invalid passthrough data: buf len %u < expected_len %zu", + buf->len, sizeof(struct bt_avrcp_passthrough_opvu_data)); + return; + } + + if (buf->len < cmd->data_len) { + bt_shell_print("Invalid passthrough cmd data length: %u, buf length = %u", + cmd->data_len, buf->len); + } + opvu = net_buf_pull_mem(buf, sizeof(*opvu)); + } + + /* Convert button state to string */ + state_str = (state == BT_AVRCP_BUTTON_PRESSED) ? "PRESSED" : "RELEASED"; + + bt_shell_print("AVRCP passthrough command received: opid = 0x%02x (%s), tid=0x%02x, len=%u", + opid, state_str, tid, cmd->data_len); + + if (cmd->data_len > 0U && opvu != NULL) { + bt_shell_print("company_id: 0x%06x", sys_get_be24(opvu->company_id)); + bt_shell_print("opid_vu: 0x%04x", sys_be16_to_cpu(opvu->opid_vu)); + } +} + +static void avrcp_list_player_app_setting_attrs_req(struct bt_avrcp_tg *tg, uint8_t tid) +{ + tg_tid = tid; + bt_shell_print("AVRCP TG: ListPlayerAppSettingAttributes, tid=0x%02x", tid); +} + +static void avrcp_list_player_app_setting_vals_req(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t attr_id) +{ + tg_tid = tid; + bt_shell_print("AVRCP TG: List App Setting vals, tid=0x%02x, attr_id=0x%02x", tid, attr_id); +} + +static void avrcp_get_curr_player_app_setting_val_req(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_get_curr_player_app_setting_val_cmd *cmd; + + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + + tg_tid = tid; + + while (buf->len > 0) { + uint8_t attr_ids = net_buf_pull_u8(buf); + + bt_shell_print(" attr_ids: %u", attr_ids); + if (cmd->num_attrs > 0) { + cmd->num_attrs--; + } else { + bt_shell_warn("num_attrs is mismatched with received data"); + break; + } + } + + if (cmd->num_attrs > 0) { + bt_shell_print("num_values mismatch: expected 0, got %u", cmd->num_attrs); + } +} + +static void avrcp_set_player_app_setting_val_req(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf) +{ + struct bt_avrcp_set_player_app_setting_val_cmd *cmd; + + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + + tg_tid = tid; + if (buf->len < (uint16_t)(cmd->num_attrs * 2U)) { + bt_shell_print("Invalid pairs: n=%u, remain=%u", cmd->num_attrs, buf->len); + return; + } + + bt_shell_print("AVRCP TG: SetPlayerApplicationSettingValue, tid=0x%02x, num=%u", tid, + cmd->num_attrs); + for (uint8_t i = 0; i < cmd->num_attrs; i++) { + cmd->attr_vals[i].attr_id = net_buf_pull_u8(buf); + cmd->attr_vals[i].value_id = net_buf_pull_u8(buf); + bt_shell_print(" pair[%u]: attr=0x%02x val=0x%02x", i, cmd->attr_vals[i].attr_id, + cmd->attr_vals[i].value_id); + } +} + +static void avrcp_get_player_app_setting_attr_text_req(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_get_player_app_setting_attr_text_cmd *cmd; + + tg_tid = tid; + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + + if (buf->len < cmd->num_attrs) { + bt_shell_print("Invalid AttrText list: n=%u remain=%u", cmd->num_attrs, buf->len); + return; + } + bt_shell_print("GetPlayerAppSettingAttributeText, tid=0x%02x, num=%u", tid, cmd->num_attrs); + for (uint8_t i = 0; i < cmd->num_attrs; i++) { + bt_shell_print(" attr_id[%u]=0x%02x", i, net_buf_pull_u8(buf)); + } + +} + +static void avrcp_get_player_app_setting_val_text_req(struct bt_avrcp_tg *tg, + uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_get_player_app_setting_val_text_cmd *cmd; + + tg_tid = tid; + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + + if (buf->len < cmd->num_values) { + bt_shell_print("Invalid ValText list: n=%u remain=%u", + cmd->num_values, buf->len); + return; + } + + bt_shell_print("GetPlayerAppSettingValueText, tid=0x%02x, attr=0x%02x, num=%u", + tid, cmd->attr_id, cmd->num_values); + + for (uint8_t i = 0; i < cmd->num_values; i++) { + bt_shell_print(" val_id[%u]=0x%02x", i, net_buf_pull_u8(buf)); + } +} + +static void avrcp_set_addressed_player_req(struct bt_avrcp_tg *tg, uint8_t tid, uint16_t player_id) +{ + bt_shell_print("AVRCP set addressed player request received, player_id = %u", player_id); + tg_tid = tid; +} + +static void avrcp_inform_batt_status_of_ct_req(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t battery_status) +{ + bt_shell_print("AVRCP inform batt status of ct request received, battery_status = %u", + battery_status); + tg_tid = tid; +} + +static void avrcp_get_play_status_req(struct bt_avrcp_tg *tg, uint8_t tid) +{ + bt_shell_print("AVRCP get play status request received"); + tg_tid = tid; +} + +static void avrcp_play_item_req(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_play_item_cmd *cmd; + + tg_tid = tid; + + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + + uint64_t uid = sys_get_be64(cmd->uid); + uint16_t uid_counter = sys_be16_to_cpu(cmd->uid_counter); + + bt_shell_print("PlayItem, tid=0x%02x, scope=0x%02x, uid=0x%016llx, uid_counter=0x%04x", + tid, cmd->scope, uid, uid_counter); + + if (buf->len > 0U) { + bt_shell_warn("trailing bytes in PlayItem req: %u", buf->len); + } +} + +static void avrcp_add_to_now_playing_req(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf) +{ + struct bt_avrcp_add_to_now_playing_cmd *cmd; + + tg_tid = tid; + + cmd = net_buf_pull_mem(buf, sizeof(*cmd)); + + uint64_t uid = sys_get_be64(cmd->uid); + uint16_t uid_counter = sys_be16_to_cpu(cmd->uid_counter); + + bt_shell_print("AddToNowPlaying tid=%u, scope=0x%02x, uid=0x%016llx, uid_counter=0x%04x", + tid, cmd->scope, (unsigned long long)uid, uid_counter); + + if (buf->len > 0U) { + bt_shell_warn("trailing bytes in AddToNowPlaying req: %u", buf->len); + } +} + +static struct bt_avrcp_tg_cb app_avrcp_tg_cb = { + .connected = avrcp_tg_connected, + .disconnected = avrcp_tg_disconnected, + .browsing_connected = avrcp_tg_browsing_connected, + .browsing_disconnected = avrcp_tg_browsing_disconnected, + .unit_info_req = avrcp_unit_info_req, + .subunit_info_req = avrcp_subunit_info_req, + .get_caps_req = avrcp_get_caps_req, + .set_browsed_player_req = avrcp_set_browsed_player_req, + .register_notification_req = avrcp_register_notification_req, + .set_absolute_volume_req = avrcp_set_absolute_volume_req, + .passthrough_req = avrcp_passthrough_req, + .get_element_attrs_req = avrcp_get_element_attrs_req, + .list_player_app_setting_attrs_req = avrcp_list_player_app_setting_attrs_req, + .list_player_app_setting_vals_req = avrcp_list_player_app_setting_vals_req, + .get_curr_player_app_setting_val_req = avrcp_get_curr_player_app_setting_val_req, + .set_player_app_setting_val_req = avrcp_set_player_app_setting_val_req, + .get_player_app_setting_attr_text_req = avrcp_get_player_app_setting_attr_text_req, + .get_player_app_setting_val_text_req = avrcp_get_player_app_setting_val_text_req, + .inform_batt_status_of_ct_req = avrcp_inform_batt_status_of_ct_req, + .get_play_status_req = avrcp_get_play_status_req, + .set_addressed_player_req = avrcp_set_addressed_player_req, + .play_item_req = avrcp_play_item_req, + .add_to_now_playing_req = avrcp_add_to_now_playing_req, +}; + +static int register_ct_cb(const struct shell *sh) +{ + int err; + + if (avrcp_ct_registered) { + return 0; + } + + err = bt_avrcp_ct_register_cb(&app_avrcp_ct_cb); + if (!err) { + avrcp_ct_registered = true; + shell_print(sh, "AVRCP CT callbacks registered"); + } else { + shell_print(sh, "failed to register AVRCP CT callbacks"); + } + + return err; +} + +static int cmd_register_ct_cb(const struct shell *sh, int32_t argc, char *argv[]) +{ + if (avrcp_ct_registered) { + shell_print(sh, "already registered"); + return 0; + } + + register_ct_cb(sh); + + return 0; +} + +static int register_tg_cb(const struct shell *sh) +{ + int err; + + if (avrcp_tg_registered) { + return 0; + } + + err = bt_avrcp_tg_register_cb(&app_avrcp_tg_cb); + if (!err) { + avrcp_tg_registered = true; shell_print(sh, "AVRCP TG callbacks registered"); } else { - shell_print(sh, "failed to register AVRCP TG callbacks"); + shell_print(sh, "failed to register AVRCP TG callbacks"); + } + + return err; +} + +static int cmd_register_tg_cb(const struct shell *sh, int32_t argc, char *argv[]) +{ + if (avrcp_tg_registered) { + shell_print(sh, "already registered"); + return 0; + } + + register_tg_cb(sh); + + return 0; +} + +static int cmd_connect(const struct shell *sh, int32_t argc, char *argv[]) +{ + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { + return -ENOEXEC; + } + + if (!default_conn) { + shell_error(sh, "BR/EDR not connected"); + return -ENOEXEC; + } + + err = bt_avrcp_connect(default_conn); + if (err < 0) { + shell_error(sh, "fail to connect AVRCP"); + } + + return 0; +} + +static int cmd_disconnect(const struct shell *sh, int32_t argc, char *argv[]) +{ + if ((!avrcp_ct_registered) && (!avrcp_tg_registered)) { + shell_error(sh, "Neither CT nor TG callbacks are registered."); + return -ENOEXEC; + } + + if (!default_conn) { + shell_print(sh, "Not connected"); + return -ENOEXEC; + } + + if ((default_ct != NULL) || (default_tg != NULL)) { + bt_avrcp_disconnect(default_conn); + } else { + shell_error(sh, "AVRCP is not connected"); + } + + return 0; +} + +static int cmd_browsing_connect(const struct shell *sh, int32_t argc, char *argv[]) +{ + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_conn == NULL) { + shell_error(sh, "BR/EDR not connected"); + return -ENOEXEC; + } + + err = bt_avrcp_browsing_connect(default_conn); + if (err < 0) { + shell_error(sh, "fail to connect AVRCP browsing"); + } else { + shell_print(sh, "AVRCP browsing connect request sent"); + } + + return err; +} + +static int cmd_browsing_disconnect(const struct shell *sh, int32_t argc, char *argv[]) +{ + int err; + + if (default_conn == NULL) { + shell_print(sh, "Not connected"); + return -ENOEXEC; + } + + if ((default_ct != NULL) || (default_tg != NULL)) { + err = bt_avrcp_browsing_disconnect(default_conn); + if (err < 0) { + shell_error(sh, "fail to disconnect AVRCP browsing"); + } else { + shell_print(sh, "AVRCP browsing disconnect request sent"); + } + } else { + shell_error(sh, "AVRCP is not connected"); + err = -ENOEXEC; + } + + return err; +} + +static int cmd_get_unit_info(const struct shell *sh, int32_t argc, char *argv[]) +{ + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct != NULL) { + bt_avrcp_ct_get_unit_info(default_ct, get_next_tid()); + } else { + shell_error(sh, "AVRCP is not connected"); + } + + return 0; +} + +static int cmd_send_unit_info_rsp(const struct shell *sh, int32_t argc, char *argv[]) +{ + struct bt_avrcp_unit_info_rsp rsp; + int err; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { + return -ENOEXEC; + } + + rsp.unit_type = BT_AVRCP_SUBUNIT_TYPE_PANEL; + rsp.company_id = BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG; + + if (default_tg != NULL) { + err = bt_avrcp_tg_send_unit_info_rsp(default_tg, tg_tid, &rsp); + if (!err) { + shell_print(sh, "AVRCP send unit info response"); + } else { + shell_error(sh, "Failed to send unit info response"); + } + } else { + shell_error(sh, "AVRCP is not connected"); + } + + return 0; +} + +static int cmd_send_passthrough_rsp(const struct shell *sh, int32_t argc, char *argv[]) +{ + struct bt_avrcp_passthrough_rsp *rsp; + struct bt_avrcp_passthrough_opvu_data *opvu = NULL; + bt_avrcp_opid_t opid = 0; + bt_avrcp_button_state_t state; + uint16_t vu_opid = 0; + bool is_op_vu = true; + struct net_buf *buf; + char *endptr; + unsigned long val; + int err; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; + } + + buf = bt_avrcp_create_pdu(NULL); + if (buf == NULL) { + shell_error(sh, "Failed to allocate buffer for AVRCP passthrough response"); + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < sizeof(struct bt_avrcp_passthrough_rsp)) { + shell_error(sh, "Not enough tailroom in buffer for passthrough rsp"); + goto failed; + } + rsp = net_buf_add(buf, sizeof(*rsp)); + + if (!strcmp(argv[1], "op")) { + is_op_vu = false; + } else if (!strcmp(argv[1], "opvu")) { + is_op_vu = true; + } else { + shell_error(sh, "Invalid response: %s", argv[1]); + goto failed; + } + + if (!strcmp(argv[2], "play")) { + opid = BT_AVRCP_OPID_PLAY; + vu_opid = (uint16_t)opid; + } else if (!strcmp(argv[2], "pause")) { + opid = BT_AVRCP_OPID_PAUSE; + vu_opid = (uint16_t)opid; + } else { + /* Try to parse as hex value */ + val = strtoul(argv[2], &endptr, 16); + if (*endptr != '\0' || val > 0xFFFFU) { + shell_error(sh, "Invalid opid: %s", argv[2]); + goto failed; + } + if (is_op_vu) { + vu_opid = (uint16_t)val; + } else { + opid = (bt_avrcp_opid_t)val; + } + } + + if (!strcmp(argv[3], "pressed")) { + state = BT_AVRCP_BUTTON_PRESSED; + } else if (!strcmp(argv[3], "released")) { + state = BT_AVRCP_BUTTON_RELEASED; + } else { + shell_error(sh, "Invalid state: %s", argv[3]); + goto failed; + } + + if (is_op_vu) { + opid = BT_AVRCP_OPID_VENDOR_UNIQUE; + } + + BT_AVRCP_PASSTHROUGH_SET_STATE_OPID(rsp, state, opid); + if (is_op_vu) { + if (net_buf_tailroom(buf) < sizeof(*opvu)) { + shell_error(sh, "Not enough tailroom in buffer for opvu"); + goto failed; + } + opvu = net_buf_add(buf, sizeof(*opvu)); + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, opvu->company_id); + opvu->opid_vu = sys_cpu_to_be16(vu_opid); + rsp->data_len = sizeof(*opvu); + } else { + rsp->data_len = 0; + } + + err = bt_avrcp_tg_send_passthrough_rsp(default_tg, tg_tid, BT_AVRCP_RSP_ACCEPTED, buf); + if (err < 0) { + shell_error(sh, "Failed to send passthrough response: %d", err); + goto failed; + } else { + shell_print(sh, "Passthrough opid=0x%02x, state=%s", opid, argv[2]); + return 0; + } + +failed: + net_buf_unref(buf); + return -ENOEXEC; +} + +static int cmd_send_subunit_info_rsp(const struct shell *sh, int32_t argc, char *argv[]) +{ + int err; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_tg != NULL) { + err = bt_avrcp_tg_send_subunit_info_rsp(default_tg, tg_tid); + if (err == 0) { + shell_print(sh, "AVRCP send subunit info response"); + } else { + shell_error(sh, "Failed to send subunit info response"); + } + } else { + shell_error(sh, "AVRCP is not connected"); + } + + return 0; +} + +static int cmd_send_get_caps_rsp(const struct shell *sh, int32_t argc, char *argv[]) +{ + struct bt_avrcp_get_caps_rsp *rsp; + struct net_buf *buf; + int err; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; + } + + buf = bt_avrcp_create_vendor_pdu(NULL); + if (buf == NULL) { + shell_error(sh, "Failed to allocate buffer for AVRCP get caps rsp"); + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < sizeof(*rsp)) { + shell_error(sh, "Not enough tailroom in buffer for get caps rsp"); + goto failed; + } + rsp = net_buf_add(buf, sizeof(*rsp)); + + /* Initialize response structure */ + rsp->cap_id = tg_cap_id; + + switch (tg_cap_id) { + case BT_AVRCP_CAP_COMPANY_ID: + /* Send Bluetooth SIG company ID as example */ + rsp->cap_cnt = 1; + if (net_buf_tailroom(buf) < BT_AVRCP_COMPANY_ID_SIZE) { + shell_error(sh, "Not enough tailroom for company ID capability rsp"); + goto failed; + } + net_buf_add(buf, BT_AVRCP_COMPANY_ID_SIZE); + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, rsp->cap); + shell_print(sh, "Sending company ID capability rsp: 0x%06x", + BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG); + break; + + case BT_AVRCP_CAP_EVENTS_SUPPORTED: + rsp->cap_cnt = ARRAY_SIZE(supported_avrcp_events); + if (net_buf_tailroom(buf) < rsp->cap_cnt) { + shell_error(sh, "Not enough tailroom for events supported capability rsp"); + goto failed; + } + net_buf_add_mem(buf, supported_avrcp_events, rsp->cap_cnt); + shell_print(sh, "Sending events supported capability rsp with %u events", + rsp->cap_cnt); + break; + + default: + shell_error(sh, "Unknown capability ID: 0x%02x", tg_cap_id); + return -EINVAL; + } + + err = bt_avrcp_tg_send_get_caps_rsp(default_tg, tg_tid, BT_AVRCP_STATUS_SUCCESS, buf); + if (err < 0) { + shell_error(sh, "Failed to send get capabilities response: %d", err); + } else { + shell_print(sh, "Get capabilities response sent successfully"); + } + + return err; +failed: + net_buf_unref(buf); + return -ENOEXEC; +} + +static int cmd_get_subunit_info(const struct shell *sh, int32_t argc, char *argv[]) +{ + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct != NULL) { + bt_avrcp_ct_get_subunit_info(default_ct, get_next_tid()); + } else { + shell_error(sh, "AVRCP is not connected"); + } + + return 0; +} + +static int cmd_passthrough(const struct shell *sh, bt_avrcp_opid_t opid, const uint8_t *payload, + uint8_t len) +{ + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct != NULL) { + bt_avrcp_ct_passthrough(default_ct, get_next_tid(), opid, BT_AVRCP_BUTTON_PRESSED, + payload, len); + bt_avrcp_ct_passthrough(default_ct, get_next_tid(), opid, BT_AVRCP_BUTTON_RELEASED, + payload, len); + } else { + shell_error(sh, "AVRCP is not connected"); + } + + return 0; +} + +static int cmd_play(const struct shell *sh, int32_t argc, char *argv[]) +{ + return cmd_passthrough(sh, BT_AVRCP_OPID_PLAY, NULL, 0); +} + +static int cmd_pause(const struct shell *sh, int32_t argc, char *argv[]) +{ + return cmd_passthrough(sh, BT_AVRCP_OPID_PAUSE, NULL, 0); +} + +static int cmd_get_caps(const struct shell *sh, int32_t argc, char *argv[]) +{ + const char *cap_id; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP is not connected"); + return 0; + } + + cap_id = argv[1]; + if (!strcmp(cap_id, "company")) { + bt_avrcp_ct_get_caps(default_ct, get_next_tid(), BT_AVRCP_CAP_COMPANY_ID); + } else if (!strcmp(cap_id, "events")) { + bt_avrcp_ct_get_caps(default_ct, get_next_tid(), BT_AVRCP_CAP_EVENTS_SUPPORTED); + } + + return 0; +} + +static int cmd_get_element_attrs(const struct shell *sh, int32_t argc, char *argv[]) +{ + struct bt_avrcp_get_element_attrs_cmd *cmd; + struct net_buf *buf; + uint64_t identifier = 0; + char *endptr; + unsigned long val; + int err = 0; + int i; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP CT is not connected"); + return -ENOEXEC; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "Failed to allocate vendor dependent command buffer"); + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < sizeof(*cmd) + (7 * sizeof(uint32_t))) { + shell_error(sh, "Not enough tailroom in buffer for browsed player rsp"); + goto failed; + } + cmd = net_buf_add(buf, sizeof(*cmd)); + cmd->num_attrs = 0U; + + /* Parse optional identifier */ + if (argc > 1) { + identifier = sys_cpu_to_be64(strtoull(argv[1], &endptr, 16)); + if (*endptr != '\0') { + shell_error(sh, "Invalid identifier: %s", argv[1]); + goto failed; + } + memcpy(cmd->identifier, &identifier, sizeof(identifier)); + } + + /* Parse optional attribute IDs */ + if (argc > 2 && identifier != 0) { + for (i = 2; i < argc && i < 9; i++) { /* Max 7 attributes + cmd + identifier */ + val = strtoul(argv[i], &endptr, 16); + if (*endptr != '\0' || val > 0xFFFFFFFFUL) { + shell_error(sh, "Invalid attribute ID: %s", argv[i]); + goto failed; + } + net_buf_add_be32(buf, (uint32_t)val); + cmd->num_attrs++; + } + } + + shell_print(sh, "Requesting element attributes: identifier=0x%016llx, num_attrs=%u", + identifier, cmd->num_attrs); + + err = bt_avrcp_ct_get_element_attrs(default_ct, get_next_tid(), buf); + if (err < 0) { + shell_error(sh, "Failed to send get element attrs command: %d", err); + goto failed; + } else { + shell_print(sh, "AVRCP CT get element attrs command sent"); + return 0; + } +failed: + net_buf_unref(buf); + return err; +} + +static int cmd_send_get_element_attrs_rsp(const struct shell *sh, int32_t argc, char *argv[]) +{ + + struct bt_avrcp_get_element_attrs_rsp *rsp; + struct bt_avrcp_media_attr *attr; + uint16_t total_size = 0; + bool use_large_attrs = false; + struct net_buf *buf; + char *endptr; + int err = 0; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_big_tx_pool); + if (buf == NULL) { + shell_error(sh, "Failed to allocate vendor dependent command buffer"); + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < sizeof(*rsp)) { + shell_error(sh, "Not enough tailroom in buffer for get element attrs rsp"); + goto failed; + } + + rsp = net_buf_add(buf, sizeof(*rsp)); + if (argc > 1) { + use_large_attrs = strtoull(argv[1], &endptr, 16); + if (*endptr != '\0') { + shell_error(sh, "Invalid identifier: %s", argv[1]); + goto failed; + } + } + + /* Determine which attribute set to use */ + if (use_large_attrs) { + rsp->num_attrs = ARRAY_SIZE(large_media_attrs); + for (int i = 0; i < rsp->num_attrs; i++) { + total_size += sizeof(*attr) + large_media_attrs[i].attr_len; + } + + if (net_buf_tailroom(buf) < total_size) { + shell_error(sh, "Not enough tailroom in buffer for large attrs"); + goto failed; + } + + for (int i = 0; i < rsp->num_attrs; i++) { + attr = net_buf_add(buf, sizeof(struct bt_avrcp_media_attr)); + attr->attr_id = sys_cpu_to_be32(large_media_attrs[i].attr_id); + attr->charset_id = sys_cpu_to_be16(large_media_attrs[i].charset_id); + attr->attr_len = sys_cpu_to_be16(large_media_attrs[i].attr_len); + net_buf_add(buf, large_media_attrs[i].attr_len); + memset(attr->attr_val, 0x0, large_media_attrs[i].attr_len); + memcpy(attr->attr_val, large_media_attrs[i].attr_val, + strlen(large_media_attrs[i].attr_val)); + } + + shell_print(sh, "Sending large Attributes response (%u attrs) for fragment test", + rsp->num_attrs); + } else { + rsp->num_attrs = ARRAY_SIZE(test_media_attrs); + for (int i = 0; i < rsp->num_attrs; i++) { + total_size += sizeof(*attr) + test_media_attrs[i].attr_len; + } + + if (net_buf_tailroom(buf) < total_size) { + shell_error(sh, "Not enough tailroom in buffer for large attrs"); + goto failed; + } + + for (int i = 0; i < rsp->num_attrs; i++) { + attr = net_buf_add(buf, sizeof(*attr)); + attr->attr_id = sys_cpu_to_be32(test_media_attrs[i].attr_id); + attr->charset_id = sys_cpu_to_be16(test_media_attrs[i].charset_id); + attr->attr_len = sys_cpu_to_be16(test_media_attrs[i].attr_len); + net_buf_add_mem(buf, test_media_attrs[i].attr_val, + test_media_attrs[i].attr_len); + } + shell_print(sh, "Sending standard GetElementAttributes response (%u attrs)", + rsp->num_attrs); + } + + err = bt_avrcp_tg_send_get_element_attrs_rsp(default_tg, tg_tid, BT_AVRCP_STATUS_SUCCESS, + buf); + if (err < 0) { + shell_error(sh, "Failed to send GetElementAttributes response: %d", err); + goto failed; + } else { + shell_print(sh, "GetElementAttributes response sent successfully"); + return 0; + } + +failed: + net_buf_unref(buf); + return -ENOEXEC; +} + +static int cmd_ct_register_notification(const struct shell *sh, int argc, char *argv[]) +{ + uint8_t event_id; + uint32_t interval = 0U; + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP CT is not connected"); + return -ENOEXEC; + } + + event_id = (uint8_t)strtoul(argv[1], NULL, 0); + if (argc > 2) { + interval = (uint32_t)strtoul(argv[2], NULL, 0); + } + + err = bt_avrcp_ct_register_notification(default_ct, get_next_tid(), event_id, + interval, avrcp_notification_cb_rsp); + if (err < 0) { + shell_error(sh, "Failed to send register_notification: %d", err); + } else { + shell_print(sh, "Sent register notification event_id=0x%02x", event_id); + } + return err; +} + +static int cmd_tg_send_notification_rsp(const struct shell *sh, int argc, char *argv[]) +{ + struct bt_avrcp_event_data data; + bool interim = false; + struct bt_avrcp_app_setting_attr_val attr_vals[1]; + uint8_t event_id = (uint8_t)strtoul(argv[1], NULL, 0); + bt_avrcp_status_t status; + uint64_t identifier; + char *endptr; + int err; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; + } + + memset(&data, 0, sizeof(data)); + + if (strcmp(argv[2], "changed") == 0) { + status = BT_AVRCP_STATUS_SUCCESS; + } else if (strcmp(argv[2], "interim") == 0) { + status = BT_AVRCP_STATUS_SUCCESS; + interim = true; + } else { + shell_error(sh, "Invalid type: %s (expected: changed|interim)", argv[2]); + status = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto done; + } + + switch (event_id) { + case BT_AVRCP_EVT_PLAYBACK_STATUS_CHANGED: + if (argc < 4) { + data.play_status = BT_AVRCP_PLAYBACK_STATUS_PLAYING; + } else { + data.play_status = (uint8_t)strtoul(argv[3], NULL, 0); + } + break; + case BT_AVRCP_EVT_TRACK_CHANGED: + if (argc < 4) { + identifier = interim ? 111111 : 1; + sys_put_be64(identifier, data.identifier); + } else { + identifier = strtoull(argv[3], &endptr, 16); + if (*endptr != '\0') { + shell_error(sh, "Invalid identifier: %s", argv[3]); + } + sys_put_be64(identifier, data.identifier); + } + break; + case BT_AVRCP_EVT_PLAYBACK_POS_CHANGED: + if (argc < 4) { + data.playback_pos = 1000; + } else { + data.playback_pos = (uint32_t)strtoul(argv[3], NULL, 0); + } + break; + case BT_AVRCP_EVT_BATT_STATUS_CHANGED: + if (argc < 4) { + data.battery_status = BT_AVRCP_BATTERY_STATUS_NORMAL; + } else { + data.battery_status = (uint8_t)strtoul(argv[3], NULL, 0); + } + break; + case BT_AVRCP_EVT_SYSTEM_STATUS_CHANGED: + if (argc < 4) { + data.system_status = BT_AVRCP_SYSTEM_STATUS_POWER_ON; + } else { + data.system_status = (uint8_t)strtoul(argv[3], NULL, 0); + } + break; + case BT_AVRCP_EVT_PLAYER_APP_SETTING_CHANGED: + data.setting_changed.num_of_attr = 1; + data.setting_changed.attr_vals = &attr_vals[0]; + data.setting_changed.attr_vals[0].attr_id = 1; + data.setting_changed.attr_vals[0].value_id = 1; + break; + case BT_AVRCP_EVT_ADDRESSED_PLAYER_CHANGED: + if (argc < 5) { + data.addressed_player_changed.player_id = 0x0001; /* Default player ID */ + data.addressed_player_changed.uid_counter = 0x0001; /* Default UID counter*/ + } else { + data.addressed_player_changed.player_id = strtoul(argv[3], NULL, 0); + data.addressed_player_changed.uid_counter = strtoul(argv[4], NULL, 0); + } + break; + case BT_AVRCP_EVT_UIDS_CHANGED: + if (argc < 4) { + data.uid_counter = 1; + } else { + data.uid_counter = (uint16_t)strtoul(argv[3], NULL, 0); + } + break; + case BT_AVRCP_EVT_VOLUME_CHANGED: + if (argc < 4) { + data.absolute_volume = 10; + } else { + data.absolute_volume = (uint8_t)strtoul(argv[3], NULL, 0); + } + break; + case BT_AVRCP_EVT_TRACK_REACHED_END: + case BT_AVRCP_EVT_TRACK_REACHED_START: + case BT_AVRCP_EVT_AVAILABLE_PLAYERS_CHANGED: + case BT_AVRCP_EVT_NOW_PLAYING_CONTENT_CHANGED: + break; + default: + shell_error(sh, "Unknown event_id: 0x%02x", event_id); + status = BT_AVRCP_STATUS_INVALID_PARAMETER; + goto done; + } + +done: + err = bt_avrcp_tg_send_notification_rsp(default_tg, tg_tid, status, event_id, &data); + if (err < 0) { + shell_error(sh, "Failed to send notification rsp: %d", err); + } else { + shell_print(sh, "Sent notification rsp event_id=0x%02x type=%s", + event_id, (interim) ? "interim" : "changed"); + } + return err; +} + +static int cmd_ct_set_absolute_volume(const struct shell *sh, int argc, char *argv[]) +{ + uint8_t absolute_volume; + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP CT is not connected"); + return -ENOEXEC; + } + + absolute_volume = (uint8_t)strtoul(argv[1], NULL, 0); + + err = bt_avrcp_ct_set_absolute_volume(default_ct, get_next_tid(), absolute_volume); + if (err < 0) { + shell_error(sh, "Failed to set absolute volume: %d", err); + } else { + shell_print(sh, "set absolute volume" + " absolute_volume=0x%02x", absolute_volume); + } + return err; +} + +static int cmd_ct_list_app_attrs(const struct shell *sh, int argc, char *argv[]) +{ + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP CT is not connected"); + return -ENOEXEC; + } + + err = bt_avrcp_ct_list_player_app_setting_attrs(default_ct, get_next_tid()); + if (err < 0) { + shell_error(sh, "list player app setting attrs failed: %d", err); + } else { + shell_print(sh, "Sent list player app setting attrs"); + } + + return err; +} + +static int cmd_ct_list_app_vals(const struct shell *sh, int argc, char *argv[]) +{ + uint8_t attr; + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP CT is not connected"); + return -ENOEXEC; + } + + attr = (uint8_t)strtoul(argv[1], NULL, 0); + + err = bt_avrcp_ct_list_player_app_setting_vals(default_ct, get_next_tid(), attr); + if (err < 0) { + shell_error(sh, "Failed to send list player app setting vals: %d", err); + return -ENOEXEC; + } + + shell_print(sh, "Sent list player app setting vals attr=0x%02x", attr); + return 0; +} + +static int cmd_ct_get_app_curr(const struct shell *sh, int argc, char *argv[]) +{ + struct bt_avrcp_get_curr_player_app_setting_val_cmd *cmd; + struct net_buf *buf; + size_t expected_len; + int err, i; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP CT is not connected"); + return -ENOEXEC; + } + + expected_len = 1 + (size_t)((argc > 1) ? (argc - 1) : 0); + + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "Failed to allocate vendor dependent command buffer"); + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < expected_len) { + shell_error(sh, "Not enough tailroom in buffer"); + goto failed; + } + cmd = net_buf_add(buf, sizeof(*cmd)); + + cmd->num_attrs = (argc > 1) ? (uint8_t)(argc - 1) : 0U; + for (i = 1; i < argc; i++) { + net_buf_add_u8(buf, (uint8_t)strtoul(argv[i], NULL, 0)); + } + + err = bt_avrcp_ct_get_curr_player_app_setting_val(default_ct, get_next_tid(), buf); + if (err < 0) { + shell_error(sh, "Failed to send get_curr_player_app_setting_val: %d", err); + goto failed; + } + + shell_print(sh, "Sent get_curr_player_app_setting_val num=%u", cmd->num_attrs); + return 0; + +failed: + net_buf_unref(buf); + return -ENOEXEC; +} + +static int cmd_ct_set_app_val(const struct shell *sh, int argc, char *argv[]) +{ + struct bt_avrcp_set_player_app_setting_val_cmd *cmd; + struct net_buf *buf; + size_t expected_len; + uint8_t pairs; + int err, i; + + if ((argc < 3) || (((argc - 1) % 2) != 0)) { + shell_error(sh, "usage: set_app_val [ ...]"); + return -ENOEXEC; + } + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP CT is not connected"); + return -ENOEXEC; + } + + pairs = (uint8_t)((argc - 1) / 2); + expected_len = 1 + (size_t)pairs * 2U; + + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "Failed to allocate vendor dependent command buffer"); + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < expected_len) { + shell_error(sh, "Not enough tailroom in buffer"); + err = -ENOMEM; + goto failed; + } + cmd = net_buf_add(buf, expected_len); + cmd->num_attrs = pairs; + + for (i = 1; i < argc; i += 2) { + cmd->attr_vals[(i-1)/2].attr_id = (uint8_t)strtoul(argv[i], NULL, 0); + cmd->attr_vals[(i-1)/2].value_id = (uint8_t)strtoul(argv[i+1], NULL, 0); + } + + err = bt_avrcp_ct_set_player_app_setting_val(default_ct, get_next_tid(), buf); + if (err < 0) { + shell_error(sh, "Failed to send set_player_app_setting_val: %d", err); + goto failed; + } + + shell_print(sh, "Sent SetPlayerApplicationSettingValue num_attrs=%u", cmd->num_attrs); + return 0; + +failed: + net_buf_unref(buf); + return -ENOEXEC; +} + +static int cmd_ct_get_app_attr_text(const struct shell *sh, int argc, char *argv[]) +{ + struct bt_avrcp_get_player_app_setting_attr_text_cmd *cmd; + struct net_buf *buf; + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (!default_ct) { + shell_error(sh, "AVRCP CT is not connected"); + return -ENOTCONN; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "No buffer"); + return -ENOMEM; + } + cmd = net_buf_add(buf, sizeof(*cmd)); + cmd->num_attrs = (uint8_t)(argc - 1); + + for (size_t i = 1; i < argc; i++) { + net_buf_add_u8(buf, (uint8_t)strtoul(argv[i], NULL, 0)); + } + + err = bt_avrcp_ct_get_player_app_setting_attr_text(default_ct, get_next_tid(), buf); + if (err < 0) { + shell_error(sh, "get_player_app_setting_attr_text failed: %d", err); + net_buf_unref(buf); + return err; + } + + shell_print(sh, "Sent get_player_app_setting_attr_text num_attrs=%u", cmd->num_attrs); + return 0; +} + +static int cmd_ct_get_app_val_text(const struct shell *sh, int argc, char *argv[]) +{ + struct bt_avrcp_get_player_app_setting_val_text_cmd *cmd; + struct net_buf *buf; + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (!default_ct) { + shell_error(sh, "AVRCP CT is not connected"); + return -ENOTCONN; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "No buffer"); + return -ENOMEM; + } + cmd = net_buf_add(buf, sizeof(*cmd)); + cmd->attr_id = strtoul(argv[1], NULL, 0); + cmd->num_values = (uint8_t)(argc - 2U); + + for (size_t i = 2U; i < argc; i++) { + net_buf_add_u8(buf, (uint8_t)strtoul(argv[i], NULL, 0)); + } + + err = bt_avrcp_ct_get_player_app_setting_val_text(default_ct, get_next_tid(), buf); + if (err < 0) { + shell_error(sh, "GetPlayerApplicationSettingValueText failed: %d", err); + net_buf_unref(buf); + } + + shell_print(sh, "Sent GetPlayerApplicationSettingValueText attr=0x%02x num=%u", + cmd->attr_id, cmd->num_values); + + return err; +} + +static int cmd_ct_inform_batt(const struct shell *sh, size_t argc, char **argv) +{ + uint8_t battery_status; + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP is not connected"); + return -ENOEXEC; + } + + battery_status = (uint16_t)strtoul(argv[1], NULL, 0); + + err = bt_avrcp_ct_inform_batt_status_of_ct(default_ct, get_next_tid(), battery_status); + if (err < 0) { + shell_error(sh, "fail to InformBatteryStatusOfCT"); + } else { + shell_print(sh, "AVRCP InformBatteryStatusOfCT"); } return err; } -static int cmd_register_tg_cb(const struct shell *sh, int32_t argc, char *argv[]) +static int cmd_ct_get_play_status(const struct shell *sh, int argc, char *argv[]) { - if (avrcp_tg_registered) { - shell_print(sh, "already registered"); - return 0; + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; } - register_tg_cb(sh); + if (default_ct == NULL) { + shell_error(sh, "AVRCP is not connected"); + return -ENOEXEC; + } + + err = bt_avrcp_ct_get_play_status(default_ct, get_next_tid()); + if (err < 0) { + shell_error(sh, "Fail to get_play_status"); + } else { + shell_print(sh, "AVRCP GetPlayStatus"); + } + + return err; +} + +static int cmd_ct_set_addressed_player(const struct shell *sh, int argc, char *argv[]) +{ + uint16_t player_id; + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP is not connected"); + return -ENOEXEC; + } + + player_id = (uint16_t)strtoul(argv[1], NULL, 0); + + err = bt_avrcp_ct_set_addressed_player(default_ct, get_next_tid(), player_id); + if (err < 0) { + shell_error(sh, "fail to set addressed player"); + } else { + shell_print(sh, "AVRCP send set addressed player req"); + } return 0; } -static int cmd_connect(const struct shell *sh, int32_t argc, char *argv[]) +static int cmd_ct_play_item(const struct shell *sh, size_t argc, char **argv) +{ + struct bt_avrcp_play_item_cmd *cmd; + struct net_buf *buf; + char *endptr = NULL; + int err; + + if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_ct == NULL) { + shell_error(sh, "AVRCP is not connected"); + return -ENOEXEC; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "No buffer"); + return -ENOMEM; + } + cmd = net_buf_add(buf, sizeof(*cmd)); + + cmd->scope = strtoul(argv[1], NULL, 0); + if (cmd->scope > BT_AVRCP_SCOPE_NOW_PLAYING) { + shell_error(sh, "scope out of range "); + goto failed; + } + + uint64_t uid = (uint64_t)strtoull(argv[2], &endptr, 16); + + if ((endptr == argv[2]) || (*endptr != '\0')) { + shell_error(sh, "invalid uid hex: %s", argv[2]); + goto failed; + } + sys_put_be64(uid, cmd->uid); + + cmd->uid_counter = sys_cpu_to_be16(strtoul(argv[3], NULL, 0)); + + err = bt_avrcp_ct_play_item(default_ct, get_next_tid(), buf); + if (err < 0) { + goto failed; + } + + shell_print(sh, "Sent PlayItem scope=0x%02x uid=0x%016llx uid_counter=0x%04x", + cmd->scope, uid, cmd->uid_counter); + + return 0; +failed: + net_buf_unref(buf); + return -ENOEXEC; +} + +static int cmd_ct_add_to_now_playing(const struct shell *sh, size_t argc, char **argv) { + struct bt_avrcp_add_to_now_playing_cmd *cmd; + struct net_buf *buf; + char *endptr = NULL; int err; if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { return -ENOEXEC; } + if (default_ct == NULL) { + shell_error(sh, "AVRCP is not connected"); + return -ENOEXEC; + } + + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "No buffer"); + return -ENOMEM; + } + cmd = net_buf_add(buf, sizeof(*cmd)); + + cmd->scope = strtoul(argv[1], NULL, 0); + if (cmd->scope > BT_AVRCP_SCOPE_NOW_PLAYING) { + shell_error(sh, "scope out of range "); + goto failed; + } + + uint64_t uid = (uint64_t)strtoull(argv[2], &endptr, 16); + + if ((endptr == argv[2]) || (*endptr != '\0')) { + shell_error(sh, "invalid uid hex: %s", argv[2]); + goto failed; + } + sys_put_be64(uid, cmd->uid); + + cmd->uid_counter = sys_cpu_to_be16(strtoul(argv[3], NULL, 0)); + + err = bt_avrcp_ct_add_to_now_playing(default_ct, get_next_tid(), buf); + if (err < 0) { + goto failed; + } + + shell_print(sh, "Sent AddToNowPlaying scope=0x%02x uid=0x%016llx uid_counter=0x%04x", + cmd->scope, uid, cmd->uid_counter); + return 0; + +failed: + net_buf_unref(buf); + return -ENOEXEC; +} + +static int cmd_tg_send_absolute_volume_rsp(const struct shell *sh, int32_t argc, char *argv[]) +{ + uint8_t absolute_volume; + int err; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; + } + + absolute_volume = (uint8_t)strtoul(argv[1], NULL, 0); + + err = bt_avrcp_tg_send_absolute_volume_rsp(default_tg, tg_tid, BT_AVRCP_STATUS_SUCCESS, + absolute_volume); + if (err < 0) { + shell_error(sh, "Failed to send set absolute volume response: %d", err); + } else { + shell_print(sh, "Set absolute volume response sent successfully"); + } + + return err; +} + +static int cmd_tg_send_list_player_app_setting_attrs_rsp(const struct shell *sh, int argc, + char *argv[]) +{ + struct bt_avrcp_list_app_setting_attr_rsp *rsp; + struct net_buf *buf; + uint8_t num; + size_t expected_len; + int err; + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { return -ENOEXEC; } - if (!default_conn) { - shell_error(sh, "BR/EDR not connected"); + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); return -ENOEXEC; } - err = bt_avrcp_connect(default_conn); - if (err) { - shell_error(sh, "fail to connect AVRCP"); + num = (argc >= 2) ? (uint8_t)strtoul(argv[1], NULL, 0) : 2; + expected_len = 1 + (size_t)num; /* Num + AttrIDs */ + + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "Failed to allocate buffer for AVRCP response"); + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < expected_len) { + shell_error(sh, "Not enough tailroom in buffer"); + goto failed; + } + + rsp = net_buf_add(buf, expected_len); + + rsp->num_attrs = num; + for (uint8_t i = 0U; i < num; i++) { + rsp->attr_ids[i] = (argc >= (2 + i + 1)) ? (uint8_t)strtoul(argv[2 + i], + NULL, 0) : (i + 1); + } + + err = bt_avrcp_tg_send_list_player_app_setting_attrs_rsp(default_tg, tg_tid, + BT_AVRCP_STATUS_SUCCESS, buf); + if (err < 0) { + shell_error(sh, "Failed to send ListPlayerAppSettingAttributes rsp: %d", err); + goto failed; + } + + shell_print(sh, "ListPlayerApplicationSettingAttributes rsp sent (num=%u)", num); + return 0; + +failed: + net_buf_unref(buf); + return -ENOEXEC; +} + +static int cmd_tg_send_list_player_app_setting_vals_rsp(const struct shell *sh, int argc, + char *argv[]) +{ + struct bt_avrcp_list_player_app_setting_vals_rsp *rsp; + struct net_buf *buf; + uint8_t num; + size_t expected_len; + int err; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; + } + + num = (argc >= 2) ? (uint8_t)strtoul(argv[1], NULL, 0) : 2; + expected_len = 1 + (size_t)num; /* Num + ValueIDs */ + + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "Failed to allocate buffer for AVRCP response"); + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < expected_len) { + shell_error(sh, "Not enough tailroom in buffer"); + goto failed; + } + rsp = net_buf_add(buf, expected_len); + + rsp->num_values = num; + + for (uint8_t i = 0U; i < num; i++) { + rsp->values[i] = (argc >= (2 + i + 1)) ? (uint8_t)strtoul(argv[2 + i], NULL, 0) : + (i + 1); + } + + err = bt_avrcp_tg_send_list_player_app_setting_vals_rsp(default_tg, tg_tid, + BT_AVRCP_STATUS_SUCCESS, buf); + if (err < 0) { + shell_error(sh, "Failed to send list player app setting vals rsp: %d", err); + goto failed; + } + + shell_print(sh, "List player app setting vals rsp sent (num=%u)", num); + return 0; + +failed: + net_buf_unref(buf); + return -ENOEXEC; +} + +static int cmd_tg_send_get_curr_player_app_setting_val_rsp(const struct shell *sh, int argc, + char *argv[]) +{ + struct net_buf *buf; + struct bt_avrcp_get_curr_player_app_setting_val_rsp *rsp; + size_t expected_len; + uint8_t num_pairs; + int err; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { + return -ENOEXEC; + } + + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; } + /* Response payload: Num + (AttrID,ValueID)[n] */ + num_pairs = (argc >= 2) ? (uint8_t)strtoul(argv[1], NULL, 0) : 1; + expected_len = sizeof(uint8_t) + (size_t)num_pairs * + sizeof(struct bt_avrcp_app_setting_attr_val); + + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "Failed to allocate buffer for AVRCP response"); + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < expected_len) { + shell_error(sh, "Not enough tailroom in buffer"); + goto failed; + } + rsp = net_buf_add(buf, expected_len); + rsp->num_attrs = num_pairs; + + /* args: [attr1 val1] [attr2 val2] ... */ + for (uint8_t i = 0U; i < rsp->num_attrs; i++) { + int ai = 2 + (i * 2); /* argv index for attr */ + + rsp->attr_vals[i].attr_id = (ai < argc) ? (uint8_t)strtoul(argv[ai], NULL, 0) : + (uint8_t)(i + 1); + rsp->attr_vals[i].value_id = (ai + 1 < argc) ? + (uint8_t)strtoul(argv[ai + 1], NULL, 0) : 1; + } + + err = bt_avrcp_tg_send_get_curr_player_app_setting_val_rsp(default_tg, tg_tid, + BT_AVRCP_STATUS_SUCCESS, buf); + if (err < 0) { + shell_error(sh, "Failed to send get curr player app setting val rsp: %d", err); + goto failed; + } + + shell_print(sh, "Send get curr player app setting val rsp sent (num=%u)", rsp->num_attrs); return 0; + +failed: + net_buf_unref(buf); + return -ENOEXEC; } -static int cmd_disconnect(const struct shell *sh, int32_t argc, char *argv[]) +static int cmd_tg_send_set_player_app_setting_val_rsp(const struct shell *sh, int argc, + char *argv[]) { - if ((!avrcp_ct_registered) && (!avrcp_tg_registered)) { - shell_error(sh, "Neither CT nor TG callbacks are registered."); + int err; + uint8_t status = BT_AVRCP_STATUS_OPERATION_COMPLETED; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { return -ENOEXEC; } - if (!default_conn) { - shell_print(sh, "Not connected"); + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); return -ENOEXEC; } - if ((default_ct != NULL) || (default_tg != NULL)) { - bt_avrcp_disconnect(default_conn); - } else { - shell_error(sh, "AVRCP is not connected"); + if (argc > 1) { + status = (uint8_t)strtoul(argv[1], NULL, 0); + } + err = bt_avrcp_tg_send_set_player_app_setting_val_rsp(default_tg, tg_tid, status); + if (err < 0) { + shell_error(sh, "Failed to send set set_player_app_setting_val rsp: %d", err); + return -ENOEXEC; } + shell_print(sh, "set_player_app_setting_val rsp sent "); return 0; } -static int cmd_browsing_connect(const struct shell *sh, int32_t argc, char *argv[]) +static int cmd_tg_send_get_player_app_setting_attr_text_rsp(const struct shell *sh, int argc, + char *argv[]) { + struct bt_avrcp_get_player_app_setting_attr_text_rsp *rsp; + struct net_buf *buf; + char *text_str = "AttrText"; int err; - if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { return -ENOEXEC; } - if (default_conn == NULL) { - shell_error(sh, "BR/EDR not connected"); + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); return -ENOEXEC; } - err = bt_avrcp_browsing_connect(default_conn); + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "Failed to allocate buffer for AVRCP response"); + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < sizeof(*rsp) + sizeof(struct bt_avrcp_app_setting_attr_text)) { + shell_error(sh, "Not enough tailroom in buffer"); + goto failed; + } + rsp = net_buf_add(buf, sizeof(*rsp) + sizeof(struct bt_avrcp_app_setting_attr_text)); + + rsp->num_attrs = 1; + rsp->attr_text[0].attr_id = 1; + rsp->attr_text[0].charset_id = sys_cpu_to_be16(BT_AVRCP_CHARSET_UTF8); + rsp->attr_text[0].text_len = strlen(text_str); + net_buf_add_mem(buf, text_str, strlen(text_str)); + + err = bt_avrcp_tg_send_get_player_app_setting_attr_text_rsp(default_tg, tg_tid, + BT_AVRCP_STATUS_SUCCESS, buf); if (err < 0) { - shell_error(sh, "fail to connect AVRCP browsing"); - } else { - shell_print(sh, "AVRCP browsing connect request sent"); + shell_error(sh, "Failed to send get player app setting attr text rsp: %d", err); + return -ENOEXEC; } - return err; + shell_print(sh, "Get player app setting attr text rsp sent"); + return 0; + +failed: + net_buf_unref(buf); + return -ENOEXEC; } -static int cmd_browsing_disconnect(const struct shell *sh, int32_t argc, char *argv[]) +static int cmd_tg_send_get_player_app_setting_val_text_rsp(const struct shell *sh, int argc, + char *argv[]) { + struct bt_avrcp_get_player_app_setting_val_text_rsp *rsp; + struct net_buf *buf; + char *value_str = "ValueText"; int err; - if (default_conn == NULL) { - shell_print(sh, "Not connected"); + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { return -ENOEXEC; } - if ((default_ct != NULL) || (default_tg != NULL)) { - err = bt_avrcp_browsing_disconnect(default_conn); - if (err < 0) { - shell_error(sh, "fail to disconnect AVRCP browsing"); - } else { - shell_print(sh, "AVRCP browsing disconnect request sent"); - } - } else { - shell_error(sh, "AVRCP is not connected"); - err = -ENOEXEC; + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; } - return err; -} + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); + if (buf == NULL) { + shell_error(sh, "Failed to allocate buffer for AVRCP response"); + return -ENOMEM; + } -static int cmd_get_unit_info(const struct shell *sh, int32_t argc, char *argv[]) -{ - if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { - return -ENOEXEC; + if (net_buf_tailroom(buf) < sizeof(*rsp) + sizeof(struct bt_avrcp_app_setting_val_text)) { + shell_error(sh, "Not enough tailroom in buffer"); + goto failed; } + rsp = net_buf_add(buf, sizeof(*rsp) + sizeof(struct bt_avrcp_app_setting_val_text)); - if (default_ct != NULL) { - bt_avrcp_ct_get_unit_info(default_ct, get_next_tid()); - } else { - shell_error(sh, "AVRCP is not connected"); + rsp->num_values = 1; + rsp->value_text[0].value_id = 1; + rsp->value_text[0].charset_id = sys_cpu_to_be16(BT_AVRCP_CHARSET_UTF8); + rsp->value_text[0].text_len = strlen(value_str); + net_buf_add_mem(buf, value_str, strlen(value_str)); + + err = bt_avrcp_tg_send_get_player_app_setting_val_text_rsp(default_tg, tg_tid, + BT_AVRCP_STATUS_SUCCESS, buf); + if (err < 0) { + shell_error(sh, "Failed to send get player app setting val text rsp: %d", err); + return -ENOEXEC; } + shell_print(sh, "Get player app setting val text rsp sent"); return 0; + +failed: + net_buf_unref(buf); + return -ENOEXEC; } -static int cmd_send_unit_info_rsp(const struct shell *sh, int32_t argc, char *argv[]) +static int cmd_tg_send_inform_batt_status_of_ct_rsp(const struct shell *sh, int argc, char *argv[]) { - struct bt_avrcp_unit_info_rsp rsp; int err; + uint8_t status = BT_AVRCP_STATUS_OPERATION_COMPLETED; if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { return -ENOEXEC; } - rsp.unit_type = BT_AVRCP_SUBUNIT_TYPE_PANEL; - rsp.company_id = BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG; + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; + } - if (default_tg != NULL) { - err = bt_avrcp_tg_send_unit_info_rsp(default_tg, tg_tid, &rsp); - if (!err) { - shell_print(sh, "AVRCP send unit info response"); - } else { - shell_error(sh, "Failed to send unit info response"); - } - } else { - shell_error(sh, "AVRCP is not connected"); + if (argc > 1) { + status = (uint8_t)strtoul(argv[1], NULL, 0); + } + + err = bt_avrcp_tg_send_inform_batt_status_of_ct_rsp(default_tg, tg_tid, status); + if (err < 0) { + shell_error(sh, "send inform batt status rsp send failed: err=%d", err); + return err; } + shell_print(sh, "send inform batt status rsp sent (status=0x%02x)", status); return 0; + } -static int cmd_send_passthrough_rsp(const struct shell *sh, int32_t argc, char *argv[]) +static int cmd_tg_send_get_play_status_rsp(const struct shell *sh, int argc, char *argv[]) { - struct bt_avrcp_passthrough_rsp *rsp; - struct bt_avrcp_passthrough_opvu_data *opvu = NULL; - bt_avrcp_opid_t opid = 0; - bt_avrcp_button_state_t state; - uint16_t vu_opid = 0; - bool is_op_vu = true; + struct bt_avrcp_get_play_status_rsp *rsp; struct net_buf *buf; - char *endptr; - unsigned long val; int err; if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { @@ -507,174 +2814,118 @@ static int cmd_send_passthrough_rsp(const struct shell *sh, int32_t argc, char * return -ENOEXEC; } - buf = bt_avrcp_create_pdu(NULL); + buf = bt_avrcp_create_vendor_pdu(&avrcp_tx_pool); if (buf == NULL) { - shell_error(sh, "Failed to allocate buffer for AVRCP passthrough response"); + shell_error(sh, "Failed to allocate buffer for AVRCP response"); return -ENOMEM; } - if (net_buf_tailroom(buf) < sizeof(struct bt_avrcp_passthrough_rsp)) { - shell_error(sh, "Not enough tailroom in buffer for passthrough rsp"); - goto failed; - } - rsp = net_buf_add(buf, sizeof(*rsp)); - - if (!strcmp(argv[1], "op")) { - is_op_vu = false; - } else if (!strcmp(argv[1], "opvu")) { - is_op_vu = true; - } else { - shell_error(sh, "Invalid response: %s", argv[1]); - goto failed; - } - - if (!strcmp(argv[2], "play")) { - opid = BT_AVRCP_OPID_PLAY; - vu_opid = (uint16_t)opid; - } else if (!strcmp(argv[2], "pause")) { - opid = BT_AVRCP_OPID_PAUSE; - vu_opid = (uint16_t)opid; - } else { - /* Try to parse as hex value */ - val = strtoul(argv[2], &endptr, 16); - if (*endptr != '\0' || val > 0xFFFFU) { - shell_error(sh, "Invalid opid: %s", argv[2]); - goto failed; - } - if (is_op_vu) { - vu_opid = (uint16_t)val; - } else { - opid = (bt_avrcp_opid_t)val; - } - } - - if (!strcmp(argv[3], "pressed")) { - state = BT_AVRCP_BUTTON_PRESSED; - } else if (!strcmp(argv[3], "released")) { - state = BT_AVRCP_BUTTON_RELEASED; - } else { - shell_error(sh, "Invalid state: %s", argv[3]); + if (net_buf_tailroom(buf) < sizeof(*rsp)) { + shell_error(sh, "Not enough tailroom in buffer"); goto failed; } - if (is_op_vu) { - opid = BT_AVRCP_OPID_VENDOR_UNIQUE; - } - - BT_AVRCP_PASSTHROUGH_SET_STATE_OPID(rsp, state, opid); - if (is_op_vu) { - if (net_buf_tailroom(buf) < sizeof(*opvu)) { - shell_error(sh, "Not enough tailroom in buffer for opvu"); - goto failed; - } - opvu = net_buf_add(buf, sizeof(*opvu)); - sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, opvu->company_id); - opvu->opid_vu = sys_cpu_to_be16(vu_opid); - rsp->data_len = sizeof(*opvu); - } else { - rsp->data_len = 0; - } + rsp = net_buf_add(buf, sizeof(*rsp)); + rsp->song_length = sys_cpu_to_be32(180000U); + rsp->song_position = sys_cpu_to_be32(30000U); + rsp->play_status = BT_AVRCP_PLAYBACK_STATUS_PLAYING; - err = bt_avrcp_tg_send_passthrough_rsp(default_tg, tg_tid, BT_AVRCP_RSP_ACCEPTED, buf); + err = bt_avrcp_tg_send_get_play_status_rsp(default_tg, tg_tid, BT_AVRCP_STATUS_SUCCESS, + buf); if (err < 0) { - shell_error(sh, "Failed to send passthrough response: %d", err); - goto failed; - } else { - shell_print(sh, "Passthrough opid=0x%02x, state=%s", opid, argv[2]); - return 0; + shell_error(sh, "Failed to send GetPlayStatus rsp: %d", err); + return -ENOEXEC; } + shell_print(sh, "GetPlayStatus rsp sent"); + return 0; + failed: net_buf_unref(buf); return -ENOEXEC; } -static int cmd_send_subunit_info_rsp(const struct shell *sh, int32_t argc, char *argv[]) +static int cmd_tg_send_set_addressed_player_rsp(const struct shell *sh, size_t argc, char **argv) { int err; + uint8_t status = BT_AVRCP_STATUS_OPERATION_COMPLETED; if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { return -ENOEXEC; } - if (default_tg != NULL) { - err = bt_avrcp_tg_send_subunit_info_rsp(default_tg, tg_tid); - if (err == 0) { - shell_print(sh, "AVRCP send subunit info response"); - } else { - shell_error(sh, "Failed to send subunit info response"); - } - } else { - shell_error(sh, "AVRCP is not connected"); + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; } - return 0; -} - -static int cmd_get_subunit_info(const struct shell *sh, int32_t argc, char *argv[]) -{ - if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { - return -ENOEXEC; + if (argc > 1) { + status = (uint8_t)strtoul(argv[1], NULL, 0); } - if (default_ct != NULL) { - bt_avrcp_ct_get_subunit_info(default_ct, get_next_tid()); - } else { - shell_error(sh, "AVRCP is not connected"); + err = bt_avrcp_tg_send_set_addressed_player_rsp(default_tg, tg_tid, status); + if (err < 0) { + shell_error(sh, "SetAddressedPlayer rsp send failed: err=%d", err); + return err; } + shell_print(sh, "SetAddressedPlayer rsp sent (status=0x%02x)", status); return 0; } -static int cmd_passthrough(const struct shell *sh, bt_avrcp_opid_t opid, const uint8_t *payload, - uint8_t len) +static int cmd_tg_send_play_item_rsp(const struct shell *sh, size_t argc, char **argv) { - if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + int err; + uint8_t status = BT_AVRCP_STATUS_OPERATION_COMPLETED; + + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { return -ENOEXEC; } - if (default_ct != NULL) { - bt_avrcp_ct_passthrough(default_ct, get_next_tid(), opid, BT_AVRCP_BUTTON_PRESSED, - payload, len); - bt_avrcp_ct_passthrough(default_ct, get_next_tid(), opid, BT_AVRCP_BUTTON_RELEASED, - payload, len); - } else { - shell_error(sh, "AVRCP is not connected"); + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; } - return 0; -} + if (argc > 1) { + status = (uint8_t)strtoul(argv[1], NULL, 0); + } -static int cmd_play(const struct shell *sh, int32_t argc, char *argv[]) -{ - return cmd_passthrough(sh, BT_AVRCP_OPID_PLAY, NULL, 0); -} + err = bt_avrcp_tg_send_play_item_rsp(default_tg, tg_tid, status); + if (err < 0) { + shell_error(sh, "Play item rsp send failed: err=%d", err); + return err; + } -static int cmd_pause(const struct shell *sh, int32_t argc, char *argv[]) -{ - return cmd_passthrough(sh, BT_AVRCP_OPID_PAUSE, NULL, 0); + shell_print(sh, "Play item rsp sent (status=0x%02x)", status); + return 0; } -static int cmd_get_cap(const struct shell *sh, int32_t argc, char *argv[]) +static int cmd_tg_send_add_to_now_playing_rsp(const struct shell *sh, size_t argc, char **argv) { - const char *cap_id; + int err; + uint8_t status = BT_AVRCP_STATUS_OPERATION_COMPLETED; - if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { + if (!avrcp_tg_registered && register_tg_cb(sh) != 0) { return -ENOEXEC; } - if (default_ct == NULL) { - shell_error(sh, "AVRCP is not connected"); - return 0; + if (default_tg == NULL) { + shell_error(sh, "AVRCP TG is not connected"); + return -ENOEXEC; } - cap_id = argv[1]; - if (!strcmp(cap_id, "company")) { - bt_avrcp_ct_get_cap(default_ct, get_next_tid(), BT_AVRCP_CAP_COMPANY_ID); - } else if (!strcmp(cap_id, "events")) { - bt_avrcp_ct_get_cap(default_ct, get_next_tid(), BT_AVRCP_CAP_EVENTS_SUPPORTED); + if (argc > 1) { + status = (uint8_t)strtoul(argv[1], NULL, 0); + } + + err = bt_avrcp_tg_send_add_to_now_playing_rsp(default_tg, tg_tid, status); + if (err < 0) { + shell_error(sh, "Add to now playing rsp send failed: err=%d", err); + return err; } + shell_print(sh, "Add to now playing rsp sent (status=0x%02x)", status); return 0; } @@ -823,12 +3074,37 @@ SHELL_STATIC_SUBCMD_SET_CREATE( SHELL_CMD_ARG(register_cb, NULL, "register avrcp ct callbacks", cmd_register_ct_cb, 1, 0), SHELL_CMD_ARG(get_unit, NULL, "get unit info", cmd_get_unit_info, 1, 0), SHELL_CMD_ARG(get_subunit, NULL, "get subunit info", cmd_get_subunit_info, 1, 0), - SHELL_CMD_ARG(get_cap, NULL, "get capabilities ", cmd_get_cap, 2, - 0), + SHELL_CMD_ARG(get_caps, NULL, "get capabilities ", cmd_get_caps, + 2, 0), SHELL_CMD_ARG(play, NULL, "request a play at the remote player", cmd_play, 1, 0), SHELL_CMD_ARG(pause, NULL, "request a pause at the remote player", cmd_pause, 1, 0), + SHELL_CMD_ARG(register_notification, NULL, "register notify [playback_interval]", + cmd_ct_register_notification, 2, 1), SHELL_CMD_ARG(set_browsed_player, NULL, "set browsed player ", cmd_set_browsed_player, 2, 0), + SHELL_CMD_ARG(set_absolute_volume, NULL, "set absolute volume ", + cmd_ct_set_absolute_volume, 2, 0), + SHELL_CMD_ARG(get_element_attrs, NULL, "get element attrs [identifier] [attr1] [attr2] ...", + cmd_get_element_attrs, 1, 9), + SHELL_CMD_ARG(list_app_attrs, NULL, HELP_NONE, cmd_ct_list_app_attrs, 1, 0), + SHELL_CMD_ARG(list_app_vals, NULL, "List App vals ", cmd_ct_list_app_vals, 2, 0), + SHELL_CMD_ARG(get_app_curr, NULL, "Get curr player app setting val [attr1] [attr2] ...", + cmd_ct_get_app_curr, 1, 8), + SHELL_CMD_ARG(set_app_val, NULL, "Set app setting Val [ ] ...", + cmd_ct_set_app_val, 3, 14), + SHELL_CMD_ARG(get_app_attr_text, NULL, "Get app setting attrs text [attr2] ...", + cmd_ct_get_app_attr_text, 2, 7), + SHELL_CMD_ARG(get_app_val_text, NULL, "Get setting vals Text [val2] ...", + cmd_ct_get_app_val_text, 3, 6), + SHELL_CMD_ARG(inform_batt, NULL, "Inform Battery Status Of CT ", + cmd_ct_inform_batt, 2, 0), + SHELL_CMD_ARG(get_play_status, NULL, HELP_NONE, cmd_ct_get_play_status, 1, 0), + SHELL_CMD_ARG(set_addressed_player, NULL, "set addressed player ", + cmd_ct_set_addressed_player, 2, 0), + SHELL_CMD_ARG(play_item, NULL, "PlayItem ", + cmd_ct_play_item, 4, 0), + SHELL_CMD_ARG(add_to_now_playing, NULL, "AddToNowPlaying ", + cmd_ct_add_to_now_playing, 4, 0), SHELL_SUBCMD_SET_END); SHELL_STATIC_SUBCMD_SET_CREATE( @@ -836,10 +3112,43 @@ SHELL_STATIC_SUBCMD_SET_CREATE( SHELL_CMD_ARG(register_cb, NULL, "register avrcp tg callbacks", cmd_register_tg_cb, 1, 0), SHELL_CMD_ARG(send_unit_rsp, NULL, "send unit info response", cmd_send_unit_info_rsp, 1, 0), SHELL_CMD_ARG(send_subunit_rsp, NULL, HELP_NONE, cmd_send_subunit_info_rsp, 1, 0), + SHELL_CMD_ARG(send_get_caps_rsp, NULL, "send get capabilities response", + cmd_send_get_caps_rsp, 1, 0), + SHELL_CMD_ARG(send_notification_rsp, NULL, "send notify rsp [value...]", + cmd_tg_send_notification_rsp, 3, 10), SHELL_CMD_ARG(send_browsed_player_rsp, NULL, HELP_BROWSED_PLAYER_RSP, cmd_send_set_browsed_player_rsp, 1, 5), SHELL_CMD_ARG(send_passthrough_rsp, NULL, HELP_PASSTHROUGH_RSP, cmd_send_passthrough_rsp, 4, 0), + SHELL_CMD_ARG(send_get_element_attrs_rsp, NULL, "send get element attrs response", + cmd_send_get_element_attrs_rsp, 2, 0), + SHELL_CMD_ARG(send_absolute_volume_rsp, NULL, "send absolute volume rsp ", + cmd_tg_send_absolute_volume_rsp, 2, 0), + SHELL_CMD_ARG(send_list_player_app_setting_attrs_rsp, NULL, + "send attrs rsp [attr_id...]", + cmd_tg_send_list_player_app_setting_attrs_rsp, 2, 8), + SHELL_CMD_ARG(send_list_player_app_setting_vals_rsp, NULL, + "send vals rsp [val_id...]", + cmd_tg_send_list_player_app_setting_vals_rsp, 2, 16), + SHELL_CMD_ARG(send_get_curr_player_app_setting_val_rsp, NULL, + "send current vals rsp [attr val]...", + cmd_tg_send_get_curr_player_app_setting_val_rsp, 2, 16), + SHELL_CMD_ARG(send_set_player_app_setting_val_rsp, NULL, "set app setting val rsp [status]", + cmd_tg_send_set_player_app_setting_val_rsp, 1, 1), + SHELL_CMD_ARG(send_get_player_app_setting_attr_text_rsp, NULL, HELP_NONE, + cmd_tg_send_get_player_app_setting_attr_text_rsp, 1, 0), + SHELL_CMD_ARG(send_get_player_app_setting_val_text_rsp, NULL, HELP_NONE, + cmd_tg_send_get_player_app_setting_val_text_rsp, 1, 0), + SHELL_CMD_ARG(send_inform_batt_status_of_ct_rsp, NULL, "send inform batt rsp [status]", + cmd_tg_send_inform_batt_status_of_ct_rsp, 1, 1), + SHELL_CMD_ARG(send_get_play_status_rsp, NULL, HELP_NONE, + cmd_tg_send_get_play_status_rsp, 1, 3), + SHELL_CMD_ARG(send_set_addressed_player_rsp, NULL, "send set addressed player rsp [status]", + cmd_tg_send_set_addressed_player_rsp, 1, 1), + SHELL_CMD_ARG(send_play_item_rsp, NULL, "send play item rsp [status]", + cmd_tg_send_play_item_rsp, 1, 1), + SHELL_CMD_ARG(send_add_to_now_playing_rsp, NULL, "send add to now playing rsp [status]", + cmd_tg_send_add_to_now_playing_rsp, 1, 1), SHELL_SUBCMD_SET_END); static int cmd_avrcp(const struct shell *sh, size_t argc, char **argv)