// SPDX-License-Identifier: LGPL-2.1-or-later /* * * BlueZ - Bluetooth protocol stack for Linux * * Copyright (C) 2014 Intel Corporation. All rights reserved. * * */ #ifdef HAVE_CONFIG_H #include #endif #define _GNU_SOURCE #include #include #include #include #include "lib/bluetooth.h" #include "src/shared/util.h" #include "src/log.h" #include "avctp.h" #include "avrcp-lib.h" /* 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 #define AVRCP_CHARSET_UTF8 0x006a #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 struct avrcp_browsing_header { uint8_t pdu_id; uint16_t params_len; uint8_t params[0]; } __attribute__ ((packed)); #define AVRCP_BROWSING_HEADER_LENGTH 3 struct get_capabilities_req { uint8_t cap; uint8_t params[0]; } __attribute__ ((packed)); struct get_capabilities_rsp { uint8_t cap; uint8_t number; uint8_t params[0]; } __attribute__ ((packed)); struct list_attributes_rsp { uint8_t number; uint8_t params[0]; } __attribute__ ((packed)); struct list_values_req { uint8_t attr; } __attribute__ ((packed)); struct list_values_rsp { uint8_t number; uint8_t params[0]; } __attribute__ ((packed)); struct get_value_req { uint8_t number; uint8_t attrs[0]; } __attribute__ ((packed)); struct attr_value { uint8_t attr; uint8_t value; } __attribute__ ((packed)); struct value_rsp { uint8_t number; struct attr_value values[0]; } __attribute__ ((packed)); struct set_value_req { uint8_t number; struct attr_value values[0]; } __attribute__ ((packed)); struct get_attribute_text_req { uint8_t number; uint8_t attrs[0]; } __attribute__ ((packed)); struct text_value { uint8_t attr; uint16_t charset; uint8_t len; char data[0]; } __attribute__ ((packed)); struct get_attribute_text_rsp { uint8_t number; struct text_value values[0]; } __attribute__ ((packed)); struct get_value_text_req { uint8_t attr; uint8_t number; uint8_t values[0]; } __attribute__ ((packed)); struct get_value_text_rsp { uint8_t number; struct text_value values[0]; } __attribute__ ((packed)); struct media_item { uint32_t attr; uint16_t charset; uint16_t len; char data[0]; } __attribute__ ((packed)); struct get_element_attributes_req { uint64_t id; uint8_t number; uint32_t attrs[0]; } __attribute__ ((packed)); struct get_element_attributes_rsp { uint8_t number; struct media_item items[0]; } __attribute__ ((packed)); struct get_play_status_rsp { uint32_t duration; uint32_t position; uint8_t status; } __attribute__ ((packed)); struct register_notification_req { uint8_t event; uint32_t interval; } __attribute__ ((packed)); struct register_notification_rsp { uint8_t event; uint8_t data[0]; } __attribute__ ((packed)); struct set_volume_req { uint8_t value; } __attribute__ ((packed)); struct set_volume_rsp { uint8_t value; } __attribute__ ((packed)); struct set_addressed_req { uint16_t id; } __attribute__ ((packed)); struct set_addressed_rsp { uint8_t status; } __attribute__ ((packed)); struct set_browsed_req { uint16_t id; } __attribute__ ((packed)); struct set_browsed_rsp { uint8_t status; uint16_t counter; uint32_t items; uint16_t charset; uint8_t depth; uint8_t data[0]; } __attribute__ ((packed)); struct get_folder_items_req { uint8_t scope; uint32_t start; uint32_t end; uint8_t number; uint32_t attrs[0]; } __attribute__ ((packed)); struct get_folder_items_rsp { uint8_t status; uint16_t counter; uint16_t number; uint8_t data[0]; } __attribute__ ((packed)); struct change_path_req { uint16_t counter; uint8_t direction; uint64_t uid; } __attribute__ ((packed)); struct change_path_rsp { uint8_t status; uint32_t items; } __attribute__ ((packed)); struct get_item_attributes_req { uint8_t scope; uint64_t uid; uint16_t counter; uint8_t number; uint32_t attrs[0]; } __attribute__ ((packed)); struct get_item_attributes_rsp { uint8_t status; uint8_t number; struct media_item items[0]; } __attribute__ ((packed)); struct play_item_req { uint8_t scope; uint64_t uid; uint16_t counter; } __attribute__ ((packed)); struct play_item_rsp { uint8_t status; } __attribute__ ((packed)); struct search_req { uint16_t charset; uint16_t len; char string[0]; } __attribute__ ((packed)); struct search_rsp { uint8_t status; uint16_t counter; uint32_t items; } __attribute__ ((packed)); struct add_to_now_playing_req { uint8_t scope; uint64_t uid; uint16_t counter; } __attribute__ ((packed)); struct add_to_now_playing_rsp { uint8_t status; } __attribute__ ((packed)); struct avrcp_control_handler { uint8_t id; uint8_t code; uint8_t rsp; ssize_t (*func) (struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data); }; struct avrcp_browsing_handler { uint8_t id; ssize_t (*func) (struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data); }; struct avrcp_continuing { uint8_t pdu_id; struct iovec pdu; }; struct avrcp { struct avctp *conn; struct avrcp_player *player; const struct avrcp_control_handler *control_handlers; void *control_data; unsigned int control_id; uint16_t control_mtu; struct avrcp_continuing *continuing; const struct avrcp_passthrough_handler *passthrough_handlers; void *passthrough_data; unsigned int passthrough_id; const struct avrcp_browsing_handler *browsing_handlers; void *browsing_data; unsigned int browsing_id; avrcp_destroy_cb_t destroy; void *destroy_data; }; struct avrcp_player { const struct avrcp_control_ind *ind; const struct avrcp_control_cfm *cfm; void *user_data; }; static inline uint32_t ntoh24(const uint8_t src[3]) { return src[0] << 16 | src[1] << 8 | src[2]; } static inline void hton24(uint8_t dst[3], uint32_t src) { dst[0] = (src & 0xff0000) >> 16; dst[1] = (src & 0x00ff00) >> 8; dst[2] = (src & 0x0000ff); } static void continuing_free(struct avrcp_continuing *continuing) { g_free(continuing->pdu.iov_base); g_free(continuing); } void avrcp_shutdown(struct avrcp *session) { if (session->conn) { if (session->control_id > 0) avctp_unregister_pdu_handler(session->conn, session->control_id); if (session->passthrough_id > 0) avctp_unregister_passthrough_handler(session->conn, session->passthrough_id); if (session->browsing_id > 0) avctp_unregister_browsing_pdu_handler(session->conn, session->browsing_id); /* clear destroy callback that would call shutdown again */ avctp_set_destroy_cb(session->conn, NULL, NULL); avctp_shutdown(session->conn); } if (session->destroy) session->destroy(session->destroy_data); if (session->continuing) continuing_free(session->continuing); g_free(session->player); g_free(session); } static struct avrcp_header *parse_pdu(uint8_t *operands, size_t operand_count) { struct avrcp_header *pdu; if (!operands || operand_count < sizeof(*pdu)) { error("AVRCP: packet too small (%zu bytes)", operand_count); return NULL; } pdu = (void *) operands; pdu->params_len = ntohs(pdu->params_len); if (operand_count != pdu->params_len + sizeof(*pdu)) { error("AVRCP: invalid parameter length (%u bytes)", pdu->params_len); return NULL; } return pdu; } static struct avrcp_browsing_header *parse_browsing_pdu(uint8_t *operands, size_t operand_count) { struct avrcp_browsing_header *pdu; if (!operands || operand_count < sizeof(*pdu)) { error("AVRCP: packet too small (%zu bytes)", operand_count); return NULL; } pdu = (void *) operands; pdu->params_len = ntohs(pdu->params_len); if (operand_count != pdu->params_len + sizeof(*pdu)) { error("AVRCP: invalid parameter length (%u bytes)", pdu->params_len); return NULL; } return pdu; } static uint8_t errno2status(int err) { switch (err) { case -ENOSYS: return AVRCP_STATUS_INVALID_COMMAND; case -EINVAL: return AVRCP_STATUS_INVALID_PARAM; case 0: return AVRCP_STATUS_SUCCESS; case -ENOTDIR: return AVRCP_STATUS_NOT_DIRECTORY; case -EBADRQC: return AVRCP_STATUS_INVALID_SCOPE; case -ERANGE: return AVRCP_STATUS_OUT_OF_BOUNDS; case -ENOENT: return AVRCP_STATUS_DOES_NOT_EXIST; default: return AVRCP_STATUS_INTERNAL_ERROR; } } static ssize_t handle_vendordep_pdu(struct avctp *conn, uint8_t transaction, uint8_t *code, uint8_t *subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; const struct avrcp_control_handler *handler; struct avrcp_header *pdu; uint32_t company_id; ssize_t ret; pdu = parse_pdu(operands, operand_count); if (!pdu) { pdu = (void *) operands; pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; goto reject; } company_id = ntoh24(pdu->company_id); if (company_id != IEEEID_BTSIG) { *code = AVC_CTYPE_NOT_IMPLEMENTED; return 0; } DBG("AVRCP PDU 0x%02X, len 0x%04X", pdu->pdu_id, pdu->params_len); pdu->packet_type = 0; pdu->rsvd = 0; if (!session->control_handlers) goto reject; for (handler = session->control_handlers; handler->id; handler++) { if (handler->id == pdu->pdu_id) break; } if (handler->id != pdu->pdu_id || handler->code != *code) { pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; goto reject; } if (!handler->func) { pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; goto reject; } ret = handler->func(session, transaction, pdu->params_len, pdu->params, session->control_data); if (ret == 0) return -EAGAIN; if (ret < 0) { if (ret == -EAGAIN) return ret; pdu->params[0] = errno2status(ret); goto reject; } *code = handler->rsp; pdu->params_len = htons(ret); return AVRCP_HEADER_LENGTH + ret; reject: pdu->params_len = htons(1); *code = AVC_CTYPE_REJECTED; return AVRCP_HEADER_LENGTH + 1; } static bool handle_passthrough_pdu(struct avctp *conn, uint8_t op, bool pressed, void *user_data) { struct avrcp *session = user_data; const struct avrcp_passthrough_handler *handler; if (!session->passthrough_handlers) return false; for (handler = session->passthrough_handlers; handler->func; handler++) { if (handler->op == op) break; } if (handler->func == NULL) return false; return handler->func(session, pressed, session->passthrough_data); } static void disconnect_cb(void *data) { struct avrcp *session = data; session->conn = NULL; avrcp_shutdown(session); } struct avrcp *avrcp_new(int fd, size_t imtu, size_t omtu, uint16_t version) { struct avrcp *session; session = g_new0(struct avrcp, 1); session->conn = avctp_new(fd, imtu, omtu, version); if (!session->conn) { g_free(session); return NULL; } session->passthrough_id = avctp_register_passthrough_handler( session->conn, handle_passthrough_pdu, session); session->control_id = avctp_register_pdu_handler(session->conn, AVC_OP_VENDORDEP, handle_vendordep_pdu, session); session->control_mtu = omtu - AVC_DATA_OFFSET; /* * 27.1.2 AV/C Command Frame * An AV/C command frame contains up to 512 octets of data */ if (session->control_mtu > AVC_DATA_MTU) session->control_mtu = AVC_DATA_MTU; avctp_set_destroy_cb(session->conn, disconnect_cb, session); return session; } static ssize_t handle_browsing_pdu(struct avctp *conn, uint8_t transaction, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; const struct avrcp_browsing_handler *handler; struct avrcp_browsing_header *pdu; int ret; pdu = parse_browsing_pdu(operands, operand_count); if (!pdu) { pdu = (void *) operands; pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; goto reject; } DBG("AVRCP Browsing PDU 0x%02X, len 0x%04X", pdu->pdu_id, pdu->params_len); if (!session->browsing_handlers) { pdu->pdu_id = AVRCP_GENERAL_REJECT; pdu->params[0] = AVRCP_STATUS_INTERNAL_ERROR; goto reject; } for (handler = session->browsing_handlers; handler->id; handler++) { if (handler->id == pdu->pdu_id) break; } if (handler->id != pdu->pdu_id) { pdu->pdu_id = AVRCP_GENERAL_REJECT; pdu->params[0] = AVRCP_STATUS_INVALID_COMMAND; goto reject; } if (!handler->func) { pdu->params[0] = AVRCP_STATUS_INVALID_PARAM; goto reject; } ret = handler->func(session, transaction, pdu->params_len, pdu->params, session->control_data); if (ret == 0) return -EAGAIN; if (ret < 0) { if (ret == -EAGAIN) return ret; pdu->params[0] = errno2status(ret); goto reject; } pdu->params_len = htons(ret); return AVRCP_BROWSING_HEADER_LENGTH + ret; reject: pdu->params_len = htons(1); return AVRCP_BROWSING_HEADER_LENGTH + 1; } static void browsing_disconnect_cb(void *data) { struct avrcp *session = data; session->browsing_id = 0; } int avrcp_connect_browsing(struct avrcp *session, int fd, size_t imtu, size_t omtu) { int err; err = avctp_connect_browsing(session->conn, fd, imtu, omtu); if (err < 0) return err; session->browsing_id = avctp_register_browsing_pdu_handler( session->conn, handle_browsing_pdu, session, browsing_disconnect_cb); return 0; } void avrcp_set_destroy_cb(struct avrcp *session, avrcp_destroy_cb_t cb, void *user_data) { session->destroy = cb; session->destroy_data = user_data; } static ssize_t get_capabilities(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct get_capabilities_req *req; if (!params || params_len != sizeof(*req)) return -EINVAL; req = (void *) params; switch (req->cap) { case CAP_COMPANY_ID: req->params[0] = 1; hton24(&req->params[1], IEEEID_BTSIG); return 5; case CAP_EVENTS_SUPPORTED: if (!player->ind || !player->ind->get_capabilities) return -ENOSYS; return player->ind->get_capabilities(session, transaction, player->user_data); } return -EINVAL; } static ssize_t list_attributes(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; DBG(""); if (!player->ind || !player->ind->list_attributes) return -ENOSYS; return player->ind->list_attributes(session, transaction, player->user_data); } static bool check_attributes(uint8_t number, const uint8_t *attrs) { int i; for (i = 0; i < number; i++) { if (attrs[i] > AVRCP_ATTRIBUTE_LAST || attrs[i] == AVRCP_ATTRIBUTE_ILEGAL) return false; } return true; } static ssize_t get_attribute_text(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct get_attribute_text_req *req; DBG(""); if (!player->ind || !player->ind->get_attribute_text) return -ENOSYS; if (!params || params_len < sizeof(*req)) return -EINVAL; req = (void *) params; if (params_len != sizeof(*req) + req->number) return -EINVAL; if (!check_attributes(req->number, req->attrs)) return -EINVAL; return player->ind->get_attribute_text(session, transaction, req->number, req->attrs, player->user_data); } static ssize_t list_values(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct list_values_req *req; DBG(""); if (!params || params_len != sizeof(*req)) return -EINVAL; req = (void *) params; if (req->attr > AVRCP_ATTRIBUTE_LAST || req->attr == AVRCP_ATTRIBUTE_ILEGAL) return -EINVAL; if (!player->ind || !player->ind->list_values) return -ENOSYS; return player->ind->list_values(session, transaction, req->attr, player->user_data); } static bool check_value(uint8_t attr, uint8_t number, const uint8_t *values) { int i; for (i = 0; i < number; i++) { /* Check for invalid value */ switch (attr) { case AVRCP_ATTRIBUTE_EQUALIZER: if (values[i] < AVRCP_EQUALIZER_OFF || values[i] > AVRCP_EQUALIZER_ON) return false; break; case AVRCP_ATTRIBUTE_REPEAT_MODE: if (values[i] < AVRCP_REPEAT_MODE_OFF || values[i] > AVRCP_REPEAT_MODE_GROUP) return false; break; case AVRCP_ATTRIBUTE_SHUFFLE: if (values[i] < AVRCP_SHUFFLE_OFF || values[i] > AVRCP_SHUFFLE_GROUP) return false; break; case AVRCP_ATTRIBUTE_SCAN: if (values[i] < AVRCP_SCAN_OFF || values[i] > AVRCP_SCAN_GROUP) return false; break; } } return true; } static ssize_t get_value_text(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct get_value_text_req *req; DBG(""); if (!player->ind || !player->ind->get_value_text) return -ENOSYS; if (!params || params_len < sizeof(*req)) return -EINVAL; req = (void *) params; if (params_len != sizeof(*req) + req->number) return -EINVAL; if (req->number > AVRCP_ATTRIBUTE_LAST || req->number == AVRCP_ATTRIBUTE_ILEGAL) return -EINVAL; if (!check_value(req->attr, req->number, req->values)) return -EINVAL; return player->ind->get_value_text(session, transaction, params[0], params[1], ¶ms[2], player->user_data); } static ssize_t get_value(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct get_value_req *req; DBG(""); if (!player->ind || !player->ind->get_value) return -ENOSYS; if (!params || params_len < sizeof(*req)) return -EINVAL; req = (void *) params; if (params_len < sizeof(*req) + req->number) return -EINVAL; if (!check_attributes(req->number, req->attrs)) return -EINVAL; return player->ind->get_value(session, transaction, params[0], ¶ms[1], player->user_data); } static ssize_t set_value(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct set_value_req *req; uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; uint8_t values[AVRCP_ATTRIBUTE_LAST]; int i; DBG(""); if (!player->ind || !player->ind->set_value) return -ENOSYS; if (!params || params_len < sizeof(*req)) return -EINVAL; req = (void *) params; if (params_len < sizeof(*req) + req->number * sizeof(*req->values)) return -EINVAL; for (i = 0; i < req->number; i++) { attrs[i] = req->values[i].attr; values[i] = req->values[i].value; if (!check_value(attrs[i], 1, &values[i])) return -EINVAL; } return player->ind->set_value(session, transaction, req->number, attrs, values, player->user_data); } static ssize_t get_play_status(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; DBG(""); if (!player->ind || !player->ind->get_play_status) return -ENOSYS; return player->ind->get_play_status(session, transaction, player->user_data); } static bool parse_attributes(struct get_element_attributes_req *req, uint16_t params_len, uint8_t number, uint32_t *attrs) { int i; for (i = 0; i < number && params_len >= sizeof(*attrs); i++, params_len -= sizeof(*attrs)) { attrs[i] = be32_to_cpu(req->attrs[i]); if (attrs[i] == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || attrs[i] > AVRCP_MEDIA_ATTRIBUTE_LAST) return false; } return true; } static ssize_t get_element_attributes(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct get_element_attributes_req *req; uint64_t uid; uint32_t attrs[AVRCP_MEDIA_ATTRIBUTE_LAST]; DBG(""); if (!player->ind || !player->ind->get_element_attributes) return -ENOSYS; req = (void *) params; if (!params || params_len < sizeof(*req)) return -EINVAL; if (!parse_attributes(req, params_len - sizeof(*req), req->number, attrs)) return -EINVAL; uid = get_be64(params); return player->ind->get_element_attributes(session, transaction, uid, req->number, attrs, player->user_data); } static ssize_t register_notification(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct register_notification_req *req; uint32_t interval; DBG(""); if (!player->ind || !player->ind->register_notification) return -ENOSYS; if (!params || params_len != sizeof(*req)) return -EINVAL; req = (void *) params; interval = be32_to_cpu(req->interval); return player->ind->register_notification(session, transaction, req->event, interval, player->user_data); } static ssize_t set_volume(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct set_volume_req *req; uint8_t volume; DBG(""); if (!player->ind || !player->ind->set_volume) return -ENOSYS; if (!params || params_len != sizeof(volume)) return -EINVAL; req = (void *) params; volume = req->value & 0x7f; return player->ind->set_volume(session, transaction, volume, player->user_data); } static ssize_t set_addressed(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct set_addressed_req *req; uint16_t id; DBG(""); if (!player->ind || !player->ind->set_addressed) return -ENOSYS; if (!params || params_len != sizeof(*req)) return -EINVAL; req = (void *) params; id = be16_to_cpu(req->id); return player->ind->set_addressed(session, transaction, id, player->user_data); } static void continuing_new(struct avrcp *session, uint8_t pdu_id, const struct iovec *iov, int iov_cnt, size_t offset) { struct avrcp_continuing *continuing; int i; size_t len = 0; continuing = g_new0(struct avrcp_continuing, 1); continuing->pdu_id = pdu_id; for (i = 0; i < iov_cnt; i++) { if (i == 0 && offset) { len += iov[i].iov_len - offset; continue; } len += iov[i].iov_len; } continuing->pdu.iov_base = g_malloc0(len); DBG("len %zu", len); for (i = 0; i < iov_cnt; i++) { if (i == 0 && offset) { memcpy(continuing->pdu.iov_base, iov[i].iov_base + offset, iov[i].iov_len - offset); continuing->pdu.iov_len += iov[i].iov_len - offset; continue; } memcpy(continuing->pdu.iov_base + continuing->pdu.iov_len, iov[i].iov_base, iov[i].iov_len); continuing->pdu.iov_len += iov[i].iov_len; } session->continuing = continuing; } static int avrcp_send_internal(struct avrcp *session, uint8_t transaction, uint8_t code, uint8_t subunit, uint8_t pdu_id, uint8_t type, const struct iovec *iov, int iov_cnt) { struct iovec pdu[iov_cnt + 1]; struct avrcp_header hdr; int i; /* * If a receiver receives a start fragment or non-fragmented AVRCP * Specific AV/C message when it already has an incomplete fragment * from that sender then the receiver shall consider the first PDU * aborted. */ if (session->continuing) { continuing_free(session->continuing); session->continuing = NULL; } memset(&hdr, 0, sizeof(hdr)); pdu[0].iov_base = &hdr; pdu[0].iov_len = sizeof(hdr); hdr.packet_type = type; for (i = 0; i < iov_cnt; i++) { pdu[i + 1].iov_base = iov[i].iov_base; if (pdu[0].iov_len + hdr.params_len + iov[i].iov_len <= session->control_mtu) { pdu[i + 1].iov_len = iov[i].iov_len; hdr.params_len += iov[i].iov_len; if (hdr.packet_type != AVRCP_PACKET_TYPE_SINGLE) hdr.packet_type = AVRCP_PACKET_TYPE_END; continue; } /* * Only send what can fit and store the remaining in the * continuing iovec */ pdu[i + 1].iov_len = session->control_mtu - (pdu[0].iov_len + hdr.params_len); hdr.params_len += pdu[i + 1].iov_len; continuing_new(session, pdu_id, &iov[i], iov_cnt - i, pdu[i + 1].iov_len); hdr.packet_type = hdr.packet_type != AVRCP_PACKET_TYPE_SINGLE ? AVRCP_PACKET_TYPE_CONTINUING : AVRCP_PACKET_TYPE_START; break; } hton24(hdr.company_id, IEEEID_BTSIG); hdr.pdu_id = pdu_id; hdr.params_len = htons(hdr.params_len); return avctp_send_vendor(session->conn, transaction, code, subunit, pdu, iov_cnt + 1); } static ssize_t request_continuing(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct iovec iov; int err; DBG(""); if (!params || params_len != 1 || !session->continuing || session->continuing->pdu_id != params[0]) return -EINVAL; iov.iov_base = session->continuing->pdu.iov_base; iov.iov_len = session->continuing->pdu.iov_len; DBG("len %zu", iov.iov_len); session->continuing->pdu.iov_base = NULL; err = avrcp_send_internal(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, params[0], AVRCP_PACKET_TYPE_CONTINUING, &iov, 1); g_free(iov.iov_base); if (err < 0) return -EINVAL; return 0; } static ssize_t abort_continuing(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { DBG(""); if (!params || params_len != 1 || !session->continuing) return -EINVAL; continuing_free(session->continuing); session->continuing = NULL; avrcp_send_internal(session, transaction, AVC_CTYPE_ACCEPTED, AVC_SUBUNIT_PANEL, AVRCP_ABORT_CONTINUING, AVRCP_PACKET_TYPE_SINGLE, NULL, 0); return 0; } static const struct avrcp_control_handler player_handlers[] = { { AVRCP_GET_CAPABILITIES, AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, get_capabilities }, { AVRCP_LIST_PLAYER_ATTRIBUTES, AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, list_attributes }, { AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, get_attribute_text }, { AVRCP_LIST_PLAYER_VALUES, AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, list_values }, { AVRCP_GET_PLAYER_VALUE_TEXT, AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, get_value_text }, { AVRCP_GET_CURRENT_PLAYER_VALUE, AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, get_value }, { AVRCP_SET_PLAYER_VALUE, AVC_CTYPE_CONTROL, AVC_CTYPE_STABLE, set_value }, { AVRCP_GET_PLAY_STATUS, AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, get_play_status }, { AVRCP_GET_ELEMENT_ATTRIBUTES, AVC_CTYPE_STATUS, AVC_CTYPE_STABLE, get_element_attributes }, { AVRCP_REGISTER_NOTIFICATION, AVC_CTYPE_NOTIFY, AVC_CTYPE_INTERIM, register_notification }, { AVRCP_SET_ABSOLUTE_VOLUME, AVC_CTYPE_CONTROL, AVC_CTYPE_STABLE, set_volume }, { AVRCP_SET_ADDRESSED_PLAYER, AVC_CTYPE_CONTROL, AVC_CTYPE_STABLE, set_addressed }, { AVRCP_REQUEST_CONTINUING, AVC_CTYPE_CONTROL, AVC_CTYPE_STABLE, request_continuing }, { AVRCP_ABORT_CONTINUING, AVC_CTYPE_CONTROL, AVC_CTYPE_ACCEPTED, abort_continuing }, { }, }; static void avrcp_set_control_handlers(struct avrcp *session, const struct avrcp_control_handler *handlers, void *user_data) { session->control_handlers = handlers; session->control_data = user_data; } static ssize_t set_browsed(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct set_browsed_req *req; uint16_t id; DBG(""); if (!player->ind || !player->ind->set_browsed) return -ENOSYS; if (!params || params_len != sizeof(*req)) return -EINVAL; req = (void *) params; id = be16_to_cpu(req->id); return player->ind->set_browsed(session, transaction, id, player->user_data); } static ssize_t get_folder_items(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct get_folder_items_req *req; uint32_t start, end; uint16_t number; uint32_t attrs[AVRCP_MEDIA_ATTRIBUTE_LAST]; int i; DBG(""); if (!player->ind || !player->ind->get_folder_items) return -ENOSYS; if (!params || params_len < sizeof(*req)) return -EINVAL; req = (void *) params; if (req->scope > AVRCP_MEDIA_NOW_PLAYING) return -EBADRQC; start = be32_to_cpu(req->start); end = be32_to_cpu(req->end); if (start > end) return -ERANGE; number = be16_to_cpu(req->number); for (i = 0; i < number; i++) { attrs[i] = be32_to_cpu(req->attrs[i]); if (attrs[i] == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || attrs[i] > AVRCP_MEDIA_ATTRIBUTE_LAST) return -EINVAL; } return player->ind->get_folder_items(session, transaction, req->scope, start, end, number, attrs, player->user_data); } static ssize_t change_path(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct change_path_req *req; uint16_t counter; uint64_t uid; DBG(""); if (!player->ind || !player->ind->change_path) return -ENOSYS; if (!params || params_len < sizeof(*req)) return -EINVAL; req = (void *) params; counter = be16_to_cpu(req->counter); uid = be64_to_cpu(req->uid); return player->ind->change_path(session, transaction, counter, req->direction, uid, player->user_data); } static ssize_t get_item_attributes(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct get_item_attributes_req *req; uint64_t uid; uint16_t counter; uint32_t attrs[AVRCP_MEDIA_ATTRIBUTE_LAST]; int i; DBG(""); if (!player->ind || !player->ind->get_item_attributes) return -ENOSYS; if (!params || params_len < sizeof(*req)) return -EINVAL; req = (void *) params; if (req->scope > AVRCP_MEDIA_NOW_PLAYING) return -EBADRQC; uid = be64_to_cpu(req->uid); counter = be16_to_cpu(req->counter); for (i = 0; i < req->number; i++) { attrs[i] = be32_to_cpu(req->attrs[i]); if (attrs[i] == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL || attrs[i] > AVRCP_MEDIA_ATTRIBUTE_LAST) return -EINVAL; } return player->ind->get_item_attributes(session, transaction, req->scope, uid, counter, req->number, attrs, player->user_data); } static ssize_t play_item(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct play_item_req *req; uint64_t uid; uint16_t counter; DBG(""); if (!player->ind || !player->ind->play_item) return -ENOSYS; if (!params || params_len < sizeof(*req)) return -EINVAL; req = (void *) params; if (req->scope > AVRCP_MEDIA_NOW_PLAYING) return -EBADRQC; uid = be64_to_cpu(req->uid); counter = be16_to_cpu(req->counter); return player->ind->play_item(session, transaction, req->scope, uid, counter, player->user_data); } static ssize_t search(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct search_req *req; char *string; uint16_t len; int ret; DBG(""); if (!player->ind || !player->ind->search) return -ENOSYS; if (!params || params_len < sizeof(*req)) return -EINVAL; req = (void *) params; len = be16_to_cpu(req->len); if (!len) return -EINVAL; string = strndup(req->string, len); ret = player->ind->search(session, transaction, string, player->user_data); free(string); return ret; } static ssize_t add_to_now_playing(struct avrcp *session, uint8_t transaction, uint16_t params_len, uint8_t *params, void *user_data) { struct avrcp_player *player = user_data; struct add_to_now_playing_req *req; uint64_t uid; uint16_t counter; DBG(""); if (!player->ind || !player->ind->add_to_now_playing) return -ENOSYS; if (!params || params_len < sizeof(*req)) return -EINVAL; req = (void *) params; if (req->scope > AVRCP_MEDIA_NOW_PLAYING) return -EBADRQC; uid = be64_to_cpu(req->uid); counter = be16_to_cpu(req->counter); return player->ind->add_to_now_playing(session, transaction, req->scope, uid, counter, player->user_data); } static const struct avrcp_browsing_handler browsing_handlers[] = { { AVRCP_SET_BROWSED_PLAYER, set_browsed }, { AVRCP_GET_FOLDER_ITEMS, get_folder_items }, { AVRCP_CHANGE_PATH, change_path }, { AVRCP_GET_ITEM_ATTRIBUTES, get_item_attributes }, { AVRCP_PLAY_ITEM, play_item }, { AVRCP_SEARCH, search }, { AVRCP_ADD_TO_NOW_PLAYING, add_to_now_playing }, { }, }; static void avrcp_set_browsing_handlers(struct avrcp *session, const struct avrcp_browsing_handler *handlers, void *user_data) { session->browsing_handlers = handlers; session->browsing_data = user_data; } void avrcp_register_player(struct avrcp *session, const struct avrcp_control_ind *ind, const struct avrcp_control_cfm *cfm, void *user_data) { struct avrcp_player *player; player = g_new0(struct avrcp_player, 1); player->ind = ind; player->cfm = cfm; player->user_data = user_data; avrcp_set_control_handlers(session, player_handlers, player); avrcp_set_browsing_handlers(session, browsing_handlers, player); session->player = player; } void avrcp_set_passthrough_handlers(struct avrcp *session, const struct avrcp_passthrough_handler *handlers, void *user_data) { session->passthrough_handlers = handlers; session->passthrough_data = user_data; } int avrcp_init_uinput(struct avrcp *session, const char *name, const char *address) { return avctp_init_uinput(session->conn, name, address); } int avrcp_send(struct avrcp *session, uint8_t transaction, uint8_t code, uint8_t subunit, uint8_t pdu_id, const struct iovec *iov, int iov_cnt) { return avrcp_send_internal(session, transaction, code, subunit, pdu_id, AVRCP_PACKET_TYPE_SINGLE, iov, iov_cnt); } static int status2errno(uint8_t status) { switch (status) { case AVRCP_STATUS_INVALID_COMMAND: return -ENOSYS; case AVRCP_STATUS_INVALID_PARAM: return -EINVAL; case AVRCP_STATUS_SUCCESS: return 0; case AVRCP_STATUS_NOT_DIRECTORY: return -ENOTDIR; case AVRCP_STATUS_INVALID_SCOPE: return -EBADRQC; case AVRCP_STATUS_OUT_OF_BOUNDS: return -ERANGE; case AVRCP_STATUS_INTERNAL_ERROR: case AVRCP_STATUS_INVALID_PLAYER_ID: case AVRCP_STATUS_PLAYER_NOT_BROWSABLE: case AVRCP_STATUS_NO_AVAILABLE_PLAYERS: case AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED: return -EPERM; default: return -EPROTO; } } static int parse_status(struct avrcp_header *pdu) { if (pdu->params_len < 1) return -EPROTO; return status2errno(pdu->params[0]); } static int parse_browsing_status(struct avrcp_browsing_header *pdu) { if (pdu->params_len < 1) return -EPROTO; return status2errno(pdu->params[0]); } static int avrcp_send_req(struct avrcp *session, uint8_t code, uint8_t subunit, uint8_t pdu_id, const struct iovec *iov, int iov_cnt, avctp_rsp_cb func, void *user_data) { struct iovec pdu[iov_cnt + 1]; struct avrcp_header hdr; int i; memset(&hdr, 0, sizeof(hdr)); pdu[0].iov_base = &hdr; pdu[0].iov_len = sizeof(hdr); for (i = 0; i < iov_cnt; i++) { pdu[i + 1].iov_base = iov[i].iov_base; pdu[i + 1].iov_len = iov[i].iov_len; hdr.params_len += iov[i].iov_len; } hton24(hdr.company_id, IEEEID_BTSIG); hdr.pdu_id = pdu_id; hdr.packet_type = AVRCP_PACKET_TYPE_SINGLE; hdr.params_len = htons(hdr.params_len); return avctp_send_vendor_req(session->conn, code, subunit, pdu, iov_cnt + 1, func, user_data); } static int avrcp_send_browsing_req(struct avrcp *session, uint8_t pdu_id, const struct iovec *iov, int iov_cnt, avctp_browsing_rsp_cb func, void *user_data) { struct iovec pdu[iov_cnt + 1]; struct avrcp_browsing_header hdr; int i; memset(&hdr, 0, sizeof(hdr)); for (i = 0; i < iov_cnt; i++) { pdu[i + 1].iov_base = iov[i].iov_base; pdu[i + 1].iov_len = iov[i].iov_len; hdr.params_len += iov[i].iov_len; } hdr.pdu_id = pdu_id; hdr.params_len = htons(hdr.params_len); pdu[0].iov_base = &hdr; pdu[0].iov_len = sizeof(hdr); return avctp_send_browsing_req(session->conn, pdu, iov_cnt + 1, func, user_data); } static int avrcp_send_browsing(struct avrcp *session, uint8_t transaction, uint8_t pdu_id, const struct iovec *iov, int iov_cnt) { struct iovec pdu[iov_cnt + 1]; struct avrcp_browsing_header hdr; int i; memset(&hdr, 0, sizeof(hdr)); for (i = 0; i < iov_cnt; i++) { pdu[i + 1].iov_base = iov[i].iov_base; pdu[i + 1].iov_len = iov[i].iov_len; hdr.params_len += iov[i].iov_len; } hdr.pdu_id = pdu_id; hdr.params_len = htons(hdr.params_len); pdu[0].iov_base = &hdr; pdu[0].iov_len = sizeof(hdr); return avctp_send_browsing(session->conn, transaction, pdu, iov_cnt + 1); } static gboolean get_capabilities_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; struct get_capabilities_rsp *rsp; uint8_t number = 0; uint8_t *params = NULL; int err; DBG(""); if (!player || !player->cfm || !player->cfm->get_capabilities) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } if (pdu->params_len < sizeof(*rsp)) { err = -EPROTO; goto done; } rsp = (void *) pdu->params; switch (rsp->cap) { case CAP_COMPANY_ID: case CAP_EVENTS_SUPPORTED: break; default: err = -EPROTO; goto done; } if (rsp->number > 0) { number = rsp->number; params = rsp->params; } err = 0; done: player->cfm->get_capabilities(session, err, number, params, player->user_data); return FALSE; } int avrcp_get_capabilities(struct avrcp *session, uint8_t param) { struct iovec iov; struct get_capabilities_req req; req.cap = param; iov.iov_base = &req; iov.iov_len = sizeof(req); return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, AVRCP_GET_CAPABILITIES, &iov, 1, get_capabilities_rsp, session); } static gboolean register_notification_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; struct register_notification_rsp *rsp; uint8_t event = 0; uint16_t value16, value16_2[2]; uint32_t value32; uint64_t value64; uint8_t *params = NULL; int err; DBG(""); if (!player || !player->cfm || !player->cfm->register_notification) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } if (pdu->params_len < sizeof(*rsp)) { err = -EPROTO; goto done; } rsp = (void *) pdu->params; event = rsp->event; if (event > AVRCP_EVENT_LAST) { err = -EPROTO; goto done; } switch (event) { case AVRCP_EVENT_STATUS_CHANGED: if (pdu->params_len != sizeof(*rsp) + sizeof(uint8_t)) { err = -EPROTO; goto done; } params = rsp->data; break; case AVRCP_EVENT_VOLUME_CHANGED: if (pdu->params_len != sizeof(*rsp) + sizeof(uint8_t)) { err = -EPROTO; goto done; } params = rsp->data; params[0] &= 0x7f; break; case AVRCP_EVENT_TRACK_CHANGED: if (pdu->params_len != sizeof(*rsp) + sizeof(value64)) { err = -EPROTO; goto done; } value64 = get_be64(rsp->data); params = (uint8_t *) &value64; break; case AVRCP_EVENT_PLAYBACK_POS_CHANGED: if (pdu->params_len != sizeof(*rsp) + sizeof(value32)) { err = -EPROTO; goto done; } value32 = get_be32(rsp->data); params = (uint8_t *) &value32; break; case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: if (pdu->params_len < sizeof(*rsp) + sizeof(value16_2)) { err = -EPROTO; goto done; } value16_2[0] = get_be16(rsp->data); value16_2[1] = get_be16(rsp->data + 2); params = (uint8_t *) value16_2; break; case AVRCP_EVENT_SETTINGS_CHANGED: if (pdu->params_len < sizeof(*rsp) + sizeof(uint8_t)) { err = -EPROTO; goto done; } params = rsp->data; break; case AVRCP_EVENT_UIDS_CHANGED: if (pdu->params_len < sizeof(*rsp) + sizeof(value16)) { err = -EPROTO; goto done; } value16 = get_be16(rsp->data); params = (uint8_t *) &value16; break; } err = 0; done: return player->cfm->register_notification(session, err, code, event, params, player->user_data); } int avrcp_register_notification(struct avrcp *session, uint8_t event, uint32_t interval) { struct iovec iov; struct register_notification_req req; if (event > AVRCP_EVENT_LAST) return -EINVAL; req.event = event; req.interval = cpu_to_be32(interval); iov.iov_base = &req; iov.iov_len = sizeof(req); return avrcp_send_req(session, AVC_CTYPE_NOTIFY, AVC_SUBUNIT_PANEL, AVRCP_REGISTER_NOTIFICATION, &iov, 1, register_notification_rsp, session); } static gboolean list_attributes_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu = (void *) operands; struct list_attributes_rsp *rsp; uint8_t number = 0; uint8_t *attrs = NULL; int err; DBG(""); if (!player || !player->cfm || !player->cfm->list_attributes) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } rsp = (void *) pdu->params; if (pdu->params_len < sizeof(*rsp)) { err = -EPROTO; goto done; } number = rsp->number; if (number > 0) attrs = rsp->params; err = 0; done: player->cfm->list_attributes(session, err, number, attrs, player->user_data); return FALSE; } int avrcp_list_player_attributes(struct avrcp *session) { return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, AVRCP_LIST_PLAYER_ATTRIBUTES, NULL, 0, list_attributes_rsp, session); } static int parse_text_rsp(struct avrcp_header *pdu, uint8_t *number, uint8_t *attrs, char **text) { uint8_t *ptr; uint16_t params_len; int i; if (pdu->params_len < 1) return -EPROTO; *number = pdu->params[0]; if (*number > AVRCP_ATTRIBUTE_LAST) { *number = 0; return -EPROTO; } params_len = pdu->params_len - 1; for (i = 0, ptr = &pdu->params[1]; i < *number && params_len > 0; i++) { struct text_value *val; if (params_len < sizeof(*val)) goto fail; val = (void *) ptr; attrs[i] = val->attr; params_len -= sizeof(*val); ptr += sizeof(*val); if (val->len > params_len) goto fail; if (val->len > 0) { text[i] = g_strndup(val->data, val->len); params_len -= val->len; ptr += val->len; } } if (i != *number) goto fail; return 0; fail: for (i -= 1; i >= 0; i--) g_free(text[i]); *number = 0; return -EPROTO; } static gboolean get_attribute_text_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; uint8_t number = 0; uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; char *text[AVRCP_ATTRIBUTE_LAST]; int err; DBG(""); if (!player || !player->cfm || !player->cfm->get_attribute_text) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } err = parse_text_rsp(pdu, &number, attrs, text); done: player->cfm->get_attribute_text(session, err, number, attrs, text, player->user_data); return FALSE; } int avrcp_get_player_attribute_text(struct avrcp *session, uint8_t number, uint8_t *attrs) { struct iovec iov[2]; if (!number || number > AVRCP_ATTRIBUTE_LAST) return -EINVAL; iov[0].iov_base = &number; iov[0].iov_len = sizeof(number); iov[1].iov_base = attrs; iov[1].iov_len = number; return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, iov, 2, get_attribute_text_rsp, session); } static gboolean list_values_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; struct list_values_rsp *rsp; uint8_t number = 0; uint8_t *values = NULL; int err; DBG(""); if (!player || !player->cfm || !player->cfm->list_values) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } if (pdu->params_len < sizeof(*rsp)) { err = -EPROTO; goto done; } rsp = (void *) pdu->params; if (rsp->number > 0) { number = rsp->number; values = rsp->params; } err = 0; done: player->cfm->list_values(session, err, number, values, player->user_data); return FALSE; } int avrcp_list_player_values(struct avrcp *session, uint8_t attr) { struct iovec iov; iov.iov_base = &attr; iov.iov_len = sizeof(attr); return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, AVRCP_LIST_PLAYER_VALUES, &iov, 1, list_values_rsp, session); } static gboolean get_value_text_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; uint8_t number = 0; uint8_t values[AVRCP_ATTRIBUTE_LAST]; char *text[AVRCP_ATTRIBUTE_LAST]; int err; DBG(""); if (!player || !player->cfm || !player->cfm->get_value_text) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } err = parse_text_rsp(pdu, &number, values, text); done: player->cfm->get_value_text(session, err, number, values, text, player->user_data); return FALSE; } int avrcp_get_player_value_text(struct avrcp *session, uint8_t attr, uint8_t number, uint8_t *values) { struct iovec iov[2]; struct get_value_text_req req; if (!number) return -EINVAL; req.attr = attr; req.number = number; iov[0].iov_base = &req; iov[0].iov_len = sizeof(req); iov[1].iov_base = values; iov[1].iov_len = number; return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, AVRCP_GET_PLAYER_VALUE_TEXT, iov, 2, get_value_text_rsp, session); } static int parse_value(struct avrcp_header *pdu, uint8_t *number, uint8_t *attrs, uint8_t *values) { int i; struct value_rsp *rsp; if (pdu->params_len < sizeof(*rsp)) return -EPROTO; rsp = (void *) pdu->params; /* * Check if PDU is big enough to hold the number of (attribute, value) * tuples. */ if (rsp->number > AVRCP_ATTRIBUTE_LAST || sizeof(*rsp) + rsp->number * 2 != pdu->params_len) { *number = 0; return -EPROTO; } for (i = 0; i < rsp->number; i++) { attrs[i] = rsp->values[i].attr; values[i] = rsp->values[i].value; } *number = rsp->number; return 0; } static gboolean get_value_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; uint8_t number = 0; uint8_t attrs[AVRCP_ATTRIBUTE_LAST]; uint8_t values[AVRCP_ATTRIBUTE_LAST]; int err; DBG(""); if (!player || !player->cfm || !player->cfm->get_value) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } err = parse_value(pdu, &number, attrs, values); done: player->cfm->get_value(session, err, number, attrs, values, player->user_data); return FALSE; } int avrcp_get_current_player_value(struct avrcp *session, uint8_t number, uint8_t *attrs) { struct iovec iov[2]; if (number > AVRCP_ATTRIBUTE_LAST) return -EINVAL; iov[0].iov_base = &number; iov[0].iov_len = sizeof(number); iov[1].iov_base = attrs; iov[1].iov_len = number; return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, AVRCP_GET_CURRENT_PLAYER_VALUE, iov, 2, get_value_rsp, session); } static gboolean set_value_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; int err; DBG(""); if (!player || !player->cfm || !player->cfm->set_value) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } err = 0; done: player->cfm->set_value(session, err, player->user_data); return FALSE; } int avrcp_set_player_value(struct avrcp *session, uint8_t number, uint8_t *attrs, uint8_t *values) { struct iovec iov[2]; struct attr_value val[AVRCP_ATTRIBUTE_LAST]; int i; if (number > AVRCP_ATTRIBUTE_LAST) return -EINVAL; iov[0].iov_base = &number; iov[0].iov_len = sizeof(number); for (i = 0; i < number; i++) { val[i].attr = attrs[i]; val[i].value = values[i]; } iov[1].iov_base = val; iov[1].iov_len = sizeof(*val) * number; return avrcp_send_req(session, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, AVRCP_SET_PLAYER_VALUE, iov, 2, set_value_rsp, session); } static gboolean get_play_status_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; struct get_play_status_rsp *rsp; uint8_t status = 0; uint32_t position = 0; uint32_t duration = 0; int err; DBG(""); if (!player || !player->cfm || !player->cfm->get_play_status) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } if (pdu->params_len < sizeof(*rsp)) { err = -EPROTO; goto done; } rsp = (void *) pdu->params; duration = be32_to_cpu(rsp->duration); position = be32_to_cpu(rsp->position); status = rsp->status; err = 0; done: player->cfm->get_play_status(session, err, status, position, duration, player->user_data); return FALSE; } int avrcp_get_play_status(struct avrcp *session) { return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, AVRCP_GET_PLAY_STATUS, NULL, 0, get_play_status_rsp, session); } static gboolean set_volume_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; struct set_volume_rsp *rsp; uint8_t value = 0; int err; DBG(""); if (!player || !player->cfm || !player->cfm->set_volume) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } if (pdu->params_len < sizeof(*rsp)) { err = -EPROTO; goto done; } rsp = (void *) pdu->params; value = rsp->value & 0x7f; err = 0; done: player->cfm->set_volume(session, err, value, player->user_data); return FALSE; } int avrcp_set_volume(struct avrcp *session, uint8_t volume) { struct iovec iov; iov.iov_base = &volume; iov.iov_len = sizeof(volume); return avrcp_send_req(session, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, AVRCP_SET_ABSOLUTE_VOLUME, &iov, 1, set_volume_rsp, session); } static int parse_attribute_list(uint8_t *params, uint16_t params_len, uint8_t number, uint32_t *attrs, char **text) { struct media_item *item; int i; if (number > AVRCP_MEDIA_ATTRIBUTE_LAST) return -EPROTO; for (i = 0; i < number && params_len >= sizeof(*item); i++) { item = (void *) params; item->attr = be32_to_cpu(item->attr); item->charset = be16_to_cpu(item->charset); item->len = be16_to_cpu(item->len); params_len -= sizeof(*item); params += sizeof(*item); if (item->len > params_len) goto fail; if (item->len > 0) { text[i] = g_strndup(item->data, item->len); attrs[i] = item->attr; params_len -= item->len; params += item->len; } else { text[i] = NULL; attrs[i] = 0; } } return 0; fail: for (i -= 1; i >= 0; i--) g_free(text[i]); return -EPROTO; } static void free_attribute_list(uint8_t number, char **text) { while(number--) g_free(text[number]); } static int parse_elements(struct avrcp_header *pdu, uint8_t *number, uint32_t *attrs, char **text) { struct get_element_attributes_rsp *rsp; if (pdu->params_len < sizeof(*rsp)) return -EPROTO; rsp = (void *) pdu->params; if (rsp->number > AVRCP_MEDIA_ATTRIBUTE_LAST) return -EPROTO; *number = rsp->number; return parse_attribute_list(pdu->params + sizeof(*rsp), pdu->params_len - sizeof(*rsp), *number, attrs, text); } static int parse_items(struct avrcp_browsing_header *pdu, uint8_t *number, uint32_t *attrs, char **text) { struct get_item_attributes_rsp *rsp; if (pdu->params_len < sizeof(*rsp)) return -EPROTO; rsp = (void *) pdu->params; if (rsp->number > AVRCP_MEDIA_ATTRIBUTE_LAST) return -EPROTO; *number = rsp->number; return parse_attribute_list(pdu->params + sizeof(*rsp), pdu->params_len - sizeof(*rsp), *number, attrs, text); } static gboolean get_element_attributes_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; uint8_t number = 0; uint32_t attrs[AVRCP_MEDIA_ATTRIBUTE_LAST]; char *text[AVRCP_MEDIA_ATTRIBUTE_LAST]; int err; DBG(""); if (!player || !player->cfm || !player->cfm->get_element_attributes) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } if (code == AVC_CTYPE_REJECTED) { err = parse_status(pdu); goto done; } err = parse_elements(pdu, &number, attrs, text); done: player->cfm->get_element_attributes(session, err, number, attrs, text, player->user_data); if (err == 0) free_attribute_list(number, text); return FALSE; } int avrcp_get_element_attributes(struct avrcp *session) { struct iovec iov; struct get_element_attributes_req req; /* This returns all attributes */ memset(&req, 0, sizeof(req)); iov.iov_base = &req; iov.iov_len = sizeof(req); return avrcp_send_req(session, AVC_CTYPE_STATUS, AVC_SUBUNIT_PANEL, AVRCP_GET_ELEMENT_ATTRIBUTES, &iov, 1, get_element_attributes_rsp, session); } static gboolean set_addressed_rsp(struct avctp *conn, uint8_t code, uint8_t subunit, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_header *pdu; int err; DBG(""); if (!player || !player->cfm || !player->cfm->set_addressed) return FALSE; pdu = parse_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } err = parse_status(pdu); done: player->cfm->set_addressed(session, err, player->user_data); return FALSE; } int avrcp_set_addressed_player(struct avrcp *session, uint16_t player_id) { struct iovec iov; struct set_addressed_req req; req.id = cpu_to_be16(player_id); iov.iov_base = &req; iov.iov_len = sizeof(req); return avrcp_send_req(session, AVC_CTYPE_CONTROL, AVC_SUBUNIT_PANEL, AVRCP_SET_ADDRESSED_PLAYER, &iov, 1, set_addressed_rsp, session); } static char *parse_folder_list(uint8_t *params, uint16_t params_len, uint8_t depth) { char **folders, *path; uint8_t count; size_t i; folders = g_new0(char *, depth + 2); folders[0] = g_strdup("/Filesystem"); for (i = 0, count = 1; count <= depth && i < params_len; count++) { uint8_t len; len = params[i++]; if (i + len > params_len || len == 0) { g_strfreev(folders); return NULL; } folders[count] = util_memdup(¶ms[i], len); i += len; } path = g_build_pathv("/", folders); g_strfreev(folders); return path; } static gboolean set_browsed_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_browsing_header *pdu; struct set_browsed_rsp *rsp; uint16_t counter = 0; uint32_t items = 0; char *path = NULL; int err; DBG(""); if (!player || !player->cfm || !player->cfm->set_browsed) return FALSE; pdu = parse_browsing_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } err = parse_browsing_status(pdu); if (err < 0) goto done; if (pdu->params_len < sizeof(*rsp)) { err = -EPROTO; goto done; } rsp = (void *) pdu->params; counter = be16_to_cpu(rsp->counter); items = be32_to_cpu(rsp->items); path = parse_folder_list(rsp->data, pdu->params_len - sizeof(*rsp), rsp->depth); if (!path) err = -EPROTO; done: player->cfm->set_browsed(session, err, counter, items, path, player->user_data); return FALSE; } int avrcp_set_browsed_player(struct avrcp *session, uint16_t player_id) { struct iovec iov; struct set_browsed_req req; req.id = cpu_to_be16(player_id); iov.iov_base = &req; iov.iov_len = sizeof(req); return avrcp_send_browsing_req(session, AVRCP_SET_BROWSED_PLAYER, &iov, 1, set_browsed_rsp, session); } static gboolean get_folder_items_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_browsing_header *pdu; struct get_folder_items_rsp *rsp; uint16_t counter = 0, number = 0; uint8_t *params = NULL; int err; DBG(""); if (!player || !player->cfm || !player->cfm->get_folder_items) return FALSE; pdu = parse_browsing_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } err = parse_browsing_status(pdu); if (err < 0) goto done; if (pdu->params_len < sizeof(*rsp)) { err = -EPROTO; goto done; } rsp = (void *) pdu->params; counter = be16_to_cpu(rsp->counter); number = be16_to_cpu(rsp->number); params = rsp->data; /* FIXME: Add proper parsing for each item type */ done: player->cfm->get_folder_items(session, err, counter, number, params, player->user_data); return FALSE; } int avrcp_get_folder_items(struct avrcp *session, uint8_t scope, uint32_t start, uint32_t end, uint8_t number, uint32_t *attrs) { struct iovec iov[2]; struct get_folder_items_req req; int i; req.scope = scope; req.start = cpu_to_be32(start); req.end = cpu_to_be32(end); req.number = number; iov[0].iov_base = &req; iov[0].iov_len = sizeof(req); if (!number) return avrcp_send_browsing_req(session, AVRCP_GET_FOLDER_ITEMS, iov, 1, get_folder_items_rsp, session); for (i = 0; i < number; i++) attrs[i] = cpu_to_be32(attrs[i]); iov[1].iov_base = attrs; iov[1].iov_len = number * sizeof(*attrs); return avrcp_send_browsing_req(session, AVRCP_GET_FOLDER_ITEMS, iov, 2, get_folder_items_rsp, session); } static gboolean change_path_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_browsing_header *pdu; struct change_path_rsp *rsp; uint32_t items = 0; int err; DBG(""); if (!player || !player->cfm || !player->cfm->change_path) return FALSE; pdu = parse_browsing_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } err = parse_browsing_status(pdu); if (err < 0) goto done; if (pdu->params_len < sizeof(*rsp)) { err = -EPROTO; goto done; } rsp = (void *) pdu->params; items = be32_to_cpu(rsp->items); done: player->cfm->change_path(session, err, items, player->user_data); return FALSE; } int avrcp_change_path(struct avrcp *session, uint8_t direction, uint64_t uid, uint16_t counter) { struct iovec iov; struct change_path_req req; req.counter = cpu_to_be16(counter); req.direction = direction; req.uid = cpu_to_be64(uid); iov.iov_base = &req; iov.iov_len = sizeof(req); return avrcp_send_browsing_req(session, AVRCP_CHANGE_PATH, &iov, 1, change_path_rsp, session); } static gboolean get_item_attributes_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_browsing_header *pdu; uint8_t number = 0; uint32_t attrs[AVRCP_MEDIA_ATTRIBUTE_LAST]; char *text[AVRCP_MEDIA_ATTRIBUTE_LAST]; int err; DBG(""); if (!player || !player->cfm || !player->cfm->get_item_attributes) return FALSE; pdu = parse_browsing_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } err = parse_browsing_status(pdu); if (err < 0) goto done; err = parse_items(pdu, &number, attrs, text); done: player->cfm->get_item_attributes(session, err, number, attrs, text, player->user_data); if (err == 0) free_attribute_list(number, text); return FALSE; } int avrcp_get_item_attributes(struct avrcp *session, uint8_t scope, uint64_t uid, uint16_t counter, uint8_t number, uint32_t *attrs) { struct iovec iov[2]; struct get_item_attributes_req req; int i; req.scope = scope; req.uid = cpu_to_be64(uid); req.counter = cpu_to_be16(counter); req.number = number; iov[0].iov_base = &req; iov[0].iov_len = sizeof(req); if (!number) return avrcp_send_browsing_req(session, AVRCP_GET_ITEM_ATTRIBUTES, iov, 1, get_item_attributes_rsp, session); if (number > AVRCP_MEDIA_ATTRIBUTE_LAST) return -EINVAL; for (i = 0; i < number; i++) { if (attrs[i] > AVRCP_MEDIA_ATTRIBUTE_LAST || attrs[i] == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL) return -EINVAL; attrs[i] = cpu_to_be32(attrs[i]); } iov[1].iov_base = attrs; iov[1].iov_len = number * sizeof(*attrs); return avrcp_send_browsing_req(session, AVRCP_GET_ITEM_ATTRIBUTES, iov, 2, get_item_attributes_rsp, session); } static gboolean play_item_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_browsing_header *pdu; int err; DBG(""); if (!player || !player->cfm || !player->cfm->play_item) return FALSE; pdu = parse_browsing_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } err = parse_browsing_status(pdu); done: player->cfm->play_item(session, err, player->user_data); return FALSE; } int avrcp_play_item(struct avrcp *session, uint8_t scope, uint64_t uid, uint16_t counter) { struct iovec iov; struct play_item_req req; if (scope > AVRCP_MEDIA_NOW_PLAYING) return -EINVAL; req.scope = scope; req.uid = cpu_to_be64(uid); req.counter = cpu_to_be16(counter); iov.iov_base = &req; iov.iov_len = sizeof(req); return avrcp_send_browsing_req(session, AVRCP_PLAY_ITEM, &iov, 1, play_item_rsp, session); } static gboolean search_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_browsing_header *pdu; struct search_rsp *rsp; uint16_t counter = 0; uint32_t items = 0; int err; DBG(""); if (!player || !player->cfm || !player->cfm->search) return FALSE; pdu = parse_browsing_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } err = parse_browsing_status(pdu); if (err < 0) goto done; if (pdu->params_len < sizeof(*rsp)) { err = -EPROTO; goto done; } rsp = (void *) pdu->params; counter = be16_to_cpu(rsp->counter); items = be32_to_cpu(rsp->items); err = 0; done: player->cfm->search(session, err, counter, items, player->user_data); return FALSE; } int avrcp_search(struct avrcp *session, const char *string) { struct iovec iov[2]; struct search_req req; size_t len; if (!string) return -EINVAL; len = strnlen(string, UINT8_MAX); req.charset = cpu_to_be16(AVRCP_CHARSET_UTF8); req.len = cpu_to_be16(len); iov[0].iov_base = &req; iov[0].iov_len = sizeof(req); iov[1].iov_base = (void *) string; iov[1].iov_len = len; return avrcp_send_browsing_req(session, AVRCP_SEARCH, iov, 2, search_rsp, session); } static gboolean add_to_now_playing_rsp(struct avctp *conn, uint8_t *operands, size_t operand_count, void *user_data) { struct avrcp *session = user_data; struct avrcp_player *player = session->player; struct avrcp_browsing_header *pdu; int err; DBG(""); if (!player || !player->cfm || !player->cfm->add_to_now_playing) return FALSE; pdu = parse_browsing_pdu(operands, operand_count); if (!pdu) { err = -EPROTO; goto done; } err = parse_browsing_status(pdu); done: player->cfm->add_to_now_playing(session, err, player->user_data); return FALSE; } int avrcp_add_to_now_playing(struct avrcp *session, uint8_t scope, uint64_t uid, uint16_t counter) { struct iovec iov; struct add_to_now_playing_req req; if (scope > AVRCP_MEDIA_NOW_PLAYING) return -EINVAL; req.scope = scope; req.uid = cpu_to_be64(uid); req.counter = cpu_to_be16(counter); iov.iov_base = &req; iov.iov_len = sizeof(req); return avrcp_send_browsing_req(session, AVRCP_ADD_TO_NOW_PLAYING, &iov, 1, add_to_now_playing_rsp, session); } int avrcp_get_capabilities_rsp(struct avrcp *session, uint8_t transaction, uint8_t number, uint8_t *events) { struct iovec iov[2]; struct get_capabilities_rsp rsp; if (number > AVRCP_EVENT_LAST) return -EINVAL; rsp.cap = CAP_EVENTS_SUPPORTED; rsp.number = number; iov[0].iov_base = &rsp; iov[0].iov_len = sizeof(rsp); iov[1].iov_base = events; iov[1].iov_len = number; return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_GET_CAPABILITIES, iov, 2); } int avrcp_list_player_attributes_rsp(struct avrcp *session, uint8_t transaction, uint8_t number, uint8_t *attrs) { struct iovec iov[2]; struct list_attributes_rsp rsp; if (number > AVRCP_ATTRIBUTE_LAST) return -EINVAL; rsp.number = number; iov[0].iov_base = &rsp; iov[0].iov_len = sizeof(rsp); if (!number) return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_LIST_PLAYER_ATTRIBUTES, iov, 1); iov[1].iov_base = attrs; iov[1].iov_len = number; return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_LIST_PLAYER_ATTRIBUTES, iov, 2); } int avrcp_get_player_attribute_text_rsp(struct avrcp *session, uint8_t transaction, uint8_t number, uint8_t *attrs, const char **text) { struct iovec iov[1 + AVRCP_ATTRIBUTE_LAST * 2]; struct text_value val[AVRCP_ATTRIBUTE_LAST]; int i; if (number > AVRCP_ATTRIBUTE_LAST) return -EINVAL; iov[0].iov_base = &number; iov[0].iov_len = sizeof(number); for (i = 0; i < number; i++) { uint8_t len = 0; if (attrs[i] > AVRCP_ATTRIBUTE_LAST || attrs[i] == AVRCP_ATTRIBUTE_ILEGAL) return -EINVAL; if (text[i]) len = strlen(text[i]); val[i].attr = attrs[i]; val[i].charset = cpu_to_be16(AVRCP_CHARSET_UTF8); val[i].len = len; iov[i + 1].iov_base = &val[i]; iov[i + 1].iov_len = sizeof(val[i]); iov[i + 2].iov_base = (void *) text[i]; iov[i + 2].iov_len = len; } return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_GET_PLAYER_ATTRIBUTE_TEXT, iov, 1 + i * 2); } int avrcp_list_player_values_rsp(struct avrcp *session, uint8_t transaction, uint8_t number, uint8_t *values) { struct iovec iov[2]; if (number > AVRCP_ATTRIBUTE_LAST) return -EINVAL; iov[0].iov_base = &number; iov[0].iov_len = sizeof(number); iov[1].iov_base = values; iov[1].iov_len = number; return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_LIST_PLAYER_VALUES, iov, 2); } int avrcp_get_play_status_rsp(struct avrcp *session, uint8_t transaction, uint32_t position, uint32_t duration, uint8_t status) { struct iovec iov; struct get_play_status_rsp rsp; rsp.duration = cpu_to_be32(duration); rsp.position = cpu_to_be32(position); rsp.status = status; iov.iov_base = &rsp; iov.iov_len = sizeof(rsp); return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_GET_PLAY_STATUS, &iov, 1); } int avrcp_get_player_values_text_rsp(struct avrcp *session, uint8_t transaction, uint8_t number, uint8_t *values, const char **text) { struct iovec iov[1 + AVRCP_ATTRIBUTE_LAST * 2]; struct text_value val[AVRCP_ATTRIBUTE_LAST]; int i; if (number > AVRCP_ATTRIBUTE_LAST) return -EINVAL; iov[0].iov_base = &number; iov[0].iov_len = sizeof(number); for (i = 0; i < number; i++) { uint8_t len = 0; if (text[i]) len = strlen(text[i]); val[i].attr = values[i]; val[i].charset = cpu_to_be16(AVRCP_CHARSET_UTF8); val[i].len = len; iov[i + 1].iov_base = &val[i]; iov[i + 1].iov_len = sizeof(val[i]); iov[i + 2].iov_base = (void *) text[i]; iov[i + 2].iov_len = len; } return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_GET_PLAYER_VALUE_TEXT, iov, 1 + i * 2); } int avrcp_get_current_player_value_rsp(struct avrcp *session, uint8_t transaction, uint8_t number, uint8_t *attrs, uint8_t *values) { struct iovec iov[1 + AVRCP_ATTRIBUTE_LAST]; struct attr_value val[AVRCP_ATTRIBUTE_LAST]; int i; if (number > AVRCP_ATTRIBUTE_LAST) return -EINVAL; iov[0].iov_base = &number; iov[0].iov_len = sizeof(number); for (i = 0; i < number; i++) { val[i].attr = attrs[i]; val[i].value = values[i]; iov[i + 1].iov_base = &val[i]; iov[i + 1].iov_len = sizeof(val[i]); } return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_GET_CURRENT_PLAYER_VALUE, iov, 1 + i); } int avrcp_set_player_value_rsp(struct avrcp *session, uint8_t transaction) { return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_SET_PLAYER_VALUE, NULL, 0); } int avrcp_get_element_attrs_rsp(struct avrcp *session, uint8_t transaction, uint8_t *params, size_t params_len) { struct iovec iov; iov.iov_base = params; iov.iov_len = params_len; return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_GET_ELEMENT_ATTRIBUTES, &iov, 1); } int avrcp_register_notification_rsp(struct avrcp *session, uint8_t transaction, uint8_t code, uint8_t event, void *data, size_t len) { struct iovec iov[2]; uint16_t *player; uint8_t *volume; if (event > AVRCP_EVENT_LAST) return -EINVAL; iov[0].iov_base = &event; iov[0].iov_len = sizeof(event); switch (event) { case AVRCP_EVENT_STATUS_CHANGED: if (len != sizeof(uint8_t)) return -EINVAL; break; case AVRCP_EVENT_VOLUME_CHANGED: if (len != sizeof(uint8_t)) return -EINVAL; volume = data; if (volume[0] > 127) return -EINVAL; break; case AVRCP_EVENT_TRACK_CHANGED: if (len != sizeof(uint64_t)) return -EINVAL; put_be64(*(uint64_t *) data, data); break; case AVRCP_EVENT_PLAYBACK_POS_CHANGED: if (len != sizeof(uint32_t)) return -EINVAL; put_be32(*(uint32_t *) data, data); break; case AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED: if (len != 4) return -EINVAL; player = data; player[0] = cpu_to_be16(player[0]); player[1] = cpu_to_be16(player[1]); break; case AVRCP_EVENT_SETTINGS_CHANGED: if (len < sizeof(uint8_t)) return -EINVAL; break; case AVRCP_EVENT_UIDS_CHANGED: if (len != sizeof(uint16_t)) return -EINVAL; put_be16(*(uint16_t *) data, data); break; default: return avrcp_send(session, transaction, code, AVC_SUBUNIT_PANEL, AVRCP_REGISTER_NOTIFICATION, iov, 1); } iov[1].iov_base = data; iov[1].iov_len = len; return avrcp_send(session, transaction, code, AVC_SUBUNIT_PANEL, AVRCP_REGISTER_NOTIFICATION, iov, 2); } int avrcp_set_volume_rsp(struct avrcp *session, uint8_t transaction, uint8_t volume) { struct iovec iov; if (volume > 127) return -EINVAL; iov.iov_base = &volume; iov.iov_len = sizeof(volume); return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_SET_ABSOLUTE_VOLUME, &iov, 1); } int avrcp_set_addressed_player_rsp(struct avrcp *session, uint8_t transaction, uint8_t status) { struct iovec iov; iov.iov_base = &status; iov.iov_len = sizeof(status); return avrcp_send(session, transaction, AVC_CTYPE_STABLE, AVC_SUBUNIT_PANEL, AVRCP_SET_ADDRESSED_PLAYER, &iov, 1); } static int avrcp_status_rsp(struct avrcp *session, uint8_t transaction, uint8_t pdu_id, uint8_t status) { struct iovec iov; if (status > AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED) return -EINVAL; iov.iov_base = &status; iov.iov_len = sizeof(status); return avrcp_send_browsing(session, transaction, pdu_id, &iov, 1); } int avrcp_set_browsed_player_rsp(struct avrcp *session, uint8_t transaction, uint8_t status, uint16_t counter, uint32_t items, uint8_t depth, const char **folders) { struct iovec iov[UINT8_MAX * 2 + 1]; struct set_browsed_rsp rsp; uint16_t len[UINT8_MAX]; int i; if (status != AVRCP_STATUS_SUCCESS) return avrcp_status_rsp(session, transaction, AVRCP_SET_BROWSED_PLAYER, status); rsp.status = status; rsp.counter = cpu_to_be16(counter); rsp.items = cpu_to_be32(items); rsp.charset = cpu_to_be16(AVRCP_CHARSET_UTF8); rsp.depth = depth; iov[0].iov_base = &rsp; iov[0].iov_len = sizeof(rsp); if (!depth) return avrcp_send_browsing(session, transaction, AVRCP_SET_BROWSED_PLAYER, iov, 1); for (i = 0; i < depth; i++) { if (!folders[i]) return -EINVAL; len[i] = strlen(folders[i]); iov[i * 2 + 2].iov_base = (void *) folders[i]; iov[i * 2 + 2].iov_len = len[i]; len[i] = cpu_to_be16(len[i]); iov[i * 2 + 1].iov_base = &len[i]; iov[i * 2 + 1].iov_len = sizeof(len[i]); } return avrcp_send_browsing(session, transaction, AVRCP_SET_BROWSED_PLAYER, iov, depth * 2 + 1); } int avrcp_get_folder_items_rsp(struct avrcp *session, uint8_t transaction, uint8_t status, uint16_t counter, uint8_t number, uint8_t *type, uint16_t *len, uint8_t **params) { struct iovec iov[UINT8_MAX * 2 + 1]; struct get_folder_items_rsp rsp; uint8_t item[UINT8_MAX][3]; int i; if (status != AVRCP_STATUS_SUCCESS) return avrcp_status_rsp(session, transaction, AVRCP_GET_FOLDER_ITEMS, status); rsp.status = status; rsp.counter = cpu_to_be16(counter); rsp.number = cpu_to_be16(number); iov[0].iov_base = &rsp; iov[0].iov_len = sizeof(rsp); for (i = 0; i < number; i++) { item[i][0] = type[i]; put_be16(len[i], &item[i][1]); iov[i * 2 + 1].iov_base = item[i]; iov[i * 2 + 1].iov_len = sizeof(item[i]); iov[i * 2 + 2].iov_base = params[i]; iov[i * 2 + 2].iov_len = len[i]; } return avrcp_send_browsing(session, transaction, AVRCP_GET_FOLDER_ITEMS, iov, number * 2 + 1); } int avrcp_change_path_rsp(struct avrcp *session, uint8_t transaction, uint8_t status, uint32_t items) { struct iovec iov; struct change_path_rsp rsp; if (status != AVRCP_STATUS_SUCCESS) return avrcp_status_rsp(session, transaction, AVRCP_CHANGE_PATH, status); rsp.status = status; rsp.items = cpu_to_be32(items); iov.iov_base = &rsp; iov.iov_len = sizeof(rsp); return avrcp_send_browsing(session, transaction, AVRCP_CHANGE_PATH, &iov, 1); } static bool pack_attribute_list(struct iovec *iov, uint8_t number, uint32_t *attrs, const char **text) { int i; struct media_item val[AVRCP_MEDIA_ATTRIBUTE_LAST]; for (i = 0; i < number; i++) { uint16_t len = 0; if (attrs[i] > AVRCP_MEDIA_ATTRIBUTE_LAST || attrs[i] == AVRCP_MEDIA_ATTRIBUTE_ILLEGAL) return false; if (text[i]) len = strlen(text[i]); val[i].attr = cpu_to_be32(attrs[i]); val[i].charset = cpu_to_be16(AVRCP_CHARSET_UTF8); val[i].len = cpu_to_be16(len); iov[i].iov_base = &val[i]; iov[i].iov_len = sizeof(val[i]); iov[i + 1].iov_base = (void *) text[i]; iov[i + 1].iov_len = len; } return true; } int avrcp_get_item_attributes_rsp(struct avrcp *session, uint8_t transaction, uint8_t status, uint8_t number, uint32_t *attrs, const char **text) { struct iovec iov[AVRCP_MEDIA_ATTRIBUTE_LAST * 2 + 1]; struct get_item_attributes_rsp rsp; if (number > AVRCP_MEDIA_ATTRIBUTE_LAST) return -EINVAL; if (status != AVRCP_STATUS_SUCCESS) return avrcp_status_rsp(session, transaction, AVRCP_GET_ITEM_ATTRIBUTES, status); rsp.status = status; rsp.number = number; iov[0].iov_base = &rsp; iov[0].iov_len = sizeof(rsp); if (!pack_attribute_list(&iov[1], number, attrs, text)) return -EINVAL; return avrcp_send_browsing(session, transaction, AVRCP_GET_ITEM_ATTRIBUTES, iov, number * 2 + 1); } int avrcp_play_item_rsp(struct avrcp *session, uint8_t transaction, uint8_t status) { return avrcp_status_rsp(session, transaction, AVRCP_PLAY_ITEM, status); } int avrcp_search_rsp(struct avrcp *session, uint8_t transaction, uint8_t status, uint16_t counter, uint32_t items) { struct iovec iov; struct search_rsp rsp; if (status != AVRCP_STATUS_SUCCESS) return avrcp_status_rsp(session, transaction, AVRCP_SEARCH, status); rsp.status = status; rsp.counter = cpu_to_be16(counter); rsp.items = cpu_to_be32(items); iov.iov_base = &rsp; iov.iov_len = sizeof(rsp); return avrcp_send_browsing(session, transaction, AVRCP_SEARCH, &iov, 1); } int avrcp_add_to_now_playing_rsp(struct avrcp *session, uint8_t transaction, uint8_t status) { return avrcp_status_rsp(session, transaction, AVRCP_ADD_TO_NOW_PLAYING, status); } int avrcp_send_passthrough(struct avrcp *session, uint32_t vendor, uint8_t op) { uint8_t params[5]; if (!vendor) return avctp_send_passthrough(session->conn, op, NULL, 0); hton24(params, vendor); put_be16(op, ¶ms[3]); return avctp_send_passthrough(session->conn, AVC_VENDOR_UNIQUE, params, sizeof(params)); }