[simulation] add simulation tests framework for tcat (#9724)

This commit adds simulation framework for tcat based on posix udp
sockets.
This commit is contained in:
Przemysław Bida
2024-03-22 10:30:59 -07:00
committed by GitHub
parent f917a5320f
commit 3ae275f1ac
12 changed files with 332 additions and 28 deletions
+1
View File
@@ -60,6 +60,7 @@ jobs:
- name: Bootstrap
run: |
sudo apt-get --no-install-recommends install -y expect ninja-build lcov socat
pip install bleak
- name: Run RCP Mode
run: |
ulimit -c unlimited
+1
View File
@@ -247,6 +247,7 @@ jobs:
- name: Bootstrap
run: |
sudo apt-get --no-install-recommends install -y expect ninja-build lcov socat
pip install bleak
- name: Run
run: |
ulimit -c unlimited
+1
View File
@@ -254,6 +254,7 @@ jobs:
- name: Bootstrap
run: |
sudo apt-get --no-install-recommends install -y expect ninja-build lcov socat
pip install bleak
- name: Run RCP Mode
run: |
ulimit -c unlimited
+149 -8
View File
@@ -26,50 +26,191 @@
* POSSIBILITY OF SUCH DAMAGE.
*/
#include "platform-simulation.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <openthread/platform/ble.h>
#include "openthread/error.h"
#include "utils/code_utils.h"
#define PLAT_BLE_MSG_DATA_MAX 2048
static uint8_t sBleBuffer[PLAT_BLE_MSG_DATA_MAX];
static int sFd = -1;
static const uint16_t kPortBase = 10000;
static uint16_t sPort = 0;
struct sockaddr_in sSockaddr;
static void initFds(void)
{
int fd;
int one = 1;
struct sockaddr_in sockaddr;
memset(&sockaddr, 0, sizeof(sockaddr));
sPort = (uint16_t)(kPortBase + gNodeId);
sockaddr.sin_family = AF_INET;
sockaddr.sin_port = htons(sPort);
sockaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
otEXPECT_ACTION((fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) != -1, perror("socket(sFd)"));
otEXPECT_ACTION(setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) != -1,
perror("setsockopt(sFd, SO_REUSEADDR)"));
otEXPECT_ACTION(setsockopt(fd, SOL_SOCKET, SO_REUSEPORT, &one, sizeof(one)) != -1,
perror("setsockopt(sFd, SO_REUSEPORT)"));
otEXPECT_ACTION(bind(fd, (struct sockaddr *)&sockaddr, sizeof(sockaddr)) != -1, perror("bind(sFd)"));
// Fd is successfully initialized.
sFd = fd;
exit:
if (sFd == -1)
{
exit(EXIT_FAILURE);
}
}
static void deinitFds(void)
{
if (sFd != -1)
{
close(sFd);
sFd = -1;
}
}
otError otPlatBleEnable(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return OT_ERROR_NOT_IMPLEMENTED;
initFds();
return OT_ERROR_NONE;
}
otError otPlatBleDisable(otInstance *aInstance)
{
deinitFds();
OT_UNUSED_VARIABLE(aInstance);
return OT_ERROR_NOT_IMPLEMENTED;
return OT_ERROR_NONE;
}
otError otPlatBleGapAdvStart(otInstance *aInstance, uint16_t aInterval)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aInterval);
return OT_ERROR_NOT_IMPLEMENTED;
return OT_ERROR_NONE;
}
otError otPlatBleGapAdvStop(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return OT_ERROR_NOT_IMPLEMENTED;
return OT_ERROR_NONE;
}
otError otPlatBleGapDisconnect(otInstance *aInstance)
{
OT_UNUSED_VARIABLE(aInstance);
return OT_ERROR_NOT_IMPLEMENTED;
return OT_ERROR_NONE;
}
otError otPlatBleGattMtuGet(otInstance *aInstance, uint16_t *aMtu)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aMtu);
return OT_ERROR_NOT_IMPLEMENTED;
*aMtu = PLAT_BLE_MSG_DATA_MAX - 1;
return OT_ERROR_NONE;
}
otError otPlatBleGattServerIndicate(otInstance *aInstance, uint16_t aHandle, const otBleRadioPacket *aPacket)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aHandle);
ssize_t rval;
otError error = OT_ERROR_NONE;
otEXPECT_ACTION(sFd != -1, error = OT_ERROR_INVALID_STATE);
rval = sendto(sFd, (const char *)aPacket->mValue, aPacket->mLength, 0, (struct sockaddr *)&sSockaddr,
sizeof(sSockaddr));
if (rval == -1)
{
perror("BLE simulation sendto failed.");
}
exit:
return error;
}
void platformBleDeinit(void) { deinitFds(); }
void platformBleUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, struct timeval *aTimeout, int *aMaxFd)
{
OT_UNUSED_VARIABLE(aTimeout);
OT_UNUSED_VARIABLE(aWriteFdSet);
if (aReadFdSet != NULL && sFd != -1)
{
FD_SET(sFd, aReadFdSet);
if (aMaxFd != NULL && *aMaxFd < sFd)
{
*aMaxFd = sFd;
}
}
}
void platformBleProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet)
{
OT_UNUSED_VARIABLE(aWriteFdSet);
otEXPECT(sFd != -1);
if (FD_ISSET(sFd, aReadFdSet))
{
socklen_t len = sizeof(sSockaddr);
ssize_t rval;
memset(&sSockaddr, 0, sizeof(sSockaddr));
rval = recvfrom(sFd, sBleBuffer, sizeof(sBleBuffer), 0, (struct sockaddr *)&sSockaddr, &len);
if (rval > 0)
{
otBleRadioPacket myPacket;
myPacket.mValue = sBleBuffer;
myPacket.mLength = (uint16_t)rval;
myPacket.mPower = 0;
otPlatBleGattServerOnWriteRequest(
aInstance, 0,
&myPacket); // TODO consider passing otPlatBleGattServerOnWriteRequest as a callback function
}
else if (rval == 0)
{
// socket is closed, which should not happen
assert(false);
}
else if (errno != EINTR && errno != EAGAIN)
{
perror("recvfrom BLE simulation failed");
exit(EXIT_FAILURE);
}
}
exit:
return;
}
OT_TOOL_WEAK void otPlatBleGattServerOnWriteRequest(otInstance *aInstance,
uint16_t aHandle,
const otBleRadioPacket *aPacket)
{
OT_UNUSED_VARIABLE(aInstance);
OT_UNUSED_VARIABLE(aHandle);
OT_UNUSED_VARIABLE(aPacket);
return OT_ERROR_NOT_IMPLEMENTED;
assert(false);
/* In case of rcp there is a problem with linking to otPlatBleGattServerOnWriteRequest
* which is available in FTD/MTD library.
*/
}
@@ -159,7 +159,7 @@ void platformRadioDeinit(void);
void platformRadioReceive(otInstance *aInstance, uint8_t *aBuf, uint16_t aBufLength);
/**
* Updates the file descriptor sets with file descriptors used by the radio driver.
* Updates the file descriptor sets with file descriptors used by the BLE radio driver.
*
* @param[in,out] aReadFdSet A pointer to the read file descriptors.
* @param[in,out] aWriteFdSet A pointer to the write file descriptors.
@@ -341,4 +341,31 @@ void platformInfraIfProcess(otInstance *aInstance, const fd_set *aReadFdSet, con
#endif // OPENTHREAD_CONFIG_BORDER_ROUTING_ENABLE
/**
* Shuts down the BLE service used by OpenThread.
*
*/
void platformBleDeinit(void);
/**
* Updates the file descriptor sets with file descriptors used by the radio driver.
*
* @param[in,out] aReadFdSet A pointer to the read file descriptors.
* @param[in,out] aWriteFdSet A pointer to the write file descriptors.
* @param[in,out] aTimeout A pointer to the timeout.
* @param[in,out] aMaxFd A pointer to the max file descriptor.
*
*/
void platformBleUpdateFdSet(fd_set *aReadFdSet, fd_set *aWriteFdSet, struct timeval *aTimeout, int *aMaxFd);
/**
* Performs BLE driver processing.
*
* @param[in] aInstance The OpenThread instance structure.
* @param[in] aReadFdSet A pointer to the read file descriptors.
* @param[in] aWriteFdSet A pointer to the write file descriptors.
*
*/
void platformBleProcess(otInstance *aInstance, const fd_set *aReadFdSet, const fd_set *aWriteFdSet);
#endif // PLATFORM_SIMULATION_H_
+7
View File
@@ -296,6 +296,10 @@ void otSysProcessDrivers(otInstance *aInstance)
platformInfraIfUpdateFdSet(&read_fds, &write_fds, &max_fd);
#endif
#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
platformBleUpdateFdSet(&read_fds, &write_fds, &timeout, &max_fd);
#endif
if (otTaskletsArePending(aInstance))
{
timeout.tv_sec = 0;
@@ -308,6 +312,9 @@ void otSysProcessDrivers(otInstance *aInstance)
{
platformUartProcess();
platformRadioProcess(aInstance, &read_fds, &write_fds);
#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
platformBleProcess(aInstance, &read_fds, &write_fds);
#endif
}
else if (errno != EINTR)
{
+1
View File
@@ -109,6 +109,7 @@ OT_POSIX_SIM_COMMON_OPTIONS=(
"-DOT_SRP_CLIENT=ON"
"-DOT_SRP_SERVER=ON"
"-DOT_UPTIME=ON"
"-DOT_BLE_TCAT=ON"
)
readonly OT_POSIX_SIM_COMMON_OPTIONS
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/expect -f
#
# Copyright (c) 2022, The OpenThread Authors.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
# 1. Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of the copyright holder nor the
# names of its contributors may be used to endorse or promote products
# derived from this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
#
source "tests/scripts/expect/_common.exp"
spawn_node 1 "cli"
switch_node 1
send "tcat start\n"
expect_line "Done"
spawn python "tools/tcat_ble_client/bbtc.py" --simulation 1 --cert_path "tools/tcat_ble_client/auth"
set py_client "$spawn_id"
expect_line "Done"
send "commission\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
expect_line "\tVALUE:\t0x00"
send "thread start\n"
expect_line "\tTYPE:\tRESPONSE_W_STATUS"
expect_line "\tVALUE:\t0x00"
send "exit\n"
expect eof
switch_node 1
send "tcat stop\n"
expect_line "Done"
send "networkkey\n"
expect_line "fda7c771a27202e232ecd04cf934f476"
expect_line "Done"
wait_for "state" "leader"
expect_line "Done"
+4 -1
View File
@@ -94,6 +94,7 @@
#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
#define MBEDTLS_SSL_KEEP_PEER_CERTIFICATE
#define MBEDTLS_GCM_C
#endif
#ifdef MBEDTLS_KEY_EXCHANGE_ECDHE_ECDSA_ENABLED
@@ -132,7 +133,9 @@
#define MBEDTLS_MEMORY_BUFFER_ALLOC_C
#endif
#if OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
#if OPENTHREAD_CONFIG_BLE_TCAT_ENABLE
#define MBEDTLS_SSL_MAX_CONTENT_LEN 2000 /**< Maxium fragment length in bytes */
#elif OPENTHREAD_CONFIG_COAP_SECURE_API_ENABLE
#define MBEDTLS_SSL_MAX_CONTENT_LEN 900 /**< Maxium fragment length in bytes */
#else
#define MBEDTLS_SSL_MAX_CONTENT_LEN 768 /**< Maxium fragment length in bytes */
Regular → Executable
+12 -5
View File
@@ -34,6 +34,7 @@ import logging
from ble.ble_connection_constants import BBTC_SERVICE_UUID, BBTC_TX_CHAR_UUID, \
BBTC_RX_CHAR_UUID, SERVER_COMMON_NAME
from ble.ble_stream import BleStream
from ble.udp_stream import UdpStream
from ble.ble_stream_secure import BleStreamSecure
from ble import ble_scanner
from cli.cli import CLI
@@ -47,10 +48,12 @@ async def main():
parser = argparse.ArgumentParser(description='Device parameters')
parser.add_argument('--debug', help='Enable debug logs', action='store_true')
parser.add_argument('--cert_path', help='Path to certificate chain and key', action='store', default='auth')
group = parser.add_mutually_exclusive_group()
group.add_argument('--mac', type=str, help='Device MAC address', action='store')
group.add_argument('--name', type=str, help='Device name', action='store')
group.add_argument('--scan', help='Scan all available devices', action='store_true')
group.add_argument('--simulation', help='Connect to simulation node id', action='store')
args = parser.parse_args()
if args.debug:
@@ -63,12 +66,11 @@ async def main():
if not (device is None):
print(f'Connecting to {device}')
ble_stream = await BleStream.create(device.address, BBTC_SERVICE_UUID, BBTC_TX_CHAR_UUID, BBTC_RX_CHAR_UUID)
ble_sstream = BleStreamSecure(ble_stream)
ble_sstream = BleStreamSecure(device)
ble_sstream.load_cert(
certfile=path.join('auth', 'commissioner_cert.pem'),
keyfile=path.join('auth', 'commissioner_key.pem'),
cafile=path.join('auth', 'ca_cert.pem'),
certfile=path.join(args.cert_path, 'commissioner_cert.pem'),
keyfile=path.join(args.cert_path, 'commissioner_key.pem'),
cafile=path.join(args.cert_path, 'ca_cert.pem'),
)
print('Setting up secure channel...')
@@ -96,11 +98,16 @@ async def get_device_by_args(args):
device = None
if args.mac:
device = await ble_scanner.find_first_by_mac(args.mac)
device = await BleStream.create(device.address, BBTC_SERVICE_UUID, BBTC_TX_CHAR_UUID, BBTC_RX_CHAR_UUID)
elif args.name:
device = await ble_scanner.find_first_by_name(args.name)
device = await BleStream.create(device.address, BBTC_SERVICE_UUID, BBTC_TX_CHAR_UUID, BBTC_RX_CHAR_UUID)
elif args.scan:
tcat_devices = await ble_scanner.scan_tcat_devices()
device = select_device_by_user_input(tcat_devices)
device = await BleStream.create(device.address, BBTC_SERVICE_UUID, BBTC_TX_CHAR_UUID, BBTC_RX_CHAR_UUID)
elif args.simulation:
device = UdpStream("127.0.0.1", int(args.simulation))
return device
+11 -13
View File
@@ -30,15 +30,13 @@ import asyncio
import ssl
import logging
from .ble_stream import BleStream
logger = logging.getLogger(__name__)
class BleStreamSecure:
def __init__(self, ble_stream: BleStream):
self.ble_stream = ble_stream
def __init__(self, stream):
self.stream = stream
self.ssl_context = ssl.create_default_context(ssl.Purpose.SERVER_AUTH)
self.incoming = ssl.MemoryBIO()
self.outgoing = ssl.MemoryBIO()
@@ -67,12 +65,12 @@ class BleStreamSecure:
# SSLWantWrite means ssl wants to send data over the link,
# but might need a receive first
except ssl.SSLWantWriteError:
output = await self.ble_stream.recv(4096)
output = await self.stream.recv(4096)
if output:
self.incoming.write(output)
data = self.outgoing.read()
if data:
await self.ble_stream.send(data)
await self.stream.send(data)
await asyncio.sleep(0.1)
# SSLWantRead means ssl wants to receive data from the link,
@@ -80,8 +78,8 @@ class BleStreamSecure:
except ssl.SSLWantReadError:
data = self.outgoing.read()
if data:
await self.ble_stream.send(data)
output = await self.ble_stream.recv(4096)
await self.stream.send(data)
output = await self.stream.recv(4096)
if output:
self.incoming.write(output)
await asyncio.sleep(0.1)
@@ -89,14 +87,14 @@ class BleStreamSecure:
async def send(self, bytes):
self.ssl_object.write(bytes)
encode = self.outgoing.read(4096)
await self.ble_stream.send(encode)
await self.stream.send(encode)
async def recv(self, buffersize, timeout=1):
end_time = asyncio.get_event_loop().time() + timeout
data = await self.ble_stream.recv(buffersize)
data = await self.stream.recv(buffersize)
while not data and asyncio.get_event_loop().time() < end_time:
await asyncio.sleep(0.1)
data = await self.ble_stream.recv(buffersize)
data = await self.stream.recv(buffersize)
if not data:
logger.warning('No response when response expected.')
return b''
@@ -108,10 +106,10 @@ class BleStreamSecure:
break
# if recv called before entire message was received from the link
except ssl.SSLWantReadError:
more = await self.ble_stream.recv(buffersize)
more = await self.stream.recv(buffersize)
while not more:
await asyncio.sleep(0.1)
more = await self.ble_stream.recv(buffersize)
more = await self.stream.recv(buffersize)
self.incoming.write(more)
return decode
+56
View File
@@ -0,0 +1,56 @@
"""
Copyright (c) 2024, The OpenThread Authors.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
3. Neither the name of the copyright holder nor the
names of its contributors may be used to endorse or promote products
derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
"""
from itertools import count, takewhile
from typing import Iterator
import logging
import time
from asyncio import sleep
import socket
logger = logging.getLogger(__name__)
class UdpStream:
BASE_PORT = 10000
def __init__(self, address, node_id):
self.__receive_buffer = b''
self.__last_recv_time = None
self.socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
self.address = (address, self.BASE_PORT + node_id)
async def send(self, data):
logger.debug(f'sending {data}')
self.socket.sendto(data, self.address)
return len(data)
async def recv(self, bufsize):
message = self.socket.recv(bufsize)
logger.debug(f'retrieved {message}')
return message