// SPDX-License-Identifier: LGPL-2.1-or-later
/*
 *
 *  BlueZ - Bluetooth protocol stack for Linux
 *
 *  Copyright (C) 2011-2014  Intel Corporation
 *  Copyright (C) 2002-2010  Marcel Holtmann <marcel@holtmann.org>
 *
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <inttypes.h>

#include "lib/bluetooth.h"
#include "lib/uuid.h"

#include "src/shared/util.h"
#include "bt.h"
#include "packet.h"
#include "display.h"
#include "l2cap.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_be32(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--) {
		uint32_t attr;

		if (!l2cap_frame_get_be32(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);
}
