From 566066a0835c4da0d473ec6eabfe0db53a84e12f Mon Sep 17 00:00:00 2001 From: Martine Lenders Date: Mon, 11 Jul 2022 13:57:11 +0200 Subject: [PATCH 1/2] libschc: initial import Co-Authored-By: boortmans --- CODEOWNERS | 1 + pkg/Kconfig | 1 + pkg/libschc/Kconfig | 39 +++ pkg/libschc/Makefile | 9 + pkg/libschc/Makefile.dep | 9 + pkg/libschc/Makefile.include | 12 + pkg/libschc/doc.txt | 11 + pkg/libschc/include/libschc/config.h | 72 +++++ pkg/libschc/include/rules/rule_config.h | 37 +++ pkg/libschc/include/rules/rules.h | 344 ++++++++++++++++++++++++ pkg/libschc/include/schc_config.h | 128 +++++++++ 11 files changed, 663 insertions(+) create mode 100644 pkg/libschc/Kconfig create mode 100644 pkg/libschc/Makefile create mode 100644 pkg/libschc/Makefile.dep create mode 100644 pkg/libschc/Makefile.include create mode 100644 pkg/libschc/doc.txt create mode 100644 pkg/libschc/include/libschc/config.h create mode 100644 pkg/libschc/include/rules/rule_config.h create mode 100644 pkg/libschc/include/rules/rules.h create mode 100644 pkg/libschc/include/schc_config.h diff --git a/CODEOWNERS b/CODEOWNERS index 45c9c91a8fd4..7930378c7039 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -99,6 +99,7 @@ /drivers/include/periph/ptp.h @maribu /pkg/cryptoauthlib/ @Einhornhool @PeterKietzmann +/pkg/libschc/ @bartmoons @miri64 /pkg/lua/ @jcarrano /pkg/lwip/ @miri64 /pkg/gecko_sdk/ @basilfx diff --git a/pkg/Kconfig b/pkg/Kconfig index 9f2a2ebe8bd9..2d377fc715e5 100644 --- a/pkg/Kconfig +++ b/pkg/Kconfig @@ -39,6 +39,7 @@ rsource "libb2/Kconfig" rsource "libcose/Kconfig" rsource "libfixmath/Kconfig" rsource "libhydrogen/Kconfig" +rsource "libschc/Kconfig" rsource "littlefs2/Kconfig" rsource "lorabasics/Kconfig" rsource "lora-serialization/Kconfig" diff --git a/pkg/libschc/Kconfig b/pkg/libschc/Kconfig new file mode 100644 index 000000000000..41f81250d341 --- /dev/null +++ b/pkg/libschc/Kconfig @@ -0,0 +1,39 @@ +# Copyright (c) 2022 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. +# +menuconfig KCONFIG_USEPKG_LIBSCHC + bool "Configure libSCHC" + depends on USEPKG_LIBSCHC + help + Configure libSCHC package via Kconfig. + +if KCONFIG_USEPKG_LIBSCHC + +config LIBSCHC_STATIC_MEMBUF_LEN + int "Static memory allocation buffer length" + default 1024 + help + Length of the static memory buffer for fragment data allocation in reassembly buffer in + bytes. + +config LIBSCHC_MBUF_POOL_SIZE + int "Maximum number of mbuf pool entries" + default 64 + help + Maximum number of entries in the mbuf used for fragment reassembly. + +config LIBSCHC_MAX_RX_CONNS + int "Maximum number of incoming connections" + default 1 + +config LIBSCHC_MAX_MTU_LEN + int "Maximum transfer unit of the underlying technology" + default 242 + +config LIBSCHC_DEBUG + bool "Enable debug output" + +endif # KCONFIG_USEPKG_LIBSCHC diff --git a/pkg/libschc/Makefile b/pkg/libschc/Makefile new file mode 100644 index 000000000000..d402b80eabda --- /dev/null +++ b/pkg/libschc/Makefile @@ -0,0 +1,9 @@ +PKG_NAME=libschc +PKG_URL=https://github.com/imec-idlab/libschc +PKG_VERSION=303e9f15bf69da5a68cda76796c76de353f44a88 +PKG_LICENSE=GPL-v3.0 + +include $(RIOTBASE)/pkg/pkg.mk + +all: + +$(QQ)"$(MAKE)" -C $(PKG_SOURCE_DIR) -f $(RIOTBASE)/Makefile.base diff --git a/pkg/libschc/Makefile.dep b/pkg/libschc/Makefile.dep new file mode 100644 index 000000000000..32df9c536be0 --- /dev/null +++ b/pkg/libschc/Makefile.dep @@ -0,0 +1,9 @@ +ifneq (,$(filter libschc_%,$(USEMODULE))) + USEPKG += libschc +endif + +ifneq (,$(filter libschc,$(USEPKG))) + USEMODULE += libschc_udpv6 +endif + +FEATURES_BLACKLIST += arch_8bit arch_16bit arch_esp8266 diff --git a/pkg/libschc/Makefile.include b/pkg/libschc/Makefile.include new file mode 100644 index 000000000000..339a36035f03 --- /dev/null +++ b/pkg/libschc/Makefile.include @@ -0,0 +1,12 @@ +CFLAGS += -Wno-enum-conversion +CFLAGS += -Wno-old-style-definition +CFLAGS += -Wno-sign-compare +CFLAGS += -Wno-strict-prototypes +CFLAGS += -Wno-unused-parameter +CFLAGS += -Wno-unused-variable + +INCLUDES += -I$(RIOTBASE)/pkg/libschc/include +INCLUDES += -I$(PKGDIRBASE)/libschc + +PSEUDOMODULES += libschc_coap +PSEUDOMODULES += libschc_udpv6 diff --git a/pkg/libschc/doc.txt b/pkg/libschc/doc.txt new file mode 100644 index 000000000000..9fe67406fbc0 --- /dev/null +++ b/pkg/libschc/doc.txt @@ -0,0 +1,11 @@ +/** + * @defgroup pkg_libschc libSCHC + * @ingroup pkg + * @brief Provides support for Static Context Header Compression and Fragmentation (SCHC) + * @see https://github.com/imec-idlab/libschc + * @see [RFC 8724](https://datatracker.ietf.org/doc/html/rfc8724) + * @experimental + * + * libSCHC is a C implementation of Static Context Header Compression and + Fragmentation + */ diff --git a/pkg/libschc/include/libschc/config.h b/pkg/libschc/include/libschc/config.h new file mode 100644 index 000000000000..88b9c1a72f68 --- /dev/null +++ b/pkg/libschc/include/libschc/config.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2022 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. + */ + +/** + * @defgroup pkg_libschc_config libSCHC compile-time configuration + * @ingroup pkg_libschc + * @ingroup config + * @brief Compile-time configuration for libSCHC + * @{ + * + * @file + * @brief RIOT-side compile-time configuration for libSCHC + * + * @author Martine S. Lenders + */ +#ifndef LIBSCHC_CONFIG_H +#define LIBSCHC_CONFIG_H + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Static memory buffer length + * + * Length of the static memory buffer for fragmentation in bytes. + */ +#ifndef CONFIG_LIBSCHC_STATIC_MEMBUF_LEN +#define CONFIG_LIBSCHC_STATIC_MEMBUF_LEN 1024 +#endif + +/** + * @brief Maximum number of mbuf pool entries + * + * Maximum number of entries in the mbuf used for fragment reassembly. + */ +#ifndef CONFIG_LIBSCHC_MBUF_POOL_SIZE +#define CONFIG_LIBSCHC_MBUF_POOL_SIZE 64 +#endif + +/** + * @brief Maximum number of incoming connections + */ +#ifndef CONFIG_LIBSCHC_MAX_RX_CONNS +#define CONFIG_LIBSCHC_MAX_RX_CONNS 1 +#endif + +/** + * @brief Maximum transfer unit of the underlying technology + */ +#ifndef CONFIG_LIBSCHC_MAX_MTU_LEN +#define CONFIG_LIBSCHC_MAX_MTU_LEN 242 +#endif + +/** + * @brief Enable debug output + */ +#ifndef CONFIG_LIBSCHC_DEBUG +#define CONFIG_LIBSCHC_DEBUG +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* LIBSCHC_CONFIG_H */ +/** @} */ diff --git a/pkg/libschc/include/rules/rule_config.h b/pkg/libschc/include/rules/rule_config.h new file mode 100644 index 000000000000..139f948518ae --- /dev/null +++ b/pkg/libschc/include/rules/rule_config.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2018 imec IDLab + * Copyright (C) 2022 Freie Universität Berlin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * @internal + * @author boortmans + * @author Martine S. Lenders + */ +#ifndef RULES_RULE_CONFIG_H +#define RULES_RULE_CONFIG_H + +#include "rules.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* RULES_RULE_CONFIG_H */ diff --git a/pkg/libschc/include/rules/rules.h b/pkg/libschc/include/rules/rules.h new file mode 100644 index 000000000000..9374e57d8415 --- /dev/null +++ b/pkg/libschc/include/rules/rules.h @@ -0,0 +1,344 @@ +/* + * Copyright (C) 2018 imec IDLab + * Copyright (C) 2022 Freie Universität Berlin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * @internal + * @author boortmans + * @author Martine S. Lenders + */ +#ifndef RULES_RULES_H +#define RULES_RULES_H + +#include "kernel_defines.h" + +#include "schc.h" +#ifdef USE_COAP +#include "net/coap.h" +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if USE_IP6 +static const struct schc_ipv6_rule_t ipv6_rule1 = { + .up = 10, .down = 10, .length = 11, + { + /* field, ML, len, pos, dir, val, MO, CDA */ + { IP6_V, 0, 4, 1, BI, {6}, &mo_equal, NOTSENT }, + { IP6_TC, 0, 8, 1, BI, {0}, &mo_ignore, NOTSENT }, + { IP6_FL, 0, 20, 1, BI, {0, 0, 0}, &mo_ignore, NOTSENT }, + { IP6_LEN, 0, 16, 1, BI, {0, 0}, &mo_ignore, COMPLENGTH }, + { IP6_NH, 0, 8, 1, BI, {17}, &mo_equal, NOTSENT }, + { IP6_HL, 0, 8, 1, UP, {64}, &mo_equal, NOTSENT }, + { IP6_HL, 0, 8, 1, DOWN, {0}, &mo_ignore, VALUESENT }, + { IP6_DEVPRE, 0, 64, 1, BI, { + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00 + }, &mo_equal, NOTSENT }, + { IP6_DEVIID, 0, 64, 1, BI, { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }, &mo_equal, NOTSENT }, + { IP6_APPPRE, 4, 64, 1, BI, { + /* you can store as many IPs as (MAX_FIELD_LENGTH / 8) */ + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x01, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x02, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x03, 0x00, 0x00, + 0x20, 0x01, 0x0d, 0xb8, 0x00, 0x04, 0x00, 0x00 + }, &mo_matchmap, MAPPINGSENT }, + { IP6_APPIID, 0, 64, 1, BI, { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02 + }, &mo_equal, NOTSENT }, + } +}; + +/* link local test rule */ +static const struct schc_ipv6_rule_t ipv6_rule2 = { + .up = 10, .down = 10, .length = 10, + { + /* field, ML, len, pos, dir, val, MO, CDA */ + { IP6_V, 0, 4, 1, BI, {6}, &mo_equal, NOTSENT }, + { IP6_TC, 0, 8, 1, BI, {0}, &mo_ignore, NOTSENT }, + { IP6_FL, 0, 20, 1, BI, {0, 0, 0}, &mo_ignore, NOTSENT }, + { IP6_LEN, 0, 16, 1, BI, {0, 0}, &mo_ignore, COMPLENGTH }, + { IP6_NH, 2, 8, 1, BI, {17, 58}, &mo_matchmap, MAPPINGSENT }, + { IP6_HL, 2, 8, 1, BI, {64, 255}, &mo_matchmap, NOTSENT }, + { IP6_DEVPRE, 0, 64, 1, BI, { + 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, &mo_equal, NOTSENT }, + { IP6_DEVIID, 62, 64, 1, BI, { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }, &mo_MSB, LSB }, + { IP6_APPPRE, 0, 64, 1, BI, { + 0xFE, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 + }, &mo_equal, NOTSENT }, + { IP6_APPIID, 62, 64, 1, BI, { + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 + }, &mo_MSB, LSB }, + } +}; +#endif + +#if USE_UDP +static const struct schc_udp_rule_t udp_rule1 = { + .up = 4, .down = 4, .length = 4, + { + /* field, ML, len, pos, dir, val, MO, CDA */ + /* set field length to 16 to indicate 16 bit values + * MO param length to 2 to indicate 2 indices */ + { UDP_DEV, 2, 16, 1, BI, { + 0x33, 0x16, /* 5683 or */ + 0x33, 0x17 /* 5684 */ + }, &mo_matchmap, MAPPINGSENT }, + { UDP_APP, 2, 16, 1, BI, { + 0x33, 0x16, /* 5683 or */ + 0x33, 0x17 /* 5684 */ + }, &mo_matchmap, MAPPINGSENT }, + { UDP_LEN, 0, 16, 1, BI, {0, 0}, &mo_ignore, COMPLENGTH }, + { UDP_CHK, 0, 16, 1, BI, {0, 0}, &mo_ignore, COMPCHK }, + } +}; + +static const struct schc_udp_rule_t udp_rule2 = { + .up = 4, .down = 4, .length = 4, + { + /* field, ML, len, pos, dir, val, MO, CDA */ + { UDP_DEV, 12, 16, 1, BI, {0x1F, 0x40}, &mo_MSB, LSB }, + { UDP_APP, 12, 16, 1, BI, {0x1F, 0x40}, &mo_MSB, LSB }, + { UDP_LEN, 0, 16, 1, BI, {0, 0}, &mo_ignore, COMPLENGTH }, + { UDP_CHK, 0, 16, 1, BI, {0, 0}, &mo_ignore, COMPCHK }, + } +}; + +static const struct schc_udp_rule_t udp_rule3 = { + .up = 4, .down = 4, .length = 4, + { + /* field, ML, len, pos, dir, val, MO, CDA */ + { UDP_DEV, 0, 16, 1, BI, {0x13, 0x89}, &mo_equal, NOTSENT }, + { UDP_APP, 0, 16, 1, BI, {0x13, 0x88}, &mo_equal, NOTSENT }, + { UDP_LEN, 0, 16, 1, BI, {0, 0}, &mo_ignore, COMPLENGTH }, + { UDP_CHK, 0, 16, 1, BI, {0, 0}, &mo_ignore, COMPCHK }, + } +}; +#endif + +#if USE_COAP +/* It is important to use strings, identical to the ones defined in coap.h for the options. */ + +/* GET /usage */ +static const struct schc_coap_rule_t coap_rule1 = { + .up = 9, .down = 7, .length = 9, + { + /* field, ML, len, pos, dir, val, MO, CDA */ + { COAP_V, 0, 2, 1, BI, {COAP_V1}, &mo_equal, NOTSENT }, + /* the MO_param_length (ML) is used to indicate the true length of the list */ + { COAP_T, 4, 2, 1, BI, { + COAP_TYPE_CON, COAP_TYPE_NON, COAP_TYPE_ACK, COAP_TYPE_RST + }, &mo_matchmap, MAPPINGSENT }, + { COAP_TKL, 0, 4, 1, BI, {4}, &mo_equal, NOTSENT }, + { COAP_C, 0, 8, 1, BI, {COAP_METHOD_PUT}, &mo_equal, NOTSENT }, + { COAP_MID, 0, 16, 1, BI, {0x23, 0xBB}, &mo_equal, NOTSENT }, + { COAP_TKN, 24, 32, 1, BI, { + 0x21, 0xFA, 0x01, 0x00 + }, &mo_MSB, LSB }, + { COAP_URIPATH, 0, 40, 1, BI, "usage", &mo_equal, NOTSENT }, + { COAP_NORESP, 0, 8, 1, BI, {0x1A}, &mo_equal, NOTSENT }, + { COAP_PAYLOAD, 0, 8, 1, BI, {0xFF}, &mo_equal, NOTSENT } + } +}; + +/* POST temperature value */ +static const struct schc_coap_rule_t coap_rule2 = { + .up = 7, .down = 7, .length = 10, + { + /* field, ML, len, pos, dir, val, MO, CDA */ + { COAP_V, 0, 2, 1, BI, {COAP_V1}, &mo_equal, NOTSENT }, + { COAP_T, 0, 2, 1, BI, {0}, &mo_ignore, VALUESENT }, + { COAP_TKL, 0, 4, 1, BI, {4}, &mo_equal, NOTSENT }, + { COAP_C, 0, 8, 1, UP, {COAP_CODE_CONTENT}, &mo_equal, NOTSENT }, + { COAP_C, 0, 8, 1, DOWN, {COAP_METHOD_GET}, &mo_equal, NOTSENT }, + /* match the first 12 bits */ + { COAP_MID, 12, 16, 1, UP, {0x23, 0xBB}, &mo_MSB, LSB }, + { COAP_MID, 0, 16, 1, DOWN, {0, 0}, &mo_ignore, VALUESENT }, + { COAP_TKN, 0, 32, 1, BI, {0, 0, 0, 0}, &mo_ignore, VALUESENT }, + { COAP_URIPATH, 0, 32, 1, DOWN, "temp", &mo_equal, NOTSENT }, + /* respond with CONTENT */ + { COAP_PAYLOAD, 0, 8, 1, UP, {0xFF}, &mo_equal, NOTSENT } + } +}; + +static const struct schc_coap_rule_t coap_rule3 = { + .up = 1, .down = 1, .length = 1, + { + /* field, ML, len, pos, dir, val, MO, CDA */ + { COAP_V, 0, 2, 1, BI, {COAP_V1}, &mo_equal, NOTSENT }, + } +}; + +static const struct schc_coap_rule_t coap_rule4 = { + .up = 12, .down = 12, .length = 12, + { + /* field, ML, len, pos, dir, val, MO, CDA */ + { COAP_V, 0, 2, 1, BI, {COAP_V1}, &mo_equal, NOTSENT }, + { COAP_T, 0, 2, 1, BI, {COAP_TYPE_CON}, &mo_equal, NOTSENT }, + { COAP_TKL, 0, 4, 1, BI, {8}, &mo_equal, NOTSENT }, + { COAP_C, 0, 8, 1, BI, {COAP_METHOD_POST}, &mo_equal, NOTSENT }, + { COAP_MID, 0, 16, 1, BI, {0x23, 0xBB}, &mo_ignore, VALUESENT }, + /* match the 24 first bits, send the last 8 */ + { COAP_TKN, 24, 32, 1, BI, { + 0x21, 0xFA, 0x01, 0x00 + }, &mo_MSB, LSB }, + { COAP_URIPATH, 0, 16, 1, BI, "rd", &mo_equal, NOTSENT }, + { COAP_CONTENTF, 0, 8, 1, BI, {0x28}, &mo_equal, NOTSENT }, + { COAP_URIQUERY, 0, 72, 1, BI, "lwm2m=1.0", &mo_equal, NOTSENT }, + { COAP_URIQUERY, 0, 88, 1, BI, "ep=magician", &mo_equal, NOTSENT }, + { COAP_URIQUERY, 0, 48, 1, BI, "lt=121", &mo_equal, NOTSENT }, + /* respond with CONTENT */ + { COAP_PAYLOAD, 0, 8, 1, BI, {0xff}, &mo_equal, NOTSENT } + } +}; +#endif + +static const struct schc_compression_rule_t comp_rule_1 = { + .rule_id = 0x01, + .rule_id_size_bits = 8U, +#if USE_IP6 + &ipv6_rule1, +#endif +#if USE_UDP + &udp_rule1, +#endif +#if USE_COAP + &coap_rule1, +#endif +}; + +static const struct schc_compression_rule_t comp_rule_2 = { + .rule_id = 0x02, + .rule_id_size_bits = 8U, +#if USE_IP6 + &ipv6_rule1, +#endif +#if USE_UDP + &udp_rule3, +#endif +#if USE_COAP + &coap_rule2, +#endif +}; + +static const struct schc_compression_rule_t comp_rule_3 = { + .rule_id = 0x03, + .rule_id_size_bits = 8U, +#if USE_IP6 + &ipv6_rule2, +#endif +#if USE_UDP + &udp_rule2, +#endif +#if USE_COAP + &coap_rule3, +#endif +}; + +static const struct schc_compression_rule_t comp_rule_4 = { + .rule_id = 0x04, + .rule_id_size_bits = 8U, +#if USE_IP6 + &ipv6_rule2, +#endif +#if USE_UDP + &udp_rule2, +#endif +#if USE_COAP + &coap_rule4, +#endif +}; + +static const struct schc_fragmentation_rule_t frag_rule_21 = { + .rule_id = 21, + .rule_id_size_bits = 8, + .mode = NO_ACK, + .dir = BI, + .FCN_SIZE = 1, /* FCN size */ + .MAX_WND_FCN = 0, /* Maximum fragments per window */ + .WINDOW_SIZE = 0, /* Window size */ + .DTAG_SIZE = 0, /* DTAG size */ +}; + +static const struct schc_fragmentation_rule_t frag_rule_22 = { + .rule_id = 22, + .rule_id_size_bits = 8, + .mode = ACK_ON_ERROR, + .dir = BI, + .FCN_SIZE = 3, /* FCN size */ + .MAX_WND_FCN = 6, /* Maximum fragments per window */ + .WINDOW_SIZE = 1, /* Window size */ + .DTAG_SIZE = 0, /* DTAG size */ +}; + +static const struct schc_fragmentation_rule_t frag_rule_23 = { + .rule_id = 23, + .rule_id_size_bits = 8, + .mode = ACK_ALWAYS, + .dir = BI, + .FCN_SIZE = 3, /* FCN size */ + .MAX_WND_FCN = 6, /* Maximum fragments per window */ + .WINDOW_SIZE = 1, /* Window size */ + .DTAG_SIZE = 0, /* DTAG size */ +}; + +/* save compression rules in flash */ +static const struct schc_compression_rule_t* node1_compression_rules[] = { + &comp_rule_1, &comp_rule_2, &comp_rule_3, &comp_rule_4 +}; + +/* save fragmentation rules in flash */ +static const struct schc_fragmentation_rule_t* node1_fragmentation_rules[] = { + &frag_rule_21, &frag_rule_22, &frag_rule_23, +}; + +/* rules for a particular device */ +static const struct schc_device node1 = { + .device_id = 1, + .uncomp_rule_id = 0, + .uncomp_rule_id_size_bits = 8, + .compression_rule_count = ARRAY_SIZE(node1_compression_rules), + .compression_context = &node1_compression_rules, + .fragmentation_rule_count = ARRAY_SIZE(node1_fragmentation_rules), + .fragmentation_context = &node1_fragmentation_rules +}; +static const struct schc_device node2 = { + .device_id = 2, + .uncomp_rule_id = 0, + .uncomp_rule_id_size_bits = 8, + .compression_rule_count = ARRAY_SIZE(node1_compression_rules), + .compression_context = &node1_compression_rules, + .fragmentation_rule_count = ARRAY_SIZE(node1_fragmentation_rules), + .fragmentation_context = &node1_fragmentation_rules +}; + +/* server keeps track of multiple devices: add devices to device list */ +static const struct schc_device* devices[] = { &node1, &node2 }; + +#define DEVICE_COUNT ((int)ARRAY_SIZE(devices)) + +#ifdef __cplusplus +} +#endif + +#endif /* RULES_RULES_H */ diff --git a/pkg/libschc/include/schc_config.h b/pkg/libschc/include/schc_config.h new file mode 100644 index 000000000000..96cea7a8dcef --- /dev/null +++ b/pkg/libschc/include/schc_config.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2018 imec IDLab + * Copyright (C) 2022 Freie Universität Berlin + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +/** + * @addtogroup pkg_libschc_config + * + * @internal + * @name libSCHC-side compile-time config for libSCHC + * @{ + * + * @file + * + * Usually this file and its macros need not to be touched. Use the compile-time + * configuration macros in @ref libschc_config.h to configure @ref pkg_libschc. + * + * @author boortmans + * @author Martine S. Lenders + */ +#ifndef SCHC_CONFIG_H +#define SCHC_CONFIG_H + +#include +#include +#include + +#include "kernel_defines.h" +#include "libschc/config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define CLICK 0 + +#define DYNAMIC_MEMORY 0 +#define STATIC_MEMORY_BUFFER_LENGTH CONFIG_LIBSCHC_STATIC_MEMBUF_LEN + +#define SCHC_CONF_RX_CONNS CONFIG_LIBSCHC_MAX_RX_CONNS +#define SCHC_CONF_MBUF_POOL_LEN CONFIG_LIBSCHC_MBUF_POOL_SIZE + +#if IS_USED(MODULE_LIBSCHC_COAP) +#define USE_COAP 1 +#else +#define USE_COAP 0 +#endif + +#if IS_USED(MODULE_LIBSCHC_UDPV6) +#define USE_IP6_UDP 1 +#else +#define USE_IP6_UDP 0 +#endif + +/* the maximum length of a single header field + * e.g. you can use 4 ipv6 source iid addresses with match-mapping */ +#define MAX_FIELD_LENGTH 32 + +/* maximum number of header fields present in a rule (vertical, top to bottom) */ +#define IP6_FIELDS 14 +#define UDP_FIELDS 4 +#define COAP_FIELDS 16 + +#define MAX_HEADER_LENGTH 256 + +#define MAX_COAP_HEADER_LENGTH 64 +#define MAX_PAYLOAD_LENGTH 256 +#define MAX_COAP_MSG_SIZE MAX_COAP_HEADER_LENGTH + MAX_PAYLOAD_LENGTH + +/* the maximum transfer unit of the underlying technology */ +#define MAX_MTU_LENGTH CONFIG_LIBSCHC_MAX_MTU_LEN + +/* the maximum number of tokens inside a JSON structure */ +#define JSON_TOKENS 16 + +#define RULE_SIZE_BITS 8 + +#if IS_ACTIVE(CONFIG_LIBSCHC_DEBUG) +#define DEBUG_PRINTF(...) printf(__VA_ARGS__) +#else +#define DEBUG_PRINTF(...) +#endif + +/* the number of ack attempts */ +#define MAX_ACK_REQUESTS 3 + +/* the number of FCN bits */ +#if IS_USED(MODULE_LORA) +#define FCN_SIZE_BITS 6 +#else +#define FCN_SIZE_BITS 3 +#endif + +/* the number of DTAG bits */ +#define DTAG_SIZE_BITS 0 + +/* the number of bytes the MIC consists of */ +#define MIC_SIZE_BYTES 4 + +/* the length of the bitmap */ +#if IS_USED(MODULE_LORA) +#define BITMAP_SIZE_BYTES 8 /* pow(2, FCN_SIZE_BITS) / 8 */ +#else +#define BITMAP_SIZE_BYTES 2 /* pow(2, FCN_SIZE_BITS) / 8 */ +#endif + +#ifdef __cplusplus +} +#endif + +#endif /* SCHC_CONFIG_H */ +/** + * @internal + * @} + */ From 9459e29ee3bfcd70a1840c17d7369c0662af05e1 Mon Sep 17 00:00:00 2001 From: Martine Lenders Date: Mon, 11 Jul 2022 14:05:01 +0200 Subject: [PATCH 2/2] tests: provide tests for libschc --- CODEOWNERS | 1 + tests/pkg_libschc/Makefile | 22 + tests/pkg_libschc/Makefile.ci | 10 + tests/pkg_libschc/main.c | 688 +++++++++++++++++++++++ tests/pkg_libschc/tests/01-run.py | 886 ++++++++++++++++++++++++++++++ tests/pkg_libschc/unittests.c | 87 +++ 6 files changed, 1694 insertions(+) create mode 100644 tests/pkg_libschc/Makefile create mode 100644 tests/pkg_libschc/Makefile.ci create mode 100644 tests/pkg_libschc/main.c create mode 100755 tests/pkg_libschc/tests/01-run.py create mode 100644 tests/pkg_libschc/unittests.c diff --git a/CODEOWNERS b/CODEOWNERS index 7930378c7039..75436eabc69b 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -146,6 +146,7 @@ /tests/driver_dht/ @wosym /tests/gnrc* @miri64 /tests/lwip* @miri64 +/tests/pkg_libschc/ @miri64 /tests/slip/ @miri64 /tests/unittests @miri64 /tests/*/tests/*.py @miri64 diff --git a/tests/pkg_libschc/Makefile b/tests/pkg_libschc/Makefile new file mode 100644 index 000000000000..8bedc0df2338 --- /dev/null +++ b/tests/pkg_libschc/Makefile @@ -0,0 +1,22 @@ +include ../Makefile.tests_common + +USEMODULE += embunit +USEMODULE += event +USEMODULE += event_timeout_ztimer +USEMODULE += event_thread +USEMODULE += fmt +USEMODULE += ipv6_addr +USEMODULE += od +USEMODULE += shell +USEMODULE += ztimer_msec + +USEPKG += libschc +USEMODULE += libschc_coap + +CFLAGS += -DEVENT_THREAD_STACKSIZE_DEFAULT=THREAD_STACKSIZE_DEFAULT + +DEBUG_TESTS ?= 0 + +$(call target-export-variables, test, DEBUG_TESTS) + +include $(RIOTBASE)/Makefile.include diff --git a/tests/pkg_libschc/Makefile.ci b/tests/pkg_libschc/Makefile.ci new file mode 100644 index 000000000000..84248e69c910 --- /dev/null +++ b/tests/pkg_libschc/Makefile.ci @@ -0,0 +1,10 @@ +BOARD_INSUFFICIENT_MEMORY := \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + samd10-xmini \ + stk3200 \ + stm32f030f4-demo \ + stm32g0316-disco \ + # diff --git a/tests/pkg_libschc/main.c b/tests/pkg_libschc/main.c new file mode 100644 index 000000000000..69117425e226 --- /dev/null +++ b/tests/pkg_libschc/main.c @@ -0,0 +1,688 @@ +/* + * Copyright (C) 2022 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 Tests for the libSCHC package. + * + * @author Martine S. Lenders + * @} + */ + +#include +#include +#include +#include + +#include "bitfield.h" +#include "event.h" +#include "event/timeout.h" +#include "event/thread.h" +#include "fmt.h" +#include "kernel_defines.h" +#include "mutex.h" +#include "net/ipv6/addr.h" +#include "od.h" +#include "shell.h" +#include "thread_flags.h" +#include "ztimer.h" + +#include "compressor.h" +#include "bit_operations.h" +#include "fragmenter.h" +#include "schc.h" + +#define THREAD_FLAG_TX_END (1U << 4) +#define FRAG_WIN_SIZE_MAX (8U) +#define TIMEOUT_EVENTS_MAX (4U) + +typedef struct { + event_t super; + event_timeout_t timeout; + void (*timer_task)(void *); + void *arg; +} _timer_event_t; + +static uint32_t _ack_delay = 0; +static unsigned _frag_counter = 0; +static thread_t *_main_thread; +static schc_fragmentation_t _rx_conn, _tx_conn; +static char _line_buf[SHELL_DEFAULT_BUFSIZE]; +static _timer_event_t _timer_event; +static BITFIELD(_acks, FRAG_WIN_SIZE_MAX); +static uint8_t _input_buf[128U]; +static uint8_t _output_buf[sizeof(_input_buf)]; +static uint8_t _input_buf_len; +static uint8_t _output_buf_len; + +static ssize_t _copy_input(int argc, char **argv) +{ + bool msn = true; /* most significant nibble */ + ssize_t start = _input_buf_len; + + if (_input_buf_len >= sizeof(_input_buf)) { + goto error; + } + for (int c = 0; c < argc; c++) { + char *ptr = argv[c]; + + while (*ptr) { + uint8_t nibble = scn_u32_hex(ptr++, 1); + + if (_input_buf_len >= sizeof(_input_buf)) { + goto error; + } + if (msn) { + _input_buf[_input_buf_len] = nibble << 4; + } + else { + _input_buf[_input_buf_len++] |= nibble; + } + msn = !msn; + } + } + if (!msn) { + _input_buf_len++; + } + return _input_buf_len - start; +error: + puts("Too many bytes added to input buffer"); + return -ENOBUFS; +} + +static int _input(int argc, char **argv) +{ + if (argc > 1 && (strcmp(argv[1], "reset") == 0)) { + _input_buf_len = 0U; + puts("Successfully reset input buffer."); + } + else if (argc < 2) { + if (_input_buf_len) { + od_hex_dump(_input_buf, _input_buf_len, OD_WIDTH_DEFAULT); + } + else { + puts("Input buffer is empty."); + } + return 0; + } + else if (argc > 2 && (strcmp(argv[1], "add") == 0)) { + ssize_t size = _copy_input(argc - 2, &argv[2]); + if (size >= 0) { + puts("Successfully added to input buffer"); + od_hex_dump(&_input_buf[_input_buf_len - size], size, OD_WIDTH_DEFAULT); + puts(""); + return 0; + } + else { + return 1; + } + } + else { + printf("usage: %s {reset|add ...}\n", argv[0]); + return 1; + } + return 0; +} + +static void _output_usage(const char *cmd) +{ + printf("usage: %s [{reset|copy}]\n", cmd); +} + +static int _output(int argc, char **argv) +{ + if (argc > 1) { + if (strcmp(argv[1], "reset") == 0) { + _output_buf_len = 0U; + puts("Successfully reset output buffer."); + } + else if (strcmp(argv[1], "copy") == 0) { + if (_output_buf_len) { + memcpy(_input_buf, _output_buf, _output_buf_len); + } + _input_buf_len = _output_buf_len; + puts("Successfully copied output buffer to input buffer."); + } + else { + _output_usage(argv[0]); + return 1; + } + } + else { + if (_output_buf_len) { + od_hex_dump(_output_buf, _output_buf_len, OD_WIDTH_DEFAULT); + } + else { + puts("Output buffer is empty."); + } + } + return 0; +} + +static int _parse_direction(const char *dir) +{ + if (strcmp(dir, "up") == 0) { + return UP; + } + if (strcmp(dir, "down") == 0) { + return DOWN; + } + if (strcmp(dir, "bi") == 0) { + return BI; + } + return -1; +} + +static void _compress_usage(char *cmd) +{ + printf("usage: %s {{up|down|bi} |init }\n", cmd); +} + +static int _compress(int argc, char **argv) +{ + if (argc > 2 && (strcmp(argv[1], "init") == 0)) { + ipv6_addr_t addr; + + if (!ipv6_addr_from_str(&addr, argv[2])) { + printf("Unable to parse source IPv6 address %s.\n", argv[2]); + _compress_usage(argv[0]); + return 1; + } + int res = schc_compressor_init(addr.u8); + /* LCOV_EXCL_START schc_compressor_init always returns 1 */ + if (res != 1) { + printf("Error initializing compressor with source IPv6 address %s.\n", argv[2]); + return 1; + } + /* LCOV_EXCL_STOP */ + printf("Successfully initialized compressor with source IPv6 address %s.\n", argv[2]); + } + else if (argc > 2) { + int dir = _parse_direction(argv[1]); + uint32_t device_id = scn_u32_hex(argv[2], 1); + + if ((device_id == 0) || (dir < 0)) { + _compress_usage(argv[0]); + } + if (_input_buf_len == 0) { + puts("No input buffer defined"); + return 1; + } + /* compress packet */ + struct schc_compression_rule_t* comp_rule; + memset(_output_buf, 0, sizeof(_output_buf)); + schc_bitarray_t bit_arr = SCHC_DEFAULT_BIT_ARRAY(0, _output_buf); + + comp_rule = schc_compress(_input_buf, _input_buf_len, &bit_arr, device_id, (direction)dir); + if (comp_rule) { + printf("Used rule %" PRIu32 "/%u to compress to\n", comp_rule->rule_id, + comp_rule->rule_id_size_bits); + od_hex_dump(bit_arr.ptr, bit_arr.len, OD_WIDTH_DEFAULT); + _output_buf_len = bit_arr.len; + } + else if (bit_arr.len > 0) { + struct schc_device *device = get_device_by_id(device_id); + + assert(device); /* LCOV_EXCL_BR_LINE hopefully always true */ + printf("Used uncompressed rule %" PRIu32 "/%u to generate\n", + device->uncomp_rule_id, device->uncomp_rule_id_size_bits); + od_hex_dump(bit_arr.ptr, bit_arr.len, OD_WIDTH_DEFAULT); + _output_buf_len = bit_arr.len; + } + else { + printf("Unable to compress (maybe wrong device ID %" PRIu32 "?)\n", device_id); + } + } + else { + _compress_usage(argv[0]); + return 1; + } + return 0; +} + +static void _decompress_usage(char *cmd) +{ + printf("usage: %s {up|down|bi} \n", cmd); +} + +static int _decompress(int argc, char **argv) +{ + if (argc > 2) { + int dir = _parse_direction(argv[1]); + uint32_t device_id = scn_u32_hex(argv[2], 1); + + if ((device_id == 0) || (dir < 0)) { + _decompress_usage(argv[0]); + return 1; + } + if (_input_buf_len == 0) { + puts("No input buffer defined"); + return 1; + } + schc_bitarray_t bit_arr = SCHC_DEFAULT_BIT_ARRAY(_input_buf_len, _input_buf); + memset(_output_buf, 0, sizeof(_output_buf)); + uint8_t len = schc_decompress(&bit_arr, _output_buf, device_id, bit_arr.len, + (direction)dir); + /* LCOV_EXCL_START len == 0 impossible to reach with current version of libSCHC */ + if (len == 0U) { + puts("Unable to decompress input"); + return 1; + } + /* LCOV_EXCL_STOP */ + else { + puts("Decompressed to"); + od_hex_dump(_output_buf, len, OD_WIDTH_DEFAULT); + } + } + else { + _decompress_usage(argv[0]); + return 1; + } + return 0; +} + +static void _set_ack_usage(char *cmd) +{ + printf("usage: %s \n", cmd); +} + +static void _ack_print(void) +{ + printf("ACKs set to %" PRIu32 " ms delay with bitmap ", _ack_delay); + for (size_t idx = 0; idx < FRAG_WIN_SIZE_MAX; idx++) { + printf(bf_isset(_acks, idx) ? "1" : "0"); + } + printf("\n"); +} + +static int _set_ack(int argc, char **argv) +{ + if (argc > 2) { + uint32_t delay = scn_u32_dec(argv[1], 10); + unsigned idx = 0; + static BITFIELD(tmp, FRAG_WIN_SIZE_MAX) = { 0 }; + + static_assert(sizeof(tmp) == sizeof(_acks), "sizeof(tmp) != sizeof(_acks)"); + for (char *bitmap_str = argv[2]; + *bitmap_str; + bitmap_str++, idx++) { + if (idx >= FRAG_WIN_SIZE_MAX) { + printf("%s does not fit fragment window size %u\n", argv[2], + FRAG_WIN_SIZE_MAX); + } + if (*bitmap_str == '1') { + bf_set(tmp, idx); + } + else if (*bitmap_str == '0') { + bf_unset(tmp, idx); + } + else { + _set_ack_usage(argv[0]); + return 1; + } + } + memcpy(_acks, tmp, sizeof(_acks)); + _ack_delay = delay; + _ack_print(); + } + else if (argc == 1) { + _ack_print(); + } + else { + _set_ack_usage(argv[0]); + return 1; + } + return 0; +} + +static bool _ack_all1(void) +{ +#if (FRAG_WIN_SIZE_MAX > 8) + for (unsigned idx = 0; idx < sizeof(_acks) - 1; idx++) { + if (_acks[idx] != 0xff) { + return false; + } + } + } +#endif + for (unsigned idx = (sizeof(_acks) - 1) * 8; idx < FRAG_WIN_SIZE_MAX; idx++) { + if (!bf_isset(_acks, idx)) { + return false; + } + } + return true; +} + +static void _timer_cb(event_t *evt) +{ + _timer_event_t *timer_event = container_of(evt, _timer_event_t, super); + schc_fragmentation_t *conn = timer_event->arg; + conn->timer_ctx = NULL; + if (conn->device_id) { /* prevent firing of expired or canceled timers */ + timer_event->timer_task(timer_event->arg); + } +} + +static void _simulate_ack(schc_fragmentation_t *conn, uint32_t device_id) +{ + if ((_tx_conn.fragmentation_rule->mode != ACK_ALWAYS) && + ((_tx_conn.fragmentation_rule->mode != ACK_ON_ERROR) || _ack_all1())) { + return; + } + if (_ack_delay > 0) { + ztimer_sleep(ZTIMER_MSEC, _ack_delay); + } + + uint8_t ack[((_tx_conn.fragmentation_rule->rule_id_size_bits / 8 + 1)) + + DTAG_SIZE_BYTES + BITMAP_SIZE_BYTES]; + uint8_t offset = _tx_conn.fragmentation_rule->rule_id_size_bits; + + uint8_t rule_id[4] = { 0 }; + memset(ack, 0, sizeof(ack)); + little_end_uint8_from_uint32(rule_id, conn->fragmentation_rule->rule_id); + copy_bits(ack, 0, rule_id, 0, offset); + copy_bits(ack, offset, conn->ack.dtag, 0, conn->fragmentation_rule->DTAG_SIZE); + offset += conn->fragmentation_rule->DTAG_SIZE; + + uint8_t window[1] = { conn->window << (8 - conn->fragmentation_rule->WINDOW_SIZE) }; + copy_bits(ack, offset, window, 0, conn->fragmentation_rule->WINDOW_SIZE); + offset += conn->fragmentation_rule->WINDOW_SIZE; + + if (_ack_all1()) { + uint8_t c[1] = { 1 << (8 - MIC_C_SIZE_BITS) }; + copy_bits(ack, offset, c, 0, MIC_C_SIZE_BITS); + offset += MIC_C_SIZE_BITS; + } + else { + uint8_t bitmap[BITMAP_SIZE_BYTES] = { 0 }; + bool set_true = true; + for (unsigned idx = 0; idx < FRAG_WIN_SIZE_MAX; idx++) { + if (bf_isset(_acks, idx)) { + set_bits(bitmap, idx, 1); + } + else { + clear_bits(bitmap, idx, 1); + if (set_true) { + /* set first 0 for next round */ + bf_set(_acks, idx); + set_true = false; + } + } + } + copy_bits(ack, offset, bitmap, 0, conn->fragmentation_rule->MAX_WND_FCN + 1); + offset += conn->fragmentation_rule->MAX_WND_FCN + 1; /* TODO must be encoded? */ + } + uint8_t packet_len = ((offset - 1) / 8) + 1; + puts("Simulate ACK"); + od_hex_dump(ack, packet_len, OD_WIDTH_DEFAULT); + schc_input(ack, packet_len, conn, device_id); +} + +static uint8_t _tx_cb(uint8_t* data, uint16_t length, uint32_t device_id) +{ + printf("TX Fragment %u on dev 0x%lx\n", ++_frag_counter, (long unsigned)device_id); + od_hex_dump(data, length, OD_WIDTH_DEFAULT); + _simulate_ack(&_tx_conn, device_id); + return 1; +} + +static uint8_t _rx_cb(uint8_t* data, uint16_t length, uint32_t device_id) +{ + printf("Packet sent on dev 0x%lx\n", (long unsigned)device_id); + od_hex_dump(data, length, OD_WIDTH_DEFAULT); + return 1; +} + +static void _tx_end(schc_fragmentation_t *conn) +{ + (void)conn; + _frag_counter = 0; + puts("TX End"); + thread_flags_set(_main_thread, THREAD_FLAG_TX_END); +} + +/* LCOV_EXCL_START _remove_timer is never called by libSCHC in tests */ +static void _free_event(_timer_event_t *evt) +{ + event_timeout_clear(&evt->timeout); + /* cancel event in case it already was posted */ + event_cancel(evt->timeout.queue, evt->timeout.event); +} + +static void _remove_timer(struct schc_fragmentation_t *conn) +{ + _timer_event_t *evt = conn->timer_ctx; + if (evt) { + _free_event(evt); + conn->timer_ctx = NULL; + } +} +/* LCOV_EXCL_STOP */ + +static void _rx_end(schc_fragmentation_t *conn) +{ + _output_buf_len = get_mbuf_len(conn); + mbuf_copy(conn, _output_buf); + od_hex_dump(_output_buf, _output_buf_len, OD_WIDTH_DEFAULT); +} + +static void _reset_timers(void) +{ + _timer_event.super.handler = _timer_cb; + event_timeout_ztimer_init(&_timer_event.timeout, ZTIMER_MSEC, EVENT_PRIO_HIGHEST, + &_timer_event.super); +} + +static int _timer(int argc, char **argv) +{ + if ((argc > 1) && (strcmp(argv[1], "reset") == 0)) { + _reset_timers(); + puts("Reset all timers"); + } + else { + printf("Free timers: %u (of %u)\n", + TIMEOUT_EVENTS_MAX, + TIMEOUT_EVENTS_MAX); + } + return 1; +} + +static void _set_schc_timer(schc_fragmentation_t *conn, + void (*callback)(void* conn), uint32_t delay, void *arg) +{ + assert(conn->timer_ctx == NULL); /* LCOV_EXCL_BR_LINE hopefully always true */ + _timer_event.timer_task = callback; + _timer_event.arg = arg; + conn->timer_ctx = &_timer_event; + event_timeout_set(&_timer_event.timeout, delay); +} + +static reliability_mode _parse_reliability_mode(const char *mode) +{ + if (strcmp(mode, "no-ack") == 0) { + return NO_ACK; + } + if (strcmp(mode, "ack-always") == 0) { + return ACK_ALWAYS; + } + if (strcmp(mode, "ack-on-error") == 0) { + return ACK_ON_ERROR; + } + if (strcmp(mode, "not-fragmented") == 0) { + return NOT_FRAGMENTED; + } + return 0; +} + +static void _fragment_usage(char *cmd) +{ + printf("usage: %s " + "{no-ack|ack-always|ack-on-error|not-fragmented}\n", cmd); +} + +static int _fragment(int argc, char **argv) +{ + if (argc > 4) { + uint32_t device_id = scn_u32_hex(argv[1], 1); + uint32_t mtu = scn_u32_dec(argv[2], 4); + uint32_t dc = scn_u32_dec(argv[3], 5); + reliability_mode mode = _parse_reliability_mode(argv[4]); + + if ((device_id == 0) || (mtu == 0) || (mtu > 1280) || (dc == 0) || (mode == 0)) { + _fragment_usage(argv[0]); + return 1; + } + if (_input_buf_len == 0) { + puts("No input buffer defined"); + return 1; + } + struct schc_fragmentation_rule_t *frag_rule = NULL; + schc_bitarray_t bit_arr = SCHC_DEFAULT_BIT_ARRAY(_input_buf_len, _input_buf); + + frag_rule = get_fragmentation_rule_by_reliability_mode(mode, device_id); + + if (!frag_rule) { + printf("No fragmentation rule known for mode %s on device %s\n", + argv[4], argv[1]); + return 1; + } + _tx_conn.bit_arr = &bit_arr; + _tx_conn.device_id = device_id; + _tx_conn.mtu = mtu; + _tx_conn.dc = dc; + _tx_conn.send = _tx_cb; + _tx_conn.end_tx = _tx_end; + _tx_conn.fragmentation_rule = frag_rule; + _tx_conn.post_timer_task = _set_schc_timer; + _tx_conn.remove_timer_entry = _remove_timer; + int ret = schc_fragment(&_tx_conn); + if (ret == -2) { + puts("No fragmentation needed"); + _frag_counter = 0; + } + else { + assert(ret >= 0); /* LCOV_EXCL_BR_LINE hopefully always true */ + thread_flags_wait_one(THREAD_FLAG_TX_END); + puts("Fragmented!"); + } + } + else if ((argc > 1) && (strcmp(argv[1], "init") == 0)) { + memset(&_tx_conn, 0, sizeof(_tx_conn)); + int res = schc_fragmenter_init(&_tx_conn); + /* LCOV_EXCL_START schc_fragmenter_init always returns 1 */ + if (res != 1) { + puts("Error initializing fragmenter."); + return 1; + } + /* LCOV_EXCL_STOP */ + puts("Fragmenter initialized"); + } + else { + _fragment_usage(argv[0]); + return 1; + } + return 0; +} + +static void _reassemble_usage(char *cmd) +{ + printf("usage: %s \n", cmd); +} + +static int _reassemble(int argc, char **argv) +{ + if (argc > 2) { + uint32_t device_id = scn_u32_hex(argv[1], 1); + uint32_t timeout = scn_u32_dec(argv[2], 5); + + if ((device_id == 0) || (timeout == 0)) { + _reassemble_usage(argv[0]); + return 1; + } + if (_input_buf_len == 0) { + puts("No input buffer defined"); + return 1; + } + + schc_bitarray_t bit_arr = SCHC_DEFAULT_BIT_ARRAY(_input_buf_len, _input_buf); + _rx_conn.bit_arr = &bit_arr; + /* get active connection and set the correct rule for this connection */ + schc_fragmentation_t *conn = schc_input(_input_buf, _input_buf_len, &_rx_conn, device_id); + + /* if returned value is &_rx_conn: acknowledgement is received, which is handled by the + * library */ + if (conn != &_rx_conn) { /* LCOV_EXCL_BR_LINE */ + conn->post_timer_task = _set_schc_timer; + conn->dc = timeout; + + if (!conn->fragmentation_rule || conn->fragmentation_rule->mode == NOT_FRAGMENTED) { + /* packet was not fragmented */ + printf("RX Unfragmented on dev 0x%lx\n", (long unsigned)conn->device_id); + _rx_end(conn); /* final packet arrived */ + } else { + int ret = schc_reassemble(conn); + + if (ret) { + printf("RX Reassembly complete on dev 0x%lx\n", + (long unsigned)conn->device_id); + } + /* use the connection to reassemble */ + if (ret && (conn->fragmentation_rule->mode == NO_ACK)) { + _rx_end(conn); /* final packet arrived */ + } + } + } + } + else if ((argc > 1) && (strcmp(argv[1], "init") == 0)) { + memset(&_tx_conn, 0, sizeof(_tx_conn)); + memset(&_rx_conn, 0, sizeof(_rx_conn)); + int res = schc_fragmenter_init(&_tx_conn); + /* LCOV_EXCL_START schc_fragmenter_init always returns 1 */ + if (res != 1) { + puts("Error initializing fragmenter."); + return 1; + } + /* LCOV_EXCL_STOP */ + _rx_conn.send = _rx_cb; + _rx_conn.end_rx = &_rx_end; + _rx_conn.remove_timer_entry = &_remove_timer; + puts("Reassembler initialized"); + } + else { + _reassemble_usage(argv[0]); + return 1; + } + return 0; +} + +extern int unittests(int argc, char **argv); + +static const shell_command_t _shell_commands[] = { + { "input", "Add bytes to input buffer", _input }, + { "output", "Handle output buffer", _output }, + { "compress", "Compress input buffer using libSCHC with preconfigured rules", _compress }, + { "decompress", "Decompress input buffer using libSCHC with preconfigured rules", _decompress }, + { "set_ack", "Set fragments to ACK with libSCHC", _set_ack }, + { "timer", "Check or reset running timers", _timer }, + { "fragment", "Fragment input buffer using libSCHC with preconfigured rules", _fragment }, + { "reassemble", "Reassemble fragment from input buffer using libSCHC with preconfigured rules", + _reassemble }, + { "unittests", "Run unittests for libSCHC", unittests }, + { NULL, NULL, NULL } +}; + +int main(void) +{ + _main_thread = thread_get_active(); + memset(_acks, 0xff, sizeof(_acks)); + _reset_timers(); + shell_run(_shell_commands, _line_buf, SHELL_DEFAULT_BUFSIZE); + return 0; /* LCOV_EXCL_LINE never reached */ +} diff --git a/tests/pkg_libschc/tests/01-run.py b/tests/pkg_libschc/tests/01-run.py new file mode 100755 index 000000000000..359bff4d0148 --- /dev/null +++ b/tests/pkg_libschc/tests/01-run.py @@ -0,0 +1,886 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2022 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. + +import os +import sys +import unittest + +from scapy.all import load_contrib, IPv6, UDP +from scapy.contrib.coap import CoAP + +from testrunner import check_unittests +from testrunner.unittest import PexpectTestCase + + +DEBUG_TESTS = bool(int(os.environ.get("DEBUG_TESTS", 0))) + + +class TestCompiledUnittests(PexpectTestCase): + LOGFILE = sys.stdout if DEBUG_TESTS else None + + def test_compiled_unittests(self): + self.spawn.sendline("unittests") + check_unittests(self.spawn) + + +class InputMixin: + MAX = 16 + + def input(self, inp): + if isinstance(inp, str): + input_hex = inp + else: + input_hex = bytes(inp).hex() + while input_hex: + self.spawn.sendline("input add {}".format(input_hex[: self.MAX])) + self.spawn.expect_exact("Successfully added to input buffer") + self.expect_od_dump_of(input_hex[:self.MAX]) + input_hex = input_hex[self.MAX:] + + def input_reset(self): + self.spawn.sendline("input reset") + + def expect_od_dump_of(self, byts): + if isinstance(byts, str): + hexbytes = byts + else: + hexbytes = bytes(byts).hex() + for i in range((len(hexbytes) // 32) + 1): + rang = hexbytes[(i * 32) : ((i * 32) + 32)] # noqa: E203 + if not rang: + # reached end directly at line break + break + od_bytes = "".join( + [ + " {}{}".format(a.upper(), b.upper()) + for a, b in zip(rang[::2], rang[1::2]) + ] + ) + self.spawn.expect_exact("{:08x}{}".format(i * 16, od_bytes)) + + +class TestSelfTest(PexpectTestCase, InputMixin): + LOGFILE = sys.stdout if DEBUG_TESTS else None + + def tearDown(self): + self.input_reset() + self.spawn.expect_exact("Successfully reset input buffer.") + self.spawn.sendline("output reset") + self.spawn.expect_exact("Successfully reset output buffer.") + self.spawn.sendline("compress init ::") + self.spawn.sendline("set_ack 0 11111111") + self.spawn.expect_exact("ACKs set to 0 ms delay with bitmap 11111111") + self.spawn.sendline("timer reset") + self.spawn.expect_exact("Reset all timers") + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_input_add(self): + self.spawn.sendline("input") + self.spawn.expect_exact("Input buffer is empty.") + self.spawn.sendline("input add 0123456789abcdef") + self.spawn.expect_exact("Successfully added to input buffer") + self.spawn.expect_exact("00000000 01 23 45 67 89 AB CD EF") + self.spawn.sendline("input") + self.spawn.expect_exact("00000000 01 23 45 67 89 AB CD EF") + self.spawn.sendline("input add dead c0ffee 20b07 42") + self.spawn.expect_exact("Successfully added to input buffer") + self.spawn.expect_exact("00000000 DE AD C0 FF EE 20 B0 74 20") + self.spawn.sendline("input") + self.spawn.expect_exact( + "00000000 01 23 45 67 89 AB CD EF DE AD C0 FF EE 20 B0 74" + ) + self.spawn.expect_exact("00000010 20") + + def test_input_add_full_buf_mod_128(self): + for _ in range(0, 128, 16): + self.spawn.sendline("input add {}".format("1a" * 16)) + self.spawn.expect_exact("Successfully added to input buffer") + self.spawn.expect_exact( + "00000000 {}".format(" ".join("1A" for _ in range(16))) + ) + self.spawn.sendline("input") + for offset in range(0, 128, 16): + self.spawn.expect_exact( + "000000{:02x} {}".format(offset, " ".join("1A" for _ in range(16))) + ) + self.spawn.sendline("input add 2b") + self.spawn.expect_exact("Too many bytes added to input buffer") + + def test_input_add_full_buf_not_mod_128(self): + for offset in range(0, 126, 21): + self.spawn.sendline("input add {}".format("1a" * 21)) + self.spawn.expect_exact("Successfully added to input buffer") + self.spawn.expect_exact( + "00000000 {}".format(" ".join("1A" for _ in range(16))) + ) + self.spawn.expect_exact( + "00000010 {}".format(" ".join("1A" for _ in range(5))) + ) + self.spawn.sendline("input add {}".format("1a" * 5)) + self.spawn.expect_exact("Too many bytes added to input buffer") + self.spawn.sendline("input") + for offset in range(0, 128, 16): + self.spawn.expect_exact( + "000000{:02x} {}".format(offset, " ".join("1A" for _ in range(16))) + ) + self.spawn.sendline("input add 2b") + self.spawn.expect_exact("Too many bytes added to input buffer") + + def test_input_usage(self): + self.spawn.sendline("input help") + self.spawn.expect_exact("usage: input {reset|add ...}") + self.spawn.sendline("input add") + self.spawn.expect_exact("usage: input {reset|add ...}") + self.spawn.sendline("input foobar test") + self.spawn.expect_exact("usage: input {reset|add ...}") + + def test_input_reset(self): + self.spawn.sendline("input") + self.spawn.expect_exact("Input buffer is empty.") + self.spawn.sendline("input reset") + self.spawn.expect_exact("Successfully reset input buffer.") + self.spawn.sendline("input") + self.spawn.expect_exact("Input buffer is empty.") + self.spawn.sendline("input add 0123456789abcdef") + self.spawn.expect_exact("Successfully added to input buffer") + self.spawn.expect_exact("00000000 01 23 45 67 89 AB CD EF") + self.spawn.sendline("input") + self.spawn.expect_exact("00000000 01 23 45 67 89 AB CD EF") + self.spawn.sendline("input reset") + self.spawn.expect_exact("Successfully reset input buffer.") + self.spawn.sendline("input") + self.spawn.expect_exact("Input buffer is empty.") + + def test_output_usage(self): + self.spawn.sendline("output help") + self.spawn.expect_exact("usage: output [{reset|copy}]") + + def test_output_show(self): + self.spawn.sendline("output") + self.spawn.expect_exact("Output buffer is empty") + + def test_output_copy(self): + self.spawn.sendline("output copy") + self.spawn.expect_exact("Successfully copied output buffer to input buffer.") + + def test_compress_init_parse_error(self): + self.spawn.sendline("compress init foobar") + self.spawn.expect_exact("Unable to parse source IPv6 address foobar.") + self.spawn.expect_exact( + "usage: compress {{up|down|bi} |init }" + ) + + def test_compress_init_success(self): + self.spawn.sendline("compress init 2001:db8::1") + self.spawn.expect_exact( + "Successfully initialized compressor with source IPv6 address 2001:db8::1" + ) + + def test_compress_usage(self): + self.spawn.sendline("compress") + self.spawn.expect_exact( + "usage: compress {{up|down|bi} |init }" + ) + self.spawn.sendline("compress bi 0") + self.spawn.expect_exact( + "usage: compress {{up|down|bi} |init }" + ) + self.spawn.sendline("compress xyz test 1337") + self.spawn.expect_exact( + "usage: compress {{up|down|bi} |init }" + ) + self.spawn.sendline("compress xyz 1337") + self.spawn.expect_exact( + "usage: compress {{up|down|bi} |init }" + ) + + def test_compress_no_input(self): + self.spawn.sendline("compress up 1") + self.spawn.expect_exact("No input buffer defined") + + def test_compress_wrong_device_id(self): + pkt = ( + IPv6(src="2001:db8:1::1", dst="2001:db8:1::2", hlim=56) + / UDP(sport=0x1F42, dport=0x1F42) + / "the payload" + ) + self.input(pkt) + self.spawn.sendline("compress init {}".format(pkt[IPv6].src)) + self.spawn.sendline("compress down 4") + self.spawn.expect_exact("Unable to compress (maybe wrong device ID 4?)") + + def test_compress_success(self): + pkt = ( + IPv6(src="2001:db8:1::1", dst="2001:db8:1::2", hlim=56) + / UDP(sport=0x1F42, dport=0x1F42) + / "the payload" + ) + self.input(pkt) + self.spawn.sendline("compress init {}".format(pkt[IPv6].src)) + self.spawn.sendline("compress down 1") + self.spawn.expect_exact("Used uncompressed rule 0/8 to generate") + self.expect_od_dump_of(b"\0" + bytes(pkt)) + self.input_reset() + pkt = ( + IPv6( + src="fe80::1", + dst="fe80::2", + ) + / UDP(sport=0x1F41, dport=0x1F42) + / CoAP() + ) + self.spawn.sendline("compress init {}".format(pkt[IPv6].src)) + self.input(pkt) + self.spawn.sendline("compress up 1") + self.spawn.expect_exact("Used rule 3/8 to compress to") + exp_pkt = b"\x03\x30\x90" + self.expect_od_dump_of(exp_pkt) + self.spawn.sendline("output") + self.expect_od_dump_of(exp_pkt) + return exp_pkt + + def test_decompress_usage(self): + self.spawn.sendline("decompress") + self.spawn.expect_exact("usage: decompress {up|down|bi} ") + self.spawn.sendline("decompress bi 0") + self.spawn.expect_exact("usage: decompress {up|down|bi} ") + self.spawn.sendline("decompress xyz test") + self.spawn.expect_exact("usage: decompress {up|down|bi} ") + self.spawn.sendline("decompress xyz 123") + self.spawn.expect_exact("usage: decompress {up|down|bi} ") + + def test_decompress_success(self): + pkt = IPv6(src="2001:db8:1::1", dst="2001:db8:1::2", hlim=56) / UDP( + sport=0x1F42, dport=0x1F42 + ) / "payload" + self.input(b"\0" + bytes(pkt)) + self.spawn.sendline("decompress down 1") + self.expect_od_dump_of(pkt) + pkt = IPv6(src="2001:db8:1::1", dst="2001:db8:1::2", hlim=56) / UDP( + sport=0x1F42, dport=0x1F42 + ) + pkt = b"\x03\x30\x90" + self.input_reset() + self.input(pkt) + self.spawn.sendline("decompress up 1") + exp_pkt = ( + IPv6( + src="fe80::1", + dst="fe80::2", + ) + / UDP(sport=0x1F41, dport=0x1F42) + / CoAP() + ) + self.expect_od_dump_of(exp_pkt) + + def test_decompress_no_input(self): + self.spawn.sendline("decompress up 1") + self.spawn.expect_exact("No input buffer defined") + + def test_set_ack_usage(self): + self.spawn.sendline("set_ack foobar") + self.spawn.expect_exact("usage: set_ack ") + self.spawn.sendline("set_ack foobar 1test") + self.spawn.expect_exact("usage: set_ack ") + self.spawn.sendline("set_ack 12 1test") + self.spawn.expect_exact("usage: set_ack ") + + def test_set_ack_too_large(self): + self.spawn.sendline("set_ack 0 010001100") + self.spawn.expect_exact("010001100 does not fit fragment window size 8") + + def test_set_ack_success(self): + self.spawn.sendline("set_ack") + self.spawn.expect_exact("ACKs set to 0 ms delay with bitmap 11111111") + self.spawn.sendline("set_ack 10 01000110") + self.spawn.expect_exact("ACKs set to 10 ms delay with bitmap 01000110") + self.spawn.sendline("set_ack 5 00000000") + self.spawn.expect_exact("ACKs set to 5 ms delay with bitmap 00000000") + self.spawn.sendline("set_ack 0 11111111") + self.spawn.expect_exact("ACKs set to 0 ms delay with bitmap 11111111") + + def test_timer_success(self): + self.spawn.sendline("timer foobar") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_fragment_usage(self): + self.spawn.sendline("fragment") + self.spawn.expect_exact( + "usage: fragment " + "{no-ack|ack-always|ack-on-error|not-fragmented}" + ) + self.spawn.sendline("fragment 1") + self.spawn.expect_exact( + "usage: fragment " + "{no-ack|ack-always|ack-on-error|not-fragmented}" + ) + self.spawn.sendline("fragment 1 25") + self.spawn.expect_exact( + "usage: fragment " + "{no-ack|ack-always|ack-on-error|not-fragmented}" + ) + self.spawn.sendline("fragment 1 25 5000") + self.spawn.expect_exact( + "usage: fragment " + "{no-ack|ack-always|ack-on-error|not-fragmented}" + ) + self.spawn.sendline("fragment 1 25 5000 foobar") + self.spawn.expect_exact( + "usage: fragment " + "{no-ack|ack-always|ack-on-error|not-fragmented}" + ) + self.spawn.sendline("fragment 1 25 dc no-ack") + self.spawn.expect_exact( + "usage: fragment " + "{no-ack|ack-always|ack-on-error|not-fragmented}" + ) + self.spawn.sendline("fragment 1 mtu 5000 ack-always") + self.spawn.expect_exact( + "usage: fragment " + "{no-ack|ack-always|ack-on-error|not-fragmented}" + ) + self.spawn.sendline("fragment 1 9000 5000 ack-on-error") + self.spawn.expect_exact( + "usage: fragment " + "{no-ack|ack-always|ack-on-error|not-fragmented}" + ) + self.spawn.sendline("fragment x 25 5000 not-fragmented") + self.spawn.expect_exact( + "usage: fragment " + "{no-ack|ack-always|ack-on-error|not-fragmented}" + ) + + def test_fragment_init(self): + self.spawn.sendline("fragment init") + self.spawn.expect_exact("Fragmenter initialized") + + def test_fragment_success_no_frag(self): + pkt = self.test_compress_success() + self.spawn.sendline("output copy") + self.spawn.expect_exact("Successfully copied output buffer to input buffer.") + self.spawn.sendline("fragment init") + self.spawn.sendline("fragment 1 25 5000 no-ack") + self.spawn.expect_exact("TX Fragment 1 on dev 0x1") + self.expect_od_dump_of(pkt) + self.spawn.expect_exact("No fragmentation needed") + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_fragment_success_no_ack(self): + pkt = ( + IPv6( + src="fe80::1", + dst="fe80::2", + ) + / UDP(sport=0x1F41, dport=0x1F42) + / CoAP() + ) + self.input(bytes(pkt)) + self.spawn.sendline("fragment init") + self.spawn.sendline("fragment 1 25 50 no-ack") + self.spawn.expect_exact("TX Fragment 1 on dev 0x1") + self.expect_od_dump_of( + b"\x15\x30\x00\x00\x00\x00\x06\x08\xa0\x7f\x40\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + ) + self.spawn.expect_exact("TX Fragment 2 on dev 0x1") + self.expect_od_dump_of( + b"\x15\x7f\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x87\xd0\x47\xd0\x80\x03\x21\x13" + ) + self.spawn.expect_exact("TX Fragment 3 on dev 0x1") + self.expect_od_dump_of(b"\x15\x98\xa6\x43\xbf\xc8\x00\x00\x00\x00") + self.spawn.expect_exact("TX End") + self.spawn.expect_exact("Fragmented!") + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_fragment_success_ack_always_all1(self): + pkt = ( + IPv6( + src="fe80::1", + dst="fe80::2", + ) + / UDP(sport=0x1F41, dport=0x1F42) + / CoAP() + ) + self.input(pkt) + self.spawn.sendline("fragment init") + self.spawn.sendline("set_ack 0 11111111") + self.spawn.sendline("fragment 1 25 50 ack-always") + self.spawn.expect_exact("TX Fragment 1 on dev 0x1") + self.expect_od_dump_of( + b"\x17\x66\x00\x00\x00\x00\x00\xc1\x14\x0f\xe8\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00" + ) + self.spawn.expect_exact("Simulate ACK") + self.expect_od_dump_of(b"\x17\x40") + self.spawn.expect_exact("TX Fragment 2 on dev 0x1") + self.expect_od_dump_of( + b"\x17\x51\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" + b"\x1f\x41\x1f\x42\x00\x0c\x84" + ) + self.spawn.expect_exact("Simulate ACK") + self.expect_od_dump_of(b"\x17\x40") + self.spawn.expect_exact("TX Fragment 3 on dev 0x1") + self.expect_od_dump_of(b"\x17\x73\x14\xc8\x77\xf4\xe4\x00\x00\x00\x00") + self.spawn.expect_exact("Simulate ACK") + self.expect_od_dump_of(b"\x17\x40") + self.spawn.expect_exact("TX Fragment 4 on dev 0x1") + self.expect_od_dump_of(b"\x17\x73\x14\xc8\x77\xf0") + self.spawn.expect_exact("Simulate ACK") + self.expect_od_dump_of(b"\x17\x40") + self.spawn.expect_exact("TX End") + self.spawn.expect_exact("Fragmented!") + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_fragment_success_ack_always_001(self): + pkt = ( + IPv6( + src="fe80::1", + dst="fe80::2", + ) + / UDP(sport=0x1F41, dport=0x1F42) + / CoAP() + ) + self.input(pkt) + self.spawn.sendline("fragment init") + self.spawn.sendline("set_ack 10 00111111") + self.spawn.sendline("fragment 1 25 50 ack-always") + self.spawn.expect_exact("TX Fragment 1 on dev 0x1") + self.expect_od_dump_of( + b"\x17\x66\x00\x00\x00\x00\x00\xc1\x14\x0f\xe8\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00" + ) + self.spawn.expect_exact("Simulate ACK") + self.expect_od_dump_of(b"\x17\x1f") + self.spawn.expect_exact("TX Fragment 2 on dev 0x1") + self.expect_od_dump_of( + b"\x17\x51\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" + b"\x1f\x41\x1f\x42\x00\x0c\x84" + ) + self.spawn.expect_exact("Simulate ACK") + self.expect_od_dump_of(b"\x17\x5f") + self.spawn.expect_exact("TX Fragment 3 on dev 0x1") + self.expect_od_dump_of(b"\x17\x73\x14\xc8\x77\xf4\xe4\x00\x00\x00\x00") + self.spawn.expect_exact("Simulate ACK") + self.expect_od_dump_of(b"\x17\x40") + self.spawn.expect_exact("TX Fragment 4 on dev 0x1") + self.expect_od_dump_of(b"\x17\x73\x14\xc8\x77\xf0") + self.spawn.expect_exact("Simulate ACK") + self.expect_od_dump_of(b"\x17\x40") + self.spawn.expect_exact("TX End") + self.spawn.expect_exact("Fragmented!") + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_fragment_success_ack_on_error_all1(self): + pkt = ( + IPv6( + src="fe80::1", + dst="fe80::2", + ) + / UDP(sport=0x1F41, dport=0x1F42) + / CoAP() + ) + self.input(pkt) + self.spawn.sendline("fragment init") + self.spawn.sendline("set_ack 0 11111111") + self.spawn.sendline("fragment 1 25 50 ack-on-error") + self.spawn.expect_exact("TX Fragment 1 on dev 0x1") + self.expect_od_dump_of( + b"\x16\x66\x00\x00\x00\x00\x00\xc1\x14\x0f\xe8\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00" + ) + self.spawn.expect_exact("TX Fragment 2 on dev 0x1") + self.expect_od_dump_of( + b"\x16\x51\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" + b"\x1f\x41\x1f\x42\x00\x0c\x84" + ) + self.spawn.expect_exact("TX Fragment 3 on dev 0x1") + self.expect_od_dump_of(b"\x16\x73\x14\xc8\x77\xf4\xe4\x00\x00\x00\x00") + self.spawn.expect_exact("TX End") + self.spawn.expect_exact("Fragmented") + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_fragment_success_ack_on_error_10(self): + pkt = ( + IPv6( + src="fe80::1", + dst="fe80::2", + ) + / UDP(sport=0x1F41, dport=0x1F42) + / CoAP() + ) + self.input(pkt) + self.spawn.sendline("fragment init") + self.spawn.sendline("set_ack 0 10111111") + self.spawn.sendline("fragment 1 25 50 ack-on-error") + self.spawn.expect_exact("TX Fragment 1 on dev 0x") + self.expect_od_dump_of( + b"\x16\x66\x00\x00\x00\x00\x00\xc1\x14\x0f\xe8\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00" + ) + self.spawn.expect_exact("Simulate ACK") + self.expect_od_dump_of(b"\x16\x5f") + self.spawn.expect_exact("TX Fragment 2 on dev 0x") + self.expect_od_dump_of( + b"\x16\x51\xFE\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" + b"\x1F\x41\x1F\x42\x00\x0C\x84" + ) + self.spawn.expect_exact("TX Fragment 3 on dev 0x") + self.expect_od_dump_of(b"\x16\x73\x14\xc8\x77\xf4\xe4\x00\x00\x00\x00") + self.spawn.expect_exact("TX End") + self.spawn.expect_exact("Fragmented") + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_fragment_no_input(self): + self.spawn.sendline("compress init fe80::1") + self.spawn.sendline("fragment init") + self.spawn.sendline("fragment 1 25 5000 no-ack") + self.spawn.expect_exact("No input buffer defined") + + def test_fragment_no_rule(self): + pkt = ( + IPv6( + src="2001:db8::1", + dst="2001:db8::2", + hlim=64, + ) + / UDP(sport=0x1F40, dport=0x1F40) + / CoAP(paymark=b"\xff") + / (2 * "0123456789abcdef") + ) + self.input(pkt) + self.spawn.sendline("compress init {}".format(pkt[IPv6].src)) + self.spawn.sendline("fragment init") + self.spawn.sendline("fragment 4 25 5000 ack-always") + self.spawn.expect_exact( + "No fragmentation rule known for mode ack-always on device 4" + ) + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_reassemble_usage(self): + self.spawn.sendline("reassemble") + self.spawn.expect_exact("usage: reassemble ") + self.spawn.sendline("reassemble foobar") + self.spawn.expect_exact("usage: reassemble ") + self.spawn.sendline("reassemble test 5000") + self.spawn.expect_exact("usage: reassemble ") + self.spawn.sendline("reassemble 1 foobar") + self.spawn.expect_exact("usage: reassemble ") + + def test_reassemble_no_input(self): + self.spawn.sendline("reassemble init") + self.spawn.sendline("reassemble 1 100") + self.spawn.expect_exact("No input buffer defined") + + def test_reassemble_no_frag(self): + pkt = b"\x03\x30\x90" + self.input(pkt) + self.spawn.sendline("reassemble init") + self.spawn.sendline("reassemble 1 100") + self.spawn.expect_exact("RX Unfragmented on dev 0x1") + self.expect_od_dump_of(pkt) + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_reassemble_success_no_ack(self): + self.input( + b"\x15\x30\x00\x00\x00\x00\x06\x08\xa0\x7f\x40\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00\x00" + ) + self.spawn.sendline("reassemble init") + self.spawn.sendline("reassemble 1 100") + self.input_reset() + self.input( + b"\x15\x7f\xa0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + b"\x87\xd0\x47\xd0\x80\x03\x21\x13" + ) + self.spawn.sendline("reassemble 1 100") + self.input_reset() + self.input(b"\x15\x98\xa6\x43\xbf\xc8\x00\x00\x00\x00") + self.spawn.sendline("reassemble 1 100") + self.spawn.expect_exact("RX Reassembly complete on dev 0x1") + self.expect_od_dump_of( + bytes( + IPv6( + src="fe80::1", + dst="fe80::2", + ) + / UDP(sport=0x1F41, dport=0x1F42) + / CoAP() + ) + ) + free_timers = 0 + total_timers = 4 + while free_timers != total_timers: + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + free_timers = int(self.spawn.match.group(1)) + total_timers = int(self.spawn.match.group(2)) + + def test_reassemble_success_ack_always(self): + self.input( + b"\x17\x66\x00\x00\x00\x00\x00\xc1\x14\x0f\xe8\x00\x00\x00\x00\x00\x00\x00" + b"\x00\x00\x00\x00\x00\x00\x00" + ) + self.spawn.sendline("reassemble init") + self.spawn.sendline("reassemble 1 100") + self.input_reset() + self.input( + b"\x17\x51\xfe\x80\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02" + b"\x1f\x41\x1f\x42\x00\x0c\x84" + ) + self.spawn.sendline("reassemble 1 100") + self.input_reset() + self.input(b"\x17\x73\x14\xc8\x77\xf4\xe4\x00\x00\x00\x00") + self.spawn.sendline("reassemble 1 100") + self.spawn.expect_exact("Packet sent on dev 0x1") + self.expect_od_dump_of(b"\x17\x40") + self.expect_od_dump_of( + IPv6( + src="fe80::1", + dst="fe80::2", + ) + / UDP(sport=0x1F41, dport=0x1F42) + / CoAP() + ) + free_timers = 0 + total_timers = 4 + while free_timers != total_timers: + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + free_timers = int(self.spawn.match.group(1)) + total_timers = int(self.spawn.match.group(2)) + + +class TestFragmentation(PexpectTestCase, InputMixin): + LOGFILE = sys.stdout if DEBUG_TESTS else None + + def tearDown(self): + self.input_reset() + self.spawn.sendline("output reset") + self.spawn.sendline("compress init ::") + self.spawn.expect_exact("Successfully reset input buffer.") + self.spawn.sendline("set_ack 0 11111111") + self.spawn.expect_exact("ACKs set to 0 ms delay with bitmap 11111111") + self.spawn.sendline("timer reset") + self.spawn.expect_exact("Reset all timers") + self.spawn.sendline("timer") + self.spawn.expect(r"Free timers: (\d+) \(of (\d+)\)") + self.assertEqual(int(self.spawn.match.group(1)), int(self.spawn.match.group(2))) + + def test_compress_rule_2_up(self): + pkt = ( + IPv6( + src="2001:db8:1::1", + dst="2001:db8:2::2", + ) + / UDP(sport=0x1389, dport=0x1388) + / CoAP( + type="ACK", + tkl=4, + code="2.05 Content", + msg_id=0x23BC, + token=b"TOK!", + paymark=b"\xff", + ) + / "payload" + ) + self.spawn.sendline("compress init {}".format(pkt[IPv6].src)) + self.input(pkt) + self.spawn.sendline("compress up 1") + self.spawn.expect_exact("Used rule 2/8 to compress to") + exp_pkt = [ + 0x02, # rule ID = 2 + # IP6_APPPRE mapping index = 1 (2b), + # COAP_T = 2 [ACK] (2b) + # COAP_MID LSB = 0xC (4b) + (1 << 6) | (2 << 4) | (0xC), + # Token + ord("T"), + ord("O"), + ord("K"), + ord("!"), + # payload + ord("p"), + ord("a"), + ord("y"), + ord("l"), + ord("o"), + ord("a"), + ord("d"), + ] + self.expect_od_dump_of(exp_pkt) + + def test_decompress_rule_2_up(self): + exp_pkt = ( + IPv6( + src="2001:db8:1::1", + dst="2001:db8:2::2", + ) + / UDP(sport=0x1389, dport=0x1388) + / CoAP( + type="ACK", + tkl=4, + code="2.05 Content", + msg_id=0x23BC, + token=b"TOK!", + paymark=b"\xff", + ) + / "payload" + ) + self.spawn.sendline("decompress init {}".format(exp_pkt[IPv6].src)) + pkt = [ + # Rule ID: 2 + 0x02, + # IP6_APPPRE mapping index = 1 (2b), + # COAP_T = 2 [ACK] (2b) (libSCHC bug, should be 2) + # COAP_MID LSB = 0xC (4b) + (1 << 6) | (2 << 4) | (0xC), + # Token + ord("T"), + ord("O"), + ord("K"), + ord("!"), + # payload + ord("p"), + ord("a"), + ord("y"), + ord("l"), + ord("o"), + ord("a"), + ord("d"), + ] + self.input(pkt) + self.spawn.sendline("decompress up 1") + self.spawn.expect_exact("Decompressed to") + self.expect_od_dump_of(exp_pkt) + + def test_compress_rule_2_down(self): + pkt = ( + IPv6( + src="2001:db8:2::2", + dst="2001:db8:1::1", + hlim=64, + ) + / UDP(sport=0x1388, dport=0x1389) + / CoAP( + type="CON", + tkl=4, + code="GET", + msg_id=0x23BC, + token=b"TOK!", + options=[("Uri-Path", "temp")], + ) + ) + self.spawn.sendline("compress init {}".format(pkt[IPv6].src)) + self.input(pkt) + self.spawn.sendline("compress down 1") + self.spawn.expect_exact("Used rule 2/8 to compress to") + exp_pkt = [ + # Rule ID: 2 + 0x02, + # Hop-Limit: 64 (8b) + 64, + # IP6_APPPRE mapping index = 1 (2b), + # COAP_T = 0 [CON] (2b) + # COAP_MID[0] 4 msb = 0x23 >> 4 + (1 << 6) | (0 << 4) | (0x23 >> 4), + # COAP_MID[0] 4 lsb = (0x23 << 4) & 0xff + # COAP_MID[1] 4 msb = 0xBC >> 4 + ((0x23 << 4) & 0xFF) | (0xBC >> 4), + # COAP_MID[1] 4 lsb = (0xBC << 4) & 0xff + # Token[0] 4 msb = ord("T") >> 4 + ((0xBC << 4) & 0xFF) | (ord("T") >> 4), + # Token[0] 4 lsb = ord("T") >> 4 + # Token[1] 4 msb = (ord("O") << 4) & 0xff + ((ord("T") << 4) & 0xFF) | (ord("O") >> 4), + # Token[1] 4 lsb = ord("O") >> 4 + # Token[2] 4 msb = (ord("K") << 4) & 0xff + ((ord("O") << 4) & 0xFF) | (ord("K") >> 4), + # Token[2] 4 lsb = ord("K") >> 4 + # Token[3] 4 msb = (ord("!") << 4) & 0xff + ((ord("K") << 4) & 0xFF) | (ord("!") >> 4), + # Token[2] 4 lsb = ord("K") >> 4 + # 4b padding + ((ord("!") << 4) & 0xFF), + ] + self.expect_od_dump_of(exp_pkt) + + def test_decompress_rule_2_down(self): + exp_pkt = ( + IPv6( + src="2001:db8:2::2", + dst="2001:db8:1::1", + hlim=64, + ) + / UDP(sport=0x1388, dport=0x1389) + / CoAP( + type="CON", + tkl=4, + code="GET", + msg_id=0x23BC, + token=b"TOK!", + options=[("Uri-Path", "temp")], + ) + ) + self.spawn.sendline("decompress init {}".format(exp_pkt[IPv6].src)) + pkt = [ + # Rule ID: 2 + 0x02, + # Hop-Limit: 64 (8b) + 64, + # IP6_APPPRE mapping index = 1 (2b), + # COAP_T = 0 [CON] (2b) + # COAP_MID[0] 4 msb = 0x23 >> 4 + (1 << 6) | (0 << 4) | (0x23 >> 4), + # COAP_MID[0] 4 lsb = (0x23 << 4) & 0xff + # COAP_MID[1] 4 msb = 0xBC >> 4 + ((0x23 << 4) & 0xFF) | (0xBC >> 4), + # COAP_MID[1] 4 lsb = (0xBC << 4) & 0xff + # Token[0] 4 msb = ord("T") >> 4 + ((0xBC << 4) & 0xFF) | (ord("T") >> 4), + # Token[0] 4 lsb = ord("T") >> 4 + # Token[1] 4 msb = (ord("O") << 4) & 0xff + ((ord("T") << 4) & 0xFF) | (ord("O") >> 4), + # Token[1] 4 lsb = ord("O") >> 4 + # Token[2] 4 msb = (ord("K") << 4) & 0xff + ((ord("O") << 4) & 0xFF) | (ord("K") >> 4), + # Token[2] 4 lsb = ord("K") >> 4 + # Token[3] 4 msb = (ord("!") << 4) & 0xff + ((ord("K") << 4) & 0xFF) | (ord("!") >> 4), + # Token[2] 4 lsb = ord("K") >> 4 + # 4b padding + ((ord("!") << 4) & 0xFF), + ] + self.input(pkt) + self.spawn.sendline("decompress down 1") + self.spawn.expect_exact("Decompressed to") + self.expect_od_dump_of(exp_pkt) + + +if __name__ == "__main__": + load_contrib("coap") + unittest.main(verbosity=2) diff --git a/tests/pkg_libschc/unittests.c b/tests/pkg_libschc/unittests.c new file mode 100644 index 000000000000..ef6caa85ec48 --- /dev/null +++ b/tests/pkg_libschc/unittests.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2022 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. + */ + +/** + * @{ + * + * @file + * @author Martine S. Lenders + */ + +#include "embUnit.h" + +#include "bit_operations.h" + +static void test_copy_bits__1bit_shift(void) +{ + static const uint8_t src[] = { 0xE0, 0xBC, 0x7D, 0xFF, 0xFE, 0xCB, 0xF5, 0x50 }; + static const uint8_t exp_dst[] = { + (0xE0 >> 1), + ((0xE0 << 7) & 0xFF) | 0xBC >> 1, + ((0xBC << 7) & 0xFF) | 0x7D >> 1, + ((0x7D << 7) & 0xFF) | 0xFF >> 1, + ((0xFF << 7) & 0xFF) | 0xFE >> 1, + ((0xFE << 7) & 0xFF) | 0xCB >> 1, + ((0xCB << 7) & 0xFF) | 0xF5 >> 1, + ((0xF5 << 7) & 0xFF) | 0x50 >> 1, + ((0x50 << 7) & 0xFF) + }; + static uint8_t dst[sizeof(exp_dst)] = { 0 }; + + copy_bits(dst, 1, src, 0, sizeof(src) * 8); + TEST_ASSERT_EQUAL_INT(sizeof(exp_dst), sizeof(dst)); + for (unsigned i = 0; i < sizeof(exp_dst); i++) { + TEST_ASSERT_EQUAL_INT(exp_dst[i], dst[i]); + } +} + +static void test_copy_bits__2bit_shift(void) +{ + static const uint8_t src[] = { + 0x80, 0x00, 0x94, 0x11, 0xB9, 0x14, 0x00, 0x02, 0x48, 0x32, 0x9A, 0x00 + }; + static const uint8_t exp_dst[] = { + (0x80 >> 2), + ((0x80 << 6) & 0xFF) | 0x00 >> 2, + ((0x00 << 6) & 0xFF) | 0x94 >> 2, + ((0x94 << 6) & 0xFF) | 0x11 >> 2, + ((0x11 << 6) & 0xFF) | 0xB9 >> 2, + ((0xB9 << 6) & 0xFF) | 0x14 >> 2, + ((0x14 << 6) & 0xFF) | 0x00 >> 2, + ((0x00 << 6) & 0xFF) | 0x02 >> 2, + ((0x02 << 6) & 0xFF) | 0x48 >> 2, + ((0x48 << 6) & 0xFF) | 0x32 >> 2, + ((0x32 << 6) & 0xFF) | 0x9A >> 2, + ((0x9A << 6) & 0xFF) | 0x00 >> 2, + ((0x00 << 6) & 0xFF), + }; + static uint8_t dst[sizeof(exp_dst)] = { 0 }; + + copy_bits(dst, 2, src, 0, sizeof(src) * 8); + TEST_ASSERT_EQUAL_INT(sizeof(exp_dst), sizeof(dst)); + for (unsigned i = 0; i < sizeof(exp_dst); i++) { + TEST_ASSERT_EQUAL_INT(exp_dst[i], dst[i]); + } +} + +int unittests(int argc, char **argv) +{ + (void)argc; + (void)argv; + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_copy_bits__1bit_shift), + new_TestFixture(test_copy_bits__2bit_shift), + }; + EMB_UNIT_TESTCALLER(libschc_tests, NULL, NULL, fixtures); + TESTS_START(); + TESTS_RUN((Test *)&libschc_tests); + TESTS_END(); + return 0; +} + +/** @} */