|  | /* | 
|  | * | 
|  | *  BlueZ - Bluetooth protocol stack for Linux | 
|  | * | 
|  | *  Copyright (C) 2011-2014  Intel Corporation | 
|  | *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org> | 
|  | * | 
|  | * | 
|  | *  This library is free software; you can redistribute it and/or | 
|  | *  modify it under the terms of the GNU Lesser General Public | 
|  | *  License as published by the Free Software Foundation; either | 
|  | *  version 2.1 of the License, or (at your option) any later version. | 
|  | * | 
|  | *  This library is distributed in the hope that it will be useful, | 
|  | *  but WITHOUT ANY WARRANTY; without even the implied warranty of | 
|  | *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
|  | *  Lesser General Public License for more details. | 
|  | * | 
|  | *  You should have received a copy of the GNU Lesser General Public | 
|  | *  License along with this library; if not, write to the Free Software | 
|  | *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA | 
|  | * | 
|  | */ | 
|  |  | 
|  | #ifdef HAVE_CONFIG_H | 
|  | #include <config.h> | 
|  | #endif | 
|  |  | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  | #include <ctype.h> | 
|  | #include <inttypes.h> | 
|  |  | 
|  | #include "lib/bluetooth.h" | 
|  |  | 
|  | #include "src/shared/util.h" | 
|  | #include "bt.h" | 
|  | #include "packet.h" | 
|  | #include "display.h" | 
|  | #include "l2cap.h" | 
|  | #include "uuid.h" | 
|  | #include "keys.h" | 
|  | #include "sdp.h" | 
|  | #include "avctp.h" | 
|  |  | 
|  | /* ctype entries */ | 
|  | #define AVC_CTYPE_CONTROL		0x0 | 
|  | #define AVC_CTYPE_STATUS		0x1 | 
|  | #define AVC_CTYPE_SPECIFIC_INQUIRY	0x2 | 
|  | #define AVC_CTYPE_NOTIFY		0x3 | 
|  | #define AVC_CTYPE_GENERAL_INQUIRY	0x4 | 
|  | #define AVC_CTYPE_NOT_IMPLEMENTED	0x8 | 
|  | #define AVC_CTYPE_ACCEPTED		0x9 | 
|  | #define AVC_CTYPE_REJECTED		0xA | 
|  | #define AVC_CTYPE_IN_TRANSITION		0xB | 
|  | #define AVC_CTYPE_STABLE		0xC | 
|  | #define AVC_CTYPE_CHANGED		0xD | 
|  | #define AVC_CTYPE_INTERIM		0xF | 
|  |  | 
|  | /* subunit type */ | 
|  | #define AVC_SUBUNIT_MONITOR		0x00 | 
|  | #define AVC_SUBUNIT_AUDIO		0x01 | 
|  | #define AVC_SUBUNIT_PRINTER		0x02 | 
|  | #define AVC_SUBUNIT_DISC		0x03 | 
|  | #define AVC_SUBUNIT_TAPE		0x04 | 
|  | #define AVC_SUBUNIT_TUNER		0x05 | 
|  | #define AVC_SUBUNIT_CA			0x06 | 
|  | #define AVC_SUBUNIT_CAMERA		0x07 | 
|  | #define AVC_SUBUNIT_PANEL		0x09 | 
|  | #define AVC_SUBUNIT_BULLETIN_BOARD	0x0a | 
|  | #define AVC_SUBUNIT_CAMERA_STORAGE	0x0b | 
|  | #define AVC_SUBUNIT_VENDOR_UNIQUE	0x0c | 
|  | #define AVC_SUBUNIT_EXTENDED		0x1e | 
|  | #define AVC_SUBUNIT_UNIT		0x1f | 
|  |  | 
|  | /* opcodes */ | 
|  | #define AVC_OP_VENDORDEP		0x00 | 
|  | #define AVC_OP_UNITINFO			0x30 | 
|  | #define AVC_OP_SUBUNITINFO		0x31 | 
|  | #define AVC_OP_PASSTHROUGH		0x7c | 
|  |  | 
|  | /* notification events */ | 
|  | #define AVRCP_EVENT_PLAYBACK_STATUS_CHANGED		0x01 | 
|  | #define AVRCP_EVENT_TRACK_CHANGED			0x02 | 
|  | #define AVRCP_EVENT_TRACK_REACHED_END			0x03 | 
|  | #define AVRCP_EVENT_TRACK_REACHED_START			0x04 | 
|  | #define AVRCP_EVENT_PLAYBACK_POS_CHANGED		0x05 | 
|  | #define AVRCP_EVENT_BATT_STATUS_CHANGED			0x06 | 
|  | #define AVRCP_EVENT_SYSTEM_STATUS_CHANGED		0x07 | 
|  | #define AVRCP_EVENT_PLAYER_APPLICATION_SETTING_CHANGED	0x08 | 
|  | #define AVRCP_EVENT_NOW_PLAYING_CONTENT_CHANGED		0x09 | 
|  | #define AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED		0x0a | 
|  | #define AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED		0x0b | 
|  | #define AVRCP_EVENT_UIDS_CHANGED			0x0c | 
|  | #define AVRCP_EVENT_VOLUME_CHANGED			0x0d | 
|  |  | 
|  | /* error statuses */ | 
|  | #define AVRCP_STATUS_INVALID_COMMAND			0x00 | 
|  | #define AVRCP_STATUS_INVALID_PARAMETER			0x01 | 
|  | #define AVRCP_STATUS_NOT_FOUND				0x02 | 
|  | #define AVRCP_STATUS_INTERNAL_ERROR			0x03 | 
|  | #define AVRCP_STATUS_SUCCESS				0x04 | 
|  | #define AVRCP_STATUS_UID_CHANGED			0x05 | 
|  | #define AVRCP_STATUS_INVALID_DIRECTION			0x07 | 
|  | #define AVRCP_STATUS_NOT_DIRECTORY			0x08 | 
|  | #define AVRCP_STATUS_DOES_NOT_EXIST			0x09 | 
|  | #define AVRCP_STATUS_INVALID_SCOPE			0x0a | 
|  | #define AVRCP_STATUS_OUT_OF_BOUNDS			0x0b | 
|  | #define AVRCP_STATUS_IS_DIRECTORY			0x0c | 
|  | #define AVRCP_STATUS_MEDIA_IN_USE			0x0d | 
|  | #define AVRCP_STATUS_NOW_PLAYING_LIST_FULL		0x0e | 
|  | #define AVRCP_STATUS_SEARCH_NOT_SUPPORTED		0x0f | 
|  | #define AVRCP_STATUS_SEARCH_IN_PROGRESS			0x10 | 
|  | #define AVRCP_STATUS_INVALID_PLAYER_ID			0x11 | 
|  | #define AVRCP_STATUS_PLAYER_NOT_BROWSABLE		0x12 | 
|  | #define AVRCP_STATUS_PLAYER_NOT_ADDRESSED		0x13 | 
|  | #define AVRCP_STATUS_NO_VALID_SEARCH_RESULTS		0x14 | 
|  | #define AVRCP_STATUS_NO_AVAILABLE_PLAYERS		0x15 | 
|  | #define AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED		0x16 | 
|  |  | 
|  | /* pdu ids */ | 
|  | #define AVRCP_GET_CAPABILITIES		0x10 | 
|  | #define AVRCP_LIST_PLAYER_ATTRIBUTES	0x11 | 
|  | #define AVRCP_LIST_PLAYER_VALUES	0x12 | 
|  | #define AVRCP_GET_CURRENT_PLAYER_VALUE	0x13 | 
|  | #define AVRCP_SET_PLAYER_VALUE		0x14 | 
|  | #define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT	0x15 | 
|  | #define AVRCP_GET_PLAYER_VALUE_TEXT	0x16 | 
|  | #define AVRCP_DISPLAYABLE_CHARSET	0x17 | 
|  | #define AVRCP_CT_BATTERY_STATUS		0x18 | 
|  | #define AVRCP_GET_ELEMENT_ATTRIBUTES	0x20 | 
|  | #define AVRCP_GET_PLAY_STATUS		0x30 | 
|  | #define AVRCP_REGISTER_NOTIFICATION	0x31 | 
|  | #define AVRCP_REQUEST_CONTINUING	0x40 | 
|  | #define AVRCP_ABORT_CONTINUING		0x41 | 
|  | #define AVRCP_SET_ABSOLUTE_VOLUME	0x50 | 
|  | #define AVRCP_SET_ADDRESSED_PLAYER	0x60 | 
|  | #define AVRCP_SET_BROWSED_PLAYER	0x70 | 
|  | #define AVRCP_GET_FOLDER_ITEMS		0x71 | 
|  | #define AVRCP_CHANGE_PATH		0x72 | 
|  | #define AVRCP_GET_ITEM_ATTRIBUTES	0x73 | 
|  | #define AVRCP_PLAY_ITEM			0x74 | 
|  | #define AVRCP_GET_TOTAL_NUMBER_OF_ITEMS	0x75 | 
|  | #define AVRCP_SEARCH			0x80 | 
|  | #define AVRCP_ADD_TO_NOW_PLAYING	0x90 | 
|  | #define AVRCP_GENERAL_REJECT		0xA0 | 
|  |  | 
|  | /* Packet types */ | 
|  | #define AVRCP_PACKET_TYPE_SINGLE	0x00 | 
|  | #define AVRCP_PACKET_TYPE_START		0x01 | 
|  | #define AVRCP_PACKET_TYPE_CONTINUING	0x02 | 
|  | #define AVRCP_PACKET_TYPE_END		0x03 | 
|  |  | 
|  | /* player attributes */ | 
|  | #define AVRCP_ATTRIBUTE_ILEGAL		0x00 | 
|  | #define AVRCP_ATTRIBUTE_EQUALIZER	0x01 | 
|  | #define AVRCP_ATTRIBUTE_REPEAT_MODE	0x02 | 
|  | #define AVRCP_ATTRIBUTE_SHUFFLE		0x03 | 
|  | #define AVRCP_ATTRIBUTE_SCAN		0x04 | 
|  |  | 
|  | /* media attributes */ | 
|  | #define AVRCP_MEDIA_ATTRIBUTE_ILLEGAL	0x00 | 
|  | #define AVRCP_MEDIA_ATTRIBUTE_TITLE	0x01 | 
|  | #define AVRCP_MEDIA_ATTRIBUTE_ARTIST	0x02 | 
|  | #define AVRCP_MEDIA_ATTRIBUTE_ALBUM	0x03 | 
|  | #define AVRCP_MEDIA_ATTRIBUTE_TRACK	0x04 | 
|  | #define AVRCP_MEDIA_ATTRIBUTE_TOTAL	0x05 | 
|  | #define AVRCP_MEDIA_ATTRIBUTE_GENRE	0x06 | 
|  | #define AVRCP_MEDIA_ATTRIBUTE_DURATION	0x07 | 
|  |  | 
|  | /* play status */ | 
|  | #define AVRCP_PLAY_STATUS_STOPPED	0x00 | 
|  | #define AVRCP_PLAY_STATUS_PLAYING	0x01 | 
|  | #define AVRCP_PLAY_STATUS_PAUSED	0x02 | 
|  | #define AVRCP_PLAY_STATUS_FWD_SEEK	0x03 | 
|  | #define AVRCP_PLAY_STATUS_REV_SEEK	0x04 | 
|  | #define AVRCP_PLAY_STATUS_ERROR		0xFF | 
|  |  | 
|  | /* media scope */ | 
|  | #define AVRCP_MEDIA_PLAYER_LIST		0x00 | 
|  | #define AVRCP_MEDIA_PLAYER_VFS		0x01 | 
|  | #define AVRCP_MEDIA_SEARCH		0x02 | 
|  | #define AVRCP_MEDIA_NOW_PLAYING		0x03 | 
|  |  | 
|  | /* Media Item Type */ | 
|  | #define AVRCP_MEDIA_PLAYER_ITEM_TYPE	0x01 | 
|  | #define AVRCP_FOLDER_ITEM_TYPE			0x02 | 
|  | #define AVRCP_MEDIA_ELEMENT_ITEM_TYPE	0x03 | 
|  |  | 
|  | /* operands in passthrough commands */ | 
|  | #define AVC_PANEL_VOLUME_UP		0x41 | 
|  | #define AVC_PANEL_VOLUME_DOWN		0x42 | 
|  | #define AVC_PANEL_MUTE			0x43 | 
|  | #define AVC_PANEL_PLAY			0x44 | 
|  | #define AVC_PANEL_STOP			0x45 | 
|  | #define AVC_PANEL_PAUSE			0x46 | 
|  | #define AVC_PANEL_RECORD		0x47 | 
|  | #define AVC_PANEL_REWIND		0x48 | 
|  | #define AVC_PANEL_FAST_FORWARD		0x49 | 
|  | #define AVC_PANEL_EJECT			0x4a | 
|  | #define AVC_PANEL_FORWARD		0x4b | 
|  | #define AVC_PANEL_BACKWARD		0x4c | 
|  |  | 
|  | struct avctp_frame { | 
|  | uint8_t hdr; | 
|  | uint8_t pt; | 
|  | uint16_t pid; | 
|  | struct l2cap_frame l2cap_frame; | 
|  | }; | 
|  |  | 
|  | static struct avrcp_continuing { | 
|  | uint16_t num; | 
|  | uint16_t size; | 
|  | } avrcp_continuing; | 
|  |  | 
|  | static const char *ctype2str(uint8_t ctype) | 
|  | { | 
|  | switch (ctype & 0x0f) { | 
|  | case AVC_CTYPE_CONTROL: | 
|  | return "Control"; | 
|  | case AVC_CTYPE_STATUS: | 
|  | return "Status"; | 
|  | case AVC_CTYPE_SPECIFIC_INQUIRY: | 
|  | return "Specific Inquiry"; | 
|  | case AVC_CTYPE_NOTIFY: | 
|  | return "Notify"; | 
|  | case AVC_CTYPE_GENERAL_INQUIRY: | 
|  | return "General Inquiry"; | 
|  | case AVC_CTYPE_NOT_IMPLEMENTED: | 
|  | return "Not Implemented"; | 
|  | case AVC_CTYPE_ACCEPTED: | 
|  | return "Accepted"; | 
|  | case AVC_CTYPE_REJECTED: | 
|  | return "Rejected"; | 
|  | case AVC_CTYPE_IN_TRANSITION: | 
|  | return "In Transition"; | 
|  | case AVC_CTYPE_STABLE: | 
|  | return "Stable"; | 
|  | case AVC_CTYPE_CHANGED: | 
|  | return "Changed"; | 
|  | case AVC_CTYPE_INTERIM: | 
|  | return "Interim"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *subunit2str(uint8_t subunit) | 
|  | { | 
|  | switch (subunit) { | 
|  | case AVC_SUBUNIT_MONITOR: | 
|  | return "Monitor"; | 
|  | case AVC_SUBUNIT_AUDIO: | 
|  | return "Audio"; | 
|  | case AVC_SUBUNIT_PRINTER: | 
|  | return "Printer"; | 
|  | case AVC_SUBUNIT_DISC: | 
|  | return "Disc"; | 
|  | case AVC_SUBUNIT_TAPE: | 
|  | return "Tape"; | 
|  | case AVC_SUBUNIT_TUNER: | 
|  | return "Tuner"; | 
|  | case AVC_SUBUNIT_CA: | 
|  | return "CA"; | 
|  | case AVC_SUBUNIT_CAMERA: | 
|  | return "Camera"; | 
|  | case AVC_SUBUNIT_PANEL: | 
|  | return "Panel"; | 
|  | case AVC_SUBUNIT_BULLETIN_BOARD: | 
|  | return "Bulletin Board"; | 
|  | case AVC_SUBUNIT_CAMERA_STORAGE: | 
|  | return "Camera Storage"; | 
|  | case AVC_SUBUNIT_VENDOR_UNIQUE: | 
|  | return "Vendor Unique"; | 
|  | case AVC_SUBUNIT_EXTENDED: | 
|  | return "Extended to next byte"; | 
|  | case AVC_SUBUNIT_UNIT: | 
|  | return "Unit"; | 
|  | default: | 
|  | return "Reserved"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *opcode2str(uint8_t opcode) | 
|  | { | 
|  | switch (opcode) { | 
|  | case AVC_OP_VENDORDEP: | 
|  | return "Vendor Dependent"; | 
|  | case AVC_OP_UNITINFO: | 
|  | return "Unit Info"; | 
|  | case AVC_OP_SUBUNITINFO: | 
|  | return "Subunit Info"; | 
|  | case AVC_OP_PASSTHROUGH: | 
|  | return "Passthrough"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static char *cap2str(uint8_t cap) | 
|  | { | 
|  | switch (cap) { | 
|  | case 0x2: | 
|  | return "CompanyID"; | 
|  | case 0x3: | 
|  | return "EventsID"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static char *event2str(uint8_t event) | 
|  | { | 
|  | switch (event) { | 
|  | case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED: | 
|  | return "EVENT_PLAYBACK_STATUS_CHANGED"; | 
|  | case AVRCP_EVENT_TRACK_CHANGED: | 
|  | return "EVENT_TRACK_CHANGED"; | 
|  | case AVRCP_EVENT_TRACK_REACHED_END: | 
|  | return "EVENT_TRACK_REACHED_END"; | 
|  | case AVRCP_EVENT_TRACK_REACHED_START: | 
|  | return "EVENT_TRACK_REACHED_START"; | 
|  | case AVRCP_EVENT_PLAYBACK_POS_CHANGED: | 
|  | return "EVENT_PLAYBACK_POS_CHANGED"; | 
|  | case AVRCP_EVENT_BATT_STATUS_CHANGED: | 
|  | return "EVENT_BATT_STATUS_CHANGED"; | 
|  | case AVRCP_EVENT_SYSTEM_STATUS_CHANGED: | 
|  | return "EVENT_SYSTEM_STATUS_CHANGED"; | 
|  | case AVRCP_EVENT_PLAYER_APPLICATION_SETTING_CHANGED: | 
|  | return "EVENT_PLAYER_APPLICATION_SETTING_CHANGED"; | 
|  | case AVRCP_EVENT_NOW_PLAYING_CONTENT_CHANGED: | 
|  | return "EVENT_NOW_PLAYING_CONTENT_CHANGED"; | 
|  | case AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED: | 
|  | return "EVENT_AVAILABLE_PLAYERS_CHANGED"; | 
|  | case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: | 
|  | return "EVENT_ADDRESSED_PLAYER_CHANGED"; | 
|  | case AVRCP_EVENT_UIDS_CHANGED: | 
|  | return "EVENT_UIDS_CHANGED"; | 
|  | case AVRCP_EVENT_VOLUME_CHANGED: | 
|  | return "EVENT_VOLUME_CHANGED"; | 
|  | default: | 
|  | return "Reserved"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *error2str(uint8_t status) | 
|  | { | 
|  | switch (status) { | 
|  | case AVRCP_STATUS_INVALID_COMMAND: | 
|  | return "Invalid Command"; | 
|  | case AVRCP_STATUS_INVALID_PARAMETER: | 
|  | return "Invalid Parameter"; | 
|  | case AVRCP_STATUS_NOT_FOUND: | 
|  | return "Not Found"; | 
|  | case AVRCP_STATUS_INTERNAL_ERROR: | 
|  | return "Internal Error"; | 
|  | case AVRCP_STATUS_SUCCESS: | 
|  | return "Success"; | 
|  | case AVRCP_STATUS_UID_CHANGED: | 
|  | return "UID Changed"; | 
|  | case AVRCP_STATUS_INVALID_DIRECTION: | 
|  | return "Invalid Direction"; | 
|  | case AVRCP_STATUS_NOT_DIRECTORY: | 
|  | return "Not a Directory"; | 
|  | case AVRCP_STATUS_DOES_NOT_EXIST: | 
|  | return "Does Not Exist"; | 
|  | case AVRCP_STATUS_INVALID_SCOPE: | 
|  | return "Invalid Scope"; | 
|  | case AVRCP_STATUS_OUT_OF_BOUNDS: | 
|  | return "Range Out of Bounds"; | 
|  | case AVRCP_STATUS_MEDIA_IN_USE: | 
|  | return "Media in Use"; | 
|  | case AVRCP_STATUS_IS_DIRECTORY: | 
|  | return "UID is a Directory"; | 
|  | case AVRCP_STATUS_NOW_PLAYING_LIST_FULL: | 
|  | return "Now Playing List Full"; | 
|  | case AVRCP_STATUS_SEARCH_NOT_SUPPORTED: | 
|  | return "Search Not Supported"; | 
|  | case AVRCP_STATUS_SEARCH_IN_PROGRESS: | 
|  | return "Search in Progress"; | 
|  | case AVRCP_STATUS_INVALID_PLAYER_ID: | 
|  | return "Invalid Player ID"; | 
|  | case AVRCP_STATUS_PLAYER_NOT_BROWSABLE: | 
|  | return "Player Not Browsable"; | 
|  | case AVRCP_STATUS_PLAYER_NOT_ADDRESSED: | 
|  | return "Player Not Addressed"; | 
|  | case AVRCP_STATUS_NO_VALID_SEARCH_RESULTS: | 
|  | return "No Valid Search Result"; | 
|  | case AVRCP_STATUS_NO_AVAILABLE_PLAYERS: | 
|  | return "No Available Players"; | 
|  | case AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED: | 
|  | return "Addressed Player Changed"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *pdu2str(uint8_t pduid) | 
|  | { | 
|  | switch (pduid) { | 
|  | case AVRCP_GET_CAPABILITIES: | 
|  | return "GetCapabilities"; | 
|  | case AVRCP_LIST_PLAYER_ATTRIBUTES: | 
|  | return "ListPlayerApplicationSettingAttributes"; | 
|  | case AVRCP_LIST_PLAYER_VALUES: | 
|  | return "ListPlayerApplicationSettingValues"; | 
|  | case AVRCP_GET_CURRENT_PLAYER_VALUE: | 
|  | return "GetCurrentPlayerApplicationSettingValue"; | 
|  | case AVRCP_SET_PLAYER_VALUE: | 
|  | return "SetPlayerApplicationSettingValue"; | 
|  | case AVRCP_GET_PLAYER_ATTRIBUTE_TEXT: | 
|  | return "GetPlayerApplicationSettingAttributeText"; | 
|  | case AVRCP_GET_PLAYER_VALUE_TEXT: | 
|  | return "GetPlayerApplicationSettingValueText"; | 
|  | case AVRCP_DISPLAYABLE_CHARSET: | 
|  | return "InformDisplayableCharacterSet"; | 
|  | case AVRCP_CT_BATTERY_STATUS: | 
|  | return "InformBatteryStatusOfCT"; | 
|  | case AVRCP_GET_ELEMENT_ATTRIBUTES: | 
|  | return "GetElementAttributes"; | 
|  | case AVRCP_GET_PLAY_STATUS: | 
|  | return "GetPlayStatus"; | 
|  | case AVRCP_REGISTER_NOTIFICATION: | 
|  | return "RegisterNotification"; | 
|  | case AVRCP_REQUEST_CONTINUING: | 
|  | return "RequestContinuingResponse"; | 
|  | case AVRCP_ABORT_CONTINUING: | 
|  | return "AbortContinuingResponse"; | 
|  | case AVRCP_SET_ABSOLUTE_VOLUME: | 
|  | return "SetAbsoluteVolume"; | 
|  | case AVRCP_SET_ADDRESSED_PLAYER: | 
|  | return "SetAddressedPlayer"; | 
|  | case AVRCP_SET_BROWSED_PLAYER: | 
|  | return "SetBrowsedPlayer"; | 
|  | case AVRCP_GET_FOLDER_ITEMS: | 
|  | return "GetFolderItems"; | 
|  | case AVRCP_CHANGE_PATH: | 
|  | return "ChangePath"; | 
|  | case AVRCP_GET_ITEM_ATTRIBUTES: | 
|  | return "GetItemAttributes"; | 
|  | case AVRCP_PLAY_ITEM: | 
|  | return "PlayItem"; | 
|  | case AVRCP_GET_TOTAL_NUMBER_OF_ITEMS: | 
|  | return "GetTotalNumOfItems"; | 
|  | case AVRCP_SEARCH: | 
|  | return "Search"; | 
|  | case AVRCP_ADD_TO_NOW_PLAYING: | 
|  | return "AddToNowPlaying"; | 
|  | case AVRCP_GENERAL_REJECT: | 
|  | return "GeneralReject"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *pt2str(uint8_t pt) | 
|  | { | 
|  | switch (pt) { | 
|  | case AVRCP_PACKET_TYPE_SINGLE: | 
|  | return "Single"; | 
|  | case AVRCP_PACKET_TYPE_START: | 
|  | return "Start"; | 
|  | case AVRCP_PACKET_TYPE_CONTINUING: | 
|  | return "Continuing"; | 
|  | case AVRCP_PACKET_TYPE_END: | 
|  | return "End"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *attr2str(uint8_t attr) | 
|  | { | 
|  | switch (attr) { | 
|  | case AVRCP_ATTRIBUTE_ILEGAL: | 
|  | return "Illegal"; | 
|  | case AVRCP_ATTRIBUTE_EQUALIZER: | 
|  | return "Equalizer ON/OFF Status"; | 
|  | case AVRCP_ATTRIBUTE_REPEAT_MODE: | 
|  | return "Repeat Mode Status"; | 
|  | case AVRCP_ATTRIBUTE_SHUFFLE: | 
|  | return "Shuffle ON/OFF Status"; | 
|  | case AVRCP_ATTRIBUTE_SCAN: | 
|  | return "Scan ON/OFF Status"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *value2str(uint8_t attr, uint8_t value) | 
|  | { | 
|  | switch (attr) { | 
|  | case AVRCP_ATTRIBUTE_ILEGAL: | 
|  | return "Illegal"; | 
|  | case AVRCP_ATTRIBUTE_EQUALIZER: | 
|  | switch (value) { | 
|  | case 0x01: | 
|  | return "OFF"; | 
|  | case 0x02: | 
|  | return "ON"; | 
|  | default: | 
|  | return "Reserved"; | 
|  | } | 
|  | case AVRCP_ATTRIBUTE_REPEAT_MODE: | 
|  | switch (value) { | 
|  | case 0x01: | 
|  | return "OFF"; | 
|  | case 0x02: | 
|  | return "Single Track Repeat"; | 
|  | case 0x03: | 
|  | return "All Track Repeat"; | 
|  | case 0x04: | 
|  | return "Group Repeat"; | 
|  | default: | 
|  | return "Reserved"; | 
|  | } | 
|  | case AVRCP_ATTRIBUTE_SHUFFLE: | 
|  | switch (value) { | 
|  | case 0x01: | 
|  | return "OFF"; | 
|  | case 0x02: | 
|  | return "All Track Shuffle"; | 
|  | case 0x03: | 
|  | return "Group Shuffle"; | 
|  | default: | 
|  | return "Reserved"; | 
|  | } | 
|  | case AVRCP_ATTRIBUTE_SCAN: | 
|  | switch (value) { | 
|  | case 0x01: | 
|  | return "OFF"; | 
|  | case 0x02: | 
|  | return "All Track Scan"; | 
|  | case 0x03: | 
|  | return "Group Scan"; | 
|  | default: | 
|  | return "Reserved"; | 
|  | } | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *charset2str(uint16_t charset) | 
|  | { | 
|  | switch (charset) { | 
|  | case 1: | 
|  | case 2: | 
|  | return "Reserved"; | 
|  | case 3: | 
|  | return "ASCII"; | 
|  | case 4: | 
|  | return "ISO_8859-1"; | 
|  | case 5: | 
|  | return "ISO_8859-2"; | 
|  | case 6: | 
|  | return "ISO_8859-3"; | 
|  | case 7: | 
|  | return "ISO_8859-4"; | 
|  | case 8: | 
|  | return "ISO_8859-5"; | 
|  | case 9: | 
|  | return "ISO_8859-6"; | 
|  | case 10: | 
|  | return "ISO_8859-7"; | 
|  | case 11: | 
|  | return "ISO_8859-8"; | 
|  | case 12: | 
|  | return "ISO_8859-9"; | 
|  | case 106: | 
|  | return "UTF-8"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *mediattr2str(uint32_t attr) | 
|  | { | 
|  | switch (attr) { | 
|  | case AVRCP_MEDIA_ATTRIBUTE_ILLEGAL: | 
|  | return "Illegal"; | 
|  | case AVRCP_MEDIA_ATTRIBUTE_TITLE: | 
|  | return "Title"; | 
|  | case AVRCP_MEDIA_ATTRIBUTE_ARTIST: | 
|  | return "Artist"; | 
|  | case AVRCP_MEDIA_ATTRIBUTE_ALBUM: | 
|  | return "Album"; | 
|  | case AVRCP_MEDIA_ATTRIBUTE_TRACK: | 
|  | return "Track"; | 
|  | case AVRCP_MEDIA_ATTRIBUTE_TOTAL: | 
|  | return "Track Total"; | 
|  | case AVRCP_MEDIA_ATTRIBUTE_GENRE: | 
|  | return "Genre"; | 
|  | case AVRCP_MEDIA_ATTRIBUTE_DURATION: | 
|  | return "Track duration"; | 
|  | default: | 
|  | return "Reserved"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *playstatus2str(uint8_t status) | 
|  | { | 
|  | switch (status) { | 
|  | case AVRCP_PLAY_STATUS_STOPPED: | 
|  | return "STOPPED"; | 
|  | case AVRCP_PLAY_STATUS_PLAYING: | 
|  | return "PLAYING"; | 
|  | case AVRCP_PLAY_STATUS_PAUSED: | 
|  | return "PAUSED"; | 
|  | case AVRCP_PLAY_STATUS_FWD_SEEK: | 
|  | return "FWD_SEEK"; | 
|  | case AVRCP_PLAY_STATUS_REV_SEEK: | 
|  | return "REV_SEEK"; | 
|  | case AVRCP_PLAY_STATUS_ERROR: | 
|  | return "ERROR"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *status2str(uint8_t status) | 
|  | { | 
|  | switch (status) { | 
|  | case 0x0: | 
|  | return "NORMAL"; | 
|  | case 0x1: | 
|  | return "WARNING"; | 
|  | case 0x2: | 
|  | return "CRITICAL"; | 
|  | case 0x3: | 
|  | return "EXTERNAL"; | 
|  | case 0x4: | 
|  | return "FULL_CHARGE"; | 
|  | default: | 
|  | return "Reserved"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *scope2str(uint8_t scope) | 
|  | { | 
|  | switch (scope) { | 
|  | case AVRCP_MEDIA_PLAYER_LIST: | 
|  | return "Media Player List"; | 
|  | case AVRCP_MEDIA_PLAYER_VFS: | 
|  | return "Media Player Virtual Filesystem"; | 
|  | case AVRCP_MEDIA_SEARCH: | 
|  | return "Search"; | 
|  | case AVRCP_MEDIA_NOW_PLAYING: | 
|  | return "Now Playing"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static char *op2str(uint8_t op) | 
|  | { | 
|  | switch (op & 0x7f) { | 
|  | case AVC_PANEL_VOLUME_UP: | 
|  | return "VOLUME UP"; | 
|  | case AVC_PANEL_VOLUME_DOWN: | 
|  | return "VOLUME DOWN"; | 
|  | case AVC_PANEL_MUTE: | 
|  | return "MUTE"; | 
|  | case AVC_PANEL_PLAY: | 
|  | return "PLAY"; | 
|  | case AVC_PANEL_STOP: | 
|  | return "STOP"; | 
|  | case AVC_PANEL_PAUSE: | 
|  | return "PAUSE"; | 
|  | case AVC_PANEL_RECORD: | 
|  | return "RECORD"; | 
|  | case AVC_PANEL_REWIND: | 
|  | return "REWIND"; | 
|  | case AVC_PANEL_FAST_FORWARD: | 
|  | return "FAST FORWARD"; | 
|  | case AVC_PANEL_EJECT: | 
|  | return "EJECT"; | 
|  | case AVC_PANEL_FORWARD: | 
|  | return "FORWARD"; | 
|  | case AVC_PANEL_BACKWARD: | 
|  | return "BACKWARD"; | 
|  | default: | 
|  | return "UNKNOWN"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *type2str(uint8_t type) | 
|  | { | 
|  | switch (type) { | 
|  | case AVRCP_MEDIA_PLAYER_ITEM_TYPE: | 
|  | return "Media Player"; | 
|  | case AVRCP_FOLDER_ITEM_TYPE: | 
|  | return "Folder"; | 
|  | case AVRCP_MEDIA_ELEMENT_ITEM_TYPE: | 
|  | return "Media Element"; | 
|  | default: | 
|  | return "Unknown"; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *playertype2str(uint8_t type) | 
|  | { | 
|  | switch (type & 0x0F) { | 
|  | case 0x01: | 
|  | return "Audio"; | 
|  | case 0x02: | 
|  | return "Video"; | 
|  | case 0x03: | 
|  | return "Audio, Video"; | 
|  | case 0x04: | 
|  | return "Audio Broadcasting"; | 
|  | case 0x05: | 
|  | return "Audio, Audio Broadcasting"; | 
|  | case 0x06: | 
|  | return "Video, Audio Broadcasting"; | 
|  | case 0x07: | 
|  | return "Audio, Video, Audio Broadcasting"; | 
|  | case 0x08: | 
|  | return "Video Broadcasting"; | 
|  | case 0x09: | 
|  | return "Audio, Video Broadcasting"; | 
|  | case 0x0A: | 
|  | return "Video, Video Broadcasting"; | 
|  | case 0x0B: | 
|  | return "Audio, Video, Video Broadcasting"; | 
|  | case 0x0C: | 
|  | return "Audio Broadcasting, Video Broadcasting"; | 
|  | case 0x0D: | 
|  | return "Audio, Audio Broadcasting, Video Broadcasting"; | 
|  | case 0x0E: | 
|  | return "Video, Audio Broadcasting, Video Broadcasting"; | 
|  | case 0x0F: | 
|  | return "Audio, Video, Audio Broadcasting, Video Broadcasting"; | 
|  | } | 
|  |  | 
|  | return "None"; | 
|  | } | 
|  |  | 
|  | static const char *playersubtype2str(uint32_t subtype) | 
|  | { | 
|  | switch (subtype & 0x03) { | 
|  | case 0x01: | 
|  | return "Audio Book"; | 
|  | case 0x02: | 
|  | return "Podcast"; | 
|  | case 0x03: | 
|  | return "Audio Book, Podcast"; | 
|  | } | 
|  |  | 
|  | return "None"; | 
|  | } | 
|  |  | 
|  | static const char *foldertype2str(uint8_t type) | 
|  | { | 
|  | switch (type) { | 
|  | case 0x00: | 
|  | return "Mixed"; | 
|  | case 0x01: | 
|  | return "Titles"; | 
|  | case 0x02: | 
|  | return "Albums"; | 
|  | case 0x03: | 
|  | return "Artists"; | 
|  | case 0x04: | 
|  | return "Genres"; | 
|  | case 0x05: | 
|  | return "Playlists"; | 
|  | case 0x06: | 
|  | return "Years"; | 
|  | } | 
|  |  | 
|  | return "Reserved"; | 
|  | } | 
|  |  | 
|  | static const char *elementtype2str(uint8_t type) | 
|  | { | 
|  | switch (type) { | 
|  | case 0x00: | 
|  | return "Audio"; | 
|  | case 0x01: | 
|  | return "Video"; | 
|  | } | 
|  |  | 
|  | return "Reserved"; | 
|  | } | 
|  |  | 
|  | static bool avrcp_passthrough_packet(struct avctp_frame *avctp_frame, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t op, len; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &op)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cOperation: 0x%02x (%s %s)", (indent - 8), ' ', op, | 
|  | op2str(op), op & 0x80 ? "Released" : "Pressed"); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &len)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cLength: 0x%02x", (indent - 8), ' ', len); | 
|  |  | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_get_capabilities(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t cap, count; | 
|  | int i; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &cap)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCapabilityID: 0x%02x (%s)", (indent - 8), ' ', cap, | 
|  | cap2str(cap)); | 
|  |  | 
|  | if (len == 1) | 
|  | return true; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &count)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCapabilityCount: 0x%02x", (indent - 8), ' ', count); | 
|  |  | 
|  | switch (cap) { | 
|  | case 0x2: | 
|  | for (; count > 0; count--) { | 
|  | uint8_t company[3]; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &company[0]) || | 
|  | !l2cap_frame_get_u8(frame, &company[1]) || | 
|  | !l2cap_frame_get_u8(frame, &company[2])) | 
|  | return false; | 
|  |  | 
|  | print_field("%*c%s: 0x%02x%02x%02x", (indent - 8), ' ', | 
|  | cap2str(cap), company[0], company[1], | 
|  | company[2]); | 
|  | } | 
|  | break; | 
|  | case 0x3: | 
|  | for (i = 0; count > 0; count--, i++) { | 
|  | uint8_t event; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &event)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*c%s: 0x%02x (%s)", (indent - 8), ' ', | 
|  | cap2str(cap), event, event2str(event)); | 
|  | } | 
|  | break; | 
|  | default: | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_list_player_attributes(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t num; | 
|  | int i; | 
|  |  | 
|  | if (len == 0) | 
|  | return true; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeCount: 0x%02x", (indent - 8), ' ', num); | 
|  |  | 
|  | for (i = 0; num > 0; num--, i++) { | 
|  | uint8_t attr; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), ' ', | 
|  | attr, attr2str(attr)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_list_player_values(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | static uint8_t attr = 0; | 
|  | uint8_t num; | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | goto response; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), ' ', | 
|  | attr, attr2str(attr)); | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cValueCount: 0x%02x", (indent - 8), ' ', num); | 
|  |  | 
|  | for (; num > 0; num--) { | 
|  | uint8_t value; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &value)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cValueID: 0x%02x (%s)", (indent - 8), | 
|  | ' ', value, value2str(attr, value)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_get_current_player_value(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t num; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | goto response; | 
|  |  | 
|  | print_field("%*cAttributeCount: 0x%02x", (indent - 8), ' ', num); | 
|  |  | 
|  | for (; num > 0; num--) { | 
|  | uint8_t attr; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), | 
|  | ' ', attr, attr2str(attr)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | print_field("%*cValueCount: 0x%02x", (indent - 8), ' ', num); | 
|  |  | 
|  | for (; num > 0; num--) { | 
|  | uint8_t attr, value; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), | 
|  | ' ', attr, attr2str(attr)); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &value)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cValueID: 0x%02x (%s)", (indent - 8), | 
|  | ' ', value, value2str(attr, value)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_set_player_value(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t num; | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | return true; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeCount: 0x%02x", (indent - 8), ' ', num); | 
|  |  | 
|  | for (; num > 0; num--) { | 
|  | uint8_t attr, value; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), ' ', | 
|  | attr, attr2str(attr)); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &value)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cValueID: 0x%02x (%s)", (indent - 8), ' ', | 
|  | value, value2str(attr, value)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_get_player_attribute_text(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t num; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeCount: 0x%02x", (indent - 8), ' ', num); | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | goto response; | 
|  |  | 
|  | for (; num > 0; num--) { | 
|  | uint8_t attr; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), | 
|  | ' ', attr, attr2str(attr)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | for (; num > 0; num--) { | 
|  | uint8_t attr, len; | 
|  | uint16_t charset; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), | 
|  | ' ', attr, attr2str(attr)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &charset)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCharsetID: 0x%04x (%s)", (indent - 8), | 
|  | ' ', charset, charset2str(charset)); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &len)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStringLength: 0x%02x", (indent - 8), ' ', len); | 
|  |  | 
|  | printf("String: "); | 
|  | for (; len > 0; len--) { | 
|  | uint8_t c; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &c)) | 
|  | return false; | 
|  |  | 
|  | printf("%1c", isprint(c) ? c : '.'); | 
|  | } | 
|  | printf("\n"); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_get_player_value_text(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | static uint8_t attr = 0; | 
|  | uint8_t num; | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | goto response; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%02x (%s)", (indent - 8), ' ', | 
|  | attr, attr2str(attr)); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cValueCount: 0x%02x", (indent - 8), ' ', num); | 
|  |  | 
|  | for (; num > 0; num--) { | 
|  | uint8_t value; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &value)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cValueID: 0x%02x (%s)", (indent - 8), | 
|  | ' ', value, value2str(attr, value)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cValueCount: 0x%02x", (indent - 8), ' ', num); | 
|  |  | 
|  | for (; num > 0; num--) { | 
|  | uint8_t value, len; | 
|  | uint16_t charset; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &value)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cValueID: 0x%02x (%s)", (indent - 8), ' ', | 
|  | value, value2str(attr, value)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &charset)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCharsetIDID: 0x%02x (%s)", (indent - 8), ' ', | 
|  | charset, charset2str(charset)); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &len)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStringLength: 0x%02x", (indent - 8), ' ', len); | 
|  |  | 
|  | printf("String: "); | 
|  | for (; len > 0; len--) { | 
|  | uint8_t c; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &c)) | 
|  | return false; | 
|  |  | 
|  | printf("%1c", isprint(c) ? c : '.'); | 
|  | } | 
|  | printf("\n"); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_displayable_charset(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t num; | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | return true; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCharsetCount: 0x%02x", (indent - 8), ' ', num); | 
|  |  | 
|  | for (; num > 0; num--) { | 
|  | uint16_t charset; | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &charset)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCharsetID: 0x%04x (%s)", (indent - 8), | 
|  | ' ', charset, charset2str(charset)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_get_element_attributes(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint64_t id; | 
|  | uint8_t num; | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | goto response; | 
|  |  | 
|  | if (!l2cap_frame_get_be64(frame, &id)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cIdentifier: 0x%jx (%s)", (indent - 8), ' ', | 
|  | id, id ? "Reserved" : "PLAYING"); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeCount: 0x%02x", (indent - 8), ' ', num); | 
|  |  | 
|  | for (; num > 0; num--) { | 
|  | uint32_t attr; | 
|  |  | 
|  | if (!l2cap_frame_get_le32(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%08x (%s)", (indent - 8), | 
|  | ' ', attr, mediattr2str(attr)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | switch (avctp_frame->pt) { | 
|  | case AVRCP_PACKET_TYPE_SINGLE: | 
|  | case AVRCP_PACKET_TYPE_START: | 
|  | if (!l2cap_frame_get_u8(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | avrcp_continuing.num = num; | 
|  | print_field("%*cAttributeCount: 0x%02x", (indent - 8), | 
|  | ' ', num); | 
|  | len--; | 
|  | break; | 
|  | case AVRCP_PACKET_TYPE_CONTINUING: | 
|  | case AVRCP_PACKET_TYPE_END: | 
|  | num = avrcp_continuing.num; | 
|  |  | 
|  | if (avrcp_continuing.size > 0) { | 
|  | char attrval[UINT8_MAX] = {0}; | 
|  | uint16_t size; | 
|  | uint8_t idx; | 
|  |  | 
|  | if (avrcp_continuing.size > len) { | 
|  | size = len; | 
|  | avrcp_continuing.size -= len; | 
|  | } else { | 
|  | size = avrcp_continuing.size; | 
|  | avrcp_continuing.size = 0; | 
|  | } | 
|  |  | 
|  | for (idx = 0; size > 0; idx++, size--) { | 
|  | uint8_t c; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &c)) | 
|  | goto failed; | 
|  |  | 
|  | sprintf(&attrval[idx], "%1c", | 
|  | isprint(c) ? c : '.'); | 
|  | } | 
|  | print_field("%*cContinuingAttributeValue: %s", | 
|  | (indent - 8), ' ', attrval); | 
|  |  | 
|  | len -= size; | 
|  | } | 
|  | break; | 
|  | default: | 
|  | goto failed; | 
|  | } | 
|  |  | 
|  | while (num > 0 && len > 0) { | 
|  | uint32_t attr; | 
|  | uint16_t charset, attrlen; | 
|  | uint8_t idx; | 
|  | char attrval[UINT8_MAX] = {0}; | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &attr)) | 
|  | goto failed; | 
|  |  | 
|  | print_field("%*cAttribute: 0x%08x (%s)", (indent - 8), | 
|  | ' ', attr, mediattr2str(attr)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &charset)) | 
|  | goto failed; | 
|  |  | 
|  | print_field("%*cCharsetID: 0x%04x (%s)", (indent - 8), | 
|  | ' ', charset, charset2str(charset)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &attrlen)) | 
|  | goto failed; | 
|  |  | 
|  | print_field("%*cAttributeValueLength: 0x%04x", | 
|  | (indent - 8), ' ', attrlen); | 
|  |  | 
|  | len -= sizeof(attr) + sizeof(charset) + sizeof(attrlen); | 
|  | num--; | 
|  |  | 
|  | for (idx = 0; attrlen > 0 && len > 0; idx++, attrlen--, len--) { | 
|  | uint8_t c; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &c)) | 
|  | goto failed; | 
|  |  | 
|  | sprintf(&attrval[idx], "%1c", isprint(c) ? c : '.'); | 
|  | } | 
|  | print_field("%*cAttributeValue: %s", (indent - 8), | 
|  | ' ', attrval); | 
|  |  | 
|  | if (attrlen > 0) | 
|  | avrcp_continuing.size = attrlen; | 
|  | } | 
|  |  | 
|  | avrcp_continuing.num = num; | 
|  | return true; | 
|  |  | 
|  | failed: | 
|  | avrcp_continuing.num = 0; | 
|  | avrcp_continuing.size = 0; | 
|  | return false; | 
|  | } | 
|  |  | 
|  | static bool avrcp_get_play_status(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint32_t interval; | 
|  | uint8_t status; | 
|  |  | 
|  | if (ctype <= AVC_CTYPE_GENERAL_INQUIRY) | 
|  | return true; | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &interval)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cSongLength: 0x%08x (%u miliseconds)", | 
|  | (indent - 8), ' ', interval, interval); | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &interval)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cSongPosition: 0x%08x (%u miliseconds)", | 
|  | (indent - 8), ' ', interval, interval); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cPlayStatus: 0x%02x (%s)", (indent - 8), | 
|  | ' ', status, playstatus2str(status)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_register_notification(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t event, status; | 
|  | uint16_t uid; | 
|  | uint32_t interval; | 
|  | uint64_t id; | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | goto response; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &event)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cEventID: 0x%02x (%s)", (indent - 8), | 
|  | ' ', event, event2str(event)); | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &interval)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cInterval: 0x%08x (%u seconds)", | 
|  | (indent - 8), ' ', interval, interval); | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &event)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cEventID: 0x%02x (%s)", (indent - 8), | 
|  | ' ', event, event2str(event)); | 
|  |  | 
|  | switch (event) { | 
|  | case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cPlayStatus: 0x%02x (%s)", (indent - 8), | 
|  | ' ', status, playstatus2str(status)); | 
|  | break; | 
|  | case AVRCP_EVENT_TRACK_CHANGED: | 
|  | if (!l2cap_frame_get_be64(frame, &id)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cIdentifier: 0x%16" PRIx64 " (%" PRIu64 ")", | 
|  | (indent - 8), ' ', id, id); | 
|  | break; | 
|  | case AVRCP_EVENT_PLAYBACK_POS_CHANGED: | 
|  | if (!l2cap_frame_get_be32(frame, &interval)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cPosition: 0x%08x (%u miliseconds)", | 
|  | (indent - 8), ' ', interval, interval); | 
|  | break; | 
|  | case AVRCP_EVENT_BATT_STATUS_CHANGED: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cBatteryStatus: 0x%02x (%s)", (indent - 8), | 
|  | ' ', status, status2str(status)); | 
|  |  | 
|  | break; | 
|  | case AVRCP_EVENT_SYSTEM_STATUS_CHANGED: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cSystemStatus: 0x%02x ", (indent - 8), | 
|  | ' ', status); | 
|  | switch (status) { | 
|  | case 0x00: | 
|  | printf("(POWER_ON)\n"); | 
|  | break; | 
|  | case 0x01: | 
|  | printf("(POWER_OFF)\n"); | 
|  | break; | 
|  | case 0x02: | 
|  | printf("(UNPLUGGED)\n"); | 
|  | break; | 
|  | default: | 
|  | printf("(UNKNOWN)\n"); | 
|  | break; | 
|  | } | 
|  | break; | 
|  | case AVRCP_EVENT_PLAYER_APPLICATION_SETTING_CHANGED: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeCount: 0x%02x", (indent - 8), | 
|  | ' ', status); | 
|  |  | 
|  | for (; status > 0; status--) { | 
|  | uint8_t attr, value; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%02x (%s)", | 
|  | (indent - 8), ' ', attr, attr2str(attr)); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &value)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cValueID: 0x%02x (%s)", (indent - 8), | 
|  | ' ', value, value2str(attr, value)); | 
|  | } | 
|  | break; | 
|  | case AVRCP_EVENT_VOLUME_CHANGED: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | status &= 0x7F; | 
|  |  | 
|  | print_field("%*cVolume: %.2f%% (%d/127)", (indent - 8), | 
|  | ' ', status/1.27, status); | 
|  | break; | 
|  | case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: | 
|  | if (!l2cap_frame_get_be16(frame, &uid)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cPlayerID: 0x%04x (%u)", (indent - 8), | 
|  | ' ', uid, uid); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &uid)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUIDCounter: 0x%04x (%u)", (indent - 8), | 
|  | ' ', uid, uid); | 
|  | break; | 
|  | case AVRCP_EVENT_UIDS_CHANGED: | 
|  | if (!l2cap_frame_get_be16(frame, &uid)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUIDCounter: 0x%04x (%u)", (indent - 8), | 
|  | ' ', uid, uid); | 
|  | break; | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_set_absolute_volume(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t value; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &value)) | 
|  | return false; | 
|  |  | 
|  | value &= 0x7F; | 
|  | print_field("%*cVolume: %.2f%% (%d/127)", (indent - 8), | 
|  | ' ', value/1.27, value); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_set_addressed_player(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint16_t id; | 
|  | uint8_t status; | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | goto response; | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &id)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cPlayerID: 0x%04x (%u)", (indent - 8), ' ', id, id); | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStatus: 0x%02x (%s)", (indent - 8), ' ', | 
|  | status, error2str(status)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_play_item(struct avctp_frame *avctp_frame, uint8_t ctype, | 
|  | uint8_t len, uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint64_t uid; | 
|  | uint16_t uidcounter; | 
|  | uint8_t scope, status; | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | goto response; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &scope)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cScope: 0x%02x (%s)", (indent - 8), ' ', | 
|  | scope, scope2str(scope)); | 
|  |  | 
|  | if (!l2cap_frame_get_be64(frame, &uid)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUID: 0x%16" PRIx64 " (%" PRIu64 ")", (indent - 8), | 
|  | ' ', uid, uid); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &uidcounter)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUIDCounter: 0x%04x (%u)", (indent - 8), ' ', | 
|  | uidcounter, uidcounter); | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStatus: 0x%02x (%s)", (indent - 8), ' ', status, | 
|  | error2str(status)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_add_to_now_playing(struct avctp_frame *avctp_frame, | 
|  | uint8_t ctype, uint8_t len, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint64_t uid; | 
|  | uint16_t uidcounter; | 
|  | uint8_t scope, status; | 
|  |  | 
|  | if (ctype > AVC_CTYPE_GENERAL_INQUIRY) | 
|  | goto response; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &scope)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cScope: 0x%02x (%s)", (indent - 8), ' ', | 
|  | scope, scope2str(scope)); | 
|  |  | 
|  | if (!l2cap_frame_get_be64(frame, &uid)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUID: 0x%16" PRIx64 " (%" PRIu64 ")", (indent - 8), | 
|  | ' ', uid, uid); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &uidcounter)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUIDCounter: 0x%04x (%u)", (indent - 8), ' ', | 
|  | uidcounter, uidcounter); | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStatus: 0x%02x (%s)", (indent - 8), ' ', status, | 
|  | error2str(status)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | struct avrcp_ctrl_pdu_data { | 
|  | uint8_t pduid; | 
|  | bool (*func) (struct avctp_frame *avctp_frame, uint8_t ctype, | 
|  | uint8_t len, uint8_t indent); | 
|  | }; | 
|  |  | 
|  | static const struct avrcp_ctrl_pdu_data avrcp_ctrl_pdu_table[] = { | 
|  | { 0x10, avrcp_get_capabilities			}, | 
|  | { 0x11, avrcp_list_player_attributes		}, | 
|  | { 0x12, avrcp_list_player_values		}, | 
|  | { 0x13, avrcp_get_current_player_value		}, | 
|  | { 0x14, avrcp_set_player_value			}, | 
|  | { 0x15, avrcp_get_player_attribute_text		}, | 
|  | { 0x16, avrcp_get_player_value_text		}, | 
|  | { 0x17, avrcp_displayable_charset		}, | 
|  | { 0x20, avrcp_get_element_attributes		}, | 
|  | { 0x30, avrcp_get_play_status			}, | 
|  | { 0x31, avrcp_register_notification		}, | 
|  | { 0x50, avrcp_set_absolute_volume		}, | 
|  | { 0x60, avrcp_set_addressed_player		}, | 
|  | { 0x74, avrcp_play_item				}, | 
|  | { 0x90, avrcp_add_to_now_playing		}, | 
|  | { } | 
|  | }; | 
|  |  | 
|  | static bool avrcp_rejected_packet(struct l2cap_frame *frame, uint8_t indent) | 
|  | { | 
|  | uint8_t status; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cError: 0x%02x (%s)", (indent - 8), ' ', status, | 
|  | error2str(status)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_pdu_packet(struct avctp_frame *avctp_frame, uint8_t ctype, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t pduid; | 
|  | uint16_t len; | 
|  | int i; | 
|  | const struct avrcp_ctrl_pdu_data *ctrl_pdu_data = NULL; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &pduid)) | 
|  | return false; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &avctp_frame->pt)) | 
|  | return false; | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &len)) | 
|  | return false; | 
|  |  | 
|  | print_indent(indent, COLOR_OFF, "AVRCP: ", pdu2str(pduid), COLOR_OFF, | 
|  | " pt %s len 0x%04x", pt2str(avctp_frame->pt), len); | 
|  |  | 
|  | if (frame->size != len) | 
|  | return false; | 
|  |  | 
|  | if (ctype == 0xA) | 
|  | return avrcp_rejected_packet(frame, indent + 2); | 
|  |  | 
|  | for (i = 0; avrcp_ctrl_pdu_table[i].func; i++) { | 
|  | if (avrcp_ctrl_pdu_table[i].pduid == pduid) { | 
|  | ctrl_pdu_data = &avrcp_ctrl_pdu_table[i]; | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | if (!ctrl_pdu_data || !ctrl_pdu_data->func) { | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | return ctrl_pdu_data->func(avctp_frame, ctype, len, indent + 2); | 
|  | } | 
|  |  | 
|  | static bool avrcp_control_packet(struct avctp_frame *avctp_frame) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  |  | 
|  | uint8_t ctype, address, subunit, opcode, company[3], indent = 2; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &ctype) || | 
|  | !l2cap_frame_get_u8(frame, &address) || | 
|  | !l2cap_frame_get_u8(frame, &opcode)) | 
|  | return false; | 
|  |  | 
|  | print_field("AV/C: %s: address 0x%02x opcode 0x%02x", | 
|  | ctype2str(ctype), address, opcode); | 
|  |  | 
|  | subunit = address >> 3; | 
|  |  | 
|  | print_field("%*cSubunit: %s", indent, ' ', subunit2str(subunit)); | 
|  |  | 
|  | print_field("%*cOpcode: %s", indent, ' ', opcode2str(opcode)); | 
|  |  | 
|  | /* Skip non-panel subunit packets */ | 
|  | if (subunit != 0x09) { | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | /* Not implemented should not contain any operand */ | 
|  | if (ctype == 0x8) { | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | switch (opcode) { | 
|  | case 0x7c: | 
|  | return avrcp_passthrough_packet(avctp_frame, 10); | 
|  | case 0x00: | 
|  | if (!l2cap_frame_get_u8(frame, &company[0]) || | 
|  | !l2cap_frame_get_u8(frame, &company[1]) || | 
|  | !l2cap_frame_get_u8(frame, &company[2])) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCompany ID: 0x%02x%02x%02x", indent, ' ', | 
|  | company[0], company[1], company[2]); | 
|  |  | 
|  | return avrcp_pdu_packet(avctp_frame, ctype, 10); | 
|  | default: | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return true; | 
|  | } | 
|  | } | 
|  |  | 
|  | static const char *dir2str(uint8_t dir) | 
|  | { | 
|  | switch (dir) { | 
|  | case 0x00: | 
|  | return "Folder Up"; | 
|  | case 0x01: | 
|  | return "Folder Down"; | 
|  | } | 
|  |  | 
|  | return "Reserved"; | 
|  | } | 
|  |  | 
|  | static bool avrcp_change_path(struct avctp_frame *avctp_frame) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint64_t uid; | 
|  | uint32_t items; | 
|  | uint16_t uidcounter; | 
|  | uint8_t dir, status, indent = 2; | 
|  |  | 
|  | if (avctp_frame->hdr & 0x02) | 
|  | goto response; | 
|  |  | 
|  | if (frame->size < 11) { | 
|  | print_field("%*cPDU Malformed", indent, ' '); | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &uidcounter)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', | 
|  | uidcounter, uidcounter); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &dir)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cDirection: 0x%02x (%s)", indent, ' ', | 
|  | dir, dir2str(dir)); | 
|  |  | 
|  | if (!l2cap_frame_get_be64(frame, &uid)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cFolderUID: 0x%16" PRIx64 " (%" PRIu64 ")", indent, ' ', | 
|  | uid, uid); | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStatus: 0x%02x (%s)", indent, ' ', | 
|  | status, error2str(status)); | 
|  |  | 
|  | if (frame->size == 1) | 
|  | return false; | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &items)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cNumber of Items: 0x%04x (%u)", indent, ' ', | 
|  | items, items); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  |  | 
|  | static struct { | 
|  | const char *str; | 
|  | bool reserved; | 
|  | } features_table[] = { | 
|  | /* Ignore passthrough bits */ | 
|  | [58] = { "Advanced Control Player" }, | 
|  | [59] = { "Browsing" }, | 
|  | [60] = { "Searching" }, | 
|  | [61] = { "AddToNowPlaying" }, | 
|  | [62] = { "Unique UIDs" }, | 
|  | [63] = { "OnlyBrowsableWhenAddressed" }, | 
|  | [64] = { "OnlySearchableWhenAddressed" }, | 
|  | [65] = { "NowPlaying" }, | 
|  | [66] = { "UIDPersistency" }, | 
|  | /* 67-127 reserved */ | 
|  | [67 ... 127] = { .reserved = true }, | 
|  | }; | 
|  |  | 
|  | static void print_features(uint8_t features[16], uint8_t indent) | 
|  | { | 
|  | int i; | 
|  |  | 
|  | for (i = 0; i < 127; i++) { | 
|  | if (!(features[i / 8] & (1 << (i % 8)))) | 
|  | continue; | 
|  |  | 
|  | if (features_table[i].reserved) { | 
|  | print_text(COLOR_WHITE_BG, "Unknown bit %u", i); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | if (!features_table[i].str) | 
|  | continue; | 
|  |  | 
|  | print_field("%*c%s", indent, ' ', features_table[i].str); | 
|  | } | 
|  | } | 
|  |  | 
|  | static bool avrcp_media_player_item(struct avctp_frame *avctp_frame, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint16_t id, charset, namelen; | 
|  | uint8_t type, status, i; | 
|  | uint32_t subtype; | 
|  | uint8_t features[16]; | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &id)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cPlayerID: 0x%04x (%u)", indent, ' ', id, id); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &type)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cPlayerType: 0x%04x (%s)", indent, ' ', | 
|  | type, playertype2str(type)); | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &subtype)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cPlayerSubType: 0x%08x (%s)", indent, ' ', | 
|  | subtype, playersubtype2str(subtype)); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cPlayStatus: 0x%02x (%s)", indent, ' ', | 
|  | status, playstatus2str(status)); | 
|  |  | 
|  | printf("%*cFeatures: 0x", indent+8, ' '); | 
|  |  | 
|  | for (i = 0; i < 16; i++) { | 
|  | if (!l2cap_frame_get_u8(frame, &features[i])) | 
|  | return false; | 
|  |  | 
|  | printf("%02x", features[i]); | 
|  | } | 
|  |  | 
|  | printf("\n"); | 
|  |  | 
|  | print_features(features, indent + 2); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &charset)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', | 
|  | charset, charset2str(charset)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &namelen)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cNameLength: 0x%04x (%u)", indent, ' ', | 
|  | namelen, namelen); | 
|  |  | 
|  | printf("%*cName: ", indent+8, ' '); | 
|  | for (; namelen > 0; namelen--) { | 
|  | uint8_t c; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &c)) | 
|  | return false; | 
|  | printf("%1c", isprint(c) ? c : '.'); | 
|  | } | 
|  | printf("\n"); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_folder_item(struct avctp_frame *avctp_frame, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t type, playable; | 
|  | uint16_t charset, namelen; | 
|  | uint64_t uid; | 
|  |  | 
|  | if (frame->size < 14) { | 
|  | printf("PDU Malformed\n"); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!l2cap_frame_get_be64(frame, &uid)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cFolderUID: 0x%16" PRIx64 " (%" PRIu64 ")", indent, ' ', | 
|  | uid, uid); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &type)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cFolderType: 0x%02x (%s)", indent, ' ', | 
|  | type, foldertype2str(type)); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &playable)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cIsPlayable: 0x%02x (%s)", indent, ' ', playable, | 
|  | playable & 0x01 ? "True" : "False"); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &charset)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', | 
|  | charset, charset2str(charset)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &namelen)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cNameLength: 0x%04x (%u)", indent, ' ', | 
|  | namelen, namelen); | 
|  |  | 
|  | printf("%*cName: ", indent+8, ' '); | 
|  | for (; namelen > 0; namelen--) { | 
|  | uint8_t c; | 
|  | if (!l2cap_frame_get_u8(frame, &c)) | 
|  | return false; | 
|  |  | 
|  | printf("%1c", isprint(c) ? c : '.'); | 
|  | } | 
|  | printf("\n"); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_attribute_entry_list(struct avctp_frame *avctp_frame, | 
|  | uint8_t indent, uint8_t count) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  |  | 
|  | for (; count > 0; count--) { | 
|  | uint32_t attr; | 
|  | uint16_t charset, len; | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%08x (%s)", indent, ' ', | 
|  | attr, mediattr2str(attr)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &charset)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', | 
|  | charset, charset2str(charset)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &len)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeLength: 0x%04x (%u)", indent, ' ', | 
|  | len, len); | 
|  |  | 
|  | printf("%*cAttributeValue: ", indent+8, ' '); | 
|  | for (; len > 0; len--) { | 
|  | uint8_t c; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &c)) | 
|  | return false; | 
|  |  | 
|  | printf("%1c", isprint(c) ? c : '.'); | 
|  | } | 
|  | printf("\n"); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_media_element_item(struct avctp_frame *avctp_frame, | 
|  | uint8_t indent) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint64_t uid; | 
|  | uint16_t charset, namelen; | 
|  | uint8_t type, count; | 
|  |  | 
|  | if (!l2cap_frame_get_be64(frame, &uid)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cElementUID: 0x%16" PRIx64 " (%" PRIu64 ")", | 
|  | indent, ' ', uid, uid); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &type)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cElementType: 0x%02x (%s)", indent, ' ', | 
|  | type, elementtype2str(type)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &charset)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', | 
|  | charset, charset2str(charset)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &namelen)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cNameLength: 0x%04x (%u)", indent, ' ', | 
|  | namelen, namelen); | 
|  |  | 
|  | printf("%*cName: ", indent+8, ' '); | 
|  | for (; namelen > 0; namelen--) { | 
|  | uint8_t c; | 
|  | if (!l2cap_frame_get_u8(frame, &c)) | 
|  | return false; | 
|  |  | 
|  | printf("%1c", isprint(c) ? c : '.'); | 
|  | } | 
|  | printf("\n"); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &count)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeCount: 0x%02x (%u)", indent, ' ', | 
|  | count, count); | 
|  |  | 
|  | if (!avrcp_attribute_entry_list(avctp_frame, indent, count)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_general_reject(struct avctp_frame *avctp_frame) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t status, indent = 2; | 
|  |  | 
|  | if (avctp_frame->hdr & 0x02) | 
|  | goto response; | 
|  |  | 
|  | print_field("%*cPDU Malformed", indent, ' '); | 
|  | packet_hexdump(frame->data, frame->size); | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStatus: 0x%02x (%s)", indent, ' ', | 
|  | status, error2str(status)); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_get_total_number_of_items(struct avctp_frame *avctp_frame) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint32_t num_of_items; | 
|  | uint16_t uidcounter; | 
|  | uint8_t scope, status, indent = 2; | 
|  |  | 
|  | if (avctp_frame->hdr & 0x02) | 
|  | goto response; | 
|  |  | 
|  | if (frame->size < 4) { | 
|  | printf("PDU Malformed\n"); | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &scope)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cScope: 0x%02x (%s)", (indent - 8), ' ', | 
|  | scope, scope2str(scope)); | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStatus: 0x%02x (%s)", indent, ' ', | 
|  | status, error2str(status)); | 
|  |  | 
|  | if (frame->size == 1) | 
|  | return false; | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &uidcounter)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', | 
|  | uidcounter, uidcounter); | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &num_of_items)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cNumber of Items: 0x%04x (%u)", indent, ' ', | 
|  | num_of_items, num_of_items); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_search_item(struct avctp_frame *avctp_frame) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint32_t items; | 
|  | uint16_t charset, namelen, uidcounter; | 
|  | uint8_t status, indent = 2; | 
|  |  | 
|  | if (avctp_frame->hdr & 0x02) | 
|  | goto response; | 
|  |  | 
|  | if (frame->size < 4) { | 
|  | printf("PDU Malformed\n"); | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &charset)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', | 
|  | charset, charset2str(charset)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &namelen)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cLength: 0x%04x (%u)", indent, ' ', namelen, namelen); | 
|  |  | 
|  | printf("%*cString: ", indent+8, ' '); | 
|  | for (; namelen > 0; namelen--) { | 
|  | uint8_t c; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &c)) | 
|  | return false; | 
|  |  | 
|  | printf("%1c", isprint(c) ? c : '.'); | 
|  | } | 
|  |  | 
|  | printf("\n"); | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStatus: 0x%02x (%s)", indent, ' ', | 
|  | status, error2str(status)); | 
|  |  | 
|  | if (frame->size == 1) | 
|  | return false; | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &uidcounter)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', | 
|  | uidcounter, uidcounter); | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &items)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cNumber of Items: 0x%04x (%u)", indent, ' ', | 
|  | items, items); | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_get_item_attributes(struct avctp_frame *avctp_frame) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint64_t uid; | 
|  | uint16_t uidcounter; | 
|  | uint8_t scope, count, status, indent = 2; | 
|  |  | 
|  | if (avctp_frame->hdr & 0x02) | 
|  | goto response; | 
|  |  | 
|  | if (frame->size < 12) { | 
|  | print_field("%*cPDU Malformed", indent, ' '); | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return false; | 
|  | } | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &scope)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cScope: 0x%02x (%s)", indent, ' ', | 
|  | scope, scope2str(scope)); | 
|  |  | 
|  | if (!l2cap_frame_get_be64(frame, &uid)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUID: 0x%016" PRIx64 " (%" PRIu64 ")", indent, | 
|  | ' ', uid, uid); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &uidcounter)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', | 
|  | uidcounter, uidcounter); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &count)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeCount: 0x%02x (%u)", indent, ' ', | 
|  | count, count); | 
|  |  | 
|  | for (; count > 0; count--) { | 
|  | uint32_t attr; | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%08x (%s)", indent, ' ', | 
|  | attr, mediattr2str(attr)); | 
|  | } | 
|  |  | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStatus: 0x%02x (%s)", indent, ' ', | 
|  | status, error2str(status)); | 
|  |  | 
|  | if (frame->size == 1) | 
|  | return false; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &count)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeCount: 0x%02x (%u)", indent, ' ', | 
|  | count, count); | 
|  |  | 
|  | if (!avrcp_attribute_entry_list(avctp_frame, indent, count)) | 
|  | return false; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_get_folder_items(struct avctp_frame *avctp_frame) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint8_t scope, count, status, indent = 2; | 
|  | uint32_t start, end; | 
|  | uint16_t uid, num; | 
|  |  | 
|  | if (avctp_frame->hdr & 0x02) | 
|  | goto response; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &scope)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cScope: 0x%02x (%s)", indent, ' ', | 
|  | scope, scope2str(scope)); | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &start)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStartItem: 0x%08x (%u)", indent, ' ', start, start); | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &end)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cEndItem: 0x%08x (%u)", indent, ' ', end, end); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &count)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeCount: 0x%02x (%u)", indent, ' ', | 
|  | count, count); | 
|  |  | 
|  | for (; count > 0; count--) { | 
|  | uint16_t attr; | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &attr)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cAttributeID: 0x%08x (%s)", indent, ' ', | 
|  | attr, mediattr2str(attr)); | 
|  | } | 
|  |  | 
|  | return false; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStatus: 0x%02x (%s)", indent, ' ', | 
|  | status, error2str(status)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &uid)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', uid, uid); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &num)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cNumOfItems: 0x%04x (%u)", indent, ' ', num, num); | 
|  |  | 
|  | for (; num > 0; num--) { | 
|  | uint8_t type; | 
|  | uint16_t len; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &type)) | 
|  | return false; | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &len)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cItem: 0x%02x (%s) ", indent, ' ', | 
|  | type, type2str(type)); | 
|  | print_field("%*cLength: 0x%04x (%u)", indent, ' ', len, len); | 
|  |  | 
|  | switch (type) { | 
|  | case AVRCP_MEDIA_PLAYER_ITEM_TYPE: | 
|  | avrcp_media_player_item(avctp_frame, indent); | 
|  | break; | 
|  | case AVRCP_FOLDER_ITEM_TYPE: | 
|  | avrcp_folder_item(avctp_frame, indent); | 
|  | break; | 
|  | case AVRCP_MEDIA_ELEMENT_ITEM_TYPE: | 
|  | avrcp_media_element_item(avctp_frame, indent); | 
|  | break; | 
|  | default: | 
|  | print_field("%*cUnknown Media Item type", indent, ' '); | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | break; | 
|  | } | 
|  | } | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_set_browsed_player(struct avctp_frame *avctp_frame) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint32_t items; | 
|  | uint16_t id, uids, charset; | 
|  | uint8_t status, folders, indent = 2; | 
|  |  | 
|  | if (avctp_frame->hdr & 0x02) | 
|  | goto response; | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &id)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cPlayerID: 0x%04x (%u)", indent, ' ', id, id); | 
|  | return true; | 
|  |  | 
|  | response: | 
|  | if (!l2cap_frame_get_u8(frame, &status)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cStatus: 0x%02x (%s)", indent, ' ', status, | 
|  | error2str(status)); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &uids)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cUIDCounter: 0x%04x (%u)", indent, ' ', uids, uids); | 
|  |  | 
|  | if (!l2cap_frame_get_be32(frame, &items)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cNumber of Items: 0x%08x (%u)", indent, ' ', | 
|  | items, items); | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &charset)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cCharsetID: 0x%04x (%s)", indent, ' ', charset, | 
|  | charset2str(charset)); | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &folders)) | 
|  | return false; | 
|  |  | 
|  | print_field("%*cFolder Depth: 0x%02x (%u)", indent, ' ', folders, | 
|  | folders); | 
|  |  | 
|  | for (; folders > 0; folders--) { | 
|  | uint8_t len; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &len)) | 
|  | return false; | 
|  |  | 
|  | if (!len) { | 
|  | print_field("%*cFolder: <empty>", indent, ' '); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | printf("%*cFolder: ", indent+8, ' '); | 
|  | for (; len > 0; len--) { | 
|  | uint8_t c; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &c)) | 
|  | return false; | 
|  |  | 
|  | printf("%1c", isprint(c) ? c : '.'); | 
|  | } | 
|  | printf("\n"); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static bool avrcp_browsing_packet(struct avctp_frame *avctp_frame) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | uint16_t len; | 
|  | uint8_t pduid; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(frame, &pduid)) | 
|  | return false; | 
|  |  | 
|  | if (!l2cap_frame_get_be16(frame, &len)) | 
|  | return false; | 
|  |  | 
|  | print_field("AVRCP: %s: len 0x%04x", pdu2str(pduid), len); | 
|  |  | 
|  | switch (pduid) { | 
|  | case AVRCP_SET_BROWSED_PLAYER: | 
|  | avrcp_set_browsed_player(avctp_frame); | 
|  | break; | 
|  | case AVRCP_GET_FOLDER_ITEMS: | 
|  | avrcp_get_folder_items(avctp_frame); | 
|  | break; | 
|  | case AVRCP_CHANGE_PATH: | 
|  | avrcp_change_path(avctp_frame); | 
|  | break; | 
|  | case AVRCP_GET_ITEM_ATTRIBUTES: | 
|  | avrcp_get_item_attributes(avctp_frame); | 
|  | break; | 
|  | case AVRCP_GET_TOTAL_NUMBER_OF_ITEMS: | 
|  | avrcp_get_total_number_of_items(avctp_frame); | 
|  | break; | 
|  | case AVRCP_SEARCH: | 
|  | avrcp_search_item(avctp_frame); | 
|  | break; | 
|  | case AVRCP_GENERAL_REJECT: | 
|  | avrcp_general_reject(avctp_frame); | 
|  | break; | 
|  | default: | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | } | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static void avrcp_packet(struct avctp_frame *avctp_frame) | 
|  | { | 
|  | struct l2cap_frame *frame = &avctp_frame->l2cap_frame; | 
|  | bool ret; | 
|  |  | 
|  | switch (frame->psm) { | 
|  | case 0x17: | 
|  | ret = avrcp_control_packet(avctp_frame); | 
|  | break; | 
|  | case 0x1B: | 
|  | ret = avrcp_browsing_packet(avctp_frame); | 
|  | break; | 
|  | default: | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (!ret) { | 
|  | print_text(COLOR_ERROR, "PDU malformed"); | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | } | 
|  | } | 
|  |  | 
|  | void avctp_packet(const struct l2cap_frame *frame) | 
|  | { | 
|  | struct l2cap_frame *l2cap_frame; | 
|  | struct avctp_frame avctp_frame; | 
|  | const char *pdu_color; | 
|  |  | 
|  | l2cap_frame_pull(&avctp_frame.l2cap_frame, frame, 0); | 
|  |  | 
|  | l2cap_frame = &avctp_frame.l2cap_frame; | 
|  |  | 
|  | if (!l2cap_frame_get_u8(l2cap_frame, &avctp_frame.hdr) || | 
|  | !l2cap_frame_get_be16(l2cap_frame, &avctp_frame.pid)) { | 
|  | print_text(COLOR_ERROR, "frame too short"); | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (frame->in) | 
|  | pdu_color = COLOR_MAGENTA; | 
|  | else | 
|  | pdu_color = COLOR_BLUE; | 
|  |  | 
|  | print_indent(6, pdu_color, "AVCTP", "", COLOR_OFF, | 
|  | " %s: %s: type 0x%02x label %d PID 0x%04x", | 
|  | frame->psm == 23 ? "Control" : "Browsing", | 
|  | avctp_frame.hdr & 0x02 ? "Response" : "Command", | 
|  | avctp_frame.hdr & 0x0c, avctp_frame.hdr >> 4, | 
|  | avctp_frame.pid); | 
|  |  | 
|  | if (avctp_frame.pid == 0x110e || avctp_frame.pid == 0x110c) | 
|  | avrcp_packet(&avctp_frame); | 
|  | else | 
|  | packet_hexdump(frame->data, frame->size); | 
|  | } |