diff --git a/include/zephyr/bluetooth/classic/avrcp.h b/include/zephyr/bluetooth/classic/avrcp.h index 82405e28d6e5a..29efa1f58a324 100644 --- a/include/zephyr/bluetooth/classic/avrcp.h +++ b/include/zephyr/bluetooth/classic/avrcp.h @@ -300,7 +300,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 +388,304 @@ 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 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 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 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 */ + uint32_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 */ + uint32_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. + * + * This callback is invoked by the AVRCP Target (TG) when a registered event + * occurs and a notification needs to be sent to the Controller (CT). + * + * @param event_id The AVRCP event identifier. This corresponds to one of the + * AVRCP event types such as EVENT_PLAYBACK_STATUS_CHANGED, + * EVENT_TRACK_CHANGED, etc. + * @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. + * + * @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. * @@ -374,8 +733,8 @@ struct bt_avrcp_ct_cb { * @param tid The transaction label of the response. * @param rsp The response for Get Capabilities command. */ - 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, + const struct bt_avrcp_get_caps_rsp *rsp); /** @brief Callback function for bt_avrcp_get_unit_info(). * @@ -423,6 +782,200 @@ 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 rsp_code The response code for the vendor dependent command. + * @param tid The transaction label of the response. + * @param absolute_volume The absolute volume value (0x00-0x7F). + */ + void (*set_absolute_volume_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t rsp_code, + uint8_t absolute_volume); + + /** @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 rsp_code The response code for the vendor dependent command. + * @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. + * 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 rsp_code, 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 rsp_code The response code for the vendor dependent command. + * @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. + * 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 rsp_code, 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 rsp_code The response code for the vendor dependent command. + * @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. + * 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 rsp_code, 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 rsp_code The response code for the vendor dependent command. + */ + void (*set_player_app_setting_val_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t rsp_code); + + /** @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 rsp_code The response code for the vendor dependent command. + * @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. + * 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 rsp_code, 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 rsp_code The response code for the vendor dependent command. + * @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. + * 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 rsp_code, 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 rsp_code The response code for the vendor dependent command. + */ + void (*inform_displayable_char_set_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, + uint8_t rsp_code); + + /** @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 rsp_code The response code for the vendor dependent command. + */ + void (*inform_batt_status_of_ct_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t rsp_code); + + /** @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 rsp_code The response code for the vendor dependent command. + * @param buf The response buffer containing the GET_ELEMENT_ATTRS payload + * returned by the TG, formatted as + * @ref bt_avrcp_get_element_attrs_rsp. + * 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 rsp_code, + 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 rsp_code The response code for the vendor dependent command. + * @param buf The response buffer containing the GET_PLAY_STATUS payload + * returned by the TG, formatted as + * @ref bt_avrcp_get_play_status_rsp. + * 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 rsp_code, + 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 rsp_code The response code for the vendor dependent command. + * @param status The status code returned by the TG, indicating the result of the + * operation. Typically corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_OPERATION_COMPLETED or BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + void (*set_addressed_player_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t rsp_code, + uint8_t status); + + /** @brief Callback for PDU ID PLAY_ITEMS. + * + * Called when the response for PLAY_ITEMS is received. + * + * @param ct AVRCP CT connection object. + * @param tid The transaction label of the request. + * @param rsp_code The response code for the vendor dependent command. + * @param status The status code returned by the TG, indicating the result of the + * operation. Typically corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_OPERATION_COMPLETED or BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + void (*play_item_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t rsp_code, + 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 rsp_code The response code for the vendor dependent command. + * @param status The status code returned by the TG, indicating the result of the + * operation. Typically corresponds to BT_AVRCP_STATUS_* values such as + * BT_AVRCP_STATUS_OPERATION_COMPLETED or BT_AVRCP_STATUS_INVALID_PARAMETER. + */ + void (*add_to_now_playing_rsp)(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t rsp_code, + uint8_t status); }; /** @brief Connect AVRCP. @@ -556,6 +1109,200 @@ 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 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 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 buf The command buffer containing the INFORM_BATT_STATUS_OF_CT + * request payload, formatted as + * @ref bt_avrcp_inform_batt_status_of_ct_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_batt_status_of_ct(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + +/** @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 buf The command buffer containing the SET_ADDRESSED_PLAYER + * request payload, formatted as + * @ref bt_avrcp_set_addressed_player_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_addressed_player(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf); + +/** @brief Send AVRCP vendor dependent command for PLAY_ITEMS. + * + * @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 +1332,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 +1354,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_cap_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 +1405,173 @@ 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 buf The buffer containing the INFORM_BATT_STATUS_OF_CT command payload, + * formatted as @ref bt_avrcp_inform_batt_status_of_ct_cmd. + * The application should parse fields in big-endian order. + */ + void (*inform_batt_status_of_ct_req)(struct bt_avrcp_tg *tg, uint8_t tid, + struct net_buf *buf); + + /** @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 buf The buffer containing the SET_ADDRESSED_PLAYER command payload, + * formatted as @ref bt_avrcp_set_addressed_player_cmd. + * The application should parse fields in big-endian order. + */ + void (*set_addressed_player_req)(struct bt_avrcp_tg *tg, uint8_t tid, struct net_buf *buf); + + /** @brief Callback for PDU ID BT_AVRCP_PDU_ID_PLAY_ITEMS. + * + * Called when the TG receives a vendor dependent command for PLAY_ITEMS. + * + * @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 +1608,36 @@ 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 rsp The response for GET_CAPABILITIES command. + * + * @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, + const struct bt_avrcp_get_caps_rsp *rsp); + +/** @brief Send notification response. + * + * This function sends a notification response to the controller. + * This can be either an interim or changed notification response. + * + * @param tg The AVRCP TG instance. + * @param tid The transaction label of the response, valid from 0 to 15. + * @param type The type of notification response (BT_AVRCP_RSP_INTERIM or BT_AVRCP_RSP_CHANGED). + * @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. + */ +int bt_avrcp_tg_send_notification_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t type, + 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 +1651,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 rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, + uint8_t absolute_volume); + /** @brief Send AVRCP Pass Through response. * * This function is called by the application to send the Pass Through response. @@ -699,8 +1680,175 @@ int bt_avrcp_tg_send_set_browsed_player_rsp(struct bt_avrcp_tg *tg, uint8_t tid, * * @return 0 in case of success or error code in case of error. */ -int bt_avrcp_tg_send_passthrough_rsp(struct bt_avrcp_tg *tg, uint8_t tid, bt_avrcp_rsp_t result, +int bt_avrcp_tg_send_passthrough_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_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 rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, 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 rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, 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 rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, 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 rsp_code AVRCP response code for this vendor dependent PDU. + * + * @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 rsp_code); + +/** @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 rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, 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 rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, 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 rsp_code AVRCP response code for this vendor dependent PDU. + * + * @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 rsp_code); + +/** @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 rsp_code AVRCP response code for this vendor dependent PDU. + * + * @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 rsp_code); + +/** @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 rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, 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 rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, + 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 rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, uint8_t status); + +/** @brief Send response for PDU ID BT_AVRCP_PDU_ID_PLAY_ITEMS. + * + * @param tg AVRCP TG connection object. + * @param tid The transaction label of the request. + * @param rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, + 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 rsp_code AVRCP response code for this vendor dependent PDU. + * @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 rsp_code, uint8_t status); #ifdef __cplusplus } #endif diff --git a/subsys/bluetooth/host/classic/Kconfig b/subsys/bluetooth/host/classic/Kconfig index 8d14d9b88d3f6..24d20c5298fb2 100644 --- a/subsys/bluetooth/host/classic/Kconfig +++ b/subsys/bluetooth/host/classic/Kconfig @@ -493,6 +493,13 @@ config BT_AVRCP_CONTROLLER help This option enables the AVRCP profile controller function +config BT_AVRCP_DATA_BUF_SIZE + int "AVRCP RX Data Buffer Size" + default 1024 + range 1024 $(INT16_MAX) + help + Size of the AVRCP data buffer in bytes. + config BT_AVRCP_BROWSING bool "Bluetooth AVRCP Browsing channel support [EXPERIMENTAL]" select EXPERIMENTAL diff --git a/subsys/bluetooth/host/classic/avrcp.c b/subsys/bluetooth/host/classic/avrcp.c index 2789da0b318da..a1fdd9c44e5c9 100644 --- a/subsys/bluetooth/host/classic/avrcp.c +++ b/subsys/bluetooth/host/classic/avrcp.c @@ -41,10 +41,23 @@ 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]; }; struct bt_avrcp_tg { struct bt_avrcp *avrcp; + + /* AVRCP TG TX pending */ + sys_slist_t tx_pending; + + /* Critical locker */ + struct k_sem lock; + + /* TX work */ + struct k_work_delayable tx_work; }; struct avrcp_handler { @@ -52,6 +65,42 @@ 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; + void (*func)(struct bt_avrcp *avrcp, uint8_t tid, uint8_t result, struct net_buf *buf); +}; + +typedef struct { + uint8_t pdu_id; + bt_avrcp_ctype_t cmd_type; +} bt_avrcp_pdu_cmd_map_t; + +static const bt_avrcp_pdu_cmd_map_t avrcp_pdu_cmd_table[] = { + { BT_AVRCP_PDU_ID_GET_CAPS, BT_AVRCP_CTYPE_STATUS }, + { BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, BT_AVRCP_CTYPE_STATUS }, + { BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, BT_AVRCP_CTYPE_STATUS }, + { BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL, BT_AVRCP_CTYPE_STATUS }, + { BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, BT_AVRCP_CTYPE_CONTROL }, + { BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT, BT_AVRCP_CTYPE_STATUS }, + { BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT, BT_AVRCP_CTYPE_STATUS }, + { BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, BT_AVRCP_CTYPE_CONTROL }, + { BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, BT_AVRCP_CTYPE_CONTROL }, + { BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, BT_AVRCP_CTYPE_STATUS }, + { BT_AVRCP_PDU_ID_GET_PLAY_STATUS, BT_AVRCP_CTYPE_STATUS }, + { BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, BT_AVRCP_CTYPE_NOTIFY }, + { BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, BT_AVRCP_CTYPE_CONTROL }, + { BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, BT_AVRCP_CTYPE_CONTROL }, + { BT_AVRCP_PDU_ID_PLAY_ITEM, BT_AVRCP_CTYPE_CONTROL }, + { BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, BT_AVRCP_CTYPE_CONTROL }, + { BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP, BT_AVRCP_CTYPE_CONTROL }, + { BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP, BT_AVRCP_CTYPE_CONTROL }, +}; + +NET_BUF_POOL_FIXED_DEFINE(avrcp_pool, CONFIG_BT_MAX_CONN*3, + CONFIG_BT_AVRCP_DATA_BUF_SIZE, + CONFIG_BT_CONN_TX_USER_DATA_SIZE, NULL); + struct avrcp_pdu_handler { bt_avrcp_pdu_id_t pdu_id; uint8_t min_len; @@ -304,6 +353,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; @@ -452,8 +511,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_pool); + if (buf == NULL) { return NULL; } @@ -571,1081 +630,3374 @@ 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, + uint16_t total_len) { - 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; + return -ENOMEM; + } - 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_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; + ctx->total_len = total_len; + ctx->received_len = 0U; + return 0; +} + +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; } - if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_cap_rsp == NULL)) { - return; + __ASSERT_NO_MSG(ct->reassembly_buf->user_data_size >= sizeof(*ctx)); + ctx = net_buf_user_data(ct->reassembly_buf); + + return ctx; +} + +static int add_fragment_data(struct bt_avrcp_ct *ct, const uint8_t *data, uint16_t data_len) +{ + struct bt_avrcp_ct_frag_reassembly_ctx *ctx; + + if ((ct == NULL) || (data == NULL) || (ct->reassembly_buf == NULL)) { + return -EINVAL; } - rsp = (struct bt_avrcp_get_cap_rsp *)buf->data; + __ASSERT_NO_MSG(ct->reassembly_buf->user_data_size >= sizeof(*ctx)); + 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; + /* Check if adding this fragment would exceed total expected length */ + if ((ctx->received_len + data_len) > ctx->total_len) { + LOG_ERR("Fragment data exceeds expected total length"); + return -EINVAL; } - if (buf->len < sizeof(*rsp) + expected_len) { - LOG_ERR("Invalid capability payload length: %d", buf->len); + if (net_buf_tailroom(ct->reassembly_buf) < data_len) { + LOG_ERR("Insufficient space in reassembly buffer"); + return -ENOMEM; + } + + /* Add fragment data to reassembly buffer */ + net_buf_add_mem(ct->reassembly_buf, data, data_len); + ctx->received_len += data_len; + + if (ctx->received_len >= ctx->total_len) { + LOG_ERR("Fragment data exceeds expected total length"); + return -ENOMEM; + } + return 0; +} + +static void cleanup_fragmentation_context(struct bt_avrcp_ct *ct) +{ + if (ct == NULL) { return; } - avrcp_ct_cb->get_cap_rsp(get_avrcp_ct(avrcp), tid, rsp); + if (ct->reassembly_buf != NULL) { + net_buf_unref(ct->reassembly_buf); + ct->reassembly_buf = NULL; + } } -static void avrcp_vendor_dependent_rsp_handler(struct bt_avrcp *avrcp, uint8_t tid, - struct net_buf *buf) +static inline bt_avrcp_ctype_t get_cmd_type_by_pdu(uint8_t pdu_id) +{ + ARRAY_FOR_EACH(avrcp_pdu_cmd_table, i) { + if (avrcp_pdu_cmd_table[i].pdu_id == pdu_id) { + return avrcp_pdu_cmd_table[i].cmd_type; + } + } + LOG_WRN("Unknown PDU ID: 0x%02X", pdu_id); + return (bt_avrcp_ctype_t)-1; +} + +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_pdu *pdu; - struct bt_avrcp_header *avrcp_hdr; - uint32_t company_id; - uint8_t pdu_id; + size_t required_size = sizeof(*pdu); - if (buf->len < (sizeof(*avrcp_hdr) + BT_AVRCP_COMPANY_ID_SIZE + sizeof(pdu_id))) { - LOG_ERR("Invalid vendor frame length: %d", buf->len); - return; + if (avrcp == NULL) { + return NULL; } - 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; + buf = avrcp_create_vendor_pdu(avrcp, avrcp_type); + if (buf == NULL) { + LOG_WRN("Insufficient buffer"); + return NULL; } - pdu = (struct bt_avrcp_avc_pdu *)buf->data; - - 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 (net_buf_tailroom(buf) < required_size) { + 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_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_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 -ENOMEM; + } + + if (data_len > 0U) { + if (net_buf_tailroom(buf) < data_len) { + LOG_WRN("Not enough tailroom: required"); + 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_send_req_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t rsp, + uint8_t 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, + rsp, sizeof(pdu_id)); + if (buf == NULL) { + return -ENOMEM; + } - avrcp_ct_cb->passthrough_rsp(get_avrcp_ct(avrcp), tid, result, rsp); + /* Add pdu_id status */ + if (net_buf_tailroom(buf) < sizeof(pdu_id)) { + LOG_WRN("Not enough tailroom for pdu_id"); + net_buf_unref(buf); + return -ENOMEM; } -} + net_buf_add_u8(buf, pdu_id); -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}, -}; + return avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); +} -static void avrcp_unit_info_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, struct net_buf *buf) +static int bt_avrcp_tg_send_vendor_err_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t pdu_id, uint8_t err_code) { - 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; + struct net_buf *buf; 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; + if ((tg == NULL) || (tg->avrcp == NULL)) { + return -EINVAL; } - 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; + buf = avrcp_prepare_vendor_pdu(tg->avrcp, BT_AVRCP_PKT_TYPE_SINGLE, BT_AVRCP_RSP_REJECTED, + pdu_id, 1U); + if (buf == NULL) { + return -ENOMEM; } - 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; + if (net_buf_tailroom(buf) < sizeof(err_code)) { + LOG_WRN("Not enough tailroom for err_code"); + net_buf_unref(buf); + return -ENOMEM; } + net_buf_add_u8(buf, err_code); - 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)) { + 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 void avrcp_vendor_dependent_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, - struct net_buf *buf) +static int bt_avrcp_ct_send_req_continuing_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t pdu_id) { -/* ToDo */ + return bt_avrcp_ct_send_req_rsp(ct, tid, BT_AVRCP_PDU_ID_REQ_CONTINUING_RSP, pdu_id); } -static void avrcp_subunit_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; - uint8_t page; - uint8_t extension_code; - int err; + tid++; + tid &= 0x0F; + return bt_avrcp_ct_send_req_rsp(ct, tid, BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP, pdu_id); +} - if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->subunit_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_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_SUBUNIT_INFO_CMD_SIZE) { - LOG_ERR("Invalid subunit info length"); - goto err_rsp; + node = sys_slist_peek_head(&tg->tx_pending); + if (!node) { + LOG_WRN("No pending tx"); + avrcp_tg_unlock(tg); + return; } - subunit_type = BT_AVRCP_HDR_GET_SUBUNIT_TYPE(avrcp_hdr); + 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); + + tx->state = state; + tx->tid = tid; + + 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_tg_tx_remove(struct bt_avrcp_tg *tg, struct net_buf *buf) +{ + avrcp_tg_lock(tg); + sys_slist_find_and_remove(&tg->tx_pending, &buf->node); + avrcp_tg_unlock(tg); +} + +static void bt_avrcp_tg_vendor_tx_work(struct k_work *work) +{ + struct k_work_delayable *dwork = k_work_delayable_from_work(work); + struct bt_avrcp_tg *tg = CONTAINER_OF(dwork, struct bt_avrcp_tg, tx_work); + sys_snode_t *node; + struct net_buf *buf; + struct bt_avrcp_tg_tx *tx; + uint16_t max_payload_size; + uint16_t sent_len = 0U; + int err; + + avrcp_tg_lock(tg); + + node = sys_slist_peek_head(&tg->tx_pending); + if (node == NULL) { + LOG_WRN("No pending tx"); + avrcp_tg_unlock(tg); + return; + } + avrcp_tg_unlock(tg); + + buf = CONTAINER_OF(node, struct net_buf, node); + + __ASSERT_NO_MSG(buf->user_data_size >= sizeof(*tx)); + tx = net_buf_user_data(buf); + + /* Calculate maximum payload size per fragment */ + max_payload_size = BT_AVRCP_FRAGMENT_SIZE - sizeof(struct bt_avrcp_header) + - sizeof(struct bt_avrcp_avc_pdu); + + /* 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 fragment at offset %u", sent_len); + goto done; + } + avrcp_tg_tx_remove(tg, buf); + 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_ERR("Abort to continuting send OR REJECT"); + struct net_buf *rsp_buf; + uint8_t error_code = BT_AVRCP_STATUS_INVALID_PARAMETER; + + rsp_buf = bt_avrcp_create_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); + } + + 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; + } + + /* 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; + } + } + + /* Send fragment */ + err = send_fragmented_vendor_rsp(tx, pkt_type, buf->data, chunk_size); + if (err < 0) { + LOG_ERR("Failed to send fragment at offset %u", tx->sent_len); + goto done; + } + + if (buf->len < chunk_size) { + LOG_WRN("Not enough buf len (%d < %d)", buf->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->tx_work, K_NO_WAIT); +} + +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_tg_tx *tx; + + if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) { + net_buf_unref(buf); + 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->tx_pending, &buf->node); + avrcp_tg_unlock(tg); + + k_work_reschedule(&tg->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) +{ + struct bt_avrcp_get_caps_rsp *rsp; + uint16_t expected_len; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_caps_rsp == NULL)) { + return; + } + + rsp = (struct bt_avrcp_get_caps_rsp *)buf->data; + + 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; + } + + if (buf->len < sizeof(*rsp) + expected_len) { + LOG_ERR("Invalid capability payload length: %d", buf->len); + return; + } + + avrcp_ct_cb->get_caps_rsp(get_avrcp_ct(avrcp), tid, rsp); +} + +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; + uint16_t expected_len; + struct bt_avrcp_ct *ct = get_avrcp_ct(avrcp); + + /* 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; + 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 registered on interim response */ + ct->ct_notify[event_id].registered = 1; + return; + } + + if ((ct->ct_notify[event_id].cb != NULL) && + (ct->ct_notify[event_id].registered == 1) && + (rsp_code == BT_AVRCP_RSP_CHANGED)) { + ct->ct_notify[event_id].cb(event_id, (struct bt_avrcp_event_data *)event_data); + } +} + +static void process_set_absolute_volume_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + uint8_t absolute_volume; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->set_absolute_volume_rsp == NULL)) { + return; + } + if (rsp_code != BT_AVRCP_RSP_ACCEPTED) { + LOG_ERR("Invalid Set Absolute Volume response type: 0x%02x", rsp_code); + return; + } + if (buf->len < sizeof(absolute_volume)) { + LOG_ERR("Invalid Set 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, rsp_code, 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) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->list_player_app_setting_attrs_rsp == NULL)) { + return; + } + + avrcp_ct_cb->list_player_app_setting_attrs_rsp(get_avrcp_ct(avrcp), tid, rsp_code, 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) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->list_player_app_setting_vals_rsp == NULL)) { + return; + } + + avrcp_ct_cb->list_player_app_setting_vals_rsp(get_avrcp_ct(avrcp), tid, rsp_code, 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) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_curr_player_app_setting_val_rsp == NULL)) { + return; + } + + avrcp_ct_cb->get_curr_player_app_setting_val_rsp(get_avrcp_ct(avrcp), tid, rsp_code, 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) +{ + ARG_UNUSED(buf); + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->set_player_app_setting_val_rsp == NULL)) { + return; + } + avrcp_ct_cb->set_player_app_setting_val_rsp(get_avrcp_ct(avrcp), tid, rsp_code); +} + +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) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_player_app_setting_attr_text_rsp == NULL)) { + return; + } + + avrcp_ct_cb->get_player_app_setting_attr_text_rsp(get_avrcp_ct(avrcp), tid, rsp_code, 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) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_player_app_setting_val_text_rsp == NULL)) { + return; + } + + avrcp_ct_cb->get_player_app_setting_val_text_rsp(get_avrcp_ct(avrcp), tid, rsp_code, buf); +} + +static void process_inform_displayable_char_set_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + ARG_UNUSED(buf); + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->inform_displayable_char_set_rsp == NULL)) { + return; + } + + avrcp_ct_cb->inform_displayable_char_set_rsp(get_avrcp_ct(avrcp), tid, rsp_code); +} + +static void process_inform_batt_status_of_ct_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + ARG_UNUSED(buf); + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->inform_batt_status_of_ct_rsp == NULL)) { + return; + } + + avrcp_ct_cb->inform_batt_status_of_ct_rsp(get_avrcp_ct(avrcp), tid, rsp_code); +} + +static void process_get_element_attrs_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_element_attrs_rsp == NULL)) { + return; + } + + avrcp_ct_cb->get_element_attrs_rsp(get_avrcp_ct(avrcp), tid, rsp_code, buf); +} + +static void process_get_play_status_rsp(struct bt_avrcp *avrcp, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->get_play_status_rsp == NULL)) { + return; + } + + avrcp_ct_cb->get_play_status_rsp(get_avrcp_ct(avrcp), tid, rsp_code, 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; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->set_addressed_player_rsp == NULL)) { + return; + } + + status = net_buf_pull_u8(buf); + avrcp_ct_cb->set_addressed_player_rsp(get_avrcp_ct(avrcp), tid, rsp_code, 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; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->play_item_rsp == NULL)) { + return; + } + + status = net_buf_pull_u8(buf); + avrcp_ct_cb->play_item_rsp(get_avrcp_ct(avrcp), tid, rsp_code, 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; + + if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->add_to_now_playing_rsp == NULL)) { + return; + } + + status = net_buf_pull_u8(buf); + avrcp_ct_cb->add_to_now_playing_rsp(get_avrcp_ct(avrcp), tid, rsp_code, status); +} + +static const struct avrcp_pdu_vendor_handler rsp_vendor_handlers[] = { + {BT_AVRCP_PDU_ID_GET_CAPS, sizeof(struct bt_avrcp_get_caps_rsp), process_get_caps_rsp}, + {BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, sizeof(uint8_t), process_register_notification_rsp}, + {BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, sizeof(uint8_t), process_set_absolute_volume_rsp}, + { BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, + sizeof(struct bt_avrcp_list_app_setting_attr_rsp), + process_list_player_app_setting_attrs_rsp }, + { BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, 0, + process_list_player_app_setting_vals_rsp}, + { BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL, + sizeof(struct bt_avrcp_get_curr_player_app_setting_val_rsp), + process_get_curr_player_app_setting_val_rsp }, + { BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, 0, process_set_player_app_setting_val_rsp }, + { BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT, + sizeof(struct bt_avrcp_get_player_app_setting_attr_text_rsp), + process_get_player_app_setting_attr_text_rsp }, + { BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT, + sizeof(struct bt_avrcp_get_player_app_setting_val_text_rsp), + process_get_player_app_setting_val_text_rsp }, + { BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, 0, process_inform_displayable_char_set_rsp }, + { BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, 0, process_inform_batt_status_of_ct_rsp }, + { BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, sizeof(struct bt_avrcp_get_element_attrs_rsp), + process_get_element_attrs_rsp }, + { BT_AVRCP_PDU_ID_GET_PLAY_STATUS, sizeof(struct bt_avrcp_get_play_status_rsp), + process_get_play_status_rsp }, + { BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, sizeof(uint8_t), process_set_addressed_player_rsp }, + { BT_AVRCP_PDU_ID_PLAY_ITEM, sizeof(uint8_t), process_play_item_rsp }, + { BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, sizeof(uint8_t), process_add_to_now_playing_rsp }, +}; + +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, struct bt_avrcp_avc_pdu *pdu, + uint8_t tid, uint8_t rsp_code, struct net_buf *buf) +{ + uint16_t param_len; + + if (pdu == NULL || buf == NULL) { + LOG_ERR("Invalid parameters"); + return; + } + + param_len = sys_be16_to_cpu(pdu->param_len); + if (buf->len < param_len) { + LOG_ERR("Invalid vendor param length: %u > buf len %u", param_len, buf->len); + return; + } + + handle_vendor_pdu(avrcp, tid, buf, rsp_code, pdu->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_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; + uint8_t pdu_id; + int err; + + if (buf->len < (sizeof(*avrcp_hdr) + BT_AVRCP_COMPANY_ID_SIZE + sizeof(pdu_id))) { + 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; + } + + if (buf->len < sizeof(*pdu)) { + LOG_ERR("Invalid vendor payload length: %u", buf->len); + 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; + } + 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, tid, rsp, buf); + break; + + case BT_AVRCP_PKT_TYPE_START: + err = init_fragmentation_context(get_avrcp_ct(avrcp), tid, rsp, + CONFIG_BT_AVRCP_DATA_BUF_SIZE); + 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, 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; + uint8_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_cap_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + 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_cap_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 GetElementAttributes 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) +{ + bt_avrcp_evt_t event_id; + uint32_t interval = 0U; /* Default value */ + uint8_t error_code; + int err; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->register_notification_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + event_id = net_buf_pull_u8(buf); + if (event_id == BT_AVRCP_EVT_PLAYBACK_POS_CHANGED) { + interval = net_buf_pull_be32(buf); + } + + return avrcp_tg_cb->register_notification_req(get_avrcp_tg(avrcp), tid, event_id, interval); + +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_INTERNAL_ERROR; + 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; + } + return avrcp_tg_cb->set_absolute_volume_req(get_avrcp_tg(avrcp), tid, absolute_volume); + +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 err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->list_player_app_setting_attrs_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->list_player_app_setting_attrs_req(get_avrcp_tg(avrcp), tid); + +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 err; + int error_code; + uint8_t attr_id; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->list_player_app_setting_vals_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + 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; + } + + return avrcp_tg_cb->list_player_app_setting_vals_req(get_avrcp_tg(avrcp), tid, attr_id); + +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 err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_curr_player_app_setting_val_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->get_curr_player_app_setting_val_req(get_avrcp_tg(avrcp), tid, buf); + +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 err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->set_player_app_setting_val_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->set_player_app_setting_val_req(get_avrcp_tg(avrcp), tid, buf); + +err_rsp: + err = bt_avrcp_tg_send_vendor_err_rsp(get_avrcp_tg(avrcp), tid, + BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, + BT_AVRCP_STATUS_INTERNAL_ERROR); + 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 err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_player_app_setting_attr_text_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->get_player_app_setting_attr_text_req(get_avrcp_tg(avrcp), tid, buf); + +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 err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_player_app_setting_val_text_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->get_player_app_setting_val_text_req(get_avrcp_tg(avrcp), tid, buf); + +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 err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->inform_displayable_char_set_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->inform_displayable_char_set_req(get_avrcp_tg(avrcp), tid, buf); + +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) +{ + int err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->inform_batt_status_of_ct_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->inform_batt_status_of_ct_req(get_avrcp_tg(avrcp), tid, buf); + +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 err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_element_attrs_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->get_element_attrs_req(get_avrcp_tg(avrcp), tid, buf); + +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 err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->get_play_status_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->get_play_status_req(get_avrcp_tg(avrcp), tid); + +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) +{ + int err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->set_addressed_player_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->set_addressed_player_req(get_avrcp_tg(avrcp), tid, buf); + +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 err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->play_item_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->play_item_req(get_avrcp_tg(avrcp), tid, buf); + +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 err; + int error_code; + + if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->add_to_now_playing_req == NULL)) { + error_code = BT_AVRCP_STATUS_INTERNAL_ERROR; + goto err_rsp; + } + + return avrcp_tg_cb->add_to_now_playing_req(get_avrcp_tg(avrcp), tid, buf); + +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)->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)->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), handle_avrcp_continuing_rsp}, + {BT_AVRCP_PDU_ID_ABORT_CONTINUING_RSP, sizeof(uint8_t), handle_avrcp_abort_continuing_rsp}, + {BT_AVRCP_PDU_ID_GET_CAPS, sizeof(uint8_t), process_get_caps_cmd}, + {BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, sizeof(struct bt_avrcp_register_notification_cmd), + avrcp_register_notification_cmd_handler}, + {BT_AVRCP_PDU_ID_SET_ABSOLUTE_VOLUME, sizeof(uint8_t), process_set_absolute_volume_cmd}, + {BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, 0, + 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), + 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), + 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), + 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), + 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), + 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), + 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), + process_inform_batt_status_of_ct_cmd}, + {BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, sizeof(struct bt_avrcp_get_element_attrs_cmd), + process_get_element_attrs_cmd}, + {BT_AVRCP_PDU_ID_GET_PLAY_STATUS, 0, process_get_play_status_cmd}, + {BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, sizeof(struct bt_avrcp_set_addressed_player_cmd), + process_set_addressed_player_cmd}, + {BT_AVRCP_PDU_ID_PLAY_ITEM, sizeof(struct bt_avrcp_play_item_cmd), process_play_item_cmd}, + {BT_AVRCP_PDU_ID_ADD_TO_NOW_PLAYING, sizeof(struct bt_avrcp_add_to_now_playing_cmd), + 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_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 ADD_TO_NOW_PLAYING error response"); + } +} + +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); - ctype = BT_AVRCP_HDR_GET_CTYPE_OR_RSP(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_avc_brow_pdu *brow; + + 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; + } + + brow = net_buf_pull_mem(buf, sizeof(struct bt_avrcp_avc_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].tx_work, bt_avrcp_tg_vendor_tx_work); + sys_slist_init(&bt_avrcp_tg_pool[i].tx_pending); + + + k_sem_init(&bt_avrcp_tg_pool[i].lock, 1, 1); + + } + 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, + sizeof(struct bt_l2cap_hdr) + + sizeof(struct bt_avctp_header_start) + + sizeof(struct bt_avrcp_header) + + sizeof(struct bt_avrcp_avc_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)); - /* 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]); + 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 ((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; +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; } - return avrcp_tg_cb->subunit_info_req(get_avrcp_tg(avrcp), tid); + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } -err_rsp: - err = bt_avrcp_send_subunit_info(avrcp, tid, BT_AVRCP_RSP_REJECTED); + 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 subunit info error response"); - if (bt_avrcp_disconnect(avrcp->acl_conn)) { - LOG_ERR("Failed to disconnect AVRCP connection"); - } + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + net_buf_unref(buf); } + return err; } -static void avrcp_pass_through_cmd_handler(struct bt_avrcp *avrcp, uint8_t tid, - struct net_buf *buf) +#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 bt_avrcp_header *avrcp_hdr; - struct net_buf *rsp_buf; + struct net_buf *buf; + struct bt_avrcp_avc_brow_pdu *pdu; int err; - if ((avrcp_tg_cb == NULL) || (avrcp_tg_cb->passthrough_req == NULL)) { - goto err_rsp; + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; } - if (buf->len < (sizeof(*avrcp_hdr) + sizeof(struct bt_avrcp_passthrough_cmd))) { - LOG_ERR("Invalid passthrough command length: %d", buf->len); - goto err_rsp; + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; } - 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; + if (ct->avrcp->browsing_session.br_chan.chan.conn == NULL) { + LOG_ERR("Browsing session not connected"); + return -ENOTCONN; } - return avrcp_tg_cb->passthrough_req(get_avrcp_tg(avrcp), tid, buf); + buf = avrcp_create_browsing_pdu(ct->avrcp); + if (buf == NULL) { + return -ENOMEM; + } -err_rsp: - rsp_buf = bt_avrcp_create_pdu(NULL); - if (rsp_buf == NULL) { - LOG_ERR("Failed to allocate response buffer"); - return; + 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; } - err = bt_avrcp_tg_send_passthrough_rsp(get_avrcp_tg(avrcp), tid, BT_AVRCP_RSP_REJECTED, - rsp_buf); + 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 passthrough error response"); - net_buf_unref(rsp_buf); - if (bt_avrcp_disconnect(avrcp->acl_conn)) { - LOG_ERR("Failed to disconnect AVRCP connection"); - } + LOG_ERR("Failed to send AVRCP browsing PDU (err: %d)", err); + net_buf_unref(buf); } + return err; } +#endif /* CONFIG_BT_AVRCP_BROWSING */ -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}, -}; +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; -/* An AVRCP message received */ -static int avrcp_recv(struct bt_avctp *session, struct net_buf *buf, bt_avctp_cr_t cr, uint8_t tid) + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + 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].registered = 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 *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; + struct bt_avrcp_header *hdr; + struct bt_avrcp_avc_pdu *pdu; + uint16_t param_len; + 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; - } - } + 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_cap(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; + } + + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + return -ENOMEM; + } + + 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; + } + + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + return -ENOMEM; + } + + if (net_buf_tailroom(buf) < sizeof(absolute_volume)) { + LOG_WRN("Not enough headroom: 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; } - LOG_WRN("received unknown opcode : 0x%02X", avrcp_hdr->opcode); - return 0; + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + return -ENOMEM; + } + + 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; } -static void init_avctp_control_channel(struct bt_avctp *session) +int bt_avrcp_ct_list_player_app_setting_vals(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t attr_id) { - 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; -} + int err; + struct net_buf *buf; -static const struct bt_avctp_ops_cb avctp_ops = { - .connected = avrcp_connected, - .disconnected = avrcp_disconnected, - .recv = avrcp_recv, -}; + if ((ct == NULL) || (ct->avrcp == NULL)) { + return -EINVAL; + } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } -static int avrcp_accept(struct bt_conn *conn, struct bt_avctp **session) -{ - struct bt_avrcp *avrcp; + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + return -ENOMEM; + } - avrcp = avrcp_get_connection(conn); - if (avrcp == NULL) { + if (net_buf_tailroom(buf) < sizeof(attr_id)) { + LOG_WRN("Not enough headroom: for attr_id"); + net_buf_unref(buf); return -ENOMEM; } - if (avrcp->acl_conn != NULL) { - return -EALREADY; + 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; +} - init_avctp_control_channel(&(avrcp->session)); - *session = &(avrcp->session); - avrcp->session.ops = &avctp_ops; - avrcp->acl_conn = bt_conn_ref(conn); +int bt_avrcp_ct_get_curr_player_app_setting_val(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf) +{ + int err; - LOG_DBG("session: %p", &(avrcp->session)); + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; + } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } - return 0; + 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; } -#if defined(CONFIG_BT_AVRCP_BROWSING) -static void init_avctp_browsing_channel(struct bt_avctp *session) +int bt_avrcp_ct_set_player_app_setting_val(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; + } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + 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; } -/* The AVCTP L2CAP channel established */ -static void browsing_avrcp_connected(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) { - struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); + int err; - 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 ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; + } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; } - 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_GET_PLAYER_APP_SETTING_ATTR_TEXT, 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_get_player_app_setting_val_text(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf) { - struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); + int err; - 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) || (buf == 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; + } + + 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; } -static int avrcp_ct_handle_set_browsed_player(struct bt_avrcp *avrcp, - uint8_t tid, struct net_buf *buf) +int bt_avrcp_ct_inform_displayable_char_set(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf) { - if ((avrcp_ct_cb == NULL) || (avrcp_ct_cb->browsed_player_rsp == NULL)) { + int err; + + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { return -EINVAL; } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } - avrcp_ct_cb->browsed_player_rsp(get_avrcp_ct(avrcp), tid, buf); - - return 0; + 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; } -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) +int bt_avrcp_ct_inform_batt_status_of_ct(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; + } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; } - player_id = net_buf_pull_be16(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; +} - LOG_DBG("Set browsed player request: player_id=0x%04x", player_id); +int bt_avrcp_ct_get_element_attrs(struct bt_avrcp_ct *ct, uint8_t tid, + struct net_buf *buf) +{ + int err; - avrcp_tg_cb->set_browsed_player_req(get_avrcp_tg(avrcp), tid, player_id); - return 0; + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; + } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; + } -error_rsp: - rsp_buf = bt_avrcp_create_pdu(NULL); - __ASSERT(rsp_buf != NULL, "Failed to allocate response buffer"); + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, buf); + if (err < 0) { + LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + } + return err; +} - if (net_buf_tailroom(rsp_buf) < sizeof(uint8_t)) { - LOG_ERR("Insufficient space in response buffer"); - net_buf_unref(rsp_buf); +int bt_avrcp_ct_get_play_status(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; + } + + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { 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); + err = bt_avrcp_ct_vendor_dependent(ct, tid, BT_AVRCP_PDU_ID_GET_PLAY_STATUS, 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_set_addressed_player(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) { - 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; - } + int err; - return handler->func(avrcp, tid, buf); + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; + } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; } - return -EOPNOTSUPP; + 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); + } + 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_play_item(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) { - struct bt_avrcp *avrcp = AVRCP_BROW_AVCTP(session); - struct bt_avrcp_avc_brow_pdu *brow; + int err; - 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) || (buf == NULL)) { + return -EINVAL; + } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; } - brow = net_buf_pull_mem(buf, sizeof(struct bt_avrcp_avc_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; + 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; +} - LOG_DBG("AVRCP browsing msg received, cr:0x%X, tid:0x%X, pdu_id:0x%02X", cr, - tid, brow->pdu_id); +int bt_avrcp_ct_add_to_now_playing(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) +{ + int err; - if (cr == BT_AVCTP_RESPONSE) { - return handle_pdu(avrcp, tid, buf, brow->pdu_id, rsp_brow_handlers, - ARRAY_SIZE(rsp_brow_handlers)); + if ((ct == NULL) || (ct->avrcp == NULL) || (buf == NULL)) { + return -EINVAL; + } + if (!IS_CT_ROLE_SUPPORTED()) { + return -ENOTSUP; } - return handle_pdu(avrcp, tid, buf, brow->pdu_id, cmd_brow_handlers, - ARRAY_SIZE(cmd_brow_handlers)); + 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; } -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) +int bt_avrcp_ct_register_cb(const struct bt_avrcp_ct_cb *cb) { - struct bt_avrcp *avrcp; + if (!cb) { + return -EINVAL; + } - avrcp = avrcp_get_connection(conn); - if (avrcp == NULL) { - LOG_ERR("Cannot allocate memory"); - return -ENOTCONN; + if (avrcp_ct_cb) { + return -EALREADY; } - if (avrcp->acl_conn == NULL) { - LOG_ERR("The control channel not established"); - return -ENOTCONN; + avrcp_ct_cb = cb; + + return 0; +} + +int bt_avrcp_tg_register_cb(const struct bt_avrcp_tg_cb *cb) +{ + if (!cb) { + return -EINVAL; } - if (avrcp->browsing_session.br_chan.chan.conn != NULL) { - LOG_ERR("Browsing session already connected"); + if (avrcp_tg_cb) { 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)); + avrcp_tg_cb = cb; return 0; } -#endif /* CONFIG_BT_AVRCP_BROWSING */ -int bt_avrcp_init(void) +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; - /* 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 ((tg == NULL) || (tg->avrcp == NULL) || (rsp == 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_TG_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 */ + buf = avrcp_create_unit_pdu(tg->avrcp, BT_AVRCP_RSP_STABLE); + if (!buf) { + LOG_WRN("Insufficient buffer"); + return -ENOMEM; + } -#if defined(CONFIG_BT_AVRCP_CONTROLLER) - bt_sdp_register_service(&avrcp_ct_rec); -#endif /* CONFIG_BT_AVRCP_CONTROLLER */ + /* 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)); - /* 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"); + 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; +} - ARRAY_FOR_EACH(avrcp_connection, i) { - bt_avrcp_ct_pool[i].avrcp = &avrcp_connection[i]; - bt_avrcp_tg_pool[i].avrcp = &avrcp_connection[i]; +int bt_avrcp_tg_send_subunit_info_rsp(struct bt_avrcp_tg *tg, uint8_t tid) +{ + if ((tg == NULL) || (tg->avrcp == NULL)) { + return -EINVAL; } - LOG_DBG("AVRCP Initialized successfully."); - return 0; + if (!IS_TG_ROLE_SUPPORTED()) { + return -ENOTSUP; + } + + return bt_avrcp_send_subunit_info(tg->avrcp, tid, BT_AVRCP_RSP_STABLE); } -int bt_avrcp_connect(struct bt_conn *conn) +static int build_get_caps_rsp_data(const struct bt_avrcp_get_caps_rsp *rsp, + struct net_buf *buf) { - struct bt_avrcp *avrcp; - int err; + uint16_t param_len; + uint8_t cap_item_size; - avrcp = avrcp_get_connection(conn); - if (avrcp == NULL) { - LOG_ERR("Cannot allocate memory"); - return -ENOTCONN; + /* Validate capability ID and calculate parameter length */ + switch (rsp->cap_id) { + case BT_AVRCP_CAP_COMPANY_ID: + cap_item_size = BT_AVRCP_COMPANY_ID_SIZE; + break; + case BT_AVRCP_CAP_EVENTS_SUPPORTED: + cap_item_size = 1U; + break; + default: + LOG_ERR("Invalid capability ID: 0x%02x", rsp->cap_id); + net_buf_unref(buf); + return -EINVAL; } - if (avrcp->acl_conn != NULL) { - return -EALREADY; - } + param_len = sizeof(rsp->cap_id) + sizeof(rsp->cap_cnt) + + (rsp->cap_cnt * cap_item_size); - 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; + if (net_buf_tailroom(buf) < param_len) { + LOG_ERR("Not enough space in net_buf"); + return -ENOMEM; } - avrcp->acl_conn = bt_conn_ref(conn); - LOG_DBG("Connection request sent"); - return err; + /* Add capability ID */ + net_buf_add_u8(buf, rsp->cap_id); + + /* Add capability count */ + net_buf_add_u8(buf, rsp->cap_cnt); + + /* Add capability data */ + if (rsp->cap_cnt > 0U) { + net_buf_add_mem(buf, rsp->cap, rsp->cap_cnt * cap_item_size); + } + return 0; } -int bt_avrcp_disconnect(struct bt_conn *conn) +int bt_avrcp_tg_send_get_caps_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + const struct bt_avrcp_get_caps_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)); + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOMEM; + } + + err = build_get_caps_rsp_data(rsp, buf); if (err < 0) { - LOG_DBG("AVCTP Disconnect failed"); + net_buf_unref(buf); return err; } + err = bt_avrcp_tg_send_vendor_rsp(tg, tid, BT_AVRCP_PDU_ID_GET_CAPS, BT_AVRCP_RSP_STABLE, + buf); + if (err < 0) { + LOG_ERR("Failed to send Get Capabilities response (err: %d)", err); + net_buf_unref(buf); + } return err; } -struct net_buf *bt_avrcp_create_pdu(struct net_buf_pool *pool) -{ - return bt_conn_create_pdu(pool, - sizeof(struct bt_l2cap_hdr) + - sizeof(struct bt_avctp_header_start) + - sizeof(struct bt_avrcp_header)); -} - #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_avc_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_avc_brow_pdu)) { + LOG_ERR("Not enough headroom in buffer for bt_avrcp_avc_brow_pdu"); + 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); - 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; + default: + return -EINVAL; + } + + if (net_buf_tailroom(buf) < param_len) { + LOG_ERR("Not enough space in net_buf"); + return -ENOMEM; } - err = bt_avctp_disconnect(&(avrcp->browsing_session)); - if (err < 0) { - LOG_ERR("AVCTP browsing disconnect failed"); - return err; + 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; + 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 type, + uint8_t event_id, struct bt_avrcp_event_data *data) { struct net_buf *buf; - struct bt_avrcp_avc_pdu *pdu; int err; - if ((ct == NULL) || (ct->avrcp == NULL)) { + if ((tg == NULL) || (tg->avrcp == NULL) || (data == 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) { + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); return -ENOMEM; } - 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); + err = build_notification_rsp_data(event_id, data, buf); + if (err < 0) { + net_buf_unref(buf); + return err; + } - err = avrcp_send(ct->avrcp, buf, BT_AVCTP_CMD, tid); + err = bt_avrcp_tg_send_vendor_rsp(tg, tid, BT_AVRCP_PDU_ID_REGISTER_NOTIFICATION, + type, 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, uint8_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 rsp_code, struct net_buf *buf) { - struct net_buf *buf; - uint8_t param[5]; 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; - } - - 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); + 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_absolute_volume_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t rsp_code, + uint8_t absolute_volume) { struct net_buf *buf; int err; + uint16_t param_len = sizeof(absolute_volume); - if ((ct == NULL) || (ct->avrcp == NULL)) { + if (absolute_volume > BT_AVRCP_MAX_ABSOLUTE_VOLUME) { return -EINVAL; } - if (!IS_CT_ROLE_SUPPORTED()) { - return -ENOTSUP; - } - - buf = avrcp_create_passthrough_pdu(ct->avrcp, BT_AVRCP_CTYPE_CONTROL); - if (!buf) { + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); 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); + if (net_buf_tailroom(buf) < param_len) { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); + return -ENOMEM; } + net_buf_add_u8(buf, absolute_volume); - err = avrcp_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, + rsp_code, buf); if (err < 0) { - LOG_ERR("Failed to send AVRCP PDU (err: %d)", err); + LOG_ERR("Failed to absolute volume (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) +int bt_avrcp_tg_send_list_player_app_setting_attrs_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) { - struct net_buf *buf; - struct bt_avrcp_avc_brow_pdu *pdu; - int err; + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_ATTRS, + rsp_code, buf); +} - if ((ct == NULL) || (ct->avrcp == NULL)) { - return -EINVAL; - } +int bt_avrcp_tg_send_list_player_app_setting_vals_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_LIST_PLAYER_APP_SETTING_VALS, + rsp_code, buf); +} - if (!IS_CT_ROLE_SUPPORTED()) { - return -ENOTSUP; - } +int bt_avrcp_tg_send_get_curr_player_app_setting_val_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_GET_CURR_PLAYER_APP_SETTING_VAL + , rsp_code, buf); +} - if (ct->avrcp->browsing_session.br_chan.chan.conn == NULL) { - LOG_ERR("Browsing session not connected"); - return -ENOTCONN; - } +int bt_avrcp_tg_send_set_player_app_setting_val_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t rsp_code) +{ + struct net_buf *buf; - buf = avrcp_create_browsing_pdu(ct->avrcp); + buf = bt_avrcp_create_pdu(&avrcp_pool); if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); 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; - } + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_SET_PLAYER_APP_SETTING_VAL, + rsp_code, buf); +} - 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); +int bt_avrcp_tg_send_get_player_app_setting_attr_text_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t rsp_code, + struct net_buf *buf) +{ + uint8_t pud_id = BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_ATTR_TEXT; - 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; + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, pud_id, rsp_code, buf); } -#endif /* CONFIG_BT_AVRCP_BROWSING */ -int bt_avrcp_ct_register_cb(const struct bt_avrcp_ct_cb *cb) +int bt_avrcp_tg_send_get_player_app_setting_val_text_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) { - if (!cb) { - return -EINVAL; - } + uint8_t pud_id = BT_AVRCP_PDU_ID_GET_PLAYER_APP_SETTING_VAL_TEXT; - if (avrcp_ct_cb) { - return -EALREADY; - } + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, pud_id, rsp_code, buf); +} - avrcp_ct_cb = cb; +int bt_avrcp_tg_send_inform_displayable_char_set_rsp(struct bt_avrcp_tg *tg, + uint8_t tid, uint8_t rsp_code) +{ + struct net_buf *buf; - return 0; + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOMEM; + } + + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_INFORM_DISPLAYABLE_CHAR_SET, + rsp_code, buf); } -int bt_avrcp_tg_register_cb(const struct bt_avrcp_tg_cb *cb) +int bt_avrcp_tg_send_inform_batt_status_of_ct_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t rsp_code) { - if (!cb) { - return -EINVAL; - } + struct net_buf *buf; - if (avrcp_tg_cb) { - return -EALREADY; + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOMEM; } - avrcp_tg_cb = cb; + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_INFORM_BATT_STATUS_OF_CT, + rsp_code, buf); +} - return 0; +int bt_avrcp_tg_send_get_element_attrs_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_GET_ELEMENT_ATTRS, + rsp_code, 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_play_status_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t rsp_code, struct net_buf *buf) +{ + return bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, + BT_AVRCP_PDU_ID_GET_PLAY_STATUS, + rsp_code, buf); +} + +int bt_avrcp_tg_send_set_addressed_player_rsp(struct bt_avrcp_tg *tg, uint8_t tid, + uint8_t rsp_code, uint8_t status) { struct net_buf *buf; int err; + uint16_t param_len = sizeof(status); - if ((tg == NULL) || (tg->avrcp == NULL) || (rsp == NULL)) { + if (status > BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED) { return -EINVAL; } - if (!IS_TG_ROLE_SUPPORTED()) { - return -ENOTSUP; + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOMEM; } - buf = avrcp_create_unit_pdu(tg->avrcp, BT_AVRCP_RSP_STABLE); - if (!buf) { - LOG_WRN("Insufficient buffer"); + if (net_buf_tailroom(buf) < param_len) { + LOG_ERR("Not enough space in net_buf"); + net_buf_unref(buf); return -ENOMEM; } + net_buf_add_u8(buf, status); - /* 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); + err = bt_avrcp_tg_send_vendor_dependent_rsp(tg, tid, BT_AVRCP_PDU_ID_SET_ADDRESSED_PLAYER, + rsp_code, 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) -{ - 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_tg_send_set_browsed_player_rsp(struct bt_avrcp_tg *tg, uint8_t tid, - struct net_buf *buf) +int bt_avrcp_tg_send_play_item_rsp(struct bt_avrcp_tg *tg, uint8_t tid, uint8_t rsp_code, + uint8_t status) { - struct bt_avrcp_avc_brow_pdu *hdr; - uint16_t param_len; + struct net_buf *buf; int err; + uint16_t param_len = sizeof(status); - if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) { - LOG_ERR("Invalid AVRCP target"); + if (status > BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED) { return -EINVAL; } - 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; + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOMEM; } - param_len = buf->len; - - 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 (net_buf_tailroom(buf) < param_len) { + 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, + rsp_code, 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 rsp_code, uint8_t status) { - struct bt_avrcp_header *avrcp_hdr; + struct net_buf *buf; int err; + uint16_t param_len = sizeof(status); - if ((tg == NULL) || (tg->avrcp == NULL) || (buf == NULL)) { + if (status > BT_AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED) { return -EINVAL; } - if (!IS_TG_ROLE_SUPPORTED()) { - return -ENOTSUP; + buf = bt_avrcp_create_pdu(&avrcp_pool); + if (buf == NULL) { + LOG_ERR("Failed to allocate buffer"); + return -ENOMEM; } - 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) < param_len) { + 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, + rsp_code, 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..128c69e4c1561 100644 --- a/subsys/bluetooth/host/classic/avrcp_internal.h +++ b/subsys/bluetooth/host/classic/avrcp_internal.h @@ -52,72 +52,40 @@ 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; /**< Transaction ID */ + uint8_t rsp; + uint16_t total_len; /**< Total length of complete response */ + uint16_t received_len; /**< Length already received */ +}; + +struct bt_avrcp_tg_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; + bt_avrcp_evt_t event_id; + bool registered; + bt_avrcp_notification_cb_t cb; +}; struct bt_avrcp_req { uint8_t tid; @@ -132,6 +100,7 @@ struct bt_avrcp_header { } __packed; struct bt_avrcp_avc_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; diff --git a/subsys/bluetooth/host/classic/l2cap_br.c b/subsys/bluetooth/host/classic/l2cap_br.c index 9969bbb9ba19a..4602ac33aa3f2 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..dcd4d18d0a367 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,8 +164,8 @@ 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, + const struct bt_avrcp_get_caps_rsp *rsp) { uint8_t i; @@ -131,6 +219,221 @@ 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, bt_avrcp_rsp_t result, + 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; + + 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; + uint8_t i; + 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 (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_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)); + printf("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; + 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 playback_interval) +{ + bt_shell_print("AVRCP register_notification_req: tid=0x%02x, event_id=0x%02x, interval=%u", + tid, event_id, playback_interval); + tg_tid = tid; +} + +static void avrcp_set_absolute_volume_rsp(struct bt_avrcp_ct *ct, uint8_t tid, uint8_t rsp_code, + uint8_t absolute_volume) +{ + bt_shell_print("AVRCP set absolute volume rsp: tid=0x%02x, rsp=0x%02x, volume=0x%02x", + tid, rsp_code, 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_browsed_player_rsp(struct bt_avrcp_ct *ct, uint8_t tid, struct net_buf *buf) { @@ -185,16 +488,166 @@ static void avrcp_browsed_player_rsp(struct bt_avrcp_ct *ct, uint8_t tid, } } +static const char *player_app_attr_name(uint8_t id) +{ + 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_list_player_app_setting_attrs_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + bt_avrcp_rsp_t result, struct net_buf *buf) +{ + struct bt_avrcp_list_app_setting_attr_rsp *rsp; + + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + + while (buf->len > 0) { + uint8_t attr = net_buf_pull_u8(buf); + + 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; + } + } + + if (rsp->num_attrs > 0) { + bt_shell_print("num_attrs mismatch: expected 0, got %u", rsp->num_attrs); + } +} + +static void avrcp_list_player_app_setting_vals_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + bt_avrcp_rsp_t result, struct net_buf *buf) +{ + struct bt_avrcp_list_player_app_setting_vals_rsp *rsp; + + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + while (buf->len > 0) { + uint8_t val = net_buf_pull_u8(buf); + + 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; + } + } + + if (rsp->num_values > 0) { + bt_shell_print("num_values mismatch: expected 0, got %u", rsp->num_values); + } +} + +static void avrcp_get_curr_player_app_setting_val_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + bt_avrcp_rsp_t result, struct net_buf *buf) +{ + struct bt_avrcp_get_curr_player_app_setting_val_rsp *rsp; + struct bt_avrcp_app_setting_attr_val attr_vals = {0}; + + rsp = net_buf_pull_mem(buf, sizeof(*rsp)); + while (buf->len > 0) { + + 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); + + 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; + } + } + + if (rsp->num_attrs > 0) { + bt_shell_print("num_attrs mismatch: expected 0, got %u", rsp->num_attrs); + } +} + +static void avrcp_set_player_app_setting_val_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + bt_avrcp_rsp_t result) +{ + bt_shell_print("SetPlayerAppSettingValue rsp: tid=0x%02x, result=%u", tid, result); +} + +static void avrcp_get_player_app_setting_attr_text_rsp(struct bt_avrcp_ct *ct, uint8_t tid, + bt_avrcp_rsp_t result, struct net_buf *buf) +{ + struct bt_avrcp_get_player_app_setting_attr_text_rsp *rsp; + struct bt_avrcp_app_setting_attr_text *attr_text; + + 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 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, + .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, + .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, }; static void avrcp_tg_connected(struct bt_conn *conn, struct bt_avrcp_tg *tg) @@ -226,6 +679,31 @@ static void avrcp_subunit_info_req(struct bt_avrcp_tg *tg, uint8_t tid) 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"); @@ -253,7 +731,7 @@ static void avrcp_passthrough_req(struct bt_avrcp_tg *tg, uint8_t tid, struct ne 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", + bt_shell_print("Invalid passthrough data: buf len %u < expected_len %zu", buf->len, sizeof(struct bt_avrcp_passthrough_opvu_data)); return; } @@ -278,6 +756,87 @@ static void avrcp_passthrough_req(struct bt_avrcp_tg *tg, uint8_t tid, struct ne } +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 struct bt_avrcp_tg_cb app_avrcp_tg_cb = { .connected = avrcp_tg_connected, .disconnected = avrcp_tg_disconnected, @@ -285,8 +844,17 @@ static struct bt_avrcp_tg_cb app_avrcp_tg_cb = { .browsing_disconnected = avrcp_tg_browsing_disconnected, .unit_info_req = avrcp_unit_info_req, .subunit_info_req = avrcp_subunit_info_req, + .get_cap_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, }; static int register_ct_cb(const struct shell *sh) @@ -369,7 +937,7 @@ static int cmd_connect(const struct shell *sh, int32_t argc, char *argv[]) } err = bt_avrcp_connect(default_conn); - if (err) { + if (err < 0) { shell_error(sh, "fail to connect AVRCP"); } @@ -611,6 +1179,58 @@ static int cmd_send_subunit_info_rsp(const struct shell *sh, int32_t argc, char 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; + uint8_t rsp_buffer[32U]; + uint8_t *cap_data; + 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; + } + + /* Initialize response structure */ + rsp = (struct bt_avrcp_get_caps_rsp *)rsp_buffer; + rsp->cap_id = tg_cap_id; + cap_data = rsp->cap; + + switch (tg_cap_id) { + case BT_AVRCP_CAP_COMPANY_ID: + /* Send Bluetooth SIG company ID as example */ + rsp->cap_cnt = 1; + sys_put_be24(BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG, cap_data); + shell_print(sh, "Sending company ID capability response: 0x%06x", + BT_AVRCP_COMPANY_ID_BLUETOOTH_SIG); + break; + + case BT_AVRCP_CAP_EVENTS_SUPPORTED: + rsp->cap_cnt = ARRAY_SIZE(supported_avrcp_events); + memcpy(cap_data, supported_avrcp_events, rsp->cap_cnt); + shell_print(sh, "Sending events supported capability response 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, rsp); + 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; +} + static int cmd_get_subunit_info(const struct shell *sh, int32_t argc, char *argv[]) { if (!avrcp_ct_registered && register_ct_cb(sh) != 0) { @@ -678,6 +1298,821 @@ static int cmd_get_cap(const struct shell *sh, int32_t argc, char *argv[]) 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_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_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 browsed player 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_RSP_STABLE, 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 err; +} + +static int cmd_ct_register_notification(const struct shell *sh, int argc, char *argv[]) +{ + uint8_t event_id; + uint32_t playback_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) { + playback_interval = (uint32_t)strtoul(argv[2], NULL, 0); + } + + err = bt_avrcp_ct_register_notification(default_ct, get_next_tid(), event_id, + playback_interval, avrcp_notification_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; + struct bt_avrcp_app_setting_attr_val attr_vals[1]; + uint8_t event_id = (uint8_t)strtoul(argv[1], NULL, 0); + bt_avrcp_rsp_t type; + 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) { + type = BT_AVRCP_RSP_CHANGED; + } else if (strcmp(argv[2], "interim") == 0) { + type = BT_AVRCP_RSP_INTERIM; + } else { + shell_error(sh, "Invalid type: %s (expected: changed|interim)", argv[2]); + return -EINVAL; + } + + if (type == BT_AVRCP_RSP_INTERIM) { + if (event_id == BT_AVRCP_EVT_TRACK_CHANGED) { + /* Interim response for track changed must have identifier set */ + identifier = 111111; + sys_put_be64(identifier, data.identifier); + } + 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 < 11) { + identifier = 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; + default: + shell_error(sh, "Unknown event_id: 0x%02x", event_id); + return -EINVAL; + } + +done: + err = bt_avrcp_tg_send_notification_rsp(default_tg, tg_tid, type, 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, (type == BT_AVRCP_RSP_CHANGED) ? "changed" : "interim"); + } + 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_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); + /* Payload: NumPairs(1) + (AttrID,ValueID)*pairs */ + expected_len = 1 + (size_t)pairs * 2U; + + buf = bt_avrcp_create_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 err; +} + +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_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_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_RSP_ACCEPTED, + 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 net_buf *buf; + uint8_t num, id; + 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 + AttrIDs */ + + buf = bt_avrcp_create_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; + } + + net_buf_add_u8(buf, num); + for (uint8_t i = 0U; i < num; i++) { + id = (argc >= (2 + i + 1)) ? (uint8_t)strtoul(argv[2 + i], NULL, 0) : (i + 1); + net_buf_add_u8(buf, id); + } + + err = bt_avrcp_tg_send_list_player_app_setting_attrs_rsp(default_tg, tg_tid, + BT_AVRCP_RSP_STABLE, 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 net_buf *buf; + uint8_t num, val; + 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_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; + } + + net_buf_add_u8(buf, num); + for (uint8_t i = 0U; i < num; i++) { + val = (argc >= (2 + i + 1)) ? (uint8_t)strtoul(argv[2 + i], NULL, 0) : (i + 1); + net_buf_add_u8(buf, val); + } + + err = bt_avrcp_tg_send_list_player_app_setting_vals_rsp(default_tg, tg_tid, + BT_AVRCP_RSP_STABLE, 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_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_RSP_STABLE, 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_tg_send_set_player_app_setting_val_rsp(const struct shell *sh, int argc, + char *argv[]) +{ + 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; + } + + err = bt_avrcp_tg_send_set_player_app_setting_val_rsp(default_tg, tg_tid, + BT_AVRCP_RSP_STABLE); + 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_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_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(&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_RSP_STABLE, buf); + if (err < 0) { + shell_error(sh, "Failed to send get player app setting attr text rsp: %d", err); + return -ENOEXEC; + } + + shell_print(sh, "Get player app setting attr text rsp sent"); + return 0; + +failed: + net_buf_unref(buf); + return -ENOEXEC; +} + static int cmd_set_browsed_player(const struct shell *sh, int32_t argc, char *argv[]) { uint16_t player_id; @@ -827,8 +2262,22 @@ SHELL_STATIC_SUBCMD_SET_CREATE( 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(get_element_attrs, NULL, "get element attrs [identifier] [attr1] [attr2] ...", + cmd_get_element_attrs, 1, 9), + SHELL_CMD_ARG(register_notification, NULL, "register notify [playback_interval]", + cmd_ct_register_notification, 2, 1), + SHELL_CMD_ARG(set_absolute_volume, NULL, "set absolute volume ", + cmd_ct_set_absolute_volume, 2, 0), SHELL_CMD_ARG(set_browsed_player, NULL, "set browsed player ", cmd_set_browsed_player, 2, 0), + SHELL_CMD_ARG(list_app_attrs, NULL, "List App attrs", 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 App vals [attr1] [attr2] ...", + cmd_ct_get_app_curr, 1, 8), + SHELL_CMD_ARG(set_app_val, NULL, "App Setting Value [ ] ...", + cmd_ct_set_app_val, 3, 14), + SHELL_CMD_ARG(get_app_attr_text, NULL, "Get PApp Setting attrs text [attr2] ...", + cmd_ct_get_app_attr_text, 2, 7), SHELL_SUBCMD_SET_END); SHELL_STATIC_SUBCMD_SET_CREATE( @@ -836,10 +2285,32 @@ 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_get_element_attrs_rsp, NULL, "send get element attrs response", + cmd_send_get_element_attrs_rsp, 2, 0), + SHELL_CMD_ARG(send_notification_rsp, NULL, + "send notification rsp [value...]", + cmd_tg_send_notification_rsp, 3, 10), + SHELL_CMD_ARG(send_absolute_volume_rsp, NULL, "send absolute volume rsp ", + cmd_tg_send_absolute_volume_rsp, 2, 0), 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_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, HELP_NONE, + cmd_tg_send_set_player_app_setting_val_rsp, 1, 0), + 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_SUBCMD_SET_END); static int cmd_avrcp(const struct shell *sh, size_t argc, char **argv)