/*
|
* Licensed to the Apache Software Foundation (ASF) under one
|
* or more contributor license agreements. See the NOTICE file
|
* distributed with this work for additional information
|
* regarding copyright ownership. The ASF licenses this file
|
* to you under the Apache License, Version 2.0 (the
|
* "License"); you may not use this file except in compliance
|
* with the License. You may obtain a copy of the License at
|
*
|
* http://www.apache.org/licenses/LICENSE-2.0
|
*
|
* Unless required by applicable law or agreed to in writing,
|
* software distributed under the License is distributed on an
|
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
* KIND, either express or implied. See the License for the
|
* specific language governing permissions and limitations
|
* under the License.
|
*/
|
|
/* l2cap.c - Bluetooth L2CAP Tester */
|
|
/*
|
* Copyright (c) 2016 Intel Corporation
|
*
|
* SPDX-License-Identifier: Apache-2.0
|
*/
|
|
#include "nimble_syscfg.h"
|
|
#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)
|
|
#include "console/console.h"
|
#include "host/ble_gap.h"
|
#include "host/ble_l2cap.h"
|
|
#include "../../../nimble/host/src/ble_l2cap_priv.h"
|
|
#include "bttester.h"
|
|
#define CONTROLLER_INDEX 0
|
#define CHANNELS MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)
|
#define TESTER_COC_MTU MYNEWT_VAL(BTTESTER_L2CAP_COC_MTU)
|
#define TESTER_COC_BUF_COUNT (3 * MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM))
|
|
static os_membuf_t tester_sdu_coc_mem[
|
OS_MEMPOOL_SIZE(TESTER_COC_BUF_COUNT, TESTER_COC_MTU)
|
];
|
|
struct os_mbuf_pool sdu_os_mbuf_pool;
|
static struct os_mempool sdu_coc_mbuf_mempool;
|
static bool hold_credit = false;
|
|
static struct channel {
|
uint8_t chan_id; /* Internal number that identifies L2CAP channel. */
|
uint8_t state;
|
struct ble_l2cap_chan *chan;
|
} channels[CHANNELS];
|
|
static uint8_t recv_cb_buf[TESTER_COC_MTU + sizeof(struct l2cap_data_received_ev)];
|
|
static struct channel *get_free_channel(void)
|
{
|
uint8_t i;
|
struct channel *chan;
|
|
for (i = 0; i < CHANNELS; i++) {
|
if (channels[i].state) {
|
continue;
|
}
|
|
chan = &channels[i];
|
chan->chan_id = i;
|
|
return chan;
|
}
|
|
return NULL;
|
}
|
|
struct channel *find_channel(struct ble_l2cap_chan *chan)
|
{
|
int i;
|
|
for (i = 0; i < CHANNELS; ++i) {
|
if (channels[i].chan == chan) {
|
return &channels[i];
|
}
|
}
|
|
return NULL;
|
}
|
|
struct channel *get_channel(uint8_t chan_id)
|
{
|
if (chan_id >= CHANNELS) {
|
return NULL;
|
}
|
|
return &channels[chan_id];
|
}
|
|
static void
|
tester_l2cap_coc_recv(struct ble_l2cap_chan *chan, struct os_mbuf *sdu)
|
{
|
SYS_LOG_DBG("LE CoC SDU received, chan: 0x%08lx, data len %d",
|
(uint32_t) chan, OS_MBUF_PKTLEN(sdu));
|
|
os_mbuf_free_chain(sdu);
|
if (!hold_credit) {
|
sdu = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
|
assert(sdu != NULL);
|
|
|
ble_l2cap_recv_ready(chan, sdu);
|
}
|
}
|
|
static void recv_cb(uint16_t conn_handle, struct ble_l2cap_chan *chan,
|
struct os_mbuf *buf, void *arg)
|
{
|
struct l2cap_data_received_ev *ev = (void *) recv_cb_buf;
|
struct channel *channel = find_channel(chan);
|
assert(channel != NULL);
|
|
ev->chan_id = channel->chan_id;
|
ev->data_length = OS_MBUF_PKTLEN(buf);
|
|
if (ev->data_length > TESTER_COC_MTU) {
|
SYS_LOG_ERR("Too large sdu received, truncating data");
|
ev->data_length = TESTER_COC_MTU;
|
}
|
os_mbuf_copydata(buf, 0, ev->data_length, ev->data);
|
|
tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_EV_DATA_RECEIVED,
|
CONTROLLER_INDEX, recv_cb_buf, sizeof(*ev) + ev->data_length);
|
|
tester_l2cap_coc_recv(chan, buf);
|
}
|
|
static void unstalled_cb(uint16_t conn_handle, struct ble_l2cap_chan *chan,
|
int status, void *arg)
|
{
|
if (status) {
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_SEND_DATA,
|
CONTROLLER_INDEX, BTP_STATUS_FAILED);
|
} else {
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_SEND_DATA,
|
CONTROLLER_INDEX, BTP_STATUS_SUCCESS);
|
}
|
}
|
|
static void reconfigured_ev(uint16_t conn_handle, struct ble_l2cap_chan *chan,
|
struct ble_l2cap_chan_info *chan_info,
|
int status)
|
{
|
struct l2cap_reconfigured_ev ev;
|
struct channel *channel;
|
|
if (status != 0) {
|
return;
|
}
|
|
channel = find_channel(chan);
|
assert(channel != NULL);
|
|
ev.chan_id = channel->chan_id;
|
ev.peer_mtu = chan_info->peer_coc_mtu;
|
ev.peer_mps = chan_info->peer_l2cap_mtu;
|
ev.our_mtu = chan_info->our_coc_mtu;
|
ev.our_mps = chan_info->our_l2cap_mtu;
|
|
tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_EV_RECONFIGURED,
|
CONTROLLER_INDEX, (uint8_t *) &ev, sizeof(ev));
|
}
|
|
static void connected_cb(uint16_t conn_handle, struct ble_l2cap_chan *chan,
|
struct ble_l2cap_chan_info *chan_info, void *arg)
|
{
|
struct l2cap_connected_ev ev;
|
struct ble_gap_conn_desc desc;
|
struct channel *channel = find_channel(chan);
|
|
if (channel == NULL) {
|
channel = get_free_channel();
|
}
|
|
ev.chan_id = channel->chan_id;
|
ev.psm = chan_info->psm;
|
ev.peer_mtu = chan_info->peer_coc_mtu;
|
ev.peer_mps = chan_info->peer_l2cap_mtu;
|
ev.our_mtu = chan_info->our_coc_mtu;
|
ev.our_mps = chan_info->our_l2cap_mtu;
|
channel->state = 1;
|
channel->chan = chan;
|
|
if (!ble_gap_conn_find(conn_handle, &desc)) {
|
ev.address_type = desc.peer_ota_addr.type;
|
memcpy(ev.address, desc.peer_ota_addr.val,
|
sizeof(ev.address));
|
}
|
|
tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_EV_CONNECTED, CONTROLLER_INDEX,
|
(uint8_t *) &ev, sizeof(ev));
|
}
|
|
static void disconnected_cb(uint16_t conn_handle, struct ble_l2cap_chan *chan,
|
struct ble_l2cap_chan_info *chan_info, void *arg)
|
{
|
struct l2cap_disconnected_ev ev;
|
struct ble_gap_conn_desc desc;
|
struct channel *channel;
|
|
memset(&ev, 0, sizeof(struct l2cap_disconnected_ev));
|
|
channel = find_channel(chan);
|
assert(channel != NULL);
|
|
channel->state = 0;
|
channel->chan = chan;
|
ev.chan_id = channel->chan_id;
|
ev.psm = chan_info->psm;
|
|
if (!ble_gap_conn_find(conn_handle, &desc)) {
|
ev.address_type = desc.peer_ota_addr.type;
|
memcpy(ev.address, desc.peer_ota_addr.val,
|
sizeof(ev.address));
|
}
|
|
tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_EV_DISCONNECTED,
|
CONTROLLER_INDEX, (uint8_t *) &ev, sizeof(ev));
|
}
|
|
static int accept_cb(uint16_t conn_handle, uint16_t peer_mtu,
|
struct ble_l2cap_chan *chan)
|
{
|
struct os_mbuf *sdu_rx;
|
|
SYS_LOG_DBG("LE CoC accepting, chan: 0x%08lx, peer_mtu %d",
|
(uint32_t) chan, peer_mtu);
|
|
sdu_rx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
|
if (!sdu_rx) {
|
return BLE_HS_ENOMEM;
|
}
|
|
ble_l2cap_recv_ready(chan, sdu_rx);
|
|
return 0;
|
}
|
|
static int
|
tester_l2cap_event(struct ble_l2cap_event *event, void *arg)
|
{
|
struct ble_l2cap_chan_info chan_info;
|
int accept_response;
|
struct ble_gap_conn_desc conn;
|
|
switch (event->type) {
|
case BLE_L2CAP_EVENT_COC_CONNECTED:
|
if (ble_l2cap_get_chan_info(event->connect.chan, &chan_info)) {
|
assert(0);
|
}
|
|
if (event->connect.status) {
|
console_printf("LE COC error: %d\n", event->connect.status);
|
return 0;
|
}
|
|
console_printf("LE COC connected, conn: %d, chan: 0x%08lx, "
|
"psm: 0x%02x, scid: 0x%04x, dcid: 0x%04x, "
|
"our_mps: %d, our_mtu: %d, peer_mps: %d, "
|
"peer_mtu: %d\n", event->connect.conn_handle,
|
(uint32_t) event->connect.chan, chan_info.psm,
|
chan_info.scid, chan_info.dcid,
|
chan_info.our_l2cap_mtu, chan_info.our_coc_mtu,
|
chan_info.peer_l2cap_mtu, chan_info.peer_coc_mtu);
|
|
connected_cb(event->connect.conn_handle,
|
event->connect.chan, &chan_info, arg);
|
|
return 0;
|
case BLE_L2CAP_EVENT_COC_DISCONNECTED:
|
if (ble_l2cap_get_chan_info(event->disconnect.chan,
|
&chan_info)) {
|
assert(0);
|
}
|
console_printf("LE CoC disconnected, chan: 0x%08lx\n",
|
(uint32_t) event->disconnect.chan);
|
|
disconnected_cb(event->disconnect.conn_handle,
|
event->disconnect.chan, &chan_info, arg);
|
return 0;
|
case BLE_L2CAP_EVENT_COC_ACCEPT:
|
ble_l2cap_get_chan_info(event->accept.chan, &chan_info);
|
if (chan_info.psm == 0x00F2) {
|
/* TSPX_psm_authentication_required */
|
ble_gap_conn_find(event->accept.conn_handle, &conn);
|
if (!conn.sec_state.authenticated) {
|
return BLE_HS_EAUTHEN;
|
}
|
} else if (chan_info.psm == 0x00F3) {
|
/* TSPX_psm_authorization_required */
|
ble_gap_conn_find(event->accept.conn_handle, &conn);
|
if (!conn.sec_state.encrypted) {
|
return BLE_HS_EAUTHOR;
|
}
|
return BLE_HS_EAUTHOR;
|
} else if (chan_info.psm == 0x00F4) {
|
/* TSPX_psm_encryption_key_size_required */
|
ble_gap_conn_find(event->accept.conn_handle, &conn);
|
if (conn.sec_state.key_size < 16) {
|
return BLE_HS_EENCRYPT_KEY_SZ;
|
}
|
}
|
|
accept_response = POINTER_TO_INT(arg);
|
if (accept_response) {
|
return accept_response;
|
}
|
|
console_printf("LE CoC accept, chan: 0x%08lx, handle: %u, sdu_size: %u\n",
|
(uint32_t) event->accept.chan,
|
event->accept.conn_handle,
|
event->accept.peer_sdu_size);
|
|
return accept_cb(event->accept.conn_handle,
|
event->accept.peer_sdu_size,
|
event->accept.chan);
|
|
case BLE_L2CAP_EVENT_COC_DATA_RECEIVED:
|
console_printf("LE CoC data received, chan: 0x%08lx, handle: %u, sdu_len: %u\n",
|
(uint32_t) event->receive.chan,
|
event->receive.conn_handle,
|
OS_MBUF_PKTLEN(event->receive.sdu_rx));
|
|
recv_cb(event->receive.conn_handle, event->receive.chan,
|
event->receive.sdu_rx, arg);
|
return 0;
|
case BLE_L2CAP_EVENT_COC_TX_UNSTALLED:
|
console_printf("LE CoC tx unstalled, chan: 0x%08lx, handle: %u, status: %d\n",
|
(uint32_t) event->tx_unstalled.chan,
|
event->tx_unstalled.conn_handle,
|
event->tx_unstalled.status);
|
|
unstalled_cb(event->tx_unstalled.conn_handle,
|
event->tx_unstalled.chan,
|
event->tx_unstalled.status, arg);
|
return 0;
|
case BLE_L2CAP_EVENT_COC_RECONFIG_COMPLETED:
|
if (ble_l2cap_get_chan_info(event->reconfigured.chan,
|
&chan_info)) {
|
assert(0);
|
}
|
console_printf("LE CoC reconfigure completed status 0x%02x, "
|
"chan: 0x%08lx\n", event->reconfigured.status,
|
(uint32_t) event->reconfigured.chan);
|
|
if (event->reconfigured.status == 0) {
|
console_printf("\t our_mps: %d our_mtu %d\n",
|
chan_info.our_l2cap_mtu, chan_info.our_coc_mtu);
|
}
|
|
reconfigured_ev(event->reconfigured.conn_handle,
|
event->reconfigured.chan,
|
&chan_info,
|
event->reconfigured.status);
|
return 0;
|
case BLE_L2CAP_EVENT_COC_PEER_RECONFIGURED:
|
if (ble_l2cap_get_chan_info(event->reconfigured.chan,
|
&chan_info)) {
|
assert(0);
|
}
|
console_printf("LE CoC peer reconfigured status 0x%02x, "
|
"chan: 0x%08lx\n", event->reconfigured.status,
|
(uint32_t) event->reconfigured.chan);
|
|
if (event->reconfigured.status == 0) {
|
console_printf("\t peer_mps: %d peer_mtu %d\n",
|
chan_info.peer_l2cap_mtu, chan_info.peer_coc_mtu);
|
}
|
|
reconfigured_ev(event->reconfigured.conn_handle,
|
event->reconfigured.chan,
|
&chan_info,
|
event->reconfigured.status);
|
return 0;
|
default:
|
return 0;
|
}
|
}
|
|
static void connect(uint8_t *data, uint16_t len)
|
{
|
const struct l2cap_connect_cmd *cmd = (void *) data;
|
uint8_t rp_buf[sizeof(struct l2cap_connect_rp) + cmd->num];
|
struct l2cap_connect_rp *rp = (void *) rp_buf;
|
struct ble_gap_conn_desc desc;
|
struct channel *chan;
|
struct os_mbuf *sdu_rx[cmd->num];
|
ble_addr_t *addr = (void *) data;
|
uint16_t mtu = htole16(cmd->mtu);
|
int rc;
|
int i, j;
|
bool ecfc = cmd->options & L2CAP_CONNECT_OPT_ECFC;
|
hold_credit = cmd->options & L2CAP_CONNECT_OPT_HOLD_CREDIT;
|
|
SYS_LOG_DBG("connect: type: %d addr: %s", addr->type, bt_hex(addr->val, 6));
|
|
if (mtu == 0 || mtu > TESTER_COC_MTU) {
|
mtu = TESTER_COC_MTU;
|
}
|
|
rc = ble_gap_conn_find_by_addr(addr, &desc);
|
if (rc) {
|
SYS_LOG_ERR("GAP conn find failed");
|
goto fail;
|
}
|
|
rp->num = cmd->num;
|
|
for (i = 0; i < cmd->num; i++) {
|
chan = get_free_channel();
|
if (!chan) {
|
SYS_LOG_ERR("No free channels");
|
goto fail;
|
}
|
/* temporarily mark channel as used to select next one */
|
chan->state = 1;
|
|
rp->chan_ids[i] = chan->chan_id;
|
|
sdu_rx[i] = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
|
if (sdu_rx[i] == NULL) {
|
SYS_LOG_ERR("Failed to alloc buf");
|
goto fail;
|
}
|
}
|
|
/* mark selected channels as unused again */
|
for (i = 0; i < cmd->num; i++) {
|
for (j = 0; j < CHANNELS; j++) {
|
if (rp->chan_ids[i] == channels[j].chan_id) {
|
channels[j].state = 0;
|
}
|
}
|
}
|
|
if (cmd->num == 1 && !ecfc) {
|
rc = ble_l2cap_connect(desc.conn_handle, htole16(cmd->psm),
|
mtu, sdu_rx[0],
|
tester_l2cap_event, NULL);
|
} else if (ecfc) {
|
rc = ble_l2cap_enhanced_connect(desc.conn_handle,
|
htole16(cmd->psm), mtu,
|
cmd->num, sdu_rx,
|
tester_l2cap_event, NULL);
|
} else {
|
SYS_LOG_ERR("Invalid 'num' parameter value");
|
goto fail;
|
}
|
|
if (rc) {
|
SYS_LOG_ERR("L2CAP connect failed\n");
|
goto fail;
|
}
|
|
tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_CONNECT, CONTROLLER_INDEX,
|
(uint8_t *) rp, sizeof(rp_buf));
|
|
return;
|
|
fail:
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_CONNECT, CONTROLLER_INDEX,
|
BTP_STATUS_FAILED);
|
}
|
|
static void disconnect(const uint8_t *data, uint16_t len)
|
{
|
const struct l2cap_disconnect_cmd *cmd = (void *) data;
|
struct channel *chan;
|
uint8_t status;
|
int err;
|
|
SYS_LOG_DBG("");
|
|
chan = get_channel(cmd->chan_id);
|
assert(chan != NULL);
|
|
err = ble_l2cap_disconnect(chan->chan);
|
if (err) {
|
status = BTP_STATUS_FAILED;
|
goto rsp;
|
}
|
|
status = BTP_STATUS_SUCCESS;
|
|
rsp:
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_DISCONNECT, CONTROLLER_INDEX,
|
status);
|
}
|
|
static void send_data(const uint8_t *data, uint16_t len)
|
{
|
const struct l2cap_send_data_cmd *cmd = (void *) data;
|
struct os_mbuf *sdu_tx = NULL;
|
int rc;
|
uint16_t data_len = sys_le16_to_cpu(cmd->data_len);
|
struct channel *chan = get_channel(cmd->chan_id);
|
|
SYS_LOG_DBG("cmd->chan_id=%d", cmd->chan_id);
|
|
if (!chan) {
|
SYS_LOG_ERR("Invalid channel\n");
|
goto fail;
|
}
|
|
/* FIXME: For now, fail if data length exceeds buffer length */
|
if (data_len > TESTER_COC_MTU) {
|
SYS_LOG_ERR("Data length exceeds buffer length");
|
goto fail;
|
}
|
|
sdu_tx = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
|
if (sdu_tx == NULL) {
|
SYS_LOG_ERR("No memory in the test sdu pool\n");
|
goto fail;
|
}
|
|
os_mbuf_append(sdu_tx, cmd->data, data_len);
|
|
/* ble_l2cap_send takes ownership of the sdu */
|
rc = ble_l2cap_send(chan->chan, sdu_tx);
|
if (rc == 0 || rc == BLE_HS_ESTALLED) {
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_SEND_DATA, CONTROLLER_INDEX,
|
BTP_STATUS_SUCCESS);
|
return;
|
}
|
|
SYS_LOG_ERR("Unable to send data: %d", rc);
|
os_mbuf_free_chain(sdu_tx);
|
|
fail:
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_SEND_DATA, CONTROLLER_INDEX,
|
BTP_STATUS_FAILED);
|
}
|
|
static int
|
l2cap_coc_err2hs_err(uint16_t coc_err)
|
{
|
switch (coc_err) {
|
case BLE_L2CAP_COC_ERR_UNKNOWN_LE_PSM:
|
return BLE_HS_ENOTSUP;
|
case BLE_L2CAP_COC_ERR_NO_RESOURCES:
|
return BLE_HS_ENOMEM;
|
case BLE_L2CAP_COC_ERR_INSUFFICIENT_AUTHEN:
|
return BLE_HS_EAUTHEN;
|
case BLE_L2CAP_COC_ERR_INSUFFICIENT_AUTHOR:
|
return BLE_HS_EAUTHOR;
|
case BLE_L2CAP_COC_ERR_INSUFFICIENT_ENC:
|
return BLE_HS_EENCRYPT;
|
case BLE_L2CAP_COC_ERR_INSUFFICIENT_KEY_SZ:
|
return BLE_HS_EENCRYPT_KEY_SZ;
|
case BLE_L2CAP_COC_ERR_UNACCEPTABLE_PARAMETERS:
|
return BLE_HS_EINVAL;
|
default:
|
return 0;
|
}
|
}
|
|
static int
|
l2cap_btp_listen_err2coc_err(uint16_t coc_err)
|
{
|
switch (coc_err) {
|
case 0x01:
|
return BLE_L2CAP_COC_ERR_INSUFFICIENT_AUTHEN;
|
case 0x02:
|
return BLE_L2CAP_COC_ERR_INSUFFICIENT_AUTHOR;
|
case 0x03:
|
return BLE_L2CAP_COC_ERR_INSUFFICIENT_KEY_SZ;
|
case 0x04:
|
return BLE_L2CAP_COC_ERR_INSUFFICIENT_ENC;
|
default:
|
return 0;
|
}
|
}
|
|
static void listen(const uint8_t *data, uint16_t len)
|
{
|
const struct l2cap_listen_cmd *cmd = (void *) data;
|
uint16_t mtu = htole16(cmd->mtu);
|
uint16_t rsp = htole16(cmd->response);
|
int rc;
|
|
SYS_LOG_DBG("");
|
|
if (mtu == 0 || mtu > TESTER_COC_MTU) {
|
mtu = TESTER_COC_MTU;
|
}
|
|
rsp = l2cap_btp_listen_err2coc_err(rsp);
|
rsp = l2cap_coc_err2hs_err(rsp);
|
|
/* TODO: Handle cmd->transport flag */
|
rc = ble_l2cap_create_server(cmd->psm, mtu, tester_l2cap_event,
|
INT_TO_POINTER(rsp));
|
if (rc) {
|
goto fail;
|
}
|
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_LISTEN, CONTROLLER_INDEX,
|
BTP_STATUS_SUCCESS);
|
return;
|
|
fail:
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_LISTEN, CONTROLLER_INDEX,
|
BTP_STATUS_FAILED);
|
}
|
|
static void credits(uint8_t *data, uint16_t len)
|
{
|
const struct l2cap_credits_cmd *cmd = (void *)data;
|
struct os_mbuf *sdu;
|
int rc;
|
|
struct channel *channel = get_channel(cmd->chan_id);
|
if (channel == NULL) {
|
goto fail;
|
}
|
|
sdu = os_mbuf_get_pkthdr(&sdu_os_mbuf_pool, 0);
|
if (sdu == NULL) {
|
os_mbuf_free_chain(sdu);
|
goto fail;
|
}
|
|
rc = ble_l2cap_recv_ready(channel->chan, sdu);
|
if (rc != 0) {
|
goto fail;
|
}
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_CREDITS, CONTROLLER_INDEX,
|
BTP_STATUS_SUCCESS);
|
return;
|
fail:
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_CREDITS, CONTROLLER_INDEX,
|
BTP_STATUS_FAILED);
|
}
|
|
static void reconfigure(const uint8_t *data, uint16_t len)
|
{
|
const struct l2cap_reconfigure_cmd *cmd = (void *) data;
|
uint16_t mtu = htole16(cmd->mtu);
|
struct ble_gap_conn_desc desc;
|
ble_addr_t *addr = (void *) data;
|
struct ble_l2cap_chan *chans[cmd->num];
|
struct channel *channel;
|
int rc;
|
int i;
|
|
SYS_LOG_DBG("");
|
|
if (mtu == 0 || mtu > TESTER_COC_MTU) {
|
mtu = TESTER_COC_MTU;
|
}
|
|
rc = ble_gap_conn_find_by_addr(addr, &desc);
|
if (rc) {
|
SYS_LOG_ERR("GAP conn find failed");
|
goto fail;
|
}
|
|
for (i = 0; i < cmd->num; ++i) {
|
channel = get_channel(cmd->idxs[i]);
|
if (channel == NULL) {
|
goto fail;
|
}
|
chans[i] = channel->chan;
|
}
|
|
rc = ble_l2cap_reconfig(chans, cmd->num, mtu);
|
if (rc) {
|
goto fail;
|
}
|
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_RECONFIGURE, CONTROLLER_INDEX,
|
BTP_STATUS_SUCCESS);
|
return;
|
|
fail:
|
tester_rsp(BTP_SERVICE_ID_L2CAP, L2CAP_RECONFIGURE, CONTROLLER_INDEX,
|
BTP_STATUS_FAILED);
|
}
|
|
static void supported_commands(uint8_t *data, uint16_t len)
|
{
|
uint8_t cmds[1];
|
struct l2cap_read_supported_commands_rp *rp = (void *) cmds;
|
|
memset(cmds, 0, sizeof(cmds));
|
|
tester_set_bit(cmds, L2CAP_READ_SUPPORTED_COMMANDS);
|
tester_set_bit(cmds, L2CAP_CONNECT);
|
tester_set_bit(cmds, L2CAP_DISCONNECT);
|
tester_set_bit(cmds, L2CAP_LISTEN);
|
tester_set_bit(cmds, L2CAP_SEND_DATA);
|
tester_set_bit(cmds, L2CAP_RECONFIGURE);
|
|
tester_send(BTP_SERVICE_ID_L2CAP, L2CAP_READ_SUPPORTED_COMMANDS,
|
CONTROLLER_INDEX, (uint8_t *) rp, sizeof(cmds));
|
}
|
|
void tester_handle_l2cap(uint8_t opcode, uint8_t index, uint8_t *data,
|
uint16_t len)
|
{
|
switch (opcode) {
|
case L2CAP_READ_SUPPORTED_COMMANDS:
|
supported_commands(data, len);
|
return;
|
case L2CAP_CONNECT:
|
connect(data, len);
|
return;
|
case L2CAP_DISCONNECT:
|
disconnect(data, len);
|
return;
|
case L2CAP_SEND_DATA:
|
send_data(data, len);
|
return;
|
case L2CAP_LISTEN:
|
listen(data, len);
|
return;
|
case L2CAP_RECONFIGURE:
|
reconfigure(data, len);
|
return;
|
case L2CAP_CREDITS:
|
credits(data, len);
|
return;
|
default:
|
tester_rsp(BTP_SERVICE_ID_L2CAP, opcode, index,
|
BTP_STATUS_UNKNOWN_CMD);
|
return;
|
}
|
}
|
|
uint8_t tester_init_l2cap(void)
|
{
|
int rc;
|
|
/* For testing we want to support all the available channels */
|
rc = os_mempool_init(&sdu_coc_mbuf_mempool, TESTER_COC_BUF_COUNT,
|
TESTER_COC_MTU, tester_sdu_coc_mem,
|
"tester_coc_sdu_pool");
|
assert(rc == 0);
|
|
rc = os_mbuf_pool_init(&sdu_os_mbuf_pool, &sdu_coc_mbuf_mempool,
|
TESTER_COC_MTU, TESTER_COC_BUF_COUNT);
|
assert(rc == 0);
|
|
return BTP_STATUS_SUCCESS;
|
}
|
|
uint8_t tester_unregister_l2cap(void)
|
{
|
return BTP_STATUS_SUCCESS;
|
}
|
|
#endif
|