tests: subsys: lorawan: add frag_decoder unit-test
The unit test allows to test the frag decoder algorithms using random binary data. The coded fragments are created on the fly using the encoder algorithm described by Semtech in the LoRaWAN TS004-1.0.0 document. Signed-off-by: Martin Jäger <martin@libre.solar>
This commit is contained in:
parent
f3e41c6a25
commit
9e341b49c8
8 changed files with 372 additions and 0 deletions
9
tests/subsys/lorawan/frag_decoder/CMakeLists.txt
Normal file
9
tests/subsys/lorawan/frag_decoder/CMakeLists.txt
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
cmake_minimum_required(VERSION 3.20.0)
|
||||||
|
|
||||||
|
find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
|
||||||
|
project(lorawan_frag_decoder_test)
|
||||||
|
|
||||||
|
FILE(GLOB app_sources src/*.c)
|
||||||
|
target_sources(app PRIVATE ${app_sources})
|
4
tests/subsys/lorawan/frag_decoder/boards/native_sim.conf
Normal file
4
tests/subsys/lorawan/frag_decoder/boards/native_sim.conf
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
# Turn off log messages for failed communication with non-existing LoRa PHY
|
||||||
|
CONFIG_LORA_LOG_LEVEL_OFF=y
|
50
tests/subsys/lorawan/frag_decoder/boards/native_sim.overlay
Normal file
50
tests/subsys/lorawan/frag_decoder/boards/native_sim.overlay
Normal file
|
@ -0,0 +1,50 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 A Labs GmbH
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*
|
||||||
|
* This overlay defines a fake LoRa PHY node which is required to build the driver.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/dt-bindings/lora/sx126x.h>
|
||||||
|
|
||||||
|
/ {
|
||||||
|
chosen {
|
||||||
|
zephyr,code-partition = &slot0_partition;
|
||||||
|
};
|
||||||
|
|
||||||
|
aliases {
|
||||||
|
lora0 = &lora;
|
||||||
|
};
|
||||||
|
|
||||||
|
test {
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <1>;
|
||||||
|
|
||||||
|
test_spi: spi@33334444 {
|
||||||
|
#address-cells = <1>;
|
||||||
|
#size-cells = <0>;
|
||||||
|
compatible = "vnd,spi";
|
||||||
|
reg = <0x33334444 0x1000>;
|
||||||
|
status = "okay";
|
||||||
|
clock-frequency = <2000000>;
|
||||||
|
|
||||||
|
cs-gpios = <&gpio0 1 GPIO_ACTIVE_LOW>;
|
||||||
|
|
||||||
|
lora: lora@0 {
|
||||||
|
compatible = "semtech,sx1262";
|
||||||
|
status = "okay";
|
||||||
|
reg = <0>;
|
||||||
|
reset-gpios = <&gpio0 2 GPIO_ACTIVE_LOW>;
|
||||||
|
busy-gpios = <&gpio0 3 GPIO_ACTIVE_HIGH>;
|
||||||
|
tx-enable-gpios = <&gpio0 4 GPIO_ACTIVE_LOW>;
|
||||||
|
rx-enable-gpios = <&gpio0 5 GPIO_ACTIVE_LOW>;
|
||||||
|
dio1-gpios = <&gpio0 5 GPIO_ACTIVE_HIGH>;
|
||||||
|
dio2-tx-enable;
|
||||||
|
dio3-tcxo-voltage = <SX126X_DIO3_TCXO_3V3>;
|
||||||
|
tcxo-power-startup-delay-ms = <5>;
|
||||||
|
spi-max-frequency = <1000000>;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
33
tests/subsys/lorawan/frag_decoder/prj.conf
Normal file
33
tests/subsys/lorawan/frag_decoder/prj.conf
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
# SPDX-License-Identifier: Apache-2.0
|
||||||
|
|
||||||
|
CONFIG_ZTEST=y
|
||||||
|
|
||||||
|
# General Zephyr settings
|
||||||
|
CONFIG_MAIN_STACK_SIZE=2048
|
||||||
|
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=2048
|
||||||
|
CONFIG_THREAD_NAME=y
|
||||||
|
CONFIG_LOG=y
|
||||||
|
|
||||||
|
# LoRa PHY and required peripherals
|
||||||
|
CONFIG_LORA=y
|
||||||
|
CONFIG_SPI=y
|
||||||
|
CONFIG_GPIO=y
|
||||||
|
|
||||||
|
# Random number generator required for several LoRaWAN services
|
||||||
|
CONFIG_ENTROPY_GENERATOR=y
|
||||||
|
|
||||||
|
# LoRaWAN application layer
|
||||||
|
CONFIG_LORAWAN=y
|
||||||
|
CONFIG_LORAWAN_EMUL=y
|
||||||
|
CONFIG_LORAMAC_REGION_EU868=y
|
||||||
|
|
||||||
|
# LoRaWAN services required for this test
|
||||||
|
CONFIG_LORAWAN_SERVICES=y
|
||||||
|
CONFIG_LORAWAN_FRAG_TRANSPORT=y
|
||||||
|
|
||||||
|
# Flash driver to store firmware image
|
||||||
|
CONFIG_FLASH=y
|
||||||
|
CONFIG_FLASH_MAP=y
|
||||||
|
CONFIG_FLASH_PAGE_LAYOUT=y
|
||||||
|
CONFIG_STREAM_FLASH=y
|
||||||
|
CONFIG_IMG_MANAGER=y
|
113
tests/subsys/lorawan/frag_decoder/src/frag_encoder.c
Normal file
113
tests/subsys/lorawan/frag_decoder/src/frag_encoder.c
Normal file
|
@ -0,0 +1,113 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 A Labs GmbH
|
||||||
|
* Copyright (c) 2024 tado GmbH
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Implementation of the fragment encoding algorithm described in the LoRaWAN TS004-1.0.0.
|
||||||
|
* https://lora-alliance.org/wp-content/uploads/2020/11/fragmented_data_block_transport_v1.0.0.pdf
|
||||||
|
*
|
||||||
|
* Note: This algorithm is not compatible with TS004-2.0.0, which has some subtle differences
|
||||||
|
* in the parity matrix generation.
|
||||||
|
*
|
||||||
|
* Variable naming according to LoRaWAN specification:
|
||||||
|
*
|
||||||
|
* M: Number of uncoded fragments (original data)
|
||||||
|
* N: Number of coded fragments (including the original data at the beginning)
|
||||||
|
* CR: Coding ratio M/N
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "frag_encoder.h"
|
||||||
|
|
||||||
|
#include <zephyr/sys/util.h>
|
||||||
|
#include <zephyr/logging/log.h>
|
||||||
|
|
||||||
|
LOG_MODULE_REGISTER(lorawan_frag_enc, CONFIG_LORAWAN_SERVICES_LOG_LEVEL);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate a 23bit Pseudorandom Binary Sequence (PRBS)
|
||||||
|
*
|
||||||
|
* @param seed Seed input value
|
||||||
|
*
|
||||||
|
* @returns Pseudorandom output value
|
||||||
|
*/
|
||||||
|
static int32_t prbs23(int32_t seed)
|
||||||
|
{
|
||||||
|
int32_t b0 = seed & 1;
|
||||||
|
int32_t b1 = (seed & 32) / 32;
|
||||||
|
|
||||||
|
return (seed / 2) + ((b0 ^ b1) << 22);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate vector for coded fragment n of the MxN parity matrix
|
||||||
|
*
|
||||||
|
* @param m Total number of uncoded fragments (M)
|
||||||
|
* @param n Coded fragment number (starting at 1 and not 0)
|
||||||
|
* @param vec Output vector (buffer size must be greater than m)
|
||||||
|
*/
|
||||||
|
void lorawan_fec_parity_matrix_vector(int m, int n, uint8_t *vec)
|
||||||
|
{
|
||||||
|
int mm, x, r;
|
||||||
|
|
||||||
|
memset(vec, 0, m);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Powers of 2 must be treated differently to make sure matrix content is close
|
||||||
|
* to random. Powers of 2 tend to generate patterns.
|
||||||
|
*/
|
||||||
|
if (is_power_of_two(m)) {
|
||||||
|
mm = m + 1;
|
||||||
|
} else {
|
||||||
|
mm = m;
|
||||||
|
}
|
||||||
|
|
||||||
|
x = 1 + (1001 * n);
|
||||||
|
|
||||||
|
for (int nb_coeff = 0; nb_coeff < (m / 2); nb_coeff++) {
|
||||||
|
r = (1 << 16);
|
||||||
|
while (r >= m) {
|
||||||
|
x = prbs23(x);
|
||||||
|
r = x % mm;
|
||||||
|
}
|
||||||
|
vec[r] = 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int lorawan_frag_encoder(const uint8_t *uncoded, size_t uncoded_len, uint8_t *coded,
|
||||||
|
size_t coded_size, size_t frag_size, unsigned int redundant_frags)
|
||||||
|
{
|
||||||
|
int uncoded_frags = DIV_ROUND_UP(uncoded_len, frag_size);
|
||||||
|
int coded_frags = uncoded_frags + redundant_frags;
|
||||||
|
uint8_t parity_vec[frag_size];
|
||||||
|
|
||||||
|
memset(parity_vec, 0, sizeof(parity_vec));
|
||||||
|
|
||||||
|
if (coded_size < coded_frags * frag_size) {
|
||||||
|
LOG_ERR("output buffer not large enough");
|
||||||
|
return -EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* copy uncoded frags to the beginning of coded fragments and pad with zeros */
|
||||||
|
memcpy(coded, uncoded, uncoded_len);
|
||||||
|
memset(coded + uncoded_len, 0, uncoded_frags * frag_size - uncoded_len);
|
||||||
|
|
||||||
|
/* generate remaining coded (redundant) frags */
|
||||||
|
for (int i = 1; i <= redundant_frags; i++) {
|
||||||
|
lorawan_fec_parity_matrix_vector(uncoded_frags, i, parity_vec);
|
||||||
|
|
||||||
|
uint8_t *out = coded + (uncoded_frags + i - 1) * frag_size;
|
||||||
|
|
||||||
|
for (int j = 0; j < uncoded_frags; j++) {
|
||||||
|
if (parity_vec[j] == 1) {
|
||||||
|
for (int m = 0; m < frag_size; m++) {
|
||||||
|
out[m] ^= coded[j * frag_size + m];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
29
tests/subsys/lorawan/frag_decoder/src/frag_encoder.h
Normal file
29
tests/subsys/lorawan/frag_decoder/src/frag_encoder.h
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 A Labs GmbH
|
||||||
|
* Copyright (c) 2024 tado GmbH
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef TEST_SUBSYS_LORAWAN_FRAG_DECODER_SRC_FRAG_ENCODER_H_
|
||||||
|
#define TEST_SUBSYS_LORAWAN_FRAG_DECODER_SRC_FRAG_ENCODER_H_
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate coded binary data according to LoRaWAN TS004-1.0.0
|
||||||
|
*
|
||||||
|
* @param uncoded Pointer to uncoded data buffer (e.g. firmware binary)
|
||||||
|
* @param uncoded_len Length of uncoded data in bytes
|
||||||
|
* @param coded Pointer to buffer for resulting coded data
|
||||||
|
* @param coded_size Size of the buffer for coded data
|
||||||
|
* @param frag_size Fragment size to be used
|
||||||
|
* @param redundant_frags Absolute number of redundant fragments to be generated
|
||||||
|
*
|
||||||
|
* @returns 0 for success or negative error code otherwise.
|
||||||
|
*/
|
||||||
|
int lorawan_frag_encoder(const uint8_t *uncoded, size_t uncoded_len, uint8_t *coded,
|
||||||
|
size_t coded_size, size_t frag_size, unsigned int redundant_frags);
|
||||||
|
|
||||||
|
#endif /* TEST_SUBSYS_LORAWAN_FRAG_DECODER_SRC_FRAG_ENCODER_H_ */
|
127
tests/subsys/lorawan/frag_decoder/src/main.c
Normal file
127
tests/subsys/lorawan/frag_decoder/src/main.c
Normal file
|
@ -0,0 +1,127 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2024 A Labs GmbH
|
||||||
|
* Copyright (c) 2024 tado GmbH
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: Apache-2.0
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <zephyr/device.h>
|
||||||
|
#include <zephyr/kernel.h>
|
||||||
|
#include <zephyr/lorawan/lorawan.h>
|
||||||
|
#include <zephyr/lorawan/emul.h>
|
||||||
|
#include <zephyr/random/random.h>
|
||||||
|
#include <zephyr/sys/util.h>
|
||||||
|
#include <zephyr/storage/flash_map.h>
|
||||||
|
#include <zephyr/ztest.h>
|
||||||
|
|
||||||
|
#include "frag_encoder.h"
|
||||||
|
|
||||||
|
#define FRAG_SIZE CONFIG_LORAWAN_FRAG_TRANSPORT_MAX_FRAG_SIZE
|
||||||
|
#define FIRMWARE_SIZE (FRAG_SIZE * 100 + 1) /* not divisible by frag size to test padding */
|
||||||
|
#define UNCODED_FRAGS (DIV_ROUND_UP(FIRMWARE_SIZE, FRAG_SIZE))
|
||||||
|
#define REDUNDANT_FRAGS \
|
||||||
|
(DIV_ROUND_UP(UNCODED_FRAGS * CONFIG_LORAWAN_FRAG_TRANSPORT_MAX_REDUNDANCY, 100))
|
||||||
|
#define PADDING (UNCODED_FRAGS * FRAG_SIZE - FIRMWARE_SIZE)
|
||||||
|
|
||||||
|
#define CMD_FRAG_SESSION_SETUP (0x02)
|
||||||
|
#define CMD_DATA_FRAGMENT (0x08)
|
||||||
|
#define FRAG_TRANSPORT_PORT (201)
|
||||||
|
#define FRAG_SESSION_INDEX (1)
|
||||||
|
|
||||||
|
#define TARGET_IMAGE_AREA FIXED_PARTITION_ID(slot1_partition)
|
||||||
|
|
||||||
|
/* below array would normally hold the actual firmware binary */
|
||||||
|
static uint8_t fw_uncoded[FIRMWARE_SIZE];
|
||||||
|
|
||||||
|
/* enough space for redundancy of up to 100% */
|
||||||
|
static uint8_t fw_coded[(UNCODED_FRAGS + REDUNDANT_FRAGS) * FRAG_SIZE];
|
||||||
|
|
||||||
|
static const struct flash_area *fa;
|
||||||
|
|
||||||
|
static struct k_sem fuota_finished_sem;
|
||||||
|
|
||||||
|
static void fuota_finished(void)
|
||||||
|
{
|
||||||
|
k_sem_give(&fuota_finished_sem);
|
||||||
|
}
|
||||||
|
|
||||||
|
ZTEST(frag_decoder, test_frag_transport)
|
||||||
|
{
|
||||||
|
uint8_t buf[256]; /* maximum size of one LoRaWAN message */
|
||||||
|
uint8_t frag_session_setup_req[] = {
|
||||||
|
CMD_FRAG_SESSION_SETUP,
|
||||||
|
0x1f,
|
||||||
|
UNCODED_FRAGS & 0xFF,
|
||||||
|
(UNCODED_FRAGS >> 8) & 0xFF,
|
||||||
|
FRAG_SIZE,
|
||||||
|
0x01,
|
||||||
|
PADDING,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
};
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
k_sem_reset(&fuota_finished_sem);
|
||||||
|
|
||||||
|
lorawan_emul_send_downlink(FRAG_TRANSPORT_PORT, false, 0, 0, sizeof(frag_session_setup_req),
|
||||||
|
frag_session_setup_req);
|
||||||
|
|
||||||
|
for (int i = 0; i < sizeof(fw_coded) / FRAG_SIZE; i++) {
|
||||||
|
if (i % 10 == 9) {
|
||||||
|
/* loose every 10th packet */
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
buf[0] = CMD_DATA_FRAGMENT;
|
||||||
|
buf[1] = (i + 1) & 0xFF;
|
||||||
|
buf[2] = (FRAG_SESSION_INDEX << 6) | ((i + 1) >> 8);
|
||||||
|
memcpy(buf + 3, fw_coded + i * FRAG_SIZE, FRAG_SIZE);
|
||||||
|
lorawan_emul_send_downlink(FRAG_TRANSPORT_PORT, false, 0, 0, FRAG_SIZE + 3, buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < UNCODED_FRAGS; i++) {
|
||||||
|
size_t num_bytes = (i == UNCODED_FRAGS - 1) ? (FRAG_SIZE - PADDING) : FRAG_SIZE;
|
||||||
|
|
||||||
|
flash_area_read(fa, i * FRAG_SIZE, buf, num_bytes);
|
||||||
|
zassert_mem_equal(buf, fw_coded + i * FRAG_SIZE, num_bytes, "fragment %d invalid",
|
||||||
|
i + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
ret = k_sem_take(&fuota_finished_sem, K_MSEC(100));
|
||||||
|
zassert_equal(ret, 0, "FUOTA finish timed out");
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *frag_decoder_setup(void)
|
||||||
|
{
|
||||||
|
const struct device *lora_dev = DEVICE_DT_GET(DT_ALIAS(lora0));
|
||||||
|
struct lorawan_join_config join_cfg = {0};
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
/* populate firmware image with random data */
|
||||||
|
sys_rand_get(fw_uncoded, sizeof(fw_uncoded));
|
||||||
|
|
||||||
|
/* create coded data (including redundant fragments) from firmware image */
|
||||||
|
ret = lorawan_frag_encoder(fw_uncoded, sizeof(fw_uncoded), fw_coded, sizeof(fw_coded),
|
||||||
|
FRAG_SIZE, REDUNDANT_FRAGS);
|
||||||
|
zassert_equal(ret, 0, "creating coded data failed: %d", ret);
|
||||||
|
|
||||||
|
k_sem_init(&fuota_finished_sem, 0, 1);
|
||||||
|
|
||||||
|
ret = flash_area_open(TARGET_IMAGE_AREA, &fa);
|
||||||
|
zassert_equal(ret, 0, "opening flash area failed: %d", ret);
|
||||||
|
|
||||||
|
zassert_true(device_is_ready(lora_dev), "LoRa device not ready");
|
||||||
|
|
||||||
|
ret = lorawan_start();
|
||||||
|
zassert_equal(ret, 0, "lorawan_start failed: %d", ret);
|
||||||
|
|
||||||
|
ret = lorawan_join(&join_cfg);
|
||||||
|
zassert_equal(ret, 0, "lorawan_join failed: %d", ret);
|
||||||
|
|
||||||
|
lorawan_frag_transport_run(fuota_finished);
|
||||||
|
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
ZTEST_SUITE(frag_decoder, NULL, frag_decoder_setup, NULL, NULL, NULL);
|
7
tests/subsys/lorawan/frag_decoder/testcase.yaml
Normal file
7
tests/subsys/lorawan/frag_decoder/testcase.yaml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
common:
|
||||||
|
tags:
|
||||||
|
- lorawan
|
||||||
|
tests:
|
||||||
|
lorawan.frag_decoder:
|
||||||
|
platform_allow:
|
||||||
|
- native_sim
|
Loading…
Add table
Add a link
Reference in a new issue