blob: 5f921f4a422e529f52fd9dbd2c20ed6cb4a9110f [file] [log] [blame]
/** @file
* @brief Audio Video Control Transport Protocol
*/
/*
* Copyright (c) 2015-2016 Intel Corporation
* Copyright (C) 2024 Xiaomi Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <string.h>
#include <strings.h>
#include <errno.h>
#include <zephyr/sys/atomic.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/sys/util.h>
#include <zephyr/bluetooth/hci.h>
#include <zephyr/bluetooth/bluetooth.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/bluetooth/classic/sdp.h>
#include "avctp_internal.h"
#include "host/hci_core.h"
#include "host/conn_internal.h"
#include "l2cap_br_internal.h"
#define LOG_LEVEL CONFIG_BT_AVCTP_LOG_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(bt_avctp);
/** @brief AVCTP Header Sizes for different packet types */
#define BT_AVCTP_HDR_SIZE_SINGLE (sizeof(struct bt_avctp_header_single))
#define BT_AVCTP_HDR_SIZE_START (sizeof(struct bt_avctp_header_start))
#define BT_AVCTP_HDR_SIZE_CONTINUE_END (sizeof(struct bt_avctp_header_continue_end))
#define AVCTP_TX_RETRY_DELAY (100)
#define AVCTP_CHAN(_ch) CONTAINER_OF(_ch, struct bt_avctp, br_chan.chan)
/* L2CAP Server list */
static sys_slist_t avctp_l2cap_server = SYS_SLIST_STATIC_INIT(&avctp_l2cap_server);
static struct k_sem avctp_lock;
/* tx list for packets */
static sys_slist_t avctp_tx_list = SYS_SLIST_STATIC_INIT(&avctp_tx_list);
static struct k_work_delayable avctp_tx_work;
struct avctp_buf_user_data {
struct bt_avctp *session;
uint16_t sent_len;
uint16_t pid;
uint8_t tid;
uint8_t num_packet;
bt_avctp_cr_t cr;
uint8_t ipid;
} __packed;
static void avctp_tx_raise(int msec)
{
if (sys_slist_is_empty(&avctp_tx_list)) {
LOG_DBG("TX list is empty, no work to submit");
return;
}
LOG_DBG("kick TX");
k_work_schedule(&avctp_tx_work, K_MSEC(msec));
}
static void bt_avctp_clear_tx(struct bt_avctp *session)
{
struct net_buf *buf, *next;
struct avctp_buf_user_data *user_data;
const sys_snode_t *head = sys_slist_peek_head(&avctp_tx_list);
SYS_SLIST_FOR_EACH_CONTAINER_SAFE(&avctp_tx_list, buf, next, node) {
user_data = net_buf_user_data(buf);
if (user_data->session != session) {
continue;
}
bool is_head = (&buf->node == head);
sys_slist_find_and_remove(&avctp_tx_list, &buf->node);
net_buf_unref(buf);
if (is_head) {
avctp_tx_raise(0);
head = sys_slist_peek_head(&avctp_tx_list);
}
}
}
static void avctp_l2cap_connected(struct bt_l2cap_chan *chan)
{
struct bt_avctp *session;
if (!chan) {
LOG_ERR("Invalid AVCTP chan");
return;
}
session = AVCTP_CHAN(chan);
LOG_DBG("chan %p session %p", chan, session);
if (session->ops && session->ops->connected) {
session->ops->connected(session);
}
}
static void avctp_l2cap_disconnected(struct bt_l2cap_chan *chan)
{
struct bt_avctp *session;
if (!chan) {
LOG_ERR("Invalid AVCTP chan");
return;
}
session = AVCTP_CHAN(chan);
LOG_DBG("chan %p session %p", chan, session);
session->br_chan.chan.conn = NULL;
if (session->reassembly_buf != NULL) {
net_buf_unref(session->reassembly_buf);
session->reassembly_buf = NULL;
}
k_sem_take(&avctp_lock, K_FOREVER);
bt_avctp_clear_tx(session);
k_sem_give(&avctp_lock);
if (session->ops && session->ops->disconnected) {
session->ops->disconnected(session);
}
}
static void avctp_l2cap_encrypt_changed(struct bt_l2cap_chan *chan, uint8_t status)
{
LOG_DBG("");
}
static void avctp_tx_cb(struct bt_conn *conn, void *user_data, int err)
{
if (err < 0) {
LOG_WRN("AVCTP TX completed with err=%d", err);
}
avctp_tx_raise(0);
}
static void avctp_tx_remove(struct net_buf *buf)
{
k_sem_take(&avctp_lock, K_FOREVER);
sys_slist_find_and_remove(&avctp_tx_list, &buf->node);
k_sem_give(&avctp_lock);
}
static int send_single_avctp(struct bt_avctp *avctp, uint8_t tid, bt_avctp_cr_t cr, uint8_t ipid,
uint16_t pid, struct net_buf *buf)
{
struct bt_avctp_header_single *avctp_hdr;
int err;
if ((avctp == NULL) || (buf == NULL)) {
return -EINVAL;
}
if (net_buf_headroom(buf) < BT_AVCTP_HDR_SIZE_SINGLE) {
LOG_ERR("Not enough headroom in buffer for AVCTP SINGLE header");
return -ENOMEM;
}
avctp_hdr = net_buf_push(buf, BT_AVCTP_HDR_SIZE_SINGLE);
memset(avctp_hdr, 0, BT_AVCTP_HDR_SIZE_SINGLE);
avctp_hdr->pid = sys_cpu_to_be16(pid);
BT_AVCTP_HDR_SET(&avctp_hdr->common, ipid, cr, BT_AVCTP_PKT_TYPE_SINGLE, tid);
avctp_tx_remove(buf);
err = bt_l2cap_br_chan_send_cb(&avctp->br_chan.chan, buf, avctp_tx_cb, NULL);
if (err < 0) {
LOG_ERR("Failed to send l2cap chan (err: %d)", err);
net_buf_unref(buf);
}
return err;
}
static int send_fragmented_avctp(struct bt_avctp *avctp, struct avctp_buf_user_data *user_data,
bt_avctp_pkt_type_t pkt_type, uint8_t *data, uint16_t data_len)
{
struct net_buf *buf;
struct bt_avctp_header_start *avctp_hdr_start;
struct bt_avctp_header_continue_end *avctp_hdr_continue_end;
int err;
if ((avctp == NULL) || (data == NULL)) {
return -EINVAL;
}
switch (pkt_type) {
case BT_AVCTP_PKT_TYPE_START:
buf = bt_l2cap_create_pdu(avctp->tx_pool, BT_AVCTP_HDR_SIZE_START);
if (buf == NULL) {
LOG_ERR("No buf for AVCTP START");
return -ENOBUFS;
}
avctp_hdr_start = net_buf_push(buf, BT_AVCTP_HDR_SIZE_START);
memset(avctp_hdr_start, 0, BT_AVCTP_HDR_SIZE_START);
avctp_hdr_start->pid = sys_cpu_to_be16(avctp->pid);
avctp_hdr_start->number_packet = user_data->num_packet;
BT_AVCTP_HDR_SET(&avctp_hdr_start->common, user_data->ipid, user_data->cr, pkt_type,
user_data->tid);
break;
case BT_AVCTP_PKT_TYPE_CONTINUE:
case BT_AVCTP_PKT_TYPE_END:
buf = bt_l2cap_create_pdu(avctp->tx_pool, BT_AVCTP_HDR_SIZE_CONTINUE_END);
if (buf == NULL) {
LOG_ERR("No buf for AVCTP CONTINUE/END");
return -ENOBUFS;
}
avctp_hdr_continue_end = net_buf_push(buf, BT_AVCTP_HDR_SIZE_CONTINUE_END);
memset(avctp_hdr_continue_end, 0, BT_AVCTP_HDR_SIZE_CONTINUE_END);
BT_AVCTP_HDR_SET(&avctp_hdr_continue_end->common, user_data->ipid, user_data->cr,
pkt_type, user_data->tid);
break;
default:
LOG_ERR("Invalid packet type: %d", pkt_type);
return -EINVAL;
}
if (data_len > 0U) {
if (net_buf_tailroom(buf) < data_len) {
LOG_WRN("Not enough tailroom for AVCTP payload (len: %d)", data_len);
net_buf_unref(buf);
return -ENOMEM;
}
net_buf_add_mem(buf, data, data_len);
}
err = bt_l2cap_br_chan_send_cb(&avctp->br_chan.chan, buf, avctp_tx_cb, NULL);
if (err < 0) {
LOG_ERR("Failed to send l2cap chan (err: %d)", err);
net_buf_unref(buf);
}
return err;
}
static inline uint8_t get_avctp_packet_num(uint16_t total_bytes, uint16_t max_payload_size)
{
uint16_t first_payload = max_payload_size - BT_AVCTP_HDR_SIZE_START;
uint16_t fragment_payload = max_payload_size - BT_AVCTP_HDR_SIZE_CONTINUE_END;
uint16_t remaining;
uint8_t additional_packet_num;
if (total_bytes <= max_payload_size - BT_AVCTP_HDR_SIZE_SINGLE) {
return 1;
}
remaining = total_bytes - first_payload;
additional_packet_num = (remaining + fragment_payload - 1) / fragment_payload;
return 1 + additional_packet_num;
}
static inline bool avctp_is_retryable_err(int err)
{
return (err == -ENOBUFS);
}
static void avctp_tx_processor(struct k_work *item)
{
struct bt_avctp *session;
struct avctp_buf_user_data *user_data;
const sys_snode_t *node;
struct net_buf *buf;
int err = 0;
uint16_t chunk_size;
uint8_t pkt_type;
uint16_t max_payload_size;
k_sem_take(&avctp_lock, K_FOREVER);
node = sys_slist_peek_head(&avctp_tx_list);
if (node == NULL) {
k_sem_give(&avctp_lock);
LOG_DBG("No pending tx");
return;
}
k_sem_give(&avctp_lock);
buf = CONTAINER_OF(node, struct net_buf, node);
__ASSERT_NO_MSG(buf->user_data_size >= sizeof(*user_data));
user_data = net_buf_user_data(buf);
session = user_data->session;
__ASSERT_NO_MSG(session != NULL);
max_payload_size = MIN(session->br_chan.tx.mtu, session->max_tx_payload_size);
/* The SINGLE packet buf can be sent directly */
if (user_data->num_packet == 1) {
err = send_single_avctp(session, user_data->tid, user_data->cr, user_data->ipid,
user_data->pid, buf);
if (err < 0) {
LOG_ERR("Failed to send SINGLE packet, len=%u", buf->len);
avctp_tx_raise(0);
}
return;
}
/* Multi-packet fragmented send */
if (user_data->sent_len == 0U) {
pkt_type = BT_AVCTP_PKT_TYPE_START;
chunk_size = max_payload_size - BT_AVCTP_HDR_SIZE_START;
} else {
bool is_last = (buf->len <= (max_payload_size - BT_AVCTP_HDR_SIZE_CONTINUE_END));
pkt_type = is_last ? BT_AVCTP_PKT_TYPE_END : BT_AVCTP_PKT_TYPE_CONTINUE;
chunk_size = MIN(buf->len, max_payload_size - BT_AVCTP_HDR_SIZE_CONTINUE_END);
}
err = send_fragmented_avctp(session, user_data, pkt_type, buf->data, chunk_size);
if (err < 0) {
if (avctp_is_retryable_err(err)) {
LOG_WRN("Retry %p on %p", buf, session);
avctp_tx_raise(AVCTP_TX_RETRY_DELAY);
return;
}
LOG_ERR("Failed to send fragment at offset %u", user_data->sent_len);
goto failed;
}
if (net_buf_tailroom(buf) < chunk_size) {
LOG_WRN("Not enough tailroom for AVCTP payload (len: %d)", chunk_size);
goto failed;
}
net_buf_pull_mem(buf, chunk_size);
user_data->sent_len += chunk_size;
if (buf->len == 0) {
avctp_tx_remove(buf);
net_buf_unref(buf);
}
return;
failed:
avctp_tx_remove(buf);
net_buf_unref(buf);
avctp_tx_raise(0);
}
static int bt_avctp_send_common(struct bt_avctp *session, struct net_buf *buf,
bt_avctp_cr_t cr, uint8_t tid, uint16_t pid,
uint8_t num_packet, uint8_t ipid)
{
struct avctp_buf_user_data *user_data;
__ASSERT_NO_MSG(buf->user_data_size >= sizeof(*user_data));
user_data = net_buf_user_data(buf);
user_data->session = session;
user_data->sent_len = 0;
user_data->cr = cr;
user_data->pid = pid;
user_data->ipid = ipid;
user_data->num_packet = num_packet;
user_data->tid = tid;
k_sem_take(&avctp_lock, K_FOREVER);
sys_slist_append(&avctp_tx_list, &buf->node);
k_sem_give(&avctp_lock);
avctp_tx_raise(0);
return 0;
}
static int dispatch_avctp_packet(struct bt_avctp *session, struct net_buf *buf,
struct bt_avctp_header_common *hdr_common, uint16_t pid)
{
struct net_buf *rsp;
int err;
bt_avctp_cr_t cr = BT_AVCTP_HDR_GET_CR(hdr_common);
uint8_t tid = BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr_common);
LOG_DBG("AVCTP msg received, cr:0x%X, tid:0x%X, pid: 0x%04X", cr, tid, pid);
if (pid == session->pid) {
return session->ops->recv(session, buf, cr, tid);
}
LOG_ERR("unsupported AVCTP PID received: 0x%04x", pid);
if (cr == BT_AVCTP_CMD) {
rsp = bt_avctp_create_pdu(NULL);
if (rsp == NULL) {
__ASSERT(0, "Failed to create AVCTP response PDU");
return -ENOMEM;
}
err = bt_avctp_send_common(session, rsp, BT_AVCTP_RESPONSE, tid, pid, 1,
BT_AVCTP_IPID_INVALID);
if (err < 0) {
net_buf_unref(rsp);
LOG_ERR("AVCTP send fail, err = %d", err);
bt_avctp_disconnect(session);
return err;
}
}
return 0; /* No need to report to the upper layer */
}
static int avctp_recv_fragmented(struct bt_avctp *avctp, struct net_buf *buf)
{
struct bt_avctp_header_common *hdr_common = (void *)buf->data;
struct bt_avctp_header_start *hdr_reassembly;
bt_avctp_pkt_type_t pkt_type = BT_AVCTP_HDR_GET_PACKET_TYPE(hdr_common);
uint8_t tid = BT_AVCTP_HDR_GET_TRANSACTION_LABLE(hdr_common);
bt_avctp_cr_t cr = BT_AVCTP_HDR_GET_CR(hdr_common);
if (pkt_type == BT_AVCTP_PKT_TYPE_START) {
if (buf->len < BT_AVCTP_HDR_SIZE_START) {
LOG_WRN("AVCTP HDR too short (len=%u, tid=%u, cr=%u)", buf->len, tid, cr);
goto failed;
}
if (avctp->reassembly_buf != NULL) {
LOG_WRN("Interleaving fragments not allowed (tid=%u, cr=%u)", tid, cr);
net_buf_unref(avctp->reassembly_buf);
avctp->reassembly_buf = NULL;
}
if (avctp->rx_pool == NULL) {
LOG_WRN("Missing RX pool for AVCTP reassembly (tid=%u, cr=%u)", tid, cr);
goto failed;
}
avctp->reassembly_buf = net_buf_alloc(avctp->rx_pool, K_FOREVER);
if (avctp->reassembly_buf == NULL) {
LOG_ERR("Failed to allocate reassembly buffer");
return -ENOMEM;
}
__ASSERT_NO_MSG(avctp->reassembly_buf->user_data_size >= sizeof(*hdr_reassembly));
hdr_reassembly = net_buf_user_data(avctp->reassembly_buf);
memcpy(hdr_reassembly, net_buf_pull_mem(buf, sizeof(*hdr_reassembly)),
sizeof(*hdr_reassembly));
if (net_buf_tailroom(avctp->reassembly_buf) < buf->len) {
LOG_WRN("Not enough tailroom for start fragment");
goto failed;
}
net_buf_add_mem(avctp->reassembly_buf, buf->data, buf->len);
if (hdr_reassembly->number_packet == 0) {
LOG_WRN("Invalid start number_packet (%u) — expected > 0 (tid=%u, cr=%u)",
hdr_reassembly->number_packet, tid, cr);
goto failed;
}
hdr_reassembly->number_packet--;
return 0;
}
if (avctp->reassembly_buf == NULL) {
LOG_WRN("Continuation/end received without Start (tid=%u, cr=%u, pkt_type=%u)",
tid, cr, (uint8_t)pkt_type);
goto failed;
}
__ASSERT_NO_MSG(avctp->reassembly_buf->user_data_size >= sizeof(*hdr_reassembly));
hdr_reassembly = net_buf_user_data(avctp->reassembly_buf);
if ((BT_AVCTP_HDR_GET_TRANSACTION_LABLE(&hdr_reassembly->common) == tid) &&
(BT_AVCTP_HDR_GET_CR(&hdr_reassembly->common) == cr)) {
if (buf->len < BT_AVCTP_HDR_SIZE_CONTINUE_END) {
LOG_WRN("invalid AVCTP continuation/end header received");
goto failed;
}
hdr_common = net_buf_pull_mem(buf, BT_AVCTP_HDR_SIZE_CONTINUE_END);
if (net_buf_tailroom(avctp->reassembly_buf) < buf->len) {
LOG_WRN("Not enough tailroom for continuation/end");
goto failed;
}
net_buf_add_mem(avctp->reassembly_buf, buf->data, buf->len);
if (hdr_reassembly->number_packet == 0) {
LOG_WRN("unexpected number_packet underflow (value=%u, tid=%u, cr=%u)",
hdr_reassembly->number_packet, tid, cr);
goto failed;
}
hdr_reassembly->number_packet--;
if (pkt_type == BT_AVCTP_PKT_TYPE_END) {
if (hdr_reassembly->number_packet > 0) {
LOG_WRN("Fragments remaining on END (remaining=%u, tid=%u, cr=%u)",
hdr_reassembly->number_packet, tid, cr);
goto failed;
}
dispatch_avctp_packet(avctp, avctp->reassembly_buf, hdr_common,
sys_be16_to_cpu(hdr_reassembly->pid));
net_buf_unref(avctp->reassembly_buf);
avctp->reassembly_buf = NULL;
return 0;
}
return 0;
}
LOG_WRN("No matching START packet found for tid=%u, cr=%u", tid, cr);
failed:
if (avctp->reassembly_buf != NULL) {
net_buf_unref(avctp->reassembly_buf);
avctp->reassembly_buf = NULL;
}
return 0; /* Need keep L2CAP up */
}
static int avctp_l2cap_recv(struct bt_l2cap_chan *chan, struct net_buf *buf)
{
struct bt_avctp *session = AVCTP_CHAN(chan);
struct bt_avctp_header_common *hdr = (void *)buf->data;
struct bt_avctp_header_single *hdr_single;
bt_avctp_pkt_type_t pkt_type;
int err;
if (buf->len < sizeof(*hdr)) {
LOG_ERR("invalid AVCTP header received");
return -EINVAL;
}
pkt_type = BT_AVCTP_HDR_GET_PACKET_TYPE(hdr);
if (pkt_type != BT_AVCTP_PKT_TYPE_SINGLE) {
err = avctp_recv_fragmented(session, buf);
if (err < 0) {
LOG_ERR("AVCTP recv fragmented packet fail, err = %d", err);
}
return err;
}
if (session->reassembly_buf != NULL) {
LOG_WRN("AVCTP: aborting in-progress reassembly due to SINGLE pkt");
net_buf_unref(session->reassembly_buf);
session->reassembly_buf = NULL;
}
if (buf->len < BT_AVCTP_HDR_SIZE_SINGLE) {
LOG_ERR("invalid AVCTP single header received");
return -EINVAL;
}
hdr_single = net_buf_pull_mem(buf, BT_AVCTP_HDR_SIZE_SINGLE);
return dispatch_avctp_packet(session, buf, &hdr_single->common,
sys_be16_to_cpu(hdr_single->pid));
}
static const struct bt_l2cap_chan_ops ops = {
.connected = avctp_l2cap_connected,
.disconnected = avctp_l2cap_disconnected,
.encrypt_change = avctp_l2cap_encrypt_changed,
.recv = avctp_l2cap_recv,
};
int bt_avctp_connect(struct bt_conn *conn, uint16_t psm, struct bt_avctp *session)
{
if (!session) {
return -EINVAL;
}
session->br_chan.chan.ops = &ops;
session->reassembly_buf = NULL;
return bt_l2cap_chan_connect(conn, &session->br_chan.chan, psm);
}
int bt_avctp_disconnect(struct bt_avctp *session)
{
if (!session) {
return -EINVAL;
}
LOG_DBG("session %p", session);
return bt_l2cap_chan_disconnect(&session->br_chan.chan);
}
struct net_buf *bt_avctp_create_pdu(struct net_buf_pool *pool)
{
struct net_buf *buf;
LOG_DBG("");
/* reserved avctp_header */
buf = bt_l2cap_create_pdu(pool, sizeof(struct bt_avctp_header_start));
if (buf == NULL) {
LOG_ERR("No buff available");
}
return buf;
}
int bt_avctp_send(struct bt_avctp *session, struct net_buf *buf, bt_avctp_cr_t cr, uint8_t tid)
{
uint8_t num_packet;
uint16_t max_payload_size;
if ((session == NULL) || (buf == NULL)) {
return -EINVAL;
}
if ((session->tx_pool != NULL) && (session->max_tx_payload_size == 0)) {
LOG_ERR("Invalid max_tx_payload_size %u", session->max_tx_payload_size);
return -EINVAL;
}
if (session->tx_pool == NULL) {
if (buf->len > (session->br_chan.tx.mtu - BT_AVCTP_HDR_SIZE_SINGLE)) {
LOG_ERR("Buffer size %u exceeds MTU %u", buf->len,
session->br_chan.tx.mtu - BT_AVCTP_HDR_SIZE_SINGLE);
return -EMSGSIZE;
}
num_packet = 1;
} else {
max_payload_size = MIN(session->br_chan.tx.mtu, session->max_tx_payload_size);
num_packet = get_avctp_packet_num(buf->len, max_payload_size);
}
return bt_avctp_send_common(session, buf, cr, tid, session->pid, num_packet,
BT_AVCTP_IPID_NONE);
}
static int avctp_l2cap_accept(struct bt_conn *conn, struct bt_l2cap_server *server,
struct bt_l2cap_chan **chan)
{
struct bt_avctp *session = NULL;
struct bt_avctp_server *avctp_server;
int err;
LOG_DBG("conn %p", conn);
avctp_server = CONTAINER_OF(server, struct bt_avctp_server, l2cap);
k_sem_take(&avctp_lock, K_FOREVER);
if (!sys_slist_find(&avctp_l2cap_server, &avctp_server->node, NULL)) {
LOG_WRN("Invalid l2cap server");
k_sem_give(&avctp_lock);
return -EINVAL;
}
k_sem_give(&avctp_lock);
/* Get the AVCTP session from upper layer */
err = avctp_server->accept(conn, &session);
if (err < 0) {
LOG_ERR("Incoming connection rejected");
return err;
}
session->br_chan.chan.ops = &ops;
session->reassembly_buf = NULL;
*chan = &session->br_chan.chan;
return 0;
}
int bt_avctp_server_register(struct bt_avctp_server *server)
{
int err;
LOG_DBG("");
if ((server == NULL) || (server->accept == NULL)) {
LOG_DBG("Invalid parameter");
return -EINVAL;
}
k_sem_take(&avctp_lock, K_FOREVER);
if (sys_slist_find(&avctp_l2cap_server, &server->node, NULL)) {
LOG_WRN("L2CAP server has been registered");
k_sem_give(&avctp_lock);
return -EEXIST;
}
server->l2cap.accept = avctp_l2cap_accept;
err = bt_l2cap_br_server_register(&server->l2cap);
if (err < 0) {
LOG_ERR("AVCTP L2CAP registration failed %d", err);
k_sem_give(&avctp_lock);
return err;
}
LOG_DBG("Register L2CAP server %p", server);
sys_slist_append(&avctp_l2cap_server, &server->node);
k_sem_give(&avctp_lock);
return err;
}
int bt_avctp_init(void)
{
LOG_DBG("Initializing AVCTP");
/* Locking semaphore initialized to 1 (unlocked) */
k_sem_init(&avctp_lock, 1, 1);
k_work_init_delayable(&avctp_tx_work, avctp_tx_processor);
return 0;
}