/* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2006-2010 Nokia Corporation * Copyright (C) 2004-2010 Marcel Holtmann * 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 #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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); }