mirror of
https://git.kernel.org/pub/scm/bluetooth/bluez.git
synced 2024-11-29 07:04:19 +08:00
c1d6620752
g_new0 always returns valid pointer and there is no need to check that.
1114 lines
24 KiB
C
1114 lines
24 KiB
C
/*
|
|
*
|
|
* BlueZ - Bluetooth protocol stack for Linux
|
|
*
|
|
* Copyright (C) 2006-2010 Nokia Corporation
|
|
* Copyright (C) 2004-2010 Marcel Holtmann <marcel@holtmann.org>
|
|
* Copyright (C) 2011 Texas Instruments, Inc.
|
|
*
|
|
*
|
|
* This program is free software; you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
|
|
*
|
|
*/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
#include <stdlib.h>
|
|
#include <stdint.h>
|
|
#include <errno.h>
|
|
#include <unistd.h>
|
|
#include <assert.h>
|
|
#include <signal.h>
|
|
#include <sys/types.h>
|
|
#include <sys/stat.h>
|
|
#include <fcntl.h>
|
|
#include <netinet/in.h>
|
|
|
|
#include <bluetooth/bluetooth.h>
|
|
#include <bluetooth/sdp.h>
|
|
#include <bluetooth/uuid.h>
|
|
|
|
#include <glib.h>
|
|
|
|
#include "adapter.h"
|
|
#include "../src/device.h"
|
|
|
|
#include "log.h"
|
|
#include "error.h"
|
|
#include "uinput.h"
|
|
#include "btio.h"
|
|
#include "manager.h"
|
|
#include "device.h"
|
|
#include "avctp.h"
|
|
#include "avrcp.h"
|
|
|
|
#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));
|
|
#define AVCTP_HEADER_LENGTH 3
|
|
|
|
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));
|
|
#define AVCTP_HEADER_LENGTH 3
|
|
|
|
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_state_callback {
|
|
avctp_state_cb cb;
|
|
void *user_data;
|
|
unsigned int id;
|
|
};
|
|
|
|
struct avctp_server {
|
|
bdaddr_t src;
|
|
GIOChannel *io;
|
|
GSList *sessions;
|
|
};
|
|
|
|
struct avctp_rsp_handler {
|
|
uint8_t id;
|
|
avctp_rsp_cb func;
|
|
void *user_data;
|
|
};
|
|
|
|
struct avctp {
|
|
struct avctp_server *server;
|
|
bdaddr_t dst;
|
|
|
|
avctp_state_t state;
|
|
|
|
int uinput;
|
|
|
|
GIOChannel *io;
|
|
guint io_id;
|
|
|
|
uint16_t mtu;
|
|
|
|
uint8_t key_quirks[256];
|
|
GSList *handlers;
|
|
};
|
|
|
|
struct avctp_pdu_handler {
|
|
uint8_t opcode;
|
|
avctp_pdu_cb cb;
|
|
void *user_data;
|
|
unsigned int id;
|
|
};
|
|
|
|
static struct {
|
|
const char *name;
|
|
uint8_t avc;
|
|
uint16_t uinput;
|
|
} key_map[] = {
|
|
{ "PLAY", PLAY_OP, KEY_PLAYCD },
|
|
{ "STOP", STAVC_OP_OP, KEY_STOPCD },
|
|
{ "PAUSE", PAUSE_OP, KEY_PAUSECD },
|
|
{ "FORWARD", FORWARD_OP, KEY_NEXTSONG },
|
|
{ "BACKWARD", BACKWARD_OP, KEY_PREVIOUSSONG },
|
|
{ "REWIND", REWIND_OP, KEY_REWIND },
|
|
{ "FAST FORWARD", FAST_FORWARD_OP, KEY_FASTFORWARD },
|
|
{ NULL }
|
|
};
|
|
|
|
static GSList *callbacks = NULL;
|
|
static GSList *servers = NULL;
|
|
static GSList *handlers = NULL;
|
|
static uint8_t id = 0;
|
|
|
|
static void auth_cb(DBusError *derr, void *user_data);
|
|
|
|
static int send_event(int fd, uint16_t type, uint16_t code, int32_t value)
|
|
{
|
|
struct uinput_event event;
|
|
|
|
memset(&event, 0, sizeof(event));
|
|
event.type = type;
|
|
event.code = code;
|
|
event.value = value;
|
|
|
|
return write(fd, &event, sizeof(event));
|
|
}
|
|
|
|
static void send_key(int fd, uint16_t key, int pressed)
|
|
{
|
|
if (fd < 0)
|
|
return;
|
|
|
|
send_event(fd, EV_KEY, key, pressed);
|
|
send_event(fd, EV_SYN, SYN_REPORT, 0);
|
|
}
|
|
|
|
static size_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)
|
|
{
|
|
const char *status;
|
|
int pressed, i;
|
|
|
|
if (*code != AVC_CTYPE_CONTROL || *subunit != AVC_SUBUNIT_PANEL) {
|
|
*code = AVC_CTYPE_REJECTED;
|
|
return 0;
|
|
}
|
|
|
|
if (operand_count == 0)
|
|
goto done;
|
|
|
|
if (operands[0] & 0x80) {
|
|
status = "released";
|
|
pressed = 0;
|
|
} else {
|
|
status = "pressed";
|
|
pressed = 1;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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 0;
|
|
}
|
|
|
|
done:
|
|
*code = AVC_CTYPE_ACCEPTED;
|
|
return operand_count;
|
|
}
|
|
|
|
static size_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 size_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 avctp_disconnected(struct avctp *session)
|
|
{
|
|
struct avctp_server *server;
|
|
|
|
if (!session)
|
|
return;
|
|
|
|
if (session->io) {
|
|
g_io_channel_shutdown(session->io, TRUE, NULL);
|
|
g_io_channel_unref(session->io);
|
|
session->io = NULL;
|
|
}
|
|
|
|
if (session->io_id) {
|
|
g_source_remove(session->io_id);
|
|
session->io_id = 0;
|
|
|
|
if (session->state == AVCTP_STATE_CONNECTING) {
|
|
struct audio_device *dev;
|
|
|
|
dev = manager_get_device(&session->server->src,
|
|
&session->dst, FALSE);
|
|
audio_device_cancel_authorization(dev, auth_cb,
|
|
session);
|
|
}
|
|
}
|
|
|
|
if (session->uinput >= 0) {
|
|
char address[18];
|
|
|
|
ba2str(&session->dst, address);
|
|
DBG("AVCTP: closing uinput for %s", address);
|
|
|
|
ioctl(session->uinput, UI_DEV_DESTROY);
|
|
close(session->uinput);
|
|
session->uinput = -1;
|
|
}
|
|
|
|
server = session->server;
|
|
server->sessions = g_slist_remove(server->sessions, session);
|
|
g_slist_free_full(session->handlers, g_free);
|
|
g_free(session);
|
|
}
|
|
|
|
static void avctp_set_state(struct avctp *session, avctp_state_t new_state)
|
|
{
|
|
GSList *l;
|
|
struct audio_device *dev;
|
|
avctp_state_t old_state = session->state;
|
|
|
|
dev = manager_get_device(&session->server->src, &session->dst, FALSE);
|
|
if (dev == NULL) {
|
|
error("avdtp_set_state(): no matching audio device");
|
|
return;
|
|
}
|
|
|
|
session->state = new_state;
|
|
|
|
for (l = callbacks; l != NULL; l = l->next) {
|
|
struct avctp_state_callback *cb = l->data;
|
|
cb->cb(dev, old_state, new_state, cb->user_data);
|
|
}
|
|
|
|
switch (new_state) {
|
|
case AVCTP_STATE_DISCONNECTED:
|
|
DBG("AVCTP Disconnected");
|
|
|
|
avctp_disconnected(session);
|
|
|
|
if (old_state != AVCTP_STATE_CONNECTED)
|
|
break;
|
|
|
|
if (!audio_device_is_active(dev, NULL))
|
|
audio_device_set_authorized(dev, FALSE);
|
|
|
|
break;
|
|
case AVCTP_STATE_CONNECTING:
|
|
DBG("AVCTP Connecting");
|
|
break;
|
|
case AVCTP_STATE_CONNECTED:
|
|
DBG("AVCTP Connected");
|
|
break;
|
|
default:
|
|
error("Invalid AVCTP state %d", new_state);
|
|
return;
|
|
}
|
|
}
|
|
|
|
static void handle_response(struct avctp *session, struct avctp_header *avctp,
|
|
struct avc_header *avc, uint8_t *operands,
|
|
size_t operand_count)
|
|
{
|
|
GSList *l;
|
|
|
|
for (l = session->handlers; l; l = l->next) {
|
|
struct avctp_rsp_handler *handler = l->data;
|
|
|
|
if (handler->id != avctp->transaction)
|
|
continue;
|
|
|
|
if (handler->func && handler->func(session, avc->code,
|
|
avc->subunit_type,
|
|
operands, operand_count,
|
|
handler->user_data))
|
|
return;
|
|
|
|
session->handlers = g_slist_remove(session->handlers, handler);
|
|
g_free(handler);
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
static gboolean session_cb(GIOChannel *chan, GIOCondition cond,
|
|
gpointer data)
|
|
{
|
|
struct avctp *session = data;
|
|
uint8_t buf[1024], *operands, code, subunit;
|
|
struct avctp_header *avctp;
|
|
struct avc_header *avc;
|
|
int ret, packet_size, operand_count, sock;
|
|
struct avctp_pdu_handler *handler;
|
|
|
|
if (cond & (G_IO_ERR | G_IO_HUP | G_IO_NVAL))
|
|
goto failed;
|
|
|
|
sock = g_io_channel_unix_get_fd(session->io);
|
|
|
|
ret = read(sock, buf, sizeof(buf));
|
|
if (ret <= 0)
|
|
goto failed;
|
|
|
|
DBG("Got %d bytes of data for AVCTP session %p", ret, session);
|
|
|
|
if ((unsigned int) ret < sizeof(struct avctp_header)) {
|
|
error("Too small AVCTP packet");
|
|
goto failed;
|
|
}
|
|
|
|
avctp = (struct avctp_header *) buf;
|
|
|
|
DBG("AVCTP transaction %u, packet type %u, C/R %u, IPID %u, "
|
|
"PID 0x%04X",
|
|
avctp->transaction, avctp->packet_type,
|
|
avctp->cr, avctp->ipid, ntohs(avctp->pid));
|
|
|
|
ret -= sizeof(struct avctp_header);
|
|
if ((unsigned int) ret < sizeof(struct avc_header)) {
|
|
error("Too small AVCTP packet");
|
|
goto failed;
|
|
}
|
|
|
|
avc = (struct avc_header *) (buf + sizeof(struct avctp_header));
|
|
|
|
ret -= sizeof(struct avc_header);
|
|
|
|
operands = buf + sizeof(struct avctp_header) + sizeof(struct avc_header);
|
|
operand_count = ret;
|
|
|
|
DBG("AV/C %s 0x%01X, subunit_type 0x%02X, subunit_id 0x%01X, "
|
|
"opcode 0x%02X, %d operands",
|
|
avctp->cr ? "response" : "command",
|
|
avc->code, avc->subunit_type, avc->subunit_id,
|
|
avc->opcode, operand_count);
|
|
|
|
if (avctp->cr == AVCTP_RESPONSE) {
|
|
handle_response(session, 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;
|
|
avc->code = AVC_CTYPE_REJECTED;
|
|
goto done;
|
|
}
|
|
|
|
handler = find_handler(handlers, avc->opcode);
|
|
if (!handler) {
|
|
DBG("handler not found for 0x%02x", avc->opcode);
|
|
packet_size += avrcp_handle_vendor_reject(&code, operands);
|
|
avc->code = code;
|
|
goto done;
|
|
}
|
|
|
|
code = avc->code;
|
|
subunit = avc->subunit_type;
|
|
|
|
packet_size += handler->cb(session, avctp->transaction, &code,
|
|
&subunit, operands, operand_count,
|
|
handler->user_data);
|
|
|
|
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_set_state(session, AVCTP_STATE_DISCONNECTED);
|
|
return FALSE;
|
|
}
|
|
|
|
static int uinput_create(char *name)
|
|
{
|
|
struct uinput_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;
|
|
}
|
|
|
|
static void init_uinput(struct avctp *session)
|
|
{
|
|
struct audio_device *dev;
|
|
char address[18], name[248 + 1];
|
|
|
|
dev = manager_get_device(&session->server->src, &session->dst, FALSE);
|
|
|
|
device_get_name(dev->btd_dev, name, sizeof(name));
|
|
if (g_str_equal(name, "Nokia CK-20W")) {
|
|
session->key_quirks[FORWARD_OP] |= QUIRK_NO_RELEASE;
|
|
session->key_quirks[BACKWARD_OP] |= QUIRK_NO_RELEASE;
|
|
session->key_quirks[PLAY_OP] |= QUIRK_NO_RELEASE;
|
|
session->key_quirks[PAUSE_OP] |= QUIRK_NO_RELEASE;
|
|
}
|
|
|
|
ba2str(&session->dst, address);
|
|
|
|
session->uinput = uinput_create(address);
|
|
if (session->uinput < 0)
|
|
error("AVRCP: failed to init uinput for %s", address);
|
|
else
|
|
DBG("AVRCP: uinput initialized for %s", address);
|
|
}
|
|
|
|
static void avctp_connect_cb(GIOChannel *chan, GError *err, gpointer data)
|
|
{
|
|
struct avctp *session = data;
|
|
char address[18];
|
|
uint16_t imtu;
|
|
GError *gerr = NULL;
|
|
|
|
if (err) {
|
|
avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
|
|
error("%s", err->message);
|
|
return;
|
|
}
|
|
|
|
bt_io_get(chan, BT_IO_L2CAP, &gerr,
|
|
BT_IO_OPT_DEST, &address,
|
|
BT_IO_OPT_IMTU, &imtu,
|
|
BT_IO_OPT_INVALID);
|
|
if (gerr) {
|
|
avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
|
|
error("%s", gerr->message);
|
|
g_error_free(gerr);
|
|
return;
|
|
}
|
|
|
|
DBG("AVCTP: connected to %s", address);
|
|
|
|
if (!session->io)
|
|
session->io = g_io_channel_ref(chan);
|
|
|
|
init_uinput(session);
|
|
|
|
avctp_set_state(session, AVCTP_STATE_CONNECTED);
|
|
session->mtu = imtu;
|
|
session->io_id = g_io_add_watch(chan,
|
|
G_IO_IN | G_IO_ERR | G_IO_HUP | G_IO_NVAL,
|
|
(GIOFunc) session_cb, session);
|
|
}
|
|
|
|
static void auth_cb(DBusError *derr, void *user_data)
|
|
{
|
|
struct avctp *session = user_data;
|
|
GError *err = NULL;
|
|
|
|
if (session->io_id) {
|
|
g_source_remove(session->io_id);
|
|
session->io_id = 0;
|
|
}
|
|
|
|
if (derr && dbus_error_is_set(derr)) {
|
|
error("Access denied: %s", derr->message);
|
|
avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
|
|
return;
|
|
}
|
|
|
|
if (!bt_io_accept(session->io, avctp_connect_cb, session,
|
|
NULL, &err)) {
|
|
error("bt_io_accept: %s", err->message);
|
|
g_error_free(err);
|
|
avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
|
|
}
|
|
}
|
|
|
|
static struct avctp_server *find_server(GSList *list, const bdaddr_t *src)
|
|
{
|
|
for (; list; list = list->next) {
|
|
struct avctp_server *server = list->data;
|
|
|
|
if (bacmp(&server->src, src) == 0)
|
|
return server;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct avctp *find_session(GSList *list, const bdaddr_t *dst)
|
|
{
|
|
for (; list != NULL; list = g_slist_next(list)) {
|
|
struct avctp *s = list->data;
|
|
|
|
if (bacmp(dst, &s->dst))
|
|
continue;
|
|
|
|
return s;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static struct avctp *avctp_get_internal(const bdaddr_t *src,
|
|
const bdaddr_t *dst)
|
|
{
|
|
struct avctp_server *server;
|
|
struct avctp *session;
|
|
|
|
assert(src != NULL);
|
|
assert(dst != NULL);
|
|
|
|
server = find_server(servers, src);
|
|
if (server == NULL)
|
|
return NULL;
|
|
|
|
session = find_session(server->sessions, dst);
|
|
if (session)
|
|
return session;
|
|
|
|
session = g_new0(struct avctp, 1);
|
|
|
|
session->server = server;
|
|
bacpy(&session->dst, dst);
|
|
session->state = AVCTP_STATE_DISCONNECTED;
|
|
|
|
server->sessions = g_slist_append(server->sessions, session);
|
|
|
|
return session;
|
|
}
|
|
|
|
static void avctp_confirm_cb(GIOChannel *chan, gpointer data)
|
|
{
|
|
struct avctp *session;
|
|
struct audio_device *dev;
|
|
char address[18];
|
|
bdaddr_t src, dst;
|
|
GError *err = NULL;
|
|
|
|
bt_io_get(chan, BT_IO_L2CAP, &err,
|
|
BT_IO_OPT_SOURCE_BDADDR, &src,
|
|
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("AVCTP: incoming connect from %s", address);
|
|
|
|
session = avctp_get_internal(&src, &dst);
|
|
if (!session)
|
|
goto drop;
|
|
|
|
dev = manager_get_device(&src, &dst, FALSE);
|
|
if (!dev) {
|
|
dev = manager_get_device(&src, &dst, TRUE);
|
|
if (!dev) {
|
|
error("Unable to get audio device object for %s",
|
|
address);
|
|
goto drop;
|
|
}
|
|
}
|
|
|
|
if (dev->control == NULL) {
|
|
btd_device_add_uuid(dev->btd_dev, AVRCP_REMOTE_UUID);
|
|
if (dev->control == NULL)
|
|
goto drop;
|
|
}
|
|
|
|
if (session->io) {
|
|
error("Refusing unexpected connect from %s", address);
|
|
goto drop;
|
|
}
|
|
|
|
avctp_set_state(session, AVCTP_STATE_CONNECTING);
|
|
session->io = g_io_channel_ref(chan);
|
|
|
|
if (audio_device_request_authorization(dev, AVRCP_TARGET_UUID,
|
|
auth_cb, session) < 0)
|
|
goto drop;
|
|
|
|
session->io_id = g_io_add_watch(chan, G_IO_ERR | G_IO_HUP | G_IO_NVAL,
|
|
session_cb, session);
|
|
return;
|
|
|
|
drop:
|
|
if (!session || !session->io)
|
|
g_io_channel_shutdown(chan, TRUE, NULL);
|
|
if (session)
|
|
avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
|
|
}
|
|
|
|
static GIOChannel *avctp_server_socket(const bdaddr_t *src, gboolean master)
|
|
{
|
|
GError *err = NULL;
|
|
GIOChannel *io;
|
|
|
|
io = bt_io_listen(BT_IO_L2CAP, NULL, avctp_confirm_cb, NULL,
|
|
NULL, &err,
|
|
BT_IO_OPT_SOURCE_BDADDR, src,
|
|
BT_IO_OPT_PSM, AVCTP_PSM,
|
|
BT_IO_OPT_SEC_LEVEL, BT_IO_SEC_MEDIUM,
|
|
BT_IO_OPT_MASTER, master,
|
|
BT_IO_OPT_INVALID);
|
|
if (!io) {
|
|
error("%s", err->message);
|
|
g_error_free(err);
|
|
}
|
|
|
|
return io;
|
|
}
|
|
|
|
static unsigned int passthrough_id = 0;
|
|
static unsigned int unit_id = 0;
|
|
static unsigned int subunit_id = 0;
|
|
|
|
int avctp_register(const bdaddr_t *src, gboolean master)
|
|
{
|
|
struct avctp_server *server;
|
|
|
|
server = g_new0(struct avctp_server, 1);
|
|
|
|
server->io = avctp_server_socket(src, master);
|
|
if (!server->io) {
|
|
g_free(server);
|
|
return -1;
|
|
}
|
|
|
|
bacpy(&server->src, src);
|
|
|
|
servers = g_slist_append(servers, server);
|
|
|
|
if (!passthrough_id)
|
|
passthrough_id = avctp_register_pdu_handler(AVC_OP_PASSTHROUGH,
|
|
handle_panel_passthrough, NULL);
|
|
|
|
if (!unit_id)
|
|
unit_id = avctp_register_pdu_handler(AVC_OP_UNITINFO, handle_unit_info,
|
|
NULL);
|
|
|
|
if (!subunit_id)
|
|
subunit_id = avctp_register_pdu_handler(AVC_OP_SUBUNITINFO,
|
|
handle_subunit_info, NULL);
|
|
|
|
return 0;
|
|
}
|
|
|
|
void avctp_unregister(const bdaddr_t *src)
|
|
{
|
|
struct avctp_server *server;
|
|
|
|
server = find_server(servers, src);
|
|
if (!server)
|
|
return;
|
|
|
|
while (server->sessions)
|
|
avctp_disconnected(server->sessions->data);
|
|
|
|
servers = g_slist_remove(servers, server);
|
|
|
|
g_io_channel_shutdown(server->io, TRUE, NULL);
|
|
g_io_channel_unref(server->io);
|
|
g_free(server);
|
|
|
|
if (servers)
|
|
return;
|
|
|
|
if (passthrough_id) {
|
|
avctp_unregister_pdu_handler(passthrough_id);
|
|
passthrough_id = 0;
|
|
}
|
|
|
|
if (unit_id) {
|
|
avctp_unregister_pdu_handler(unit_id);
|
|
passthrough_id = 0;
|
|
}
|
|
|
|
if (subunit_id) {
|
|
avctp_unregister_pdu_handler(subunit_id);
|
|
subunit_id = 0;
|
|
}
|
|
}
|
|
|
|
int avctp_send_passthrough(struct avctp *session, uint8_t op)
|
|
{
|
|
unsigned char buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + 2];
|
|
struct avctp_header *avctp = (void *) buf;
|
|
struct avc_header *avc = (void *) &buf[AVCTP_HEADER_LENGTH];
|
|
uint8_t *operands = &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
|
|
int sk;
|
|
|
|
if (session->state != AVCTP_STATE_CONNECTED)
|
|
return -ENOTCONN;
|
|
|
|
memset(buf, 0, sizeof(buf));
|
|
|
|
avctp->transaction = id++;
|
|
avctp->packet_type = AVCTP_PACKET_SINGLE;
|
|
avctp->cr = AVCTP_COMMAND;
|
|
avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
|
|
|
|
avc->code = AVC_CTYPE_CONTROL;
|
|
avc->subunit_type = AVC_SUBUNIT_PANEL;
|
|
avc->opcode = AVC_OP_PASSTHROUGH;
|
|
|
|
operands[0] = op & 0x7f;
|
|
operands[1] = 0;
|
|
|
|
sk = g_io_channel_unix_get_fd(session->io);
|
|
|
|
if (write(sk, buf, sizeof(buf)) < 0)
|
|
return -errno;
|
|
|
|
/* Button release */
|
|
avctp->transaction = id++;
|
|
operands[0] |= 0x80;
|
|
|
|
if (write(sk, buf, sizeof(buf)) < 0)
|
|
return -errno;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int avctp_send(struct avctp *session, uint8_t transaction, uint8_t cr,
|
|
uint8_t code, uint8_t subunit, uint8_t opcode,
|
|
uint8_t *operands, size_t operand_count)
|
|
{
|
|
uint8_t *buf;
|
|
struct avctp_header *avctp;
|
|
struct avc_header *avc;
|
|
uint8_t *pdu;
|
|
int sk, err = 0;
|
|
uint16_t size;
|
|
|
|
if (session->state != AVCTP_STATE_CONNECTED)
|
|
return -ENOTCONN;
|
|
|
|
sk = g_io_channel_unix_get_fd(session->io);
|
|
size = AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH + operand_count;
|
|
buf = g_malloc0(size);
|
|
|
|
avctp = (void *) buf;
|
|
avc = (void *) &buf[AVCTP_HEADER_LENGTH];
|
|
pdu = (void *) &buf[AVCTP_HEADER_LENGTH + AVC_HEADER_LENGTH];
|
|
|
|
avctp->transaction = transaction;
|
|
avctp->packet_type = AVCTP_PACKET_SINGLE;
|
|
avctp->cr = cr;
|
|
avctp->pid = htons(AV_REMOTE_SVCLASS_ID);
|
|
|
|
avc->code = code;
|
|
avc->subunit_type = subunit;
|
|
avc->opcode = opcode;
|
|
|
|
memcpy(pdu, operands, operand_count);
|
|
|
|
if (write(sk, buf, size) < 0)
|
|
err = -errno;
|
|
|
|
g_free(buf);
|
|
return err;
|
|
}
|
|
|
|
int avctp_send_vendordep(struct avctp *session, uint8_t transaction,
|
|
uint8_t code, uint8_t subunit,
|
|
uint8_t *operands, size_t operand_count)
|
|
{
|
|
return avctp_send(session, transaction, AVCTP_RESPONSE, code, subunit,
|
|
AVC_OP_VENDORDEP, operands, operand_count);
|
|
}
|
|
|
|
int avctp_send_vendordep_req(struct avctp *session, uint8_t code,
|
|
uint8_t subunit, uint8_t *operands,
|
|
size_t operand_count,
|
|
avctp_rsp_cb func, void *user_data)
|
|
{
|
|
struct avctp_rsp_handler *handler;
|
|
int err;
|
|
|
|
err = avctp_send(session, id, AVCTP_COMMAND, code, subunit,
|
|
AVC_OP_VENDORDEP, operands, operand_count);
|
|
if (err < 0)
|
|
return err;
|
|
|
|
handler = g_new0(struct avctp_rsp_handler, 1);
|
|
handler->id = id;
|
|
handler->func = func;
|
|
handler->user_data = user_data;
|
|
|
|
session->handlers = g_slist_prepend(session->handlers, handler);
|
|
|
|
id++;
|
|
|
|
return 0;
|
|
}
|
|
|
|
unsigned int avctp_add_state_cb(avctp_state_cb cb, void *user_data)
|
|
{
|
|
struct avctp_state_callback *state_cb;
|
|
static unsigned int id = 0;
|
|
|
|
state_cb = g_new(struct avctp_state_callback, 1);
|
|
state_cb->cb = cb;
|
|
state_cb->user_data = user_data;
|
|
state_cb->id = ++id;
|
|
|
|
callbacks = g_slist_append(callbacks, state_cb);
|
|
|
|
return state_cb->id;
|
|
}
|
|
|
|
gboolean avctp_remove_state_cb(unsigned int id)
|
|
{
|
|
GSList *l;
|
|
|
|
for (l = callbacks; l != NULL; l = l->next) {
|
|
struct avctp_state_callback *cb = l->data;
|
|
if (cb && cb->id == id) {
|
|
callbacks = g_slist_remove(callbacks, cb);
|
|
g_free(cb);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
unsigned int avctp_register_pdu_handler(uint8_t opcode, avctp_pdu_cb cb,
|
|
void *user_data)
|
|
{
|
|
struct avctp_pdu_handler *handler;
|
|
static unsigned int id = 0;
|
|
|
|
handler = find_handler(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;
|
|
|
|
handlers = g_slist_append(handlers, handler);
|
|
|
|
return handler->id;
|
|
}
|
|
|
|
gboolean avctp_unregister_pdu_handler(unsigned int id)
|
|
{
|
|
GSList *l;
|
|
|
|
for (l = handlers; l != NULL; l = l->next) {
|
|
struct avctp_pdu_handler *handler = l->data;
|
|
|
|
if (handler->id == id) {
|
|
handlers = g_slist_remove(handlers, handler);
|
|
g_free(handler);
|
|
return TRUE;
|
|
}
|
|
}
|
|
|
|
return FALSE;
|
|
}
|
|
|
|
struct avctp *avctp_connect(const bdaddr_t *src, const bdaddr_t *dst)
|
|
{
|
|
struct avctp *session;
|
|
GError *err = NULL;
|
|
GIOChannel *io;
|
|
|
|
session = avctp_get_internal(src, dst);
|
|
if (!session)
|
|
return NULL;
|
|
|
|
if (session->state > AVCTP_STATE_DISCONNECTED)
|
|
return session;
|
|
|
|
avctp_set_state(session, AVCTP_STATE_CONNECTING);
|
|
|
|
io = bt_io_connect(BT_IO_L2CAP, avctp_connect_cb, session, NULL, &err,
|
|
BT_IO_OPT_SOURCE_BDADDR, &session->server->src,
|
|
BT_IO_OPT_DEST_BDADDR, &session->dst,
|
|
BT_IO_OPT_PSM, AVCTP_PSM,
|
|
BT_IO_OPT_INVALID);
|
|
if (err) {
|
|
avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
|
|
error("%s", err->message);
|
|
g_error_free(err);
|
|
return NULL;
|
|
}
|
|
|
|
session->io = io;
|
|
|
|
return session;
|
|
}
|
|
|
|
void avctp_disconnect(struct avctp *session)
|
|
{
|
|
if (!session->io)
|
|
return;
|
|
|
|
avctp_set_state(session, AVCTP_STATE_DISCONNECTED);
|
|
}
|
|
|
|
struct avctp *avctp_get(const bdaddr_t *src, const bdaddr_t *dst)
|
|
{
|
|
return avctp_get_internal(src, dst);
|
|
}
|