test: lwm2m: Add tests for Block-Wise transfers

Block-Wise GET, PUT and SEND

Signed-off-by: Seppo Takalo <seppo.takalo@nordicsemi.no>
This commit is contained in:
Seppo Takalo 2024-05-10 17:34:48 +03:00 committed by Alberto Escolar
commit a9e91af375
7 changed files with 257 additions and 7 deletions

View file

@ -523,6 +523,7 @@ config LWM2M_SECONDS_TO_UPDATE_EARLY
config LWM2M_SHELL config LWM2M_SHELL
bool "LwM2M shell utilities" bool "LwM2M shell utilities"
select SHELL select SHELL
select CRC
help help
Activate shell module that provides LwM2M commands like Activate shell module that provides LwM2M commands like
send to the console. send to the console.

View file

@ -17,6 +17,7 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#include <zephyr/kernel.h> #include <zephyr/kernel.h>
#include <zephyr/net/lwm2m.h> #include <zephyr/net/lwm2m.h>
#include <zephyr/shell/shell.h> #include <zephyr/shell/shell.h>
#include <zephyr/sys/crc.h>
#include <lwm2m_engine.h> #include <lwm2m_engine.h>
#include <lwm2m_util.h> #include <lwm2m_util.h>
@ -31,7 +32,8 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME);
"-uX\tRead value as uintX_t\n" \ "-uX\tRead value as uintX_t\n" \
"-sX\tRead value as intX_t\n" \ "-sX\tRead value as intX_t\n" \
"-f \tRead value as float\n" \ "-f \tRead value as float\n" \
"-t \tRead value as time_t\n" "-t \tRead value as time_t\n" \
"-crc32\tCalculate CRC32 of the content\n"
#define LWM2M_HELP_WRITE "write PATH [OPTIONS] VALUE\nWrite into LwM2M resource\n" \ #define LWM2M_HELP_WRITE "write PATH [OPTIONS] VALUE\nWrite into LwM2M resource\n" \
"-s \tWrite value as string (default)\n" \ "-s \tWrite value as string (default)\n" \
"-b \tWrite value as bool\n" \ "-b \tWrite value as bool\n" \
@ -180,6 +182,19 @@ static int cmd_read(const struct shell *sh, size_t argc, char **argv)
goto out; goto out;
} }
shell_hexdump(sh, buff, buff_len); shell_hexdump(sh, buff, buff_len);
} else if (strcmp(dtype, "-crc32") == 0) {
const char *buff;
uint16_t buff_len = 0;
ret = lwm2m_get_res_buf(&path, (void **)&buff,
NULL, &buff_len, NULL);
if (ret != 0) {
goto out;
}
uint32_t crc = crc32_ieee(buff, buff_len);
shell_print(sh, "%u", crc);
} else if (strcmp(dtype, "-s") == 0) { } else if (strcmp(dtype, "-s") == 0) {
const char *buff; const char *buff;
uint16_t buff_len = 0; uint16_t buff_len = 0;

View file

@ -21,8 +21,11 @@ CONFIG_LWM2M_SHELL=y
CONFIG_LWM2M_TICKLESS=y CONFIG_LWM2M_TICKLESS=y
CONFIG_NET_SOCKETPAIR=y CONFIG_NET_SOCKETPAIR=y
#Enable Portfolio object #Enable test objects
CONFIG_LWM2M_PORTFOLIO_OBJ_SUPPORT=y CONFIG_LWM2M_PORTFOLIO_OBJ_SUPPORT=y
CONFIG_LWM2M_BINARYAPPDATA_OBJ_SUPPORT=y
CONFIG_LWM2M_FIRMWARE_UPDATE_OBJ_SUPPORT=y
CONFIG_LWM2M_FIRMWARE_UPDATE_PULL_SUPPORT=y
#LwM2M v1.1 configure #LwM2M v1.1 configure
CONFIG_LWM2M_VERSION_1_1=y CONFIG_LWM2M_VERSION_1_1=y
@ -36,7 +39,7 @@ CONFIG_LWM2M_RW_SENML_JSON_SUPPORT=y
#Enable SenML CBOR content format #Enable SenML CBOR content format
CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT=y CONFIG_LWM2M_RW_SENML_CBOR_SUPPORT=y
CONFIG_LWM2M_RW_SENML_CBOR_RECORDS=40 CONFIG_LWM2M_RW_SENML_CBOR_RECORDS=60
CONFIG_ZCBOR_CANONICAL=y CONFIG_ZCBOR_CANONICAL=y
#Enable legacy content formats #Enable legacy content formats
@ -90,11 +93,11 @@ CONFIG_MBEDTLS_SSL_DTLS_CONNECTION_ID=y
# MTU - IPv6 header - UDP header - DTLS header - CoAP header room # MTU - IPv6 header - UDP header - DTLS header - CoAP header room
# 1280 - 40 - 8 - 21 - 48 # 1280 - 40 - 8 - 21 - 48
CONFIG_LWM2M_COAP_MAX_MSG_SIZE=1163 CONFIG_LWM2M_COAP_MAX_MSG_SIZE=1163
CONFIG_LWM2M_COAP_BLOCK_SIZE=1024 CONFIG_LWM2M_COAP_BLOCK_SIZE=512
CONFIG_LWM2M_COAP_BLOCK_TRANSFER=y CONFIG_LWM2M_COAP_BLOCK_TRANSFER=y
CONFIG_LWM2M_COAP_ENCODE_BUFFER_SIZE=4096 CONFIG_LWM2M_COAP_ENCODE_BUFFER_SIZE=8192
CONFIG_LWM2M_NUM_OUTPUT_BLOCK_CONTEXT=1 CONFIG_LWM2M_NUM_OUTPUT_BLOCK_CONTEXT=2
CONFIG_LWM2M_NUM_BLOCK1_CONTEXT=1 CONFIG_LWM2M_NUM_BLOCK1_CONTEXT=2
CONFIG_SYS_HASH_FUNC32=y CONFIG_SYS_HASH_FUNC32=y
CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE=0 CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE=0
CONFIG_LWM2M_ENGINE_MAX_PENDING=2 CONFIG_LWM2M_ENGINE_MAX_PENDING=2

View file

@ -156,6 +156,8 @@ class Leshan:
return 'integer' return 'integer'
if isinstance(value, datetime): if isinstance(value, datetime):
return 'time' return 'time'
if isinstance(value, bytes):
return 'opaque'
return 'string' return 'string'
@classmethod @classmethod
@ -163,6 +165,8 @@ class Leshan:
"""Wrapper for special types that are not understood by Json""" """Wrapper for special types that are not understood by Json"""
if isinstance(value, datetime): if isinstance(value, datetime):
return int(value.timestamp()) return int(value.timestamp())
elif isinstance(value, bytes):
return binascii.b2a_hex(value).decode()
else: else:
return value return value

View file

@ -0,0 +1,133 @@
"""
Tests for Block-Wise transfers in LwM2M
#######################################
Copyright (c) 2024 Nordic Semiconductor ASA
SPDX-License-Identifier: Apache-2.0
"""
import time
import logging
import zlib
import re
import random
import string
import binascii
from leshan import Leshan
from twister_harness import Shell
from twister_harness import DeviceAdapter
logger = logging.getLogger(__name__)
def test_blockwise_1(shell: Shell, dut: DeviceAdapter, leshan: Leshan, endpoint: str):
"""Blockwise test 1: Block-Wise PUT using OPAQUE content format"""
fw = b'1234567890' * 500
fmt = leshan.format
to = leshan.timeout
leshan.format = 'OPAQUE'
leshan.timeout = 600
leshan.write(endpoint, '5/0/0', fw)
# Our Firmware object prints out the CRC of the received firmware
# when Update is executed
leshan.execute(endpoint, '5/0/2')
lines = dut.readlines_until(regex='app_fw_update: UPDATE', timeout=5.0)
assert len(lines) > 0
line = lines[-1]
crc = int(re.search('CRC ([0-9]+)', line).group(1))
# Verify that CRC matches
assert crc == zlib.crc32(fw)
leshan.format = fmt
leshan.timeout = to
def test_blockwise_2(shell: Shell, dut: DeviceAdapter, leshan: Leshan, endpoint: str):
"""Blockwise test 2: Block-Wise PUT with retry"""
fw = b'1234567890' * 500
fmt = leshan.format
to = leshan.timeout
leshan.format = 'OPAQUE'
# Set timeout to 1 second to force Leshan to stop sending
leshan.timeout = 1
try:
leshan.write(endpoint, '5/0/0', fw)
except Exception as e:
logger.debug(f'Caught exception: {e}')
shell.exec_command('lwm2m update')
time.sleep(1)
# Now send the firmware again using longer timeout
leshan.timeout = 600
leshan.write(endpoint, '5/0/0', fw)
# Our Firmware object prints out the CRC of the received firmware
# when Update is executed
leshan.execute(endpoint, '5/0/2')
lines = dut.readlines_until(regex='app_fw_update: UPDATE', timeout=5.0)
assert len(lines) > 0
line = lines[-1]
crc = int(re.search('CRC ([0-9]+)', line).group(1))
# Verify that CRC matches
assert crc == zlib.crc32(fw)
leshan.format = fmt
leshan.timeout = to
def test_blockwise_3(shell: Shell, dut: DeviceAdapter, leshan: Leshan, endpoint: str):
"""Blockwise test 3: Block-Wise Get using TLV and SenML-CBOR content formats"""
shell.exec_command('lwm2m create /19/0')
# Generate 4 kB of binary app-data
# and write it into BinaryAppData object
data = ''.join(random.choice(string.ascii_letters) for i in range(4096)).encode()
fmt = leshan.format
to = leshan.timeout
leshan.format = 'OPAQUE'
leshan.timeout = 600
leshan.write(endpoint, '19/0/0/0', data)
# Verify data is correctly transferred
lines = shell.get_filtered_output(shell.exec_command('lwm2m read /19/0/0/0 -crc32'))
crc = int(lines[0])
assert crc == zlib.crc32(data)
# Try reading the data using different content formats
for fmt in ['TLV', 'SENML_CBOR']:
leshan.format = fmt
data = leshan.read(endpoint, '19/0/0')
data = binascii.a2b_hex(data[0][0])
assert crc == zlib.crc32(data)
leshan.format = fmt
leshan.timeout = to
shell.exec_command('lwm2m delete /19/0')
def test_blockwise_4(shell: Shell, dut: DeviceAdapter, leshan: Leshan, endpoint: str):
"""Blockwise test 4: Block-Wise SEND using SenML-CBOR content format"""
shell.exec_command('lwm2m create /19/0')
# Generate 4 kB of binary app-data
data = ''.join(random.choice(string.ascii_letters) for i in range(4096)).encode()
fmt = leshan.format
to = leshan.timeout
leshan.format = 'OPAQUE'
leshan.timeout = 600
leshan.write(endpoint, '19/0/0/0', data)
leshan.format = 'SENML_CBOR'
# Execute SEND and verify that correct data is received
with leshan.get_event_stream(endpoint) as events:
shell.exec_command('lwm2m send /19/0')
dut.readlines_until(regex=r'.*SEND status: 0', timeout=5.0)
send = events.next_event('SEND')
assert send is not None
content = binascii.a2b_hex(send[19][0][0][0])
assert zlib.crc32(content) == zlib.crc32(data)
leshan.format = fmt
leshan.timeout = to
shell.exec_command('lwm2m delete /19/0')

View file

@ -0,0 +1,80 @@
/*
* Copyright (c) 2024 Nordic Semiconductor
*
* SPDX-License-Identifier: Apache-2.0
*/
#define LOG_MODULE_NAME app_fw_update
#define LOG_LEVEL LOG_LEVEL_DBG
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(LOG_MODULE_NAME);
#include <zephyr/net/lwm2m.h>
#include <zephyr/sys/crc.h>
#include "lwm2m_engine.h"
static uint8_t firmware_buf[64];
static uint32_t crc;
/* Array with supported PULL firmware update protocols */
static uint8_t supported_protocol[1];
static int firmware_update_cb(uint16_t obj_inst_id,
uint8_t *args, uint16_t args_len)
{
LOG_INF("UPDATE, (CRC %u)", crc);
lwm2m_set_u8(&LWM2M_OBJ(5, 0, 3), STATE_IDLE);
lwm2m_set_u8(&LWM2M_OBJ(5, 0, 5), RESULT_SUCCESS);
return 0;
}
static void *firmware_get_buf(uint16_t obj_inst_id, uint16_t res_id,
uint16_t res_inst_id, size_t *data_len)
{
*data_len = sizeof(firmware_buf);
return firmware_buf;
}
static int firmware_block_received_cb(uint16_t obj_inst_id, uint16_t res_id,
uint16_t res_inst_id, uint8_t *data,
uint16_t data_len, bool last_block,
size_t total_size, size_t offset)
{
if (offset == 0) {
crc = crc32_ieee(data, data_len);
} else {
crc = crc32_ieee_update(crc, data, data_len);
}
LOG_INF("FIRMWARE: BLOCK RECEIVED: offset:%zd len:%u last_block:%d crc: %u",
offset, data_len, last_block, crc);
/* Add extra delay so short block-wise may timeout */
k_sleep(K_MSEC(100));
return 0;
}
static int firmware_cancel_cb(const uint16_t obj_inst_id)
{
LOG_INF("FIRMWARE: Update canceled");
return 0;
}
static int init_firmware_update(void)
{
/* setup data buffer for block-wise transfer */
lwm2m_register_pre_write_callback(&LWM2M_OBJ(5, 0, 0), firmware_get_buf);
lwm2m_firmware_set_write_cb(firmware_block_received_cb);
/* register cancel callback */
lwm2m_firmware_set_cancel_cb(firmware_cancel_cb);
lwm2m_firmware_set_update_cb(firmware_update_cb);
lwm2m_create_res_inst(&LWM2M_OBJ(5, 0, 8, 0));
lwm2m_set_res_buf(&LWM2M_OBJ(5, 0, 8, 0), &supported_protocol[0],
sizeof(supported_protocol[0]),
sizeof(supported_protocol[0]), 0);
return 0;
}
LWM2M_APP_INIT(init_firmware_update);

View file

@ -88,6 +88,18 @@ int set_socketoptions(struct lwm2m_ctx *ctx)
return lwm2m_set_default_sockopt(ctx); return lwm2m_set_default_sockopt(ctx);
} }
static int create_appdata(uint16_t obj_inst_id)
{
/* Create BinaryAppData object */
static uint8_t data[4096];
static char description[16];
lwm2m_set_res_buf(&LWM2M_OBJ(19, 0, 0, 0), data, sizeof(data), 0, 0);
lwm2m_set_res_buf(&LWM2M_OBJ(19, 0, 3), description, sizeof(description), 0, 0);
return 0;
}
static int lwm2m_setup(void) static int lwm2m_setup(void)
{ {
/* setup DEVICE object */ /* setup DEVICE object */
@ -118,6 +130,8 @@ static int lwm2m_setup(void)
lwm2m_create_res_inst(&LWM2M_OBJ(3, 0, 8, 1)); lwm2m_create_res_inst(&LWM2M_OBJ(3, 0, 8, 1));
lwm2m_set_res_buf(&LWM2M_OBJ(3, 0, 8, 1), &usb_ma, sizeof(usb_ma), sizeof(usb_ma), 0); lwm2m_set_res_buf(&LWM2M_OBJ(3, 0, 8, 1), &usb_ma, sizeof(usb_ma), sizeof(usb_ma), 0);
lwm2m_register_create_callback(19, create_appdata);
return 0; return 0;
} }