diff --git a/Makefile.am b/Makefile.am
index 02ad23c..0f5790a 100644
--- a/Makefile.am
+++ b/Makefile.am
unit_test_avdtp_SOURCES = unit/test-avdtp.c \
src/log.h src/log.c \
- android/avdtp.c android/avdtp.h
+ unit/avdtp.c unit/avdtp.h
unit_test_avdtp_LDADD = src/libshared-glib.la $(GLIB_LIBS)
unit_tests += unit/test-avctp
unit_test_avctp_SOURCES = unit/test-avctp.c \
src/log.h src/log.c \
- android/avctp.c android/avctp.h
+ unit/avctp.c unit/avctp.h
unit_test_avctp_LDADD = src/libshared-glib.la $(GLIB_LIBS)
unit_tests += unit/test-avrcp
unit_test_avrcp_SOURCES = unit/test-avrcp.c \
src/log.h src/log.c \
- android/avctp.c android/avctp.h \
- android/avrcp-lib.c android/avrcp-lib.h
+ unit/avctp.c unit/avctp.h \
+ unit/avrcp-lib.c unit/avrcp-lib.h
unit_test_avrcp_LDADD = lib/libbluetooth-internal.la \
src/libshared-glib.la $(GLIB_LIBS)
diff --git a/unit/avctp.c b/unit/avctp.c
new file mode 100644
index 0000000..d8104a7
--- /dev/null
+++ b/unit/avctp.c
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ * Copyright (C) 2011 Texas Instruments, Inc.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <signal.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <netinet/in.h>
+#include <linux/uinput.h>
+
+#include <glib.h>
+
+#include "lib/sdp.h"
+#include "src/shared/util.h"
+#include "src/log.h"
+#include "avctp.h"
+
+/*
+ * AV/C Panel 1.23, page 76:
+ * command with the pressed value is valid for two seconds
+ */
+#define AVC_PRESS_TIMEOUT 2
+
+#define QUIRK_NO_RELEASE 1 << 0
+
+/* Message types */
+#define AVCTP_COMMAND 0
+#define AVCTP_RESPONSE 1
+
+/* Packet types */
+#define AVCTP_PACKET_SINGLE 0
+#define AVCTP_PACKET_START 1
+#define AVCTP_PACKET_CONTINUE 2
+#define AVCTP_PACKET_END 3
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avctp_header {
+ uint8_t ipid:1;
+ uint8_t cr:1;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+ uint16_t pid;
+} __attribute__ ((packed));
+
+struct avc_header {
+ uint8_t code:4;
+ uint8_t _hdr0:4;
+ uint8_t subunit_id:3;
+ uint8_t subunit_type:5;
+ uint8_t opcode;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avctp_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t cr:1;
+ uint8_t ipid:1;
+ uint16_t pid;
+} __attribute__ ((packed));
+
+struct avc_header {
+ uint8_t _hdr0:4;
+ uint8_t code:4;
+ uint8_t subunit_type:5;
+ uint8_t subunit_id:3;
+ uint8_t opcode;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct avctp_control_req {
+ struct avctp_pending_req *p;
+ uint8_t code;
+ uint8_t subunit;
+ uint8_t op;
+ struct iovec *iov;
+ int iov_cnt;
+ avctp_rsp_cb func;
+ void *user_data;
+};
+
+struct avctp_browsing_req {
+ struct avctp_pending_req *p;
+ struct iovec *iov;
+ int iov_cnt;
+ avctp_browsing_rsp_cb func;
+ void *user_data;
+};
+
+typedef int (*avctp_process_cb) (void *data);
+
+struct avctp_pending_req {
+ struct avctp_channel *chan;
+ uint8_t transaction;
+ guint timeout;
+ int err;
+ avctp_process_cb process;
+ void *data;
+ avctp_destroy_cb_t destroy;
+};
+
+struct avctp_channel {
+ struct avctp *session;
+ GIOChannel *io;
+ uint8_t transaction;
+ guint watch;
+ uint16_t imtu;
+ uint16_t omtu;
+ uint8_t *buffer;
+ GSList *handlers;
+ struct avctp_pending_req *p;
+ GQueue *queue;
+ GSList *processed;
+ guint process_id;
+ avctp_destroy_cb_t destroy;
+};
+
+struct key_pressed {
+ uint8_t op;
+ uint8_t *params;
+ size_t params_len;
+ guint timer;
+};
+
+struct avctp {
+ unsigned int ref;
+ int uinput;
+
+ unsigned int passthrough_id;
+ unsigned int unit_id;
+ unsigned int subunit_id;
+
+ struct avctp_channel *control;
+ struct avctp_channel *browsing;
+
+ struct avctp_passthrough_handler *handler;
+
+ uint8_t key_quirks[256];
+ struct key_pressed key;
+ uint16_t version;
+
+ avctp_destroy_cb_t destroy;
+ void *data;
+};
+
+struct avctp_passthrough_handler {
+ avctp_passthrough_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+struct avctp_pdu_handler {
+ uint8_t opcode;
+ avctp_control_pdu_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+struct avctp_browsing_pdu_handler {
+ avctp_browsing_pdu_cb cb;
+ void *user_data;
+ unsigned int id;
+ avctp_destroy_cb_t destroy;
+};
+
+static struct {
+ const char *name;
+ uint8_t avc;
+ uint16_t uinput;
+} key_map[] = {
+ { "SELECT", AVC_SELECT, KEY_SELECT },
+ { "UP", AVC_UP, KEY_UP },
+ { "DOWN", AVC_DOWN, KEY_DOWN },
+ { "LEFT", AVC_LEFT, KEY_LEFT },
+ { "RIGHT", AVC_RIGHT, KEY_RIGHT },
+ { "ROOT MENU", AVC_ROOT_MENU, KEY_MENU },
+ { "CONTENTS MENU", AVC_CONTENTS_MENU, KEY_PROGRAM },
+ { "FAVORITE MENU", AVC_FAVORITE_MENU, KEY_FAVORITES },
+ { "EXIT", AVC_EXIT, KEY_EXIT },
+ { "ON DEMAND MENU", AVC_ON_DEMAND_MENU, KEY_MENU },
+ { "APPS MENU", AVC_APPS_MENU, KEY_MENU },
+ { "0", AVC_0, KEY_0 },
+ { "1", AVC_1, KEY_1 },
+ { "2", AVC_2, KEY_2 },
+ { "3", AVC_3, KEY_3 },
+ { "4", AVC_4, KEY_4 },
+ { "5", AVC_5, KEY_5 },
+ { "6", AVC_6, KEY_6 },
+ { "7", AVC_7, KEY_7 },
+ { "8", AVC_8, KEY_8 },
+ { "9", AVC_9, KEY_9 },
+ { "DOT", AVC_DOT, KEY_DOT },
+ { "ENTER", AVC_ENTER, KEY_ENTER },
+ { "CHANNEL UP", AVC_CHANNEL_UP, KEY_CHANNELUP },
+ { "CHANNEL DOWN", AVC_CHANNEL_DOWN, KEY_CHANNELDOWN },
+ { "CHANNEL PREVIOUS", AVC_CHANNEL_PREVIOUS, KEY_LAST },
+ { "INPUT SELECT", AVC_INPUT_SELECT, KEY_CONFIG },
+ { "INFO", AVC_INFO, KEY_INFO },
+ { "HELP", AVC_HELP, KEY_HELP },
+ { "POWER", AVC_POWER, KEY_POWER2 },
+ { "VOLUME UP", AVC_VOLUME_UP, KEY_VOLUMEUP },
+ { "VOLUME DOWN", AVC_VOLUME_DOWN, KEY_VOLUMEDOWN },
+ { "MUTE", AVC_MUTE, KEY_MUTE },
+ { "PLAY", AVC_PLAY, KEY_PLAYCD },
+ { "STOP", AVC_STOP, KEY_STOPCD },
+ { "PAUSE", AVC_PAUSE, KEY_PAUSECD },
+ { "FORWARD", AVC_FORWARD, KEY_NEXTSONG },
+ { "BACKWARD", AVC_BACKWARD, KEY_PREVIOUSSONG },
+ { "RECORD", AVC_RECORD, KEY_RECORD },
+ { "REWIND", AVC_REWIND, KEY_REWIND },
+ { "FAST FORWARD", AVC_FAST_FORWARD, KEY_FASTFORWARD },
+ { "LIST", AVC_LIST, KEY_LIST },
+ { "F1", AVC_F1, KEY_F1 },
+ { "F2", AVC_F2, KEY_F2 },
+ { "F3", AVC_F3, KEY_F3 },
+ { "F4", AVC_F4, KEY_F4 },
+ { "F5", AVC_F5, KEY_F5 },
+ { "F6", AVC_F6, KEY_F6 },
+ { "F7", AVC_F7, KEY_F7 },
+ { "F8", AVC_F8, KEY_F8 },
+ { "F9", AVC_F9, KEY_F9 },
+ { "RED", AVC_RED, KEY_RED },
+ { "GREEN", AVC_GREEN, KEY_GREEN },
+ { "BLUE", AVC_BLUE, KEY_BLUE },
+ { "YELLOW", AVC_YELLOW, KEY_YELLOW },
+ { NULL }
+};
+
+static gboolean process_queue(gpointer user_data);
+static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code,
+ uint8_t subunit, uint8_t *operands,
+ size_t operand_count, void *user_data);
+
+static int send_event(int fd, uint16_t type, uint16_t code, int32_t value)
+{
+ struct input_event event;
+ int err;
+
+ memset(&event, 0, sizeof(event));
+ event.type = type;
+ event.code = code;
+ event.value = value;
+
+ do {
+ err = write(fd, &event, sizeof(event));
+ } while (err < 0 && errno == EINTR);
+
+ if (err < 0) {
+ err = -errno;
+ error("send_event: %s (%d)", strerror(-err), -err);
+ }
+
+ return err;
+}
+
+static void send_key(int fd, uint16_t key, int pressed)
+{
+ send_event(fd, EV_KEY, key, pressed);
+ send_event(fd, EV_SYN, SYN_REPORT, 0);
+}
+
+static gboolean auto_release(gpointer user_data)
+{
+ struct avctp *session = user_data;
+
+ session->key.timer = 0;
+
+ DBG("AV/C: key press timeout");
+
+ send_key(session->uinput, session->key.op, 0);
+
+ return FALSE;
+}
+
+static ssize_t handle_panel_passthrough(struct avctp *session,
+ uint8_t transaction, uint8_t *code,
+ uint8_t *subunit, uint8_t *operands,
+ size_t operand_count, void *user_data)
+{
+ struct avctp_passthrough_handler *handler = session->handler;
+ const char *status;
+ int pressed, i;
+
+ if (*code != AVC_CTYPE_CONTROL || *subunit != AVC_SUBUNIT_PANEL) {
+ *code = AVC_CTYPE_REJECTED;
+ return operand_count;
+ }
+
+ if (operand_count == 0)
+ goto done;
+
+ if (operands[0] & 0x80) {
+ status = "released";
+ pressed = 0;
+ } else {
+ status = "pressed";
+ pressed = 1;
+ }
+
+ if (session->key.timer == 0 && handler != NULL) {
+ if (handler->cb(session, operands[0] & 0x7F,
+ pressed, handler->user_data))
+ goto done;
+ }
+
+ if (session->uinput < 0) {
+ DBG("AV/C: uinput not initialized");
+ *code = AVC_CTYPE_NOT_IMPLEMENTED;
+ return 0;
+ }
+
+ for (i = 0; key_map[i].name != NULL; i++) {
+ uint8_t key_quirks;
+
+ if ((operands[0] & 0x7F) != key_map[i].avc)
+ continue;
+
+ DBG("AV/C: %s %s", key_map[i].name, status);
+
+ key_quirks = session->key_quirks[key_map[i].avc];
+
+ if (key_quirks & QUIRK_NO_RELEASE) {
+ if (!pressed) {
+ DBG("AV/C: Ignoring release");
+ break;
+ }
+
+ DBG("AV/C: treating key press as press + release");
+ send_key(session->uinput, key_map[i].uinput, 1);
+ send_key(session->uinput, key_map[i].uinput, 0);
+ break;
+ }
+
+ if (pressed) {
+ if (session->key.timer > 0) {
+ g_source_remove(session->key.timer);
+ send_key(session->uinput, session->key.op, 0);
+ }
+
+ session->key.op = key_map[i].uinput;
+ session->key.timer = g_timeout_add_seconds(
+ AVC_PRESS_TIMEOUT,
+ auto_release,
+ session);
+ } else if (session->key.timer > 0) {
+ g_source_remove(session->key.timer);
+ session->key.timer = 0;
+ }
+
+ send_key(session->uinput, key_map[i].uinput, pressed);
+ break;
+ }
+
+ if (key_map[i].name == NULL) {
+ DBG("AV/C: unknown button 0x%02X %s",
+ operands[0] & 0x7F, status);
+ *code = AVC_CTYPE_NOT_IMPLEMENTED;
+ return operand_count;
+ }
+
+done:
+ *code = AVC_CTYPE_ACCEPTED;
+ return operand_count;
+}
+
+static ssize_t handle_unit_info(struct avctp *session,
+ uint8_t transaction, uint8_t *code,
+ uint8_t *subunit, uint8_t *operands,
+ size_t operand_count, void *user_data)
+{
+ if (*code != AVC_CTYPE_STATUS) {
+ *code = AVC_CTYPE_REJECTED;
+ return 0;
+ }
+
+ *code = AVC_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)
+ operands[0] = 0x07;
+ if (operand_count >= 2)
+ operands[1] = AVC_SUBUNIT_PANEL << 3;
+
+ DBG("reply to AVC_OP_UNITINFO");
+
+ return operand_count;
+}
+
+static ssize_t handle_subunit_info(struct avctp *session,
+ uint8_t transaction, uint8_t *code,
+ uint8_t *subunit, uint8_t *operands,
+ size_t operand_count, void *user_data)
+{
+ if (*code != AVC_CTYPE_STATUS) {
+ *code = AVC_CTYPE_REJECTED;
+ return 0;
+ }
+
+ *code = AVC_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 >= 2)
+ operands[1] = AVC_SUBUNIT_PANEL << 3;
+
+ DBG("reply to AVC_OP_SUBUNITINFO");
+
+ return operand_count;
+}
+
+static struct avctp_pdu_handler *find_handler(GSList *list, uint8_t opcode)
+{
+ for (; list; list = list->next) {
+ struct avctp_pdu_handler *handler = list->data;
+
+ if (handler->opcode == opcode)
+ return handler;
+ }
+
+ return NULL;
+}
+
+static void pending_destroy(gpointer data, gpointer user_data)
+{
+ struct avctp_pending_req *req = data;
+
+ if (req->destroy)
+ req->destroy(req->data);
+
+ if (req->timeout > 0)
+ g_source_remove(req->timeout);
+
+ g_free(req);
+}
+
+static void avctp_channel_destroy(struct avctp_channel *chan)
+{
+ g_io_channel_shutdown(chan->io, TRUE, NULL);
+ g_io_channel_unref(chan->io);
+
+ if (chan->watch)
+ g_source_remove(chan->watch);
+
+ if (chan->p)
+ pending_destroy(chan->p, NULL);
+
+ if (chan->process_id > 0)
+ g_source_remove(chan->process_id);
+
+ if (chan->destroy)
+ chan->destroy(chan);
+
+ g_free(chan->buffer);
+ g_queue_foreach(chan->queue, pending_destroy, NULL);
+ g_queue_free(chan->queue);
+ g_slist_foreach(chan->processed, pending_destroy, NULL);
+ g_slist_free(chan->processed);
+ g_slist_free_full(chan->handlers, g_free);
+ g_free(chan);
+}
+
+static int avctp_send(struct avctp_channel *control, uint8_t transaction,
+ uint8_t cr, uint8_t code,
+ uint8_t subunit, uint8_t opcode,
+ const struct iovec *iov, int iov_cnt)
+{
+ struct avctp_header avctp;
+ struct avc_header avc;
+ struct msghdr msg;
+ int sk, err = 0;
+ struct iovec pdu[iov_cnt + 2];
+ int i;
+ size_t len = sizeof(avctp) + sizeof(avc);
+
+ DBG("");
+
+ pdu[0].iov_base = &avctp;
+ pdu[0].iov_len = sizeof(avctp);
+ pdu[1].iov_base = &avc;
+ pdu[1].iov_len = sizeof(avc);
+
+ for (i = 0; i < iov_cnt; i++) {
+ pdu[i + 2].iov_base = iov[i].iov_base;
+ pdu[i + 2].iov_len = iov[i].iov_len;
+ len += iov[i].iov_len;
+ }
+
+ if (control->omtu < len)
+ return -EOVERFLOW;
+
+ sk = g_io_channel_unix_get_fd(control->io);
+
+ memset(&avctp, 0, sizeof(avctp));
+
+ avctp.transaction = transaction;
+ avctp.packet_type = AVCTP_PACKET_SINGLE;
+ avctp.cr = cr;
+ avctp.pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ memset(&avc, 0, sizeof(avc));
+
+ avc.code = code;
+ avc.subunit_type = subunit;
+ avc.opcode = opcode;
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = pdu;
+ msg.msg_iovlen = iov_cnt + 2;
+
+ if (sendmsg(sk, &msg, 0) < 0)
+ err = -errno;
+
+ return err;
+}
+
+static int avctp_browsing_send(struct avctp_channel *browsing,
+ uint8_t transaction, uint8_t cr,
+ const struct iovec *iov, int iov_cnt)
+{
+ struct avctp_header avctp;
+ struct msghdr msg;
+ struct iovec pdu[iov_cnt + 1];
+ int sk, err = 0;
+ int i;
+ size_t len = sizeof(avctp);
+
+ 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;
+ len += iov[i].iov_len;
+ }
+
+ pdu[0].iov_base = &avctp;
+ pdu[0].iov_len = sizeof(avctp);
+
+ if (browsing->omtu < len)
+ return -EOVERFLOW;
+
+ sk = g_io_channel_unix_get_fd(browsing->io);
+
+ memset(&avctp, 0, sizeof(avctp));
+
+ avctp.transaction = transaction;
+ avctp.packet_type = AVCTP_PACKET_SINGLE;
+ avctp.cr = cr;
+ avctp.pid = htons(AV_REMOTE_SVCLASS_ID);
+
+ memset(&msg, 0, sizeof(msg));
+ msg.msg_iov = pdu;
+ msg.msg_iovlen = iov_cnt + 1;
+
+ if (sendmsg(sk, &msg, 0) < 0)
+ err = -errno;
+
+ return err;
+}
+
+static void control_req_destroy(void *data)
+{
+ struct avctp_control_req *req = data;
+ struct avctp_pending_req *p = req->p;
+ struct avctp *session = p->chan->session;
+ int i;
+
+ if (p->err == 0 || req->func == NULL)
+ goto done;
+
+ req->func(session, AVC_CTYPE_REJECTED, req->subunit, NULL, 0,
+ req->user_data);
+
+done:
+ for (i = 0; i < req->iov_cnt; i++)
+ g_free(req->iov[i].iov_base);
+
+ g_free(req->iov);
+ g_free(req);
+}
+
+static void browsing_req_destroy(void *data)
+{
+ struct avctp_browsing_req *req = data;
+ struct avctp_pending_req *p = req->p;
+ struct avctp *session = p->chan->session;
+ int i;
+
+ if (p->err == 0 || req->func == NULL)
+ goto done;
+
+ req->func(session, NULL, 0, req->user_data);
+
+done:
+ for (i = 0; i < req->iov_cnt; i++)
+ g_free(req->iov[i].iov_base);
+
+ g_free(req->iov);
+ g_free(req);
+}
+
+static gboolean req_timeout(gpointer user_data)
+{
+ struct avctp_channel *chan = user_data;
+ struct avctp_pending_req *p = chan->p;
+
+ DBG("transaction %u", p->transaction);
+
+ p->timeout = 0;
+ p->err = -ETIMEDOUT;
+
+ pending_destroy(p, NULL);
+ chan->p = NULL;
+
+ if (chan->process_id == 0)
+ chan->process_id = g_idle_add(process_queue, chan);
+
+ return FALSE;
+}
+
+static int process_control(void *data)
+{
+ struct avctp_control_req *req = data;
+ struct avctp_pending_req *p = req->p;
+
+ return avctp_send(p->chan, p->transaction, AVCTP_COMMAND, req->code,
+ req->subunit, req->op, req->iov, req->iov_cnt);
+}
+
+static int process_browsing(void *data)
+{
+ struct avctp_browsing_req *req = data;
+ struct avctp_pending_req *p = req->p;
+
+ return avctp_browsing_send(p->chan, p->transaction, AVCTP_COMMAND,
+ req->iov, req->iov_cnt);
+}
+
+static gboolean process_queue(void *user_data)
+{
+ struct avctp_channel *chan = user_data;
+ struct avctp_pending_req *p = chan->p;
+
+ chan->process_id = 0;
+
+ if (p != NULL)
+ return FALSE;
+
+ while ((p = g_queue_pop_head(chan->queue))) {
+
+ if (p->process(p->data) == 0)
+ break;
+
+ pending_destroy(p, NULL);
+ }
+
+ if (p == NULL)
+ return FALSE;
+
+ chan->p = p;
+ p->timeout = g_timeout_add_seconds(2, req_timeout, chan);
+
+ return FALSE;
+
+}
+
+static struct avctp *avctp_ref(struct avctp *session)
+{
+ __sync_fetch_and_add(&session->ref, 1);
+
+ DBG("%p: ref=%d", session, session->ref);
+
+ return session;
+}
+
+static void avctp_unref(struct avctp *session)
+{
+ DBG("%p: ref=%d", session, session->ref);
+
+ if (__sync_sub_and_fetch(&session->ref, 1))
+ return;
+
+ if (session->browsing)
+ avctp_channel_destroy(session->browsing);
+
+ if (session->control)
+ avctp_channel_destroy(session->control);
+
+ if (session->destroy)
+ session->destroy(session->data);
+
+ g_free(session->handler);
+
+ if (session->key.timer > 0)
+ g_source_remove(session->key.timer);
+
+ if (session->uinput >= 0) {
+ DBG("AVCTP: closing uinput");
+
+ ioctl(session->uinput, UI_DEV_DESTROY);
+ close(session->uinput);
+ session->uinput = -1;
+ }
+
+ g_free(session);
+}
+
+static void control_response(struct avctp_channel *control,
+ struct avctp_header *avctp,
+ struct avc_header *avc,
+ uint8_t *operands,
+ size_t operand_count)
+{
+ struct avctp_pending_req *p = control->p;
+ struct avctp_control_req *req;
+ GSList *l;
+
+ if (p && p->transaction == avctp->transaction) {
+ control->processed = g_slist_prepend(control->processed, p);
+
+ if (p->timeout > 0) {
+ g_source_remove(p->timeout);
+ p->timeout = 0;
+ }
+
+ control->p = NULL;
+
+ if (control->process_id == 0)
+ control->process_id = g_idle_add(process_queue,
+ control);
+ }
+
+ avctp_ref(control->session);
+
+ for (l = control->processed; l; l = l->next) {
+ p = l->data;
+ req = p->data;
+
+ if (p->transaction != avctp->transaction)
+ continue;
+
+ if (req->func && req->func(control->session, avc->code,
+ avc->subunit_type,
+ operands, operand_count,
+ req->user_data))
+ break;
+
+ control->processed = g_slist_remove(control->processed, p);
+ pending_destroy(p, NULL);
+
+ break;
+ }
+
+ avctp_unref(control->session);
+}
+
+static void browsing_response(struct avctp_channel *browsing,
+ struct avctp_header *avctp,
+ uint8_t *operands,
+ size_t operand_count)
+{
+ struct avctp_pending_req *p = browsing->p;
+ struct avctp_browsing_req *req;
+ GSList *l;
+
+ if (p && p->transaction == avctp->transaction) {
+ browsing->processed = g_slist_prepend(browsing->processed, p);
+
+ if (p->timeout > 0) {
+ g_source_remove(p->timeout);
+ p->timeout = 0;
+ }
+
+ browsing->p = NULL;
+
+ if (browsing->process_id == 0)
+ browsing->process_id = g_idle_add(process_queue,
+ browsing);
+ }
+
+ avctp_ref(browsing->session);
+
+ for (l = browsing->processed; l; l = l->next) {
+ p = l->data;
+ req = p->data;
+
+ if (p->transaction != avctp->transaction)
+ continue;
+
+ if (req->func && req->func(browsing->session, operands,
+ operand_count, req->user_data))
+ break;
+
+ browsing->processed = g_slist_remove(browsing->processed, p);
+ pending_destroy(p, NULL);
+
+ break;
+ }
+
+ avctp_unref(browsing->session);
+}
+
+static gboolean session_browsing_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avctp *session = data;
+ struct avctp_channel *browsing = session->browsing;
+ uint8_t *buf = browsing->buffer;
+ uint8_t *operands;
+ struct avctp_header *avctp;
+ int sock, ret, packet_size, operand_count;
+ struct avctp_browsing_pdu_handler *handler;
+
+ if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+ goto failed;
+
+ sock = g_io_channel_unix_get_fd(chan);
+
+ ret = read(sock, buf, browsing->imtu);
+ if (ret <= 0)
+ goto failed;
+
+ if (ret < AVCTP_HEADER_LENGTH) {
+ error("Too small AVCTP packet");
+ goto failed;
+ }
+
+ avctp = (struct avctp_header *) buf;
+
+ if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
+ error("Invalid packet type");
+ goto failed;
+ }
+
+ operands = buf + AVCTP_HEADER_LENGTH;
+ ret -= AVCTP_HEADER_LENGTH;
+ operand_count = ret;
+
+ if (avctp->cr == AVCTP_RESPONSE) {
+ browsing_response(browsing, avctp, operands, operand_count);
+ return TRUE;
+ }
+
+ packet_size = AVCTP_HEADER_LENGTH;
+ avctp->cr = AVCTP_RESPONSE;
+
+ handler = g_slist_nth_data(browsing->handlers, 0);
+ if (handler == NULL) {
+ DBG("handler not found");
+ /* FIXME: Add general reject */
+ /* packet_size += avrcp_browsing_general_reject(operands); */
+ goto send;
+ }
+
+ ret = handler->cb(session, avctp->transaction, operands, operand_count,
+ handler->user_data);
+ if (ret < 0) {
+ if (ret == -EAGAIN)
+ return TRUE;
+ goto failed;
+ }
+
+ packet_size += ret;
+
+send:
+ if (packet_size != 0) {
+ ret = write(sock, buf, packet_size);
+ if (ret != packet_size)
+ goto failed;
+ }
+
+ return TRUE;
+
+failed:
+ DBG("AVCTP Browsing: disconnected");
+
+ avctp_channel_destroy(session->browsing);
+ session->browsing = NULL;
+
+ return FALSE;
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond, gpointer data)
+{
+ struct avctp *session = data;
+ struct avctp_channel *control = session->control;
+ uint8_t *buf = control->buffer;
+ uint8_t *operands, code, subunit;
+ struct avctp_header *avctp;
+ struct avc_header *avc;
+ int packet_size, operand_count, sock;
+ struct avctp_pdu_handler *handler;
+ ssize_t ret;
+
+ if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
+ goto failed;
+
+ sock = g_io_channel_unix_get_fd(chan);
+
+ ret = read(sock, buf, control->imtu);
+ if (ret <= 0)
+ goto failed;
+
+ if (ret < AVCTP_HEADER_LENGTH) {
+ error("Too small AVCTP packet");
+ goto failed;
+ }
+
+ avctp = (struct avctp_header *) buf;
+
+ ret -= AVCTP_HEADER_LENGTH;
+ if (ret < AVC_HEADER_LENGTH) {
+ error("Too small AVC packet");
+ goto failed;
+ }
+
+ avc = (struct avc_header *) (buf + AVCTP_HEADER_LENGTH);
+
+ ret -= AVC_HEADER_LENGTH;
+
+ operands = (uint8_t *) avc + AVC_HEADER_LENGTH;
+ operand_count = ret;
+
+ if (avctp->cr == AVCTP_RESPONSE) {
+ control_response(control, avctp, avc, operands, operand_count);
+ return TRUE;
+ }
+
+ packet_size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH;
+ avctp->cr = AVCTP_RESPONSE;
+
+ if (avctp->packet_type != AVCTP_PACKET_SINGLE) {
+ avc->code = AVC_CTYPE_NOT_IMPLEMENTED;
+ goto done;
+ }
+
+ if (avctp->pid != htons(AV_REMOTE_SVCLASS_ID)) {
+ avctp->ipid = 1;
+ packet_size = AVCTP_HEADER_LENGTH;
+ goto done;
+ }
+
+ handler = find_handler(control->handlers, avc->opcode);
+ if (!handler) {
+ DBG("handler not found for 0x%02x", avc->opcode);
+ avc->code = AVC_CTYPE_REJECTED;
+ goto done;
+ }
+
+ code = avc->code;
+ subunit = avc->subunit_type;
+
+ ret = handler->cb(session, avctp->transaction, &code,
+ &subunit, operands, operand_count,
+ handler->user_data);
+ if (ret < 0) {
+ if (ret == -EAGAIN)
+ return TRUE;
+ goto failed;
+ }
+
+ packet_size += ret;
+ avc->code = code;
+ avc->subunit_type = subunit;
+
+done:
+ ret = write(sock, buf, packet_size);
+ if (ret != packet_size)
+ goto failed;
+
+ return TRUE;
+
+failed:
+ DBG("AVCTP session %p got disconnected", session);
+ avctp_shutdown(session);
+ return FALSE;
+}
+
+static int uinput_create(const char *name)
+{
+ struct uinput_user_dev dev;
+ int fd, err, i;
+
+ fd = open("/dev/uinput", O_RDWR);
+ if (fd < 0) {
+ fd = open("/dev/input/uinput", O_RDWR);
+ if (fd < 0) {
+ fd = open("/dev/misc/uinput", O_RDWR);
+ if (fd < 0) {
+ err = -errno;
+ error("Can't open input device: %s (%d)",
+ strerror(-err), -err);
+ return err;
+ }
+ }
+ }
+
+ memset(&dev, 0, sizeof(dev));
+ if (name)
+ strncpy(dev.name, name, UINPUT_MAX_NAME_SIZE - 1);
+
+ dev.id.bustype = BUS_BLUETOOTH;
+ dev.id.vendor = 0x0000;
+ dev.id.product = 0x0000;
+ dev.id.version = 0x0000;
+
+ if (write(fd, &dev, sizeof(dev)) < 0) {
+ err = -errno;
+ error("Can't write device information: %s (%d)",
+ strerror(-err), -err);
+ close(fd);
+ return err;
+ }
+
+ ioctl(fd, UI_SET_EVBIT, EV_KEY);
+ ioctl(fd, UI_SET_EVBIT, EV_REL);
+ ioctl(fd, UI_SET_EVBIT, EV_REP);
+ ioctl(fd, UI_SET_EVBIT, EV_SYN);
+
+ for (i = 0; key_map[i].name != NULL; i++)
+ ioctl(fd, UI_SET_KEYBIT, key_map[i].uinput);
+
+ if (ioctl(fd, UI_DEV_CREATE, NULL) < 0) {
+ err = -errno;
+ error("Can't create uinput device: %s (%d)",
+ strerror(-err), -err);
+ close(fd);
+ return err;
+ }
+
+ return fd;
+}
+
+int avctp_init_uinput(struct avctp *session, const char *name,
+ const char *address)
+{
+ if (g_str_equal(name, "Nokia CK-20W")) {
+ session->key_quirks[AVC_FORWARD] |= QUIRK_NO_RELEASE;
+ session->key_quirks[AVC_BACKWARD] |= QUIRK_NO_RELEASE;
+ session->key_quirks[AVC_PLAY] |= QUIRK_NO_RELEASE;
+ session->key_quirks[AVC_PAUSE] |= QUIRK_NO_RELEASE;
+ }
+
+ session->uinput = uinput_create(address);
+ if (session->uinput < 0) {
+ error("AVCTP: failed to init uinput for %s", address);
+ return session->uinput;
+ }
+
+ return 0;
+}
+
+static struct avctp_channel *avctp_channel_create(struct avctp *session, int fd,
+ size_t imtu, size_t omtu,
+ avctp_destroy_cb_t destroy)
+{
+ struct avctp_channel *chan;
+
+ chan = g_new0(struct avctp_channel, 1);
+ chan->session = session;
+ chan->io = g_io_channel_unix_new(fd);
+ chan->queue = g_queue_new();
+ chan->imtu = imtu;
+ chan->omtu = omtu;
+ chan->buffer = g_malloc0(MAX(imtu, omtu));
+ chan->destroy = destroy;
+
+ return chan;
+}
+
+static void handler_free(void *data)
+{
+ struct avctp_browsing_pdu_handler *handler = data;
+
+ if (handler->destroy)
+ handler->destroy(handler->user_data);
+
+ g_free(data);
+}
+
+static void avctp_destroy_browsing(void *data)
+{
+ struct avctp_channel *chan = data;
+
+ g_slist_free_full(chan->handlers, handler_free);
+
+ chan->handlers = NULL;
+}
+
+static struct avctp_pending_req *pending_create(struct avctp_channel *chan,
+ avctp_process_cb process,
+ void *data,
+ avctp_destroy_cb_t destroy)
+{
+ struct avctp_pending_req *p;
+ GSList *l, *tmp;
+
+ if (!chan->processed)
+ goto done;
+
+ tmp = g_slist_copy(chan->processed);
+
+ /* Find first unused transaction id */
+ for (l = tmp; l; l = g_slist_next(l)) {
+ struct avctp_pending_req *req = l->data;
+
+ if (req->transaction == chan->transaction) {
+ chan->transaction++;
+ chan->transaction %= 16;
+ tmp = g_slist_delete_link(tmp, l);
+ l = tmp;
+ }
+ }
+
+ g_slist_free(tmp);
+
+done:
+ p = g_new0(struct avctp_pending_req, 1);
+ p->chan = chan;
+ p->transaction = chan->transaction;
+ p->process = process;
+ p->data = data;
+ p->destroy = destroy;
+
+ chan->transaction++;
+ chan->transaction %= 16;
+
+ return p;
+}
+
+static int avctp_send_req(struct avctp *session, uint8_t code, uint8_t subunit,
+ uint8_t opcode, const struct iovec *iov, int iov_cnt,
+ avctp_rsp_cb func, void *user_data)
+{
+ struct avctp_channel *control = session->control;
+ struct avctp_pending_req *p;
+ struct avctp_control_req *req;
+ struct iovec *pdu;
+ int i;
+
+ if (control == NULL)
+ return -ENOTCONN;
+
+ pdu = g_new0(struct iovec, iov_cnt);
+
+ for (i = 0; i < iov_cnt; i++) {
+ pdu[i].iov_len = iov[i].iov_len;
+ pdu[i].iov_base = util_memdup(iov[i].iov_base, iov[i].iov_len);
+ }
+
+ req = g_new0(struct avctp_control_req, 1);
+ req->code = code;
+ req->subunit = subunit;
+ req->op = opcode;
+ req->func = func;
+ req->iov = pdu;
+ req->iov_cnt = iov_cnt;
+ req->user_data = user_data;
+
+ p = pending_create(control, process_control, req, control_req_destroy);
+
+ req->p = p;
+
+ g_queue_push_tail(control->queue, p);
+
+ if (control->process_id == 0)
+ control->process_id = g_idle_add(process_queue, control);
+
+ return 0;
+}
+
+int avctp_send_browsing_req(struct avctp *session,
+ const struct iovec *iov, int iov_cnt,
+ avctp_browsing_rsp_cb func, void *user_data)
+{
+ struct avctp_channel *browsing = session->browsing;
+ struct avctp_pending_req *p;
+ struct avctp_browsing_req *req;
+ struct iovec *pdu;
+ int i;
+
+ if (browsing == NULL)
+ return -ENOTCONN;
+
+ pdu = g_new0(struct iovec, iov_cnt);
+
+ for (i = 0; i < iov_cnt; i++) {
+ pdu[i].iov_len = iov[i].iov_len;
+ pdu[i].iov_base = util_memdup(iov[i].iov_base, iov[i].iov_len);
+ }
+
+ req = g_new0(struct avctp_browsing_req, 1);
+ req->func = func;
+ req->iov = pdu;
+ req->iov_cnt = iov_cnt;
+ req->user_data = user_data;
+
+ p = pending_create(browsing, process_browsing, req,
+ browsing_req_destroy);
+
+ req->p = p;
+
+ g_queue_push_tail(browsing->queue, p);
+
+ /* Connection did not complete, delay process of the request */
+ if (browsing->watch == 0)
+ return 0;
+
+ if (browsing->process_id == 0)
+ browsing->process_id = g_idle_add(process_queue, browsing);
+
+ return 0;
+}
+
+int avctp_send_browsing(struct avctp *session, uint8_t transaction,
+ const struct iovec *iov, int iov_cnt)
+{
+ struct avctp_channel *browsing = session->browsing;
+
+ if (browsing == NULL)
+ return -ENOTCONN;
+
+ return avctp_browsing_send(browsing, transaction, AVCTP_RESPONSE,
+ iov, iov_cnt);
+}
+
+static const char *op2str(uint8_t op)
+{
+ int i;
+
+ for (i = 0; key_map[i].name != NULL; i++) {
+ if ((op & 0x7F) == key_map[i].avc)
+ return key_map[i].name;
+ }
+
+ return "UNKNOWN";
+}
+
+static int avctp_passthrough_press(struct avctp *session, uint8_t op,
+ uint8_t *params, size_t params_len)
+{
+ struct iovec iov[2];
+ int iov_cnt;
+ uint8_t operands[2];
+
+ DBG("%s", op2str(op));
+
+ iov[0].iov_base = operands;
+ iov[0].iov_len = sizeof(operands);
+
+ /* Button pressed */
+ operands[0] = op & 0x7f;
+
+ if (params_len > 0) {
+ iov[1].iov_base = params;
+ iov[1].iov_len = params_len;
+ iov_cnt = 2;
+ operands[1] = params_len;
+ } else {
+ iov_cnt = 1;
+ operands[1] = 0;
+ }
+
+ return avctp_send_req(session, AVC_CTYPE_CONTROL,
+ AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH,
+ iov, iov_cnt, avctp_passthrough_rsp, NULL);
+}
+
+static int avctp_passthrough_release(struct avctp *session, uint8_t op,
+ uint8_t *params, size_t params_len)
+{
+ struct iovec iov[2];
+ int iov_cnt;
+ uint8_t operands[2];
+
+ DBG("%s", op2str(op));
+
+ iov[0].iov_base = operands;
+ iov[0].iov_len = sizeof(operands);
+
+ /* Button released */
+ operands[0] = op | 0x80;
+
+ if (params_len > 0) {
+ iov[1].iov_base = params;
+ iov[1].iov_len = params_len;
+ iov_cnt = 2;
+ operands[1] = params_len;
+ } else {
+ iov_cnt = 1;
+ operands[1] = 0;
+ }
+
+ return avctp_send_req(session, AVC_CTYPE_CONTROL,
+ AVC_SUBUNIT_PANEL, AVC_OP_PASSTHROUGH,
+ iov, iov_cnt, NULL, NULL);
+}
+
+static gboolean repeat_timeout(gpointer user_data)
+{
+ struct avctp *session = user_data;
+
+ avctp_passthrough_release(session, session->key.op, session->key.params,
+ session->key.params_len);
+ avctp_passthrough_press(session, session->key.op, session->key.params,
+ session->key.params_len);
+
+ return TRUE;
+}
+
+static void release_pressed(struct avctp *session)
+{
+ avctp_passthrough_release(session, session->key.op, session->key.params,
+ session->key.params_len);
+
+ if (session->key.timer > 0)
+ g_source_remove(session->key.timer);
+
+ session->key.timer = 0;
+}
+
+static bool set_pressed(struct avctp *session, uint8_t op, uint8_t *params,
+ size_t params_len)
+{
+ if (session->key.timer > 0) {
+ if (session->key.op == op)
+ return TRUE;
+ release_pressed(session);
+ }
+
+ if (op != AVC_FAST_FORWARD && op != AVC_REWIND)
+ return FALSE;
+
+ session->key.op = op;
+ session->key.params = params;
+ session->key.params_len = params_len;
+ session->key.timer = g_timeout_add_seconds(AVC_PRESS_TIMEOUT,
+ repeat_timeout,
+ session);
+
+ return TRUE;
+}
+
+static gboolean avctp_passthrough_rsp(struct avctp *session, uint8_t code,
+ uint8_t subunit, uint8_t *operands,
+ size_t operand_count, void *user_data)
+{
+ uint8_t *params;
+ size_t params_len;
+
+ DBG("code 0x%02x operand_count %zd", code, operand_count);
+
+ if (code != AVC_CTYPE_ACCEPTED)
+ return FALSE;
+
+ if (operands[0] == AVC_VENDOR_UNIQUE) {
+ params = &operands[2];
+ params_len = operand_count - 2;
+ } else {
+ params = NULL;
+ params_len = 0;
+ }
+
+ if (set_pressed(session, operands[0], params, params_len))
+ return FALSE;
+
+ avctp_passthrough_release(session, operands[0], params, params_len);
+
+ return FALSE;
+}
+
+int avctp_send_passthrough(struct avctp *session, uint8_t op, uint8_t *params,
+ size_t params_len)
+{
+ /* Auto release if key pressed */
+ if (session->key.timer > 0)
+ release_pressed(session);
+
+ return avctp_passthrough_press(session, op, params, params_len);
+}
+
+int avctp_send_vendor(struct avctp *session, uint8_t transaction,
+ uint8_t code, uint8_t subunit,
+ const struct iovec *iov, int iov_cnt)
+{
+ struct avctp_channel *control = session->control;
+
+ if (control == NULL)
+ return -ENOTCONN;
+
+ return avctp_send(control, transaction, AVCTP_RESPONSE, code, subunit,
+ AVC_OP_VENDORDEP, iov, iov_cnt);
+}
+
+int avctp_send_vendor_req(struct avctp *session, uint8_t code, uint8_t subunit,
+ const struct iovec *iov, int iov_cnt,
+ avctp_rsp_cb func, void *user_data)
+{
+ return avctp_send_req(session, code, subunit, AVC_OP_VENDORDEP, iov,
+ iov_cnt, func, user_data);
+}
+
+unsigned int avctp_register_passthrough_handler(struct avctp *session,
+ avctp_passthrough_cb cb,
+ void *user_data)
+{
+ struct avctp_channel *control = session->control;
+ struct avctp_passthrough_handler *handler;
+ static unsigned int id = 0;
+
+ if (control == NULL || session->handler != NULL)
+ return 0;
+
+ handler = g_new(struct avctp_passthrough_handler, 1);
+ handler->cb = cb;
+ handler->user_data = user_data;
+ handler->id = ++id;
+
+ session->handler = handler;
+
+ return handler->id;
+}
+
+bool avctp_unregister_passthrough_handler(struct avctp *session,
+ unsigned int id)
+{
+ if (session->handler == NULL)
+ return false;
+
+ if (session->handler->id != id)
+ return false;
+
+ g_free(session->handler);
+ session->handler = NULL;
+ return true;
+}
+
+unsigned int avctp_register_pdu_handler(struct avctp *session, uint8_t opcode,
+ avctp_control_pdu_cb cb,
+ void *user_data)
+{
+ struct avctp_channel *control = session->control;
+ struct avctp_pdu_handler *handler;
+ static unsigned int id = 0;
+
+ if (control == NULL)
+ return 0;
+
+ handler = find_handler(control->handlers, opcode);
+ if (handler)
+ return 0;
+
+ handler = g_new(struct avctp_pdu_handler, 1);
+ handler->opcode = opcode;
+ handler->cb = cb;
+ handler->user_data = user_data;
+ handler->id = ++id;
+
+ control->handlers = g_slist_append(control->handlers, handler);
+
+ return handler->id;
+}
+
+unsigned int avctp_register_browsing_pdu_handler(struct avctp *session,
+ avctp_browsing_pdu_cb cb,
+ void *user_data,
+ avctp_destroy_cb_t destroy)
+{
+ struct avctp_channel *browsing = session->browsing;
+ struct avctp_browsing_pdu_handler *handler;
+ static unsigned int id = 0;
+
+ if (browsing == NULL)
+ return 0;
+
+ if (browsing->handlers != NULL)
+ return 0;
+
+ handler = g_new(struct avctp_browsing_pdu_handler, 1);
+ handler->cb = cb;
+ handler->user_data = user_data;
+ handler->id = ++id;
+ handler->destroy = destroy;
+
+ browsing->handlers = g_slist_append(browsing->handlers, handler);
+
+ return handler->id;
+}
+
+bool avctp_unregister_pdu_handler(struct avctp *session, unsigned int id)
+{
+ struct avctp_channel *control = session->control;
+ GSList *l;
+
+ if (!control)
+ return false;
+
+ for (l = control->handlers; l; l = g_slist_next(l)) {
+ struct avctp_pdu_handler *handler = l->data;
+
+ if (handler->id != id)
+ continue;
+
+ control->handlers = g_slist_remove(control->handlers, handler);
+ g_free(handler);
+ return true;
+ }
+
+ return false;
+}
+
+bool avctp_unregister_browsing_pdu_handler(struct avctp *session,
+ unsigned int id)
+{
+ struct avctp_channel *browsing = session->browsing;
+ GSList *l;
+
+ if (browsing == NULL)
+ return false;
+
+ for (l = browsing->handlers; l; l = g_slist_next(l)) {
+ struct avctp_browsing_pdu_handler *handler = l->data;
+
+ if (handler->id != id)
+ continue;
+
+ browsing->handlers = g_slist_remove(browsing->handlers,
+ handler);
+ g_free(handler);
+ return true;
+ }
+
+ return false;
+}
+
+struct avctp *avctp_new(int fd, size_t imtu, size_t omtu, uint16_t version)
+{
+ struct avctp *session;
+ struct avctp_channel *control;
+ GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+
+ session = g_new0(struct avctp, 1);
+ session->version = version;
+
+ control = avctp_channel_create(session, fd, imtu, omtu, NULL);
+ if (!control) {
+ g_free(session);
+ return NULL;
+ }
+
+ session->uinput = -1;
+ session->control = control;
+ session->passthrough_id = avctp_register_pdu_handler(session,
+ AVC_OP_PASSTHROUGH,
+ handle_panel_passthrough,
+ NULL);
+ session->unit_id = avctp_register_pdu_handler(session,
+ AVC_OP_UNITINFO,
+ handle_unit_info,
+ NULL);
+ session->subunit_id = avctp_register_pdu_handler(session,
+ AVC_OP_SUBUNITINFO,
+ handle_subunit_info,
+ NULL);
+
+ control->watch = g_io_add_watch(session->control->io, cond,
+ (GIOFunc) session_cb, session);
+
+ return avctp_ref(session);
+}
+
+int avctp_connect_browsing(struct avctp *session, int fd, size_t imtu,
+ size_t omtu)
+{
+ struct avctp_channel *browsing;
+ GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+
+ if (session->browsing)
+ return -EISCONN;
+
+ browsing = avctp_channel_create(session, fd, imtu, omtu,
+ avctp_destroy_browsing);
+ if (!browsing)
+ return -EINVAL;
+
+ session->browsing = browsing;
+ browsing->watch = g_io_add_watch(session->browsing->io, cond,
+ (GIOFunc) session_browsing_cb, session);
+
+ return 0;
+}
+
+void avctp_set_destroy_cb(struct avctp *session, avctp_destroy_cb_t cb,
+ void *user_data)
+{
+ session->destroy = cb;
+ session->data = user_data;
+}
+
+void avctp_shutdown(struct avctp *session)
+{
+ if (!session)
+ return;
+
+ avctp_unref(session);
+}
diff --git a/unit/avctp.h b/unit/avctp.h
new file mode 100644
index 0000000..e82e024
--- /dev/null
+++ b/unit/avctp.h
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ */
+
+#define AVCTP_CONTROL_PSM 23
+#define AVCTP_BROWSING_PSM 27
+
+#define AVCTP_HEADER_LENGTH 3
+#define AVC_HEADER_LENGTH 3
+
+#define AVC_DATA_OFFSET AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH
+#define AVC_DATA_MTU 512
+
+/* ctype entries */
+#define AVC_CTYPE_CONTROL 0x0
+#define AVC_CTYPE_STATUS 0x1
+#define AVC_CTYPE_NOTIFY 0x3
+#define AVC_CTYPE_NOT_IMPLEMENTED 0x8
+#define AVC_CTYPE_ACCEPTED 0x9
+#define AVC_CTYPE_REJECTED 0xA
+#define AVC_CTYPE_STABLE 0xC
+#define AVC_CTYPE_CHANGED 0xD
+#define AVC_CTYPE_INTERIM 0xF
+
+/* opcodes */
+#define AVC_OP_VENDORDEP 0x00
+#define AVC_OP_UNITINFO 0x30
+#define AVC_OP_SUBUNITINFO 0x31
+#define AVC_OP_PASSTHROUGH 0x7c
+
+/* subunits of interest */
+#define AVC_SUBUNIT_PANEL 0x09
+
+/* operands in passthrough commands */
+#define AVC_SELECT 0x00
+#define AVC_UP 0x01
+#define AVC_DOWN 0x02
+#define AVC_LEFT 0x03
+#define AVC_RIGHT 0x04
+#define AVC_ROOT_MENU 0x09
+#define AVC_CONTENTS_MENU 0x0b
+#define AVC_FAVORITE_MENU 0x0c
+#define AVC_EXIT 0x0d
+#define AVC_ON_DEMAND_MENU 0x0e
+#define AVC_APPS_MENU 0x0f
+#define AVC_0 0x20
+#define AVC_1 0x21
+#define AVC_2 0x22
+#define AVC_3 0x23
+#define AVC_4 0x24
+#define AVC_5 0x25
+#define AVC_6 0x26
+#define AVC_7 0x27
+#define AVC_8 0x28
+#define AVC_9 0x29
+#define AVC_DOT 0x2a
+#define AVC_ENTER 0x2b
+#define AVC_CHANNEL_UP 0x30
+#define AVC_CHANNEL_DOWN 0x31
+#define AVC_CHANNEL_PREVIOUS 0x32
+#define AVC_INPUT_SELECT 0x34
+#define AVC_INFO 0x35
+#define AVC_HELP 0x36
+#define AVC_PAGE_UP 0x37
+#define AVC_PAGE_DOWN 0x38
+#define AVC_LOCK 0x3a
+#define AVC_POWER 0x40
+#define AVC_VOLUME_UP 0x41
+#define AVC_VOLUME_DOWN 0x42
+#define AVC_MUTE 0x43
+#define AVC_PLAY 0x44
+#define AVC_STOP 0x45
+#define AVC_PAUSE 0x46
+#define AVC_RECORD 0x47
+#define AVC_REWIND 0x48
+#define AVC_FAST_FORWARD 0x49
+#define AVC_EJECT 0x4a
+#define AVC_FORWARD 0x4b
+#define AVC_BACKWARD 0x4c
+#define AVC_LIST 0x4d
+#define AVC_F1 0x71
+#define AVC_F2 0x72
+#define AVC_F3 0x73
+#define AVC_F4 0x74
+#define AVC_F5 0x75
+#define AVC_F6 0x76
+#define AVC_F7 0x77
+#define AVC_F8 0x78
+#define AVC_F9 0x79
+#define AVC_RED 0x7a
+#define AVC_GREEN 0x7b
+#define AVC_BLUE 0x7c
+#define AVC_YELLOW 0x7c
+
+#define AVC_VENDOR_UNIQUE 0x7e
+
+#define AVC_VENDOR_NEXT_GROUP 0x00
+#define AVC_VENDOR_PREV_GROUP 0x01
+
+struct avctp;
+
+typedef bool (*avctp_passthrough_cb) (struct avctp *session,
+ uint8_t op, bool pressed,
+ void *user_data);
+typedef ssize_t (*avctp_control_pdu_cb) (struct avctp *session,
+ uint8_t transaction, uint8_t *code,
+ uint8_t *subunit, uint8_t *operands,
+ size_t operand_count, void *user_data);
+typedef gboolean (*avctp_rsp_cb) (struct avctp *session, uint8_t code,
+ uint8_t subunit, uint8_t *operands,
+ size_t operand_count, void *user_data);
+typedef gboolean (*avctp_browsing_rsp_cb) (struct avctp *session,
+ uint8_t *operands, size_t operand_count,
+ void *user_data);
+typedef ssize_t (*avctp_browsing_pdu_cb) (struct avctp *session,
+ uint8_t transaction,
+ uint8_t *operands, size_t operand_count,
+ void *user_data);
+
+typedef void (*avctp_destroy_cb_t) (void *user_data);
+
+struct avctp *avctp_new(int fd, size_t imtu, size_t omtu, uint16_t version);
+void avctp_set_destroy_cb(struct avctp *session, avctp_destroy_cb_t cb,
+ void *user_data);
+
+int avctp_init_uinput(struct avctp *session, const char *name,
+ const char *address);
+int avctp_connect_browsing(struct avctp *session, int fd, size_t imtu,
+ size_t omtu);
+
+void avctp_shutdown(struct avctp *session);
+
+unsigned int avctp_register_passthrough_handler(struct avctp *session,
+ avctp_passthrough_cb cb,
+ void *user_data);
+bool avctp_unregister_passthrough_handler(struct avctp *session,
+ unsigned int id);
+
+unsigned int avctp_register_pdu_handler(struct avctp *session, uint8_t opcode,
+ avctp_control_pdu_cb cb,
+ void *user_data);
+bool avctp_unregister_pdu_handler(struct avctp *session, unsigned int id);
+
+unsigned int avctp_register_browsing_pdu_handler(struct avctp *session,
+ avctp_browsing_pdu_cb cb,
+ void *user_data,
+ avctp_destroy_cb_t destroy);
+bool avctp_unregister_browsing_pdu_handler(struct avctp *session,
+ unsigned int id);
+
+int avctp_send_passthrough(struct avctp *session, uint8_t op, uint8_t *params,
+ size_t params_len);
+int avctp_send_vendor(struct avctp *session, uint8_t transaction,
+ uint8_t code, uint8_t subunit,
+ const struct iovec *iov, int iov_cnt);
+int avctp_send_vendor_req(struct avctp *session, uint8_t code, uint8_t subunit,
+ const struct iovec *iov, int iov_cnt,
+ avctp_rsp_cb func, void *user_data);
+int avctp_send_browsing(struct avctp *session, uint8_t transaction,
+ const struct iovec *iov, int iov_cnt);
+int avctp_send_browsing_req(struct avctp *session,
+ const struct iovec *iov, int iov_cnt,
+ avctp_browsing_rsp_cb func, void *user_data);
diff --git a/unit/avdtp.c b/unit/avdtp.c
new file mode 100644
index 0000000..e046685
--- /dev/null
+++ b/unit/avdtp.c
+// SPDX-License-Identifier: GPL-2.0-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <unistd.h>
+#include <assert.h>
+#include <string.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+
+#include <glib.h>
+
+#include "lib/bluetooth.h"
+#include "src/log.h"
+#include "src/shared/util.h"
+#include "src/shared/queue.h"
+#include "avdtp.h"
+#include "../profiles/audio/a2dp-codecs.h"
+
+#define MAX_SEID 0x3E
+static uint64_t seids;
+
+#ifndef MAX
+# define MAX(x, y) ((x) > (y) ? (x) : (y))
+#endif
+
+#define AVDTP_DISCOVER 0x01
+#define AVDTP_GET_CAPABILITIES 0x02
+#define AVDTP_SET_CONFIGURATION 0x03
+#define AVDTP_GET_CONFIGURATION 0x04
+#define AVDTP_RECONFIGURE 0x05
+#define AVDTP_OPEN 0x06
+#define AVDTP_START 0x07
+#define AVDTP_CLOSE 0x08
+#define AVDTP_SUSPEND 0x09
+#define AVDTP_ABORT 0x0A
+#define AVDTP_SECURITY_CONTROL 0x0B
+#define AVDTP_GET_ALL_CAPABILITIES 0x0C
+#define AVDTP_DELAY_REPORT 0x0D
+
+#define AVDTP_PKT_TYPE_SINGLE 0x00
+#define AVDTP_PKT_TYPE_START 0x01
+#define AVDTP_PKT_TYPE_CONTINUE 0x02
+#define AVDTP_PKT_TYPE_END 0x03
+
+#define AVDTP_MSG_TYPE_COMMAND 0x00
+#define AVDTP_MSG_TYPE_GEN_REJECT 0x01
+#define AVDTP_MSG_TYPE_ACCEPT 0x02
+#define AVDTP_MSG_TYPE_REJECT 0x03
+
+#define REQ_TIMEOUT 6
+#define ABORT_TIMEOUT 2
+#define DISCONNECT_TIMEOUT 1
+#define START_TIMEOUT 1
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_common_header {
+ uint8_t message_type:2;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+} __attribute__ ((packed));
+
+struct avdtp_single_header {
+ uint8_t message_type:2;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+ uint8_t signal_id:6;
+ uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct avdtp_start_header {
+ uint8_t message_type:2;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+ uint8_t no_of_packets;
+ uint8_t signal_id:6;
+ uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct avdtp_continue_header {
+ uint8_t message_type:2;
+ uint8_t packet_type:2;
+ uint8_t transaction:4;
+} __attribute__ ((packed));
+
+struct seid_info {
+ uint8_t rfa0:1;
+ uint8_t inuse:1;
+ uint8_t seid:6;
+ uint8_t rfa2:3;
+ uint8_t type:1;
+ uint8_t media_type:4;
+} __attribute__ ((packed));
+
+struct seid {
+ uint8_t rfa0:2;
+ uint8_t seid:6;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avdtp_common_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t message_type:2;
+} __attribute__ ((packed));
+
+struct avdtp_single_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t message_type:2;
+ uint8_t rfa0:2;
+ uint8_t signal_id:6;
+} __attribute__ ((packed));
+
+struct avdtp_start_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t message_type:2;
+ uint8_t no_of_packets;
+ uint8_t rfa0:2;
+ uint8_t signal_id:6;
+} __attribute__ ((packed));
+
+struct avdtp_continue_header {
+ uint8_t transaction:4;
+ uint8_t packet_type:2;
+ uint8_t message_type:2;
+} __attribute__ ((packed));
+
+struct seid_info {
+ uint8_t seid:6;
+ uint8_t inuse:1;
+ uint8_t rfa0:1;
+ uint8_t media_type:4;
+ uint8_t type:1;
+ uint8_t rfa2:3;
+} __attribute__ ((packed));
+
+struct seid {
+ uint8_t seid:6;
+ uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+/* packets */
+
+struct discover_resp {
+ struct seid_info seps[0];
+} __attribute__ ((packed));
+
+struct getcap_resp {
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct start_req {
+ struct seid first_seid;
+ struct seid other_seids[0];
+} __attribute__ ((packed));
+
+struct suspend_req {
+ struct seid first_seid;
+ struct seid other_seids[0];
+} __attribute__ ((packed));
+
+struct seid_rej {
+ uint8_t error;
+} __attribute__ ((packed));
+
+struct conf_rej {
+ uint8_t category;
+ uint8_t error;
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct seid_req {
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+} __attribute__ ((packed));
+
+struct setconf_req {
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+ uint8_t rfa1:2;
+ uint8_t int_seid:6;
+
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct stream_rej {
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+ uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+
+ uint8_t serv_cap;
+ uint8_t serv_cap_len;
+
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct delay_req {
+ uint8_t rfa0:2;
+ uint8_t acp_seid:6;
+ uint16_t delay;
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct seid_req {
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+} __attribute__ ((packed));
+
+struct setconf_req {
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+ uint8_t int_seid:6;
+ uint8_t rfa1:2;
+
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct stream_rej {
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+ uint8_t error;
+} __attribute__ ((packed));
+
+struct reconf_req {
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+
+ uint8_t serv_cap;
+ uint8_t serv_cap_len;
+
+ uint8_t caps[0];
+} __attribute__ ((packed));
+
+struct delay_req {
+ uint8_t acp_seid:6;
+ uint8_t rfa0:2;
+ uint16_t delay;
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+struct in_buf {
+ gboolean active;
+ int no_of_packets;
+ uint8_t transaction;
+ uint8_t message_type;
+ uint8_t signal_id;
+ uint8_t buf[1024];
+ uint8_t data_size;
+};
+
+struct pending_req {
+ uint8_t transaction;
+ uint8_t signal_id;
+ void *data;
+ size_t data_size;
+ struct avdtp_stream *stream; /* Set if the request targeted a stream */
+ guint timeout;
+ gboolean collided;
+};
+
+struct avdtp_remote_sep {
+ uint8_t seid;
+ uint8_t type;
+ uint8_t media_type;
+ struct avdtp_service_capability *codec;
+ gboolean delay_reporting;
+ GSList *caps; /* of type struct avdtp_service_capability */
+ struct avdtp_stream *stream;
+};
+
+struct avdtp_local_sep {
+ avdtp_state_t state;
+ struct avdtp_stream *stream;
+ struct seid_info info;
+ uint8_t codec;
+ uint32_t vndcodec_vendor;
+ uint16_t vndcodec_codec;
+ gboolean delay_reporting;
+ GSList *caps;
+ struct avdtp_sep_ind *ind;
+ struct avdtp_sep_cfm *cfm;
+ void *user_data;
+};
+
+struct stream_callback {
+ avdtp_stream_state_cb cb;
+ void *user_data;
+ unsigned int id;
+};
+
+struct discover_callback {
+ unsigned int id;
+ avdtp_discover_cb_t cb;
+ void *user_data;
+};
+
+struct disconnect_callback {
+ unsigned int id;
+ avdtp_disconnect_cb_t cb;
+ void *user_data;
+};
+
+struct avdtp_stream {
+ GIOChannel *io;
+ uint16_t imtu;
+ uint16_t omtu;
+ struct avdtp *session;
+ struct avdtp_local_sep *lsep;
+ uint8_t rseid;
+ GSList *caps;
+ GSList *callbacks;
+ struct avdtp_service_capability *codec;
+ guint io_id; /* Transport GSource ID */
+ guint timer; /* Waiting for other side to close or open
+ * the transport channel */
+ gboolean open_acp; /* If we are in ACT role for Open */
+ gboolean close_int; /* If we are in INT role for Close */
+ gboolean abort_int; /* If we are in INT role for Abort */
+ guint start_timer; /* Wait START command timer */
+ gboolean delay_reporting;
+ uint16_t delay; /* AVDTP 1.3 Delay Reporting feature */
+ gboolean starting; /* only valid while sep state == OPEN */
+};
+
+/* Structure describing an AVDTP connection between two devices */
+
+struct avdtp {
+ unsigned int ref;
+
+ uint16_t version;
+
+ guint auth_id;
+
+ GIOChannel *io;
+ guint io_id;
+
+ GSList *seps; /* Elements of type struct avdtp_remote_sep * */
+ struct queue *lseps; /* Elements of type struct avdtp_local_sep * */
+
+ GSList *streams; /* Elements of type struct avdtp_stream * */
+
+ GSList *req_queue; /* Elements of type struct pending_req * */
+ GSList *prio_queue; /* Same as req_queue but is processed before it */
+
+ struct avdtp_stream *pending_open;
+
+ uint16_t imtu;
+ uint16_t omtu;
+
+ struct in_buf in;
+
+ char *buf;
+
+ struct discover_callback *discover;
+ struct pending_req *req;
+
+ GSList *disconnect;
+
+ bool shutdown;
+};
+
+static int send_request(struct avdtp *session, gboolean priority,
+ struct avdtp_stream *stream, uint8_t signal_id,
+ void *buffer, size_t size);
+static gboolean avdtp_parse_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ uint8_t transaction, uint8_t signal_id,
+ void *buf, int size);
+static gboolean avdtp_parse_rej(struct avdtp *session,
+ struct avdtp_stream *stream,
+ uint8_t transaction, uint8_t signal_id,
+ void *buf, int size);
+static int process_queue(struct avdtp *session);
+static void avdtp_sep_set_state(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ avdtp_state_t state);
+
+static const char *avdtp_statestr(avdtp_state_t state)
+{
+ switch (state) {
+ case AVDTP_STATE_IDLE:
+ return "IDLE";
+ case AVDTP_STATE_CONFIGURED:
+ return "CONFIGURED";
+ case AVDTP_STATE_OPEN:
+ return "OPEN";
+ case AVDTP_STATE_STREAMING:
+ return "STREAMING";
+ case AVDTP_STATE_CLOSING:
+ return "CLOSING";
+ case AVDTP_STATE_ABORTING:
+ return "ABORTING";
+ default:
+ return "<unknown state>";
+ }
+}
+
+static gboolean try_send(int sk, void *data, size_t len)
+{
+ int err;
+
+ do {
+ err = send(sk, data, len, 0);
+ } while (err < 0 && errno == EINTR);
+
+ if (err < 0) {
+ error("send: %s (%d)", strerror(errno), errno);
+ return FALSE;
+ } else if ((size_t) err != len) {
+ error("try_send: complete buffer not sent (%d/%zu bytes)",
+ err, len);
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+static gboolean avdtp_send(struct avdtp *session, uint8_t transaction,
+ uint8_t message_type, uint8_t signal_id,
+ void *data, size_t len)
+{
+ unsigned int cont_fragments, sent;
+ struct avdtp_start_header start;
+ struct avdtp_continue_header cont;
+ int sock;
+
+ if (session->io == NULL) {
+ error("avdtp_send: session is closed");
+ return FALSE;
+ }
+
+ sock = g_io_channel_unix_get_fd(session->io);
+
+ /* Single packet - no fragmentation */
+ if (sizeof(struct avdtp_single_header) + len <= session->omtu) {
+ struct avdtp_single_header single;
+
+ memset(&single, 0, sizeof(single));
+
+ single.transaction = transaction;
+ single.packet_type = AVDTP_PKT_TYPE_SINGLE;
+ single.message_type = message_type;
+ single.signal_id = signal_id;
+
+ memcpy(session->buf, &single, sizeof(single));
+ memcpy(session->buf + sizeof(single), data, len);
+
+ return try_send(sock, session->buf, sizeof(single) + len);
+ }
+
+ /* Check if there is enough space to start packet */
+ if (session->omtu < sizeof(start)) {
+ error("No enough space to fragment packet");
+ return FALSE;
+ }
+
+ /* Count the number of needed fragments */
+ cont_fragments = (len - (session->omtu - sizeof(start))) /
+ (session->omtu - sizeof(cont)) + 1;
+
+ DBG("%zu bytes split into %d fragments", len, cont_fragments + 1);
+
+ /* Send the start packet */
+ memset(&start, 0, sizeof(start));
+ start.transaction = transaction;
+ start.packet_type = AVDTP_PKT_TYPE_START;
+ start.message_type = message_type;
+ start.no_of_packets = cont_fragments + 1;
+ start.signal_id = signal_id;
+
+ memcpy(session->buf, &start, sizeof(start));
+ memcpy(session->buf + sizeof(start), data,
+ session->omtu - sizeof(start));
+
+ if (!try_send(sock, session->buf, session->omtu))
+ return FALSE;
+
+ DBG("first packet with %zu bytes sent", session->omtu - sizeof(start));
+
+ sent = session->omtu - sizeof(start);
+
+ /* Send the continue fragments and the end packet */
+ while (sent < len) {
+ int left, to_copy;
+
+ left = len - sent;
+ if (left + sizeof(cont) > session->omtu) {
+ cont.packet_type = AVDTP_PKT_TYPE_CONTINUE;
+ to_copy = session->omtu - sizeof(cont);
+ DBG("sending continue with %d bytes", to_copy);
+ } else {
+ cont.packet_type = AVDTP_PKT_TYPE_END;
+ to_copy = left;
+ DBG("sending end with %d bytes", to_copy);
+ }
+
+ cont.transaction = transaction;
+ cont.message_type = message_type;
+
+ memcpy(session->buf, &cont, sizeof(cont));
+ memcpy(session->buf + sizeof(cont), data + sent, to_copy);
+
+ if (!try_send(sock, session->buf, to_copy + sizeof(cont)))
+ return FALSE;
+
+ sent += to_copy;
+ }
+
+ return TRUE;
+}
+
+static void pending_req_free(void *data)
+{
+ struct pending_req *req = data;
+
+ if (req->timeout)
+ g_source_remove(req->timeout);
+ g_free(req->data);
+ g_free(req);
+}
+
+static void close_stream(struct avdtp_stream *stream)
+{
+ int sock;
+
+ if (stream->io == NULL)
+ return;
+
+ sock = g_io_channel_unix_get_fd(stream->io);
+
+ shutdown(sock, SHUT_RDWR);
+
+ g_io_channel_shutdown(stream->io, FALSE, NULL);
+
+ g_io_channel_unref(stream->io);
+ stream->io = NULL;
+}
+
+static gboolean stream_close_timeout(gpointer user_data)
+{
+ struct avdtp_stream *stream = user_data;
+
+ DBG("Timed out waiting for peer to close the transport channel");
+
+ stream->timer = 0;
+
+ close_stream(stream);
+
+ return FALSE;
+}
+
+static gboolean stream_open_timeout(gpointer user_data)
+{
+ struct avdtp_stream *stream = user_data;
+
+ DBG("Timed out waiting for peer to open the transport channel");
+
+ stream->timer = 0;
+
+ stream->session->pending_open = NULL;
+
+ avdtp_abort(stream->session, stream);
+
+ return FALSE;
+}
+
+void avdtp_error_init(struct avdtp_error *err, uint8_t category, int id)
+{
+ err->category = category;
+
+ if (category == AVDTP_ERRNO)
+ err->err.posix_errno = id;
+ else
+ err->err.error_code = id;
+}
+
+uint8_t avdtp_error_category(struct avdtp_error *err)
+{
+ return err->category;
+}
+
+int avdtp_error_error_code(struct avdtp_error *err)
+{
+ assert(err->category != AVDTP_ERRNO);
+ return err->err.error_code;
+}
+
+int avdtp_error_posix_errno(struct avdtp_error *err)
+{
+ assert(err->category == AVDTP_ERRNO);
+ return err->err.posix_errno;
+}
+
+static struct avdtp_stream *find_stream_by_rseid(struct avdtp *session,
+ uint8_t rseid)
+{
+ GSList *l;
+
+ for (l = session->streams; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_stream *stream = l->data;
+
+ if (stream->rseid == rseid)
+ return stream;
+ }
+
+ return NULL;
+}
+
+static struct avdtp_remote_sep *find_remote_sep(GSList *seps, uint8_t seid)
+{
+ GSList *l;
+
+ for (l = seps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_remote_sep *sep = l->data;
+
+ if (sep->seid == seid)
+ return sep;
+ }
+
+ return NULL;
+}
+
+static void stream_free(void *data)
+{
+ struct avdtp_stream *stream = data;
+ struct avdtp_remote_sep *rsep;
+
+ stream->lsep->info.inuse = 0;
+ stream->lsep->stream = NULL;
+
+ rsep = find_remote_sep(stream->session->seps, stream->rseid);
+ if (rsep)
+ rsep->stream = NULL;
+
+ if (stream->timer)
+ g_source_remove(stream->timer);
+
+ if (stream->start_timer > 0)
+ g_source_remove(stream->start_timer);
+
+ if (stream->io)
+ close_stream(stream);
+
+ if (stream->io_id)
+ g_source_remove(stream->io_id);
+
+ g_slist_free_full(stream->callbacks, g_free);
+ g_slist_free_full(stream->caps, g_free);
+
+ g_free(stream);
+}
+
+static gboolean transport_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avdtp_stream *stream = data;
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (stream->close_int && sep->cfm && sep->cfm->close)
+ sep->cfm->close(stream->session, sep, stream, NULL,
+ sep->user_data);
+
+ if (!(cond & G_IO_NVAL))
+ close_stream(stream);
+
+ stream->io_id = 0;
+
+ if (!stream->abort_int)
+ avdtp_sep_set_state(stream->session, sep, AVDTP_STATE_IDLE);
+
+ return FALSE;
+}
+
+static void handle_transport_connect(struct avdtp *session, GIOChannel *io,
+ uint16_t imtu, uint16_t omtu)
+{
+ struct avdtp_stream *stream = session->pending_open;
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ session->pending_open = NULL;
+
+ if (stream->timer) {
+ g_source_remove(stream->timer);
+ stream->timer = 0;
+ }
+
+ if (io == NULL)
+ return;
+
+ if (stream->io == NULL)
+ stream->io = g_io_channel_ref(io);
+
+ stream->omtu = omtu;
+ stream->imtu = imtu;
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+
+ stream->io_id = g_io_add_watch(io, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
+ (GIOFunc) transport_cb, stream);
+}
+
+static int pending_req_cmp(gconstpointer a, gconstpointer b)
+{
+ const struct pending_req *req = a;
+ const struct avdtp_stream *stream = b;
+
+ if (req->stream == stream)
+ return 0;
+
+ return -1;
+}
+
+static void cleanup_queue(struct avdtp *session, struct avdtp_stream *stream)
+{
+ GSList *l;
+ struct pending_req *req;
+
+ while ((l = g_slist_find_custom(session->prio_queue, stream,
+ pending_req_cmp))) {
+ req = l->data;
+ pending_req_free(req);
+ session->prio_queue = g_slist_remove(session->prio_queue, req);
+ }
+
+ while ((l = g_slist_find_custom(session->req_queue, stream,
+ pending_req_cmp))) {
+ req = l->data;
+ pending_req_free(req);
+ session->req_queue = g_slist_remove(session->req_queue, req);
+ }
+}
+
+static void handle_unanswered_req(struct avdtp *session,
+ struct avdtp_stream *stream)
+{
+ struct pending_req *req;
+ struct avdtp_local_sep *lsep;
+ struct avdtp_error err;
+
+ if (!session->req->timeout)
+ /* Request is in process */
+ return;
+
+ if (session->req->signal_id == AVDTP_ABORT) {
+ /* Avoid freeing the Abort request here */
+ DBG("handle_unanswered_req: Abort req, returning");
+ session->req->stream = NULL;
+ return;
+ }
+
+ req = session->req;
+ session->req = NULL;
+
+ avdtp_error_init(&err, AVDTP_ERRNO, EIO);
+
+ lsep = stream->lsep;
+
+ switch (req->signal_id) {
+ case AVDTP_RECONFIGURE:
+ error("No reply to Reconfigure request");
+ if (lsep && lsep->cfm && lsep->cfm->reconfigure)
+ lsep->cfm->reconfigure(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_OPEN:
+ error("No reply to Open request");
+ if (lsep && lsep->cfm && lsep->cfm->open)
+ lsep->cfm->open(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_START:
+ error("No reply to Start request");
+ if (lsep && lsep->cfm && lsep->cfm->start)
+ lsep->cfm->start(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_SUSPEND:
+ error("No reply to Suspend request");
+ if (lsep && lsep->cfm && lsep->cfm->suspend)
+ lsep->cfm->suspend(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_CLOSE:
+ error("No reply to Close request");
+ if (lsep && lsep->cfm && lsep->cfm->close)
+ lsep->cfm->close(session, lsep, stream, &err,
+ lsep->user_data);
+ break;
+ case AVDTP_SET_CONFIGURATION:
+ error("No reply to SetConfiguration request");
+ if (lsep && lsep->cfm && lsep->cfm->set_configuration)
+ lsep->cfm->set_configuration(session, lsep, stream,
+ &err, lsep->user_data);
+ }
+
+ pending_req_free(req);
+}
+
+static void avdtp_sep_set_state(struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ avdtp_state_t state)
+{
+ struct avdtp_stream *stream = sep->stream;
+ avdtp_state_t old_state;
+ struct avdtp_error err, *err_ptr = NULL;
+ GSList *l;
+
+ if (!stream) {
+ error("Error changing sep state: stream not available");
+ return;
+ }
+
+ if (sep->state == state) {
+ avdtp_error_init(&err, AVDTP_ERRNO, EIO);
+ DBG("stream state change failed: %s", avdtp_strerror(&err));
+ err_ptr = &err;
+ } else {
+ err_ptr = NULL;
+ DBG("stream state changed: %s -> %s",
+ avdtp_statestr(sep->state),
+ avdtp_statestr(state));
+ }
+
+ old_state = sep->state;
+ sep->state = state;
+
+ switch (state) {
+ case AVDTP_STATE_CONFIGURED:
+ if (sep->info.type == AVDTP_SEP_TYPE_SINK)
+ avdtp_delay_report(session, stream, stream->delay);
+ break;
+ case AVDTP_STATE_OPEN:
+ stream->starting = FALSE;
+ break;
+ case AVDTP_STATE_STREAMING:
+ if (stream->start_timer) {
+ g_source_remove(stream->start_timer);
+ stream->start_timer = 0;
+ }
+ stream->open_acp = FALSE;
+ break;
+ case AVDTP_STATE_CLOSING:
+ case AVDTP_STATE_ABORTING:
+ if (stream->start_timer) {
+ g_source_remove(stream->start_timer);
+ stream->start_timer = 0;
+ }
+ break;
+ case AVDTP_STATE_IDLE:
+ if (stream->start_timer) {
+ g_source_remove(stream->start_timer);
+ stream->start_timer = 0;
+ }
+ if (session->pending_open == stream)
+ handle_transport_connect(session, NULL, 0, 0);
+ if (session->req && session->req->stream == stream)
+ handle_unanswered_req(session, stream);
+ /* Remove pending commands for this stream from the queue */
+ cleanup_queue(session, stream);
+ break;
+ default:
+ break;
+ }
+
+ l = stream->callbacks;
+ while (l != NULL) {
+ struct stream_callback *cb = l->data;
+ l = g_slist_next(l);
+ cb->cb(stream, old_state, state, err_ptr, cb->user_data);
+ }
+
+ if (state == AVDTP_STATE_IDLE &&
+ g_slist_find(session->streams, stream)) {
+ session->streams = g_slist_remove(session->streams, stream);
+ stream_free(stream);
+ }
+
+ if (session->io && session->shutdown && session->streams == NULL) {
+ int sock = g_io_channel_unix_get_fd(session->io);
+ shutdown(sock, SHUT_RDWR);
+ }
+}
+
+static void finalize_discovery(struct avdtp *session, int err)
+{
+ struct discover_callback *discover = session->discover;
+ struct avdtp_error avdtp_err;
+
+ if (!discover)
+ return;
+
+ session->discover = NULL;
+
+ avdtp_error_init(&avdtp_err, AVDTP_ERRNO, err);
+
+ if (discover->id > 0)
+ g_source_remove(discover->id);
+
+ if (discover->cb)
+ discover->cb(session, session->seps, err ? &avdtp_err : NULL,
+ discover->user_data);
+ g_free(discover);
+}
+
+static void release_stream(struct avdtp_stream *stream, struct avdtp *session)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (sep->cfm && sep->cfm->abort &&
+ (sep->state != AVDTP_STATE_ABORTING ||
+ stream->abort_int))
+ sep->cfm->abort(session, sep, stream, NULL, sep->user_data);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+}
+
+static void sep_free(gpointer data)
+{
+ struct avdtp_remote_sep *sep = data;
+
+ g_slist_free_full(sep->caps, g_free);
+ g_free(sep);
+}
+
+static void avdtp_free(void *data)
+{
+ struct avdtp *session = data;
+
+ DBG("%p", session);
+
+ g_slist_free_full(session->streams, stream_free);
+
+ if (session->io) {
+ g_io_channel_shutdown(session->io, FALSE, NULL);
+ g_io_channel_unref(session->io);
+ }
+
+ if (session->io_id) {
+ g_source_remove(session->io_id);
+ session->io_id = 0;
+ }
+
+ if (session->req)
+ pending_req_free(session->req);
+
+ g_slist_free_full(session->req_queue, pending_req_free);
+ g_slist_free_full(session->prio_queue, pending_req_free);
+ g_slist_free_full(session->seps, sep_free);
+ g_slist_free_full(session->disconnect, g_free);
+
+ /* Free copy of the SEP list */
+ session->lseps = NULL;
+
+ g_free(session->buf);
+
+ g_free(session);
+}
+
+static void process_disconnect(void *data)
+{
+ struct disconnect_callback *callback = data;
+
+ callback->cb(callback->user_data);
+
+ g_free(callback);
+}
+
+static void connection_lost(struct avdtp *session, int err)
+{
+ DBG("Disconnected: %s (%d)", strerror(err), err);
+
+ g_slist_foreach(session->streams, (GFunc) release_stream, session);
+ session->streams = NULL;
+
+ avdtp_ref(session);
+
+ finalize_discovery(session, err);
+
+ g_slist_free_full(session->disconnect, process_disconnect);
+ session->disconnect = NULL;
+
+ avdtp_unref(session);
+}
+
+void avdtp_unref(struct avdtp *session)
+{
+ if (!session)
+ return;
+
+ session->ref--;
+
+ DBG("%p: ref=%d", session, session->ref);
+
+ if (session->ref > 0)
+ return;
+
+ finalize_discovery(session, ECONNABORTED);
+
+ avdtp_free(session);
+}
+
+struct avdtp *avdtp_ref(struct avdtp *session)
+{
+ session->ref++;
+
+ DBG("%p: ref=%d", session, session->ref);
+
+ return session;
+}
+
+static bool match_by_seid(const void *data, const void *user_data)
+{
+ const struct avdtp_local_sep *sep = data;
+ uint8_t seid = PTR_TO_UINT(user_data);
+
+ return sep->info.seid == seid;
+}
+
+static struct avdtp_local_sep *find_local_sep_by_seid(struct avdtp *session,
+ uint8_t seid)
+{
+ return queue_find(session->lseps, match_by_seid, INT_TO_PTR(seid));
+}
+
+struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session,
+ struct avdtp_local_sep *lsep)
+{
+ GSList *l;
+
+ if (lsep->info.inuse)
+ return NULL;
+
+ for (l = session->seps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_remote_sep *sep = l->data;
+ struct avdtp_service_capability *cap;
+ struct avdtp_media_codec_capability *codec_data;
+
+ /* Type must be different: source <-> sink */
+ if (sep->type == lsep->info.type)
+ continue;
+
+ if (sep->media_type != lsep->info.media_type)
+ continue;
+
+ if (!sep->codec)
+ continue;
+
+ cap = sep->codec;
+ codec_data = (void *) cap->data;
+
+ if (codec_data->media_codec_type != lsep->codec)
+ continue;
+
+ /* FIXME: Add Vendor Specific Codec match to SEP callback */
+ if (lsep->codec == A2DP_CODEC_VENDOR) {
+ a2dp_vendor_codec_t *vndcodec =
+ (void *) codec_data->data;
+
+ if (A2DP_GET_VENDOR_ID(*vndcodec) !=
+ lsep->vndcodec_vendor)
+ continue;
+
+ if (A2DP_GET_CODEC_ID(*vndcodec) !=
+ lsep->vndcodec_codec)
+ continue;
+ }
+
+ if (sep->stream == NULL)
+ return sep;
+ }
+
+ return NULL;
+}
+
+static GSList *caps_to_list(uint8_t *data, int size,
+ struct avdtp_service_capability **codec,
+ gboolean *delay_reporting)
+{
+ GSList *caps;
+ int processed;
+
+ if (delay_reporting)
+ *delay_reporting = FALSE;
+
+ for (processed = 0, caps = NULL; processed + 2 <= size;) {
+ struct avdtp_service_capability *cap;
+ uint8_t length, category;
+
+ category = data[0];
+ length = data[1];
+
+ if (processed + 2 + length > size) {
+ error("Invalid capability data in getcap resp");
+ break;
+ }
+
+ cap = g_malloc(sizeof(struct avdtp_service_capability) +
+ length);
+ memcpy(cap, data, 2 + length);
+
+ processed += 2 + length;
+ data += 2 + length;
+
+ caps = g_slist_append(caps, cap);
+
+ if (category == AVDTP_MEDIA_CODEC &&
+ length >=
+ sizeof(struct avdtp_media_codec_capability))
+ *codec = cap;
+ else if (category == AVDTP_DELAY_REPORTING && delay_reporting)
+ *delay_reporting = TRUE;
+ }
+
+ return caps;
+}
+
+static gboolean avdtp_unknown_cmd(struct avdtp *session, uint8_t transaction,
+ uint8_t signal_id)
+{
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_GEN_REJECT,
+ signal_id, NULL, 0);
+}
+
+static void copy_seps(void *data, void *user_data)
+{
+ struct avdtp_local_sep *sep = data;
+ struct seid_info **p = user_data;
+
+ memcpy(*p, &sep->info, sizeof(struct seid_info));
+ *p = *p + 1;
+}
+
+static gboolean avdtp_discover_cmd(struct avdtp *session, uint8_t transaction,
+ void *buf, int size)
+{
+ unsigned int rsp_size, sep_count;
+ struct seid_info *seps, *p;
+ gboolean ret;
+
+ sep_count = queue_length(session->lseps);
+
+ if (sep_count == 0) {
+ uint8_t err = AVDTP_NOT_SUPPORTED_COMMAND;
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_DISCOVER, &err, sizeof(err));
+ }
+
+ rsp_size = sep_count * sizeof(struct seid_info);
+
+ seps = g_new0(struct seid_info, sep_count);
+ p = seps;
+
+ queue_foreach(session->lseps, copy_seps, &p);
+
+ ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_DISCOVER, seps, rsp_size);
+ g_free(seps);
+
+ return ret;
+}
+
+static gboolean avdtp_getcap_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, unsigned int size,
+ gboolean get_all)
+{
+ GSList *l, *caps;
+ struct avdtp_local_sep *sep = NULL;
+ unsigned int rsp_size;
+ uint8_t err, buf[1024], *ptr = buf;
+ uint8_t cmd;
+
+ cmd = get_all ? AVDTP_GET_ALL_CAPABILITIES : AVDTP_GET_CAPABILITIES;
+
+ if (size < sizeof(struct seid_req)) {
+ err = AVDTP_BAD_LENGTH;
+ goto failed;
+ }
+
+ sep = find_local_sep_by_seid(session, req->acp_seid);
+ if (!sep) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (!sep->ind->get_capability(session, sep, &caps, &err,
+ sep->user_data))
+ goto failed;
+
+ for (l = caps, rsp_size = 0; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_service_capability *cap = l->data;
+
+ if (rsp_size + cap->length + 2 > sizeof(buf))
+ break;
+
+ memcpy(ptr, cap, cap->length + 2);
+ rsp_size += cap->length + 2;
+ ptr += cap->length + 2;
+
+ g_free(cap);
+ }
+
+ if (get_all && sep->delay_reporting) {
+ ptr[0] = AVDTP_DELAY_REPORTING;
+ ptr[1] = 0x00;
+ rsp_size += 2;
+ }
+
+ g_slist_free(caps);
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT, cmd,
+ buf, rsp_size);
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT, cmd,
+ &err, sizeof(err));
+}
+
+static void setconf_cb(struct avdtp *session, struct avdtp_stream *stream,
+ struct avdtp_error *err)
+{
+ struct conf_rej rej;
+ struct avdtp_local_sep *sep;
+
+ if (err != NULL) {
+ rej.error = AVDTP_UNSUPPORTED_CONFIGURATION;
+ rej.category = err->err.error_code;
+ avdtp_send(session, session->in.transaction,
+ AVDTP_MSG_TYPE_REJECT, AVDTP_SET_CONFIGURATION,
+ &rej, sizeof(rej));
+ return;
+ }
+
+ if (!avdtp_send(session, session->in.transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_SET_CONFIGURATION, NULL, 0)) {
+ stream_free(stream);
+ return;
+ }
+
+ sep = stream->lsep;
+ sep->stream = stream;
+ sep->info.inuse = 1;
+ session->streams = g_slist_append(session->streams, stream);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+}
+
+static gboolean avdtp_setconf_cmd(struct avdtp *session, uint8_t transaction,
+ struct setconf_req *req, unsigned int size)
+{
+ struct conf_rej rej;
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ uint8_t err, category = 0x00;
+ GSList *l;
+
+ if (size < sizeof(struct setconf_req)) {
+ error("Too short getcap request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session, req->acp_seid);
+ if (!sep) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (sep->stream) {
+ err = AVDTP_SEP_IN_USE;
+ goto failed;
+ }
+
+ stream = g_new0(struct avdtp_stream, 1);
+ stream->session = session;
+ stream->lsep = sep;
+ stream->rseid = req->int_seid;
+ stream->caps = caps_to_list(req->caps,
+ size - sizeof(struct setconf_req),
+ &stream->codec,
+ &stream->delay_reporting);
+
+ /*
+ * Verify that the Media Transport capability's length = 0.
+ * Reject otherwise
+ */
+ for (l = stream->caps; l != NULL; l = g_slist_next(l)) {
+ struct avdtp_service_capability *cap = l->data;
+
+ if (cap->category == AVDTP_MEDIA_TRANSPORT &&
+ cap->length != 0) {
+ err = AVDTP_BAD_MEDIA_TRANSPORT_FORMAT;
+ goto failed_stream;
+ }
+ }
+
+ if (stream->delay_reporting && session->version < 0x0103)
+ session->version = 0x0103;
+
+ if (sep->ind && sep->ind->set_configuration) {
+ if (!sep->ind->set_configuration(session, sep, stream,
+ stream->caps,
+ setconf_cb,
+ sep->user_data)) {
+ err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ category = 0x00;
+ goto failed_stream;
+ }
+ } else {
+ if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_SET_CONFIGURATION, NULL, 0)) {
+ stream_free(stream);
+ return FALSE;
+ }
+
+ sep->stream = stream;
+ sep->info.inuse = 1;
+ session->streams = g_slist_append(session->streams, stream);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+ }
+
+ return TRUE;
+
+failed_stream:
+ stream_free(stream);
+failed:
+ rej.error = err;
+ rej.category = category;
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_SET_CONFIGURATION, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_getconf_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, int size)
+{
+ GSList *l;
+ struct avdtp_local_sep *sep = NULL;
+ int rsp_size;
+ uint8_t err;
+ uint8_t buf[1024];
+ uint8_t *ptr = buf;
+
+ if (size < (int) sizeof(struct seid_req)) {
+ error("Too short getconf request");
+ return FALSE;
+ }
+
+ memset(buf, 0, sizeof(buf));
+
+ sep = find_local_sep_by_seid(session, req->acp_seid);
+ if (!sep) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+ if (!sep->stream || !sep->stream->caps) {
+ err = AVDTP_UNSUPPORTED_CONFIGURATION;
+ goto failed;
+ }
+
+ for (l = sep->stream->caps, rsp_size = 0; l; l = g_slist_next(l)) {
+ struct avdtp_service_capability *cap = l->data;
+
+ if (rsp_size + cap->length + 2 > (int) sizeof(buf))
+ break;
+
+ memcpy(ptr, cap, cap->length + 2);
+ rsp_size += cap->length + 2;
+ ptr += cap->length + 2;
+ }
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_GET_CONFIGURATION, buf, rsp_size);
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_GET_CONFIGURATION, &err, sizeof(err));
+}
+
+static gboolean avdtp_reconf_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, int size)
+{
+ struct conf_rej rej;
+
+ rej.error = AVDTP_NOT_SUPPORTED_COMMAND;
+ rej.category = 0x00;
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_RECONFIGURE, &rej, sizeof(rej));
+}
+
+static void check_seid_collision(struct pending_req *req, uint8_t id)
+{
+ struct seid_req *seid = req->data;
+
+ if (seid->acp_seid == id)
+ req->collided = TRUE;
+}
+
+static void check_start_collision(struct pending_req *req, uint8_t id)
+{
+ struct start_req *start = req->data;
+ struct seid *seid = &start->first_seid;
+ int count = 1 + req->data_size - sizeof(struct start_req);
+ int i;
+
+ for (i = 0; i < count; i++, seid++) {
+ if (seid->seid == id) {
+ req->collided = TRUE;
+ return;
+ }
+ }
+}
+
+static void check_suspend_collision(struct pending_req *req, uint8_t id)
+{
+ struct suspend_req *suspend = req->data;
+ struct seid *seid = &suspend->first_seid;
+ int count = 1 + req->data_size - sizeof(struct suspend_req);
+ int i;
+
+ for (i = 0; i < count; i++, seid++) {
+ if (seid->seid == id) {
+ req->collided = TRUE;
+ return;
+ }
+ }
+}
+
+static void avdtp_check_collision(struct avdtp *session, uint8_t cmd,
+ struct avdtp_stream *stream)
+{
+ struct pending_req *req = session->req;
+
+ if (req == NULL || (req->signal_id != cmd && cmd != AVDTP_ABORT))
+ return;
+
+ if (cmd == AVDTP_ABORT)
+ cmd = req->signal_id;
+
+ switch (cmd) {
+ case AVDTP_OPEN:
+ case AVDTP_CLOSE:
+ check_seid_collision(req, stream->rseid);
+ break;
+ case AVDTP_START:
+ check_start_collision(req, stream->rseid);
+ break;
+ case AVDTP_SUSPEND:
+ check_suspend_collision(req, stream->rseid);
+ break;
+ }
+}
+
+static gboolean avdtp_open_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ uint8_t err;
+
+ if (size < sizeof(struct seid_req)) {
+ error("Too short abort request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session, req->acp_seid);
+ if (!sep) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (sep->state != AVDTP_STATE_CONFIGURED) {
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ if (sep->ind && sep->ind->open) {
+ if (!sep->ind->open(session, sep, stream, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ avdtp_check_collision(session, AVDTP_OPEN, stream);
+
+ if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_OPEN, NULL, 0))
+ return FALSE;
+
+ stream->open_acp = TRUE;
+ session->pending_open = stream;
+ stream->timer = g_timeout_add_seconds(REQ_TIMEOUT,
+ stream_open_timeout,
+ stream);
+
+ return TRUE;
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_OPEN, &err, sizeof(err));
+}
+
+static gboolean avdtp_start_cmd(struct avdtp *session, uint8_t transaction,
+ struct start_req *req, unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ struct stream_rej rej;
+ struct seid *seid;
+ uint8_t err, failed_seid;
+ int seid_count, i;
+
+ if (size < sizeof(struct start_req)) {
+ error("Too short start request");
+ return FALSE;
+ }
+
+ seid_count = 1 + size - sizeof(struct start_req);
+
+ seid = &req->first_seid;
+
+ for (i = 0; i < seid_count; i++, seid++) {
+ failed_seid = seid->seid;
+
+ sep = find_local_sep_by_seid(session, seid->seid);
+ if (!sep || !sep->stream) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ /* Also reject start cmd if state is not open */
+ if (sep->state != AVDTP_STATE_OPEN) {
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+ stream->starting = TRUE;
+
+ if (sep->ind && sep->ind->start) {
+ if (!sep->ind->start(session, sep, stream, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ avdtp_check_collision(session, AVDTP_START, stream);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING);
+ }
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_START, NULL, 0);
+
+failed:
+ DBG("Rejecting (%d)", err);
+ memset(&rej, 0, sizeof(rej));
+ rej.acp_seid = failed_seid;
+ rej.error = err;
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_START, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_close_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ uint8_t err;
+
+ if (size < sizeof(struct seid_req)) {
+ error("Too short close request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session, req->acp_seid);
+ if (!sep || !sep->stream) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ if (sep->state != AVDTP_STATE_OPEN &&
+ sep->state != AVDTP_STATE_STREAMING) {
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ if (sep->ind && sep->ind->close) {
+ if (!sep->ind->close(session, sep, stream, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ avdtp_check_collision(session, AVDTP_CLOSE, stream);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING);
+
+ if (!avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_CLOSE, NULL, 0))
+ return FALSE;
+
+ stream->timer = g_timeout_add_seconds(REQ_TIMEOUT,
+ stream_close_timeout,
+ stream);
+
+ return TRUE;
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_CLOSE, &err, sizeof(err));
+}
+
+static gboolean avdtp_suspend_cmd(struct avdtp *session, uint8_t transaction,
+ struct suspend_req *req, unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ struct stream_rej rej;
+ struct seid *seid;
+ uint8_t err, failed_seid;
+ int seid_count, i;
+
+ if (size < sizeof(struct suspend_req)) {
+ error("Too short suspend request");
+ return FALSE;
+ }
+
+ seid_count = 1 + size - sizeof(struct suspend_req);
+
+ seid = &req->first_seid;
+
+ for (i = 0; i < seid_count; i++, seid++) {
+ failed_seid = seid->seid;
+
+ sep = find_local_sep_by_seid(session, seid->seid);
+ if (!sep || !sep->stream) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ if (sep->state != AVDTP_STATE_STREAMING) {
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ }
+
+ if (sep->ind && sep->ind->suspend) {
+ if (!sep->ind->suspend(session, sep, stream, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ avdtp_check_collision(session, AVDTP_SUSPEND, stream);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+ }
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_SUSPEND, NULL, 0);
+
+failed:
+ memset(&rej, 0, sizeof(rej));
+ rej.acp_seid = failed_seid;
+ rej.error = err;
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_SUSPEND, &rej, sizeof(rej));
+}
+
+static gboolean avdtp_abort_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ uint8_t err;
+ gboolean ret;
+
+ if (size < sizeof(struct seid_req)) {
+ error("Too short abort request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session, req->acp_seid);
+ if (!sep || !sep->stream)
+ return TRUE;
+
+ if (sep->ind && sep->ind->abort)
+ sep->ind->abort(session, sep, sep->stream, &err,
+ sep->user_data);
+
+ avdtp_check_collision(session, AVDTP_ABORT, sep->stream);
+
+ ret = avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_ABORT, NULL, 0);
+ if (ret)
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING);
+
+ return ret;
+}
+
+static gboolean avdtp_secctl_cmd(struct avdtp *session, uint8_t transaction,
+ struct seid_req *req, int size)
+{
+ return avdtp_unknown_cmd(session, transaction, AVDTP_SECURITY_CONTROL);
+}
+
+static gboolean avdtp_delayreport_cmd(struct avdtp *session,
+ uint8_t transaction,
+ struct delay_req *req,
+ unsigned int size)
+{
+ struct avdtp_local_sep *sep;
+ struct avdtp_stream *stream;
+ uint8_t err;
+
+ if (size < sizeof(struct delay_req)) {
+ error("Too short delay report request");
+ return FALSE;
+ }
+
+ sep = find_local_sep_by_seid(session, req->acp_seid);
+ if (!sep || !sep->stream) {
+ err = AVDTP_BAD_ACP_SEID;
+ goto failed;
+ }
+
+ stream = sep->stream;
+
+ switch (sep->state) {
+ case AVDTP_STATE_IDLE:
+ case AVDTP_STATE_ABORTING:
+ case AVDTP_STATE_CLOSING:
+ err = AVDTP_BAD_STATE;
+ goto failed;
+ case AVDTP_STATE_CONFIGURED:
+ case AVDTP_STATE_OPEN:
+ case AVDTP_STATE_STREAMING:
+ default:
+ break;
+ }
+
+ stream->delay = ntohs(req->delay);
+
+ if (sep->ind && sep->ind->delayreport) {
+ if (!sep->ind->delayreport(session, sep, stream->rseid,
+ stream->delay, &err,
+ sep->user_data))
+ goto failed;
+ }
+
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_ACCEPT,
+ AVDTP_DELAY_REPORT, NULL, 0);
+
+failed:
+ return avdtp_send(session, transaction, AVDTP_MSG_TYPE_REJECT,
+ AVDTP_DELAY_REPORT, &err, sizeof(err));
+}
+
+static gboolean avdtp_parse_cmd(struct avdtp *session, uint8_t transaction,
+ uint8_t signal_id, void *buf, int size)
+{
+ switch (signal_id) {
+ case AVDTP_DISCOVER:
+ DBG("Received DISCOVER_CMD");
+ return avdtp_discover_cmd(session, transaction, buf, size);
+ case AVDTP_GET_CAPABILITIES:
+ DBG("Received GET_CAPABILITIES_CMD");
+ return avdtp_getcap_cmd(session, transaction, buf, size,
+ FALSE);
+ case AVDTP_GET_ALL_CAPABILITIES:
+ DBG("Received GET_ALL_CAPABILITIES_CMD");
+ return avdtp_getcap_cmd(session, transaction, buf, size, TRUE);
+ case AVDTP_SET_CONFIGURATION:
+ DBG("Received SET_CONFIGURATION_CMD");
+ return avdtp_setconf_cmd(session, transaction, buf, size);
+ case AVDTP_GET_CONFIGURATION:
+ DBG("Received GET_CONFIGURATION_CMD");
+ return avdtp_getconf_cmd(session, transaction, buf, size);
+ case AVDTP_RECONFIGURE:
+ DBG("Received RECONFIGURE_CMD");
+ return avdtp_reconf_cmd(session, transaction, buf, size);
+ case AVDTP_OPEN:
+ DBG("Received OPEN_CMD");
+ return avdtp_open_cmd(session, transaction, buf, size);
+ case AVDTP_START:
+ DBG("Received START_CMD");
+ return avdtp_start_cmd(session, transaction, buf, size);
+ case AVDTP_CLOSE:
+ DBG("Received CLOSE_CMD");
+ return avdtp_close_cmd(session, transaction, buf, size);
+ case AVDTP_SUSPEND:
+ DBG("Received SUSPEND_CMD");
+ return avdtp_suspend_cmd(session, transaction, buf, size);
+ case AVDTP_ABORT:
+ DBG("Received ABORT_CMD");
+ return avdtp_abort_cmd(session, transaction, buf, size);
+ case AVDTP_SECURITY_CONTROL:
+ DBG("Received SECURITY_CONTROL_CMD");
+ return avdtp_secctl_cmd(session, transaction, buf, size);
+ case AVDTP_DELAY_REPORT:
+ DBG("Received DELAY_REPORT_CMD");
+ return avdtp_delayreport_cmd(session, transaction, buf, size);
+ default:
+ DBG("Received unknown request id %u", signal_id);
+ return avdtp_unknown_cmd(session, transaction, signal_id);
+ }
+}
+
+enum avdtp_parse_result { PARSE_ERROR, PARSE_FRAGMENT, PARSE_SUCCESS };
+
+static enum avdtp_parse_result avdtp_parse_data(struct avdtp *session,
+ void *buf, size_t size)
+{
+ struct avdtp_common_header *header = buf;
+ struct avdtp_single_header *single = (void *) session->buf;
+ struct avdtp_start_header *start = (void *) session->buf;
+ void *payload;
+ gsize payload_size;
+
+ switch (header->packet_type) {
+ case AVDTP_PKT_TYPE_SINGLE:
+ if (size < sizeof(*single)) {
+ error("Received too small single packet (%zu bytes)",
+ size);
+ return PARSE_ERROR;
+ }
+ if (session->in.active) {
+ error("SINGLE: Invalid AVDTP packet fragmentation");
+ return PARSE_ERROR;
+ }
+
+ payload = session->buf + sizeof(*single);
+ payload_size = size - sizeof(*single);
+
+ session->in.active = TRUE;
+ session->in.data_size = 0;
+ session->in.no_of_packets = 1;
+ session->in.transaction = header->transaction;
+ session->in.message_type = header->message_type;
+ session->in.signal_id = single->signal_id;
+
+ break;
+ case AVDTP_PKT_TYPE_START:
+ if (size < sizeof(*start)) {
+ error("Received too small start packet (%zu bytes)",
+ size);
+ return PARSE_ERROR;
+ }
+ if (session->in.active) {
+ error("START: Invalid AVDTP packet fragmentation");
+ return PARSE_ERROR;
+ }
+
+ session->in.active = TRUE;
+ session->in.data_size = 0;
+ session->in.transaction = header->transaction;
+ session->in.message_type = header->message_type;
+ session->in.no_of_packets = start->no_of_packets;
+ session->in.signal_id = start->signal_id;
+
+ payload = session->buf + sizeof(*start);
+ payload_size = size - sizeof(*start);
+
+ break;
+ case AVDTP_PKT_TYPE_CONTINUE:
+ if (size < sizeof(struct avdtp_continue_header)) {
+ error("Received too small continue packet (%zu bytes)",
+ size);
+ return PARSE_ERROR;
+ }
+ if (!session->in.active) {
+ error("CONTINUE: Invalid AVDTP packet fragmentation");
+ return PARSE_ERROR;
+ }
+ if (session->in.transaction != header->transaction) {
+ error("Continue transaction id doesn't match");
+ return PARSE_ERROR;
+ }
+ if (session->in.no_of_packets <= 1) {
+ error("Too few continue packets");
+ return PARSE_ERROR;
+ }
+
+ payload = session->buf + sizeof(struct avdtp_continue_header);
+ payload_size = size - sizeof(struct avdtp_continue_header);
+
+ break;
+ case AVDTP_PKT_TYPE_END:
+ if (size < sizeof(struct avdtp_continue_header)) {
+ error("Received too small end packet (%zu bytes)",
+ size);
+ return PARSE_ERROR;
+ }
+ if (!session->in.active) {
+ error("END: Invalid AVDTP packet fragmentation");
+ return PARSE_ERROR;
+ }
+ if (session->in.transaction != header->transaction) {
+ error("End transaction id doesn't match");
+ return PARSE_ERROR;
+ }
+ if (session->in.no_of_packets > 1) {
+ error("Got an end packet too early");
+ return PARSE_ERROR;
+ }
+
+ payload = session->buf + sizeof(struct avdtp_continue_header);
+ payload_size = size - sizeof(struct avdtp_continue_header);
+
+ break;
+ default:
+ error("Invalid AVDTP packet type 0x%02X", header->packet_type);
+ return PARSE_ERROR;
+ }
+
+ if (session->in.data_size + payload_size >
+ sizeof(session->in.buf)) {
+ error("Not enough incoming buffer space!");
+ return PARSE_ERROR;
+ }
+
+ memcpy(session->in.buf + session->in.data_size, payload, payload_size);
+ session->in.data_size += payload_size;
+
+ if (session->in.no_of_packets > 1) {
+ session->in.no_of_packets--;
+ DBG("Received AVDTP fragment. %d to go",
+ session->in.no_of_packets);
+ return PARSE_FRAGMENT;
+ }
+
+ session->in.active = FALSE;
+
+ return PARSE_SUCCESS;
+}
+
+static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
+ gpointer data)
+{
+ struct avdtp *session = data;
+ struct avdtp_common_header *header;
+ ssize_t size;
+ int fd;
+
+ DBG("");
+
+ if (cond & G_IO_NVAL) {
+ session->io_id = 0;
+
+ return FALSE;
+ }
+
+ header = (void *) session->buf;
+
+ if (cond & (G_IO_HUP | G_IO_ERR))
+ goto failed;
+
+ fd = g_io_channel_unix_get_fd(chan);
+ size = read(fd, session->buf, session->imtu);
+ if (size < 0) {
+ error("IO Channel read error");
+ goto failed;
+ }
+
+ if ((size_t) size < sizeof(struct avdtp_common_header)) {
+ error("Received too small packet (%zu bytes)", size);
+ goto failed;
+ }
+
+ switch (avdtp_parse_data(session, session->buf, size)) {
+ case PARSE_ERROR:
+ goto failed;
+ case PARSE_FRAGMENT:
+ return TRUE;
+ case PARSE_SUCCESS:
+ break;
+ }
+
+ /* Take a reference to protect against callback destroying session */
+ avdtp_ref(session);
+
+ if (session->in.message_type == AVDTP_MSG_TYPE_COMMAND) {
+ if (!avdtp_parse_cmd(session, session->in.transaction,
+ session->in.signal_id,
+ session->in.buf,
+ session->in.data_size)) {
+ error("Unable to handle command. Disconnecting");
+ goto failed;
+ }
+
+ if (session->req && session->req->collided) {
+ DBG("Collision detected");
+ goto next;
+ }
+
+ avdtp_unref(session);
+ return TRUE;
+ }
+
+ if (session->req == NULL) {
+ error("No pending request, ignoring message");
+ avdtp_unref(session);
+ return TRUE;
+ }
+
+ if (header->transaction != session->req->transaction) {
+ error("Transaction label doesn't match");
+ avdtp_unref(session);
+ return TRUE;
+ }
+
+ if (session->in.signal_id != session->req->signal_id) {
+ error("Response signal doesn't match");
+ avdtp_unref(session);
+ return TRUE;
+ }
+
+ g_source_remove(session->req->timeout);
+ session->req->timeout = 0;
+
+ switch (header->message_type) {
+ case AVDTP_MSG_TYPE_ACCEPT:
+ if (!avdtp_parse_resp(session, session->req->stream,
+ session->in.transaction,
+ session->in.signal_id,
+ session->in.buf,
+ session->in.data_size)) {
+ error("Unable to parse accept response");
+ goto failed;
+ }
+ break;
+ case AVDTP_MSG_TYPE_REJECT:
+ if (!avdtp_parse_rej(session, session->req->stream,
+ session->in.transaction,
+ session->in.signal_id,
+ session->in.buf,
+ session->in.data_size)) {
+ error("Unable to parse reject response");
+ goto failed;
+ }
+ break;
+ case AVDTP_MSG_TYPE_GEN_REJECT:
+ error("Received a General Reject message");
+ break;
+ default:
+ error("Unknown message type 0x%02X", header->message_type);
+ break;
+ }
+
+next:
+ pending_req_free(session->req);
+ session->req = NULL;
+
+ if (session->ref > 1)
+ process_queue(session);
+
+ avdtp_unref(session);
+
+ return TRUE;
+
+failed:
+ session->io_id = 0;
+ connection_lost(session, EIO);
+
+ return FALSE;
+}
+
+static int set_priority(int fd, int priority)
+{
+ int err;
+
+ err = setsockopt(fd, SOL_SOCKET, SO_PRIORITY, &priority,
+ sizeof(priority));
+ if (err == 0 || errno == ENOTSOCK)
+ return 0;
+
+ err = -errno;
+ error("setsockopt(SO_PRIORITY): %s (%d)", strerror(-err), -err);
+
+ return err;
+}
+
+struct avdtp *avdtp_new(int fd, size_t imtu, size_t omtu, uint16_t version,
+ struct queue *lseps)
+{
+ struct avdtp *session;
+ GIOCondition cond = G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL;
+ int new_fd;
+
+ if (!lseps)
+ return NULL;
+
+ new_fd = dup(fd);
+ if (new_fd < 0) {
+ error("dup(): %s (%d)", strerror(errno), errno);
+ return NULL;
+ }
+
+ if (set_priority(new_fd, 6) < 0) {
+ close(new_fd);
+ return NULL;
+ }
+
+ session = g_new0(struct avdtp, 1);
+ session->io = g_io_channel_unix_new(new_fd);
+ session->version = version;
+ session->imtu = imtu;
+ session->omtu = omtu;
+ session->buf = g_malloc0(MAX(session->imtu, session->omtu));
+
+ /* This watch should be low priority since otherwise the
+ * connect callback might be dispatched before the session
+ * callback if the kernel wakes us up at the same time for
+ * them. This could happen if a headset is very quick in
+ * sending the Start command after connecting the stream
+ * transport channel.
+ */
+ session->io_id = g_io_add_watch_full(session->io, G_PRIORITY_LOW, cond,
+ (GIOFunc) session_cb, session,
+ NULL);
+
+ session->lseps = lseps;
+
+ return avdtp_ref(session);
+}
+
+unsigned int avdtp_add_disconnect_cb(struct avdtp *session,
+ avdtp_disconnect_cb_t cb,
+ void *user_data)
+{
+ struct disconnect_callback *callback;
+ static unsigned int id = 0;
+
+ callback = g_new0(struct disconnect_callback, 1);
+ callback->id = ++id;
+ callback->cb = cb;
+ callback->user_data = user_data;
+ session->disconnect = g_slist_append(session->disconnect, callback);
+
+ return id;
+}
+
+gboolean avdtp_remove_disconnect_cb(struct avdtp *session, unsigned int id)
+{
+ GSList *l;
+
+ for (l = session->disconnect; l; l = g_slist_next(l)) {
+ struct disconnect_callback *callback = l->data;
+
+ if (callback->id != id)
+ continue;
+
+ session->disconnect = g_slist_remove(session->disconnect,
+ callback);
+ g_free(callback);
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+void avdtp_shutdown(struct avdtp *session)
+{
+ GSList *l;
+ bool aborting = false;
+
+ if (!session->io)
+ return;
+
+ for (l = session->streams; l; l = g_slist_next(l)) {
+ struct avdtp_stream *stream = l->data;
+
+ if (stream->abort_int ||
+ avdtp_close(session, stream, TRUE) == 0)
+ aborting = true;
+ }
+
+ if (aborting) {
+ /* defer shutdown until all streams are aborted properly */
+ session->shutdown = true;
+ } else {
+ int sock = g_io_channel_unix_get_fd(session->io);
+
+ shutdown(sock, SHUT_RDWR);
+ }
+}
+
+static void queue_request(struct avdtp *session, struct pending_req *req,
+ gboolean priority)
+{
+ if (priority)
+ session->prio_queue = g_slist_append(session->prio_queue, req);
+ else
+ session->req_queue = g_slist_append(session->req_queue, req);
+}
+
+static uint8_t req_get_seid(struct pending_req *req)
+{
+ if (req->signal_id == AVDTP_DISCOVER)
+ return 0;
+
+ return ((struct seid_req *) (req->data))->acp_seid;
+}
+
+static int cancel_request(struct avdtp *session, int err)
+{
+ struct pending_req *req;
+ struct seid_req sreq;
+ struct avdtp_local_sep *lsep;
+ struct avdtp_stream *stream;
+ uint8_t seid;
+ struct avdtp_error averr;
+
+ req = session->req;
+ session->req = NULL;
+
+ avdtp_error_init(&averr, AVDTP_ERRNO, err);
+
+ seid = req_get_seid(req);
+ if (seid)
+ stream = find_stream_by_rseid(session, seid);
+ else
+ stream = NULL;
+
+ if (stream) {
+ stream->abort_int = TRUE;
+ lsep = stream->lsep;
+ } else
+ lsep = NULL;
+
+ switch (req->signal_id) {
+ case AVDTP_RECONFIGURE:
+ error("Reconfigure: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->reconfigure)
+ lsep->cfm->reconfigure(session, lsep, stream, &averr,
+ lsep->user_data);
+ break;
+ case AVDTP_OPEN:
+ error("Open: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->open)
+ lsep->cfm->open(session, lsep, stream, &averr,
+ lsep->user_data);
+ break;
+ case AVDTP_START:
+ error("Start: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->start) {
+ lsep->cfm->start(session, lsep, stream, &averr,
+ lsep->user_data);
+ if (stream)
+ stream->starting = FALSE;
+ }
+ break;
+ case AVDTP_SUSPEND:
+ error("Suspend: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->suspend)
+ lsep->cfm->suspend(session, lsep, stream, &averr,
+ lsep->user_data);
+ break;
+ case AVDTP_CLOSE:
+ error("Close: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->close) {
+ lsep->cfm->close(session, lsep, stream, &averr,
+ lsep->user_data);
+ if (stream)
+ stream->close_int = FALSE;
+ }
+ break;
+ case AVDTP_SET_CONFIGURATION:
+ error("SetConfiguration: %s (%d)", strerror(err), err);
+ if (lsep && lsep->cfm && lsep->cfm->set_configuration)
+ lsep->cfm->set_configuration(session, lsep, stream,
+ &averr, lsep->user_data);
+ goto failed;
+ case AVDTP_DISCOVER:
+ error("Discover: %s (%d)", strerror(err), err);
+ goto failed;
+ case AVDTP_GET_CAPABILITIES:
+ error("GetCapabilities: %s (%d)", strerror(err), err);
+ goto failed;
+ case AVDTP_ABORT:
+ error("Abort: %s (%d)", strerror(err), err);
+ goto failed;
+ }
+
+ if (!stream)
+ goto failed;
+
+ memset(&sreq, 0, sizeof(sreq));
+ sreq.acp_seid = seid;
+
+ err = send_request(session, TRUE, stream, AVDTP_ABORT, &sreq,
+ sizeof(sreq));
+ if (err < 0) {
+ error("Unable to send abort request");
+ goto failed;
+ }
+
+ goto done;
+
+failed:
+ connection_lost(session, err);
+done:
+ pending_req_free(req);
+ return err;
+}
+
+static gboolean request_timeout(gpointer user_data)
+{
+ struct avdtp *session = user_data;
+
+ cancel_request(session, ETIMEDOUT);
+
+ return FALSE;
+}
+
+static int send_req(struct avdtp *session, gboolean priority,
+ struct pending_req *req)
+{
+ static int transaction = 0;
+ int err;
+
+ if (session->req != NULL) {
+ queue_request(session, req, priority);
+ return 0;
+ }
+
+ req->transaction = transaction++;
+ transaction %= 16;
+
+ /* FIXME: Should we retry to send if the buffer
+ was not totally sent or in case of EINTR? */
+ if (!avdtp_send(session, req->transaction, AVDTP_MSG_TYPE_COMMAND,
+ req->signal_id, req->data, req->data_size)) {
+ err = -EIO;
+ goto failed;
+ }
+
+ session->req = req;
+
+ req->timeout = g_timeout_add_seconds(req->signal_id == AVDTP_ABORT ?
+ ABORT_TIMEOUT : REQ_TIMEOUT,
+ request_timeout,
+ session);
+ return 0;
+
+failed:
+ g_free(req->data);
+ g_free(req);
+ return err;
+}
+
+static int send_request(struct avdtp *session, gboolean priority,
+ struct avdtp_stream *stream, uint8_t signal_id,
+ void *buffer, size_t size)
+{
+ struct pending_req *req;
+
+ if (size > 0 && !buffer) {
+ DBG("Invalid buffer %p", buffer);
+ return -EINVAL;
+ }
+
+ if (stream && stream->abort_int && signal_id != AVDTP_ABORT) {
+ DBG("Unable to send requests while aborting");
+ return -EINVAL;
+ }
+
+ req = g_new0(struct pending_req, 1);
+ req->signal_id = signal_id;
+ req->data_size = size;
+ req->stream = stream;
+
+ if (size > 0) {
+ req->data = g_malloc(size);
+ memcpy(req->data, buffer, size);
+ }
+
+ return send_req(session, priority, req);
+}
+
+static gboolean avdtp_discover_resp(struct avdtp *session,
+ struct discover_resp *resp, int size)
+{
+ int sep_count, i;
+ uint8_t getcap_cmd;
+ int ret = 0;
+ gboolean getcap_pending = FALSE;
+
+ if (session->version >= 0x0103)
+ getcap_cmd = AVDTP_GET_ALL_CAPABILITIES;
+ else
+ getcap_cmd = AVDTP_GET_CAPABILITIES;
+
+ sep_count = size / sizeof(struct seid_info);
+
+ for (i = 0; i < sep_count; i++) {
+ struct avdtp_remote_sep *sep;
+ struct avdtp_stream *stream;
+ struct seid_req req;
+
+ DBG("seid %d type %d media %d in use %d",
+ resp->seps[i].seid, resp->seps[i].type,
+ resp->seps[i].media_type, resp->seps[i].inuse);
+
+ stream = find_stream_by_rseid(session, resp->seps[i].seid);
+
+ sep = find_remote_sep(session->seps, resp->seps[i].seid);
+ if (!sep) {
+ if (resp->seps[i].inuse && !stream)
+ continue;
+ sep = g_new0(struct avdtp_remote_sep, 1);
+ session->seps = g_slist_append(session->seps, sep);
+ }
+
+ sep->stream = stream;
+ sep->seid = resp->seps[i].seid;
+ sep->type = resp->seps[i].type;
+ sep->media_type = resp->seps[i].media_type;
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = sep->seid;
+
+ ret = send_request(session, TRUE, NULL, getcap_cmd,
+ &req, sizeof(req));
+ if (ret < 0)
+ break;
+ getcap_pending = TRUE;
+ }
+
+ if (!getcap_pending)
+ finalize_discovery(session, -ret);
+
+ return TRUE;
+}
+
+static gboolean avdtp_get_capabilities_resp(struct avdtp *session,
+ struct getcap_resp *resp,
+ unsigned int size)
+{
+ struct avdtp_remote_sep *sep;
+ uint8_t seid;
+
+ /* Check for minimum required packet size includes:
+ * 1. getcap resp header
+ * 2. media transport capability (2 bytes)
+ * 3. media codec capability type + length (2 bytes)
+ * 4. the actual media codec elements
+ * */
+ if (size < (sizeof(struct getcap_resp) + 4 +
+ sizeof(struct avdtp_media_codec_capability))) {
+ error("Too short getcap resp packet");
+ return FALSE;
+ }
+
+ seid = ((struct seid_req *) session->req->data)->acp_seid;
+
+ sep = find_remote_sep(session->seps, seid);
+
+ DBG("seid %d type %d media %d", sep->seid,
+ sep->type, sep->media_type);
+
+ if (sep->caps) {
+ g_slist_free_full(sep->caps, g_free);
+ sep->caps = NULL;
+ sep->codec = NULL;
+ sep->delay_reporting = FALSE;
+ }
+
+ sep->caps = caps_to_list(resp->caps, size - sizeof(struct getcap_resp),
+ &sep->codec, &sep->delay_reporting);
+
+ return TRUE;
+}
+
+static gboolean avdtp_set_configuration_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_single_header *resp,
+ int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CONFIGURED);
+
+ if (sep->cfm && sep->cfm->set_configuration)
+ sep->cfm->set_configuration(session, sep, stream, NULL,
+ sep->user_data);
+
+ return TRUE;
+}
+
+static gboolean avdtp_reconfigure_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_single_header *resp,
+ int size)
+{
+ return TRUE;
+}
+
+static gboolean avdtp_open_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct seid_rej *resp, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ session->pending_open = stream;
+
+ if (!stream->open_acp && sep->cfm && sep->cfm->open)
+ sep->cfm->open(session, sep, stream, NULL, sep->user_data);
+
+ return TRUE;
+}
+
+static gboolean avdtp_start_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct seid_rej *resp, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ /* We might be in STREAMING already if both sides send START_CMD at the
+ * same time and the one in SNK role doesn't reject it as it should */
+ if (sep->state != AVDTP_STATE_STREAMING)
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_STREAMING);
+
+ if (sep->cfm && sep->cfm->start)
+ sep->cfm->start(session, sep, stream, NULL, sep->user_data);
+
+ return TRUE;
+}
+
+static gboolean avdtp_close_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct seid_rej *resp, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_CLOSING);
+
+ close_stream(stream);
+
+ return TRUE;
+}
+
+static gboolean avdtp_suspend_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ void *data, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_OPEN);
+
+ if (sep->cfm && sep->cfm->suspend)
+ sep->cfm->suspend(session, sep, stream, NULL, sep->user_data);
+
+ return TRUE;
+}
+
+static gboolean avdtp_abort_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct seid_rej *resp, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_ABORTING);
+
+ if (sep->cfm && sep->cfm->abort)
+ sep->cfm->abort(session, sep, stream, NULL, sep->user_data);
+
+ avdtp_sep_set_state(session, sep, AVDTP_STATE_IDLE);
+
+ return TRUE;
+}
+
+static gboolean avdtp_delay_report_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ void *data, int size)
+{
+ struct avdtp_local_sep *sep = stream->lsep;
+
+ if (sep->cfm && sep->cfm->delay_report)
+ sep->cfm->delay_report(session, sep, stream, NULL,
+ sep->user_data);
+
+ return TRUE;
+}
+
+static gboolean avdtp_parse_resp(struct avdtp *session,
+ struct avdtp_stream *stream,
+ uint8_t transaction, uint8_t signal_id,
+ void *buf, int size)
+{
+ struct pending_req *next;
+ const char *get_all = "";
+
+ if (session->prio_queue)
+ next = session->prio_queue->data;
+ else if (session->req_queue)
+ next = session->req_queue->data;
+ else
+ next = NULL;
+
+ switch (signal_id) {
+ case AVDTP_DISCOVER:
+ DBG("DISCOVER request succeeded");
+ return avdtp_discover_resp(session, buf, size);
+ case AVDTP_GET_ALL_CAPABILITIES:
+ get_all = "ALL_";
+ /* fall through */
+ case AVDTP_GET_CAPABILITIES:
+ DBG("GET_%sCAPABILITIES request succeeded", get_all);
+ if (!avdtp_get_capabilities_resp(session, buf, size))
+ return FALSE;
+ if (!(next && (next->signal_id == AVDTP_GET_CAPABILITIES ||
+ next->signal_id == AVDTP_GET_ALL_CAPABILITIES)))
+ finalize_discovery(session, 0);
+ return TRUE;
+ }
+
+ /* The remaining commands require an existing stream so bail out
+ * here if the stream got unexpectedly disconnected */
+ if (!stream) {
+ DBG("AVDTP: stream was closed while waiting for reply");
+ return TRUE;
+ }
+
+ switch (signal_id) {
+ case AVDTP_SET_CONFIGURATION:
+ DBG("SET_CONFIGURATION request succeeded");
+ return avdtp_set_configuration_resp(session, stream,
+ buf, size);
+ case AVDTP_RECONFIGURE:
+ DBG("RECONFIGURE request succeeded");
+ return avdtp_reconfigure_resp(session, stream, buf, size);
+ case AVDTP_OPEN:
+ DBG("OPEN request succeeded");
+ return avdtp_open_resp(session, stream, buf, size);
+ case AVDTP_SUSPEND:
+ DBG("SUSPEND request succeeded");
+ return avdtp_suspend_resp(session, stream, buf, size);
+ case AVDTP_START:
+ DBG("START request succeeded");
+ return avdtp_start_resp(session, stream, buf, size);
+ case AVDTP_CLOSE:
+ DBG("CLOSE request succeeded");
+ return avdtp_close_resp(session, stream, buf, size);
+ case AVDTP_ABORT:
+ DBG("ABORT request succeeded");
+ return avdtp_abort_resp(session, stream, buf, size);
+ case AVDTP_DELAY_REPORT:
+ DBG("DELAY_REPORT request succeeded");
+ return avdtp_delay_report_resp(session, stream, buf, size);
+ }
+
+ error("Unknown signal id in accept response: %u", signal_id);
+ return TRUE;
+}
+
+static gboolean seid_rej_to_err(struct seid_rej *rej, unsigned int size,
+ struct avdtp_error *err)
+{
+ if (size < sizeof(struct seid_rej)) {
+ error("Too small packet for seid_rej");
+ return FALSE;
+ }
+
+ avdtp_error_init(err, 0x00, rej->error);
+
+ return TRUE;
+}
+
+static gboolean conf_rej_to_err(struct conf_rej *rej, unsigned int size,
+ struct avdtp_error *err)
+{
+ if (size < sizeof(struct conf_rej)) {
+ error("Too small packet for conf_rej");
+ return FALSE;
+ }
+
+ avdtp_error_init(err, rej->category, rej->error);
+
+ return TRUE;
+}
+
+static gboolean stream_rej_to_err(struct stream_rej *rej, unsigned int size,
+ struct avdtp_error *err,
+ uint8_t *acp_seid)
+{
+ if (size < sizeof(struct stream_rej)) {
+ error("Too small packet for stream_rej");
+ return FALSE;
+ }
+
+ avdtp_error_init(err, 0x00, rej->error);
+
+ if (acp_seid)
+ *acp_seid = rej->acp_seid;
+
+ return TRUE;
+}
+
+static gboolean avdtp_parse_rej(struct avdtp *session,
+ struct avdtp_stream *stream,
+ uint8_t transaction, uint8_t signal_id,
+ void *buf, int size)
+{
+ struct avdtp_error err;
+ uint8_t acp_seid;
+ struct avdtp_local_sep *sep = stream ? stream->lsep : NULL;
+
+ switch (signal_id) {
+ case AVDTP_DISCOVER:
+ case AVDTP_GET_CAPABILITIES:
+ case AVDTP_GET_ALL_CAPABILITIES:
+ if (!seid_rej_to_err(buf, size, &err))
+ return FALSE;
+ error("%s request rejected: %s (%d)",
+ signal_id == AVDTP_DISCOVER ? "DISCOVER" :
+ signal_id == AVDTP_GET_CAPABILITIES ?
+ "GET_CAPABILITIES" : "GET_ALL_CAPABILITIES",
+ avdtp_strerror(&err), err.err.error_code);
+ if (session->discover) {
+ session->discover->cb(session, session->seps, &err,
+ session->discover->user_data);
+ g_free(session->discover);
+ session->discover = NULL;
+ }
+ return TRUE;
+ case AVDTP_OPEN:
+ if (!seid_rej_to_err(buf, size, &err))
+ return FALSE;
+ error("OPEN request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->open)
+ sep->cfm->open(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ case AVDTP_SET_CONFIGURATION:
+ if (!conf_rej_to_err(buf, size, &err))
+ return FALSE;
+ error("SET_CONFIGURATION request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->set_configuration)
+ sep->cfm->set_configuration(session, sep, stream,
+ &err, sep->user_data);
+ return TRUE;
+ case AVDTP_GET_CONFIGURATION:
+ if (!seid_rej_to_err(buf, size, &err))
+ return FALSE;
+ error("GET_CONFIGURATION request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->get_configuration)
+ sep->cfm->get_configuration(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ case AVDTP_RECONFIGURE:
+ if (!conf_rej_to_err(buf, size, &err))
+ return FALSE;
+ error("RECONFIGURE request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->reconfigure)
+ sep->cfm->reconfigure(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ case AVDTP_START:
+ if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+ return FALSE;
+ error("START request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->start) {
+ stream->starting = FALSE;
+ sep->cfm->start(session, sep, stream, &err,
+ sep->user_data);
+ }
+ return TRUE;
+ case AVDTP_SUSPEND:
+ if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+ return FALSE;
+ error("SUSPEND request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->suspend)
+ sep->cfm->suspend(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ case AVDTP_CLOSE:
+ if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+ return FALSE;
+ error("CLOSE request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->close) {
+ sep->cfm->close(session, sep, stream, &err,
+ sep->user_data);
+ stream->close_int = FALSE;
+ }
+ return TRUE;
+ case AVDTP_ABORT:
+ if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+ return FALSE;
+ error("ABORT request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->abort)
+ sep->cfm->abort(session, sep, stream, &err,
+ sep->user_data);
+ return FALSE;
+ case AVDTP_DELAY_REPORT:
+ if (!stream_rej_to_err(buf, size, &err, &acp_seid))
+ return FALSE;
+ error("DELAY_REPORT request rejected: %s (%d)",
+ avdtp_strerror(&err), err.err.error_code);
+ if (sep && sep->cfm && sep->cfm->delay_report)
+ sep->cfm->delay_report(session, sep, stream, &err,
+ sep->user_data);
+ return TRUE;
+ default:
+ error("Unknown reject response signal id: %u", signal_id);
+ return TRUE;
+ }
+}
+
+struct avdtp_service_capability *avdtp_stream_get_codec(
+ struct avdtp_stream *stream)
+{
+ GSList *l;
+
+ for (l = stream->caps; l; l = l->next) {
+ struct avdtp_service_capability *cap = l->data;
+
+ if (cap->category == AVDTP_MEDIA_CODEC)
+ return cap;
+ }
+
+ return NULL;
+}
+
+static gboolean avdtp_stream_has_capability(struct avdtp_stream *stream,
+ struct avdtp_service_capability *cap)
+{
+ GSList *l;
+ struct avdtp_service_capability *stream_cap;
+
+ for (l = stream->caps; l; l = g_slist_next(l)) {
+ stream_cap = l->data;
+
+ if (stream_cap->category != cap->category ||
+ stream_cap->length != cap->length)
+ continue;
+
+ if (memcmp(stream_cap->data, cap->data, cap->length) == 0)
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream,
+ GSList *caps)
+{
+ for (; caps; caps = g_slist_next(caps)) {
+ struct avdtp_service_capability *cap = caps->data;
+
+ if (!avdtp_stream_has_capability(stream, cap))
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+struct avdtp_remote_sep *avdtp_stream_get_remote_sep(
+ struct avdtp_stream *stream)
+{
+ GSList *l;
+
+ for (l = stream->session->seps; l; l = l->next) {
+ struct avdtp_remote_sep *sep = l->data;
+
+ if (sep->seid == stream->rseid)
+ return sep;
+ }
+
+ return NULL;
+}
+
+gboolean avdtp_stream_set_transport(struct avdtp_stream *stream, int fd,
+ size_t imtu, size_t omtu)
+{
+ GIOChannel *io;
+
+ if (stream != stream->session->pending_open)
+ return FALSE;
+
+ if (set_priority(fd, 5) < 0)
+ return FALSE;
+
+ io = g_io_channel_unix_new(fd);
+
+ handle_transport_connect(stream->session, io, imtu, omtu);
+
+ g_io_channel_unref(io);
+
+ return TRUE;
+}
+
+gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
+ uint16_t *imtu, uint16_t *omtu,
+ GSList **caps)
+{
+ if (stream->io == NULL)
+ return FALSE;
+
+ if (sock)
+ *sock = g_io_channel_unix_get_fd(stream->io);
+
+ if (omtu)
+ *omtu = stream->omtu;
+
+ if (imtu)
+ *imtu = stream->imtu;
+
+ if (caps)
+ *caps = stream->caps;
+
+ return TRUE;
+}
+
+static int process_queue(struct avdtp *session)
+{
+ GSList **queue, *l;
+ struct pending_req *req;
+
+ if (session->req)
+ return 0;
+
+ if (session->prio_queue)
+ queue = &session->prio_queue;
+ else
+ queue = &session->req_queue;
+
+ if (!*queue)
+ return 0;
+
+ l = *queue;
+ req = l->data;
+
+ *queue = g_slist_remove(*queue, req);
+
+ return send_req(session, FALSE, req);
+}
+
+struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep)
+{
+ return sep->codec;
+}
+
+struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category,
+ const void *data,
+ int length)
+{
+ struct avdtp_service_capability *cap;
+
+ if (category < AVDTP_MEDIA_TRANSPORT ||
+ category > AVDTP_DELAY_REPORTING)
+ return NULL;
+
+ if (length > 0 && !data)
+ return NULL;
+
+ cap = g_malloc(sizeof(struct avdtp_service_capability) + length);
+ cap->category = category;
+ cap->length = length;
+
+ if (length > 0)
+ memcpy(cap->data, data, length);
+
+ return cap;
+}
+
+static gboolean process_discover(gpointer data)
+{
+ struct avdtp *session = data;
+
+ session->discover->id = 0;
+
+ finalize_discovery(session, 0);
+
+ return FALSE;
+}
+
+int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
+ void *user_data)
+{
+ int err;
+
+ if (session->discover)
+ return -EBUSY;
+
+ session->discover = g_new0(struct discover_callback, 1);
+
+ if (session->seps) {
+ session->discover->cb = cb;
+ session->discover->user_data = user_data;
+ session->discover->id = g_idle_add(process_discover, session);
+ return 0;
+ }
+
+ err = send_request(session, FALSE, NULL, AVDTP_DISCOVER, NULL, 0);
+ if (err == 0) {
+ session->discover->cb = cb;
+ session->discover->user_data = user_data;
+ }
+
+ return err;
+}
+
+gboolean avdtp_stream_remove_cb(struct avdtp *session,
+ struct avdtp_stream *stream,
+ unsigned int id)
+{
+ GSList *l;
+ struct stream_callback *cb;
+
+ if (!stream)
+ return FALSE;
+
+ for (cb = NULL, l = stream->callbacks; l != NULL; l = l->next) {
+ struct stream_callback *tmp = l->data;
+ if (tmp && tmp->id == id) {
+ cb = tmp;
+ break;
+ }
+ }
+
+ if (!cb)
+ return FALSE;
+
+ stream->callbacks = g_slist_remove(stream->callbacks, cb);
+ g_free(cb);
+
+ return TRUE;
+}
+
+unsigned int avdtp_stream_add_cb(struct avdtp *session,
+ struct avdtp_stream *stream,
+ avdtp_stream_state_cb cb, void *data)
+{
+ struct stream_callback *stream_cb;
+ static unsigned int id = 0;
+
+ stream_cb = g_new(struct stream_callback, 1);
+ stream_cb->cb = cb;
+ stream_cb->user_data = data;
+ stream_cb->id = ++id;
+
+ stream->callbacks = g_slist_append(stream->callbacks, stream_cb);
+
+ return stream_cb->id;
+}
+
+int avdtp_get_configuration(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct seid_req req;
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, AVDTP_GET_CONFIGURATION,
+ &req, sizeof(req));
+}
+
+static void copy_capabilities(gpointer data, gpointer user_data)
+{
+ struct avdtp_service_capability *src_cap = data;
+ struct avdtp_service_capability *dst_cap;
+ GSList **l = user_data;
+
+ dst_cap = avdtp_service_cap_new(src_cap->category, src_cap->data,
+ src_cap->length);
+
+ *l = g_slist_append(*l, dst_cap);
+}
+
+int avdtp_set_configuration(struct avdtp *session,
+ struct avdtp_remote_sep *rsep,
+ struct avdtp_local_sep *lsep,
+ GSList *caps,
+ struct avdtp_stream **stream)
+{
+ struct setconf_req *req;
+ struct avdtp_stream *new_stream;
+ unsigned char *ptr;
+ int err, caps_len;
+ struct avdtp_service_capability *cap;
+ GSList *l;
+
+ if (!(lsep && rsep))
+ return -EINVAL;
+
+ DBG("%p: int_seid=%u, acp_seid=%u", session,
+ lsep->info.seid, rsep->seid);
+
+ new_stream = g_new0(struct avdtp_stream, 1);
+ new_stream->session = session;
+ new_stream->lsep = lsep;
+ new_stream->rseid = rsep->seid;
+
+ if (rsep->delay_reporting && lsep->delay_reporting) {
+ struct avdtp_service_capability *delay_reporting;
+
+ delay_reporting = avdtp_service_cap_new(AVDTP_DELAY_REPORTING,
+ NULL, 0);
+ caps = g_slist_append(caps, delay_reporting);
+ new_stream->delay_reporting = TRUE;
+ }
+
+ g_slist_foreach(caps, copy_capabilities, &new_stream->caps);
+
+ /* Calculate total size of request */
+ for (l = caps, caps_len = 0; l != NULL; l = g_slist_next(l)) {
+ cap = l->data;
+ caps_len += cap->length + 2;
+ }
+
+ req = g_malloc0(sizeof(struct setconf_req) + caps_len);
+
+ req->int_seid = lsep->info.seid;
+ req->acp_seid = rsep->seid;
+
+ /* Copy the capabilities into the request */
+ for (l = caps, ptr = req->caps; l != NULL; l = g_slist_next(l)) {
+ cap = l->data;
+ memcpy(ptr, cap, cap->length + 2);
+ ptr += cap->length + 2;
+ }
+
+ err = send_request(session, FALSE, new_stream,
+ AVDTP_SET_CONFIGURATION, req,
+ sizeof(struct setconf_req) + caps_len);
+ if (err < 0)
+ stream_free(new_stream);
+ else {
+ lsep->info.inuse = 1;
+ lsep->stream = new_stream;
+ rsep->stream = new_stream;
+ session->streams = g_slist_append(session->streams, new_stream);
+ if (stream)
+ *stream = new_stream;
+ }
+
+ g_free(req);
+
+ return err;
+}
+
+int avdtp_open(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct seid_req req;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state > AVDTP_STATE_CONFIGURED)
+ return -EINVAL;
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, AVDTP_OPEN,
+ &req, sizeof(req));
+}
+
+static gboolean start_timeout(gpointer user_data)
+{
+ struct avdtp_stream *stream = user_data;
+ struct avdtp *session = stream->session;
+
+ stream->open_acp = FALSE;
+
+ if (avdtp_start(session, stream) < 0)
+ error("wait_timeout: avdtp_start failed");
+
+ stream->start_timer = 0;
+
+ return FALSE;
+}
+
+int avdtp_start(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct start_req req;
+ int ret;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state != AVDTP_STATE_OPEN)
+ return -EINVAL;
+
+ /* Recommendation 12:
+ * If the RD has configured and opened a stream it is also responsible
+ * to start the streaming via GAVDP_START.
+ */
+ if (stream->open_acp) {
+ /* If timer already active wait it */
+ if (stream->start_timer)
+ return 0;
+
+ stream->start_timer = g_timeout_add_seconds(START_TIMEOUT,
+ start_timeout,
+ stream);
+ return 0;
+ }
+
+ if (stream->close_int == TRUE) {
+ error("avdtp_start: rejecting start since close is initiated");
+ return -EINVAL;
+ }
+
+ if (stream->starting == TRUE) {
+ DBG("stream already started");
+ return -EINPROGRESS;
+ }
+
+ memset(&req, 0, sizeof(req));
+ req.first_seid.seid = stream->rseid;
+
+ ret = send_request(session, FALSE, stream, AVDTP_START,
+ &req, sizeof(req));
+ if (ret == 0)
+ stream->starting = TRUE;
+
+ return ret;
+}
+
+int avdtp_close(struct avdtp *session, struct avdtp_stream *stream,
+ gboolean immediate)
+{
+ struct seid_req req;
+ int ret;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->close_int == TRUE) {
+ error("avdtp_close: rejecting since close is already initiated");
+ return -EINVAL;
+ }
+
+ /* If stream is not yet in the OPEN state, let's use ABORT_CMD */
+ if (stream->lsep->state < AVDTP_STATE_OPEN)
+ return avdtp_abort(session, stream);
+
+ if (immediate && session->req && stream == session->req->stream)
+ return avdtp_abort(session, stream);
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+
+ ret = send_request(session, FALSE, stream, AVDTP_CLOSE,
+ &req, sizeof(req));
+ if (ret == 0)
+ stream->close_int = TRUE;
+
+ return ret;
+}
+
+int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct seid_req req;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state <= AVDTP_STATE_OPEN || stream->close_int)
+ return -EINVAL;
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+
+ return send_request(session, FALSE, stream, AVDTP_SUSPEND,
+ &req, sizeof(req));
+}
+
+int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream)
+{
+ struct seid_req req;
+ int ret;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state == AVDTP_STATE_ABORTING)
+ return -EINVAL;
+
+ if (session->req && session->req->timeout > 0 &&
+ stream == session->req->stream)
+ return cancel_request(session, ECANCELED);
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+
+ ret = send_request(session, TRUE, stream, AVDTP_ABORT,
+ &req, sizeof(req));
+ if (ret == 0)
+ stream->abort_int = TRUE;
+
+ return ret;
+}
+
+int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream,
+ uint16_t delay)
+{
+ struct delay_req req;
+
+ if (!g_slist_find(session->streams, stream))
+ return -EINVAL;
+
+ if (stream->lsep->state != AVDTP_STATE_CONFIGURED &&
+ stream->lsep->state != AVDTP_STATE_STREAMING)
+ return -EINVAL;
+
+ if (!stream->delay_reporting || session->version < 0x0103)
+ return -EINVAL;
+
+ stream->delay = delay;
+
+ memset(&req, 0, sizeof(req));
+ req.acp_seid = stream->rseid;
+ req.delay = htons(delay);
+
+ return send_request(session, TRUE, stream, AVDTP_DELAY_REPORT,
+ &req, sizeof(req));
+}
+
+struct avdtp_local_sep *avdtp_register_sep(struct queue *lseps, uint8_t type,
+ uint8_t media_type,
+ uint8_t codec_type,
+ gboolean delay_reporting,
+ struct avdtp_sep_ind *ind,
+ struct avdtp_sep_cfm *cfm,
+ void *user_data)
+{
+ struct avdtp_local_sep *sep;
+ uint8_t seid = util_get_uid(&seids, MAX_SEID);
+
+ if (!seid)
+ return NULL;
+
+ sep = g_new0(struct avdtp_local_sep, 1);
+
+ sep->state = AVDTP_STATE_IDLE;
+ sep->info.seid = seid;
+ sep->info.type = type;
+ sep->info.media_type = media_type;
+ sep->codec = codec_type;
+ sep->ind = ind;
+ sep->cfm = cfm;
+ sep->user_data = user_data;
+ sep->delay_reporting = delay_reporting;
+
+ DBG("SEP %p registered: type:%d codec:%d seid:%d", sep,
+ sep->info.type, sep->codec, sep->info.seid);
+
+ queue_push_tail(lseps, sep);
+
+ return sep;
+}
+
+void avdtp_sep_set_vendor_codec(struct avdtp_local_sep *sep, uint32_t vendor_id,
+ uint16_t codec_id)
+{
+ sep->vndcodec_vendor = vendor_id;
+ sep->vndcodec_codec = codec_id;
+}
+
+int avdtp_unregister_sep(struct queue *lseps, struct avdtp_local_sep *sep)
+{
+ if (!sep)
+ return -EINVAL;
+
+ if (sep->stream)
+ release_stream(sep->stream, sep->stream->session);
+
+ DBG("SEP %p unregistered: type:%d codec:%d seid:%d", sep,
+ sep->info.type, sep->codec, sep->info.seid);
+
+ util_clear_uid(&seids, sep->info.seid);
+ queue_remove(lseps, sep);
+ g_free(sep);
+
+ return 0;
+}
+
+const char *avdtp_strerror(struct avdtp_error *err)
+{
+ if (err->category == AVDTP_ERRNO)
+ return strerror(err->err.posix_errno);
+
+ switch (err->err.error_code) {
+ case AVDTP_BAD_HEADER_FORMAT:
+ return "Bad Header Format";
+ case AVDTP_BAD_LENGTH:
+ return "Bad Packet Length";
+ case AVDTP_BAD_ACP_SEID:
+ return "Bad Acceptor SEID";
+ case AVDTP_SEP_IN_USE:
+ return "Stream End Point in Use";
+ case AVDTP_SEP_NOT_IN_USE:
+ return "Stream End Point Not in Use";
+ case AVDTP_BAD_SERV_CATEGORY:
+ return "Bad Service Category";
+ case AVDTP_BAD_PAYLOAD_FORMAT:
+ return "Bad Payload format";
+ case AVDTP_NOT_SUPPORTED_COMMAND:
+ return "Command Not Supported";
+ case AVDTP_INVALID_CAPABILITIES:
+ return "Invalid Capabilities";
+ case AVDTP_BAD_RECOVERY_TYPE:
+ return "Bad Recovery Type";
+ case AVDTP_BAD_MEDIA_TRANSPORT_FORMAT:
+ return "Bad Media Transport Format";
+ case AVDTP_BAD_RECOVERY_FORMAT:
+ return "Bad Recovery Format";
+ case AVDTP_BAD_ROHC_FORMAT:
+ return "Bad Header Compression Format";
+ case AVDTP_BAD_CP_FORMAT:
+ return "Bad Content Protection Format";
+ case AVDTP_BAD_MULTIPLEXING_FORMAT:
+ return "Bad Multiplexing Format";
+ case AVDTP_UNSUPPORTED_CONFIGURATION:
+ return "Configuration not supported";
+ case AVDTP_BAD_STATE:
+ return "Bad State";
+ default:
+ return "Unknown error";
+ }
+}
+
+avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep)
+{
+ return sep->state;
+}
+
+gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream)
+{
+ return g_slist_find(session->streams, stream) ? TRUE : FALSE;
+}
diff --git a/unit/avdtp.h b/unit/avdtp.h
new file mode 100644
index 0000000..f45edae
--- /dev/null
+++ b/unit/avdtp.h
+/* SPDX-License-Identifier: GPL-2.0-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2006-2010 Nokia Corporation
+ * Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
+ *
+ *
+ */
+
+struct avdtp;
+struct avdtp_stream;
+struct avdtp_local_sep;
+struct avdtp_remote_sep;
+struct avdtp_error {
+ uint8_t category;
+ union {
+ uint8_t error_code;
+ int posix_errno;
+ } err;
+};
+
+#define AVDTP_PSM 25
+
+/* SEP capability categories */
+#define AVDTP_MEDIA_TRANSPORT 0x01
+#define AVDTP_REPORTING 0x02
+#define AVDTP_RECOVERY 0x03
+#define AVDTP_CONTENT_PROTECTION 0x04
+#define AVDTP_HEADER_COMPRESSION 0x05
+#define AVDTP_MULTIPLEXING 0x06
+#define AVDTP_MEDIA_CODEC 0x07
+#define AVDTP_DELAY_REPORTING 0x08
+#define AVDTP_ERRNO 0xff
+
+/* AVDTP error definitions */
+#define AVDTP_BAD_HEADER_FORMAT 0x01
+#define AVDTP_BAD_LENGTH 0x11
+#define AVDTP_BAD_ACP_SEID 0x12
+#define AVDTP_SEP_IN_USE 0x13
+#define AVDTP_SEP_NOT_IN_USE 0x14
+#define AVDTP_BAD_SERV_CATEGORY 0x17
+#define AVDTP_BAD_PAYLOAD_FORMAT 0x18
+#define AVDTP_NOT_SUPPORTED_COMMAND 0x19
+#define AVDTP_INVALID_CAPABILITIES 0x1A
+#define AVDTP_BAD_RECOVERY_TYPE 0x22
+#define AVDTP_BAD_MEDIA_TRANSPORT_FORMAT 0x23
+#define AVDTP_BAD_RECOVERY_FORMAT 0x25
+#define AVDTP_BAD_ROHC_FORMAT 0x26
+#define AVDTP_BAD_CP_FORMAT 0x27
+#define AVDTP_BAD_MULTIPLEXING_FORMAT 0x28
+#define AVDTP_UNSUPPORTED_CONFIGURATION 0x29
+#define AVDTP_BAD_STATE 0x31
+
+/* SEP types definitions */
+#define AVDTP_SEP_TYPE_SOURCE 0x00
+#define AVDTP_SEP_TYPE_SINK 0x01
+
+/* Media types definitions */
+#define AVDTP_MEDIA_TYPE_AUDIO 0x00
+#define AVDTP_MEDIA_TYPE_VIDEO 0x01
+#define AVDTP_MEDIA_TYPE_MULTIMEDIA 0x02
+
+typedef enum {
+ AVDTP_STATE_IDLE,
+ AVDTP_STATE_CONFIGURED,
+ AVDTP_STATE_OPEN,
+ AVDTP_STATE_STREAMING,
+ AVDTP_STATE_CLOSING,
+ AVDTP_STATE_ABORTING,
+} avdtp_state_t;
+
+struct avdtp_service_capability {
+ uint8_t category;
+ uint8_t length;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+
+struct avdtp_media_codec_capability {
+ uint8_t rfa0:4;
+ uint8_t media_type:4;
+ uint8_t media_codec_type;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+#elif __BYTE_ORDER == __BIG_ENDIAN
+
+struct avdtp_media_codec_capability {
+ uint8_t media_type:4;
+ uint8_t rfa0:4;
+ uint8_t media_codec_type;
+ uint8_t data[0];
+} __attribute__ ((packed));
+
+#else
+#error "Unknown byte order"
+#endif
+
+typedef void (*avdtp_stream_state_cb) (struct avdtp_stream *stream,
+ avdtp_state_t old_state,
+ avdtp_state_t new_state,
+ struct avdtp_error *err,
+ void *user_data);
+
+typedef void (*avdtp_set_configuration_cb) (struct avdtp *session,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err);
+
+/* Callbacks for when a reply is received to a command that we sent */
+struct avdtp_sep_cfm {
+ void (*set_configuration) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err,
+ void *user_data);
+ void (*get_configuration) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err,
+ void *user_data);
+ void (*open) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data);
+ void (*start) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream, struct avdtp_error *err,
+ void *user_data);
+ void (*suspend) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data);
+ void (*close) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data);
+ void (*abort) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data);
+ void (*reconfigure) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data);
+ void (*delay_report) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ struct avdtp_error *err, void *user_data);
+};
+
+/*
+ * Callbacks for indicating when we received a new command. The return value
+ * indicates whether the command should be rejected or accepted
+ */
+struct avdtp_sep_ind {
+ gboolean (*get_capability) (struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ GSList **caps, uint8_t *err,
+ void *user_data);
+ gboolean (*set_configuration) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream,
+ GSList *caps,
+ avdtp_set_configuration_cb cb,
+ void *user_data);
+ gboolean (*get_configuration) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ uint8_t *err, void *user_data);
+ gboolean (*open) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data);
+ gboolean (*start) (struct avdtp *session, struct avdtp_local_sep *lsep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data);
+ gboolean (*suspend) (struct avdtp *session,
+ struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data);
+ gboolean (*close) (struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data);
+ void (*abort) (struct avdtp *session, struct avdtp_local_sep *sep,
+ struct avdtp_stream *stream, uint8_t *err,
+ void *user_data);
+ gboolean (*reconfigure) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ uint8_t *err, void *user_data);
+ gboolean (*delayreport) (struct avdtp *session,
+ struct avdtp_local_sep *lsep,
+ uint8_t rseid, uint16_t delay,
+ uint8_t *err, void *user_data);
+};
+
+typedef void (*avdtp_discover_cb_t) (struct avdtp *session, GSList *seps,
+ struct avdtp_error *err, void *user_data);
+typedef void (*avdtp_disconnect_cb_t) (void *user_data);
+
+struct avdtp *avdtp_new(int fd, size_t imtu, size_t omtu, uint16_t version,
+ struct queue *lseps);
+
+unsigned int avdtp_add_disconnect_cb(struct avdtp *session,
+ avdtp_disconnect_cb_t cb,
+ void *user_data);
+gboolean avdtp_remove_disconnect_cb(struct avdtp *session, unsigned int id);
+
+void avdtp_shutdown(struct avdtp *session);
+
+void avdtp_unref(struct avdtp *session);
+struct avdtp *avdtp_ref(struct avdtp *session);
+
+struct avdtp_service_capability *avdtp_service_cap_new(uint8_t category,
+ const void *data,
+ int size);
+
+struct avdtp_service_capability *avdtp_get_codec(struct avdtp_remote_sep *sep);
+
+int avdtp_discover(struct avdtp *session, avdtp_discover_cb_t cb,
+ void *user_data);
+
+gboolean avdtp_has_stream(struct avdtp *session, struct avdtp_stream *stream);
+
+unsigned int avdtp_stream_add_cb(struct avdtp *session,
+ struct avdtp_stream *stream,
+ avdtp_stream_state_cb cb, void *data);
+gboolean avdtp_stream_remove_cb(struct avdtp *session,
+ struct avdtp_stream *stream,
+ unsigned int id);
+
+gboolean avdtp_stream_set_transport(struct avdtp_stream *stream, int fd,
+ size_t imtu, size_t omtu);
+gboolean avdtp_stream_get_transport(struct avdtp_stream *stream, int *sock,
+ uint16_t *imtu, uint16_t *omtu,
+ GSList **caps);
+struct avdtp_service_capability *avdtp_stream_get_codec(
+ struct avdtp_stream *stream);
+gboolean avdtp_stream_has_capabilities(struct avdtp_stream *stream,
+ GSList *caps);
+struct avdtp_remote_sep *avdtp_stream_get_remote_sep(
+ struct avdtp_stream *stream);
+
+int avdtp_set_configuration(struct avdtp *session,
+ struct avdtp_remote_sep *rsep,
+ struct avdtp_local_sep *lsep,
+ GSList *caps,
+ struct avdtp_stream **stream);
+
+int avdtp_get_configuration(struct avdtp *session,
+ struct avdtp_stream *stream);
+
+int avdtp_open(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_start(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_suspend(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_close(struct avdtp *session, struct avdtp_stream *stream,
+ gboolean immediate);
+int avdtp_abort(struct avdtp *session, struct avdtp_stream *stream);
+int avdtp_delay_report(struct avdtp *session, struct avdtp_stream *stream,
+ uint16_t delay);
+
+struct avdtp_local_sep *avdtp_register_sep(struct queue *lseps, uint8_t type,
+ uint8_t media_type,
+ uint8_t codec_type,
+ gboolean delay_reporting,
+ struct avdtp_sep_ind *ind,
+ struct avdtp_sep_cfm *cfm,
+ void *user_data);
+void avdtp_sep_set_vendor_codec(struct avdtp_local_sep *sep, uint32_t vendor_id,
+ uint16_t codec_id);
+
+/* Find a matching pair of local and remote SEP ID's */
+struct avdtp_remote_sep *avdtp_find_remote_sep(struct avdtp *session,
+ struct avdtp_local_sep *lsep);
+
+int avdtp_unregister_sep(struct queue *lseps, struct avdtp_local_sep *sep);
+
+avdtp_state_t avdtp_sep_get_state(struct avdtp_local_sep *sep);
+
+void avdtp_error_init(struct avdtp_error *err, uint8_t type, int id);
+const char *avdtp_strerror(struct avdtp_error *err);
+uint8_t avdtp_error_category(struct avdtp_error *err);
+int avdtp_error_error_code(struct avdtp_error *err);
+int avdtp_error_posix_errno(struct avdtp_error *err);
diff --git a/unit/avrcp-lib.c b/unit/avrcp-lib.c
new file mode 100644
index 0000000..b342692
--- /dev/null
+++ b/unit/avrcp-lib.c
+// 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 <config.h>
+#endif
+
+#define _GNU_SOURCE
+#include <stdbool.h>
+#include <glib.h>
+#include <errno.h>
+#include <string.h>
+
+#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));
+}
diff --git a/unit/avrcp-lib.h b/unit/avrcp-lib.h
new file mode 100644
index 0000000..5adb321
--- /dev/null
+++ b/unit/avrcp-lib.h
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2014 Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+/* Control PDU ids */
+#define AVRCP_GET_CAPABILITIES 0x10
+#define AVRCP_LIST_PLAYER_ATTRIBUTES 0X11
+#define AVRCP_LIST_PLAYER_VALUES 0x12
+#define AVRCP_GET_CURRENT_PLAYER_VALUE 0x13
+#define AVRCP_SET_PLAYER_VALUE 0x14
+#define AVRCP_GET_PLAYER_ATTRIBUTE_TEXT 0x15
+#define AVRCP_GET_PLAYER_VALUE_TEXT 0x16
+#define AVRCP_DISPLAYABLE_CHARSET 0x17
+#define AVRCP_CT_BATTERY_STATUS 0x18
+#define AVRCP_GET_ELEMENT_ATTRIBUTES 0x20
+#define AVRCP_GET_PLAY_STATUS 0x30
+#define AVRCP_REGISTER_NOTIFICATION 0x31
+#define AVRCP_REQUEST_CONTINUING 0x40
+#define AVRCP_ABORT_CONTINUING 0x41
+#define AVRCP_SET_ABSOLUTE_VOLUME 0x50
+#define AVRCP_SET_ADDRESSED_PLAYER 0x60
+#define AVRCP_SET_BROWSED_PLAYER 0x70
+#define AVRCP_GET_FOLDER_ITEMS 0x71
+#define AVRCP_CHANGE_PATH 0x72
+#define AVRCP_GET_ITEM_ATTRIBUTES 0x73
+#define AVRCP_PLAY_ITEM 0x74
+#define AVRCP_SEARCH 0x80
+#define AVRCP_ADD_TO_NOW_PLAYING 0x90
+#define AVRCP_GENERAL_REJECT 0xA0
+
+/* Notification events */
+#define AVRCP_EVENT_STATUS_CHANGED 0x01
+#define AVRCP_EVENT_TRACK_CHANGED 0x02
+#define AVRCP_EVENT_TRACK_REACHED_END 0x03
+#define AVRCP_EVENT_TRACK_REACHED_START 0x04
+#define AVRCP_EVENT_PLAYBACK_POS_CHANGED 0x05
+#define AVRCP_EVENT_SETTINGS_CHANGED 0x08
+#define AVRCP_EVENT_NOW_PLAYING_CONTENT_CHANGED 0x09
+#define AVRCP_EVENT_AVAILABLE_PLAYERS_CHANGED 0x0a
+#define AVRCP_EVENT_ADDRESSED_PLAYER_CHANGED 0x0b
+#define AVRCP_EVENT_UIDS_CHANGED 0x0c
+#define AVRCP_EVENT_VOLUME_CHANGED 0x0d
+#define AVRCP_EVENT_LAST AVRCP_EVENT_VOLUME_CHANGED
+
+/* Status codes */
+#define AVRCP_STATUS_INVALID_COMMAND 0x00
+#define AVRCP_STATUS_INVALID_PARAM 0x01
+#define AVRCP_STATUS_PARAM_NOT_FOUND 0x02
+#define AVRCP_STATUS_INTERNAL_ERROR 0x03
+#define AVRCP_STATUS_SUCCESS 0x04
+#define AVRCP_STATUS_UID_CHANGED 0x05
+#define AVRCP_STATUS_NOT_DIRECTORY 0x08
+#define AVRCP_STATUS_DOES_NOT_EXIST 0x09
+#define AVRCP_STATUS_INVALID_SCOPE 0x0a
+#define AVRCP_STATUS_OUT_OF_BOUNDS 0x0b
+#define AVRCP_STATUS_INVALID_PLAYER_ID 0x11
+#define AVRCP_STATUS_PLAYER_NOT_BROWSABLE 0x12
+#define AVRCP_STATUS_NO_AVAILABLE_PLAYERS 0x15
+#define AVRCP_STATUS_ADDRESSED_PLAYER_CHANGED 0x16
+
+/* Capabilities for AVRCP_GET_CAPABILITIES pdu */
+#define CAP_COMPANY_ID 0x02
+#define CAP_EVENTS_SUPPORTED 0x03
+
+/* Player Attributes */
+#define AVRCP_ATTRIBUTE_ILEGAL 0x00
+#define AVRCP_ATTRIBUTE_EQUALIZER 0x01
+#define AVRCP_ATTRIBUTE_REPEAT_MODE 0x02
+#define AVRCP_ATTRIBUTE_SHUFFLE 0x03
+#define AVRCP_ATTRIBUTE_SCAN 0x04
+#define AVRCP_ATTRIBUTE_LAST AVRCP_ATTRIBUTE_SCAN
+
+/* equalizer values */
+#define AVRCP_EQUALIZER_OFF 0x01
+#define AVRCP_EQUALIZER_ON 0x02
+
+/* repeat mode values */
+#define AVRCP_REPEAT_MODE_OFF 0x01
+#define AVRCP_REPEAT_MODE_SINGLE 0x02
+#define AVRCP_REPEAT_MODE_ALL 0x03
+#define AVRCP_REPEAT_MODE_GROUP 0x04
+
+/* shuffle values */
+#define AVRCP_SHUFFLE_OFF 0x01
+#define AVRCP_SHUFFLE_ALL 0x02
+#define AVRCP_SHUFFLE_GROUP 0x03
+
+/* scan values */
+#define AVRCP_SCAN_OFF 0x01
+#define AVRCP_SCAN_ALL 0x02
+#define AVRCP_SCAN_GROUP 0x03
+
+/* media attributes */
+#define AVRCP_MEDIA_ATTRIBUTE_ILLEGAL 0x00
+#define AVRCP_MEDIA_ATTRIBUTE_TITLE 0x01
+#define AVRCP_MEDIA_ATTRIBUTE_ARTIST 0x02
+#define AVRCP_MEDIA_ATTRIBUTE_ALBUM 0x03
+#define AVRCP_MEDIA_ATTRIBUTE_TRACK 0x04
+#define AVRCP_MEDIA_ATTRIBUTE_N_TRACKS 0x05
+#define AVRCP_MEDIA_ATTRIBUTE_GENRE 0x06
+#define AVRCP_MEDIA_ATTRIBUTE_DURATION 0x07
+#define AVRCP_MEDIA_ATTRIBUTE_LAST AVRCP_MEDIA_ATTRIBUTE_DURATION
+
+/* Media Scope */
+#define AVRCP_MEDIA_PLAYER_LIST 0x00
+#define AVRCP_MEDIA_PLAYER_VFS 0x01
+#define AVRCP_MEDIA_SEARCH 0x02
+#define AVRCP_MEDIA_NOW_PLAYING 0x03
+
+/* SDP features */
+#define AVRCP_FEATURE_CATEGORY_1 0x0001
+#define AVRCP_FEATURE_CATEGORY_2 0x0002
+#define AVRCP_FEATURE_CATEGORY_3 0x0004
+#define AVRCP_FEATURE_CATEGORY_4 0x0008
+#define AVRCP_FEATURE_PLAYER_SETTINGS 0x0010
+#define AVRCP_FEATURE_GROUP_NAVIGATION 0x0020
+#define AVRCP_FEATURE_BROWSING 0x0040
+#define AVRCP_FEATURE_MULTIPLE_PLAYERS 0x0080
+
+/* Company IDs for vendor dependent commands */
+#define IEEEID_BTSIG 0x001958
+
+struct avrcp;
+
+struct avrcp_control_ind {
+ int (*get_capabilities) (struct avrcp *session, uint8_t transaction,
+ void *user_data);
+ int (*list_attributes) (struct avrcp *session, uint8_t transaction,
+ void *user_data);
+ int (*get_attribute_text) (struct avrcp *session, uint8_t transaction,
+ uint8_t number, uint8_t *attrs,
+ void *user_data);
+ int (*list_values) (struct avrcp *session, uint8_t transaction,
+ uint8_t attr, void *user_data);
+ int (*get_value_text) (struct avrcp *session, uint8_t transaction,
+ uint8_t attr, uint8_t number,
+ uint8_t *values, void *user_data);
+ int (*get_value) (struct avrcp *session, uint8_t transaction,
+ uint8_t number, uint8_t *attrs,
+ void *user_data);
+ int (*set_value) (struct avrcp *session, uint8_t transaction,
+ uint8_t number, uint8_t *attrs,
+ uint8_t *values, void *user_data);
+ int (*get_play_status) (struct avrcp *session, uint8_t transaction,
+ void *user_data);
+ int (*get_element_attributes) (struct avrcp *session,
+ uint8_t transaction, uint64_t uid,
+ uint8_t number, uint32_t *attrs,
+ void *user_data);
+ int (*register_notification) (struct avrcp *session,
+ uint8_t transaction, uint8_t event,
+ uint32_t interval, void *user_data);
+ int (*set_volume) (struct avrcp *session, uint8_t transaction,
+ uint8_t volume, void *user_data);
+ int (*set_addressed) (struct avrcp *session, uint8_t transaction,
+ uint16_t id, void *user_data);
+ int (*set_browsed) (struct avrcp *session, uint8_t transaction,
+ uint16_t id, void *user_data);
+ int (*get_folder_items) (struct avrcp *session, uint8_t transaction,
+ uint8_t scope, uint32_t start,
+ uint32_t end, uint16_t number,
+ uint32_t *attrs, void *user_data);
+ int (*change_path) (struct avrcp *session, uint8_t transaction,
+ uint16_t counter, uint8_t direction,
+ uint64_t uid, void *user_data);
+ int (*get_item_attributes) (struct avrcp *session, uint8_t transaction,
+ uint8_t scope, uint64_t uid,
+ uint16_t counter, uint8_t number,
+ uint32_t *attrs, void *user_data);
+ int (*play_item) (struct avrcp *session, uint8_t transaction,
+ uint8_t scope, uint64_t uid,
+ uint16_t counter, void *user_data);
+ int (*search) (struct avrcp *session, uint8_t transaction,
+ const char *string, void *user_data);
+ int (*add_to_now_playing) (struct avrcp *session, uint8_t transaction,
+ uint8_t scope, uint64_t uid,
+ uint16_t counter, void *user_data);
+};
+
+struct avrcp_control_cfm {
+ void (*get_capabilities) (struct avrcp *session, int err,
+ uint8_t number, uint8_t *params,
+ void *user_data);
+ void (*list_attributes) (struct avrcp *session, int err,
+ uint8_t number, uint8_t *attrs,
+ void *user_data);
+ void (*get_attribute_text) (struct avrcp *session, int err,
+ uint8_t number, uint8_t *attrs,
+ char **text, void *user_data);
+ void (*list_values) (struct avrcp *session, int err,
+ uint8_t number, uint8_t *values,
+ void *user_data);
+ void (*get_value_text) (struct avrcp *session, int err,
+ uint8_t number, uint8_t *values,
+ char **text, void *user_data);
+ void (*get_value) (struct avrcp *session, int err,
+ uint8_t number, uint8_t *attrs,
+ uint8_t *values, void *user_data);
+ void (*set_value) (struct avrcp *session, int err, void *user_data);
+ void (*get_play_status) (struct avrcp *session, int err,
+ uint8_t status, uint32_t position,
+ uint32_t duration, void *user_data);
+ void (*get_element_attributes) (struct avrcp *session, int err,
+ uint8_t number, uint32_t *attrs,
+ char **text, void *user_data);
+ bool (*register_notification) (struct avrcp *session, int err,
+ uint8_t code, uint8_t event,
+ void *params, void *user_data);
+ void (*set_volume) (struct avrcp *session, int err, uint8_t volume,
+ void *user_data);
+ void (*set_addressed) (struct avrcp *session, int err,
+ void *user_data);
+ void (*set_browsed) (struct avrcp *session, int err,
+ uint16_t counter, uint32_t items,
+ char *path, void *user_data);
+ void (*get_folder_items) (struct avrcp *session, int err,
+ uint16_t counter, uint16_t number,
+ uint8_t *params, void *user_data);
+ void (*change_path) (struct avrcp *session, int err,
+ uint32_t items, void *user_data);
+ void (*get_item_attributes) (struct avrcp *session, int err,
+ uint8_t number, uint32_t *attrs,
+ char **text, void *user_data);
+ void (*play_item) (struct avrcp *session, int err, void *user_data);
+ void (*search) (struct avrcp *session, int err, uint16_t counter,
+ uint32_t items, void *user_data);
+ void (*add_to_now_playing) (struct avrcp *session, int err,
+ void *user_data);
+};
+
+struct avrcp_passthrough_handler {
+ uint8_t op;
+ bool (*func) (struct avrcp *session, bool pressed, void *user_data);
+};
+
+typedef void (*avrcp_destroy_cb_t) (void *user_data);
+
+struct avrcp *avrcp_new(int fd, size_t imtu, size_t omtu, uint16_t version);
+void avrcp_shutdown(struct avrcp *session);
+void avrcp_set_destroy_cb(struct avrcp *session, avrcp_destroy_cb_t cb,
+ void *user_data);
+int avrcp_connect_browsing(struct avrcp *session, int fd, size_t imtu,
+ size_t omtu);
+
+void avrcp_register_player(struct avrcp *session,
+ const struct avrcp_control_ind *ind,
+ const struct avrcp_control_cfm *cfm,
+ void *user_data);
+void avrcp_set_passthrough_handlers(struct avrcp *session,
+ const struct avrcp_passthrough_handler *handlers,
+ void *user_data);
+int avrcp_init_uinput(struct avrcp *session, const char *name,
+ const char *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);
+int avrcp_get_capabilities(struct avrcp *session, uint8_t param);
+int avrcp_register_notification(struct avrcp *session, uint8_t event,
+ uint32_t interval);
+int avrcp_list_player_attributes(struct avrcp *session);
+int avrcp_get_player_attribute_text(struct avrcp *session, uint8_t number,
+ uint8_t *attrs);
+int avrcp_list_player_values(struct avrcp *session, uint8_t attr);
+int avrcp_get_player_value_text(struct avrcp *session, uint8_t attr,
+ uint8_t number, uint8_t *values);
+int avrcp_set_player_value(struct avrcp *session, uint8_t number,
+ uint8_t *attrs, uint8_t *values);
+int avrcp_get_current_player_value(struct avrcp *session, uint8_t number,
+ uint8_t *attrs);
+int avrcp_get_play_status(struct avrcp *session);
+int avrcp_set_volume(struct avrcp *session, uint8_t volume);
+int avrcp_get_element_attributes(struct avrcp *session);
+int avrcp_set_addressed_player(struct avrcp *session, uint16_t player_id);
+int avrcp_set_browsed_player(struct avrcp *session, uint16_t player_id);
+int avrcp_get_folder_items(struct avrcp *session, uint8_t scope,
+ uint32_t start, uint32_t end, uint8_t number,
+ uint32_t *attrs);
+int avrcp_change_path(struct avrcp *session, uint8_t direction, uint64_t uid,
+ uint16_t counter);
+int avrcp_get_item_attributes(struct avrcp *session, uint8_t scope,
+ uint64_t uid, uint16_t counter, uint8_t number,
+ uint32_t *attrs);
+int avrcp_play_item(struct avrcp *session, uint8_t scope, uint64_t uid,
+ uint16_t counter);
+int avrcp_search(struct avrcp *session, const char *string);
+int avrcp_add_to_now_playing(struct avrcp *session, uint8_t scope, uint64_t uid,
+ uint16_t counter);
+
+int avrcp_get_capabilities_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t number, uint8_t *events);
+int avrcp_list_player_attributes_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t number, uint8_t *attrs);
+int avrcp_get_player_attribute_text_rsp(struct avrcp *session,
+ uint8_t transaction, uint8_t number,
+ uint8_t *attrs, const char **text);
+int avrcp_list_player_values_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t number, uint8_t *values);
+int avrcp_get_play_status_rsp(struct avrcp *session, uint8_t transaction,
+ uint32_t position, uint32_t duration,
+ uint8_t status);
+int avrcp_get_player_values_text_rsp(struct avrcp *session,
+ uint8_t transaction, uint8_t number,
+ uint8_t *values, const char **text);
+int avrcp_get_current_player_value_rsp(struct avrcp *session,
+ uint8_t transaction, uint8_t number,
+ uint8_t *attrs, uint8_t *values);
+int avrcp_set_player_value_rsp(struct avrcp *session, uint8_t transaction);
+int avrcp_get_element_attrs_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t *params, size_t params_len);
+int avrcp_register_notification_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t code, uint8_t event,
+ void *data, size_t len);
+int avrcp_set_volume_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t volume);
+int avrcp_set_addressed_player_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t status);
+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);
+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);
+int avrcp_change_path_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t status, uint32_t items);
+int avrcp_get_item_attributes_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t status, uint8_t number,
+ uint32_t *attrs, const char **text);
+int avrcp_play_item_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t status);
+int avrcp_search_rsp(struct avrcp *session, uint8_t transaction, uint8_t status,
+ uint16_t counter, uint32_t items);
+int avrcp_add_to_now_playing_rsp(struct avrcp *session, uint8_t transaction,
+ uint8_t status);
+
+int avrcp_send_passthrough(struct avrcp *session, uint32_t vendor, uint8_t op);
diff --git a/unit/avrcp.c b/unit/avrcp.c
new file mode 100644
index 0000000..fe092f0
--- /dev/null
+++ b/unit/avrcp.c
+// SPDX-License-Identifier: LGPL-2.1-or-later
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2013-2014 Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <stdlib.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <glib.h>
+
+#include "btio/btio.h"
+#include "lib/bluetooth.h"
+#include "lib/sdp.h"
+#include "lib/sdp_lib.h"
+#include "src/sdp-client.h"
+#include "src/shared/util.h"
+#include "src/log.h"
+
+#include "avctp.h"
+#include "avrcp-lib.h"
+#include "hal-msg.h"
+#include "ipc-common.h"
+#include "ipc.h"
+#include "bluetooth.h"
+#include "avrcp.h"
+#include "utils.h"
+
+#define L2CAP_PSM_AVCTP 0x17
+
+static bdaddr_t adapter_addr;
+static uint32_t record_tg_id = 0;
+static uint32_t record_ct_id = 0;
+static GSList *devices = NULL;
+static GIOChannel *server = NULL;
+static struct ipc *hal_ipc = NULL;
+
+struct avrcp_request {
+ struct avrcp_device *dev;
+ uint8_t pdu_id;
+ uint8_t event_id;
+ uint8_t transaction;
+};
+
+struct avrcp_device {
+ bdaddr_t dst;
+ uint16_t version;
+ uint16_t features;
+ struct avrcp *session;
+ GIOChannel *io;
+ GQueue *queue;
+};
+
+static struct avrcp_request *pop_request(uint8_t pdu_id, uint8_t event_id,
+ bool peek)
+{
+ GSList *l;
+
+ for (l = devices; l; l = g_slist_next(l)) {
+ struct avrcp_device *dev = l->data;
+ GList *reqs = g_queue_peek_head_link(dev->queue);
+ int i;
+
+ for (i = 0; reqs; reqs = g_list_next(reqs), i++) {
+ struct avrcp_request *req = reqs->data;
+
+ if (req->pdu_id != pdu_id || req->event_id != event_id)
+ continue;
+
+ if (!peek)
+ g_queue_pop_nth(dev->queue, i);
+
+ return req;
+ }
+ }
+
+ return NULL;
+}
+
+static void handle_get_play_status(const void *buf, uint16_t len)
+{
+ const struct hal_cmd_avrcp_get_play_status *cmd = buf;
+ uint8_t status;
+ struct avrcp_request *req;
+ int ret;
+
+ DBG("");
+
+ req = pop_request(AVRCP_GET_PLAY_STATUS, 0, false);
+ if (!req) {
+ status = HAL_STATUS_FAILED;
+ goto done;
+ }
+
+ ret = avrcp_get_play_status_rsp(req->dev->session, req->transaction,
+ cmd->position, cmd->duration,
+ cmd->status);
+ if (ret < 0) {
+ status = HAL_STATUS_FAILED;
+ g_free(req);
+ goto done;
+ }
+
+ status = HAL_STATUS_SUCCESS;
+ g_free(req);
+
+done:
+ ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_OP_AVRCP_GET_PLAY_STATUS, status);
+}
+
+static void handle_list_player_attrs(const void *buf, uint16_t len)
+{
+ DBG("");
+
+ ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_OP_AVRCP_LIST_PLAYER_ATTRS, HAL_STATUS_FAILED);
+}
+
+static void handle_list_player_values(const void *buf, uint16_t len)
+{
+ DBG("");
+
+ ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_OP_AVRCP_LIST_PLAYER_VALUES, HAL_STATUS_FAILED);
+}
+
+static void handle_get_player_attrs(const void *buf, uint16_t len)
+{
+ DBG("");
+
+ ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_OP_AVRCP_GET_PLAYER_ATTRS, HAL_STATUS_FAILED);
+}
+
+static void handle_get_player_attrs_text(const void *buf, uint16_t len)
+{
+ DBG("");
+
+ ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_OP_AVRCP_GET_PLAYER_ATTRS_TEXT, HAL_STATUS_FAILED);
+}
+
+static void handle_get_player_values_text(const void *buf, uint16_t len)
+{
+ DBG("");
+
+ ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_OP_AVRCP_GET_PLAYER_VALUES_TEXT, HAL_STATUS_FAILED);
+}
+
+static size_t write_element_text(uint8_t id, uint8_t text_len, uint8_t *text,
+ uint8_t *pdu)
+{
+ uint16_t charset = 106;
+ size_t len = 0;
+
+ put_be32(id, pdu);
+ pdu += 4;
+ len += 4;
+
+ put_be16(charset, pdu);
+ pdu += 2;
+ len += 2;
+
+ put_be16(text_len, pdu);
+ pdu += 2;
+ len += 2;
+
+ memcpy(pdu, text, text_len);
+ len += text_len;
+
+ return len;
+}
+
+static void write_element_attrs(uint8_t *ptr, uint8_t number, uint8_t *pdu,
+ size_t *len)
+{
+ int i;
+
+ *pdu = number;
+ pdu++;
+ *len += 1;
+
+ for (i = 0; i < number; i++) {
+ struct hal_avrcp_player_setting_text *text = (void *) ptr;
+ size_t ret;
+
+ ret = write_element_text(text->id, text->len, text->text, pdu);
+
+ ptr += sizeof(*text) + text->len;
+ pdu += ret;
+ *len += ret;
+ }
+}
+
+static void handle_get_element_attrs_text(const void *buf, uint16_t len)
+{
+ struct hal_cmd_avrcp_get_element_attrs_text *cmd = (void *) buf;
+ uint8_t status;
+ struct avrcp_request *req;
+ uint8_t pdu[IPC_MTU];
+ uint8_t *ptr;
+ size_t pdu_len;
+ int ret;
+
+ DBG("");
+
+ req = pop_request(AVRCP_GET_ELEMENT_ATTRIBUTES, 0, false);
+ if (!req) {
+ status = HAL_STATUS_FAILED;
+ goto done;
+ }
+
+ ptr = (uint8_t *) &cmd->values[0];
+ pdu_len = 0;
+ write_element_attrs(ptr, cmd->number, pdu, &pdu_len);
+
+ ret = avrcp_get_element_attrs_rsp(req->dev->session, req->transaction,
+ pdu, pdu_len);
+ if (ret < 0) {
+ status = HAL_STATUS_FAILED;
+ g_free(req);
+ goto done;
+ }
+
+ status = HAL_STATUS_SUCCESS;
+ g_free(req);
+
+done:
+ ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_OP_AVRCP_GET_ELEMENT_ATTRS_TEXT, status);
+}
+
+static void handle_set_player_attrs_value(const void *buf, uint16_t len)
+{
+ DBG("");
+
+ ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_OP_AVRCP_SET_PLAYER_ATTRS_VALUE, HAL_STATUS_FAILED);
+}
+
+static void handle_register_notification(const void *buf, uint16_t len)
+{
+ struct hal_cmd_avrcp_register_notification *cmd = (void *) buf;
+ uint8_t status;
+ struct avrcp_request *req;
+ uint8_t code;
+ bool peek = false;
+ int ret;
+
+ DBG("");
+
+ switch (cmd->type) {
+ case HAL_AVRCP_EVENT_TYPE_INTERIM:
+ code = AVC_CTYPE_INTERIM;
+ peek = true;
+ break;
+ case HAL_AVRCP_EVENT_TYPE_CHANGED:
+ code = AVC_CTYPE_CHANGED;
+ break;
+ default:
+ status = HAL_STATUS_FAILED;
+ goto done;
+ }
+
+ req = pop_request(AVRCP_REGISTER_NOTIFICATION, cmd->event, peek);
+ if (!req) {
+ status = HAL_STATUS_FAILED;
+ goto done;
+ }
+
+ ret = avrcp_register_notification_rsp(req->dev->session,
+ req->transaction, code,
+ cmd->event, cmd->data,
+ cmd->len);
+ if (ret < 0) {
+ status = HAL_STATUS_FAILED;
+ if (!peek)
+ g_free(req);
+ goto done;
+ }
+
+ status = HAL_STATUS_SUCCESS;
+ if (!peek)
+ g_free(req);
+
+done:
+ ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_OP_AVRCP_REGISTER_NOTIFICATION, status);
+}
+
+static void handle_set_volume(const void *buf, uint16_t len)
+{
+ struct hal_cmd_avrcp_set_volume *cmd = (void *) buf;
+ struct avrcp_device *dev;
+ uint8_t status;
+ int ret;
+
+ DBG("");
+
+ if (!devices) {
+ error("AVRCP: No device found to set volume");
+ status = HAL_STATUS_FAILED;
+ goto done;
+ }
+
+ /*
+ * Peek the first device since the HAL cannot really address a specific
+ * device it might mean there could only be one connected.
+ */
+ dev = devices->data;
+
+ ret = avrcp_set_volume(dev->session, cmd->value & 0x7f);
+ if (ret < 0) {
+ status = HAL_STATUS_FAILED;
+ goto done;
+ }
+
+ status = HAL_STATUS_SUCCESS;
+
+done:
+ ipc_send_rsp(hal_ipc, HAL_SERVICE_ID_AVRCP, HAL_OP_AVRCP_SET_VOLUME,
+ status);
+}
+
+static const struct ipc_handler cmd_handlers[] = {
+ /* HAL_OP_AVRCP_GET_PLAY_STATUS */
+ { handle_get_play_status, false,
+ sizeof(struct hal_cmd_avrcp_get_play_status) },
+ /* HAL_OP_AVRCP_LIST_PLAYER_ATTRS */
+ { handle_list_player_attrs, true,
+ sizeof(struct hal_cmd_avrcp_list_player_attrs) },
+ /* HAL_OP_AVRCP_LIST_PLAYER_VALUES */
+ { handle_list_player_values, true,
+ sizeof(struct hal_cmd_avrcp_list_player_values) },
+ /* HAL_OP_AVRCP_GET_PLAYER_ATTRS */
+ { handle_get_player_attrs, true,
+ sizeof(struct hal_cmd_avrcp_get_player_attrs) },
+ /* HAL_OP_AVRCP_GET_PLAYER_ATTRS_TEXT */
+ { handle_get_player_attrs_text, true,
+ sizeof(struct hal_cmd_avrcp_get_player_attrs_text) },
+ /* HAL_OP_AVRCP_GET_PLAYER_VALUES_TEXT */
+ { handle_get_player_values_text, true,
+ sizeof(struct hal_cmd_avrcp_get_player_values_text) },
+ /* HAL_OP_AVRCP_GET_ELEMENT_ATTRS_TEXT */
+ { handle_get_element_attrs_text, true,
+ sizeof(struct hal_cmd_avrcp_get_element_attrs_text) },
+ /* HAL_OP_AVRCP_SET_PLAYER_ATTRS_VALUE */
+ { handle_set_player_attrs_value, true,
+ sizeof(struct hal_cmd_avrcp_set_player_attrs_value) },
+ /* HAL_OP_AVRCP_REGISTER_NOTIFICATION */
+ { handle_register_notification, true,
+ sizeof(struct hal_cmd_avrcp_register_notification) },
+ /* HAL_OP_AVRCP_SET_VOLUME */
+ { handle_set_volume, false, sizeof(struct hal_cmd_avrcp_set_volume) },
+};
+
+static sdp_record_t *avrcp_tg_record(void)
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, l2cap, avctp, avrtg;
+ sdp_profile_desc_t profile[1];
+ sdp_list_t *aproto_control, *proto_control[2];
+ sdp_record_t *record;
+ sdp_data_t *psm, *version, *features;
+ uint16_t lp = L2CAP_PSM_AVCTP;
+ uint16_t avrcp_ver = 0x0105, avctp_ver = 0x0104;
+ uint16_t feat = (AVRCP_FEATURE_CATEGORY_1 |
+ AVRCP_FEATURE_CATEGORY_2 |
+ AVRCP_FEATURE_CATEGORY_3 |
+ AVRCP_FEATURE_CATEGORY_4);
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(NULL, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ /* Service Class ID List */
+ sdp_uuid16_create(&avrtg, AV_REMOTE_TARGET_SVCLASS_ID);
+ svclass_id = sdp_list_append(NULL, &avrtg);
+ sdp_set_service_classes(record, svclass_id);
+
+ /* Protocol Descriptor List */
+ sdp_uuid16_create(&l2cap, L2CAP_UUID);
+ proto_control[0] = sdp_list_append(NULL, &l2cap);
+ psm = sdp_data_alloc(SDP_UINT16, &lp);
+ proto_control[0] = sdp_list_append(proto_control[0], psm);
+ apseq = sdp_list_append(NULL, proto_control[0]);
+
+ sdp_uuid16_create(&avctp, AVCTP_UUID);
+ proto_control[1] = sdp_list_append(NULL, &avctp);
+ version = sdp_data_alloc(SDP_UINT16, &avctp_ver);
+ proto_control[1] = sdp_list_append(proto_control[1], version);
+ apseq = sdp_list_append(apseq, proto_control[1]);
+
+ aproto_control = sdp_list_append(NULL, apseq);
+ sdp_set_access_protos(record, aproto_control);
+
+ /* Bluetooth Profile Descriptor List */
+ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+ profile[0].version = avrcp_ver;
+ pfseq = sdp_list_append(NULL, &profile[0]);
+ sdp_set_profile_descs(record, pfseq);
+
+ features = sdp_data_alloc(SDP_UINT16, &feat);
+ sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+ sdp_set_info_attr(record, "AVRCP TG", NULL, NULL);
+
+ sdp_data_free(psm);
+ sdp_data_free(version);
+ sdp_list_free(proto_control[0], NULL);
+ sdp_list_free(proto_control[1], NULL);
+ sdp_list_free(apseq, NULL);
+ sdp_list_free(aproto_control, NULL);
+ sdp_list_free(pfseq, NULL);
+ sdp_list_free(root, NULL);
+ sdp_list_free(svclass_id, NULL);
+
+ return record;
+}
+
+static sdp_record_t *avrcp_ct_record(void)
+{
+ sdp_list_t *svclass_id, *pfseq, *apseq, *root;
+ uuid_t root_uuid, l2cap, avctp, avrct, avrctr;
+ sdp_profile_desc_t profile[1];
+ sdp_list_t *aproto, *proto[2];
+ sdp_record_t *record;
+ sdp_data_t *psm, *version, *features;
+ uint16_t lp = AVCTP_CONTROL_PSM;
+ uint16_t avrcp_ver = 0x0105, avctp_ver = 0x0104;
+ uint16_t feat = ( AVRCP_FEATURE_CATEGORY_1 |
+ AVRCP_FEATURE_CATEGORY_2 |
+ AVRCP_FEATURE_CATEGORY_3 |
+ AVRCP_FEATURE_CATEGORY_4);
+
+ record = sdp_record_alloc();
+ if (!record)
+ return NULL;
+
+ sdp_uuid16_create(&root_uuid, PUBLIC_BROWSE_GROUP);
+ root = sdp_list_append(NULL, &root_uuid);
+ sdp_set_browse_groups(record, root);
+
+ /* Service Class ID List */
+ sdp_uuid16_create(&avrct, AV_REMOTE_SVCLASS_ID);
+ svclass_id = sdp_list_append(NULL, &avrct);
+ sdp_uuid16_create(&avrctr, AV_REMOTE_CONTROLLER_SVCLASS_ID);
+ svclass_id = sdp_list_append(svclass_id, &avrctr);
+ sdp_set_service_classes(record, svclass_id);
+
+ /* Protocol Descriptor List */
+ sdp_uuid16_create(&l2cap, L2CAP_UUID);
+ proto[0] = sdp_list_append(NULL, &l2cap);
+ psm = sdp_data_alloc(SDP_UINT16, &lp);
+ proto[0] = sdp_list_append(proto[0], psm);
+ apseq = sdp_list_append(NULL, proto[0]);
+
+ sdp_uuid16_create(&avctp, AVCTP_UUID);
+ proto[1] = sdp_list_append(NULL, &avctp);
+ version = sdp_data_alloc(SDP_UINT16, &avctp_ver);
+ proto[1] = sdp_list_append(proto[1], version);
+ apseq = sdp_list_append(apseq, proto[1]);
+
+ aproto = sdp_list_append(NULL, apseq);
+ sdp_set_access_protos(record, aproto);
+
+ /* Bluetooth Profile Descriptor List */
+ sdp_uuid16_create(&profile[0].uuid, AV_REMOTE_PROFILE_ID);
+ profile[0].version = avrcp_ver;
+ pfseq = sdp_list_append(NULL, &profile[0]);
+ sdp_set_profile_descs(record, pfseq);
+
+ features = sdp_data_alloc(SDP_UINT16, &feat);
+ sdp_attr_add(record, SDP_ATTR_SUPPORTED_FEATURES, features);
+
+ sdp_set_info_attr(record, "AVRCP CT", NULL, NULL);
+
+ free(psm);
+ free(version);
+ sdp_list_free(proto[0], NULL);
+ sdp_list_free(proto[1], NULL);
+ sdp_list_free(apseq, NULL);
+ sdp_list_free(pfseq, NULL);
+ sdp_list_free(aproto, NULL);
+ sdp_list_free(root, NULL);
+ sdp_list_free(svclass_id, NULL);
+
+ return record;
+}
+
+static void queue_free(void *data, void *user_data)
+{
+ g_free(data);
+}
+
+static void avrcp_device_free(void *data)
+{
+ struct avrcp_device *dev = data;
+
+ if (dev->queue) {
+ g_queue_foreach(dev->queue, queue_free, NULL);
+ g_queue_free(dev->queue);
+ }
+
+ if (dev->session)
+ avrcp_shutdown(dev->session);
+
+ if (dev->io) {
+ g_io_channel_shutdown(dev->io, FALSE, NULL);
+ g_io_channel_unref(dev->io);
+ }
+
+ g_free(dev);
+}
+
+static void avrcp_device_remove(struct avrcp_device *dev)
+{
+ devices = g_slist_remove(devices, dev);
+ avrcp_device_free(dev);
+}
+
+static struct avrcp_device *avrcp_device_new(const bdaddr_t *dst)
+{
+ struct avrcp_device *dev;
+
+ dev = g_new0(struct avrcp_device, 1);
+ bacpy(&dev->dst, dst);
+ devices = g_slist_prepend(devices, dev);
+
+ return dev;
+}
+
+static int device_cmp(gconstpointer s, gconstpointer user_data)
+{
+ const struct avrcp_device *dev = s;
+ const bdaddr_t *dst = user_data;
+
+ return bacmp(&dev->dst, dst);
+}
+
+static struct avrcp_device *avrcp_device_find(const bdaddr_t *dst)
+{
+ GSList *l;
+
+ l = g_slist_find_custom(devices, dst, device_cmp);
+ if (!l)
+ return NULL;
+
+ return l->data;
+}
+
+static void disconnect_cb(void *data)
+{
+ struct avrcp_device *dev = data;
+
+ DBG("");
+
+ dev->session = NULL;
+
+ avrcp_device_remove(dev);
+}
+
+static bool handle_fast_forward(struct avrcp *session, bool pressed,
+ void *user_data)
+{
+ struct hal_ev_avrcp_passthrough_cmd ev;
+
+ DBG("pressed %s", pressed ? "true" : "false");
+
+ ev.id = AVC_FAST_FORWARD;
+ ev.state = pressed;
+
+ ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_EV_AVRCP_PASSTHROUGH_CMD, sizeof(ev), &ev);
+
+ return true;
+}
+
+static bool handle_rewind(struct avrcp *session, bool pressed,
+ void *user_data)
+{
+ struct hal_ev_avrcp_passthrough_cmd ev;
+
+ DBG("pressed %s", pressed ? "true" : "false");
+
+ ev.id = AVC_REWIND;
+ ev.state = pressed;
+
+ ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_EV_AVRCP_PASSTHROUGH_CMD, sizeof(ev), &ev);
+
+ return true;
+}
+
+static const struct avrcp_passthrough_handler passthrough_handlers[] = {
+ { AVC_FAST_FORWARD, handle_fast_forward },
+ { AVC_REWIND, handle_rewind },
+ { },
+};
+
+static int handle_get_capabilities_cmd(struct avrcp *session,
+ uint8_t transaction, void *user_data)
+{
+ uint8_t events[] = { AVRCP_EVENT_STATUS_CHANGED,
+ AVRCP_EVENT_TRACK_CHANGED,
+ AVRCP_EVENT_PLAYBACK_POS_CHANGED };
+
+ DBG("");
+
+ /*
+ * Android do not provide this info via HAL so the list most
+ * be hardcoded according to what RegisterNotification can
+ * actually handle
+ */
+ avrcp_get_capabilities_rsp(session, transaction, sizeof(events),
+ events);
+
+ return 0;
+}
+
+static void push_request(struct avrcp_device *dev, uint8_t pdu_id,
+ uint8_t event_id, uint8_t transaction)
+{
+ struct avrcp_request *req;
+
+ req = g_new0(struct avrcp_request, 1);
+ req->dev = dev;
+ req->pdu_id = pdu_id;
+ req->event_id = event_id;
+ req->transaction = transaction;
+
+ g_queue_push_tail(dev->queue, req);
+}
+
+static int handle_get_play_status_cmd(struct avrcp *session,
+ uint8_t transaction, void *user_data)
+{
+ struct avrcp_device *dev = user_data;
+
+ DBG("");
+
+ ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_EV_AVRCP_GET_PLAY_STATUS, 0, NULL);
+
+ push_request(dev, AVRCP_GET_PLAY_STATUS, 0, transaction);
+
+ return 0;
+}
+
+static int handle_get_element_attrs_cmd(struct avrcp *session,
+ uint8_t transaction, uint64_t uid,
+ uint8_t number, uint32_t *attrs,
+ void *user_data)
+{
+ struct avrcp_device *dev = user_data;
+ uint8_t buf[IPC_MTU];
+ struct hal_ev_avrcp_get_element_attrs *ev = (void *) buf;
+ int i;
+
+ DBG("");
+
+ ev->number = number;
+ /* Set everything in case of empty list */
+ if (ev->number == 0) {
+ for (i = 0; i < HAL_AVRCP_MEDIA_ATTR_DURATION; i++) {
+ /* Skip 0x00 as the attributes start with 0x01 */
+ ev->attrs[i] = i + 1;
+ }
+ ev->number = i;
+ goto done;
+ }
+
+ for (i = 0; i < number; i++)
+ ev->attrs[i] = attrs[i];
+
+done:
+ ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_EV_AVRCP_GET_ELEMENT_ATTRS,
+ sizeof(*ev) + ev->number, ev);
+
+ push_request(dev, AVRCP_GET_ELEMENT_ATTRIBUTES, 0, transaction);
+
+ return 0;
+
+}
+
+static int handle_register_notification_cmd(struct avrcp *session,
+ uint8_t transaction,
+ uint8_t event,
+ uint32_t interval,
+ void *user_data)
+{
+ struct avrcp_device *dev = user_data;
+ struct hal_ev_avrcp_register_notification ev;
+
+ DBG("");
+
+ /* TODO: Add any missing events supported by Android */
+ switch (event) {
+ case AVRCP_EVENT_STATUS_CHANGED:
+ case AVRCP_EVENT_TRACK_CHANGED:
+ case AVRCP_EVENT_PLAYBACK_POS_CHANGED:
+ break;
+ default:
+ return -EINVAL;
+ }
+
+ ev.event = event;
+ ev.param = interval;
+
+ ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_EV_AVRCP_REGISTER_NOTIFICATION,
+ sizeof(ev), &ev);
+
+ push_request(dev, AVRCP_REGISTER_NOTIFICATION, event, transaction);
+
+ return 0;
+}
+
+static const struct avrcp_control_ind control_ind = {
+ .get_capabilities = handle_get_capabilities_cmd,
+ .get_play_status = handle_get_play_status_cmd,
+ .get_element_attributes = handle_get_element_attrs_cmd,
+ .register_notification = handle_register_notification_cmd,
+};
+
+static bool handle_register_notification_rsp(struct avrcp *session, int err,
+ uint8_t code, uint8_t event,
+ void *params,
+ void *user_data)
+{
+ struct avrcp_device *dev = user_data;
+ struct hal_ev_avrcp_volume_changed ev;
+ uint8_t *volume = params;
+
+ if (err < 0) {
+ error("AVRCP: %s", strerror(-err));
+ return false;
+ }
+
+ if (code != AVC_CTYPE_INTERIM && code != AVC_CTYPE_CHANGED)
+ return false;
+
+ if (event != AVRCP_EVENT_VOLUME_CHANGED)
+ return false;
+
+ ev.type = code;
+ ev.volume = volume[0];
+
+ ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_EV_AVRCP_VOLUME_CHANGED,
+ sizeof(ev), &ev);
+
+ if (code == AVC_CTYPE_INTERIM)
+ return true;
+
+ avrcp_register_notification(dev->session, event, 0);
+ return false;
+}
+
+static void handle_get_capabilities_rsp(struct avrcp *session, int err,
+ uint8_t number, uint8_t *events,
+ void *user_data)
+{
+ struct avrcp_device *dev = user_data;
+ int i;
+
+ if (err < 0) {
+ error("AVRCP: %s", strerror(-err));
+ return;
+ }
+
+ for (i = 0; i < number; i++) {
+ if (events[i] != AVRCP_EVENT_VOLUME_CHANGED)
+ continue;
+
+ avrcp_register_notification(dev->session, events[i], 0);
+ break;
+ }
+
+ return;
+}
+
+static void handle_set_volume_rsp(struct avrcp *session, int err,
+ uint8_t value, void *user_data)
+{
+ struct hal_ev_avrcp_volume_changed ev;
+
+ if (err < 0) {
+ ev.volume = 0;
+ ev.type = AVC_CTYPE_REJECTED;
+ goto done;
+ }
+
+ ev.volume = value;
+ ev.type = AVC_CTYPE_ACCEPTED;
+
+done:
+ ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_EV_AVRCP_VOLUME_CHANGED,
+ sizeof(ev), &ev);
+}
+
+static const struct avrcp_control_cfm control_cfm = {
+ .get_capabilities = handle_get_capabilities_rsp,
+ .register_notification = handle_register_notification_rsp,
+ .set_volume = handle_set_volume_rsp,
+};
+
+static int avrcp_device_add_session(struct avrcp_device *dev, int fd,
+ uint16_t imtu, uint16_t omtu)
+{
+ struct hal_ev_avrcp_remote_features ev;
+ char address[18];
+
+ dev->session = avrcp_new(fd, imtu, omtu, dev->version);
+ if (!dev->session)
+ return -EINVAL;
+
+ avrcp_set_destroy_cb(dev->session, disconnect_cb, dev);
+ avrcp_set_passthrough_handlers(dev->session, passthrough_handlers,
+ dev);
+ avrcp_register_player(dev->session, &control_ind, &control_cfm, dev);
+
+ dev->queue = g_queue_new();
+
+ ba2str(&dev->dst, address);
+
+ /* FIXME: get the real name of the device */
+ avrcp_init_uinput(dev->session, "bluetooth", address);
+
+ bdaddr2android(&dev->dst, ev.bdaddr);
+ ev.features = HAL_AVRCP_FEATURE_NONE;
+
+ DBG("version 0x%02x", dev->version);
+
+ if (dev->version < 0x0103)
+ goto done;
+
+ ev.features |= HAL_AVRCP_FEATURE_METADATA;
+
+ if (dev->version < 0x0104)
+ goto done;
+
+ ev.features |= HAL_AVRCP_FEATURE_ABSOLUTE_VOLUME;
+
+ avrcp_get_capabilities(dev->session, CAP_EVENTS_SUPPORTED);
+
+done:
+ ipc_send_notif(hal_ipc, HAL_SERVICE_ID_AVRCP,
+ HAL_EV_AVRCP_REMOTE_FEATURES,
+ sizeof(ev), &ev);
+
+ return 0;
+}
+
+static void connect_cb(GIOChannel *chan, GError *err, gpointer user_data)
+{
+ struct avrcp_device *dev = user_data;
+ uint16_t imtu, omtu;
+ char address[18];
+ GError *gerr = NULL;
+ int fd;
+
+ if (err) {
+ error("%s", err->message);
+ return;
+ }
+
+ bt_io_get(chan, &gerr,
+ BT_IO_OPT_DEST, address,
+ BT_IO_OPT_IMTU, &imtu,
+ BT_IO_OPT_OMTU, &omtu,
+ BT_IO_OPT_INVALID);
+ if (gerr) {
+ error("%s", gerr->message);
+ g_error_free(gerr);
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ return;
+ }
+
+ fd = g_io_channel_unix_get_fd(chan);
+ if (avrcp_device_add_session(dev, fd, imtu, omtu) < 0) {
+ avrcp_device_free(dev);
+ return;
+ }
+
+ g_io_channel_set_close_on_unref(chan, FALSE);
+
+ if (dev->io) {
+ g_io_channel_unref(dev->io);
+ dev->io = NULL;
+ }
+
+ DBG("%s connected", address);
+}
+
+static bool avrcp_device_connect(struct avrcp_device *dev, BtIOConnect cb)
+{
+ GError *err = NULL;
+
+ dev->io = bt_io_connect(cb, dev, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
+ BT_IO_OPT_DEST_BDADDR, &dev->dst,
+ BT_IO_OPT_PSM, L2CAP_PSM_AVCTP,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ return false;
+ }
+
+ return true;
+}
+
+static void search_cb(sdp_list_t *recs, int err, gpointer data)
+{
+ struct avrcp_device *dev = data;
+ sdp_list_t *list;
+
+ DBG("");
+
+ if (!g_slist_find(devices, dev))
+ return;
+
+ if (err < 0) {
+ error("Unable to get AV_REMOTE_SVCLASS_ID SDP record: %s",
+ strerror(-err));
+ goto fail;
+ }
+
+ if (!recs || !recs->data) {
+ error("No AVRCP records found");
+ goto fail;
+ }
+
+ for (list = recs; list; list = list->next) {
+ sdp_record_t *rec = list->data;
+ sdp_list_t *l;
+ sdp_profile_desc_t *desc;
+ int features;
+
+ if (sdp_get_profile_descs(rec, &l) < 0)
+ continue;
+
+ desc = l->data;
+ dev->version = desc->version;
+
+ if (sdp_get_int_attr(rec, SDP_ATTR_SUPPORTED_FEATURES,
+ &features) == 0)
+ dev->features = features;
+
+ sdp_list_free(l, free);
+ break;
+ }
+
+ if (dev->io) {
+ GError *gerr = NULL;
+ if (!bt_io_accept(dev->io, connect_cb, dev, NULL, &gerr)) {
+ error("bt_io_accept: %s", gerr->message);
+ g_error_free(gerr);
+ goto fail;
+ }
+ return;
+ }
+
+ if (!avrcp_device_connect(dev, connect_cb)) {
+ error("Unable to connect to AVRCP");
+ goto fail;
+ }
+
+ return;
+
+fail:
+ avrcp_device_remove(dev);
+}
+
+static int avrcp_device_search(struct avrcp_device *dev)
+{
+ uuid_t uuid;
+
+ sdp_uuid16_create(&uuid, AV_REMOTE_SVCLASS_ID);
+
+ return bt_search_service(&adapter_addr, &dev->dst, &uuid, search_cb,
+ dev, NULL, 0);
+}
+
+static void confirm_cb(GIOChannel *chan, gpointer data)
+{
+ struct avrcp_device *dev;
+ char address[18];
+ bdaddr_t dst;
+ GError *err = NULL;
+
+ bt_io_get(chan, &err,
+ BT_IO_OPT_DEST_BDADDR, &dst,
+ BT_IO_OPT_DEST, address,
+ BT_IO_OPT_INVALID);
+ if (err) {
+ error("%s", err->message);
+ g_error_free(err);
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ return;
+ }
+
+ DBG("incoming connect from %s", address);
+
+ dev = avrcp_device_find(&dst);
+ if (dev && dev->session) {
+ error("AVRCP: Refusing unexpected connect");
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ return;
+ }
+
+ dev = avrcp_device_new(&dst);
+ if (avrcp_device_search(dev) < 0) {
+ error("AVRCP: Failed to search SDP details");
+ avrcp_device_free(dev);
+ g_io_channel_shutdown(chan, TRUE, NULL);
+ }
+
+ dev->io = g_io_channel_ref(chan);
+}
+
+bool bt_avrcp_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode)
+{
+ GError *err = NULL;
+ sdp_record_t *rec;
+
+ DBG("");
+
+ bacpy(&adapter_addr, addr);
+
+ server = bt_io_listen(NULL, confirm_cb, NULL, NULL, &err,
+ BT_IO_OPT_SOURCE_BDADDR, &adapter_addr,
+ BT_IO_OPT_PSM, L2CAP_PSM_AVCTP,
+ BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
+ BT_IO_OPT_INVALID);
+ if (!server) {
+ error("Failed to listen on AVDTP channel: %s", err->message);
+ g_error_free(err);
+ return false;
+ }
+
+ rec = avrcp_tg_record();
+ if (!rec) {
+ error("Failed to allocate AVRCP TG record");
+ goto fail;
+ }
+
+ if (bt_adapter_add_record(rec, 0) < 0) {
+ error("Failed to register AVRCP TG record");
+ sdp_record_free(rec);
+ goto fail;
+ }
+ record_tg_id = rec->handle;
+
+ rec = avrcp_ct_record();
+ if (!rec) {
+ error("Failed to allocate AVRCP CT record");
+ bt_adapter_remove_record(record_tg_id);
+ goto fail;
+ }
+
+ if (bt_adapter_add_record(rec, 0) < 0) {
+ error("Failed to register AVRCP CT record");
+ bt_adapter_remove_record(record_tg_id);
+ sdp_record_free(rec);
+ goto fail;
+ }
+ record_ct_id = rec->handle;
+
+ hal_ipc = ipc;
+
+ ipc_register(hal_ipc, HAL_SERVICE_ID_AVRCP, cmd_handlers,
+ G_N_ELEMENTS(cmd_handlers));
+
+ return true;
+fail:
+ g_io_channel_shutdown(server, TRUE, NULL);
+ g_io_channel_unref(server);
+ server = NULL;
+
+ return false;
+}
+
+void bt_avrcp_unregister(void)
+{
+ DBG("");
+
+ g_slist_free_full(devices, avrcp_device_free);
+ devices = NULL;
+
+ ipc_unregister(hal_ipc, HAL_SERVICE_ID_AVRCP);
+ hal_ipc = NULL;
+
+ bt_adapter_remove_record(record_tg_id);
+ record_tg_id = 0;
+
+ bt_adapter_remove_record(record_ct_id);
+ record_ct_id = 0;
+
+ if (server) {
+ g_io_channel_shutdown(server, TRUE, NULL);
+ g_io_channel_unref(server);
+ server = NULL;
+ }
+}
+
+void bt_avrcp_connect(const bdaddr_t *dst)
+{
+ struct avrcp_device *dev;
+ char addr[18];
+
+ DBG("");
+
+ if (avrcp_device_find(dst))
+ return;
+
+ dev = avrcp_device_new(dst);
+ if (avrcp_device_search(dev) < 0) {
+ error("AVRCP: Failed to search SDP details");
+ avrcp_device_free(dev);
+ }
+
+ ba2str(&dev->dst, addr);
+ DBG("connecting to %s", addr);
+}
+
+void bt_avrcp_disconnect(const bdaddr_t *dst)
+{
+ struct avrcp_device *dev;
+
+ DBG("");
+
+ dev = avrcp_device_find(dst);
+ if (!dev)
+ return;
+
+ if (dev->session) {
+ avrcp_shutdown(dev->session);
+ return;
+ }
+
+ avrcp_device_remove(dev);
+}
diff --git a/unit/avrcp.h b/unit/avrcp.h
new file mode 100644
index 0000000..474413b
--- /dev/null
+++ b/unit/avrcp.h
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+/*
+ *
+ * BlueZ - Bluetooth protocol stack for Linux
+ *
+ * Copyright (C) 2013-2014 Intel Corporation. All rights reserved.
+ *
+ *
+ */
+
+bool bt_avrcp_register(struct ipc *ipc, const bdaddr_t *addr, uint8_t mode);
+void bt_avrcp_unregister(void);
+
+void bt_avrcp_connect(const bdaddr_t *dst);
+void bt_avrcp_disconnect(const bdaddr_t *dst);