Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 24 additions & 0 deletions tests/nimble_l2cap/Makefile
Original file line number Diff line number Diff line change
@@ -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
7 changes: 7 additions & 0 deletions tests/nimble_l2cap/README.md
Original file line number Diff line number Diff line change
@@ -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 changes: 334 additions & 0 deletions tests/nimble_l2cap/main.c
Original file line number Diff line number Diff line change
@@ -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;
}
21 changes: 21 additions & 0 deletions tests/nimble_l2cap_server/Makefile
Original file line number Diff line number Diff line change
@@ -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
Loading