2011-08-11 14:16:47

by David Stockwell

[permalink] [raw]
Subject: [PATCH 5/5] Add AVRCP-Metadata 1.3 Processing

Add AVRCP-Metadata 1.3 Processing

Metadata is transferred using Vendor Dependent messages
with the Company ID set to that of the Bluetooth SIG:
0x001958.

Includes:
* Provides for fragmentation over AVCTP, both incoming and
outgoing, when message size exceeds MTU.
Necessary for AVRCP v1.4 (metadata browsing).
* Improved layering between AVRCP Metadata, AVRCP, and AVCTP
for clarity and -- I hope -- easier enhancement.
* Support for following PDUs:
- GET_CAPABILITIES
- GET_ELEMENT_ATTRS
- REGISTER_NOTIFICATION
- GET_PLAY_STATUS
- INFORM_CT_BATTERY_STATUS
* Support for following Notifications/Events:
- PLAYBACK_STATUS_CHANGED
- TRACK_CHANGED
- TRACK_REACHED_END
- TRACK_REACHED_START
- PLAYBACK_POSITION_CHANGED
- BATTERY_STATUS_CHANGED
* The following read-only Properties
- Battery (status)
- MTU (debug: will probably delete)
- PlayInterval (notifies TG of desired play pos updates)
- Version (version of AVRCP: 1.3)
* Implemented method: ChangeTrack (TG signals to CT that
track has changed, plus stores metadata: Title,
Artist, Album, Track Duration, etc.)
* Implemented method: ChangePlayback (play status of
track on TG).
* New signal: GetPlayStatus(void), for use by CT that does
not use asynchronous Notifications.

Signed-off-by: David Stockwell <[email protected]>
---
audio/control.c | 1863
++++++++++++++++++++++++++++++++++++++++++++++++++-----
1 files changed, 1715 insertions(+), 148 deletions(-)

diff --git a/audio/control.c b/audio/control.c
index 472cab2..33b2f4b 100644
--- a/audio/control.c
+++ b/audio/control.c
@@ -34,6 +34,7 @@
#include <signal.h>
#include <sys/types.h>
#include <sys/stat.h>
+#include <sys/uio.h>
#include <fcntl.h>
#include <netinet/in.h>

@@ -206,6 +207,14 @@
#define EVENT_UIDS_CHANGED 0x0C
#define EVENT_VOLUME_CHANGED 0x0D

+/* AVRCP Play Statuses */
+#define AVRCP_STATUS_STOPPED 0x00
+#define AVRCP_STATUS_PLAYING 0x01
+#define AVRCP_STATUS_PAUSED 0x02
+#define AVRCP_STATUS_FWD_SEEK 0x03
+#define AVRCP_STATUS_REV_SEEK 0x04
+#define AVRCP_STATUS_ERROR 0xFF
+
/* BT-SIG defined Company ID for Metadata */
#define BTSIG_COMPANY_ID 0x001958

@@ -229,6 +238,16 @@ struct avctp_header {
} __attribute__ ((packed));
#define AVCTP_HEADER_LENGTH 3

+struct avctp_header_first {
+ uint8_t ipid:1;
+ uint8_t cr:1;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+ uint8_t packets;
+ uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_FIRST_LENGTH 4
+
struct avrcp_header {
uint8_t ctype:4;
uint8_t _hdr0:4;
@@ -238,16 +257,6 @@ struct avrcp_header {
} __attribute__ ((packed));
#define AVRCP_HEADER_LENGTH 3

-struct avrcp_spec_avc_pdu {
- 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_SPECAVCPDU_HEADER_LENGTH 7
-
#elif __BYTE_ORDER == __BIG_ENDIAN

struct avctp_header {
@@ -259,6 +268,16 @@ struct avctp_header {
} __attribute__ ((packed));
#define AVCTP_HEADER_LENGTH 3

+struct avctp_header_first {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t cr:1;
+ uint8_t ipid:1;
+ uint8_t packets;
+ uint16_t pid;
+} __attribute__ ((packed));
+#define AVCTP_HEADER_FIRST_LENGTH 4
+
struct avrcp_header {
uint8_t _hdr0:4;
uint8_t ctype:4;
@@ -268,16 +287,6 @@ struct avrcp_header {
} __attribute__ ((packed));
#define AVRCP_HEADER_LENGTH 3

-struct avrcp_spec_avc_pdu {
- 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_SPECAVCPDU_HEADER_LENGTH 7
-
#else
#error "Unknown byte order"
#endif
@@ -312,7 +321,7 @@ struct avrcp_passthru {
struct avrcp_header hdr;
uint8_t key:7;
uint8_t key_state:1;
- uint8_t op_len; /* =length(company_id+op_data) */
+ uint8_t op_len; /* =length(company_id+op_data) */
uint8_t company_id[3];
uint8_t op_data[];
} __attribute__ ((packed));
@@ -355,7 +364,7 @@ struct avrcp_passthru {
struct avrcp_header hdr;
uint8_t key_state:1;
uint8_t key:7;
- uint8_t op_len; /* =length(company_id+op_data) */
+ uint8_t op_len; /* =length(company_id+op_data) */
uint8_t company_id[3];
uint8_t op_data[];
} __attribute__ ((packed));
@@ -387,17 +396,83 @@ struct avctp_server {
uint32_t ct_record_id;
};

+#define META_ATTR_TITLE 1
+#define META_ATTR_ARTIST 2
+#define META_ATTR_ALBUM 3
+#define META_ATTR_TRKNUM 4
+#define META_ATTR_TTLTRK 5
+#define META_ATTR_GENRE 6
+#define META_ATTR_TIME 7
+#define META_ATTR_MAX 7
+
+struct meta_elem {
+ uint8_t *elem;
+ uint16_t elem_len;
+ uint16_t charset;
+};
+
+struct meta_elem_hdr {
+ uint32_t attr_id;
+ uint16_t charset;
+ uint16_t attr_len;
+ uint8_t value[];
+} __attribute__ ((packed));
+
struct control {
struct audio_device *dev;

avctp_state_t state;
+/*
+ * The following transaction counter is incremented when sending
+ * AVCTP messages
+ */
+ uint8_t transaction;
+/*
+ * Store role of this unit in conversation with other object
+ */
+ uint8_t role; /* property (r/w) */
+/*
+ * Version of AVRCP Supported
+ */
+ uint16_t version; /* property (rw) */
+/*
+ * Buffer allocated for receiving AVCTP packets for Commands
+ * and Responses.
+ */
+ uint8_t *recv_buf;
+ int recv_buf_size;
+ int recv_buf_pos;
+ int recv_pkts;
+ int recv_trans_id;
+
+/* Capabilities: Company IDs and Events Supported/Subscribed */
+
+ uint32_t cap_company_ids[16]; /* property (rw) */
+ uint8_t cap_events[16]; /* property (rw) */
+ uint8_t sub_events[16];
+
+/* Track Metadata */
+
+ uint64_t trk_uuid;
+ struct meta_elem trk_elem[META_ATTR_MAX+1];
+
+/* Play Status */
+
+ uint8_t play_state;
+ uint32_t play_pos;
+ uint32_t play_length;
+ uint32_t play_intvl;
+
+/* CT attributes */
+ uint8_t ct_battery;
+ uint16_t ct_charset;

int uinput;

GIOChannel *io;
- guint io_id;
+ guint io_id;

- uint16_t mtu;
+ uint16_t mtu; /* property (ro) */

gboolean target;

@@ -496,7 +571,7 @@ static sdp_record_t *avrcp_tg_record(void)
sdp_record_t *record;
sdp_data_t *psm, *version, *features;
uint16_t lp = AVCTP_PSM;
- uint16_t avrcp_ver = 0x0100, avctp_ver = 0x0103, feat = 0x000f;
+ uint16_t avrcp_ver = 0x0103, avctp_ver = 0x0103, feat = 0x000f;

record = sdp_record_alloc();
if (!record)
@@ -552,6 +627,21 @@ static sdp_record_t *avrcp_tg_record(void)
}

/**
+ * @brief set_company_id: Three-byte Company_ID to AVRCP message
+ *
+ * AVRCP uses three-byte company_ids, which must be moved from
+ * internal form to BT/network big-endian order.
+ */
+static void set_company_id(unsigned char *cid, int company_id)
+{
+ int cid_idx;
+ for (cid_idx = 2; cid_idx >= 0; --cid_idx) {
+ *(cid+cid_idx) = company_id & 0xFF;
+ company_id >>= 8;
+ }
+}
+
+/**
* @brief get_company_id: Three-byte Company_ID from AVRCP message
*
* AVRCP uses three-byte company_ids, which must be converted from
@@ -590,12 +680,12 @@ static void send_key(int fd, uint16_t key, int pressed)
}

/**
- * @brief: handle_panel_passthrough: Handles AVRCP 1.0+ PASSTHROUGH command.
+ * @brief: handle_panel_passthrough: Handles AVRCP 1.0+ PASSTHROUGH.
*
* Original version only passed the keystroke to uinput.
*
- * Added a Passthrough signal, with the key state AND the optional
- * following company_id and vendor-unique message.
+ * Added a Passthrough signal, with the key state and optional
+ * company_id and vendor-unique message.
*/

static void handle_panel_passthrough(struct control *control,
@@ -698,15 +788,6 @@ static void handle_panel_passthrough(struct control
*control,
DBG("AVRCP: unknown button 0x%02X %s", avrcp->key, status);
}

-/* handle vendordep pdu inside an avctp packet */
-static int handle_vendordep_pdu(struct control *control,
- struct avrcp_header *avrcp,
- int operand_count)
-{
- avrcp->ctype = CTYPE_NOT_IMPLEMENTED;
- return AVRCP_HEADER_LENGTH;
-}
-
static void avctp_disconnected(struct audio_device *dev)
{
struct control *control = dev->control;
@@ -762,8 +843,8 @@ static void avctp_set_state(struct control *control,
avctp_state_t new_state)
AUDIO_CONTROL_INTERFACE,
"Disconnected", DBUS_TYPE_INVALID);
emit_property_changed(dev->conn, dev->path,
- AUDIO_CONTROL_INTERFACE, "Connected",
- DBUS_TYPE_BOOLEAN, &value);
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_BOOLEAN, &value);

if (!audio_device_is_active(dev, NULL))
audio_device_set_authorized(dev, FALSE);
@@ -779,8 +860,8 @@ static void avctp_set_state(struct control *control,
avctp_state_t new_state)
AUDIO_CONTROL_INTERFACE, "Connected",
DBUS_TYPE_INVALID);
emit_property_changed(control->dev->conn, control->dev->path,
- AUDIO_CONTROL_INTERFACE, "Connected",
- DBUS_TYPE_BOOLEAN, &value);
+ AUDIO_CONTROL_INTERFACE, "Connected",
+ DBUS_TYPE_BOOLEAN, &value);
break;
default:
error("Invalid AVCTP state %d", new_state);
@@ -795,109 +876,1144 @@ static void avctp_set_state(struct control *control,
avctp_state_t new_state)
}
}

-static gboolean control_cb(GIOChannel *chan, GIOCondition cond,
+/**
+ * @brief avrcp_write: Called to transmit AVRCP command or response.
+ *
+ * This function writes/sends the AVRCP message created from various
+ * functions, wrapping it in one or more AVCTP messages.
+ *
+ * If a message is too long to be "written" in a single packet, message
+ * is broken into multiple packets and sent a packet at a time.
+ *
+ * For efficiency, the AVCTP header for each packet is constructed
+ * separately, and prepended to the avrcp message, or its fragments,
+ * using writev.
+ *
+ * For the AV/C control channel, AVRCP messages must not be larger
+ * than 512 bytes. However, unless metadata is ridiculously large,
+ * this limit should never be hit. Therefore, there really is no
+ * need for AVRCP-level fragmentation.
+ */
+
+static int avrcp_write(struct control *control,
+ struct avrcp_header *avrcp, int avrcp_length)
+{
+ struct iovec buffers[2];
+ uint8_t avctp_buff[4]; /* struct avctp_header_first */
+ int socket = g_io_channel_unix_get_fd(control->io);
+ int write_length;
+
+ memset(&avctp_buff, 0, sizeof(avctp_buff));
+
+ DBG("AVRCP Write ctype %u, %i bytes. AVCTP MTU: %i", avrcp->ctype,
+ avrcp_length, control->mtu);
+
+ if (avrcp_length+sizeof(struct avctp_header) <= control->mtu) {
+ struct avctp_header *avctp =
+ (struct avctp_header *) &avctp_buff;
+ avctp->ipid = 0;
+ if (avrcp->ctype >= CTYPE_NOT_IMPLEMENTED)
+ avctp->cr = AVCTP_RESPONSE;
+ else
+ avctp->cr = AVCTP_COMMAND;
+
+ avctp->transaction = ++(control->transaction);
+ avctp->packet_type = AVCTP_PACKET_SINGLE;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ buffers[0].iov_base = (void *) avctp;
+ buffers[0].iov_len = sizeof(*avctp);
+ buffers[1].iov_base = (void *) avrcp;
+ buffers[1].iov_len = avrcp_length;
+
+ write_length = writev(socket, buffers, 2);
+ if (write_length < 0)
+ return -errno;
+
+ DBG("AVRCP wrote %i bytes.", write_length);
+
+ return write_length;
+/*
+ * Fragment AVRCP messages longer than AVCTP MTU
+ */
+ } else {
+ int packet_length = 0;
+ int avrcp_offset = 0;
+ int avctp_packets = 0;
+ struct avctp_header_first *avctp =
+ (struct avctp_header_first *) &avctp_buff;
+
+ /* Count number of MTU-sized fragments */
+
+ DBG("AVRCP fragmented write, %u bytes.", avrcp_length);
+ avctp_packets = 1;
+ avrcp_offset = avrcp_length - control->mtu +
+ sizeof(struct avctp_header_first);
+ while (avrcp_offset > 0) {
+ ++avctp_packets;
+ avrcp_offset -= (control->mtu - 1);
+ }
+
+ /* Send first fragment */
+
+ avctp->packets = avctp_packets;
+ avctp->ipid = 0;
+ if (avrcp->ctype >= CTYPE_NOT_IMPLEMENTED) {
+ avctp->cr = AVCTP_RESPONSE;
+ } else {
+ avctp->cr = AVCTP_COMMAND;
+ control->transaction++;
+ }
+ avctp->transaction = control->transaction;
+ avctp->packet_type = AVCTP_PACKET_FIRST;
+ avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ buffers[0].iov_base = (void *) avctp;
+ buffers[0].iov_len = sizeof(*avctp);
+ buffers[1].iov_base = (void *) avrcp;
+ buffers[1].iov_len = control->mtu-sizeof(*avctp);
+
+ packet_length = writev(socket, buffers, 2);
+ if (packet_length < 0)
+ return -errno;
+ avrcp_offset = buffers[1].iov_len;
+
+ /* Send second to n-1 fragments */
+
+ while ((avrcp_length-avrcp_offset) > (control->mtu-1)) {
+ avctp->packet_type = AVCTP_PACKET_CONTINUE;
+ buffers[0].iov_base = (void *) avctp;
+ buffers[0].iov_len = 1;
+ buffers[1].iov_base = (void *) avrcp+avrcp_offset;
+ buffers[1].iov_len = control->mtu - 1;
+
+ packet_length = writev(socket, buffers, 2);
+ if (packet_length < 0)
+ return -errno;
+ avrcp_offset += buffers[1].iov_len;
+ }
+
+ /* Send final fragment */
+
+ avctp->packet_type = AVCTP_PACKET_END;
+ buffers[0].iov_base = (void *) avctp;
+ buffers[0].iov_len = 1;
+ buffers[1].iov_base = (void *) avrcp + avrcp_offset;
+ buffers[1].iov_len = avrcp_length - avrcp_offset;
+
+ packet_length = writev(socket, buffers, 2);
+ if (packet_length < 0)
+ return -errno;
+ write_length = avrcp_offset + buffers[1].iov_len;
+ }
+ return write_length;
+}
+
+/**
+ * @brief register_notification
+ *
+ * Confirm CT's notification request in response to the
+ * Register Notification subscription request from the CT.
+ */
+static int register_notification(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length,
+ uint8_t event_id)
+{
+ struct avrcp_vendor_dep *avrcp_resp;
+ int avrcp_resp_len = sizeof(struct avrcp_vendor_dep) + 11;
+ int write_length = 0;
+
+ avrcp_resp =
+ (struct avrcp_vendor_dep *) g_malloc0(avrcp_resp_len);
+
+ avrcp_resp->hdr.ctype = CTYPE_INTERIM;
+ avrcp_resp->hdr.subunit_id = avrcp->hdr.subunit_id;
+ avrcp_resp->hdr.subunit_type = avrcp->hdr.subunit_type;
+ avrcp_resp->hdr.opcode = avrcp->hdr.opcode;
+ set_company_id((uint8_t *)&avrcp_resp->company_id, BTSIG_COMPANY_ID);
+
+ avrcp_resp->pdu_id = avrcp->pdu_id;
+ avrcp_resp->packet_type = AVCTP_PACKET_SINGLE;
+ avrcp_resp->pdu_data[0] = event_id;
+ avrcp_resp_len = 1;
+
+ switch (event_id) {
+ case EVENT_PLAYBACK_STATUS_CHANGED: {
+ avrcp_resp->pdu_data[1] = control->play_state;
+ avrcp_resp_len++;
+ break;
+ }
+ case EVENT_TRACK_CHANGED: {
+ struct {
+ uint32_t hi;
+ uint32_t lo;
+ } track_id;
+ track_id.hi = htonl(control->trk_uuid >> 32);
+ track_id.lo = htonl(control->trk_uuid & 0xFFFFFFFF);
+ memcpy((void *) &avrcp_resp->pdu_data[1],
+ (void *) &track_id, 8);
+ avrcp_resp_len += 8;
+ break;
+ }
+ case EVENT_TRACK_REACHED_END: {
+ break;
+ }
+ case EVENT_TRACK_REACHED_START: {
+ break;
+ }
+ case EVENT_PLAYBACK_POS_CHANGED: {
+ /*
+ * Registering Event Track Position Changed:
+ * capture and save the notification interval (in seconds).
+ */
+ uint32_t interval;
+ memcpy((void *) &interval,
+ (void *) &avrcp->pdu_data[1], 4);
+ control->play_intvl = ntohl(interval);
+
+ interval = htonl(control->play_pos);
+ memcpy((void *) &avrcp->pdu_data[1],
+ (void *) &interval, 4);
+ avrcp_resp_len += 4;
+ break;
+ }
+ case EVENT_BATT_STATUS_CHANGED: {
+ avrcp_resp->pdu_data[1] = control->ct_battery;
+ avrcp_resp_len++;
+ break;
+ }
+ case EVENT_SYSTEM_STATUS_CHANGED: {
+ avrcp_resp->pdu_data[1] = 0;
+ avrcp_resp_len++;
+ break;
+ }
+ case EVENT_PLAYER_APP_SETTING_CHANGED: {
+ avrcp_resp->pdu_data[1] = 0;
+ avrcp_resp_len++;
+ break;
+ }
+ default:
+ break;
+ }
+ avrcp_resp->length = htons(avrcp_resp_len);
+ avrcp_resp_len += sizeof(struct avrcp_vendor_dep);
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp_resp,
+ avrcp_resp_len);
+
+ control->sub_events[event_id] = TRUE;
+
+ g_free(avrcp_resp);
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_get_companies
+ *
+ * PDU_GET_CAPABILITIES to get list of Company_IDs supported.
+ */
+static int handle_pdu_get_companies(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ struct avrcp_vendor_dep *avrcp_resp;
+ int i;
+ int write_length;
+ int avrcp_resp_len =
+ sizeof(struct avrcp_vendor_dep) + 2;
+ int company_id_count = 0;
+ while (control->cap_company_ids[company_id_count] > 0) {
+ avrcp_resp_len += 3;
+ company_id_count++;
+ }
+
+ avrcp_resp = (struct avrcp_vendor_dep *) g_malloc0(avrcp_resp_len);
+
+ avrcp_resp->hdr.ctype = CTYPE_STABLE;
+ avrcp_resp->hdr.subunit_id = avrcp->hdr.subunit_id;
+ avrcp_resp->hdr.subunit_type = avrcp->hdr.subunit_type;
+ avrcp_resp->hdr.opcode = avrcp->hdr.opcode;
+ set_company_id((uint8_t *) &avrcp_resp->company_id, BTSIG_COMPANY_ID);
+
+ avrcp_resp->pdu_id = avrcp->pdu_id;
+ avrcp_resp->packet_type = AVCTP_PACKET_SINGLE;
+ avrcp_resp->length = htons(avrcp_resp_len -
+ sizeof(struct avrcp_vendor_dep));
+ avrcp_resp->pdu_data[0] = 0x02;
+ avrcp_resp->pdu_data[1] = company_id_count;
+
+ for (i = 0; i < company_id_count; ++i) {
+ set_company_id(avrcp_resp->pdu_data + 2 + 3 * i,
+ control->cap_company_ids[i]);
+ };
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp_resp,
+ avrcp_resp_len);
+ g_free(avrcp_resp);
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_get_events
+ *
+ * PDU_GET_CAPABILITIES to get list of Notification Events supported.
+ */
+static int handle_pdu_get_events(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ struct avrcp_vendor_dep *avrcp_resp;
+ int write_length;
+ int i = 0, i_out = 0;
+ int event_count = 0;
+ int avrcp_resp_len;
+
+ for (i = EVENT_PLAYBACK_STATUS_CHANGED;
+ i <= 15; ++i) {
+ if (control->cap_events[i] > 0) {
+ avrcp_resp_len++;
+ event_count++;
+ }
+ }
+
+ avrcp_resp_len =
+ sizeof(struct avrcp_vendor_dep) + 2 +
+ (event_count);
+ avrcp_resp =
+ (struct avrcp_vendor_dep *)
+ g_malloc0(avrcp_resp_len);
+
+ avrcp_resp->hdr.ctype = CTYPE_STABLE;
+ avrcp_resp->hdr.subunit_id =
+ avrcp->hdr.subunit_id;
+ avrcp_resp->hdr.subunit_type =
+ avrcp->hdr.subunit_type;
+ avrcp_resp->hdr.opcode = avrcp->hdr.opcode;
+ set_company_id(
+ (uint8_t *)&avrcp_resp->company_id,
+ BTSIG_COMPANY_ID);
+
+ avrcp_resp->pdu_id = avrcp->pdu_id;
+ avrcp_resp->packet_type = AVCTP_PACKET_SINGLE;
+ avrcp_resp->length =
+ htons(avrcp_resp_len -
+ sizeof(struct avrcp_vendor_dep));
+ avrcp_resp->pdu_data[0] = 0x03;
+ avrcp_resp->pdu_data[1] = event_count;
+
+ for (i = EVENT_PLAYBACK_STATUS_CHANGED;
+ i <= 15; ++i) {
+ if (control->cap_events[i] > 0) {
+ avrcp_resp->pdu_data[2 + i_out] = i;
+ i_out++;
+ }
+ }
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp_resp,
+ avrcp_resp_len);
+ g_free(avrcp_resp);
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_reg_notify
+ *
+ * PDU_REGISTER_NOTIFICATION to register CT for asynchronous notifications.
+ */
+static int handle_pdu_reg_notify(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ int write_length;
+
+ if (avrcp->hdr.ctype != CTYPE_NOTIFY) {
+ DBG("RegisterNotification: expected "
+ "CTYPE_NOTIFY, received: 0x%02X",
+ avrcp->hdr.ctype);
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header *));
+ }
+
+ if (!control->cap_events[avrcp->pdu_data[0]]) {
+ DBG("RegisterNotification: requested "
+ "event 0x%02X not supported",
+ avrcp->pdu_data[0]);
+ avrcp->hdr.ctype = CTYPE_NOT_IMPLEMENTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header *));
+ }
+
+ /* Add current Event to Subscribed Event list. */
+
+ write_length = register_notification(control, avrcp, avrcp_length,
+ avrcp->pdu_data[0]);
+ if (write_length < 0)
+ return write_length;
+
+ if (avrcp->pdu_data[0] ==
+ EVENT_PLAYBACK_POS_CHANGED) {
+ emit_property_changed(control->dev->conn,
+ control->dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ "PlayInterval",
+ DBUS_TYPE_UINT32,
+ &control->play_intvl);
+ }
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_get_attrs
+ *
+ * PDU_GET_ELEMENT_ATTRS to respond with Metadata to CT.
+ */
+static int handle_pdu_get_attrs(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ struct avrcp_vendor_dep *avrcp_resp;
+ int i = 0;
+ uint8_t *pelem;
+ int write_length;
+ int elem_length = 0, avrcp_resp_len = 0;
+
+ if (avrcp->hdr.ctype != CTYPE_STATUS) {
+ DBG("GetElementAttrs: expected CTYPE_STATUS, "
+ "received: 0x%02X", avrcp->hdr.ctype);
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header));
+ }
+
+ /* Parse the list of requested elements,
+ * calculate the total size of the metadata. */
+
+ DBG("GetElementAttrs received, %d requested",
+ avrcp->pdu_data[8]);
+ for (i = 0; i < avrcp->pdu_data[8]; ++i) {
+ uint32_t elem_id;
+
+ memcpy(&elem_id, &(avrcp->pdu_data[9 + 4 * i]), 4);
+ elem_id = ntohl(elem_id);
+ DBG("GetElementAttr %d requested", elem_id);
+ if (elem_id > 0 && elem_id <= META_ATTR_MAX) {
+ if (control->trk_elem[elem_id].elem) {
+ elem_length +=
+ sizeof(struct meta_elem_hdr) +
+ control->trk_elem[elem_id].elem_len;
+ DBG("GetElementAttr %d, %d bytes", elem_id,
+ control->trk_elem[elem_id].elem_len);
+ } else {
+ elem_length += sizeof(struct meta_elem_hdr);
+ DBG("GetElementAttr %d: no data", elem_id);
+ }
+ } else {
+ /* Reject the GetElements call as invalid */
+ }
+ }
+ avrcp_resp_len = elem_length + sizeof(struct avrcp_vendor_dep) + 1;
+ DBG("GetElementAttr %d bytes, total %d bytes", elem_length,
+ avrcp_resp_len);
+
+ /* Allocate a buffer for the metadata, plus the AVRCP header */
+
+ avrcp_resp = (struct avrcp_vendor_dep *) g_malloc0(avrcp_resp_len);
+
+ avrcp_resp->hdr.ctype = CTYPE_STABLE;
+ avrcp_resp->hdr.subunit_id = avrcp->hdr.subunit_id;
+ avrcp_resp->hdr.subunit_type = avrcp->hdr.subunit_type;
+ avrcp_resp->hdr.opcode = avrcp->hdr.opcode;
+ set_company_id((uint8_t *)&avrcp_resp->company_id, BTSIG_COMPANY_ID);
+ avrcp_resp->pdu_id = avrcp->pdu_id;
+ avrcp_resp->length = htons(elem_length);
+ avrcp_resp->pdu_data[0] = avrcp->pdu_data[8];
+
+ /* Parse the list of requested elements,
+ * copy the metadata into the response */
+
+ pelem = (uint8_t *) avrcp_resp + sizeof(struct avrcp_vendor_dep) + 1;
+ for (i = 0; i < avrcp->pdu_data[8]; ++i) {
+ uint32_t elem_id;
+ struct meta_elem_hdr elem;
+
+ memcpy(&elem_id, &(avrcp->pdu_data[9 + 4 * i]), 4);
+ elem_id = ntohl(elem_id);
+
+ elem.attr_id = htonl(elem_id);
+ elem.attr_len = htons(control->trk_elem[elem_id].elem_len);
+ elem.charset = htons(control->trk_elem[elem_id].charset);
+
+ memcpy((void *) pelem, (void *) &elem,
+ sizeof(struct meta_elem_hdr));
+ pelem += sizeof(struct meta_elem_hdr);
+
+ memcpy((void *) pelem,
+ (void *) control->trk_elem[elem_id].elem,
+ control->trk_elem[elem_id].elem_len);
+ pelem += control->trk_elem[elem_id].elem_len;
+ }
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp_resp,
+ avrcp_resp_len);
+ g_free(avrcp_resp);
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_play_stat
+ *
+ * PDU_GET_PLAY_STATUS to respond with Track Status to CT.
+ */
+static int handle_pdu_play_stat(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ struct avrcp_vendor_dep *avrcp_resp;
+ int write_length;
+ int avrcp_resp_len = 0;
+
+ if (avrcp->hdr.ctype != CTYPE_STATUS) {
+ DBG("GetPlayStatus: expected CTYPE_STATUS, received: 0x%02X",
+ avrcp->hdr.ctype);
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header));
+ }
+
+ /* GetPlayStatus has no parameters. */
+
+ DBG("GetPlayStatus received");
+
+ g_dbus_emit_signal(control->dev->conn,
+ control->dev->path,
+ AUDIO_CONTROL_INTERFACE,
+ "GetPlayStatus",
+ DBUS_TYPE_INVALID);
+
+ /* Allocate a buffer for the response, including AVRCP header */
+
+ avrcp_resp_len = sizeof(struct avrcp_vendor_dep) + 9;
+ avrcp_resp = (struct avrcp_vendor_dep *) g_malloc0(avrcp_resp_len);
+
+ avrcp_resp->hdr.ctype = CTYPE_STABLE;
+ avrcp_resp->hdr.subunit_id = avrcp->hdr.subunit_id;
+ avrcp_resp->hdr.subunit_type = avrcp->hdr.subunit_type;
+ avrcp_resp->hdr.opcode = avrcp->hdr.opcode;
+ set_company_id((uint8_t *) &avrcp_resp->company_id, BTSIG_COMPANY_ID);
+ avrcp_resp->pdu_id = avrcp->pdu_id;
+ avrcp_resp->length = htons(9);
+ memcpy(&avrcp_resp->pdu_data[0], &control->play_length, 4);
+ memcpy(&avrcp_resp->pdu_data[4], &control->play_pos, 4);
+ avrcp_resp->pdu_data[8] = control->play_state;
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp_resp,
+ avrcp_resp_len);
+ g_free(avrcp_resp);
+ return write_length;
+}
+
+/**
+ * @brief handle_pdu_battery_stat
+ *
+ * PDU_INFORM_CT_BATTERY_STAT to receive battery state from CT.
+ */
+static int handle_pdu_battery_stat(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ if (avrcp->hdr.ctype != CTYPE_CONTROL) {
+ DBG("BatteryStatus: expected CTYPE_CONTROL, "
+ "received: 0x%02X", avrcp->hdr.ctype);
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control, (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header));
+ }
+
+ /* BatteryStatus has one parameter:
+ * store in control and emit property. */
+
+ DBG("BatteryStatus received");
+ if (control->ct_battery != avrcp->pdu_data[0]) {
+ control->ct_battery = avrcp->pdu_data[0];
+ emit_property_changed(control->dev->conn, control->dev->path,
+ AUDIO_CONTROL_INTERFACE, "Battery",
+ DBUS_TYPE_BYTE, &control->ct_battery);
+ }
+ /* Response is shorter...just reuse it. */
+
+ avrcp->hdr.ctype = CTYPE_STABLE;
+ avrcp->length = 0;
+
+ return avrcp_write(control, (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_vendor_dep));
+}
+
+/**
+ * @brief handle_avrcp_meta_cmd
+ *
+ * When this function is called, we already know we have a valid
+ * AVRCP Metadata command. From this point, we branch based on the PDU.
+ */
+static int handle_avrcp_meta_cmd(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+ switch (avrcp->pdu_id) {
+ case PDU_GET_CAPABILITIES:
+ if (avrcp->hdr.ctype != CTYPE_STATUS) {
+ DBG("GetCapabilities: expected CTYPE_STATUS, "
+ "received: 0x%02X", avrcp->hdr.ctype);
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ sizeof(struct avrcp_header *));
+ }
+ if (avrcp->pdu_data[0] == 0x02) {
+ return handle_pdu_get_companies(control,
+ avrcp, avrcp_length);
+ } else if (avrcp->pdu_data[0] == 0x03) {
+ return handle_pdu_get_events(control,
+ avrcp, avrcp_length);
+ }
+
+ /* No other capabilities supported in AVRCP spec */
+
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ avrcp_length);
+
+ case PDU_REGISTER_NOTIFICATION:
+ return handle_pdu_reg_notify(control, avrcp, avrcp_length);
+
+ case PDU_GET_ELEMENT_ATTRS:
+ return handle_pdu_get_attrs(control, avrcp, avrcp_length);
+
+ case PDU_GET_PLAY_STATUS:
+ return handle_pdu_play_stat(control, avrcp, avrcp_length);
+
+ case PDU_INFORM_CT_BATTERY_STAT:
+ return handle_pdu_battery_stat(control, avrcp, avrcp_length);
+
+ case PDU_INFORM_DISPLAYABLE_CHAR_SET:
+
+ case PDU_LIST_PLAYER_APP_SETTING_ATTRS:
+ case PDU_LIST_PLAYER_APP_SETTING_VALS:
+
+ case PDU_GET_CURR_PLAYER_APP_SETTING_VALUE:
+ case PDU_SET_PLAYER_APP_SETTING_VALUE:
+
+ avrcp->hdr.ctype = CTYPE_NOT_IMPLEMENTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+
+ /*
+ * Following cases are not implemented:
+ * Unless metadata is allowed to be unlimited, there is
+ * no case where an AVRCP packet will exceed AVC Control
+ * Channel MTU (512 bytes).
+ */
+ case PDU_REGISTER_CONTINUING_RESPONSE:
+ case PDU_ABORT_CONTINUING_RESPONSE:
+ avrcp->hdr.ctype = CTYPE_NOT_IMPLEMENTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+ /*
+ * The following are all AVRCP 1.4+ PDUs.
+ */
+ case PDU_GET_PLAYER_APP_SETTING_ATTR_TEXT:
+ case PDU_GET_PLAYER_APP_SETTING_VALUE_TEXT:
+ case PDU_SET_ABSOLUTE_VOLUME:
+ case PDU_SET_ADDRESSED_PLAYER:
+ case PDU_SET_BROWSED_PLAYER:
+ case PDU_GET_FOLDER_ITEMS:
+ case PDU_CHANGE_PATH:
+ case PDU_GET_ITEM_ATTRS:
+ case PDU_PLAY_ITEM:
+ case PDU_SEARCH:
+ case PDU_ADD_TO_NOW_PLAYING:
+ avrcp->hdr.ctype = CTYPE_NOT_IMPLEMENTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+
+ default: /* invalid PDU */
+ avrcp->hdr.ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+ }
+}
+
+/**
+ * @brief handle_avrcp_meta_resp
+ *
+ * When this function is called, we have a response
+ * to our previous AVRCP Metadata command. Branch based on the PDU.
+ *
+ * Only used with the BlueZ as CT case (unimplemented).
+ */
+static int handle_avrcp_meta_resp(struct control *control,
+ struct avrcp_vendor_dep *avrcp,
+ int avrcp_length)
+{
+/*
+ * Operating as TG, there is little reason for handling Metadata responses.
+ */
+ DBG("STUB: handle_avrcp_meta_resp");
+ return 0;
+}
+
+/**
+ * @brief handle_avrcp_message: Parse (assembled) AVRCP message.
+ *
+ * This function returns the AVRCP message, which is usually contained in
+ * a single AVCTP packet.
+ *
+ * If multiple AVCTP packets are needed to transmit a single AVRCP message
+ * (very short MTU), the packets are reassembled in the lower layer and
+ * presented to this layer as a whole message.
+ */
+static int handle_avrcp_message(struct control *control,
+ struct avrcp_header *avrcp, int avrcp_length, int trans_id) {
+
+ DBG("AVRCP %p, length %d, transid %u", avrcp, avrcp_length, trans_id);
+/*
+ * There are two fundamental cases, AVRCP messages received as Commands,
+ * and Responses to messages sent from this system.
+ */
+ if (avrcp->ctype < CTYPE_NOT_IMPLEMENTED) {
+ DBG("AVRCP COMMAND 0x%01X, subunit_type 0x%02X, "
+ "subunit_id 0x%01X, opcode 0x%02X, length %d",
+ avrcp->ctype, avrcp->subunit_type, avrcp->subunit_id,
+ avrcp->opcode, avrcp_length);
+
+ switch (avrcp->opcode) {
+ case OP_UNITINFO: {
+ struct avrcp_unitinfo *uin =
+ (struct avrcp_unitinfo *) avrcp;
+ if (avrcp->ctype != CTYPE_STATUS) {
+ error("OP_UNITINFO received, ctype expected: "
+ "0x%02X, actual: 0x%02X",
+ CTYPE_STATUS, avrcp->ctype);
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ avrcp, avrcp_length);
+ }
+
+ uin->ext_code = 0x07;
+ uin->unit_type = SUBUNIT_PANEL;
+ uin->unit = 0;
+ set_company_id((uint8_t *) &uin->company_id,
+ BTSIG_COMPANY_ID);
+
+ avrcp->ctype = CTYPE_STABLE;
+ return avrcp_write(control, avrcp,
+ sizeof(struct avrcp_unitinfo));
+ }
+ case OP_SUBUNITINFO: {
+ struct avrcp_subunitinfo *suin =
+ (struct avrcp_subunitinfo *) avrcp;
+ if (avrcp->ctype != CTYPE_STATUS) {
+ error("OP_SUBUNITINFO received, ctype "
+ "expected: 0x%02X, actual: 0x%02X",
+ CTYPE_STATUS, avrcp->ctype);
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ avrcp, avrcp_length);
+ }
+
+ if (suin->page == 0) {
+ suin->subunit[0].subunit_type = SUBUNIT_PANEL;
+ suin->subunit[0].subunit_id_max = 0;
+ };
+ avrcp->ctype = CTYPE_STABLE;
+ return avrcp_write(control, avrcp,
+ sizeof(struct avrcp_subunitinfo));
+ }
+ case OP_PASSTHROUGH: {
+ struct avrcp_passthru *pass =
+ (struct avrcp_passthru *) avrcp;
+ if (avrcp->ctype != CTYPE_CONTROL) {
+ error("OP_PASSTHROUGH received, ctype "
+ "expected: 0x%02x, actual: 0x%02X",
+ CTYPE_CONTROL, avrcp->ctype);
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ avrcp, avrcp_length);
+ }
+ if (avrcp->subunit_type != SUBUNIT_PANEL) {
+ error("OP_PASSTHROUGH received, not directed "
+ "to PANEL: 0x%02X",
+ avrcp->subunit_type);
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ avrcp, avrcp_length);
+ }
+
+ handle_panel_passthrough(control,
+ (struct avrcp_passthru *) avrcp,
+ avrcp_length);
+
+ avrcp->ctype = CTYPE_ACCEPTED;
+ pass->op_len = 0;
+ return avrcp_write(control, avrcp,
+ offsetof(struct avrcp_passthru, company_id));
+ }
+ case OP_VENDOR_DEPENDENT: {
+ struct avrcp_vendor_dep *vend =
+ (struct avrcp_vendor_dep *) avrcp;
+ DBG("OP_VENDOR_DEPENDENT received, ctype 0x%02X",
+ avrcp->ctype);
+
+ /* If Version supported is not 1.3 or greater,
+ * reject this call. */
+
+ if (get_company_id((uint8_t *) &vend->company_id) ==
+ BTSIG_COMPANY_ID) {
+ if (control->version >= 0x0103) {
+ return handle_avrcp_meta_cmd(control,
+ vend,
+ avrcp_length);
+ } else {
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control,
+ avrcp, avrcp_length);
+ }
+ }
+
+ /* FIXME: If not a standard Metadata VD message,
+ * need to pass to the App as is via signal. */
+
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control, avrcp, avrcp_length);
+ }
+ default:
+ DBG("Unknown opcode received: 0x%02X, ctype: 0x%02X",
+ avrcp->opcode, avrcp->ctype);
+ avrcp->ctype = CTYPE_REJECTED;
+ return avrcp_write(control, avrcp, avrcp_length);
+ }
+
+ /* Handle AVRCP message Responses to our Command messages */
+
+ } else {
+ DBG("AVRCP RESPONSE 0x%01X, subunit_type 0x%02X, "
+ "subunit_id 0x%01X, opcode 0x%02X, length %d",
+ avrcp->ctype, avrcp->subunit_type, avrcp->subunit_id,
+ avrcp->opcode, avrcp_length);
+
+ switch (avrcp->opcode) {
+ case OP_UNITINFO:
+ if (avrcp->ctype != CTYPE_STABLE)
+ error("OP_UNITINFO response, ctype expected: "
+ "0x%02X, actual: 0x%02X",
+ CTYPE_STABLE, avrcp->ctype);
+ break;
+
+ case OP_SUBUNITINFO:
+ if (avrcp->ctype != CTYPE_STABLE)
+ error("OP_SUBUNITINFO response, ctype "
+ "expected: 0x%02X, actual: 0x%02X",
+ CTYPE_STABLE, avrcp->ctype);
+ break;
+
+ case OP_PASSTHROUGH:
+ if (avrcp->ctype != CTYPE_ACCEPTED)
+ error("OP_PASSTHROUGH response, ctype "
+ "expected: 0x%02x, actual: 0x%02X",
+ CTYPE_ACCEPTED, avrcp->ctype);
+
+ if (avrcp->subunit_type != SUBUNIT_PANEL)
+ error("OP_PASSTHROUGH response, not directed "
+ "to PANEL: 0x%02X",
+ avrcp->subunit_type);
+ break;
+
+ case OP_VENDOR_DEPENDENT: {
+ struct avrcp_vendor_dep *vend =
+ (struct avrcp_vendor_dep *) avrcp;
+ DBG("OP_VENDOR_DEPENDENT response, ctype 0x%02x",
+ avrcp->ctype);
+ /*
+ * FIXME: If Version supported is not 1.3 or greater,
+ * reject this call.
+ */
+ if (get_company_id((uint8_t *) &vend->company_id) ==
+ BTSIG_COMPANY_ID)
+ return handle_avrcp_meta_resp(control,
+ vend,
+ avrcp_length);
+ /*
+ * FIXME: If not a standard BT-SIG Metadata VD
+ * message, pass to the App as-is via a TBD signal.
+ */
+ break;
+ }
+
+ default:
+ error("Unknown opcode response: 0x%02X, ctype: "
+ "0x%02X", avrcp->opcode,
+ avrcp->ctype);
+ return -1;
+ }
+ return 0;
+ }
+ return -1;
+}
+
+/**
+ * @brief avctp_packet_failed: Support function for avctp_packet_cb
+ *
+ * Called to deal with unrecoverable receive/send errors when receiving
+ * and responding to received AVCTP packets.
+ */
+static gboolean avctp_packet_failed(struct control *control)
+{
+ DBG("AVCTP session %p got disconnected", control);
+ avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
+ return FALSE;
+}
+
+/**
+ * @brief avctp_packet_reject: Support function for avctp_packet_cb
+ *
+ * Called when rejecting an invalid AVCTP packet, primarily with an
+ * PID other than AV_REMOTE_SVCLASS_ID.
+ */
+static gboolean avctp_packet_reject(struct control *control,
+ struct avctp_header *avctp, int packet_size)
+{
+ int ret;
+ int socket = g_io_channel_unix_get_fd(control->io);
+
+ ret = write(socket, (uint8_t *) avctp, packet_size);
+ if (ret != packet_size)
+ return avctp_packet_failed(control);
+
+ return TRUE;
+}
+
+/**
+ * @brief avctp_packet_cb: Message received from "other" device.
+ *
+ * This function returns the AVCTP message, (soon) reassembles any
+ * fragmented packets, then peels out the AVRCP message and presents
+ * it to avrcp_message_cb.
+ *
+ * This layering of received message handling should prove easier
+ * to understand and debug (for future maintainers) than handling
+ * all in the same callback function.
+ */
+static gboolean avctp_packet_cb(GIOChannel *chan, GIOCondition cond,
gpointer data)
{
struct control *control = data;
- unsigned char buf[1024], *operands;
+ struct iovec buffers[2];
+ uint8_t buf[1024];
struct avctp_header *avctp;
struct avrcp_header *avrcp;
- int ret, packet_size, operand_count, sock;
+ int ret, packet_size, socket;

if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
- goto failed;
+ return avctp_packet_failed(control);
+
+ socket = g_io_channel_unix_get_fd(control->io);
+ avctp = (struct avctp_header *) buf;
+
+ /*
+ * If not fragmented
+ */
+ if (!control->recv_buf) {
+ ret = read(socket, buf, sizeof(buf));
+ if (ret <= 0)
+ return avctp_packet_failed(control);
+ } else {

- sock = g_io_channel_unix_get_fd(control->io);
+ buffers[0].iov_base = (void *) avctp;
+ buffers[0].iov_len = 1;
+ buffers[1].iov_base = (void *) control->recv_buf +
+ control->recv_buf_pos;
+ buffers[1].iov_len = control->mtu - 1;

- ret = read(sock, buf, sizeof(buf));
- if (ret <= 0)
- goto failed;
+ ret = readv(socket, buffers, 2);
+ if (ret <= 0)
+ return avctp_packet_failed(control);
+ }

DBG("Got %d bytes of data for AVCTP session %p", ret, control);

if ((unsigned int) ret < sizeof(struct avctp_header)) {
- error("Too small AVCTP packet");
- goto failed;
+ error("AVCTP packet too small");
+ return avctp_packet_failed(control);
}

packet_size = ret;

- avctp = (struct avctp_header *) buf;
+ DBG("AVCTP %p, transaction %u, packet type %u, C/R %u, IPID %u, "
+ "PID 0x%04X", avctp,
+ avctp->transaction, avctp->packet_type,
+ avctp->cr, avctp->ipid, ntohs(avctp->pid));
+
+ if (avctp->packet_type == AVCTP_PACKET_SINGLE) {
+ if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+ avctp->ipid = 1;
+ avctp->cr = AVCTP_RESPONSE;
+ packet_size = sizeof(struct avctp_header);
+ return avctp_packet_reject(control,
+ avctp, packet_size);
+ }

- DBG("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, "
+ if ((packet_size-sizeof(struct avctp_header)) <
+ sizeof(struct avrcp_header)) {
+ error("AVCTP single packet too small for "
+ "AVRCP header");
+ return avctp_packet_failed(control);
+ }
+ avrcp = (struct avrcp_header *) ((void *) avctp +
+ sizeof(struct avctp_header));
+ DBG("AVRCP %p: ctype %u, subunit id %u, subunit type %u, "
+ "opcode %u",
+ avrcp, avrcp->ctype, avrcp->subunit_id,
+ avrcp->subunit_type, avrcp->opcode);
+ } else if (avctp->packet_type == AVCTP_PACKET_FIRST) {
+ struct avctp_header_first *avctpf =
+ (struct avctp_header_first *) avctp;
+ if (avctpf->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+ avctpf->ipid = 1;
+ avctpf->cr = AVCTP_RESPONSE;
+ packet_size = sizeof(struct avctp_header_first);
+ return avctp_packet_reject(control,
+ avctp, packet_size);
+ }
+ if ((packet_size-sizeof(struct avctp_header_first)) <
+ sizeof(struct avrcp_header)) {
+ error("AVCTP first packet too small for AVRCP header");
+ return avctp_packet_failed(control);
+ }
+ avrcp = (struct avrcp_header *) ((void *) avctpf +
+ sizeof(struct avctp_header_first));
+ DBG("AVRCP %p: ctype %u, subunit id %u, subunit type %u, "
+ "opcode %u",
+ avrcp, avrcp->ctype, avrcp->subunit_id,
+ avrcp->subunit_type, avrcp->opcode);
+ }
+
+ /* Make sure the AVCTP cr matches the AVRCP ctype */
+
+ if (avctp->packet_type == AVCTP_PACKET_SINGLE ||
+ avctp->packet_type == AVCTP_PACKET_FIRST) {
+ if (!avctp->cr && avrcp->ctype >= CTYPE_NOT_IMPLEMENTED) {
+ error("AVRCP Response code 0x%02X received "
+ "in AVCTP Command packet", avrcp->ctype);
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->ctype = CTYPE_REJECTED;
+ /* Fragmented Packet in Progress := FALSE */
+ packet_size = sizeof(struct avrcp_header);
+ return avctp_packet_reject(control,
+ avctp, packet_size);
+
+
+ } else if (avctp->cr && avrcp->ctype < CTYPE_NOT_IMPLEMENTED) {
+ error("AVRCP Command code 0x%02X received "
+ "in AVCTP Response packet", avrcp->ctype);
+ avctp->cr = AVCTP_RESPONSE;
+ avrcp->ctype = CTYPE_REJECTED;
+ packet_size = sizeof(struct avrcp_header);
+ /* Fragmented Packet in Progress := FALSE */
+ return avctp_packet_reject(control,
+ avctp, packet_size);
+ }
+ }
+
+ switch (avctp->packet_type) {
+ case AVCTP_PACKET_SINGLE: {
+ DBG("AVCTP transaction %u, pk type %u, C/R %u, IPID %u, "
"PID 0x%04X",
avctp->transaction, avctp->packet_type,
avctp->cr, avctp->ipid, ntohs(avctp->pid));
+ avrcp = (struct avrcp_header *)
+ (buf + sizeof(struct avctp_header));
+ packet_size -= sizeof(struct avctp_header);

- ret -= sizeof(struct avctp_header);
- if ((unsigned int) ret < sizeof(struct avrcp_header)) {
- error("Too small AVRCP packet");
- goto failed;
- }
+ /* If (Fragmented Packet in Progress),
+ * Single packet acceptable only if Passthrough */

- avrcp = (struct avrcp_header *) (buf + sizeof(struct avctp_header));
+ return handle_avrcp_message(control,
+ avrcp, packet_size,
+ avctp->transaction);
+ break;
+ }
+ case AVCTP_PACKET_FIRST: {
+ struct avctp_header_first *avctpf =
+ (struct avctp_header_first *) avctp;
+ DBG("AVCTP transaction %u, pk type %u, C/R %u, IPID %u, "
+ "PID 0x%04X, packets %d",
+ avctpf->transaction, avctpf->packet_type,
+ avctpf->cr, avctpf->ipid, ntohs(avctpf->pid),
+ avctpf->packets);
+ packet_size -= sizeof(struct avctp_header_first);
+ control->recv_trans_id = avctpf->transaction;
+ control->recv_pkts = avctpf->packets - 1;
+ control->recv_buf = g_malloc(control->recv_pkts * packet_size);
+ control->recv_buf_pos = 0;
+
+ /* Copy first part of AVRCP message to recv_buf */
+
+ memcpy(control->recv_buf,
+ buf + sizeof(struct avctp_header_first), packet_size);
+
+ /* Fragmented Packet in Progress := TRUE */
+
+ control->recv_buf_pos = packet_size;
+ break;
+ }
+ case AVCTP_PACKET_CONTINUE: {
+ /* IF NOT fragmented packet in progress, REJECT */

- operands = buf + sizeof(struct avctp_header) + sizeof(struct
avrcp_header);
- operand_count = ret - sizeof(struct avrcp_header);
+ /* IF received trans_ID does not match first, REJECT */

- DBG("AVRCP %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
- "opcode 0x%02X, %d operands",
- avctp->cr ? "response" : "command",
- avrcp->ctype, avrcp->subunit_type, avrcp->subunit_id,
- avrcp->opcode, operand_count);
-
- if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
- avctp->cr = AVCTP_RESPONSE;
- avrcp->ctype = CTYPE_NOT_IMPLEMENTED;
- } else if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
- avctp->ipid = 1;
- avctp->cr = AVCTP_RESPONSE;
- packet_size = sizeof(*avctp);
- } else if (avctp->cr == AVCTP_COMMAND &&
- avrcp->ctype == CTYPE_CONTROL &&
- avrcp->subunit_type == SUBUNIT_PANEL &&
- avrcp->opcode == OP_PASSTHROUGH) {
- handle_panel_passthrough(control,
- (struct avrcp_passthru *) avrcp, ret);
- avctp->cr = AVCTP_RESPONSE;
- avrcp->ctype = CTYPE_ACCEPTED;
- } else if (avctp->cr == AVCTP_COMMAND &&
- avrcp->ctype == CTYPE_STATUS &&
- (avrcp->opcode == OP_UNITINFO
- || avrcp->opcode == OP_SUBUNITINFO)) {
- avctp->cr = AVCTP_RESPONSE;
- avrcp->ctype = CTYPE_STABLE;
- /* The first operand should be 0x07 for the UNITINFO response.
- * Neither AVRCP (section 22.1, page 117) nor AVC Digital
- * Interface Command Set (section 9.2.1, page 45) specs
- * explain this value but both use it */
- if (operand_count >= 1 && avrcp->opcode == OP_UNITINFO)
- operands[0] = 0x07;
- if (operand_count >= 2)
- operands[1] = SUBUNIT_PANEL << 3;
- DBG("reply to %s", avrcp->opcode == OP_UNITINFO ?
- "OP_UNITINFO" : "OP_SUBUNITINFO");
- } else if (avctp->cr == AVCTP_COMMAND &&
- avrcp->opcode == OP_VENDORDEP) {
- int r_size;
- operand_count -= 3;
- avctp->cr = AVCTP_RESPONSE;
- r_size = handle_vendordep_pdu(control, avrcp, operand_count);
- packet_size = AVCTP_HEADER_LENGTH + r_size;
- } else {
- avctp->cr = AVCTP_RESPONSE;
- avrcp->ctype = CTYPE_REJECTED;
+ DBG("AVCTP transaction %u, pk type %u, C/R %u, IPID %u",
+ avctp->transaction, avctp->packet_type,
+ avctp->cr, avctp->ipid);
+ control->recv_pkts--;
+ control->recv_buf_pos = 0;
+ break;
}
- ret = write(sock, buf, packet_size);
- if (ret != packet_size)
- goto failed;
+ case AVCTP_PACKET_END: {
+ /* IF NOT fragmented packet in progress, REJECT */

+ DBG("AVCTP transaction %u, pk type %u, C/R %u, "
+ "IPID %u",
+ avctp->transaction, avctp->packet_type,
+ avctp->cr, avctp->ipid);
+ avrcp = (struct avrcp_header *) control->recv_buf;
+ control->recv_pkts--;
+ control->recv_buf_size =
+ control->recv_buf_pos + packet_size;
+ control->recv_buf_pos = 0;
+
+ /* Fragmented Packet in Progress := FALSE */
+ /* call avrcp_message_cb */
+
+ return handle_avrcp_message(control,
+ avrcp, packet_size,
+ avctp->transaction);
+ break;
+ }
+ default:
+ break;
+ }
return TRUE;
-
-failed:
- DBG("AVCTP session %p got disconnected", control);
- avctp_set_state(control, AVCTP_STATE_DISCONNECTED);
- return FALSE;
}

static int uinput_create(char *name)
@@ -1011,10 +2127,15 @@ static void avctp_connect_cb(GIOChannel *chan, GError
*err, gpointer data)
init_uinput(control);

avctp_set_state(control, AVCTP_STATE_CONNECTED);
+ if (control->mtu != imtu) {
+ emit_property_changed(control->dev->conn, control->dev->path,
+ AUDIO_CONTROL_INTERFACE, "MTU",
+ DBUS_TYPE_UINT16, &imtu);
+ }
control->mtu = imtu;
control->io_id = g_io_add_watch(chan,
G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- (GIOFunc) control_cb, control);
+ (GIOFunc) avctp_packet_cb, control);
}

static void auth_cb(DBusError *derr, void *user_data)
@@ -1088,7 +2209,7 @@ static void avctp_confirm_cb(GIOChannel *chan, gpointer
data)
goto drop;

control->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
- control_cb, control);
+ avctp_packet_cb, control);
return;

drop:
@@ -1261,6 +2382,413 @@ void avrcp_unregister(const bdaddr_t *src)
connection = NULL;
}

+/**
+ * @brief send_notification
+ *
+ * Called to send a notification response, triggered by a
+ * ChangeTrack or ChangePlayback method call from the TG.
+ */
+static int send_notification(struct control *control,
+ uint8_t event_id)
+{
+ struct avrcp_vendor_dep *avrcp;
+ int avrcp_length, write_length;
+ DBG("Send Notification: %d", event_id);
+ /*
+ * If the event is not supported or not subscribed,
+ * there is nothing to do.
+ */
+ if (!control->cap_events[event_id] || !control->sub_events[event_id])
+ return 1;
+
+ avrcp_length = sizeof(struct avrcp_vendor_dep) + 11;
+ avrcp = (struct avrcp_vendor_dep *) g_malloc0(avrcp_length);
+
+ avrcp->hdr.ctype = CTYPE_CHANGED;
+ avrcp->hdr.subunit_id = 0x07;
+ avrcp->hdr.subunit_type = SUBUNIT_PANEL;
+ avrcp->hdr.opcode = OP_VENDOR_DEPENDENT;
+ set_company_id((uint8_t *)&avrcp->company_id, BTSIG_COMPANY_ID);
+
+ avrcp->pdu_id = PDU_REGISTER_NOTIFICATION;
+ avrcp->packet_type = AVCTP_PACKET_SINGLE;
+ avrcp->pdu_data[0] = event_id;
+ avrcp_length = 1;
+
+ switch (event_id) {
+ case EVENT_PLAYBACK_STATUS_CHANGED: {
+ avrcp->pdu_data[1] = control->play_state;
+ avrcp_length++;
+ break;
+ }
+ case EVENT_TRACK_CHANGED: {
+ struct {
+ uint32_t hi;
+ uint32_t lo;
+ } track_id;
+ track_id.hi = htonl(control->trk_uuid >> 32);
+ track_id.lo = htonl(control->trk_uuid & 0xFFFFFFFF);
+ memcpy((void *) &avrcp->pdu_data[1],
+ (void *) &track_id, 8);
+ avrcp_length += 8;
+ break;
+ }
+ case EVENT_TRACK_REACHED_END: {
+ break;
+ }
+ case EVENT_TRACK_REACHED_START: {
+ break;
+ }
+ case EVENT_PLAYBACK_POS_CHANGED: {
+ /*
+ * Registering Event Track Position Changed:
+ * capture and save the notification interval
+ * (in seconds).
+ */
+ uint32_t interval = htonl(control->play_pos);
+ memcpy((void *) &avrcp->pdu_data[1],
+ (void *) &interval, 4);
+ avrcp_length += 4;
+ break;
+ }
+ case EVENT_BATT_STATUS_CHANGED: {
+ avrcp->pdu_data[1] = control->ct_battery;
+ avrcp_length++;
+ break;
+ }
+ case EVENT_SYSTEM_STATUS_CHANGED: {
+ avrcp->pdu_data[1] = 0;
+ avrcp_length++;
+ break;
+ }
+ case EVENT_PLAYER_APP_SETTING_CHANGED: {
+ avrcp->pdu_data[1] = 0;
+ avrcp_length++;
+ break;
+ }
+ default:
+ break;
+ }
+ avrcp->length = htons(avrcp_length);
+ avrcp_length += sizeof(struct avrcp_vendor_dep);
+
+ write_length = avrcp_write(control,
+ (struct avrcp_header *) avrcp,
+ avrcp_length);
+
+ control->sub_events[event_id] = FALSE;
+
+ g_free(avrcp);
+ return write_length;
+}
+
+/**
+ * @brief: parse_track_properties
+ *
+ * Iterates through the dict parameter to ChangeTrack storing
+ * metadata elements into Strings for convenience in sending
+ * on to CT.
+ */
+static int parse_track_properties(DBusMessageIter *props,
+ struct control *control,
+ uint64_t tmp_trkid)
+{
+ const char *tmp_title, *tmp_artist, *tmp_album, *tmp_genre;
+ dbus_int32_t tmp_trknum = -1, tmp_numtrks = -1, tmp_trkdur = -1;
+ gboolean has_title = FALSE;
+
+ while (dbus_message_iter_get_arg_type(props) == DBUS_TYPE_DICT_ENTRY) {
+ const char *key;
+ DBusMessageIter value, entry;
+ int var;
+
+ dbus_message_iter_recurse(props, &entry);
+ dbus_message_iter_get_basic(&entry, &key);
+
+ DBG("Key %s returned", key);
+
+ dbus_message_iter_next(&entry);
+ dbus_message_iter_recurse(&entry, &value);
+
+ var = dbus_message_iter_get_arg_type(&value);
+ if (strcasecmp(key, "Title") == 0) {
+ if (var != DBUS_TYPE_STRING)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, &tmp_title);
+ DBG("%s %s returned", key, tmp_title);
+ has_title = TRUE;
+ } else if (strcasecmp(key, "Artist") == 0) {
+ if (var != DBUS_TYPE_STRING)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, &tmp_artist);
+ DBG("%s %s returned", key, tmp_artist);
+ } else if (strcasecmp(key, "Album") == 0) {
+ if (var != DBUS_TYPE_STRING)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, &tmp_album);
+ DBG("%s %s returned", key, tmp_album);
+ } else if (strcasecmp(key, "Genre") == 0) {
+ if (var != DBUS_TYPE_STRING)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, &tmp_genre);
+ DBG("%s %s returned", key, tmp_genre);
+ } else if (strcasecmp(key, "TrackNumber") == 0) {
+ if (var != DBUS_TYPE_INT32)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, &tmp_trknum);
+ DBG("%s %d returned", key, tmp_trknum);
+ } else if (strcasecmp(key, "NumberOfTracks") == 0) {
+ if (var != DBUS_TYPE_INT32)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, &tmp_numtrks);
+ DBG("%s %d returned", key, tmp_numtrks);
+ } else if (strcasecmp(key, "TrackDuration") == 0) {
+ if (var != DBUS_TYPE_INT32)
+ return -EINVAL;
+ dbus_message_iter_get_basic(&value, &tmp_trkdur);
+ DBG("%s %d returned", key, tmp_trkdur);
+ }
+ dbus_message_iter_next(props);
+ }
+
+ if (!tmp_title)
+ return -EINVAL;
+
+ /* Clear the previous Track-ID and Metadata from the Control block. */
+
+ if (control->trk_elem[META_ATTR_TITLE].elem)
+ g_free(control->trk_elem[META_ATTR_TITLE].elem);
+ control->trk_elem[META_ATTR_TITLE].elem_len = 0;
+
+ if (control->trk_elem[META_ATTR_ARTIST].elem)
+ g_free(control->trk_elem[META_ATTR_ARTIST].elem);
+ control->trk_elem[META_ATTR_ARTIST].elem_len = 0;
+
+ if (control->trk_elem[META_ATTR_ALBUM].elem)
+ g_free(control->trk_elem[META_ATTR_ALBUM].elem);
+ control->trk_elem[META_ATTR_ALBUM].elem_len = 0;
+
+ if (control->trk_elem[META_ATTR_GENRE].elem)
+ g_free(control->trk_elem[META_ATTR_GENRE].elem);
+ control->trk_elem[META_ATTR_GENRE].elem_len = 0;
+
+ if (control->trk_elem[META_ATTR_TRKNUM].elem)
+ g_free(control->trk_elem[META_ATTR_TRKNUM].elem);
+ control->trk_elem[META_ATTR_TRKNUM].elem_len = 0;
+
+ if (control->trk_elem[META_ATTR_TTLTRK].elem)
+ g_free(control->trk_elem[META_ATTR_TTLTRK].elem);
+ control->trk_elem[META_ATTR_TTLTRK].elem_len = 0;
+
+ if (control->trk_elem[META_ATTR_TIME].elem)
+ g_free(control->trk_elem[META_ATTR_TIME].elem);
+ control->trk_elem[META_ATTR_TIME].elem_len = 0;
+
+ /* Move the retrieved Track-ID and Metadata to the Control block. */
+
+ control->trk_uuid = tmp_trkid;
+
+ control->trk_elem[META_ATTR_TITLE].elem =
+ (uint8_t *) g_strdup(tmp_title);
+ control->trk_elem[META_ATTR_TITLE].elem_len =
+ strlen(tmp_title);
+ control->trk_elem[META_ATTR_TITLE].charset =
+ control->ct_charset;
+
+ if (tmp_artist) {
+ control->trk_elem[META_ATTR_ARTIST].elem =
+ (uint8_t *) g_strdup(tmp_artist);
+ control->trk_elem[META_ATTR_ARTIST].elem_len =
+ strlen(tmp_artist);
+ control->trk_elem[META_ATTR_ARTIST].charset =
+ control->ct_charset;
+ }
+
+ if (tmp_album) {
+ control->trk_elem[META_ATTR_ALBUM].elem =
+ (uint8_t *) g_strdup(tmp_album);
+ control->trk_elem[META_ATTR_ALBUM].elem_len =
+ strlen(tmp_album);
+ control->trk_elem[META_ATTR_ALBUM].charset =
+ control->ct_charset;
+ }
+
+ if (tmp_genre) {
+ control->trk_elem[META_ATTR_GENRE].elem =
+ (uint8_t *) g_strdup(tmp_genre);
+ control->trk_elem[META_ATTR_GENRE].elem_len =
+ strlen(tmp_genre);
+ control->trk_elem[META_ATTR_GENRE].charset =
+ control->ct_charset;
+ }
+
+ if (tmp_trknum >= 0) {
+ control->trk_elem[META_ATTR_TRKNUM].elem =
+ (uint8_t *) g_strdup_printf("%d", tmp_trknum);
+ control->trk_elem[META_ATTR_TRKNUM].elem_len =
+ strlen((const char *)
+ control->trk_elem[META_ATTR_TRKNUM].elem);
+ control->trk_elem[META_ATTR_TRKNUM].charset =
+ control->ct_charset;
+ }
+
+ if (tmp_numtrks >= 0) {
+ control->trk_elem[META_ATTR_TTLTRK].elem =
+ (uint8_t *) g_strdup_printf("%d", tmp_numtrks);
+ control->trk_elem[META_ATTR_TTLTRK].elem_len =
+ strlen((const char *)
+ control->trk_elem[META_ATTR_TTLTRK].elem);
+ control->trk_elem[META_ATTR_TTLTRK].charset =
+ control->ct_charset;
+ }
+
+ if (tmp_trkdur >= 0) {
+ control->trk_elem[META_ATTR_TIME].elem =
+ (uint8_t *) g_strdup_printf("%d", tmp_trkdur);
+ control->trk_elem[META_ATTR_TIME].elem_len =
+ strlen((const char *)
+ control->trk_elem[META_ATTR_TIME].elem);
+ control->trk_elem[META_ATTR_TIME].charset =
+ control->ct_charset;
+ }
+
+ return 0;
+}
+
+/**
+ * @brief: control_change_track
+ *
+ * Implements ChangeTrack method
+ */
+static DBusMessage *control_change_track(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ int write_length;
+
+ DBusMessageIter args, props;
+ uint64_t tmp_trkid, prev_trkid;
+
+ DBG("Change Track received");
+
+ prev_trkid = control->trk_uuid;
+
+ dbus_message_iter_init(msg, &args);
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_UINT64)
+ return btd_error_invalid_args(msg);
+ dbus_message_iter_get_basic(&args, &tmp_trkid);
+ dbus_message_iter_next(&args);
+
+#if __WORDSIZE == 32
+ DBG("Track ID: 0x%llx", tmp_trkid);
+#else
+ DBG("Track ID: 0x%lx", tmp_trkid);
+#endif
+
+ /* Recurse to fetch the a{sv} Hash Table */
+
+ dbus_message_iter_recurse(&args, &props);
+ if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_DICT_ENTRY)
+ return btd_error_invalid_args(msg);
+
+ if (parse_track_properties(&props, control, tmp_trkid) < 0)
+ return btd_error_invalid_args(msg);
+
+ /* Notify CT of any events triggered by change-of-track */
+
+ if (prev_trkid != (uint64_t) -1) {
+
+ if (control->play_pos == control->play_length) {
+ write_length = send_notification(control,
+ EVENT_TRACK_REACHED_END);
+ if (write_length < 0)
+ return btd_error_failed(msg,
+ strerror(-write_length));
+ }
+
+ control->play_state = AVRCP_STATUS_STOPPED;
+ write_length = send_notification(control,
+ EVENT_PLAYBACK_STATUS_CHANGED);
+ if (write_length < 0)
+ return btd_error_failed(msg, strerror(-write_length));
+ }
+
+ write_length = send_notification(control, EVENT_TRACK_CHANGED);
+ if (write_length < 0)
+ return btd_error_failed(msg, strerror(-write_length));
+
+ write_length = send_notification(control, EVENT_TRACK_REACHED_START);
+ if (write_length < 0)
+ return btd_error_failed(msg, strerror(-write_length));
+
+ return dbus_message_new_method_return(msg);
+}
+
+/**
+ * @brief: control_change_playback
+ *
+ * Implements ChangePlayback method
+ */
+static DBusMessage *control_change_playback(DBusConnection *conn,
+ DBusMessage *msg,
+ void *data)
+{
+ struct audio_device *device = data;
+ struct control *control = device->control;
+ int write_length;
+
+ DBusMessageIter args, props, value;
+ const char *key;
+
+ dbus_message_iter_init(msg, &args);
+
+ if (dbus_message_iter_get_arg_type(&args) != DBUS_TYPE_STRING)
+ return btd_error_invalid_args(msg);
+ dbus_message_iter_get_basic(&args, &key);
+ dbus_message_iter_next(&args);
+
+ dbus_message_iter_recurse(&args, &props);
+ if (dbus_message_iter_get_arg_type(&props) != DBUS_TYPE_VARIANT)
+ return btd_error_invalid_args(msg);
+ dbus_message_iter_recurse(&props, &value);
+ if (dbus_message_iter_get_arg_type(&value) != DBUS_TYPE_INT32)
+ return btd_error_invalid_args(msg);
+
+ if (strcasecmp(key, "playing") == 0) {
+ dbus_message_iter_get_basic(&value, &control->play_pos);
+ control->play_state = AVRCP_STATUS_PLAYING;
+ } else if (strcasecmp(key, "stopped") == 0) {
+ control->play_state = AVRCP_STATUS_STOPPED;
+ } else if (strcasecmp(key, "paused") == 0) {
+ control->play_state = AVRCP_STATUS_PAUSED;
+ } else if (strcasecmp(key, "forward-seek") == 0) {
+ dbus_message_iter_get_basic(&value, &control->play_pos);
+ control->play_state = AVRCP_STATUS_FWD_SEEK;
+ } else if (strcasecmp(key, "reverse-seek") == 0) {
+ dbus_message_iter_get_basic(&value, &control->play_pos);
+ control->play_state = AVRCP_STATUS_REV_SEEK;
+ } else if (strcasecmp(key, "at-start") == 0) {
+ control->play_pos = 0;
+ control->play_state = AVRCP_STATUS_STOPPED;
+ } else if (strcasecmp(key, "at-end") == 0) {
+ control->play_pos = control->play_length;
+ control->play_state = AVRCP_STATUS_STOPPED;
+ } else if (strcasecmp(key, "error") == 0) {
+ control->play_state = AVRCP_STATUS_ERROR;
+ } else {
+ return btd_error_invalid_args(msg);
+ }
+
+ write_length = send_notification(control,
+ EVENT_PLAYBACK_STATUS_CHANGED);
+ if (write_length < 0)
+ return btd_error_failed(msg, strerror(-write_length));
+
+ return dbus_message_new_method_return(msg);
+}
+
static DBusMessage *control_is_connected(DBusConnection *conn,
DBusMessage *msg,
void *data)
@@ -1282,38 +2810,36 @@ static DBusMessage
*control_is_connected(DBusConnection *conn,
return reply;
}

-static int avctp_send_passthrough(struct control *control, uint8_t op)
+static int avrcp_send_passthrough(struct control *control, uint8_t op)
{
- unsigned char buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH + 2];
- struct avctp_header *avctp = (void *) buf;
- struct avrcp_header *avrcp = (void *) &buf[AVCTP_HEADER_LENGTH];
- uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVRCP_HEADER_LENGTH];
- int sk = g_io_channel_unix_get_fd(control->io);
- static uint8_t transaction = 0;
+ uint8_t buf[sizeof(struct avrcp_passthru)];
+ struct avrcp_passthru *avrcp = (void *) &buf;
+ /* Do not send anything beyond op length (zero) */
+ int avrcp_length = offsetof(struct avrcp_passthru, company_id);
+ int err;

memset(buf, 0, sizeof(buf));

- avctp->transaction = transaction++;
- avctp->packet_type = AVCTP_PACKET_SINGLE;
- avctp->cr = AVCTP_COMMAND;
- avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
-
- avrcp->ctype = CTYPE_CONTROL;
- avrcp->subunit_type = SUBUNIT_PANEL;
- avrcp->opcode = OP_PASSTHROUGH;
+ avrcp->hdr.ctype = CTYPE_CONTROL;
+ avrcp->hdr.subunit_type = SUBUNIT_PANEL;
+ avrcp->hdr.opcode = OP_PASSTHROUGH;

- operands[0] = op & 0x7f;
- operands[1] = 0;
+ avrcp->key = op;
+ avrcp->key_state = 0;
+ avrcp->op_len = 0;

- if (write(sk, buf, sizeof(buf)) < 0)
- return -errno;
+ err = avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+ if (err < 0)
+ return err;

/* Button release */
- avctp->transaction = transaction++;
- operands[0] |= 0x80;
+ avrcp->key_state = 1;

- if (write(sk, buf, sizeof(buf)) < 0)
- return -errno;
+ err = avrcp_write(control,
+ (struct avrcp_header *) avrcp, avrcp_length);
+ if (err < 0)
+ return err;

return 0;
}
@@ -1331,7 +2857,7 @@ static DBusMessage *volume_up(DBusConnection *conn,
DBusMessage *msg,
if (!control->target)
return btd_error_not_supported(msg);

- err = avctp_send_passthrough(control, VOL_UP_OP);
+ err = avrcp_send_passthrough(control, VOL_UP_OP);
if (err < 0)
return btd_error_failed(msg, strerror(-err));

@@ -1351,7 +2877,7 @@ static DBusMessage *volume_down(DBusConnection *conn,
DBusMessage *msg,
if (!control->target)
return btd_error_not_supported(msg);

- err = avctp_send_passthrough(control, VOL_DOWN_OP);
+ err = avrcp_send_passthrough(control, VOL_DOWN_OP);
if (err < 0)
return btd_error_failed(msg, strerror(-err));

@@ -1362,10 +2888,12 @@ static DBusMessage
*control_get_properties(DBusConnection *conn,
DBusMessage *msg, void *data)
{
struct audio_device *device = data;
+ struct control *control = device->control;
DBusMessage *reply;
DBusMessageIter iter;
DBusMessageIter dict;
gboolean value;
+ char *version;

reply = dbus_message_new_method_return(msg);
if (!reply)
@@ -1378,29 +2906,45 @@ static DBusMessage
*control_get_properties(DBusConnection *conn,
DBUS_TYPE_STRING_AS_STRING DBUS_TYPE_VARIANT_AS_STRING
DBUS_DICT_ENTRY_END_CHAR_AS_STRING, &dict);

+ /* Battery */
+ dict_append_entry(&dict, "Battery", DBUS_TYPE_BYTE,
+ &control->ct_battery);
/* Connected */
value = (device->control->state == AVCTP_STATE_CONNECTED);
dict_append_entry(&dict, "Connected", DBUS_TYPE_BOOLEAN, &value);
+ /* MTU */
+ dict_append_entry(&dict, "MTU", DBUS_TYPE_UINT16, &control->mtu);
+ /* PlayInterval */
+ dict_append_entry(&dict, "PlayInterval", DBUS_TYPE_UINT32,
+ &control->play_intvl);
+ /* Version */
+ version = g_strdup_printf("%d.%d", control->version / 256,
+ control->version % 256);
+ dict_append_entry(&dict, "Version", DBUS_TYPE_STRING, &version);

dbus_message_iter_close_container(&iter, &dict);
+ g_free(version);

return reply;
}

static GDBusMethodTable control_methods[] = {
{ "IsConnected", "", "b", control_is_connected,
- G_DBUS_METHOD_FLAG_DEPRECATED },
+ G_DBUS_METHOD_FLAG_DEPRECATED },
{ "GetProperties", "", "a{sv}",control_get_properties },
{ "VolumeUp", "", "", volume_up },
{ "VolumeDown", "", "", volume_down },
+ { "ChangeTrack", "ta{sv}", "", control_change_track },
+ { "ChangePlayback", "si", "", control_change_playback },
{ NULL, NULL, NULL, NULL }
};

static GDBusSignalTable control_signals[] = {
- { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
- { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
- { "PropertyChanged", "sv" },
- { "Passthrough", "ybis" },
+ { "Connected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
+ { "Disconnected", "", G_DBUS_SIGNAL_FLAG_DEPRECATED},
+ { "PropertyChanged", "sv" },
+ { "GetPlayStatus", "" },
+ { "Passthrough", "ybis" },
{ NULL, NULL }
};

@@ -1451,6 +2995,29 @@ struct control *control_init(struct audio_device *dev,
uint16_t uuid16)
control->state = AVCTP_STATE_DISCONNECTED;
control->uinput = -1;

+ /*
+ * FIXME: Initialize Company IDs, Event Capabilities from
+ * gbl_ default settings, to be set from audio.conf.
+ */
+
+ control->role = 1; /* TG */
+ control->ct_battery = 0;
+ control->ct_charset = 0x6A; /* UTF-8 */
+
+ control->version = 0x0103; /* AVRCP v1.3 */
+
+ control->cap_company_ids[0] = BTSIG_COMPANY_ID;
+
+ control->cap_events[EVENT_PLAYBACK_STATUS_CHANGED] = TRUE;
+ control->cap_events[EVENT_TRACK_CHANGED] = TRUE;
+ control->cap_events[EVENT_TRACK_REACHED_END] = TRUE;
+ control->cap_events[EVENT_TRACK_REACHED_START] = TRUE;
+ control->cap_events[EVENT_PLAYBACK_POS_CHANGED] = TRUE;
+
+ control->play_state = AVRCP_STATUS_STOPPED;
+ control->trk_uuid = (uint64_t) -1;
+ control->play_pos = (uint32_t) -1;
+
if (uuid16 == AV_REMOTE_TARGET_SVCLASS_ID)
control->target = TRUE;

--
1.7.4.4