/*
|
* 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.
|
*/
|
|
/* bttester.c - Bluetooth Tester */
|
|
/*
|
* Copyright (c) 2015-2016 Intel Corporation
|
*
|
* SPDX-License-Identifier: Apache-2.0
|
*/
|
|
#include <stdio.h>
|
#include <string.h>
|
#include <stdlib.h>
|
|
#include "nimble_syscfg.h"
|
#include "console/console.h"
|
|
#include "bttester_pipe.h"
|
#include "bttester.h"
|
|
#define CMD_QUEUED 2
|
|
static struct os_eventq avail_queue;
|
static struct os_eventq *cmds_queue;
|
static struct os_event bttester_ev[CMD_QUEUED];
|
|
struct btp_buf {
|
struct os_event *ev;
|
union {
|
uint8_t data[BTP_MTU];
|
struct btp_hdr hdr;
|
};
|
};
|
|
static struct btp_buf cmd_buf[CMD_QUEUED];
|
|
static void supported_commands(uint8_t *data, uint16_t len)
|
{
|
uint8_t buf[1];
|
struct core_read_supported_commands_rp *rp = (void *) buf;
|
|
memset(buf, 0, sizeof(buf));
|
|
tester_set_bit(buf, CORE_READ_SUPPORTED_COMMANDS);
|
tester_set_bit(buf, CORE_READ_SUPPORTED_SERVICES);
|
tester_set_bit(buf, CORE_REGISTER_SERVICE);
|
tester_set_bit(buf, CORE_UNREGISTER_SERVICE);
|
|
tester_send(BTP_SERVICE_ID_CORE, CORE_READ_SUPPORTED_COMMANDS,
|
BTP_INDEX_NONE, (uint8_t *) rp, sizeof(buf));
|
}
|
|
static void supported_services(uint8_t *data, uint16_t len)
|
{
|
uint8_t buf[1];
|
struct core_read_supported_services_rp *rp = (void *) buf;
|
|
memset(buf, 0, sizeof(buf));
|
|
tester_set_bit(buf, BTP_SERVICE_ID_CORE);
|
tester_set_bit(buf, BTP_SERVICE_ID_GAP);
|
tester_set_bit(buf, BTP_SERVICE_ID_GATT);
|
#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)
|
tester_set_bit(buf, BTP_SERVICE_ID_L2CAP);
|
#endif /* MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) */
|
#if MYNEWT_VAL(BLE_MESH)
|
tester_set_bit(buf, BTP_SERVICE_ID_MESH);
|
#endif /* MYNEWT_VAL(BLE_MESH) */
|
tester_set_bit(buf, BTP_SERVICE_ID_GATTC);
|
|
tester_send(BTP_SERVICE_ID_CORE, CORE_READ_SUPPORTED_SERVICES,
|
BTP_INDEX_NONE, (uint8_t *) rp, sizeof(buf));
|
}
|
|
static void register_service(uint8_t *data, uint16_t len)
|
{
|
struct core_register_service_cmd *cmd = (void *) data;
|
uint8_t status;
|
|
switch (cmd->id) {
|
case BTP_SERVICE_ID_GAP:
|
status = tester_init_gap();
|
/* Rsp with success status will be handled by bt enable cb */
|
if (status == BTP_STATUS_FAILED) {
|
goto rsp;
|
}
|
return;
|
case BTP_SERVICE_ID_GATT:
|
status = tester_init_gatt();
|
break;
|
#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)
|
case BTP_SERVICE_ID_L2CAP:
|
status = tester_init_l2cap();
|
break;
|
#endif /* MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) */
|
#if MYNEWT_VAL(BLE_MESH)
|
case BTP_SERVICE_ID_MESH:
|
status = tester_init_mesh();
|
break;
|
#endif /* MYNEWT_VAL(BLE_MESH) */
|
default:
|
status = BTP_STATUS_FAILED;
|
break;
|
}
|
|
rsp:
|
tester_rsp(BTP_SERVICE_ID_CORE, CORE_REGISTER_SERVICE, BTP_INDEX_NONE,
|
status);
|
}
|
|
static void unregister_service(uint8_t *data, uint16_t len)
|
{
|
struct core_unregister_service_cmd *cmd = (void *) data;
|
uint8_t status;
|
|
switch (cmd->id) {
|
case BTP_SERVICE_ID_GAP:
|
status = tester_unregister_gap();
|
break;
|
case BTP_SERVICE_ID_GATT:
|
status = tester_unregister_gatt();
|
break;
|
#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)
|
case BTP_SERVICE_ID_L2CAP:
|
status = tester_unregister_l2cap();
|
break;
|
#endif /* MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) */
|
#if MYNEWT_VAL(BLE_MESH)
|
case BTP_SERVICE_ID_MESH:
|
status = tester_unregister_mesh();
|
break;
|
#endif /* MYNEWT_VAL(BLE_MESH) */
|
default:
|
status = BTP_STATUS_FAILED;
|
break;
|
}
|
|
tester_rsp(BTP_SERVICE_ID_CORE, CORE_UNREGISTER_SERVICE, BTP_INDEX_NONE,
|
status);
|
}
|
|
static void handle_core(uint8_t opcode, uint8_t index, uint8_t *data,
|
uint16_t len)
|
{
|
if (index != BTP_INDEX_NONE) {
|
tester_rsp(BTP_SERVICE_ID_CORE, opcode, index,
|
BTP_STATUS_FAILED);
|
return;
|
}
|
|
switch (opcode) {
|
case CORE_READ_SUPPORTED_COMMANDS:
|
supported_commands(data, len);
|
return;
|
case CORE_READ_SUPPORTED_SERVICES:
|
supported_services(data, len);
|
return;
|
case CORE_REGISTER_SERVICE:
|
register_service(data, len);
|
return;
|
case CORE_UNREGISTER_SERVICE:
|
unregister_service(data, len);
|
return;
|
default:
|
tester_rsp(BTP_SERVICE_ID_CORE, opcode, BTP_INDEX_NONE,
|
BTP_STATUS_UNKNOWN_CMD);
|
return;
|
}
|
}
|
|
static void cmd_handler(struct os_event *ev)
|
{
|
uint16_t len;
|
struct btp_buf *cmd;
|
|
if (!ev || !ev->ev_arg) {
|
return;
|
}
|
|
cmd = ev->ev_arg;
|
|
len = sys_le16_to_cpu(cmd->hdr.len);
|
if (MYNEWT_VAL(BTTESTER_BTP_LOG)) {
|
console_printf("[DBG] received %d bytes: %s\n",
|
sizeof(cmd->hdr) + len,
|
bt_hex(cmd->data,
|
sizeof(cmd->hdr) + len));
|
}
|
|
/* TODO
|
* verify if service is registered before calling handler
|
*/
|
|
switch (cmd->hdr.service) {
|
case BTP_SERVICE_ID_CORE:
|
handle_core(cmd->hdr.opcode, cmd->hdr.index,
|
cmd->hdr.data, len);
|
break;
|
case BTP_SERVICE_ID_GAP:
|
tester_handle_gap(cmd->hdr.opcode, cmd->hdr.index,
|
cmd->hdr.data, len);
|
break;
|
case BTP_SERVICE_ID_GATT:
|
tester_handle_gatt(cmd->hdr.opcode, cmd->hdr.index,
|
cmd->hdr.data, len);
|
break;
|
#if MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM)
|
case BTP_SERVICE_ID_L2CAP:
|
tester_handle_l2cap(cmd->hdr.opcode, cmd->hdr.index,
|
cmd->hdr.data, len);
|
break;
|
#endif /* MYNEWT_VAL(BLE_L2CAP_COC_MAX_NUM) */
|
#if MYNEWT_VAL(BLE_MESH)
|
case BTP_SERVICE_ID_MESH:
|
tester_handle_mesh(cmd->hdr.opcode, cmd->hdr.index,
|
cmd->hdr.data, len);
|
break;
|
#endif /* MYNEWT_VAL(BLE_MESH) */
|
case BTP_SERVICE_ID_GATTC:
|
tester_handle_gattc(cmd->hdr.opcode, cmd->hdr.index,
|
cmd->hdr.data, len);
|
break;
|
default:
|
tester_rsp(cmd->hdr.service, cmd->hdr.opcode,
|
cmd->hdr.index, BTP_STATUS_FAILED);
|
break;
|
}
|
|
os_eventq_put(&avail_queue, ev);
|
}
|
|
static uint8_t *recv_cb(uint8_t *buf, size_t *off)
|
{
|
struct btp_hdr *cmd = (void *) buf;
|
struct os_event *new_ev;
|
struct btp_buf *new_buf, *old_buf;
|
uint16_t len;
|
|
if (*off < sizeof(*cmd)) {
|
return buf;
|
}
|
|
len = sys_le16_to_cpu(cmd->len);
|
if (len > BTP_MTU - sizeof(*cmd)) {
|
*off = 0;
|
return buf;
|
}
|
|
if (*off < sizeof(*cmd) + len) {
|
return buf;
|
}
|
|
new_ev = os_eventq_get_no_wait(&avail_queue);
|
if (!new_ev) {
|
SYS_LOG_ERR("BT tester: RX overflow");
|
*off = 0;
|
return buf;
|
}
|
|
old_buf = CONTAINER_OF(buf, struct btp_buf, data);
|
os_eventq_put(cmds_queue, old_buf->ev);
|
|
new_buf = new_ev->ev_arg;
|
*off = 0;
|
return new_buf->data;
|
}
|
|
static void avail_queue_init(void)
|
{
|
int i;
|
|
os_eventq_init(&avail_queue);
|
|
for (i = 0; i < CMD_QUEUED; i++) {
|
cmd_buf[i].ev = &bttester_ev[i];
|
bttester_ev[i].ev_cb = cmd_handler;
|
bttester_ev[i].ev_arg = &cmd_buf[i];
|
|
os_eventq_put(&avail_queue, &bttester_ev[i]);
|
}
|
}
|
|
void bttester_evq_set(struct os_eventq *evq)
|
{
|
cmds_queue = evq;
|
}
|
|
void tester_init(void)
|
{
|
struct os_event *ev;
|
struct btp_buf *buf;
|
|
avail_queue_init();
|
bttester_evq_set(os_eventq_dflt_get());
|
|
ev = os_eventq_get(&avail_queue);
|
buf = ev->ev_arg;
|
|
if (bttester_pipe_init()) {
|
SYS_LOG_ERR("Failed to initialize pipe");
|
return;
|
}
|
|
bttester_pipe_register(buf->data, BTP_MTU, recv_cb);
|
|
tester_send(BTP_SERVICE_ID_CORE, CORE_EV_IUT_READY, BTP_INDEX_NONE,
|
NULL, 0);
|
}
|
|
void tester_send(uint8_t service, uint8_t opcode, uint8_t index, uint8_t *data,
|
size_t len)
|
{
|
struct btp_hdr msg;
|
|
msg.service = service;
|
msg.opcode = opcode;
|
msg.index = index;
|
msg.len = len;
|
|
bttester_pipe_send((uint8_t *)&msg, sizeof(msg));
|
if (data && len) {
|
bttester_pipe_send(data, len);
|
}
|
|
if (MYNEWT_VAL(BTTESTER_BTP_LOG)) {
|
console_printf("[DBG] send %d bytes hdr: %s\n", sizeof(msg),
|
bt_hex((char *) &msg, sizeof(msg)));
|
if (data && len) {
|
console_printf("[DBG] send %d bytes data: %s\n", len,
|
bt_hex((char *) data, len));
|
}
|
}
|
}
|
|
void tester_send_buf(uint8_t service, uint8_t opcode, uint8_t index,
|
struct os_mbuf *data)
|
{
|
struct btp_hdr msg;
|
|
msg.service = service;
|
msg.opcode = opcode;
|
msg.index = index;
|
msg.len = os_mbuf_len(data);
|
|
bttester_pipe_send((uint8_t *)&msg, sizeof(msg));
|
if (data && msg.len) {
|
bttester_pipe_send_buf(data);
|
}
|
}
|
|
void tester_rsp(uint8_t service, uint8_t opcode, uint8_t index, uint8_t status)
|
{
|
struct btp_status s;
|
|
if (status == BTP_STATUS_SUCCESS) {
|
tester_send(service, opcode, index, NULL, 0);
|
return;
|
}
|
|
s.code = status;
|
tester_send(service, BTP_STATUS, index, (uint8_t *) &s, sizeof(s));
|
}
|