diff options
Diffstat (limited to 'audio/avrcp.c')
-rw-r--r-- | audio/avrcp.c | 1468 |
1 files changed, 0 insertions, 1468 deletions
diff --git a/audio/avrcp.c b/audio/avrcp.c deleted file mode 100644 index 89ee112b..00000000 --- a/audio/avrcp.c +++ /dev/null @@ -1,1468 +0,0 @@ -/* - * - * BlueZ - Bluetooth protocol stack for Linux - * - * Copyright (C) 2006-2010 Nokia Corporation - * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org> - * Copyright (C) 2011 Texas Instruments, Inc. - * - * - * 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 <stdlib.h> -#include <stdint.h> -#include <errno.h> -#include <unistd.h> -#include <assert.h> -#include <signal.h> -#include <sys/types.h> -#include <sys/stat.h> -#include <fcntl.h> - -#include <bluetooth/bluetooth.h> -#include <bluetooth/sdp.h> -#include <bluetooth/sdp_lib.h> -#include <bluetooth/uuid.h> - -#include <glib.h> -#include <dbus/dbus.h> -#include <gdbus.h> - -#include "../src/adapter.h" -#include "../src/device.h" - -#include "log.h" -#include "error.h" -#include "device.h" -#include "manager.h" -#include "avctp.h" -#include "avrcp.h" -#include "sdpd.h" -#include "dbus-common.h" - -/* Company IDs for vendor dependent commands */ -#define IEEEID_BTSIG 0x001958 - -/* Error codes for metadata transfer */ -#define E_INVALID_COMMAND 0x00 -#define E_INVALID_PARAM 0x01 -#define E_PARAM_NOT_FOUND 0x02 -#define E_INTERNAL 0x03 - -/* 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 types for metadata transfer */ -#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 - -/* Capabilities for AVRCP_GET_CAPABILITIES pdu */ -#define CAP_COMPANY_ID 0x02 -#define CAP_EVENTS_SUPPORTED 0x03 - -#define AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH 5 - -#define AVRCP_FEATURE_CATEGORY_1 0x0001 -#define AVRCP_FEATURE_CATEGORY_2 0x0002 -#define AVRCP_FEATURE_CATEGORY_3 0x0004 -#define AVRCP_FEATURE_CATEGORY_4 0x0008 -#define AVRCP_FEATURE_PLAYER_SETTINGS 0x0010 - -enum battery_status { - BATTERY_STATUS_NORMAL = 0, - BATTERY_STATUS_WARNING = 1, - BATTERY_STATUS_CRITICAL = 2, - BATTERY_STATUS_EXTERNAL = 3, - BATTERY_STATUS_FULL_CHARGE = 4, -}; - -#if __BYTE_ORDER == __LITTLE_ENDIAN - -struct avrcp_header { - uint8_t company_id[3]; - uint8_t pdu_id; - uint8_t packet_type:2; - uint8_t rsvd:6; - uint16_t params_len; - uint8_t params[0]; -} __attribute__ ((packed)); -#define AVRCP_HEADER_LENGTH 7 - -#elif __BYTE_ORDER == __BIG_ENDIAN - -struct avrcp_header { - uint8_t company_id[3]; - uint8_t pdu_id; - uint8_t rsvd:6; - uint8_t packet_type:2; - uint16_t params_len; - uint8_t params[0]; -} __attribute__ ((packed)); -#define AVRCP_HEADER_LENGTH 7 - -#else -#error "Unknown byte order" -#endif - -#define AVRCP_MTU (AVC_MTU - AVC_HEADER_LENGTH) -#define AVRCP_PDU_MTU (AVRCP_MTU - AVRCP_HEADER_LENGTH) - -struct avrcp_server { - bdaddr_t src; - uint32_t tg_record_id; - uint32_t ct_record_id; - GSList *players; - struct avrcp_player *active_player; -}; - -struct pending_pdu { - uint8_t pdu_id; - GList *attr_ids; - uint16_t offset; -}; - -struct avrcp_player { - struct avrcp_server *server; - struct avctp *session; - struct audio_device *dev; - - unsigned int handler; - uint16_t registered_events; - uint8_t transaction_events[AVRCP_EVENT_LAST + 1]; - struct pending_pdu *pending_pdu; - - struct avrcp_player_cb *cb; - void *user_data; - GDestroyNotify destroy; -}; - -static GSList *servers = NULL; -static unsigned int avctp_id = 0; - -/* Company IDs supported by this device */ -static uint32_t company_ids[] = { - IEEEID_BTSIG, -}; - -static void register_volume_notification(struct avrcp_player *player); - -static sdp_record_t *avrcp_ct_record(void) -{ - sdp_list_t *svclass_id, *pfseq, *apseq, *root; - uuid_t root_uuid, l2cap, avctp, avrct; - sdp_profile_desc_t profile[1]; - sdp_list_t *aproto, *proto[2]; - sdp_record_t *record; - sdp_data_t *psm, *version, *features; - uint16_t lp = AVCTP_PSM; - uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103; - uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 | - AVRCP_FEATURE_CATEGORY_2 | - AVRCP_FEATURE_CATEGORY_3 | - AVRCP_FEATURE_CATEGORY_4 ); - - record = sdp_record_alloc(); - if (!record) - return NULL; - - sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); - root = sdp_list_append(0, &root_uuid); - sdp_set_browse_groups(record, root); - - /* Service Class ID List */ - sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID); - svclass_id = sdp_list_append(0, &avrct); - sdp_set_service_classes(record, svclass_id); - - /* Protocol Descriptor List */ - sdp_uuid16_create(&l2cap, L2CAP_UUID); - proto[0] = sdp_list_append(0, &l2cap); - psm = sdp_data_alloc(SDP_UINT16, &lp); - proto[0] = sdp_list_append(proto[0], psm); - apseq = sdp_list_append(0, proto[0]); - - sdp_uuid16_create(&avctp, AVCTP_UUID); - proto[1] = sdp_list_append(0, &avctp); - version = sdp_data_alloc(SDP_UINT16, &avctp_ver); - proto[1] = sdp_list_append(proto[1], version); - apseq = sdp_list_append(apseq, proto[1]); - - aproto = sdp_list_append(0, apseq); - sdp_set_access_protos(record, aproto); - - /* Bluetooth Profile Descriptor List */ - sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); - profile[0].version = avrcp_ver; - pfseq = sdp_list_append(0, &profile[0]); - sdp_set_profile_descs(record, pfseq); - - features = sdp_data_alloc(SDP_UINT16, &feat); - sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); - - sdp_set_info_attr(record, "AVRCP CT", 0, 0); - - free(psm); - free(version); - sdp_list_free(proto[0], 0); - sdp_list_free(proto[1], 0); - sdp_list_free(apseq, 0); - sdp_list_free(pfseq, 0); - sdp_list_free(aproto, 0); - sdp_list_free(root, 0); - sdp_list_free(svclass_id, 0); - - return record; -} - -static sdp_record_t *avrcp_tg_record(void) -{ - sdp_list_t *svclass_id, *pfseq, *apseq, *root; - uuid_t root_uuid, l2cap, avctp, avrtg; - sdp_profile_desc_t profile[1]; - sdp_list_t *aproto, *proto[2]; - sdp_record_t *record; - sdp_data_t *psm, *version, *features; - uint16_t lp = AVCTP_PSM; - uint16_t avrcp_ver = 0x0104, avctp_ver = 0x0103; - uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 | - AVRCP_FEATURE_CATEGORY_2 | - AVRCP_FEATURE_CATEGORY_3 | - AVRCP_FEATURE_CATEGORY_4 | - AVRCP_FEATURE_PLAYER_SETTINGS ); - - record = sdp_record_alloc(); - if (!record) - return NULL; - - sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP); - root = sdp_list_append(0, &root_uuid); - sdp_set_browse_groups(record, root); - - /* Service Class ID List */ - sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID); - svclass_id = sdp_list_append(0, &avrtg); - sdp_set_service_classes(record, svclass_id); - - /* Protocol Descriptor List */ - sdp_uuid16_create(&l2cap, L2CAP_UUID); - proto[0] = sdp_list_append(0, &l2cap); - psm = sdp_data_alloc(SDP_UINT16, &lp); - proto[0] = sdp_list_append(proto[0], psm); - apseq = sdp_list_append(0, proto[0]); - - sdp_uuid16_create(&avctp, AVCTP_UUID); - proto[1] = sdp_list_append(0, &avctp); - version = sdp_data_alloc(SDP_UINT16, &avctp_ver); - proto[1] = sdp_list_append(proto[1], version); - apseq = sdp_list_append(apseq, proto[1]); - - aproto = sdp_list_append(0, apseq); - sdp_set_access_protos(record, aproto); - - /* Bluetooth Profile Descriptor List */ - sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID); - profile[0].version = avrcp_ver; - pfseq = sdp_list_append(0, &profile[0]); - sdp_set_profile_descs(record, pfseq); - - features = sdp_data_alloc(SDP_UINT16, &feat); - sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features); - - sdp_set_info_attr(record, "AVRCP TG", 0, 0); - - free(psm); - free(version); - sdp_list_free(proto[0], 0); - sdp_list_free(proto[1], 0); - sdp_list_free(apseq, 0); - sdp_list_free(aproto, 0); - sdp_list_free(pfseq, 0); - sdp_list_free(root, 0); - sdp_list_free(svclass_id, 0); - - return record; -} - -static unsigned int attr_get_max_val(uint8_t attr) -{ - switch (attr) { - case AVRCP_ATTRIBUTE_EQUALIZER: - return AVRCP_EQUALIZER_ON; - case AVRCP_ATTRIBUTE_REPEAT_MODE: - return AVRCP_REPEAT_MODE_GROUP; - case AVRCP_ATTRIBUTE_SHUFFLE: - return AVRCP_SHUFFLE_GROUP; - case AVRCP_ATTRIBUTE_SCAN: - return AVRCP_SCAN_GROUP; - } - - return 0; -} - -static const char *battery_status_to_str(enum battery_status status) -{ - switch (status) { - case BATTERY_STATUS_NORMAL: - return "normal"; - case BATTERY_STATUS_WARNING: - return "warning"; - case BATTERY_STATUS_CRITICAL: - return "critical"; - case BATTERY_STATUS_EXTERNAL: - return "external"; - case BATTERY_STATUS_FULL_CHARGE: - return "fullcharge"; - } - - return NULL; -} - -/* - * get_company_id: - * - * Get three-byte Company_ID from incoming AVRCP message - */ -static uint32_t get_company_id(const uint8_t cid[3]) -{ - return cid[0] << 16 | cid[1] << 8 | cid[2]; -} - -/* - * set_company_id: - * - * Set three-byte Company_ID into outgoing AVRCP message - */ -static void set_company_id(uint8_t cid[3], const uint32_t cid_in) -{ - cid[0] = cid_in >> 16; - cid[1] = cid_in >> 8; - cid[2] = cid_in; -} - -int avrcp_player_event(struct avrcp_player *player, uint8_t id, void *data) -{ - uint8_t buf[AVRCP_HEADER_LENGTH + 9]; - struct avrcp_header *pdu = (void *) buf; - uint16_t size; - int err; - - if (player->session == NULL) - return -ENOTCONN; - - if (!(player->registered_events & (1 << id))) - return 0; - - memset(buf, 0, sizeof(buf)); - - set_company_id(pdu->company_id, IEEEID_BTSIG); - - pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; - pdu->params[0] = id; - - DBG("id=%u", id); - - switch (id) { - case AVRCP_EVENT_STATUS_CHANGED: - size = 2; - pdu->params[1] = *((uint8_t *)data); - - break; - case AVRCP_EVENT_TRACK_CHANGED: - size = 9; - memcpy(&pdu->params[1], data, sizeof(uint64_t)); - - break; - case AVRCP_EVENT_TRACK_REACHED_END: - case AVRCP_EVENT_TRACK_REACHED_START: - size = 1; - break; - default: - error("Unknown event %u", id); - return -EINVAL; - } - - pdu->params_len = htons(size); - - err = avctp_send_vendordep(player->session, player->transaction_events[id], - AVC_CTYPE_CHANGED, AVC_SUBUNIT_PANEL, - buf, size + AVRCP_HEADER_LENGTH); - if (err < 0) - return err; - - /* Unregister event as per AVRCP 1.3 spec, section 5.4.2 */ - player->registered_events ^= 1 << id; - - return 0; -} - -static uint16_t player_write_media_attribute(struct avrcp_player *player, - uint32_t id, uint8_t *buf, - uint16_t *pos, - uint16_t *offset) -{ - uint16_t len; - uint16_t attr_len; - char valstr[20]; - void *value; - - DBG("%u", id); - - value = player->cb->get_metadata(id, player->user_data); - if (value == NULL) { - *offset = 0; - return 0; - } - - switch (id) { - case AVRCP_MEDIA_ATTRIBUTE_TRACK: - case AVRCP_MEDIA_ATTRIBUTE_N_TRACKS: - case AVRCP_MEDIA_ATTRIBUTE_DURATION: - snprintf(valstr, 20, "%u", GPOINTER_TO_UINT(value)); - value = valstr; - break; - } - - attr_len = strlen(value); - value = ((char *) value) + *offset; - len = attr_len - *offset; - - if (len > AVRCP_PDU_MTU - *pos) { - len = AVRCP_PDU_MTU - *pos; - *offset += len; - } else { - *offset = 0; - } - - memcpy(&buf[*pos], value, len); - *pos += len; - - return attr_len; -} - -static GList *player_fill_media_attribute(struct avrcp_player *player, - GList *attr_ids, uint8_t *buf, - uint16_t *pos, uint16_t *offset) -{ - struct media_attribute_header { - uint32_t id; - uint16_t charset; - uint16_t len; - } *hdr = NULL; - GList *l; - - for (l = attr_ids; l != NULL; l = g_list_delete_link(l, l)) { - uint32_t attr = GPOINTER_TO_UINT(l->data); - uint16_t attr_len; - - if (*offset == 0) { - if (*pos + sizeof(*hdr) >= AVRCP_PDU_MTU) - break; - - hdr = (void *) &buf[*pos]; - hdr->id = htonl(attr); - hdr->charset = htons(0x6A); /* Always use UTF-8 */ - *pos += sizeof(*hdr); - } - - attr_len = player_write_media_attribute(player, attr, buf, - pos, offset); - - if (hdr != NULL) - hdr->len = htons(attr_len); - - if (*offset > 0) - break; - } - - return l; -} - -static struct pending_pdu *pending_pdu_new(uint8_t pdu_id, GList *attr_ids, - unsigned int offset) -{ - struct pending_pdu *pending = g_new(struct pending_pdu, 1); - - pending->pdu_id = pdu_id; - pending->attr_ids = attr_ids; - pending->offset = offset; - - return pending; -} - -static gboolean player_abort_pending_pdu(struct avrcp_player *player) -{ - if (player->pending_pdu == NULL) - return FALSE; - - g_list_free(player->pending_pdu->attr_ids); - g_free(player->pending_pdu); - player->pending_pdu = NULL; - - return TRUE; -} - -static int player_set_attribute(struct avrcp_player *player, - uint8_t attr, uint8_t val) -{ - DBG("Change attribute: %u %u", attr, val); - - return player->cb->set_setting(attr, val, player->user_data); -} - -static int player_get_attribute(struct avrcp_player *player, uint8_t attr) -{ - int value; - - DBG("attr %u", attr); - - value = player->cb->get_setting(attr, player->user_data); - if (value < 0) - DBG("attr %u not supported by player", attr); - - return value; -} - -static uint8_t avrcp_handle_get_capabilities(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - unsigned int i; - - if (len != 1) - goto err; - - DBG("id=%u", pdu->params[0]); - - switch (pdu->params[0]) { - case CAP_COMPANY_ID: - for (i = 0; i < G_N_ELEMENTS(company_ids); i++) { - set_company_id(&pdu->params[2 + i * 3], - company_ids[i]); - } - - pdu->params_len = htons(2 + (3 * G_N_ELEMENTS(company_ids))); - pdu->params[1] = G_N_ELEMENTS(company_ids); - - return AVC_CTYPE_STABLE; - case CAP_EVENTS_SUPPORTED: - pdu->params[1] = 4; - pdu->params[2] = AVRCP_EVENT_STATUS_CHANGED; - pdu->params[3] = AVRCP_EVENT_TRACK_CHANGED; - pdu->params[4] = AVRCP_EVENT_TRACK_REACHED_START; - pdu->params[5] = AVRCP_EVENT_TRACK_REACHED_END; - - pdu->params_len = htons(2 + pdu->params[1]); - return AVC_CTYPE_STABLE; - } - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_list_player_attributes(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - unsigned int i; - - if (len != 0) { - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; - } - - if (!player) - goto done; - - for (i = 1; i <= AVRCP_ATTRIBUTE_SCAN; i++) { - if (player_get_attribute(player, i) < 0) - continue; - - len++; - pdu->params[len] = i; - } - -done: - pdu->params[0] = len; - pdu->params_len = htons(len + 1); - - return AVC_CTYPE_STABLE; -} - -static uint8_t avrcp_handle_list_player_values(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - unsigned int i; - - if (len != 1 || !player) - goto err; - - if (player_get_attribute(player, pdu->params[0]) < 0) - goto err; - - len = attr_get_max_val(pdu->params[0]); - - for (i = 1; i <= len; i++) - pdu->params[i] = i; - - pdu->params[0] = len; - pdu->params_len = htons(len + 1); - - return AVC_CTYPE_STABLE; - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_get_element_attributes(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - uint64_t *identifier = (uint64_t *) &pdu->params[0]; - uint16_t pos; - uint8_t nattr; - GList *attr_ids; - uint16_t offset; - - if (len < 9 || *identifier != 0) - goto err; - - nattr = pdu->params[8]; - - if (len < nattr * sizeof(uint32_t) + 1) - goto err; - - if (!nattr) { - /* - * Return all available information, at least - * title must be returned if there's a track selected. - */ - attr_ids = player->cb->list_metadata(player->user_data); - len = g_list_length(attr_ids); - } else { - unsigned int i; - uint32_t *attr = (uint32_t *) &pdu->params[9]; - - for (i = 0, len = 0, attr_ids = NULL; i < nattr; i++, attr++) { - uint32_t id = ntohl(bt_get_unaligned(attr)); - - /* Don't add invalid attributes */ - if (id == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || - id > AVRCP_MEDIA_ATTRIBUTE_LAST) - continue; - - len++; - attr_ids = g_list_prepend(attr_ids, - GUINT_TO_POINTER(id)); - } - - attr_ids = g_list_reverse(attr_ids); - } - - if (!len) - goto err; - - player_abort_pending_pdu(player); - pos = 1; - offset = 0; - attr_ids = player_fill_media_attribute(player, attr_ids, pdu->params, - &pos, &offset); - - if (attr_ids != NULL) { - player->pending_pdu = pending_pdu_new(pdu->pdu_id, attr_ids, - offset); - pdu->packet_type = AVRCP_PACKET_TYPE_START; - } - - pdu->params[0] = len; - pdu->params_len = htons(pos); - - return AVC_CTYPE_STABLE; -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_get_current_player_value(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - uint8_t *settings; - unsigned int i; - - if (player == NULL || len <= 1 || pdu->params[0] != len - 1) - goto err; - - /* - * Save a copy of requested settings because we can override them - * while responding - */ - settings = g_memdup(&pdu->params[1], pdu->params[0]); - len = 0; - - /* - * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs - * and send a response with the existent ones. Only if all IDs are - * non-existent we should send an error. - */ - for (i = 0; i < pdu->params[0]; i++) { - int val; - - if (settings[i] < AVRCP_ATTRIBUTE_EQUALIZER || - settings[i] > AVRCP_ATTRIBUTE_SCAN) { - DBG("Ignoring %u", settings[i]); - continue; - } - - val = player_get_attribute(player, settings[i]); - if (val < 0) - continue; - - pdu->params[++len] = settings[i]; - pdu->params[++len] = val; - } - - g_free(settings); - - if (len) { - pdu->params[0] = len / 2; - pdu->params_len = htons(len + 1); - - return AVC_CTYPE_STABLE; - } - - error("No valid attributes in request"); - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_set_player_value(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - unsigned int i; - uint8_t *param; - - if (len < 3 || len > 2 * pdu->params[0] + 1U) - goto err; - - /* - * From sec. 5.7 of AVRCP 1.3 spec, we should igore non-existent IDs - * and set the existent ones. Sec. 5.2.4 is not clear however how to - * indicate that a certain ID was not accepted. If at least one - * attribute is valid, we respond with no parameters. Otherwise an - * E_INVALID_PARAM is sent. - */ - for (len = 0, i = 0, param = &pdu->params[1]; i < pdu->params[0]; - i++, param += 2) { - if (player_set_attribute(player, param[0], param[1]) < 0) - continue; - - len++; - } - - if (len) { - pdu->params_len = 0; - - return AVC_CTYPE_ACCEPTED; - } - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_displayable_charset(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - - if (len < 3) { - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; - } - - /* - * We acknowledge the commands, but we always use UTF-8 for - * encoding since CT is obliged to support it. - */ - pdu->params_len = 0; - return AVC_CTYPE_STABLE; -} - -static uint8_t avrcp_handle_ct_battery_status(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - const char *valstr; - - if (len != 1) - goto err; - - valstr = battery_status_to_str(pdu->params[0]); - if (valstr == NULL) - goto err; - - pdu->params_len = 0; - - return AVC_CTYPE_STABLE; - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_get_play_status(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - uint32_t position; - uint32_t duration; - void *pduration; - - if (len != 0) { - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; - } - - position = player->cb->get_position(player->user_data); - pduration = player->cb->get_metadata(AVRCP_MEDIA_ATTRIBUTE_DURATION, - player->user_data); - if (pduration != NULL) - duration = htonl(GPOINTER_TO_UINT(pduration)); - else - duration = htonl(UINT32_MAX); - - position = htonl(position); - - memcpy(&pdu->params[0], &duration, 4); - memcpy(&pdu->params[4], &position, 4); - pdu->params[8] = player->cb->get_status(player->user_data);; - - pdu->params_len = htons(9); - - return AVC_CTYPE_STABLE; -} - -static uint8_t avrcp_handle_register_notification(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - uint64_t uid; - - /* - * 1 byte for EventID, 4 bytes for Playback interval but the latest - * one is applicable only for EVENT_PLAYBACK_POS_CHANGED. See AVRCP - * 1.3 spec, section 5.4.2. - */ - if (len != 5) - goto err; - - switch (pdu->params[0]) { - case AVRCP_EVENT_STATUS_CHANGED: - len = 2; - pdu->params[1] = player->cb->get_status(player->user_data); - - break; - case AVRCP_EVENT_TRACK_CHANGED: - len = 9; - uid = player->cb->get_uid(player->user_data); - memcpy(&pdu->params[1], &uid, sizeof(uint64_t)); - - break; - case AVRCP_EVENT_TRACK_REACHED_END: - case AVRCP_EVENT_TRACK_REACHED_START: - len = 1; - break; - default: - /* All other events are not supported yet */ - goto err; - } - - /* Register event and save the transaction used */ - player->registered_events |= (1 << pdu->params[0]); - player->transaction_events[pdu->params[0]] = transaction; - - pdu->params_len = htons(len); - - return AVC_CTYPE_INTERIM; - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_request_continuing(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - struct pending_pdu *pending; - - if (len != 1 || player->pending_pdu == NULL) - goto err; - - pending = player->pending_pdu; - - if (pending->pdu_id != pdu->params[0]) - goto err; - - - len = 0; - pending->attr_ids = player_fill_media_attribute(player, - pending->attr_ids, - pdu->params, &len, - &pending->offset); - pdu->pdu_id = pending->pdu_id; - - if (pending->attr_ids == NULL) { - g_free(player->pending_pdu); - player->pending_pdu = NULL; - pdu->packet_type = AVRCP_PACKET_TYPE_END; - } else { - pdu->packet_type = AVRCP_PACKET_TYPE_CONTINUING; - } - - pdu->params_len = htons(len); - - return AVC_CTYPE_STABLE; -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static uint8_t avrcp_handle_abort_continuing(struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction) -{ - uint16_t len = ntohs(pdu->params_len); - struct pending_pdu *pending; - - if (len != 1 || player->pending_pdu == NULL) - goto err; - - pending = player->pending_pdu; - - if (pending->pdu_id != pdu->params[0]) - goto err; - - player_abort_pending_pdu(player); - pdu->params_len = 0; - - return AVC_CTYPE_ACCEPTED; - -err: - pdu->params_len = htons(1); - pdu->params[0] = E_INVALID_PARAM; - return AVC_CTYPE_REJECTED; -} - -static struct pdu_handler { - uint8_t pdu_id; - uint8_t code; - uint8_t (*func) (struct avrcp_player *player, - struct avrcp_header *pdu, - uint8_t transaction); -} handlers[] = { - { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS, - avrcp_handle_get_capabilities }, - { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS, - avrcp_handle_list_player_attributes }, - { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS, - avrcp_handle_list_player_values }, - { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS, - avrcp_handle_get_element_attributes }, - { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS, - avrcp_handle_get_current_player_value }, - { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL, - avrcp_handle_set_player_value }, - { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS, - NULL }, - { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS, - NULL }, - { AVRCP_DISPLAYABLE_CHARSET, AVC_CTYPE_STATUS, - avrcp_handle_displayable_charset }, - { AVRCP_CT_BATTERY_STATUS, AVC_CTYPE_STATUS, - avrcp_handle_ct_battery_status }, - { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS, - avrcp_handle_get_play_status }, - { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY, - avrcp_handle_register_notification }, - { AVRCP_REQUEST_CONTINUING, AVC_CTYPE_CONTROL, - avrcp_handle_request_continuing }, - { AVRCP_ABORT_CONTINUING, AVC_CTYPE_CONTROL, - avrcp_handle_abort_continuing }, - { }, -}; - -/* handle vendordep pdu inside an avctp packet */ -static size_t handle_vendordep_pdu(struct avctp *session, uint8_t transaction, - uint8_t *code, uint8_t *subunit, - uint8_t *operands, size_t operand_count, - void *user_data) -{ - struct avrcp_player *player = user_data; - struct pdu_handler *handler; - struct avrcp_header *pdu = (void *) operands; - uint32_t company_id = get_company_id(pdu->company_id); - - if (company_id != IEEEID_BTSIG) { - *code = AVC_CTYPE_NOT_IMPLEMENTED; - return 0; - } - - DBG("AVRCP PDU 0x%02X, company 0x%06X len 0x%04X", - pdu->pdu_id, company_id, pdu->params_len); - - pdu->packet_type = 0; - pdu->rsvd = 0; - - if (operand_count < AVRCP_HEADER_LENGTH) { - pdu->params[0] = E_INVALID_COMMAND; - goto err_metadata; - } - - for (handler = handlers; handler; handler++) { - if (handler->pdu_id == pdu->pdu_id) - break; - } - - if (!handler || handler->code != *code) { - pdu->params[0] = E_INVALID_COMMAND; - goto err_metadata; - } - - if (!handler->func) { - pdu->params[0] = E_INVALID_PARAM; - goto err_metadata; - } - - *code = handler->func(player, pdu, transaction); - - if (*code != AVC_CTYPE_REJECTED && - pdu->pdu_id != AVRCP_GET_ELEMENT_ATTRIBUTES && - pdu->pdu_id != AVRCP_REQUEST_CONTINUING && - pdu->pdu_id != AVRCP_ABORT_CONTINUING) - player_abort_pending_pdu(player); - - return AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); - -err_metadata: - pdu->params_len = htons(1); - *code = AVC_CTYPE_REJECTED; - - return AVRCP_HEADER_LENGTH + 1; -} - -size_t avrcp_handle_vendor_reject(uint8_t *code, uint8_t *operands) -{ - struct avrcp_header *pdu = (void *) operands; - uint32_t company_id = get_company_id(pdu->company_id); - - *code = AVC_CTYPE_REJECTED; - pdu->params_len = htons(1); - pdu->params[0] = E_INTERNAL; - - DBG("rejecting AVRCP PDU 0x%02X, company 0x%06X len 0x%04X", - pdu->pdu_id, company_id, pdu->params_len); - - return AVRCP_HEADER_LENGTH + 1; -} - -static struct avrcp_server *find_server(GSList *list, const bdaddr_t *src) -{ - for (; list; list = list->next) { - struct avrcp_server *server = list->data; - - if (bacmp(&server->src, src) == 0) - return server; - } - - return NULL; -} - -static gboolean avrcp_handle_volume_changed(struct avctp *session, - uint8_t code, uint8_t subunit, - uint8_t *operands, size_t operand_count, - void *user_data) -{ - struct avrcp_player *player = user_data; - struct avrcp_header *pdu = (void *) operands; - uint8_t volume; - - if (code != AVC_CTYPE_INTERIM && code != AVC_CTYPE_CHANGED) - return FALSE; - - volume = pdu->params[1] & 0x7F; - - player->cb->set_volume(volume, player->dev, player->user_data); - - if (code == AVC_CTYPE_CHANGED) { - register_volume_notification(player); - return FALSE; - } - - return TRUE; -} - -static void register_volume_notification(struct avrcp_player *player) -{ - uint8_t buf[AVRCP_HEADER_LENGTH + AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH]; - struct avrcp_header *pdu = (void *) buf; - uint8_t length; - - memset(buf, 0, sizeof(buf)); - - set_company_id(pdu->company_id, IEEEID_BTSIG); - pdu->pdu_id = AVRCP_REGISTER_NOTIFICATION; - pdu->packet_type = AVRCP_PACKET_TYPE_SINGLE; - pdu->params[0] = AVRCP_EVENT_VOLUME_CHANGED; - pdu->params_len = htons(AVRCP_REGISTER_NOTIFICATION_PARAM_LENGTH); - - length = AVRCP_HEADER_LENGTH + ntohs(pdu->params_len); - - avctp_send_vendordep_req(player->session, AVC_CTYPE_NOTIFY, - AVC_SUBUNIT_PANEL, buf, length, - avrcp_handle_volume_changed, player); -} - -static void state_changed(struct audio_device *dev, avctp_state_t old_state, - avctp_state_t new_state, void *user_data) -{ - struct avrcp_server *server; - struct avrcp_player *player; - const sdp_record_t *rec; - sdp_list_t *list; - sdp_profile_desc_t *desc; - - server = find_server(servers, &dev->src); - if (!server) - return; - - player = server->active_player; - if (!player) - return; - - switch (new_state) { - case AVCTP_STATE_DISCONNECTED: - player->session = NULL; - player->dev = NULL; - player->registered_events = 0; - - if (player->handler) { - avctp_unregister_pdu_handler(player->handler); - player->handler = 0; - } - - break; - case AVCTP_STATE_CONNECTING: - player->session = avctp_connect(&dev->src, &dev->dst); - player->dev = dev; - - if (!player->handler) - player->handler = avctp_register_pdu_handler( - AVC_OP_VENDORDEP, - handle_vendordep_pdu, - player); - break; - case AVCTP_STATE_CONNECTED: - rec = btd_device_get_record(dev->btd_dev, AVRCP_TARGET_UUID); - if (rec == NULL) - return; - - if (sdp_get_profile_descs(rec, &list) < 0) - return; - - desc = list->data; - - if (desc && desc->version >= 0x0104) - register_volume_notification(player); - - sdp_list_free(list, free); - default: - return; - } -} - -gboolean avrcp_connect(struct audio_device *dev) -{ - struct avctp *session; - - session = avctp_connect(&dev->src, &dev->dst); - if (session) - return FALSE; - - return TRUE; -} - -void avrcp_disconnect(struct audio_device *dev) -{ - struct avctp *session; - - session = avctp_get(&dev->src, &dev->dst); - if (!session) - return; - - avctp_disconnect(session); -} - -int avrcp_register(DBusConnection *conn, const bdaddr_t *src, GKeyFile *config) -{ - sdp_record_t *record; - gboolean tmp, master = TRUE; - GError *err = NULL; - struct avrcp_server *server; - - if (config) { - tmp = g_key_file_get_boolean(config, "General", - "Master", &err); - if (err) { - DBG("audio.conf: %s", err->message); - g_error_free(err); - } else - master = tmp; - } - - server = g_new0(struct avrcp_server, 1); - if (!server) - return -ENOMEM; - - record = avrcp_tg_record(); - if (!record) { - error("Unable to allocate new service record"); - g_free(server); - return -1; - } - - if (add_record_to_server(src, record) < 0) { - error("Unable to register AVRCP target service record"); - g_free(server); - sdp_record_free(record); - return -1; - } - server->tg_record_id = record->handle; - - record = avrcp_ct_record(); - if (!record) { - error("Unable to allocate new service record"); - g_free(server); - return -1; - } - - if (add_record_to_server(src, record) < 0) { - error("Unable to register AVRCP service record"); - sdp_record_free(record); - g_free(server); - return -1; - } - server->ct_record_id = record->handle; - - if (avctp_register(src, master) < 0) { - remove_record_from_server(server->ct_record_id); - remove_record_from_server(server->tg_record_id); - g_free(server); - return -1; - } - - bacpy(&server->src, src); - - servers = g_slist_append(servers, server); - - return 0; -} - -static void player_destroy(gpointer data) -{ - struct avrcp_player *player = data; - - if (player->destroy) - player->destroy(player->user_data); - - player_abort_pending_pdu(player); - - if (player->handler) - avctp_unregister_pdu_handler(player->handler); - - g_free(player); -} - -void avrcp_unregister(const bdaddr_t *src) -{ - struct avrcp_server *server; - - server = find_server(servers, src); - if (!server) - return; - - g_slist_free_full(server->players, player_destroy); - - servers = g_slist_remove(servers, server); - - remove_record_from_server(server->ct_record_id); - remove_record_from_server(server->tg_record_id); - - avctp_unregister(&server->src); - g_free(server); - - if (servers) - return; - - if (avctp_id) { - avctp_remove_state_cb(avctp_id); - avctp_id = 0; - } -} - -struct avrcp_player *avrcp_register_player(const bdaddr_t *src, - struct avrcp_player_cb *cb, - void *user_data, - GDestroyNotify destroy) -{ - struct avrcp_server *server; - struct avrcp_player *player; - - server = find_server(servers, src); - if (!server) - return NULL; - - player = g_new0(struct avrcp_player, 1); - player->server = server; - player->cb = cb; - player->user_data = user_data; - player->destroy = destroy; - - if (!server->players) - server->active_player = player; - - if (!avctp_id) - avctp_id = avctp_add_state_cb(state_changed, NULL); - - server->players = g_slist_append(server->players, player); - - return player; -} - -void avrcp_unregister_player(struct avrcp_player *player) -{ - struct avrcp_server *server = player->server; - - server->players = g_slist_remove(server->players, player); - - if (server->active_player == player) - server->active_player = g_slist_nth_data(server->players, 0); - - player_destroy(player); -} - -static gboolean avrcp_handle_set_volume(struct avctp *session, - uint8_t code, uint8_t subunit, - uint8_t *operands, size_t operand_count, - void *user_data) -{ - struct avrcp_player *player = user_data; - struct avrcp_header *pdu = (void *) operands; - uint8_t volume; - - if (code == AVC_CTYPE_REJECTED || code == AVC_CTYPE_NOT_IMPLEMENTED) - return FALSE; - - volume = pdu->params[0] & 0x7F; - - player->cb->set_volume(volume, player->dev, player->user_data); - - return FALSE; -} - -int avrcp_set_volume(struct audio_device *dev, uint8_t volume) -{ - struct avrcp_server *server; - struct avrcp_player *player; - uint8_t buf[AVRCP_HEADER_LENGTH + 1]; - struct avrcp_header *pdu = (void *) buf; - - server = find_server(servers, &dev->src); - if (server == NULL) - return -EINVAL; - - player = server->active_player; - if (player == NULL) - return -ENOTSUP; - - if (player->session == NULL) - return -ENOTCONN; - - memset(buf, 0, sizeof(buf)); - - set_company_id(pdu->company_id, IEEEID_BTSIG); - - pdu->pdu_id = AVRCP_SET_ABSOLUTE_VOLUME; - pdu->params[0] = volume; - pdu->params_len = htons(1); - - DBG("volume=%u", volume); - - return avctp_send_vendordep_req(player->session, AVC_CTYPE_CONTROL, - AVC_SUBUNIT_PANEL, buf, sizeof(buf), - avrcp_handle_set_volume, player); -} |