1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

tests: add nimble_l2cap test application(s)

This commit is contained in:
Hauke Petersen 2019-03-21 18:28:26 +01:00
parent 713fd19040
commit 07a12108da
8 changed files with 705 additions and 0 deletions

View File

@ -0,0 +1,24 @@
# Configure default and allowed boards
BOARD ?= nrf52dk
BOARD_WHITELIST := nrf52dk nrf52840dk
# load the default test environment
include ../Makefile.tests_common
# RIOT modules used in for this test
USEMODULE += xtimer
USEMODULE += shell
USEMODULE += shell_commands
USEMODULE += ps
USEMODULE += bluetil_ad
# Get the shared NimBLE and test configuration from the backend server
include $(RIOTBASE)/tests/nimble_l2cap_server/nimble.inc.mk
# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
CFLAGS += -DLOG_LEVEL=LOG_ALL
CFLAGS += -DDEBUG_ASSERT_VERBOSE
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,7 @@
# About
This is a client application for testing and benchmarking of raw L2CAP
connection oriented channels (COC). This application works together with the
`tests/nimble_l2cap_server` application.
See `tests/nimble_l2cap_server/README.md` for more information.

334
tests/nimble_l2cap/main.c Normal file
View File

@ -0,0 +1,334 @@
/*
* Copyright (C) 2019 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Client to test and benchmark raw L2CAP COC for NimBLE
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*
* @}
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "nimble_riot.h"
#include "host/ble_hs.h"
#include "host/ble_gap.h"
#include "host/util/util.h"
#include "assert.h"
#include "shell.h"
#include "net/bluetil/ad.h"
#include "nimble_l2cap_test_conf.h"
#define ENABLE_DEBUG (1)
#include "debug.h"
#define FLAG_UP (1u << 0)
#define FLAG_SYNC (1u << 1)
/* synchronization state */
static thread_t *_main;
static volatile uint32_t _last_rx_seq;
/* BLE connection state */
static uint16_t _handle = 0;
static struct ble_l2cap_chan *_coc = NULL;
/* buffer allocation */
static os_membuf_t _coc_mem[OS_MEMPOOL_SIZE(MBUFCNT, MBUFSIZE)];
static struct os_mempool _coc_mempool;
static struct os_mbuf_pool _coc_mbuf_pool;
static uint32_t _rxbuf[APP_MTU / 4];
static uint32_t _txbuf[APP_MTU / 4];
static void _on_data(struct ble_l2cap_event *event)
{
int res;
(void)res;
struct os_mbuf *rxd = event->receive.sdu_rx;
assert(rxd != NULL);
int rx_len = (int)OS_MBUF_PKTLEN(rxd);
assert(rx_len <= (int)APP_MTU);
res = os_mbuf_copydata(rxd, 0, rx_len, _rxbuf);
assert(res == 0);
_last_rx_seq = _rxbuf[POS_SEQ];
if (_rxbuf[POS_TYPE] == TYPE_INCTEST) {
thread_flags_set(_main, FLAG_SYNC);
}
/* free buffer */
res = os_mbuf_free_chain(rxd);
assert(res == 0);
rxd = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0);
assert(rxd != NULL);
res = ble_l2cap_recv_ready(_coc, rxd);
assert(res == 0);
}
static int _on_l2cap_evt(struct ble_l2cap_event *event, void *arg)
{
(void)arg;
switch (event->type) {
case BLE_L2CAP_EVENT_COC_CONNECTED:
_coc = event->connect.chan;
puts("# L2CAP: CONNECTED");
thread_flags_set(_main, FLAG_UP);
break;
case BLE_L2CAP_EVENT_COC_DISCONNECTED:
_coc = NULL;
puts("# L2CAP: DISCONNECTED");
break;
case BLE_L2CAP_EVENT_COC_DATA_RECEIVED:
_on_data(event);
break;
case BLE_L2CAP_EVENT_COC_ACCEPT:
/* this event should never be triggered for the L2CAP client */
/* fallthrough */
default:
assert(0);
break;
}
return 0;
}
static int _on_gap_evt(struct ble_gap_event *event, void *arg)
{
(void)arg;
printf("# GAP event: %i\n", (int)event->type);
switch (event->type) {
case BLE_GAP_EVENT_CONNECT: {
_handle = event->connect.conn_handle;
struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0);
assert(sdu_rx != NULL);
int res = ble_l2cap_connect(_handle, APP_CID, APP_MTU, sdu_rx,
_on_l2cap_evt, NULL);
assert(res == 0);
break;
}
case BLE_GAP_EVENT_DISCONNECT:
_handle = 0;
puts("# TERMINATED, bye bye");
break;
case BLE_GAP_EVENT_CONN_UPDATE:
case BLE_GAP_EVENT_CONN_UPDATE_REQ:
default:
break;
}
return 0;
}
static void _filter_and_connect(struct ble_gap_disc_desc *disc)
{
int res;
bluetil_ad_t ad;
bluetil_ad_init(&ad, disc->data,
(size_t)disc->length_data, (size_t)disc->length_data);
res = bluetil_ad_find_and_cmp(&ad, BLE_GAP_AD_NAME,
APP_NODENAME, (sizeof(APP_NODENAME) - 1));
if (res) {
res = ble_gap_disc_cancel();
assert(res == 0);
printf("# Found Server, connecting now");
res = ble_gap_connect(nimble_riot_own_addr_type, &disc->addr,
BLE_HS_FOREVER, NULL, _on_gap_evt, NULL);
assert(res == 0);
}
}
static int _on_scan_evt(struct ble_gap_event *event, void *arg)
{
(void)arg;
switch (event->type) {
case BLE_GAP_EVENT_DISC:
_filter_and_connect(&event->disc);
break;
case BLE_GAP_EVENT_DISC_COMPLETE:
default:
break;
}
return 0;
}
static void _send(uint32_t type, uint32_t seq, size_t len)
{
int res = 0;
(void)res;
struct os_mbuf *txd;
assert(_coc);
assert(len >= 8);
assert(len <= APP_MTU);
printf("# Sending: size %5u seq %5u\n", (unsigned)len, (unsigned)seq);
_txbuf[POS_TYPE] = type;
_txbuf[POS_SEQ] = seq;
txd = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0);
assert(txd != NULL);
res = os_mbuf_append(txd, _txbuf, len);
assert(res == 0);
do {
res = ble_l2cap_send(_coc, txd);
} while (res == BLE_HS_EBUSY);
if (res != 0) {
printf("# err: failed to send (%i)\n", res);
assert(0);
}
}
static int _cmd_inctest(int argc, char **argv)
{
size_t step = 10;
size_t limit = APP_MTU;
if ((argc == 2) && (strncmp(argv[1], "help", 4) == 0)) {
printf("usage: %s [step [limit]]\n", argv[0]);
return 0;
}
if (_coc == NULL) {
puts("err: not connected");
return 1;
}
/* parse params */
if (argc >= 2) {
step = (size_t)atoi(argv[1]);
}
if (argc >= 3) {
limit = (size_t)atoi(argv[2]);
if ((limit < 8) || (limit > APP_MTU)) {
puts("err: invalid limit payload length given");
return 1;
}
}
/* send out packets with increasing payload size */
thread_flags_clear(FLAG_SYNC);
uint32_t seq = 0;
uint32_t time = xtimer_now_usec();
for (size_t i = 8; i < limit; i += step) {
_send(TYPE_INCTEST, ++seq, i);
thread_flags_wait_all(FLAG_SYNC);
if (_last_rx_seq != seq) {
printf("# err: bad sequence number in response (%u)\n",
(unsigned)_last_rx_seq);
assert(0);
}
}
time = (xtimer_now_usec() - time);
puts("# TEST COMPLETE");
printf("-> runtime: %ums\n", (unsigned)(time / 1000));
return 0;
}
static int _cmd_floodtest(int argc, char **argv)
{
size_t pktsize = PKTSIZE_DEFAULT;
unsigned limit = FLOOD_DEFAULT;
if ((argc == 2) && (strncmp(argv[1], "help", 4) == 0)) {
printf("usage: %s [payload_size [num_of_packets]]\n", argv[0]);
return 0;
}
if (_coc == NULL) {
puts("err: not connected");
return 1;
}
if (argc >= 2) {
pktsize = (size_t)atoi(argv[1]);
if ((pktsize < 8) || (pktsize > APP_MTU)) {
puts("err: invalid packet size given");
return 1;
}
}
if (argc >= 3) {
limit = (unsigned)atoi(argv[2]);
}
/* now lets flood */
uint32_t seq = 0;
uint32_t time = xtimer_now_usec();
unsigned sum = 0;
for (unsigned i = 0; i < limit; i++) {
_send(TYPE_FLOODING, ++seq, pktsize);
sum += pktsize;
}
/* we wait for the last packet to be returned to compensate any internal
* packet buffering, but we also include this extra packet transmission in
* the throughput calculation */
sum += pktsize;
while (_last_rx_seq != seq) {}
time = (xtimer_now_usec() - time);
puts("# TEST COMPLETE");
printf("-> runtime: %ums\n", (unsigned)(time / 1000));
printf("-> ~ %u bytes/s\n", (unsigned)((sum * 1000) / (time / 1000)));
return 0;
}
static const shell_command_t _cmds[] = {
{ "inctest", "send stream of payloads of incremental size", _cmd_inctest },
{ "flood", "flood connection with data", _cmd_floodtest },
{ NULL, NULL, NULL }
};
int main(void)
{
int res;
(void)res;
puts("NimBLE L2CAP test application");
/* save context of the main thread */
_main = (thread_t *)thread_get(thread_getpid());
/* initialize buffers and setup the test environment */
res = os_mempool_init(&_coc_mempool, MBUFCNT, MBUFSIZE, _coc_mem, "appbuf");
assert(res == 0);
res = os_mbuf_pool_init(&_coc_mbuf_pool, &_coc_mempool, MBUFSIZE, MBUFCNT);
assert(res == 0);
/* start scanning for a suitable test server */
puts("# Scanning now");
struct ble_gap_disc_params params = { 0 };
res = ble_gap_disc(nimble_riot_own_addr_type, BLE_HS_FOREVER,
&params, _on_scan_evt, NULL);
assert(res == 0);
/* wait until we are connected to the test server */
thread_flags_wait_all(FLAG_UP);
puts("# Connection established");
printf("# MTUs: our %i, remote %i\n",
ble_l2cap_get_our_mtu(_coc), ble_l2cap_get_peer_mtu(_coc));
/* start shell */
puts("# Shell is now available");
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(_cmds, line_buf, SHELL_DEFAULT_BUFSIZE);
/* should be never reached */
return 0;
}

View File

@ -0,0 +1,21 @@
# Configure default and allowed boards
BOARD ?= nrf52dk
BOARD_WHITELIST := nrf52dk nrf52840dk
# load the default test environment
include ../Makefile.tests_common
# RIOT modules used in for this test
USEMODULE += xtimer
USEMODULE += bluetil_ad
# Get the shared NimBLE and test configuration from the backend server
include $(RIOTBASE)/tests/nimble_l2cap_server/nimble.inc.mk
# Comment this out to disable code in RIOT that does safety checking
# which is not needed in a production environment but helps in the
# development process:
CFLAGS += -DLOG_LEVEL=LOG_ALL
CFLAGS += -DDEBUG_ASSERT_VERBOSE
include $(RIOTBASE)/Makefile.include

View File

@ -0,0 +1,69 @@
# About
This application acts as a L2CAP echo server for testing and benchmarking raw
L2CAP connection oriented channel (COC) connections using NimBLE on RIOT. This
server application works in conjunction with the `nimble_l2cap` client test
application in `tests/nimble_l2cap`.
Next to the actual L2CAP server implementation, this folder also holds some
shared configuration for both the server and the client test applications.
# Usage
To run the L2CAP test suite, you need to have two BLE (NimBLE) capable boards in
radio range of each other available. Simple flash the server application on one
board and the test application on the other one. The applications are build in a
way, that they will automatically discover each other and open a dedicated
L2CAP channel connection that is used for all subsequent test.
The server will print something like
```
2019-03-21 21:27:12,012 - INFO # main(): This is RIOT! (Version: XX)
2019-03-21 21:27:12,012 - INFO # NimBLE L2CAP test server
2019-03-21 21:27:12,013 - INFO # # now advertising
[pausing here until the client is detected]
2019-03-21 21:27:16,928 - INFO # # GAP event 0
2019-03-21 21:27:16,981 - INFO # # L2CAP: CONNECTED
2019-03-21 21:27:16,983 - INFO # # MTUs: our 250, remote 250
```
The clients corresponding output will look similar to
```
2019-03-21 21:27:16,837 - INFO # main(): This is RIOT! (Version: XX)
2019-03-21 21:27:16,839 - INFO # NimBLE L2CAP test application
2019-03-21 21:27:16,840 - INFO # # Scanning now
[pausing here until server is detected]
2019-03-21 21:27:16,930 - INFO # # Found Server, connecting now# GAP event: 0
2019-03-21 21:27:17,030 - INFO # # L2CAP: CONNECTED
2019-03-21 21:27:17,035 - INFO # # Connection established, running test suite now
2019-03-21 21:27:17,037 - INFO # # MTUs: our 250, remote 250
2019-03-21 21:27:17,039 - INFO # # Shell is now available
```
Once the connection between the client and server is established, you can use
the shell commands provided by the client application to stress the opened
L2CAP channel.
E.g. do something like:
```
flood 5000 10
```
to send 10 packets of 5000 bytes. The clients output should look like this:
```
flood 5000 10
2019-03-21 21:30:57,151 - INFO # flood 5000 10
2019-03-21 21:30:57,154 - INFO # # Sending: size 5000 seq 1
2019-03-21 21:30:57,159 - INFO # # Sending: size 5000 seq 2
2019-03-21 21:30:57,688 - INFO # # Sending: size 5000 seq 3
2019-03-21 21:30:58,688 - INFO # # Sending: size 5000 seq 4
2019-03-21 21:30:59,688 - INFO # # Sending: size 5000 seq 5
2019-03-21 21:31:00,688 - INFO # # Sending: size 5000 seq 6
2019-03-21 21:31:01,687 - INFO # # Sending: size 5000 seq 7
2019-03-21 21:31:02,688 - INFO # # Sending: size 5000 seq 8
2019-03-21 21:31:03,688 - INFO # # Sending: size 5000 seq 9
2019-03-21 21:31:04,688 - INFO # # Sending: size 5000 seq 10
2019-03-21 21:31:07,187 - INFO # # TEST COMPLETE
2019-03-21 21:31:07,189 - INFO # -> runtime: 10082ms
2019-03-21 21:31:07,190 - INFO # -> ~ 5455 bytes/s
```

View File

@ -0,0 +1,61 @@
/*
* Copyright (C) 2019 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Shared configuration for NimBLE L2CAP tests
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*/
#ifndef NIMBLE_L2CAP_TEST_CONF_H
#define NIMBLE_L2CAP_TEST_CONF_H
#ifdef __cplusplus
extern "C" {
#endif
/**
* @name Calculate values for the application specific mbuf pool
* @{
*/
#define MBUFSIZE_OVHD (sizeof(struct os_mbuf) + \
sizeof(struct os_mbuf_pkthdr))
#define MBUFS_PER_MTU (APP_MTU / APP_BUF_CHUNKSIZE)
#define MBUFSIZE (APP_BUF_CHUNKSIZE + MBUFSIZE_OVHD)
#define MBUFCNT (APP_BUF_NUM * MBUFS_PER_MTU)
/** @} */
/**
* @name Default parameters for selected test cases
* @{
*/
#define PKTSIZE_DEFAULT (100U)
#define FLOOD_DEFAULT (50U)
/** @} */
/**
* @name Field offsets and type values for test packet payloads
* @{
*/
#define TYPE_INCTEST (0x17192123)
#define TYPE_FLOODING (0x73829384)
#define POS_TYPE (0U)
#define POS_SEQ (1U)
/** @} */
#ifdef __cplusplus
}
#endif
#endif /* NIMBLE_L2CAP_TEST_CONF_H */
/** @} */

View File

@ -0,0 +1,167 @@
/*
* Copyright (C) 2019 Freie Universität Berlin
*
* This file is subject to the terms and conditions of the GNU Lesser
* General Public License v2.1. See the file LICENSE in the top level
* directory for more details.
*/
/**
* @ingroup tests
* @{
*
* @file
* @brief Server to test and benchmark raw L2CAP COC for NimBLE
*
* @author Hauke Petersen <hauke.petersen@fu-berlin.de>
*
* @}
*/
#include <stdio.h>
#include <stdlib.h>
#include "nimble_riot.h"
#include "host/ble_hs.h"
#include "host/ble_gap.h"
#include "host/util/util.h"
#include "assert.h"
#include "net/bluetil/ad.h"
#include "nimble_l2cap_test_conf.h"
/* BLE connection state */
static uint16_t _handle = 0;
static struct ble_l2cap_chan *_coc = NULL;
/* buffer allocation */
static os_membuf_t _coc_mem[OS_MEMPOOL_SIZE(MBUFCNT, MBUFSIZE)];
static struct os_mempool _coc_mempool;
static struct os_mbuf_pool _coc_mbuf_pool;
static uint32_t _rxbuf[APP_MTU / 4];
/* safe AD fields */
static uint8_t _ad_buf[BLE_HS_ADV_MAX_SZ];
static bluetil_ad_t _ad;
static struct ble_gap_adv_params _adv_params = { 0 };
static void _advertise_now(void);
static void _on_data(struct ble_l2cap_event *event)
{
int res;
struct os_mbuf *rxd;
rxd = event->receive.sdu_rx;
assert(rxd != NULL);
int rx_len = (int)OS_MBUF_PKTLEN(rxd);
assert(rx_len <= (int)APP_MTU);
res = os_mbuf_copydata(rxd, 0, rx_len, _rxbuf);
assert(res == 0);
printf("# Received: len %5i, seq %5u\n", rx_len, (unsigned)_rxbuf[POS_SEQ]);
res = ble_l2cap_send(_coc, rxd);
assert(res == 0);
/* allocate new mbuf for receiving new data */
rxd = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0);
assert(rxd != NULL);
res = ble_l2cap_recv_ready(_coc, rxd);
assert(res == 0);
}
static int _on_gap_evt(struct ble_gap_event *event, void *arg)
{
(void)arg;
printf("# GAP event %i\n", (int)event->type);
switch (event->type) {
case BLE_GAP_EVENT_CONNECT:
_handle = event->connect.conn_handle;
break;
case BLE_GAP_EVENT_DISCONNECT:
_coc = NULL;
_handle = 0;
_advertise_now();
break;
case BLE_GAP_EVENT_CONN_UPDATE:
case BLE_GAP_EVENT_CONN_UPDATE_REQ:
default:
break;
}
return 0;
}
static int _on_l2cap_evt(struct ble_l2cap_event *event, void *arg)
{
(void)arg;
switch (event->type) {
case BLE_L2CAP_EVENT_COC_CONNECTED:
_coc = event->connect.chan;
puts("# L2CAP: CONNECTED");
printf("# MTUs: our %i, remote %i\n",
ble_l2cap_get_our_mtu(_coc), ble_l2cap_get_peer_mtu(_coc));
break;
case BLE_L2CAP_EVENT_COC_DISCONNECTED:
_coc = NULL;
puts("# L2CAP: DISCONNECTED");
break;
case BLE_L2CAP_EVENT_COC_ACCEPT: {
struct os_mbuf *sdu_rx = os_mbuf_get_pkthdr(&_coc_mbuf_pool, 0);
assert(sdu_rx != NULL);
ble_l2cap_recv_ready(event->accept.chan, sdu_rx);
break;
}
case BLE_L2CAP_EVENT_COC_DATA_RECEIVED:
_on_data(event);
break;
default:
assert(0);
break;
}
return 0;
}
static void _advertise_now(void)
{
int res = ble_gap_adv_start(nimble_riot_own_addr_type, NULL, BLE_HS_FOREVER,
&_adv_params, _on_gap_evt, NULL);
assert(res == 0);
}
int main(void)
{
int res;
(void)res;
puts("NimBLE L2CAP test server");
/* initialize buffers and setup the test environment */
res = os_mempool_init(&_coc_mempool, MBUFCNT, MBUFSIZE, _coc_mem, "appbuf");
assert(res == 0);
res = os_mbuf_pool_init(&_coc_mbuf_pool, &_coc_mempool, MBUFSIZE, MBUFCNT);
assert(res == 0);
/* create l2cap server */
res = ble_l2cap_create_server(APP_CID, APP_MTU, _on_l2cap_evt, NULL);
assert(res == 0);
/* initialize advertising data and parameters */
_adv_params.conn_mode = BLE_GAP_CONN_MODE_UND;
_adv_params.disc_mode = BLE_GAP_DISC_MODE_GEN;
bluetil_ad_init_with_flags(&_ad, _ad_buf, sizeof(_ad_buf),
BLUETIL_AD_FLAGS_DEFAULT);
bluetil_ad_add_name(&_ad, APP_NODENAME);
res = ble_gap_adv_set_data(_ad.buf, (int)_ad.pos);
assert(res == 0);
/* start advertising the test server */
_advertise_now();
puts("# now advertising");
/* nothing else to be done in the main thread */
return 0;
}

View File

@ -0,0 +1,22 @@
# Set the tests default configuration
APP_MTU ?= 5000
APP_BUF_CHUNKSIZE ?= 250 # must be full divider of APP_MTU
APP_BUF_NUM ?= 3
APP_NODENAME ?= \"nimble_l2cap_test_server\"
APP_CID ?= 0x0235
# Apply configuration values
CFLAGS += -DAPP_MTU=$(APP_MTU)
CFLAGS += -DAPP_BUF_CHUNKSIZE=$(APP_BUF_CHUNKSIZE)
CFLAGS += -DAPP_BUF_NUM=$(APP_BUF_NUM)
CFLAGS += -DAPP_NODENAME=$(APP_NODENAME)
CFLAGS += -DAPP_CID=$(APP_CID)
# configure NimBLE
USEPKG += nimble
CFLAGS += -DMYNEWT_VAL_BLE_L2CAP_COC_MAX_NUM=1
CFLAGS += -DMYNEWT_VAL_BLE_L2CAP_COC_MTU=250
CFLAGS += -DMYNEWT_VAL_BLE_MAX_CONNECTIONS=1
CFLAGS += -DMYNEWT_VAL_MSYS_1_BLOCK_COUNT=55
INCLUDES += -I$(RIOTBASE)/tests/nimble_l2cap_server/include