blob: c93afe084009c1033a07eaa1d96657fd0ab1e568 [file] [log] [blame]
/* l2cap.c - Bluetooth L2CAP Tester */
/*
* Copyright (c) 2016 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/bluetooth/bluetooth.h>
#include <errno.h>
#include <zephyr/bluetooth/l2cap.h>
#include <zephyr/bluetooth/att.h>
#include <zephyr/sys/byteorder.h>
#include <zephyr/logging/log.h>
#define LOG_MODULE_NAME bttester_l2cap
LOG_MODULE_REGISTER(LOG_MODULE_NAME, CONFIG_BTTESTER_LOG_LEVEL);
#include "btp/btp.h"
#define DATA_MTU_INITIAL 128
#define DATA_MTU 256
#define DATA_BUF_SIZE BT_L2CAP_SDU_BUF_SIZE(DATA_MTU)
#define CHANNELS 2
#define SERVERS 1
NET_BUF_POOL_FIXED_DEFINE(data_pool, CHANNELS, DATA_BUF_SIZE, 8, NULL);
static bool authorize_flag;
static uint8_t req_keysize;
static struct channel {
uint8_t chan_id; /* Internal number that identifies L2CAP channel. */
struct bt_l2cap_le_chan le;
bool in_use;
bool hold_credit;
struct net_buf *pending_credit;
} channels[CHANNELS];
/* TODO Extend to support multiple servers */
static struct bt_l2cap_server servers[SERVERS];
static struct net_buf *alloc_buf_cb(struct bt_l2cap_chan *chan)
{
return net_buf_alloc(&data_pool, K_FOREVER);
}
static uint8_t recv_cb_buf[DATA_BUF_SIZE + sizeof(struct btp_l2cap_data_received_ev)];
static int recv_cb(struct bt_l2cap_chan *l2cap_chan, struct net_buf *buf)
{
struct btp_l2cap_data_received_ev *ev = (void *) recv_cb_buf;
struct channel *chan = CONTAINER_OF(l2cap_chan, struct channel, le);
ev->chan_id = chan->chan_id;
ev->data_length = sys_cpu_to_le16(buf->len);
memcpy(ev->data, buf->data, buf->len);
tester_event(BTP_SERVICE_ID_L2CAP, BTP_L2CAP_EV_DATA_RECEIVED,
recv_cb_buf, sizeof(*ev) + buf->len);
if (chan->hold_credit && !chan->pending_credit) {
/* no need for extra ref, as when returning EINPROGRESS user
* becomes owner of the netbuf
*/
chan->pending_credit = buf;
return -EINPROGRESS;
}
return 0;
}
static void connected_cb(struct bt_l2cap_chan *l2cap_chan)
{
struct btp_l2cap_connected_ev ev;
struct channel *chan = CONTAINER_OF(l2cap_chan, struct channel, le);
struct bt_conn_info info;
ev.chan_id = chan->chan_id;
/* TODO: ev.psm */
if (!bt_conn_get_info(l2cap_chan->conn, &info)) {
switch (info.type) {
case BT_CONN_TYPE_LE:
ev.mtu_remote = sys_cpu_to_le16(chan->le.tx.mtu);
ev.mps_remote = sys_cpu_to_le16(chan->le.tx.mps);
ev.mtu_local = sys_cpu_to_le16(chan->le.rx.mtu);
ev.mps_local = sys_cpu_to_le16(chan->le.rx.mps);
bt_addr_le_copy(&ev.address, info.le.dst);
break;
case BT_CONN_TYPE_BR:
default:
/* TODO figure out how (if) want to handle BR/EDR */
return;
}
}
tester_event(BTP_SERVICE_ID_L2CAP, BTP_L2CAP_EV_CONNECTED, &ev, sizeof(ev));
}
static void disconnected_cb(struct bt_l2cap_chan *l2cap_chan)
{
struct btp_l2cap_disconnected_ev ev;
struct channel *chan = CONTAINER_OF(l2cap_chan, struct channel, le);
struct bt_conn_info info;
/* release netbuf on premature disconnection */
if (chan->pending_credit) {
net_buf_unref(chan->pending_credit);
chan->pending_credit = NULL;
}
(void)memset(&ev, 0, sizeof(struct btp_l2cap_disconnected_ev));
/* TODO: ev.result */
ev.chan_id = chan->chan_id;
/* TODO: ev.psm */
if (!bt_conn_get_info(l2cap_chan->conn, &info)) {
switch (info.type) {
case BT_CONN_TYPE_LE:
bt_addr_le_copy(&ev.address, info.le.dst);
break;
case BT_CONN_TYPE_BR:
default:
/* TODO figure out how (if) want to handle BR/EDR */
return;
}
}
chan->in_use = false;
tester_event(BTP_SERVICE_ID_L2CAP, BTP_L2CAP_EV_DISCONNECTED, &ev, sizeof(ev));
}
#if defined(CONFIG_BT_L2CAP_ECRED)
static void reconfigured_cb(struct bt_l2cap_chan *l2cap_chan)
{
struct btp_l2cap_reconfigured_ev ev;
struct channel *chan = CONTAINER_OF(l2cap_chan, struct channel, le);
(void)memset(&ev, 0, sizeof(ev));
ev.chan_id = chan->chan_id;
ev.mtu_remote = sys_cpu_to_le16(chan->le.tx.mtu);
ev.mps_remote = sys_cpu_to_le16(chan->le.tx.mps);
ev.mtu_local = sys_cpu_to_le16(chan->le.rx.mtu);
ev.mps_local = sys_cpu_to_le16(chan->le.rx.mps);
tester_event(BTP_SERVICE_ID_L2CAP, BTP_L2CAP_EV_RECONFIGURED, &ev, sizeof(ev));
}
#endif
static const struct bt_l2cap_chan_ops l2cap_ops = {
.alloc_buf = alloc_buf_cb,
.recv = recv_cb,
.connected = connected_cb,
.disconnected = disconnected_cb,
#if defined(CONFIG_BT_L2CAP_ECRED)
.reconfigured = reconfigured_cb,
#endif
};
static struct channel *get_free_channel()
{
uint8_t i;
struct channel *chan;
for (i = 0U; i < CHANNELS; i++) {
if (channels[i].in_use) {
continue;
}
chan = &channels[i];
(void)memset(chan, 0, sizeof(*chan));
chan->chan_id = i;
channels[i].in_use = true;
return chan;
}
return NULL;
}
static uint8_t connect(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_l2cap_connect_cmd *cp = cmd;
struct btp_l2cap_connect_rp *rp = rsp;
struct bt_conn *conn;
struct channel *chan = NULL;
struct bt_l2cap_chan *allocated_channels[5] = {};
uint16_t mtu = sys_le16_to_cpu(cp->mtu);
uint16_t psm = sys_le16_to_cpu(cp->psm);
uint8_t i = 0;
bool ecfc = cp->options & BTP_L2CAP_CONNECT_OPT_ECFC;
int err;
if (cp->num == 0 || cp->num > CHANNELS || mtu > DATA_MTU_INITIAL) {
return BTP_STATUS_FAILED;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
return BTP_STATUS_FAILED;
}
for (i = 0U; i < cp->num; i++) {
chan = get_free_channel();
if (!chan) {
goto fail;
}
chan->le.chan.ops = &l2cap_ops;
chan->le.rx.mtu = mtu;
rp->chan_id[i] = chan->chan_id;
allocated_channels[i] = &chan->le.chan;
chan->hold_credit = cp->options & BTP_L2CAP_CONNECT_OPT_HOLD_CREDIT;
}
if (cp->num == 1 && !ecfc) {
err = bt_l2cap_chan_connect(conn, &chan->le.chan, psm);
if (err < 0) {
goto fail;
}
} else if (ecfc) {
#if defined(CONFIG_BT_L2CAP_ECRED)
err = bt_l2cap_ecred_chan_connect(conn, allocated_channels,
psm);
if (err < 0) {
goto fail;
}
#else
goto fail;
#endif
} else {
LOG_ERR("Invalid 'num' parameter value");
goto fail;
}
rp->num = cp->num;
*rsp_len = sizeof(*rp) + (rp->num * sizeof(rp->chan_id[0]));
return BTP_STATUS_SUCCESS;
fail:
for (i = 0U; i < ARRAY_SIZE(allocated_channels); i++) {
if (allocated_channels[i]) {
channels[BT_L2CAP_LE_CHAN(allocated_channels[i])->ident].in_use = false;
}
}
return BTP_STATUS_FAILED;
}
static uint8_t disconnect(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_l2cap_disconnect_cmd *cp = cmd;
struct channel *chan;
int err;
if (cp->chan_id >= CHANNELS) {
return BTP_STATUS_FAILED;
}
chan = &channels[cp->chan_id];
err = bt_l2cap_chan_disconnect(&chan->le.chan);
if (err) {
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
#if defined(CONFIG_BT_L2CAP_ECRED)
static uint8_t reconfigure(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_l2cap_reconfigure_cmd *cp = cmd;
uint16_t mtu;
struct bt_conn *conn;
int err;
struct bt_l2cap_chan *reconf_channels[CHANNELS + 1] = {};
if (cmd_len < sizeof(*cp) ||
cmd_len != sizeof(*cp) + cp->num) {
return BTP_STATUS_FAILED;
}
if (cp->num > CHANNELS) {
return BTP_STATUS_FAILED;
}
mtu = sys_le16_to_cpu(cp->mtu);
if (mtu > DATA_MTU) {
return BTP_STATUS_FAILED;
}
for (int i = 0; i < cp->num; i++) {
if (cp->chan_id[i] > CHANNELS) {
return BTP_STATUS_FAILED;
}
reconf_channels[i] = &channels[cp->chan_id[i]].le.chan;
}
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
LOG_ERR("Unknown connection");
return BTP_STATUS_FAILED;
}
err = bt_l2cap_ecred_chan_reconfigure(reconf_channels, mtu);
if (err) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
bt_conn_unref(conn);
return BTP_STATUS_SUCCESS;
}
#endif
#if defined(CONFIG_BT_EATT)
static uint8_t disconnect_eatt_chans(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_l2cap_disconnect_eatt_chans_cmd *cp = cmd;
struct bt_conn *conn;
int err;
conn = bt_conn_lookup_addr_le(BT_ID_DEFAULT, &cp->address);
if (!conn) {
LOG_ERR("Unknown connection");
return BTP_STATUS_FAILED;
}
for (int i = 0; i < cp->count; i++) {
err = bt_eatt_disconnect_one(conn);
if (err) {
bt_conn_unref(conn);
return BTP_STATUS_FAILED;
}
}
bt_conn_unref(conn);
return BTP_STATUS_SUCCESS;
}
#endif
static uint8_t send_data(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_l2cap_send_data_cmd *cp = cmd;
struct channel *chan;
struct net_buf *buf;
uint16_t data_len;
int ret;
if (cmd_len < sizeof(*cp) ||
cmd_len != sizeof(*cp) + sys_le16_to_cpu(cp->data_len)) {
return BTP_STATUS_FAILED;
}
if (cp->chan_id >= CHANNELS) {
return BTP_STATUS_FAILED;
}
chan = &channels[cp->chan_id];
data_len = sys_le16_to_cpu(cp->data_len);
/* FIXME: For now, fail if data length exceeds buffer length */
if (data_len > DATA_MTU) {
return BTP_STATUS_FAILED;
}
/* FIXME: For now, fail if data length exceeds remote's L2CAP SDU */
if (data_len > chan->le.tx.mtu) {
return BTP_STATUS_FAILED;
}
buf = net_buf_alloc(&data_pool, K_FOREVER);
net_buf_reserve(buf, BT_L2CAP_SDU_CHAN_SEND_RESERVE);
net_buf_add_mem(buf, cp->data, data_len);
ret = bt_l2cap_chan_send(&chan->le.chan, buf);
if (ret < 0) {
LOG_ERR("Unable to send data: %d", -ret);
net_buf_unref(buf);
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static struct bt_l2cap_server *get_free_server(void)
{
uint8_t i;
for (i = 0U; i < SERVERS ; i++) {
if (servers[i].psm) {
continue;
}
return &servers[i];
}
return NULL;
}
static bool is_free_psm(uint16_t psm)
{
uint8_t i;
for (i = 0U; i < ARRAY_SIZE(servers); i++) {
if (servers[i].psm == psm) {
return false;
}
}
return true;
}
static int accept(struct bt_conn *conn, struct bt_l2cap_chan **l2cap_chan)
{
struct channel *chan;
if (bt_conn_enc_key_size(conn) < req_keysize) {
return -EPERM;
}
if (authorize_flag) {
return -EACCES;
}
chan = get_free_channel();
if (!chan) {
return -ENOMEM;
}
chan->le.chan.ops = &l2cap_ops;
chan->le.rx.mtu = DATA_MTU_INITIAL;
*l2cap_chan = &chan->le.chan;
return 0;
}
static uint8_t listen(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_l2cap_listen_cmd *cp = cmd;
struct bt_l2cap_server *server;
uint16_t psm = sys_le16_to_cpu(cp->psm);
/* TODO: Handle cmd->transport flag */
if (psm == 0 || !is_free_psm(psm)) {
return BTP_STATUS_FAILED;
}
server = get_free_server();
if (!server) {
return BTP_STATUS_FAILED;
}
server->accept = accept;
server->psm = psm;
switch (cp->response) {
case BTP_L2CAP_CONNECTION_RESPONSE_SUCCESS:
break;
case BTP_L2CAP_CONNECTION_RESPONSE_INSUFF_ENC_KEY:
/* TSPX_psm_encryption_key_size_required */
req_keysize = 16;
break;
case BTP_L2CAP_CONNECTION_RESPONSE_INSUFF_AUTHOR:
authorize_flag = true;
break;
case BTP_L2CAP_CONNECTION_RESPONSE_INSUFF_AUTHEN:
server->sec_level = BT_SECURITY_L3;
break;
case BTP_L2CAP_CONNECTION_RESPONSE_INSUFF_ENCRYPTION:
server->sec_level = BT_SECURITY_L2;
break;
default:
return BTP_STATUS_FAILED;
}
if (bt_l2cap_server_register(server) < 0) {
server->psm = 0U;
return BTP_STATUS_FAILED;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t credits(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
const struct btp_l2cap_credits_cmd *cp = cmd;
struct channel *chan;
if (cp->chan_id >= CHANNELS) {
return BTP_STATUS_FAILED;
}
chan = &channels[cp->chan_id];
if (!chan->in_use) {
return BTP_STATUS_FAILED;
}
if (chan->pending_credit) {
if (bt_l2cap_chan_recv_complete(&chan->le.chan,
chan->pending_credit) < 0) {
return BTP_STATUS_FAILED;
}
chan->pending_credit = NULL;
}
return BTP_STATUS_SUCCESS;
}
static uint8_t supported_commands(const void *cmd, uint16_t cmd_len,
void *rsp, uint16_t *rsp_len)
{
struct btp_l2cap_read_supported_commands_rp *rp = rsp;
/* octet 0 */
tester_set_bit(rp->data, BTP_L2CAP_READ_SUPPORTED_COMMANDS);
tester_set_bit(rp->data, BTP_L2CAP_CONNECT);
tester_set_bit(rp->data, BTP_L2CAP_DISCONNECT);
tester_set_bit(rp->data, BTP_L2CAP_SEND_DATA);
tester_set_bit(rp->data, BTP_L2CAP_LISTEN);
#if defined(CONFIG_BT_L2CAP_ECRED)
tester_set_bit(rp->data, BTP_L2CAP_RECONFIGURE);
#endif
/* octet 1 */
tester_set_bit(rp->data, BTP_L2CAP_CREDITS);
#if defined(CONFIG_BT_EATT)
tester_set_bit(rp->data, BTP_L2CAP_DISCONNECT_EATT_CHANS);
#endif
*rsp_len = sizeof(*rp) + 2;
return BTP_STATUS_SUCCESS;
}
static const struct btp_handler handlers[] = {
{
.opcode = BTP_L2CAP_READ_SUPPORTED_COMMANDS,
.index = BTP_INDEX_NONE,
.expect_len = 0,
.func = supported_commands,
},
{
.opcode = BTP_L2CAP_CONNECT,
.expect_len = sizeof(struct btp_l2cap_connect_cmd),
.func = connect,
},
{
.opcode = BTP_L2CAP_DISCONNECT,
.expect_len = sizeof(struct btp_l2cap_disconnect_cmd),
.func = disconnect,
},
{
.opcode = BTP_L2CAP_SEND_DATA,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = send_data,
},
{
.opcode = BTP_L2CAP_LISTEN,
.expect_len = sizeof(struct btp_l2cap_listen_cmd),
.func = listen,
},
{
.opcode = BTP_L2CAP_RECONFIGURE,
.expect_len = BTP_HANDLER_LENGTH_VARIABLE,
.func = reconfigure,
},
{
.opcode = BTP_L2CAP_CREDITS,
.expect_len = sizeof(struct btp_l2cap_credits_cmd),
.func = credits,
},
{
.opcode = BTP_L2CAP_DISCONNECT_EATT_CHANS,
.expect_len = sizeof(struct btp_l2cap_disconnect_eatt_chans_cmd),
.func = disconnect_eatt_chans,
},
};
uint8_t tester_init_l2cap(void)
{
tester_register_command_handlers(BTP_SERVICE_ID_L2CAP, handlers,
ARRAY_SIZE(handlers));
return BTP_STATUS_SUCCESS;
}
uint8_t tester_unregister_l2cap(void)
{
return BTP_STATUS_SUCCESS;
}