mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-12-14 06:25:08 +08:00
2285 lines
48 KiB
C
2285 lines
48 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2011 Intel Corporation.
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program 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 General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; 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 <errno.h>
|
|
#include <unistd.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
#include <inttypes.h>
|
|
|
|
#include "parser.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_TURNER 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
|
|
|
|
/* 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
|
|
|
|
/* 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
|
|
|
|
/* 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_SEARCH 0x80
|
|
#define AVRCP_ADD_TO_NOW_PLAYING 0x90
|
|
#define AVRCP_GENERAL_REJECT 0xA0
|
|
|
|
/* 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
|
|
|
|
/* 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 0x0
|
|
#define AVRCP_MEDIA_ATTRIBUTE_TITLE 0x1
|
|
#define AVRCP_MEDIA_ATTRIBUTE_ARTIST 0x2
|
|
#define AVRCP_MEDIA_ATTRIBUTE_ALBUM 0x3
|
|
#define AVRCP_MEDIA_ATTRIBUTE_TRACK 0x4
|
|
#define AVRCP_MEDIA_ATTRIBUTE_TOTAL 0x5
|
|
#define AVRCP_MEDIA_ATTRIBUTE_GENRE 0x6
|
|
#define AVRCP_MEDIA_ATTRIBUTE_DURATION 0x7
|
|
|
|
/* 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
|
|
|
|
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 *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 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 *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_SEARCH:
|
|
return "Search";
|
|
case AVRCP_ADD_TO_NOW_PLAYING:
|
|
return "AddToNowPlaying";
|
|
case AVRCP_GENERAL_REJECT:
|
|
return "GeneralReject";
|
|
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 Bonds";
|
|
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 "Seach 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 void avrcp_rejected_dump(int level, struct frame *frm, uint16_t len)
|
|
{
|
|
uint8_t status;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 1) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
status = get_u8(frm);
|
|
printf("Error: 0x%02x (%s)\n", status, error2str(status));
|
|
}
|
|
|
|
static void avrcp_get_capabilities_dump(int level, struct frame *frm, uint16_t len)
|
|
{
|
|
uint8_t cap;
|
|
uint8_t count;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 1) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
cap = get_u8(frm);
|
|
printf("CapabilityID: 0x%02x (%s)\n", cap, cap2str(cap));
|
|
|
|
if (len == 1)
|
|
return;
|
|
|
|
p_indent(level, frm);
|
|
|
|
count = get_u8(frm);
|
|
printf("CapabilityCount: 0x%02x\n", count);
|
|
|
|
switch (cap) {
|
|
case 0x2:
|
|
for (; count > 0; count--) {
|
|
int i;
|
|
|
|
p_indent(level, frm);
|
|
|
|
printf("%s: 0x", cap2str(cap));
|
|
for (i = 0; i < 3; i++)
|
|
printf("%02x", get_u8(frm));
|
|
printf("\n");
|
|
}
|
|
break;
|
|
case 0x3:
|
|
for (; count > 0; count--) {
|
|
uint8_t event;
|
|
|
|
p_indent(level, frm);
|
|
|
|
event = get_u8(frm);
|
|
printf("%s: 0x%02x (%s)\n", cap2str(cap), event,
|
|
event2str(event));
|
|
}
|
|
break;
|
|
default:
|
|
raw_dump(level, frm);
|
|
}
|
|
}
|
|
|
|
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 void avrcp_list_player_attributes_dump(int level, struct frame *frm,
|
|
uint16_t len)
|
|
{
|
|
uint8_t num;
|
|
|
|
if (len == 0)
|
|
return;
|
|
|
|
p_indent(level, frm);
|
|
|
|
num = get_u8(frm);
|
|
printf("AttributeCount: 0x%02x\n", num);
|
|
|
|
for (; num > 0; num--) {
|
|
uint8_t attr;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u8(frm);
|
|
printf("AttributeID: 0x%02x (%s)\n", attr, attr2str(attr));
|
|
}
|
|
}
|
|
|
|
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 Suffle";
|
|
case 0x03:
|
|
return "Group Suffle";
|
|
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 void avrcp_list_player_values_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
static uint8_t attr = 0; /* Remember attribute */
|
|
uint8_t num;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 1) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
goto response;
|
|
|
|
attr = get_u8(frm);
|
|
printf("AttributeID: 0x%02x (%s)\n", attr, attr2str(attr));
|
|
|
|
return;
|
|
|
|
response:
|
|
num = get_u8(frm);
|
|
printf("ValueCount: 0x%02x\n", num);
|
|
|
|
for (; num > 0; num--) {
|
|
uint8_t value;
|
|
|
|
p_indent(level, frm);
|
|
|
|
value = get_u8(frm);
|
|
printf("ValueID: 0x%02x (%s)\n", value,
|
|
value2str(attr, value));
|
|
}
|
|
}
|
|
|
|
static void avrcp_get_current_player_value_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint8_t num;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 2) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
goto response;
|
|
|
|
num = get_u8(frm);
|
|
printf("AttributeCount: 0x%02x\n", num);
|
|
|
|
for (; num > 0; num--) {
|
|
uint8_t attr;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u8(frm);
|
|
printf("AttributeID: 0x%02x (%s)\n", attr, attr2str(attr));
|
|
}
|
|
|
|
return;
|
|
|
|
response:
|
|
num = get_u8(frm);
|
|
printf("ValueCount: 0x%02x\n", num);
|
|
|
|
for (; num > 0; num--) {
|
|
uint8_t attr, value;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u8(frm);
|
|
printf("AttributeID: 0x%02x (%s)\n", attr, attr2str(attr));
|
|
|
|
p_indent(level, frm);
|
|
|
|
value = get_u8(frm);
|
|
printf("ValueID: 0x%02x (%s)\n", value,
|
|
value2str(attr, value));
|
|
}
|
|
}
|
|
|
|
static void avrcp_set_player_value_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint8_t num;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
return;
|
|
|
|
if (len < 1) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
num = get_u8(frm);
|
|
printf("AttributeCount: 0x%02x\n", num);
|
|
|
|
for (; num > 0; num--) {
|
|
uint8_t attr, value;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u8(frm);
|
|
printf("AttributeID: 0x%02x (%s)\n", attr, attr2str(attr));
|
|
|
|
p_indent(level, frm);
|
|
|
|
value = get_u8(frm);
|
|
printf("ValueID: 0x%02x (%s)\n", value,
|
|
value2str(attr, value));
|
|
}
|
|
}
|
|
|
|
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 void avrcp_get_player_attribute_text_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint8_t num;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 1) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
num = get_u8(frm);
|
|
printf("AttributeCount: 0x%02x\n", num);
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
goto response;
|
|
|
|
for (; num > 0; num--) {
|
|
uint8_t attr;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u8(frm);
|
|
printf("AttributeID: 0x%02x (%s)\n", attr, attr2str(attr));
|
|
}
|
|
|
|
return;
|
|
|
|
response:
|
|
for (; num > 0; num--) {
|
|
uint8_t attr, len;
|
|
uint16_t charset;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u8(frm);
|
|
printf("AttributeID: 0x%02x (%s)\n", attr, attr2str(attr));
|
|
|
|
p_indent(level, frm);
|
|
|
|
charset = get_u16(frm);
|
|
printf("CharsetID: 0x%04x (%s)\n", charset,
|
|
charset2str(charset));
|
|
|
|
p_indent(level, frm);
|
|
|
|
len = get_u8(frm);
|
|
printf("StringLength: 0x%02x\n", len);
|
|
|
|
p_indent(level, frm);
|
|
|
|
printf("String: ");
|
|
for (; len > 0; len--) {
|
|
uint8_t c = get_u8(frm);
|
|
printf("%1c", isprint(c) ? c : '.');
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
static void avrcp_get_player_value_text_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
static uint8_t attr = 0; /* Remember attribute */
|
|
uint8_t num;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 1) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
goto response;
|
|
|
|
attr = get_u8(frm);
|
|
printf("AttributeID: 0x%02x (%s)\n", attr, attr2str(attr));
|
|
|
|
p_indent(level, frm);
|
|
|
|
num = get_u8(frm);
|
|
printf("ValueCount: 0x%02x\n", num);
|
|
|
|
for (; num > 0; num--) {
|
|
uint8_t value;
|
|
|
|
p_indent(level, frm);
|
|
|
|
value = get_u8(frm);
|
|
printf("ValueID: 0x%02x (%s)\n", value,
|
|
value2str(attr, value));
|
|
}
|
|
|
|
return;
|
|
|
|
response:
|
|
num = get_u8(frm);
|
|
printf("ValueCount: 0x%02x\n", num);
|
|
|
|
for (; num > 0; num--) {
|
|
uint8_t value, len;
|
|
uint16_t charset;
|
|
|
|
p_indent(level, frm);
|
|
|
|
value = get_u8(frm);
|
|
printf("ValueID: 0x%02x (%s)\n", value,
|
|
value2str(attr, value));
|
|
|
|
p_indent(level, frm);
|
|
|
|
charset = get_u16(frm);
|
|
printf("CharsetID: 0x%04x (%s)\n", charset,
|
|
charset2str(charset));
|
|
|
|
p_indent(level, frm);
|
|
|
|
len = get_u8(frm);
|
|
printf("StringLength: 0x%02x\n", len);
|
|
|
|
p_indent(level, frm);
|
|
|
|
printf("String: ");
|
|
for (; len > 0; len--) {
|
|
uint8_t c = get_u8(frm);
|
|
printf("%1c", isprint(c) ? c : '.');
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
static void avrcp_displayable_charset(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint8_t num;
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
return;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 2) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
num = get_u8(frm);
|
|
printf("CharsetCount: 0x%02x\n", num);
|
|
|
|
for (; num > 0; num--) {
|
|
uint16_t charset;
|
|
|
|
p_indent(level, frm);
|
|
|
|
charset = get_u16(frm);
|
|
printf("CharsetID: 0x%04x (%s)\n", charset,
|
|
charset2str(charset));
|
|
}
|
|
}
|
|
|
|
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 void avrcp_ct_battery_status_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint8_t status;
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
return;
|
|
|
|
p_indent(level, frm);
|
|
|
|
status = get_u8(frm);
|
|
printf("BatteryStatus: 0x%02x (%s)\n", status, status2str(status));
|
|
}
|
|
|
|
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 void avrcp_get_element_attributes_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len,
|
|
uint8_t pt)
|
|
{
|
|
uint64_t id;
|
|
uint8_t num;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
goto response;
|
|
|
|
if (len < 9) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
id = get_u64(frm);
|
|
printf("Identifier: 0x%jx (%s)\n", id, id ? "Reserved" : "PLAYING");
|
|
|
|
p_indent(level, frm);
|
|
|
|
num = get_u8(frm);
|
|
printf("AttributeCount: 0x%02x\n", num);
|
|
|
|
for (; num > 0; num--) {
|
|
uint32_t attr;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u32(frm);
|
|
printf("Attribute: 0x%08x (%s)\n", attr, mediattr2str(attr));
|
|
}
|
|
|
|
return;
|
|
|
|
response:
|
|
if (pt == AVRCP_PACKET_TYPE_SINGLE || pt == AVRCP_PACKET_TYPE_START) {
|
|
if (len < 1) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
num = get_u8(frm);
|
|
avrcp_continuing.num = num;
|
|
printf("AttributeCount: 0x%02x\n", num);
|
|
len--;
|
|
} else {
|
|
num = avrcp_continuing.num;
|
|
|
|
if (avrcp_continuing.size > 0) {
|
|
uint16_t size;
|
|
|
|
if (avrcp_continuing.size > len) {
|
|
size = len;
|
|
avrcp_continuing.size -= len;
|
|
} else {
|
|
size = avrcp_continuing.size;
|
|
avrcp_continuing.size = 0;
|
|
}
|
|
|
|
printf("ContinuingAttributeValue: ");
|
|
for (; size > 0; size--) {
|
|
uint8_t c = get_u8(frm);
|
|
printf("%1c", isprint(c) ? c : '.');
|
|
}
|
|
printf("\n");
|
|
|
|
len -= size;
|
|
}
|
|
}
|
|
|
|
while (num > 0 && len > 0) {
|
|
uint32_t attr;
|
|
uint16_t charset, attrlen;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u32(frm);
|
|
printf("Attribute: 0x%08x (%s)\n", attr, mediattr2str(attr));
|
|
|
|
p_indent(level, frm);
|
|
|
|
charset = get_u16(frm);
|
|
printf("CharsetID: 0x%04x (%s)\n", charset,
|
|
charset2str(charset));
|
|
|
|
p_indent(level, frm);
|
|
attrlen = get_u16(frm);
|
|
printf("AttributeValueLength: 0x%04x\n", attrlen);
|
|
|
|
len -= sizeof(attr) + sizeof(charset) + sizeof(attrlen);
|
|
num--;
|
|
|
|
p_indent(level, frm);
|
|
|
|
printf("AttributeValue: ");
|
|
for (; attrlen > 0 && len > 0; attrlen--, len--) {
|
|
uint8_t c = get_u8(frm);
|
|
printf("%1c", isprint(c) ? c : '.');
|
|
}
|
|
printf("\n");
|
|
|
|
if (attrlen > 0)
|
|
avrcp_continuing.size = attrlen;
|
|
}
|
|
|
|
avrcp_continuing.num = num;
|
|
}
|
|
|
|
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 void avrcp_get_play_status_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint32_t interval;
|
|
uint8_t status;
|
|
|
|
if (ctype <= AVC_CTYPE_GENERAL_INQUIRY)
|
|
return;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 9) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
interval = get_u32(frm);
|
|
printf("SongLength: 0x%08x (%u miliseconds)\n", interval, interval);
|
|
|
|
p_indent(level, frm);
|
|
|
|
interval = get_u32(frm);
|
|
printf("SongPosition: 0x%08x (%u miliconds)\n", interval, interval);
|
|
|
|
p_indent(level, frm);
|
|
|
|
status = get_u8(frm);
|
|
printf("PlayStatus: 0x%02x (%s)\n", status, playstatus2str(status));
|
|
}
|
|
|
|
static void avrcp_register_notification_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint8_t event, status;
|
|
uint16_t uid;
|
|
uint32_t interval;
|
|
uint64_t id;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
goto response;
|
|
|
|
if (len < 5) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
event = get_u8(frm);
|
|
printf("EventID: 0x%02x (%s)\n", event, event2str(event));
|
|
|
|
p_indent(level, frm);
|
|
|
|
interval = get_u32(frm);
|
|
printf("Interval: 0x%08x (%u seconds)\n", interval, interval);
|
|
|
|
return;
|
|
|
|
response:
|
|
if (len < 1) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
event = get_u8(frm);
|
|
printf("EventID: 0x%02x (%s)\n", event, event2str(event));
|
|
|
|
p_indent(level, frm);
|
|
|
|
switch (event) {
|
|
case AVRCP_EVENT_PLAYBACK_STATUS_CHANGED:
|
|
status = get_u8(frm);
|
|
printf("PlayStatus: 0x%02x (%s)\n", status,
|
|
playstatus2str(status));
|
|
break;
|
|
case AVRCP_EVENT_TRACK_CHANGED:
|
|
id = get_u64(frm);
|
|
printf("Identifier: 0x%jx (%s)\n", id,
|
|
id ? "Reserved" : "PLAYING");
|
|
break;
|
|
case AVRCP_EVENT_PLAYBACK_POS_CHANGED:
|
|
interval = get_u32(frm);
|
|
printf("Position: 0x%08x (%u miliseconds)\n", interval,
|
|
interval);
|
|
break;
|
|
case AVRCP_EVENT_BATT_STATUS_CHANGED:
|
|
status = get_u8(frm);
|
|
printf("BatteryStatus: 0x%02x (%s)\n", status,
|
|
status2str(status));
|
|
break;
|
|
case AVRCP_EVENT_SYSTEM_STATUS_CHANGED:
|
|
status = get_u8(frm);
|
|
printf("SystemStatus: 0x%02x ", status);
|
|
switch (status) {
|
|
case 0x00:
|
|
printf("(POWER_ON)\n");
|
|
case 0x01:
|
|
printf("(POWER_OFF)\n");
|
|
case 0x02:
|
|
printf("(UNPLUGGED)\n");
|
|
default:
|
|
printf("(UNKOWN)\n");
|
|
}
|
|
break;
|
|
case AVRCP_EVENT_PLAYER_APPLICATION_SETTING_CHANGED:
|
|
status = get_u8(frm);
|
|
printf("AttributeCount: 0x%02x\n", status);
|
|
|
|
for (; status > 0; status--) {
|
|
uint8_t attr, value;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u8(frm);
|
|
printf("AttributeID: 0x%02x (%s)\n", attr,
|
|
attr2str(attr));
|
|
|
|
p_indent(level, frm);
|
|
|
|
value = get_u8(frm);
|
|
printf("ValueID: 0x%02x (%s)\n", value,
|
|
value2str(attr, value));
|
|
}
|
|
break;
|
|
case AVRCP_EVENT_VOLUME_CHANGED:
|
|
status = get_u8(frm) & 0x7F;
|
|
printf("Volume: %.2f%% (%d/127)\n", status/1.27, status);
|
|
break;
|
|
case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED:
|
|
uid = get_u16(frm);
|
|
printf("PlayerID: 0x%04x (%u)\n", uid, uid);
|
|
|
|
|
|
p_indent(level, frm);
|
|
|
|
uid = get_u16(frm);
|
|
printf("UIDCounter: 0x%04x (%u)\n", uid, uid);
|
|
break;
|
|
}
|
|
}
|
|
|
|
static void avrcp_set_absolute_volume_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint8_t value;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 1) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
value = get_u8(frm) & 0x7F;
|
|
printf("Volume: %.2f%% (%d/127)\n", value/1.27, value);
|
|
}
|
|
|
|
static void avrcp_set_addressed_player(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint16_t id;
|
|
uint8_t status;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
goto response;
|
|
|
|
if (len < 2) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
id = get_u16(frm);
|
|
printf("PlayerID: 0x%04x (%u)\n", id, id);
|
|
return;
|
|
|
|
response:
|
|
if (len < 1) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
status = get_u8(frm);
|
|
printf("Status: 0x%02x (%s)\n", status, error2str(status));
|
|
}
|
|
|
|
static void avrcp_set_browsed_player_dump(int level, struct frame *frm,
|
|
uint8_t hdr, uint16_t len)
|
|
{
|
|
uint32_t items;
|
|
uint16_t id, uids, charset;
|
|
uint8_t status, folders;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (hdr & 0x02)
|
|
goto response;
|
|
|
|
if (len < 2) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
id = get_u16(frm);
|
|
printf("PlayerID: 0x%04x (%u)", id, id);
|
|
return;
|
|
|
|
response:
|
|
if (len != 1 && len < 10) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
status = get_u8(frm);
|
|
printf("Status: 0x%02x (%s)\n", status, error2str(status));
|
|
|
|
if (len == 1)
|
|
return;
|
|
|
|
p_indent(level, frm);
|
|
|
|
uids = get_u16(frm);
|
|
printf("UIDCounter: 0x%04x (%u)", uids, uids);
|
|
|
|
p_indent(level, frm);
|
|
|
|
items = get_u32(frm);
|
|
printf("Number of Items: 0x%04x (%u)", items, items);
|
|
|
|
p_indent(level, frm);
|
|
|
|
charset = get_u16(frm);
|
|
printf("CharsetID: 0x%04x (%s)\n", charset, charset2str(charset));
|
|
|
|
p_indent(level, frm);
|
|
|
|
folders = get_u8(frm);
|
|
printf("Folder Depth: 0x%02x (%u)", folders, folders);
|
|
|
|
for (; folders > 0; folders--) {
|
|
uint16_t len;
|
|
|
|
p_indent(level, frm);
|
|
|
|
len = get_u8(frm);
|
|
printf("Folder: ");
|
|
for (; len > 0; len--) {
|
|
uint8_t c = get_u8(frm);
|
|
printf("%1c", isprint(c) ? c : '.');
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
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 void avrcp_play_item_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint64_t uid;
|
|
uint32_t uidcounter;
|
|
uint8_t scope, status;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
goto response;
|
|
|
|
if (len < 13) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
scope = get_u8(frm);
|
|
printf("Scope: 0x%02x (%s)", scope, scope2str(scope));
|
|
|
|
p_indent(level, frm);
|
|
|
|
uid = get_u64(frm);
|
|
printf("UID: 0x%16" PRIx64 " (%" PRIu64 ")\n", uid, uid);
|
|
|
|
p_indent(level, frm);
|
|
|
|
uidcounter = get_u16(frm);
|
|
printf("UIDCounter: 0x%04x (%u)\n", uidcounter, uidcounter);
|
|
|
|
return;
|
|
|
|
response:
|
|
status = get_u8(frm);
|
|
printf("Status: 0x%02x (%s)\n", status, error2str(status));
|
|
}
|
|
|
|
static void avrcp_add_to_now_playing_dump(int level, struct frame *frm,
|
|
uint8_t ctype, uint16_t len)
|
|
{
|
|
uint64_t uid;
|
|
uint32_t uidcounter;
|
|
uint8_t scope, status;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (ctype > AVC_CTYPE_GENERAL_INQUIRY)
|
|
goto response;
|
|
|
|
if (len < 11) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
scope = get_u8(frm);
|
|
printf("Scope: 0x%02x (%s)\n", scope, scope2str(scope));
|
|
|
|
p_indent(level, frm);
|
|
|
|
uid = get_u64(frm);
|
|
printf("UID: 0x%16" PRIx64 " (%" PRIu64 ")\n", uid, uid);
|
|
|
|
p_indent(level, frm);
|
|
|
|
uidcounter = get_u16(frm);
|
|
printf("UIDCounter: 0x%04x (%u)\n", uidcounter, uidcounter);
|
|
|
|
return;
|
|
|
|
response:
|
|
status = get_u8(frm);
|
|
printf("Status: 0x%02x (%s)\n", status, error2str(status));
|
|
}
|
|
|
|
static void avrcp_pdu_dump(int level, struct frame *frm, uint8_t ctype)
|
|
{
|
|
uint8_t pduid, pt;
|
|
uint16_t len;
|
|
|
|
p_indent(level, frm);
|
|
|
|
pduid = get_u8(frm);
|
|
pt = get_u8(frm);
|
|
len = get_u16(frm);
|
|
|
|
printf("AVRCP: %s: pt %s len 0x%04x\n", pdu2str(pduid),
|
|
pt2str(pt), len);
|
|
|
|
if (len != frm->len) {
|
|
p_indent(level, frm);
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
if (ctype == AVC_CTYPE_REJECTED) {
|
|
avrcp_rejected_dump(level + 1, frm, len);
|
|
return;
|
|
}
|
|
|
|
switch (pduid) {
|
|
case AVRCP_GET_CAPABILITIES:
|
|
avrcp_get_capabilities_dump(level + 1, frm, len);
|
|
break;
|
|
case AVRCP_LIST_PLAYER_ATTRIBUTES:
|
|
avrcp_list_player_attributes_dump(level + 1, frm, len);
|
|
break;
|
|
case AVRCP_LIST_PLAYER_VALUES:
|
|
avrcp_list_player_values_dump(level + 1, frm, ctype, len);
|
|
break;
|
|
case AVRCP_GET_CURRENT_PLAYER_VALUE:
|
|
avrcp_get_current_player_value_dump(level + 1, frm, ctype,
|
|
len);
|
|
break;
|
|
case AVRCP_SET_PLAYER_VALUE:
|
|
avrcp_set_player_value_dump(level + 1, frm, ctype, len);
|
|
break;
|
|
case AVRCP_GET_PLAYER_ATTRIBUTE_TEXT:
|
|
avrcp_get_player_attribute_text_dump(level + 1, frm, ctype,
|
|
len);
|
|
break;
|
|
case AVRCP_GET_PLAYER_VALUE_TEXT:
|
|
avrcp_get_player_value_text_dump(level + 1, frm, ctype, len);
|
|
break;
|
|
case AVRCP_DISPLAYABLE_CHARSET:
|
|
avrcp_displayable_charset(level + 1, frm, ctype, len);
|
|
break;
|
|
case AVRCP_CT_BATTERY_STATUS:
|
|
avrcp_ct_battery_status_dump(level + 1, frm, ctype, len);
|
|
break;
|
|
case AVRCP_GET_ELEMENT_ATTRIBUTES:
|
|
avrcp_get_element_attributes_dump(level + 1, frm, ctype, len,
|
|
pt);
|
|
break;
|
|
case AVRCP_GET_PLAY_STATUS:
|
|
avrcp_get_play_status_dump(level + 1, frm, ctype, len);
|
|
break;
|
|
case AVRCP_REGISTER_NOTIFICATION:
|
|
avrcp_register_notification_dump(level + 1, frm, ctype, len);
|
|
break;
|
|
case AVRCP_SET_ABSOLUTE_VOLUME:
|
|
avrcp_set_absolute_volume_dump(level + 1, frm, ctype, len);
|
|
break;
|
|
case AVRCP_SET_ADDRESSED_PLAYER:
|
|
avrcp_set_addressed_player(level + 1, frm, ctype, len);
|
|
break;
|
|
case AVRCP_PLAY_ITEM:
|
|
avrcp_play_item_dump(level + 1, frm, ctype, len);
|
|
break;
|
|
case AVRCP_ADD_TO_NOW_PLAYING:
|
|
avrcp_add_to_now_playing_dump(level + 1, frm, ctype, len);
|
|
break;
|
|
default:
|
|
raw_dump(level, frm);
|
|
}
|
|
}
|
|
|
|
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 void avrcp_passthrough_dump(int level, struct frame *frm)
|
|
{
|
|
uint8_t op, len;
|
|
|
|
p_indent(level, frm);
|
|
|
|
op = get_u8(frm);
|
|
printf("Operation: 0x%02x (%s %s)\n", op, op2str(op),
|
|
op & 0x80 ? "Released" : "Pressed");
|
|
|
|
p_indent(level, frm);
|
|
|
|
len = get_u8(frm);
|
|
|
|
printf("Lenght: 0x%02x\n", len);
|
|
|
|
raw_dump(level, frm);
|
|
}
|
|
|
|
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_TURNER:
|
|
return "Turner";
|
|
case AVC_SUBUNIT_CA:
|
|
return "CA";
|
|
case AVC_SUBUNIT_CAMERA:
|
|
return "Camera";
|
|
case AVC_SUBUNIT_PANEL:
|
|
return "Panel";
|
|
case AVC_SUBUNIT_BULLETIN_BOARD:
|
|
return "Bulleting 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 *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 void avrcp_media_player_item_dump(int level, struct frame *frm,
|
|
uint16_t len)
|
|
{
|
|
uint16_t id, charset;
|
|
uint8_t type, status, namelen;
|
|
uint32_t subtype;
|
|
uint64_t features[2];
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 28) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
id = get_u16(frm);
|
|
printf("PlayerID: 0x%04x (%u)\n", id, id);
|
|
|
|
p_indent(level, frm);
|
|
|
|
type = get_u8(frm);
|
|
printf("PlayerType: 0x%04x (%s)\n", type, playertype2str(type));
|
|
|
|
p_indent(level, frm);
|
|
|
|
subtype = get_u32(frm);
|
|
printf("PlayerSubtype: 0x%08x (%s)\n", subtype,
|
|
playersubtype2str(subtype));
|
|
|
|
p_indent(level, frm);
|
|
|
|
status = get_u8(frm);
|
|
printf("PlayStatus: 0x%02x (%s)\n", status, playstatus2str(status));
|
|
|
|
p_indent(level, frm);
|
|
|
|
get_u128(frm, &features[0], &features[1]);
|
|
printf("Features: 0x%16" PRIx64 "%16" PRIx64 "\n", features[1],
|
|
features[0]);
|
|
|
|
p_indent(level, frm);
|
|
|
|
charset = get_u16(frm);
|
|
printf("CharsetID: 0x%04x (%s)\n", charset, charset2str(charset));
|
|
|
|
p_indent(level, frm);
|
|
|
|
namelen = get_u8(frm);
|
|
printf("NameLength: 0x%02x (%u)\n", namelen, namelen);
|
|
|
|
p_indent(level, frm);
|
|
|
|
printf("Name: ");
|
|
for (; namelen > 0; namelen--) {
|
|
uint8_t c = get_u8(frm);
|
|
printf("%1c", isprint(c) ? c : '.');
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
static const char *foldertype2str(uint8_t type)
|
|
{
|
|
switch (type) {
|
|
case 0x00:
|
|
return "Mixed";
|
|
case 0x01:
|
|
return "Titles";
|
|
case 0x02:
|
|
return "Albuns";
|
|
case 0x03:
|
|
return "Artists";
|
|
case 0x04:
|
|
return "Genres";
|
|
case 0x05:
|
|
return "Playlists";
|
|
case 0x06:
|
|
return "Years";
|
|
}
|
|
|
|
return "Reserved";
|
|
}
|
|
|
|
static void avrcp_folder_item_dump(int level, struct frame *frm, uint16_t len)
|
|
{
|
|
uint8_t type, playable, namelen;
|
|
uint16_t charset;
|
|
uint64_t uid;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (len < 14) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
uid = get_u64(frm);
|
|
printf("FolderUID: 0x%16" PRIx64 " (%" PRIu64 ")\n", uid, uid);
|
|
|
|
p_indent(level, frm);
|
|
|
|
type = get_u8(frm);
|
|
printf("FolderType: 0x%02x (%s)\n", type, foldertype2str(type));
|
|
|
|
p_indent(level, frm);
|
|
|
|
playable = get_u8(frm);
|
|
printf("IsPlayable: 0x%02x (%s)\n", playable,
|
|
playable & 0x01 ? "True" : "False");
|
|
|
|
p_indent(level, frm);
|
|
|
|
charset = get_u16(frm);
|
|
printf("CharsetID: 0x%04x (%s)\n", charset, charset2str(charset));
|
|
|
|
p_indent(level, frm);
|
|
|
|
namelen = get_u8(frm);
|
|
printf("NameLength: 0x%02x (%u)\n", namelen, namelen);
|
|
|
|
p_indent(level, frm);
|
|
|
|
printf("Name: ");
|
|
for (; namelen > 0; namelen--) {
|
|
uint8_t c = get_u8(frm);
|
|
printf("%1c", isprint(c) ? c : '.');
|
|
}
|
|
printf("\n");
|
|
}
|
|
|
|
static const char *elementtype2str(uint8_t type)
|
|
{
|
|
switch (type) {
|
|
case 0x00:
|
|
return "Audio";
|
|
case 0x01:
|
|
return "Video";
|
|
}
|
|
|
|
return "Reserved";
|
|
}
|
|
|
|
static void avrcp_attribute_entry_list_dump(int level, struct frame *frm,
|
|
uint8_t count)
|
|
{
|
|
for (; count > 0; count--) {
|
|
uint32_t attr;
|
|
uint16_t charset;
|
|
uint8_t len;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u32(frm);
|
|
printf("AttributeID: 0x%08x (%s)\n", attr, mediattr2str(attr));
|
|
|
|
p_indent(level, frm);
|
|
|
|
charset = get_u16(frm);
|
|
printf("CharsetID: 0x%04x (%s)\n", charset,
|
|
charset2str(charset));
|
|
|
|
p_indent(level, frm);
|
|
|
|
len = get_u8(frm);
|
|
printf("AttributeLength: 0x%02x (%u)\n", len, len);
|
|
|
|
p_indent(level, frm);
|
|
|
|
printf("AttributeValue: ");
|
|
for (; len > 0; len--) {
|
|
uint8_t c = get_u8(frm);
|
|
printf("%1c", isprint(c) ? c : '.');
|
|
}
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
static void avrcp_media_element_item_dump(int level, struct frame *frm,
|
|
uint16_t len)
|
|
{
|
|
uint64_t uid;
|
|
uint16_t charset;
|
|
uint8_t type, namelen, count;
|
|
|
|
if (len < 14) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
uid = get_u64(frm);
|
|
printf("ElementUID: 0x%16" PRIx64 " (%" PRIu64 ")\n", uid, uid);
|
|
|
|
p_indent(level, frm);
|
|
|
|
type = get_u8(frm);
|
|
printf("ElementType: 0x%02x (%s)\n", type, elementtype2str(type));
|
|
|
|
p_indent(level, frm);
|
|
|
|
charset = get_u16(frm);
|
|
printf("CharsetID: 0x%04x (%s)\n", charset, charset2str(charset));
|
|
|
|
p_indent(level, frm);
|
|
|
|
namelen = get_u8(frm);
|
|
printf("NameLength: 0x%02x (%u)\n", namelen, namelen);
|
|
|
|
p_indent(level, frm);
|
|
|
|
printf("Name: ");
|
|
for (; namelen > 0; namelen--) {
|
|
uint8_t c = get_u8(frm);
|
|
printf("%1c", isprint(c) ? c : '.');
|
|
}
|
|
printf("\n");
|
|
|
|
p_indent(level, frm);
|
|
|
|
count = get_u8(frm);
|
|
printf("AttributeCount: 0x%02x (%u)\n", count, count);
|
|
|
|
avrcp_attribute_entry_list_dump(level, frm, count);
|
|
}
|
|
|
|
static void avrcp_get_folder_items_dump(int level, struct frame *frm,
|
|
uint8_t hdr, uint16_t len)
|
|
{
|
|
uint8_t scope, count, status;
|
|
uint32_t start, end;
|
|
uint16_t uid, num;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (hdr & 0x02)
|
|
goto response;
|
|
|
|
if (len < 10) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
scope = get_u8(frm);
|
|
printf("Scope: 0x%02x (%s)\n", scope, scope2str(scope));
|
|
|
|
p_indent(level, frm);
|
|
|
|
start = get_u32(frm);
|
|
printf("StartItem: 0x%08x (%u)\n", start, start);
|
|
|
|
p_indent(level, frm);
|
|
|
|
end = get_u32(frm);
|
|
printf("EndItem: 0x%08x (%u)\n", end, end);
|
|
|
|
p_indent(level, frm);
|
|
|
|
count = get_u8(frm);
|
|
printf("AttributeCount: 0x%02x (%u)\n", count, count);
|
|
|
|
for (; count > 0; count--) {
|
|
uint32_t attr;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u32(frm);
|
|
printf("AttributeID: 0x%08x (%s)\n", attr, mediattr2str(attr));
|
|
}
|
|
|
|
return;
|
|
|
|
response:
|
|
status = get_u8(frm);
|
|
printf("Status: 0x%02x (%s)\n", status, error2str(status));
|
|
|
|
if (len == 1)
|
|
return;
|
|
|
|
p_indent(level, frm);
|
|
|
|
uid = get_u16(frm);
|
|
printf("UIDCounter: 0x%04x (%u)\n", uid, uid);
|
|
|
|
p_indent(level, frm);
|
|
|
|
num = get_u16(frm);
|
|
printf("Number of Items: 0x%04x (%u)\n", num, num);
|
|
|
|
for (; num > 0; num--) {
|
|
uint8_t type;
|
|
uint16_t len;
|
|
|
|
p_indent(level, frm);
|
|
|
|
type = get_u8(frm);
|
|
len = get_u16(frm);
|
|
switch (type) {
|
|
case 0x01:
|
|
printf("Item: 0x01 (Media Player)) ");
|
|
printf("Length: 0x%04x (%u)\n", len, len);
|
|
avrcp_media_player_item_dump(level, frm, len);
|
|
break;
|
|
case 0x02:
|
|
printf("Item: 0x02 (Folder) ");
|
|
printf("Length: 0x%04x (%u)\n", len, len);
|
|
avrcp_folder_item_dump(level, frm, len);
|
|
break;
|
|
case 0x03:
|
|
printf("Item: 0x03 (Media Element) ");
|
|
printf("Length: 0x%04x (%u)\n", len, len);
|
|
avrcp_media_element_item_dump(level, frm, len);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
static const char *dir2str(uint8_t dir)
|
|
{
|
|
switch (dir) {
|
|
case 0x00:
|
|
return "Folder Up";
|
|
case 0x01:
|
|
return "Folder Down";
|
|
}
|
|
|
|
return "Reserved";
|
|
}
|
|
|
|
static void avrcp_change_path_dump(int level, struct frame *frm, uint8_t hdr,
|
|
uint16_t len)
|
|
{
|
|
uint64_t uid;
|
|
uint32_t items;
|
|
uint16_t uidcounter;
|
|
uint8_t dir, status;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (hdr & 0x02)
|
|
goto response;
|
|
|
|
if (len < 11) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
uidcounter = get_u16(frm);
|
|
printf("UIDCounter: 0x%04x (%u)\n", uidcounter, uidcounter);
|
|
|
|
p_indent(level, frm);
|
|
|
|
dir = get_u8(frm);
|
|
printf("Direction: 0x%02x (%s)\n", dir, dir2str(dir));
|
|
|
|
p_indent(level, frm);
|
|
|
|
uid = get_u64(frm);
|
|
printf("FolderUID: 0x%16" PRIx64 " (%" PRIu64 ")\n", uid, uid);
|
|
|
|
return;
|
|
|
|
response:
|
|
status = get_u8(frm);
|
|
printf("Status: 0x%02x (%s)\n", status, error2str(status));
|
|
|
|
if (len == 1)
|
|
return;
|
|
|
|
p_indent(level, frm);
|
|
|
|
items = get_u32(frm);
|
|
printf("Number of Items: 0x%04x (%u)", items, items);
|
|
}
|
|
|
|
static void avrcp_get_item_attributes_dump(int level, struct frame *frm,
|
|
uint8_t hdr, uint16_t len)
|
|
{
|
|
uint64_t uid;
|
|
uint32_t uidcounter;
|
|
uint8_t scope, count, status;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (hdr & 0x02)
|
|
goto response;
|
|
|
|
if (len < 12) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
scope = get_u8(frm);
|
|
printf("Scope: 0x%02x (%s)\n", scope, scope2str(scope));
|
|
|
|
p_indent(level, frm);
|
|
|
|
uid = get_u64(frm);
|
|
printf("UID: 0x%16" PRIx64 " (%" PRIu64 ")\n", uid, uid);
|
|
|
|
p_indent(level, frm);
|
|
|
|
uidcounter = get_u16(frm);
|
|
printf("UIDCounter: 0x%04x (%u)\n", uidcounter, uidcounter);
|
|
|
|
p_indent(level, frm);
|
|
|
|
count = get_u32(frm);
|
|
printf("AttributeCount: 0x%04x (%u)", count, count);
|
|
|
|
for (; count > 0; count--) {
|
|
uint32_t attr;
|
|
|
|
p_indent(level, frm);
|
|
|
|
attr = get_u32(frm);
|
|
printf("AttributeID: 0x%08x (%s)\n", attr, mediattr2str(attr));
|
|
}
|
|
|
|
return;
|
|
|
|
response:
|
|
status = get_u8(frm);
|
|
printf("Status: 0x%02x (%s)\n", status, error2str(status));
|
|
|
|
if (len == 1)
|
|
return;
|
|
|
|
p_indent(level, frm);
|
|
|
|
count = get_u32(frm);
|
|
printf("AttributeCount: 0x%04x (%u)", count, count);
|
|
|
|
avrcp_attribute_entry_list_dump(level, frm, count);
|
|
}
|
|
|
|
static void avrcp_search_dump(int level, struct frame *frm, uint8_t hdr,
|
|
uint16_t len)
|
|
{
|
|
uint32_t uidcounter, items;
|
|
uint16_t charset;
|
|
uint8_t namelen, status;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (hdr & 0x02)
|
|
goto response;
|
|
|
|
if (len < 3) {
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
charset = get_u16(frm);
|
|
printf("CharsetID: 0x%04x (%s)\n", charset, charset2str(charset));
|
|
|
|
p_indent(level, frm);
|
|
|
|
namelen = get_u8(frm);
|
|
printf("Length: 0x%02x (%u)\n", namelen, namelen);
|
|
|
|
p_indent(level, frm);
|
|
|
|
printf("String: ");
|
|
for (; namelen > 0; namelen--) {
|
|
uint8_t c = get_u8(frm);
|
|
printf("%1c", isprint(c) ? c : '.');
|
|
}
|
|
|
|
return;
|
|
|
|
response:
|
|
status = get_u8(frm);
|
|
printf("Status: 0x%02x (%s)\n", status, error2str(status));
|
|
|
|
if (len == 1)
|
|
return;
|
|
|
|
p_indent(level, frm);
|
|
|
|
uidcounter = get_u16(frm);
|
|
printf("UIDCounter: 0x%04x (%u)\n", uidcounter, uidcounter);
|
|
|
|
p_indent(level, frm);
|
|
|
|
items = get_u32(frm);
|
|
printf("Number of Items: 0x%04x (%u)", items, items);
|
|
}
|
|
|
|
static void avrcp_general_reject_dump(int level, struct frame *frm,
|
|
uint8_t hdr, uint16_t len)
|
|
{
|
|
uint8_t status;
|
|
|
|
p_indent(level, frm);
|
|
|
|
if (hdr & 0x02)
|
|
goto response;
|
|
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
|
|
response:
|
|
status = get_u8(frm);
|
|
printf("Status: 0x%02x (%s)\n", status, error2str(status));
|
|
}
|
|
|
|
static void avrcp_browsing_dump(int level, struct frame *frm, uint8_t hdr)
|
|
{
|
|
uint8_t pduid;
|
|
uint16_t len;
|
|
|
|
pduid = get_u8(frm);
|
|
len = get_u16(frm);
|
|
|
|
printf("AVRCP: %s: len 0x%04x\n", pdu2str(pduid), len);
|
|
|
|
if (len != frm->len) {
|
|
p_indent(level, frm);
|
|
printf("PDU Malformed\n");
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
switch (pduid) {
|
|
case AVRCP_SET_BROWSED_PLAYER:
|
|
avrcp_set_browsed_player_dump(level + 1, frm, hdr, len);
|
|
break;
|
|
case AVRCP_GET_FOLDER_ITEMS:
|
|
avrcp_get_folder_items_dump(level + 1, frm, hdr, len);
|
|
break;
|
|
case AVRCP_CHANGE_PATH:
|
|
avrcp_change_path_dump(level + 1, frm, hdr, len);
|
|
break;
|
|
case AVRCP_GET_ITEM_ATTRIBUTES:
|
|
avrcp_get_item_attributes_dump(level + 1, frm, hdr, len);
|
|
break;
|
|
case AVRCP_SEARCH:
|
|
avrcp_search_dump(level + 1, frm, hdr, len);
|
|
break;
|
|
case AVRCP_GENERAL_REJECT:
|
|
avrcp_general_reject_dump(level + 1, frm, hdr, len);
|
|
break;
|
|
default:
|
|
raw_dump(level, frm);
|
|
}
|
|
}
|
|
|
|
static void avrcp_control_dump(int level, struct frame *frm)
|
|
{
|
|
uint8_t ctype, address, subunit, opcode, company[3];
|
|
int i;
|
|
|
|
ctype = get_u8(frm);
|
|
address = get_u8(frm);
|
|
opcode = get_u8(frm);
|
|
|
|
printf("AV/C: %s: address 0x%02x opcode 0x%02x\n", ctype2str(ctype),
|
|
address, opcode);
|
|
|
|
p_indent(level + 1, frm);
|
|
|
|
subunit = address >> 3;
|
|
printf("Subunit: %s\n", subunit2str(subunit));
|
|
|
|
p_indent(level + 1, frm);
|
|
|
|
printf("Opcode: %s\n", opcode2str(opcode));
|
|
|
|
/* Skip non-panel subunit packets */
|
|
if (subunit != AVC_SUBUNIT_PANEL) {
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
/* Not implemented should not contain any operand */
|
|
if (ctype == AVC_CTYPE_NOT_IMPLEMENTED) {
|
|
raw_dump(level, frm);
|
|
return;
|
|
}
|
|
|
|
switch (opcode) {
|
|
case AVC_OP_PASSTHROUGH:
|
|
avrcp_passthrough_dump(level + 1, frm);
|
|
break;
|
|
case AVC_OP_VENDORDEP:
|
|
p_indent(level + 1, frm);
|
|
|
|
printf("Company ID: 0x");
|
|
for (i = 0; i < 3; i++) {
|
|
company[i] = get_u8(frm);
|
|
printf("%02x", company[i]);
|
|
}
|
|
printf("\n");
|
|
|
|
avrcp_pdu_dump(level + 1, frm, ctype);
|
|
break;
|
|
default:
|
|
raw_dump(level, frm);
|
|
}
|
|
}
|
|
|
|
void avrcp_dump(int level, struct frame *frm, uint8_t hdr, uint16_t psm)
|
|
{
|
|
p_indent(level, frm);
|
|
|
|
switch (psm) {
|
|
case 0x17:
|
|
avrcp_control_dump(level, frm);
|
|
break;
|
|
case 0x1B:
|
|
avrcp_browsing_dump(level, frm, hdr);
|
|
break;
|
|
default:
|
|
raw_dump(level, frm);
|
|
}
|
|
}
|