diff --git a/subsys/net/lib/lwm2m/Kconfig b/subsys/net/lib/lwm2m/Kconfig index d8e0b6fc61f..4fdd7228f70 100644 --- a/subsys/net/lib/lwm2m/Kconfig +++ b/subsys/net/lib/lwm2m/Kconfig @@ -523,6 +523,7 @@ config LWM2M_SECONDS_TO_UPDATE_EARLY config LWM2M_SHELL bool "LwM2M shell utilities" select SHELL + select CRC help Activate shell module that provides LwM2M commands like send to the console. diff --git a/subsys/net/lib/lwm2m/lwm2m_shell.c b/subsys/net/lib/lwm2m/lwm2m_shell.c index 5f4d3e20269..153423f6402 100644 --- a/subsys/net/lib/lwm2m/lwm2m_shell.c +++ b/subsys/net/lib/lwm2m/lwm2m_shell.c @@ -17,6 +17,7 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME); #include #include #include +#include #include #include @@ -31,7 +32,8 @@ LOG_MODULE_REGISTER(LOG_MODULE_NAME); "-uX\tRead value as uintX_t\n" \ "-sX\tRead value as intX_t\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" \ "-s \tWrite value as string (default)\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; } 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) { const char *buff; uint16_t buff_len = 0; diff --git a/tests/net/lib/lwm2m/interop/prj.conf b/tests/net/lib/lwm2m/interop/prj.conf index 6115e6f40e3..39d998db6f6 100644 --- a/tests/net/lib/lwm2m/interop/prj.conf +++ b/tests/net/lib/lwm2m/interop/prj.conf @@ -21,8 +21,11 @@ CONFIG_LWM2M_SHELL=y CONFIG_LWM2M_TICKLESS=y CONFIG_NET_SOCKETPAIR=y -#Enable Portfolio object +#Enable test objects 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 CONFIG_LWM2M_VERSION_1_1=y @@ -36,7 +39,7 @@ CONFIG_LWM2M_RW_SENML_JSON_SUPPORT=y #Enable SenML CBOR content format 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 #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 # 1280 - 40 - 8 - 21 - 48 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_ENCODE_BUFFER_SIZE=4096 -CONFIG_LWM2M_NUM_OUTPUT_BLOCK_CONTEXT=1 -CONFIG_LWM2M_NUM_BLOCK1_CONTEXT=1 +CONFIG_LWM2M_COAP_ENCODE_BUFFER_SIZE=8192 +CONFIG_LWM2M_NUM_OUTPUT_BLOCK_CONTEXT=2 +CONFIG_LWM2M_NUM_BLOCK1_CONTEXT=2 CONFIG_SYS_HASH_FUNC32=y CONFIG_LWM2M_ENGINE_VALIDATION_BUFFER_SIZE=0 CONFIG_LWM2M_ENGINE_MAX_PENDING=2 diff --git a/tests/net/lib/lwm2m/interop/pytest/leshan.py b/tests/net/lib/lwm2m/interop/pytest/leshan.py index 181f15ebf61..198c3fc2880 100644 --- a/tests/net/lib/lwm2m/interop/pytest/leshan.py +++ b/tests/net/lib/lwm2m/interop/pytest/leshan.py @@ -156,6 +156,8 @@ class Leshan: return 'integer' if isinstance(value, datetime): return 'time' + if isinstance(value, bytes): + return 'opaque' return 'string' @classmethod @@ -163,6 +165,8 @@ class Leshan: """Wrapper for special types that are not understood by Json""" if isinstance(value, datetime): return int(value.timestamp()) + elif isinstance(value, bytes): + return binascii.b2a_hex(value).decode() else: return value diff --git a/tests/net/lib/lwm2m/interop/pytest/test_blockwise.py b/tests/net/lib/lwm2m/interop/pytest/test_blockwise.py new file mode 100644 index 00000000000..27bece69f1e --- /dev/null +++ b/tests/net/lib/lwm2m/interop/pytest/test_blockwise.py @@ -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') diff --git a/tests/net/lib/lwm2m/interop/src/firmware_update.c b/tests/net/lib/lwm2m/interop/src/firmware_update.c new file mode 100644 index 00000000000..7ed44093dbd --- /dev/null +++ b/tests/net/lib/lwm2m/interop/src/firmware_update.c @@ -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 +LOG_MODULE_REGISTER(LOG_MODULE_NAME); + +#include +#include +#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); diff --git a/tests/net/lib/lwm2m/interop/src/lwm2m-client.c b/tests/net/lib/lwm2m/interop/src/lwm2m-client.c index cb4ab22acd2..bc668c15c3c 100644 --- a/tests/net/lib/lwm2m/interop/src/lwm2m-client.c +++ b/tests/net/lib/lwm2m/interop/src/lwm2m-client.c @@ -88,6 +88,18 @@ int set_socketoptions(struct lwm2m_ctx *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) { /* setup DEVICE object */ @@ -118,6 +130,8 @@ static int lwm2m_setup(void) 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_register_create_callback(19, create_appdata); + return 0; }