From 27f4939211a03292ee1bb7646d6ef58d8fe3f9f3 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Mon, 28 Oct 2019 18:57:44 +0100 Subject: [PATCH 01/10] gnrc_sixlowpan_frag_vrb: add reverse look-up To label switch ACKs for fragments back to the originator, a reverse look-up in the VRB is required. --- sys/include/net/gnrc/sixlowpan/frag/vrb.h | 17 ++++++++++++++ .../frag/vrb/gnrc_sixlowpan_frag_vrb.c | 23 +++++++++++++++++++ 2 files changed, 40 insertions(+) diff --git a/sys/include/net/gnrc/sixlowpan/frag/vrb.h b/sys/include/net/gnrc/sixlowpan/frag/vrb.h index 3bb859195a7c..462a82e971a6 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/vrb.h +++ b/sys/include/net/gnrc/sixlowpan/frag/vrb.h @@ -117,6 +117,23 @@ void gnrc_sixlowpan_frag_vrb_gc(void); gnrc_sixlowpan_frag_vrb_t *gnrc_sixlowpan_frag_vrb_get( const uint8_t *src, size_t src_len, unsigned src_tag); +/** + * @brief Reverse VRB lookup + * + * @param[in] netif Network interface the reverse label-switched packet + * came over + * @param[in] src Link-layer source address of reverse label-switched + * packet. + * @param[in] src_len Length of @p src. + * @param[in] tag Tag of the reverse label-switched packet. + * + * @return The VRB entry with `vrb->super.dst == src` and `vrb->out_tag == tag`. + * @return NULL, if there is no entry in the VRB that has these values. + */ +gnrc_sixlowpan_frag_vrb_t *gnrc_sixlowpan_frag_vrb_reverse( + const gnrc_netif_t *netif, const uint8_t *src, size_t src_len, + unsigned tag); + /** * @brief Removes an entry from the VRB * diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/vrb/gnrc_sixlowpan_frag_vrb.c b/sys/net/gnrc/network_layer/sixlowpan/frag/vrb/gnrc_sixlowpan_frag_vrb.c index 00159c3228c8..1e6c7797fb05 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/frag/vrb/gnrc_sixlowpan_frag_vrb.c +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/vrb/gnrc_sixlowpan_frag_vrb.c @@ -184,6 +184,29 @@ gnrc_sixlowpan_frag_vrb_t *gnrc_sixlowpan_frag_vrb_get( return NULL; } +gnrc_sixlowpan_frag_vrb_t *gnrc_sixlowpan_frag_vrb_reverse( + const gnrc_netif_t *netif, const uint8_t *src, size_t src_len, + unsigned tag) +{ + DEBUG("6lo vrb: trying to get entry for reverse label switching (%s, %u)\n", + gnrc_netif_addr_to_str(src, src_len, addr_str), tag); + for (unsigned i = 0; i < CONFIG_GNRC_SIXLOWPAN_FRAG_VRB_SIZE; i++) { + gnrc_sixlowpan_frag_vrb_t *vrbe = &_vrb[i]; + + if ((vrbe->out_tag == tag) && (vrbe->out_netif == netif) && + (memcmp(vrbe->super.dst, src, src_len) == 0)) { + DEBUG("6lo vrb: got VRB entry from (%s, %u)\n", + gnrc_netif_addr_to_str(vrbe->super.src, + vrbe->super.src_len, + addr_str), vrbe->super.tag); + return vrbe; + } + } + DEBUG("6lo vrb: no entry found\n"); + return NULL; + +} + void gnrc_sixlowpan_frag_vrb_gc(void) { uint32_t now_usec = xtimer_now_usec(); From 0af8a1600ad43cc17e7fc8c3a4acd16b71edd5f8 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Mon, 28 Oct 2019 13:47:47 +0100 Subject: [PATCH 02/10] sixlowpan|gnrc_sixlowpan: change SFR draft to RFC in doc --- sys/include/net/gnrc/sixlowpan/config.h | 4 ++-- sys/include/net/gnrc/sixlowpan/frag/rb.h | 8 ++++---- sys/include/net/sixlowpan.h | 9 +++------ sys/include/net/sixlowpan/sfr.h | 9 +++------ 4 files changed, 12 insertions(+), 18 deletions(-) diff --git a/sys/include/net/gnrc/sixlowpan/config.h b/sys/include/net/gnrc/sixlowpan/config.h index e4017146a3c5..f3e0a580880c 100644 --- a/sys/include/net/gnrc/sixlowpan/config.h +++ b/sys/include/net/gnrc/sixlowpan/config.h @@ -163,8 +163,8 @@ extern "C" { /** * @name Selective fragment recovery configuration - * @see [draft-ietf-6lo-fragment-recovery-07, section 7.1] - * (https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-07#section-7.1) + * @see [RFC 8931, section 7.1] + * (https://tools.ietf.org/html/rfc8931#section-7.1) * @note Only applicable with gnrc_sixlowpan_frag_sfr module * @{ */ diff --git a/sys/include/net/gnrc/sixlowpan/frag/rb.h b/sys/include/net/gnrc/sixlowpan/frag/rb.h index 3909edad30b0..ff5dfb825996 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/rb.h +++ b/sys/include/net/gnrc/sixlowpan/frag/rb.h @@ -130,8 +130,8 @@ gnrc_sixlowpan_frag_rb_t *gnrc_sixlowpan_frag_rb_add(gnrc_netif_hdr_t *netif_hdr * * @note datagram_size is not a search parameter as the primary use case * for this function is [Selective Fragment Recovery] - * (https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-05) - * where this information only exists in the first fragment. + * (https://tools.ietf.org/html/rfc8931) where this information only + * exists in the first fragment. * * @return true, if an entry with the given tuple exist. * @return false, if no entry with the given tuple exist. @@ -151,8 +151,8 @@ bool gnrc_sixlowpan_frag_rb_exists(const gnrc_netif_hdr_t *netif_hdr, * * @note datagram_size is not a search parameter as the primary use case * for this function is [Selective Fragment Recovery] - * (https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-05) - * where this information only exists in the first fragment. + * (https://tools.ietf.org/html/rfc8931) where this information only + * exists in the first fragment. */ void gnrc_sixlowpan_frag_rb_rm_by_datagram(const gnrc_netif_hdr_t *netif_hdr, uint16_t tag); diff --git a/sys/include/net/sixlowpan.h b/sys/include/net/sixlowpan.h index 7e4dcd27df7d..773b60048cfb 100644 --- a/sys/include/net/sixlowpan.h +++ b/sys/include/net/sixlowpan.h @@ -63,22 +63,19 @@ extern "C" { /** * @brief Dispatch mask for 6LoWPAN selective fragment recovery - * @see [draft-ietf-6lo-fragment-recovery-05, - * section 5](https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-05#section-5) + * @see [RFC 8931, section 5](https://tools.ietf.org/html/rfc8931#section-5) */ #define SIXLOWPAN_SFR_DISP_MASK (0xfe) /** * @brief Dispatch for 6LoWPAN recoverable fragment - * @see [draft-ietf-6lo-fragment-recovery-05, section - * 5.1](https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-05#section-5.1) + * @see [RFC 8931, section 5.1](https://tools.ietf.org/html/rfc8931#section-5.1) */ #define SIXLOWPAN_SFR_RFRAG_DISP (0xe8) /** * @brief Dispatch for 6LoWPAN recoverable fragment acknowledgment - * @see [draft-ietf-6lo-fragment-recovery-05, section - * 5.2](https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-05#section-5.2) + * @see [RFC 8931, section 5.2](https://tools.ietf.org/html/rfc8931#section-5.2) */ #define SIXLOWPAN_SFR_ACK_DISP (0xea) diff --git a/sys/include/net/sixlowpan/sfr.h b/sys/include/net/sixlowpan/sfr.h index b2a2fe21b24b..492da358d9b4 100644 --- a/sys/include/net/sixlowpan/sfr.h +++ b/sys/include/net/sixlowpan/sfr.h @@ -57,8 +57,7 @@ extern "C" { /** * @brief Generic type for selective fragment recovery headers * - * @see [draft-ietf-6lo-fragment-recovery-05, section - * 5](https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-05#section-5) + * @see [RFC 8931, section 5](https://tools.ietf.org/html/rfc8931#section-5) */ typedef struct __attribute__((packed)) { /** @@ -92,8 +91,7 @@ typedef struct __attribute__((packed)) { /** * @brief Recoverable fragment header * - * @see [draft-ietf-6lo-fragment-recovery-05, section - * 5.1](https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-05#section-5.1) + * @see [RFC 8931, section 5.1](https://tools.ietf.org/html/rfc8931#section-5.1) */ typedef struct __attribute__((packed)) { sixlowpan_sfr_t base; /**< generic part */ @@ -137,8 +135,7 @@ typedef struct __attribute__((packed)) { /** * @brief Recoverable fragment (RFRAG) acknowledgment header * - * @see [draft-ietf-6lo-fragment-recovery-05, section - * 5.2](https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-05#section-5.2) + * @see [RFC 8931, section 5.2](https://tools.ietf.org/html/rfc8931#section-5.2) */ typedef struct __attribute__((packed)) { sixlowpan_sfr_t base; /**< generic part */ From 82c4d263e8b32bed854ce2b90f0c15e46ebf6d38 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Thu, 29 Oct 2020 22:15:29 +0100 Subject: [PATCH 03/10] gnrc_sixlowpan_config: rename SRF parameters for Kconfig --- sys/include/net/gnrc/sixlowpan/config.h | 79 ++++++++++++++----------- 1 file changed, 45 insertions(+), 34 deletions(-) diff --git a/sys/include/net/gnrc/sixlowpan/config.h b/sys/include/net/gnrc/sixlowpan/config.h index f3e0a580880c..ce344608ca0e 100644 --- a/sys/include/net/gnrc/sixlowpan/config.h +++ b/sys/include/net/gnrc/sixlowpan/config.h @@ -171,8 +171,8 @@ extern "C" { /** * @brief Default minimum value for fragment size (MinFragmentSize) */ -#ifndef GNRC_SIXLOWPAN_SFR_MIN_FRAG_SIZE -#define GNRC_SIXLOWPAN_SFR_MIN_FRAG_SIZE (96U) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_MIN_FRAG_SIZE +#define CONFIG_GNRC_SIXLOWPAN_SFR_MIN_FRAG_SIZE 96U #endif /** @@ -182,32 +182,37 @@ extern "C" { * the chances of buffer bloat and transmission loss. The value must be less * than 512 if the unit is defined for the PHY layer is the octet. */ -#ifndef GNRC_SIXLOWPAN_SFR_MAX_FRAG_SIZE -#define GNRC_SIXLOWPAN_SFR_MAX_FRAG_SIZE (112U) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_MAX_FRAG_SIZE +#define CONFIG_GNRC_SIXLOWPAN_SFR_MAX_FRAG_SIZE 112U #endif /** * @brief Default value for fragment size that the sender should use to start * with (OptFragmentSize) + * + * @pre Must be inclusively between + * @ref CONFIG_GNRC_SIXLOWPAN_SFR_MIN_FRAG_SIZE and + * @ref CONFIG_GNRC_SIXLOWPAN_SFR_MAX_FRAG_SIZE */ -#ifndef GNRC_SIXLOWPAN_SFR_OPT_FRAG_SIZE -#define GNRC_SIXLOWPAN_SFR_OPT_FRAG_SIZE (GNRC_SIXLOWPAN_SFR_MAX_FRAG_SIZE) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_OPT_FRAG_SIZE +#define CONFIG_GNRC_SIXLOWPAN_SFR_OPT_FRAG_SIZE CONFIG_GNRC_SIXLOWPAN_SFR_MAX_FRAG_SIZE #endif /** * @brief Indicates whether the sender should react to ECN (UseECN) * - * When the sender reacts to ECN its window size will vary between @ref - * GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE and @ref GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE. + * When the sender reacts to Explicit Congestion Notification (ECN) its window + * size will vary between @ref CONFIG_GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE and @ref + * CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE. */ -#define GNRC_SIXLOWPAN_SFR_USE_ECN (0U) +#define CONFIG_GNRC_SIXLOWPAN_SFR_USE_ECN 0U /** * @brief Default minimum value of window size that the sender can use * (MinWindowSize) */ -#ifndef GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE -#define GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE (1U) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE +#define CONFIG_GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE 1U #endif /** @@ -216,16 +221,20 @@ extern "C" { * * @warning **Must** be lesser than 32. */ -#ifndef GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE -#define GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE (16U) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE +#define CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE 16U #endif /** * @brief Default value of window size that the sender should start with * (OptWindowSize) + * + * @pre Must be inclusively between + * @ref CONFIG_GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE and + * @ref CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE */ -#ifndef GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE -#define GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE (16U) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE +#define CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE 16U #endif /** @@ -240,51 +249,53 @@ extern "C" { * ratio of air and memory in intermediate nodes that a particular datagram will * use. */ -#ifndef GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US -#define GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US (100U) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US +#define CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US 100U #endif /** - * @brief Default minimum amount of time in milliseconds a node should wait - * for an RFRAG Acknowledgment before it takes a next action + * @brief Minimum RFRAG-ACK timeout in msec before a node takes a next action * (MinARQTimeOut) */ -#ifndef GNRC_SIXLOWPAN_SFR_MIN_ARQ_TIMEOUT_MS -#define GNRC_SIXLOWPAN_SFR_MIN_ARQ_TIMEOUT_MS (350U) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_MIN_ARQ_TIMEOUT_MS +#define CONFIG_GNRC_SIXLOWPAN_SFR_MIN_ARQ_TIMEOUT_MS 350U #endif /** - * @brief Default maximum amount of time in milliseconds a node should wait - * for an RFRAG Acknowledgment before it takes a next action + * @brief Maximum RFRAG-ACK timeout in msec before a node takes a next action * (MaxARQTimeOut) */ -#ifndef GNRC_SIXLOWPAN_SFR_MAX_ARQ_TIMEOUT_MS -#define GNRC_SIXLOWPAN_SFR_MAX_ARQ_TIMEOUT_MS (700U) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_MAX_ARQ_TIMEOUT_MS +#define CONFIG_GNRC_SIXLOWPAN_SFR_MAX_ARQ_TIMEOUT_MS 700U #endif /** - * @brief Default starting point of the value of the amount of time in - * milliseconds that a sender should wait for an RFRAG Acknowledgment - * before it takes a next action (OptARQTimeOut) + * @brief Default RFRAG-ACK timeout in msec before a node takes a next action + * (OptARQTimeOut) + * + * @pre Must be inclusively between + * @ref CONFIG_GNRC_SIXLOWPAN_SFR_MIN_ARQ_TIMEOUT_MS and + * @ref CONFIG_GNRC_SIXLOWPAN_SFR_MAX_ARQ_TIMEOUT_MS */ -#ifndef GNRC_SIXLOWPAN_SFR_OPT_ARQ_TIMEOUT_MS -#define GNRC_SIXLOWPAN_SFR_OPT_ARQ_TIMEOUT_MS (GNRC_SIXLOWPAN_SFR_MAX_ARQ_TIMEOUT_MS) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_OPT_ARQ_TIMEOUT_MS +#define CONFIG_GNRC_SIXLOWPAN_SFR_OPT_ARQ_TIMEOUT_MS \ + CONFIG_GNRC_SIXLOWPAN_SFR_MAX_ARQ_TIMEOUT_MS #endif /** * @brief The maximum number of retries for a particular fragment * (MaxFragRetries) */ -#ifndef GNRC_SIXLOWPAN_SFR_FRAG_RETRIES -#define GNRC_SIXLOWPAN_SFR_FRAG_RETRIES (2U) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES +#define CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES 2U #endif /** * @brief The maximum number of retries from scratch for a particular * datagram (MaxDatagramRetries) */ -#ifndef GNRC_SIXLOWPAN_SFR_DG_RETRIES -#define GNRC_SIXLOWPAN_SFR_DG_RETRIES (0U) +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_DG_RETRIES +#define CONFIG_GNRC_SIXLOWPAN_SFR_DG_RETRIES 0U #endif /** @} */ From 80ff517eba8ef701b56752e87210d974b1afb16a Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 27 Sep 2019 12:10:47 +0200 Subject: [PATCH 04/10] gnrc_netif: 6lo.h: add flags to indicate SRF capability --- sys/include/net/gnrc/netif/6lo.h | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sys/include/net/gnrc/netif/6lo.h b/sys/include/net/gnrc/netif/6lo.h index 2c8b57b00989..766d1b5ac3d1 100644 --- a/sys/include/net/gnrc/netif/6lo.h +++ b/sys/include/net/gnrc/netif/6lo.h @@ -18,12 +18,34 @@ #ifndef NET_GNRC_NETIF_6LO_H #define NET_GNRC_NETIF_6LO_H +#include #include #ifdef __cplusplus extern "C" { #endif +/** + * @name Local 6LoWPAN capability flags + * @anchor net_gnrc_netif_6lo_local_flags + * @see gnrc_netif_6lo_t::local_flags + * + * Like the the capability flags in the [6LoWPAN Capability Indication Option + * (6CIO)](https://tools.ietf.org/html/rfc7400#section-3.3) are less about + * hardware capabilities than about the implementation status within the + * network. For the flags in this group it is currently undefined how to + * exchange the capabilities between nodes, but they might be added to the 6CIO + * at a later point. Once the 6CIO is implemented in GNRC and the flag is + * supported by it, the corresponding flag in these local flags can be removed. + * @{ + */ +/** + * @brief Selective Fragment Recovery enabled + * @see [RFC 8931](https://tools.ietf.org/html/rfc8931) + */ +#define GNRC_NETIF_6LO_LOCAL_FLAGS_SFR (0x01) +/** @} */ + /** * @brief 6Lo component of @ref gnrc_netif_t */ @@ -35,6 +57,15 @@ typedef struct { * @ref net_gnrc_sixlowpan_frag "gnrc_sixlowpan_frag". */ uint16_t max_frag_size; + /** + * @brief 6LoWPAN capability flags beyond the ones advertised in + * [6LoWPAN Capability Indication Option + * (6CIO)](https://tools.ietf.org/html/rfc7400#section-3.3) + * + * @see [Local 6LoWPAN capability flags](@ref + * net_gnrc_netif_6lo_local_flags) + */ + uint8_t local_flags; } gnrc_netif_6lo_t; #ifdef __cplusplus From bd300a3cc01cd710529db61de360020e99392eb3 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Mon, 2 Dec 2019 11:58:17 +0100 Subject: [PATCH 05/10] gnrc_sixlowpan_frag_rb: add check function for empty interval pool --- sys/include/net/gnrc/sixlowpan/frag/rb.h | 21 +++++++++++++++++++ .../frag/rb/gnrc_sixlowpan_frag_rb.c | 12 +++++++++++ 2 files changed, 33 insertions(+) diff --git a/sys/include/net/gnrc/sixlowpan/frag/rb.h b/sys/include/net/gnrc/sixlowpan/frag/rb.h index ff5dfb825996..8bfa78c84de5 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/rb.h +++ b/sys/include/net/gnrc/sixlowpan/frag/rb.h @@ -251,6 +251,27 @@ static inline void gnrc_sixlowpan_frag_rb_remove(gnrc_sixlowpan_frag_rb_t *rbuf) } #endif +#if defined(TEST_SUITES) || defined(DOXYGEN) +/** + * @brief Check if pool of fragment intervals is empty + * + * @see @ref gnrc_sixlowpan_frag_rb_int_t + * @note Returns only non-true values if @ref TEST_SUITES is defined. + * + * @return true, if pool of fragment intervals is empty + * @return false, if pool of fragment intervals is not empty + */ +bool gnrc_sixlowpan_frag_rb_ints_empty(void); +#else /* defined(TEST_SUITES) || defined(DOXYGEN) */ +/* always true without TEST_SUITES defined to optimize out when not testing, + * as checking the status of the fragment interval pool is unnecessary in + * production */ +static inline bool gnrc_sixlowpan_frag_rb_ints_empty(void) +{ + return true; +} +#endif /* defined(TEST_SUITES) || defined(DOXYGEN) */ + #ifdef __cplusplus } #endif diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c b/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c index fee17cc9e35a..dedd6cec6a12 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c @@ -423,6 +423,18 @@ static gnrc_sixlowpan_frag_rb_int_t *_rbuf_int_get_free(void) return NULL; } +#ifdef TEST_SUITES +bool gnrc_sixlowpan_frag_rb_ints_empty(void) +{ + for (unsigned int i = 0; i < RBUF_INT_SIZE; i++) { + if (rbuf_int[i].end > 0) { + return false; + } + } + return true; +} +#endif /* TEST_SUITES */ + static bool _rbuf_update_ints(gnrc_sixlowpan_frag_rb_base_t *entry, uint16_t offset, size_t frag_size) { From d393008b9f2f8e5ccdfa0621b9e0c8771303c553 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 30 Oct 2020 13:51:37 +0100 Subject: [PATCH 06/10] gnrc_sixlowpan_frag_rb: externalize get_by_tag function --- sys/include/net/gnrc/sixlowpan/frag/rb.h | 23 +++++++++++++++++++ .../frag/rb/gnrc_sixlowpan_frag_rb.c | 7 +++++- tests/gnrc_sixlowpan_frag/main.c | 20 ++++++++++++++++ 3 files changed, 49 insertions(+), 1 deletion(-) diff --git a/sys/include/net/gnrc/sixlowpan/frag/rb.h b/sys/include/net/gnrc/sixlowpan/frag/rb.h index 8bfa78c84de5..1463b81421f4 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/rb.h +++ b/sys/include/net/gnrc/sixlowpan/frag/rb.h @@ -118,6 +118,29 @@ gnrc_sixlowpan_frag_rb_t *gnrc_sixlowpan_frag_rb_add(gnrc_netif_hdr_t *netif_hdr gnrc_pktsnip_t *frag, size_t offset, unsigned page); +/** + * @brief Gets a reassembly buffer entry with a given link-layer address + * pair and tag. + * + * @pre `netif_hdr != NULL` + * + * @param[in] netif_hdr An interface header to provide the (source, destination) + * link-layer address pair. Must not be NULL. + * @param[in] tag Tag to search for. + * + * @note datagram_size is not a search parameter as the primary use case + * for this function is [Selective Fragment Recovery] + * (https://tools.ietf.org/html/rfc8931) where this information only + * exists in the first fragment. + * + * @return The reassembly buffer entry identified by the source and destination + * address in the @p netif_hdr and @p tag, if any such entry exist. + * @return NULL, if no entry with the given identifying tuple exist. + */ +gnrc_sixlowpan_frag_rb_t *gnrc_sixlowpan_frag_rb_get_by_datagram( + const gnrc_netif_hdr_t *netif_hdr, + uint16_t tag); + /** * @brief Checks if a reassembly buffer entry with a given link-layer address * pair and tag exists diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c b/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c index dedd6cec6a12..4a98dcee8fea 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c @@ -153,7 +153,12 @@ gnrc_sixlowpan_frag_rb_t *gnrc_sixlowpan_frag_rb_add(gnrc_netif_hdr_t *netif_hdr return (res < 0) ? NULL : &rbuf[res]; } - +gnrc_sixlowpan_frag_rb_t *gnrc_sixlowpan_frag_rb_get_by_datagram( + const gnrc_netif_hdr_t *netif_hdr, + uint16_t tag) +{ + return _rbuf_get_by_tag(netif_hdr, tag); +} bool gnrc_sixlowpan_frag_rb_exists(const gnrc_netif_hdr_t *netif_hdr, uint16_t tag) diff --git a/tests/gnrc_sixlowpan_frag/main.c b/tests/gnrc_sixlowpan_frag/main.c index b1e0c91edce7..e79aaae20c22 100644 --- a/tests/gnrc_sixlowpan_frag/main.c +++ b/tests/gnrc_sixlowpan_frag/main.c @@ -536,6 +536,25 @@ static void test_rbuf_add__overlap_rhs(void) _check_pktbuf(NULL); } +static void test_rbuf_get_by_dg(void) +{ + const gnrc_sixlowpan_frag_rb_t *entry; + + TEST_ASSERT_NULL( + gnrc_sixlowpan_frag_rb_get_by_datagram(&_test_netif_hdr.hdr, TEST_TAG) + ); + /* add a fragment */ + _rbuf_create_first_fragment(); + TEST_ASSERT_NOT_NULL( + gnrc_sixlowpan_frag_rb_get_by_datagram(&_test_netif_hdr.hdr, TEST_TAG) + ); + /* get entry to release entry->pkt it in `_check_pktbuf()` */ + entry = _first_non_empty_rbuf(); + /* entry is however not properly removed yet */ + TEST_ASSERT_NOT_NULL(entry); + _check_pktbuf(entry); +} + static void test_rbuf_exists(void) { const gnrc_sixlowpan_frag_rb_t *entry; @@ -631,6 +650,7 @@ static void run_unittests(void) new_TestFixture(test_rbuf_add__too_big_fragment), new_TestFixture(test_rbuf_add__overlap_lhs), new_TestFixture(test_rbuf_add__overlap_rhs), + new_TestFixture(test_rbuf_get_by_dg), new_TestFixture(test_rbuf_exists), new_TestFixture(test_rbuf_rm_by_dg), new_TestFixture(test_rbuf_rm), From ec436e99d9e1841453502a2a68630a21d990b155 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Thu, 17 Oct 2019 15:30:10 +0200 Subject: [PATCH 07/10] gnrc_sixlowpan_frag_rb: add handling for RFRAG packets --- .../frag/rb/gnrc_sixlowpan_frag_rb.c | 66 ++++++++++++++++--- 1 file changed, 57 insertions(+), 9 deletions(-) diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c b/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c index 4a98dcee8fea..a84b060b02e5 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c @@ -29,6 +29,7 @@ #include "net/gnrc/sixlowpan/frag/minfwd.h" #include "net/gnrc/sixlowpan/frag/vrb.h" #include "net/sixlowpan.h" +#include "net/sixlowpan/sfr.h" #include "thread.h" #include "xtimer.h" #include "utlist.h" @@ -205,9 +206,18 @@ static gnrc_sixlowpan_frag_rb_t *_rbuf_get_by_tag(const gnrc_netif_hdr_t *netif_ #ifndef NDEBUG static bool _valid_offset(gnrc_pktsnip_t *pkt, size_t offset) { - return (sixlowpan_frag_1_is(pkt->data) && (offset == 0)) || - (sixlowpan_frag_n_is(pkt->data) && - (offset == sixlowpan_frag_offset(pkt->data))); + return ( + IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG) && + ((sixlowpan_frag_1_is(pkt->data) && (offset == 0)) || + (sixlowpan_frag_n_is(pkt->data) && + (offset == sixlowpan_frag_offset(pkt->data)))) + ) || ( + IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) && + /* offset == 0 is an abort condition that should not be handed to the + * reassembly buffer */ + sixlowpan_sfr_rfrag_is(pkt->data) && + (sixlowpan_sfr_rfrag_get_offset(pkt->data) != 0) + ); } #endif @@ -239,6 +249,25 @@ static size_t _6lo_frag_size(gnrc_pktsnip_t *pkt, size_t offset, uint8_t *data) return frag_size; } +static uint16_t _6lo_sfr_datagram_size(gnrc_pktsnip_t *pkt, size_t offset) +{ + /* offset doubles as datagram size in RFRAG header when sequence number is 0 + * see https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-08#section-5.1 */ + return (offset == 0) ? sixlowpan_sfr_rfrag_get_offset(pkt->data) : 0; +} + +static uint8_t *_6lo_sfr_payload(gnrc_pktsnip_t *pkt) +{ + return ((uint8_t *)pkt->data) + sizeof(sixlowpan_sfr_rfrag_t); +} + +static size_t _6lo_sfr_frag_size(gnrc_pktsnip_t *pkt) +{ + /* TODO: if necessary check MAC layer here, + * see https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-08#section-5.1 */ + return sixlowpan_sfr_rfrag_get_frag_size(pkt->data); +} + static int _rbuf_add(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, size_t offset, unsigned page) { @@ -249,18 +278,37 @@ static int _rbuf_add(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, } entry; const uint8_t *src = gnrc_netif_hdr_get_src_addr(netif_hdr); const uint8_t *dst = gnrc_netif_hdr_get_dst_addr(netif_hdr); - uint8_t *data; - size_t frag_size; + uint8_t *data = NULL; + size_t frag_size = 0; /* assign 0, otherwise cppcheck complains ;-) */ int res; uint16_t datagram_size; uint16_t datagram_tag; /* check if provided offset is the same as in fragment */ assert(_valid_offset(pkt, offset)); - data = _6lo_frag_payload(pkt); - frag_size = _6lo_frag_size(pkt, offset, data); - datagram_size = sixlowpan_frag_datagram_size(pkt->data); - datagram_tag = sixlowpan_frag_datagram_tag(pkt->data); + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG) && sixlowpan_frag_is(pkt->data)) { + data = _6lo_frag_payload(pkt); + frag_size = _6lo_frag_size(pkt, offset, data); + datagram_size = sixlowpan_frag_datagram_size(pkt->data); + datagram_tag = sixlowpan_frag_datagram_tag(pkt->data); + } + else if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) && + sixlowpan_sfr_rfrag_is(pkt->data)) { + sixlowpan_sfr_rfrag_t *rfrag = pkt->data;; + + data = _6lo_sfr_payload(pkt); + frag_size = _6lo_sfr_frag_size(pkt); + /* offset doubles as datagram size in RFRAG header when sequence number + * is 0 */ + datagram_size = _6lo_sfr_datagram_size(pkt, offset); + datagram_tag = rfrag->base.tag; + } + else { + /* either one of the if branches above was taken */ + assert(data != NULL); + gnrc_pktbuf_release(pkt); + return RBUF_ADD_ERROR; + } gnrc_sixlowpan_frag_rb_gc(); /* only check VRB for subsequent frags, first frags create and not get VRB From 1cd1716280667168bf273345f57e2e5e15623771 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 27 Sep 2019 15:34:35 +0200 Subject: [PATCH 08/10] gnrc_sixlowpan_frag: initial import of Selective Fragment Recovery --- sys/Makefile.dep | 21 +- sys/include/net/gnrc/sixlowpan/config.h | 4 + sys/include/net/gnrc/sixlowpan/frag/fb.h | 15 +- sys/include/net/gnrc/sixlowpan/frag/rb.h | 14 + sys/include/net/gnrc/sixlowpan/frag/sfr.h | 171 ++ .../net/gnrc/sixlowpan/frag/sfr_types.h | 65 + sys/include/net/gnrc/sixlowpan/frag/vrb.h | 7 + sys/net/gnrc/Makefile | 3 + sys/net/gnrc/netif/gnrc_netif.c | 6 + .../gnrc/network_layer/sixlowpan/frag/Kconfig | 10 +- .../network_layer/sixlowpan/frag/fb/Kconfig | 1 + .../frag/rb/gnrc_sixlowpan_frag_rb.c | 65 +- .../network_layer/sixlowpan/frag/sfr/Kconfig | 101 ++ .../network_layer/sixlowpan/frag/sfr/Makefile | 3 + .../frag/sfr/gnrc_sixlowpan_frag_sfr.c | 1604 +++++++++++++++++ .../network_layer/sixlowpan/gnrc_sixlowpan.c | 81 +- .../sixlowpan/iphc/gnrc_sixlowpan_iphc.c | 72 +- 17 files changed, 2207 insertions(+), 36 deletions(-) create mode 100644 sys/include/net/gnrc/sixlowpan/frag/sfr.h create mode 100644 sys/include/net/gnrc/sixlowpan/frag/sfr_types.h create mode 100644 sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Kconfig create mode 100644 sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Makefile create mode 100644 sys/net/gnrc/network_layer/sixlowpan/frag/sfr/gnrc_sixlowpan_frag_sfr.c diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 0e11d736e3cd..989e4b618fef 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -280,20 +280,26 @@ endif ifneq (,$(filter gnrc_sixlowpan_default,$(USEMODULE))) USEMODULE += gnrc_ipv6_nib_6ln USEMODULE += gnrc_sixlowpan - USEMODULE += gnrc_sixlowpan_frag + ifeq (,$(filter gnrc_sixlowpan_frag_sfr,$(USEMODULE))) + USEMODULE += gnrc_sixlowpan_frag + endif USEMODULE += gnrc_sixlowpan_iphc endif ifneq (,$(filter gnrc_sixlowpan_router_default,$(USEMODULE))) USEMODULE += gnrc_ipv6_nib_6lr - USEMODULE += gnrc_sixlowpan_frag + ifeq (,$(filter gnrc_sixlowpan_frag_sfr,$(USEMODULE))) + USEMODULE += gnrc_sixlowpan_frag + endif USEMODULE += gnrc_sixlowpan_iphc endif ifneq (,$(filter gnrc_sixlowpan_border_router_default,$(USEMODULE))) USEMODULE += gnrc_ipv6_nib_6lbr USEMODULE += gnrc_ipv6_router_default - USEMODULE += gnrc_sixlowpan_frag + ifeq (,$(filter gnrc_sixlowpan_frag_sfr,$(USEMODULE))) + USEMODULE += gnrc_sixlowpan_frag + endif USEMODULE += gnrc_sixlowpan_iphc endif @@ -318,6 +324,15 @@ ifneq (,$(filter gnrc_sixlowpan_frag_rb,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter gnrc_sixlowpan_frag_sfr,$(USEMODULE))) + USEMODULE += gnrc_sixlowpan + USEMODULE += gnrc_sixlowpan_frag_fb + USEMODULE += gnrc_sixlowpan_frag_vrb + USEMODULE += gnrc_sixlowpan_frag_rb + USEMODULE += evtimer + USEMODULE += xtimer +endif + ifneq (,$(filter gnrc_sixlowpan_frag_vrb,$(USEMODULE))) USEMODULE += xtimer USEMODULE += gnrc_sixlowpan_frag_fb diff --git a/sys/include/net/gnrc/sixlowpan/config.h b/sys/include/net/gnrc/sixlowpan/config.h index ce344608ca0e..343d17bb6c75 100644 --- a/sys/include/net/gnrc/sixlowpan/config.h +++ b/sys/include/net/gnrc/sixlowpan/config.h @@ -63,7 +63,11 @@ extern "C" { * [gnrc_sixlowpan_frag_fb](@ref net_gnrc_sixlowpan_frag_fb) module */ #ifndef CONFIG_GNRC_SIXLOWPAN_FRAG_FB_SIZE +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) +#define CONFIG_GNRC_SIXLOWPAN_FRAG_FB_SIZE (4U) +#else /* defined(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) */ #define CONFIG_GNRC_SIXLOWPAN_FRAG_FB_SIZE (1U) +#endif /* defined(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) */ #endif /** diff --git a/sys/include/net/gnrc/sixlowpan/frag/fb.h b/sys/include/net/gnrc/sixlowpan/frag/fb.h index d24aed929815..07f15a207290 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/fb.h +++ b/sys/include/net/gnrc/sixlowpan/frag/fb.h @@ -28,6 +28,7 @@ #ifdef MODULE_GNRC_SIXLOWPAN_FRAG_HINT #include "net/gnrc/sixlowpan/frag/hint.h" #endif /* MODULE_GNRC_SIXLOWPAN_FRAG_HINT */ +#include "net/gnrc/sixlowpan/frag/sfr_types.h" #ifdef __cplusplus extern "C" { @@ -43,10 +44,22 @@ extern "C" { */ typedef struct { gnrc_pktsnip_t *pkt; /**< Pointer to the IPv6 packet to be fragmented */ - uint16_t datagram_size; /**< Length of just the (uncompressed) IPv6 packet to be fragmented */ + /** + * @brief Length of just the (uncompressed) IPv6 packet to be fragmented + * + * @note With @ref net_gnrc_sixlowpan_frag_sfr this denotes the + * _compressed form_ of the datagram + */ + uint16_t datagram_size; uint16_t tag; /**< Tag used for the fragment */ uint16_t offset; /**< Offset of the Nth fragment from the beginning of the * payload datagram */ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) + /** + * @brief Extension for selective fragment recovery. + */ + gnrc_sixlowpan_frag_sfr_fb_t sfr; +#endif /* IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) */ #ifdef MODULE_GNRC_SIXLOWPAN_FRAG_HINT /** * @brief Hint for the size (smaller than link-layer PDU) for the next diff --git a/sys/include/net/gnrc/sixlowpan/frag/rb.h b/sys/include/net/gnrc/sixlowpan/frag/rb.h index 1463b81421f4..c990ada64083 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/rb.h +++ b/sys/include/net/gnrc/sixlowpan/frag/rb.h @@ -25,6 +25,9 @@ #include "net/gnrc/netif/hdr.h" #include "net/gnrc/pkt.h" +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR +#include "net/sixlowpan/sfr.h" +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ #include "net/gnrc/sixlowpan/config.h" @@ -96,6 +99,17 @@ typedef struct { * @brief The reassembled packet in the packet buffer */ gnrc_pktsnip_t *pkt; +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) + /** + * @brief Bitmap for received fragments + * + * @note Only available with module `gnrc_sixlowpan_frag_sfr` compiled + * in. + */ + BITFIELD(received, SIXLOWPAN_SFR_ACK_BITMAP_SIZE); + int8_t offset_diff; /**< offset change due to + * recompression */ +#endif /* IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) */ } gnrc_sixlowpan_frag_rb_t; /** diff --git a/sys/include/net/gnrc/sixlowpan/frag/sfr.h b/sys/include/net/gnrc/sixlowpan/frag/sfr.h new file mode 100644 index 000000000000..3d5b9a5fcfbc --- /dev/null +++ b/sys/include/net/gnrc/sixlowpan/frag/sfr.h @@ -0,0 +1,171 @@ +/* + * Copyright (C) 2019 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 net_gnrc_sixlowpan_frag_sfr 6LoWPAN selective fragment recovery + * @ingroup net_gnrc_sixlowpan + * @brief 6LoWPAN selective fragment recovery implementation for GNRC + * + * 6LoWPAN selective fragment recovery is an alternative fragmentation + * specification to [classic 6LoWPAN fragmentation](@ref + * net_gnrc_sixlowpan_frag). It can be run in parallel to classic fragmentation, + * but is incompatible with its message formats. + * + * How nodes can exchange that they are able to communicate using selective + * fragment recovery is currently not specified, so this feature should only be + * used if the operator of a network can ensure that all 6LoWPAN nodes within + * that network can communicate using selective fragment recovery. + * + * @see [RFC 8931](https://tools.ietf.org/html/rfc8931) + * @{ + * + * @file + * @brief 6LoWPAN selective fragment recovery definitions for GNRC + * + * @author Martine Lenders + */ +#ifndef NET_GNRC_SIXLOWPAN_FRAG_SFR_H +#define NET_GNRC_SIXLOWPAN_FRAG_SFR_H + +#include "assert.h" +#include "bitfield.h" +#include "net/gnrc/pkt.h" +#include "net/gnrc/netif.h" +#include "net/gnrc/sixlowpan/config.h" +#include "net/gnrc/sixlowpan/frag/fb.h" +#include "net/gnrc/sixlowpan/frag/vrb.h" +#include "net/gnrc/sixlowpan/frag/sfr_types.h" +#include "net/sixlowpan/sfr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Message type to signal an acknowledgement request timeout. + */ +#define GNRC_SIXLOWPAN_FRAG_SFR_ARQ_TIMEOUT_MSG (0x0227) + +/** + * @brief Message type to signal the sending of the next frame. + */ +#define GNRC_SIXLOWPAN_FRAG_SFR_INTER_FRAG_GAP_MSG (0x0228) + +/** + * @brief Initialize selective fragment recovery + */ +void gnrc_sixlowpan_frag_sfr_init(void); + +/** + * @brief Initialize a network interface for selective fragment recovery + * + * @note This is a NOP without module `gnrc_sixlowpan_frag_sfr` + * + * @param[in] netif A network interface + */ +static inline void gnrc_sixlowpan_frag_sfr_init_iface(gnrc_netif_t *netif) +{ + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) && + gnrc_netif_is_6lo(netif)) { +#if IS_USED(MODULE_GNRC_NETIF_6LO) + netif->sixlo.local_flags |= GNRC_NETIF_6LO_LOCAL_FLAGS_SFR; + netif->sixlo.max_frag_size = + (netif->sixlo.max_frag_size > CONFIG_GNRC_SIXLOWPAN_SFR_OPT_FRAG_SIZE) + ? CONFIG_GNRC_SIXLOWPAN_SFR_OPT_FRAG_SIZE + : netif->sixlo.max_frag_size; + assert(netif->sixlo.max_frag_size >= CONFIG_GNRC_SIXLOWPAN_SFR_MIN_FRAG_SIZE); +#endif + } +} + +/** + * @brief Checks if a network interface is configured for selective fragment + * recovery + * + * @param[in] netif A network interface. + * + * @return true, if @p netif supports selective fragment recovery and has it + * enabled. + * @return false, if @p netif does not support selective fragment recovery or + * does not have it enabled. + */ +static inline bool gnrc_sixlowpan_frag_sfr_netif(gnrc_netif_t *netif) +{ +#if IS_USED(MODULE_GNRC_NETIF_6LO) + return IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) && + gnrc_netif_is_6lo(netif) && + (netif->sixlo.local_flags & GNRC_NETIF_6LO_LOCAL_FLAGS_SFR); +#else + (void)netif; + return false; +#endif +} + +/** + * @brief Sends a packet via selective fragment recovery + * + * @pre `ctx != NULL` + * @pre gnrc_sixlowpan_frag_fb_t::pkt of @p ctx is equal to @p pkt or + * `pkt == NULL`. + * + * @param[in] pkt A packet. May be NULL. + * @param[in] ctx Fragmentation buffer entry of. Expected to be of type + * @ref gnrc_sixlowpan_frag_fb_t, with gnrc_sixlowpan_frag_fb_t + * set to @p pkt. Must not be NULL. + * @param[in] page Current 6Lo dispatch parsing page. + */ +void gnrc_sixlowpan_frag_sfr_send(gnrc_pktsnip_t *pkt, void *ctx, + unsigned page); + +/** + * @brief Handles a packet containing a selective fragment recovery header + * + * @param[in] pkt The packet to handle. + * @param[in] ctx Context for the packet. May be NULL. + * @param[in] page Current 6Lo dispatch parsing page. + */ +void gnrc_sixlowpan_frag_sfr_recv(gnrc_pktsnip_t *pkt, void *ctx, unsigned page); + +/** + * @brief Forward a fragment via selective fragment recovery + * + * @param[in] pkt The fragment to forward (without RFRAG header). + * Is consumed by this function. + * @param[in] rfrag The originally received RFRAG header. + * @param[in] vrbe Virtual reassembly buffer containing the forwarding + * information. + * @param[in] page Current 6Lo dispatch parsing page. + * + * @return 0, on success. + * @return -ENOMEM, when packet buffer is too full to prepare packet for + * forwarding. @p pkt is released in that case. + */ +int gnrc_sixlowpan_frag_sfr_forward(gnrc_pktsnip_t *pkt, + sixlowpan_sfr_rfrag_t *rfrag, + gnrc_sixlowpan_frag_vrb_t *vrbe, + unsigned page); + +/** + * @brief Handles an Acknowledgment request timeout + * + * @param[in] fbuf The fragmentation buffer representing the datagram for which + * fragments the Acknowledgment request timed out. + */ +void gnrc_sixlowpan_frag_sfr_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf); + +/** + * @brief Handles inter frame gap + */ +void gnrc_sixlowpan_frag_sfr_inter_frame_gap(void); + +#ifdef __cplusplus +} +#endif + +#endif /* NET_GNRC_SIXLOWPAN_FRAG_SFR_H */ +/** @} */ diff --git a/sys/include/net/gnrc/sixlowpan/frag/sfr_types.h b/sys/include/net/gnrc/sixlowpan/frag/sfr_types.h new file mode 100644 index 000000000000..1894c0120848 --- /dev/null +++ b/sys/include/net/gnrc/sixlowpan/frag/sfr_types.h @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2019 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. + */ + +/** + * @addtogroup net_gnrc_sixlowpan_frag_sfr + * @{ + * + * @file + * @brief 6LoWPAN selective fragment recovery type definitions for GNRC + * + * @author Martine Lenders + */ +#ifndef NET_GNRC_SIXLOWPAN_FRAG_SFR_TYPES_H +#define NET_GNRC_SIXLOWPAN_FRAG_SFR_TYPES_H + +#include + +#include "bitfield.h" +#include "clist.h" +#include "evtimer_msg.h" +#include "msg.h" +#include "xtimer.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Bitmap type to simplify comparisons + */ +typedef union { + uint32_t u32; /**< numerical version of bitmap */ + BITFIELD(bf, 32U); /**< bitfield version of bitmap */ +} gnrc_sixlowpan_frag_sfr_bitmap_t; + +/** + * @brief Extension for @ref net_gnrc_sixlowpan_frag_fb for selective + * fragment recovery + */ +typedef struct gnrc_sixlowpan_frag_sfr_fb { + /** + * @brief Acknowledgment request timeout event + */ + evtimer_msg_event_t arq_timeout_event; + uint32_t arq_timeout; /**< Time in microseconds the sender should + * wait for an RFRAG Acknowledgment */ + uint8_t cur_seq; /**< Sequence number for next fragment */ + uint8_t frags_sent; /**< Number of fragments sent */ + uint8_t window_size; /**< Current window size in number of + * fragments */ + uint8_t retrans; /**< Datagram retransmissions */ + clist_node_t window; /**< Sent fragments of the current window */ +} gnrc_sixlowpan_frag_sfr_fb_t; + +#ifdef __cplusplus +} +#endif + +#endif /* NET_GNRC_SIXLOWPAN_FRAG_SFR_TYPES_H */ +/** @} */ diff --git a/sys/include/net/gnrc/sixlowpan/frag/vrb.h b/sys/include/net/gnrc/sixlowpan/frag/vrb.h index 462a82e971a6..58fe7838af7a 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/vrb.h +++ b/sys/include/net/gnrc/sixlowpan/frag/vrb.h @@ -54,6 +54,13 @@ typedef struct { * @brief Outgoing tag to gnrc_sixlowpan_frag_rb_base_t::dst */ uint16_t out_tag; +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) + int16_t offset_diff; /**< offset change due to recompression */ + /** + * @brief Incoming interface to gnrc_sixlowpan_frag_rb_base_t::src + */ + gnrc_netif_t *in_netif; +#endif /* IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) */ } gnrc_sixlowpan_frag_vrb_t; /** diff --git a/sys/net/gnrc/Makefile b/sys/net/gnrc/Makefile index dc63650d80d3..8f8da293d16b 100644 --- a/sys/net/gnrc/Makefile +++ b/sys/net/gnrc/Makefile @@ -106,6 +106,9 @@ endif ifneq (,$(filter gnrc_sixlowpan_frag_rb,$(USEMODULE))) DIRS += network_layer/sixlowpan/frag/rb endif +ifneq (,$(filter gnrc_sixlowpan_frag_sfr,$(USEMODULE))) + DIRS += network_layer/sixlowpan/frag/sfr +endif ifneq (,$(filter gnrc_sixlowpan_frag_stats,$(USEMODULE))) DIRS += network_layer/sixlowpan/frag/stats endif diff --git a/sys/net/gnrc/netif/gnrc_netif.c b/sys/net/gnrc/netif/gnrc_netif.c index 6e33d5b19930..94dc8dc212cc 100644 --- a/sys/net/gnrc/netif/gnrc_netif.c +++ b/sys/net/gnrc/netif/gnrc_netif.c @@ -32,6 +32,9 @@ #if IS_USED(MODULE_GNRC_NETIF_PKTQ) #include "net/gnrc/netif/pktq.h" #endif /* IS_USED(MODULE_GNRC_NETIF_PKTQ) */ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) +#include "net/gnrc/sixlowpan/frag/sfr.h" +#endif /* IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) */ #if IS_USED(MODULE_NETSTATS) #include "net/netstats.h" #endif /* IS_USED(MODULE_NETSTATS) */ @@ -1389,6 +1392,9 @@ void gnrc_netif_default_init(gnrc_netif_t *netif) _init_from_device(netif); #ifdef DEVELHELP _test_options(netif); +#endif +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) + gnrc_sixlowpan_frag_sfr_init_iface(netif); #endif netif->cur_hl = CONFIG_GNRC_NETIF_DEFAULT_HL; #ifdef MODULE_GNRC_IPV6_NIB diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/Kconfig b/sys/net/gnrc/network_layer/sixlowpan/frag/Kconfig index 71df2bee96c9..09667f2d676e 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/frag/Kconfig +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/Kconfig @@ -5,10 +5,16 @@ # directory for more details. # -if USEMODULE_GNRC_SIXLOWPAN_FRAG +if USEMODULE_GNRC_SIXLOWPAN_FRAG || USEMODULE_GNRC_SIXLOWPAN_FRAG_SFR rsource "fb/Kconfig" rsource "rb/Kconfig" rsource "vrb/Kconfig" -endif # USEMODULE_GNRC_SIXLOWPAN_FRAG +endif # USEMODULE_GNRC_SIXLOWPAN_FRAG || USEMODULE_GNRC_SIXLOWPAN_FRAG_SFR + +if USEMODULE_GNRC_SIXLOWPAN_FRAG_SFR + +rsource "sfr/Kconfig" + +endif # USEMODULE_GNRC_SIXLOWPAN_FRAG_SFR diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/fb/Kconfig b/sys/net/gnrc/network_layer/sixlowpan/frag/fb/Kconfig index ec333cd722fa..745485b9dab2 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/frag/fb/Kconfig +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/fb/Kconfig @@ -14,6 +14,7 @@ if KCONFIG_USEMODULE_GNRC_SIXLOWPAN_FRAG_FB config GNRC_SIXLOWPAN_FRAG_FB_SIZE int "Number of datagrams that can be fragmented simultaneously" + default 4 if MODULE_GNRC_SIXLOWPAN_FRAG_SFR default 1 help This determines the number of @ref gnrc_sixlowpan_frag_fb_t instances diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c b/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c index a84b060b02e5..97fa3cd2fdc1 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/rb/gnrc_sixlowpan_frag_rb.c @@ -252,7 +252,7 @@ static size_t _6lo_frag_size(gnrc_pktsnip_t *pkt, size_t offset, uint8_t *data) static uint16_t _6lo_sfr_datagram_size(gnrc_pktsnip_t *pkt, size_t offset) { /* offset doubles as datagram size in RFRAG header when sequence number is 0 - * see https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-08#section-5.1 */ + * see https://tools.ietf.org/html/rfc8931#section-5.1 */ return (offset == 0) ? sixlowpan_sfr_rfrag_get_offset(pkt->data) : 0; } @@ -264,10 +264,27 @@ static uint8_t *_6lo_sfr_payload(gnrc_pktsnip_t *pkt) static size_t _6lo_sfr_frag_size(gnrc_pktsnip_t *pkt) { /* TODO: if necessary check MAC layer here, - * see https://tools.ietf.org/html/draft-ietf-6lo-fragment-recovery-08#section-5.1 */ + * see https://tools.ietf.org/html/rfc8931#section-5.1 */ return sixlowpan_sfr_rfrag_get_frag_size(pkt->data); } +static gnrc_pktsnip_t *_mark_frag_hdr(gnrc_pktsnip_t *pkt) +{ + if (IS_USED(MODULE_GNRC_SIXLOWPAN_IPHC)) { + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) && + sixlowpan_sfr_rfrag_is(pkt->data)) { + return gnrc_pktbuf_mark(pkt, sizeof(sixlowpan_sfr_rfrag_t), + GNRC_NETTYPE_SIXLOWPAN); + } + else if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG) && + sixlowpan_frag_is(pkt->data)) { + return gnrc_pktbuf_mark(pkt, sizeof(sixlowpan_frag_t), + GNRC_NETTYPE_SIXLOWPAN); + } + } + return NULL; +} + static int _rbuf_add(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, size_t offset, unsigned page) { @@ -294,7 +311,7 @@ static int _rbuf_add(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, } else if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) && sixlowpan_sfr_rfrag_is(pkt->data)) { - sixlowpan_sfr_rfrag_t *rfrag = pkt->data;; + sixlowpan_sfr_rfrag_t *rfrag = pkt->data; data = _6lo_sfr_payload(pkt); frag_size = _6lo_sfr_frag_size(pkt); @@ -354,6 +371,9 @@ static int _rbuf_add(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, return RBUF_ADD_ERROR; } entry.rbuf = &rbuf[res]; +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) + offset += entry.rbuf->offset_diff; +#endif /* IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) */ if ((offset + frag_size) > entry.super->datagram_size) { DEBUG("6lo rfrag: fragment too big for resulting datagram, discarding datagram\n"); gnrc_pktbuf_release(entry.rbuf->pkt); @@ -379,11 +399,11 @@ static int _rbuf_add(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, DEBUG("6lo rbuf: add fragment data\n"); entry.super->current_size += (uint16_t)frag_size; if (offset == 0) { -#ifdef MODULE_GNRC_SIXLOWPAN_IPHC - if (sixlowpan_iphc_is(data)) { + if (IS_USED(MODULE_GNRC_SIXLOWPAN_IPHC) && + sixlowpan_iphc_is(data)) { DEBUG("6lo rbuf: detected IPHC header.\n"); - gnrc_pktsnip_t *frag_hdr = gnrc_pktbuf_mark(pkt, - sizeof(sixlowpan_frag_t), GNRC_NETTYPE_SIXLOWPAN); + gnrc_pktsnip_t *frag_hdr = _mark_frag_hdr(pkt); + if (frag_hdr == NULL) { DEBUG("6lo rbuf: unable to mark fragment header. " "aborting reassembly.\n"); @@ -403,9 +423,7 @@ static int _rbuf_add(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, return res; } } - else -#endif - if (data[0] == SIXLOWPAN_UNCOMP) { + else if (data[0] == SIXLOWPAN_UNCOMP) { DEBUG("6lo rbuf: detected uncompressed datagram\n"); data++; if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_MINFWD) && @@ -429,12 +447,17 @@ static int _rbuf_add(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, return _forward_uncomp(pkt, rbuf, vrbe, page); } } + else if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) && + sixlowpan_sfr_rfrag_is(pkt->data)) { + entry.super->datagram_size--; + } } } - if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_MINFWD)) { - /* all cases to try forwarding with minfwd above failed so just do - * normal reassembly. For the `minfwd` case however, we need to - * resize `entry.rbuf->pkt`, since we kept the packet allocation + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_MINFWD) || + IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR)) { + /* all cases to try forwarding with minfwd or SFR above failed so + * just do normal reassembly. For the `minfwd` case however, we need + * to resize `entry.rbuf->pkt`, since we kept the packet allocation * with fragment forwarding as minimal as possible in * `_rbuf_get()` */ res = _rbuf_resize_for_reassembly(entry.rbuf); @@ -575,8 +598,14 @@ static int _rbuf_get(const void *src, size_t src_len, for (unsigned int i = 0; i < CONFIG_GNRC_SIXLOWPAN_FRAG_RBUF_SIZE; i++) { /* check first if entry already available */ - if ((rbuf[i].pkt != NULL) && (rbuf[i].super.datagram_size == size) && - (rbuf[i].super.tag == tag) && (rbuf[i].super.src_len == src_len) && + if ((rbuf[i].pkt != NULL) && (rbuf[i].super.tag == tag) && + ((IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) && + /* not all SFR fragments carry the datagram size, so make 0 a + * legal value to not compare datagram size */ + ((size == 0) || (rbuf[i].super.datagram_size == size))) || + (!IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) && + (rbuf[i].super.datagram_size == size))) && + (rbuf[i].super.src_len == src_len) && (rbuf[i].super.dst_len == dst_len) && (memcmp(rbuf[i].super.src, src, src_len) == 0) && (memcmp(rbuf[i].super.dst, dst, dst_len) == 0)) { @@ -689,6 +718,10 @@ static int _rbuf_get(const void *src, size_t src_len, res->super.dst_len = dst_len; res->super.tag = tag; res->super.current_size = 0; +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) + res->offset_diff = 0U; + memset(res->received, 0U, sizeof(res->received)); +#endif /* IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) */ DEBUG("6lo rfrag: entry %p (%s, ", (void *)res, gnrc_netif_addr_to_str(res->super.src, res->super.src_len, diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Kconfig b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Kconfig new file mode 100644 index 000000000000..3db6a9e11a20 --- /dev/null +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Kconfig @@ -0,0 +1,101 @@ +# Copyright (c) 2020 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_USEMODULE_GNRC_SIXLOWPAN_FRAG_SFR + bool "Configure GNRC 6LoWPAN Selective Fragment Recovery" + depends on USEMODULE_GNRC_SIXLOWPAN_FRAG_SFR + help + Configure GNRC 6LoWPAN Selective Fragement Recovery using Kconfig. + +if KCONFIG_USEMODULE_GNRC_SIXLOWPAN_FRAG_SFR + +config GNRC_SIXLOWPAN_SFR_MIN_FRAG_SIZE + int "Default minimum value for fragment size (MinFragmentSize)" + default 96 + +config GNRC_SIXLOWPAN_SFR_MAX_FRAG_SIZE + int "Default maximum value for fragment size (MaxFragmentSize)" + default 112 + help + It must be lower than the minimum MTU along the path. A large value + augments the chances of buffer bloat and transmission loss. The value + must be less than 512 if the unit is defined for the PHY layer is the + octet. + +config GNRC_SIXLOWPAN_SFR_OPT_FRAG_SIZE + int "Default value for fragment size that the sender should use to start with (OptFragmentSize)" + default 112 + help + Must be inclusively between @ref GNRC_SIXLOWPAN_SFR_MIN_FRAG_SIZE and + @ref GNRC_SIXLOWPAN_SFR_MAX_FRAG_SIZE + +config GNRC_SIXLOWPAN_SFR_USE_ECN + bool "Indicates whether the sender should react to ECN (UseECN)" + default false + help + When the sender reacts to Explicit Congestion Notification (ECN) its + window size will vary between @ref + CONFIG_GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE and @ref + CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE. + +if GNRC_SIXLOWPAN_SFR_USE_ECN +comment "Warning: Reaction of sender to ECN is not implemented yet" +endif + +config GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE + int "Default minimum value of window size that the sender can use (MinWindowSize)" + default 1 + range 1 32 + +config GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE + int "Default maximum value of window size that the sender can use (MaxWindowSize)" + default 16 + range 1 32 + +config GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE + int "Default value of window size that the sender should start with (OptWindowSize)" + default 16 + range 1 32 + help + Must be inclusively between @ref GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE and + @ref GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE + +config GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US + int "Default minimum amount of time between transmissions in microseconds (InterFrameGap)" + default 100 + help + All packets to a same destination, and in particular fragments, may be + subject to receive while transmitting and hidden terminal collisions + with the next or the previous transmission as the fragments progress + along a same path. The InterFrameGap protects the propagation of to one + transmission before the next one is triggered and creates a duty cycle + that controls the ratio of air and memory in intermediate nodes that a + particular datagram will use. + +config GNRC_SIXLOWPAN_SFR_MIN_ARQ_TIMEOUT_MS + int "Minimum RFRAG-ACK timeout in msec before a node takes a next action (MinARQTimeOut)" + default 350 + +config GNRC_SIXLOWPAN_SFR_MAX_ARQ_TIMEOUT_MS + int "Maximum RFRAG-ACK timeout in msec before a node takes a next action (MaxARQTimeOut)" + default 700 + +config GNRC_SIXLOWPAN_SFR_OPT_ARQ_TIMEOUT_MS + int "Default RFRAG-ACK timeout in msec before a node takes a next action (OptARQTimeOut)" + default 700 + help + Must be inclusively between @ref GNRC_SIXLOWPAN_SFR_MIN_ARQ_TIMEOUT_MS + and @ref GNRC_SIXLOWPAN_SFR_MAX_ARQ_TIMEOUT_MS + +config GNRC_SIXLOWPAN_SFR_FRAG_RETRIES + int "The maximum number of retries for a particular fragment (MaxFragRetries)" + default 2 + +config GNRC_SIXLOWPAN_SFR_DG_RETRIES + int "The maximum number of retries from scratch for a particular datagram (MaxDatagramRetries)" + default 0 + +endif diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Makefile b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Makefile new file mode 100644 index 000000000000..70cae6bbeb49 --- /dev/null +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Makefile @@ -0,0 +1,3 @@ +MODULE := gnrc_sixlowpan_frag_sfr + +include $(RIOTBASE)/Makefile.base diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/gnrc_sixlowpan_frag_sfr.c b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/gnrc_sixlowpan_frag_sfr.c new file mode 100644 index 000000000000..9b2119c8c448 --- /dev/null +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/gnrc_sixlowpan_frag_sfr.c @@ -0,0 +1,1604 @@ +/* + * Copyright (C) 2019 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 Lenders + */ + +#include "irq.h" +#include "kernel_defines.h" +#ifdef MODULE_GNRC_IPV6_NIB +#include "net/ipv6/addr.h" +#endif +#ifdef MODULE_GNRC_IPV6 +#include "net/ipv6/hdr.h" +#endif +#include "net/gnrc/neterr.h" +#include "net/gnrc/netif/internal.h" +#include "net/gnrc/pkt.h" +#include "net/gnrc/sixlowpan.h" +#include "net/gnrc/sixlowpan/config.h" +#include "net/gnrc/sixlowpan/frag/fb.h" +#include "net/gnrc/sixlowpan/frag/rb.h" +#include "net/gnrc/sixlowpan/frag/vrb.h" +#include "net/sixlowpan/sfr.h" +#include "thread.h" +#include "unaligned.h" +#include "xtimer.h" + +#include "net/gnrc/sixlowpan/frag/sfr.h" + +#define ENABLE_DEBUG 0 +#include "debug.h" + +#define FRAG_DESCS_POOL_SIZE (CONFIG_GNRC_SIXLOWPAN_FRAG_FB_SIZE * \ + CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE) +#define FRAME_QUEUE_POOL_SIZE (FRAG_DESCS_POOL_SIZE + \ + CONFIG_GNRC_SIXLOWPAN_FRAG_VRB_SIZE) + +typedef struct { + clist_node_t super; /**< list parent instance */ + uint32_t last_sent; /**< last time sent in microseconds */ + /** + * @brief Acknowledgment request flag, sequence number, and fragment size + */ + uint16_t ar_seq_fs; + uint16_t offset; /**< offset of the fragment */ + uint8_t retries; /**< how often the fragment was retried */ +} _frag_desc_t; + +typedef struct { + clist_node_t super; /**< list parent instance */ + gnrc_pktsnip_t *frame; /**< frame in the queue */ + uint8_t datagram_tag; /**< tag for identification */ + uint8_t page; /**< parsing page context for the frame */ +} _frame_queue_t; + +typedef struct { + enum { + _UNDEF = 0, + _RB, + _VRB, + } type; + union { + gnrc_sixlowpan_frag_rb_base_t *base; + gnrc_sixlowpan_frag_rb_t *rb; + gnrc_sixlowpan_frag_vrb_t *vrb; + } entry; +} _generic_rb_entry_t; + +#ifdef MODULE_GNRC_IPV6_NIB +static char addr_str[IPV6_ADDR_MAX_STR_LEN]; +#else /* MODULE_GNRC_IPV6_NIB */ +static char addr_str[GNRC_NETIF_HDR_L2ADDR_PRINT_LEN]; +#endif /* MODULE_GNRC_IPV6_NIB */ + +static evtimer_msg_t _arq_timer; +static xtimer_t _if_gap_timer = { .offset = 0, .long_offset = 0 }; +static msg_t _if_gap_msg = { .type = GNRC_SIXLOWPAN_FRAG_SFR_INTER_FRAG_GAP_MSG }; +static uint32_t _last_frame_sent = 0U; + +static _frag_desc_t _frag_descs_pool[FRAG_DESCS_POOL_SIZE]; +static _frame_queue_t _frame_queue_pool[FRAME_QUEUE_POOL_SIZE]; + +static clist_node_t _frag_descs_free; +static clist_node_t _frame_queue_free; +static clist_node_t _frame_queue; + +static const gnrc_sixlowpan_frag_sfr_bitmap_t _full_bitmap = { .u32 = UINT32_MAX }; +static const gnrc_sixlowpan_frag_sfr_bitmap_t _null_bitmap = { .u32 = 0U }; + +/** + * @brief Converts a @ref sys_bitmap based bitmap to a + * gnrc_sixlowpan_frag_sfr_bitmap_t + * + * @param[in] bitmap A @ref sys_bitmap + * + * @return A gnrc_sixlowpan_frag_sfr_bitmap_t. + */ +static inline gnrc_sixlowpan_frag_sfr_bitmap_t *_to_bitmap(uint8_t *bitmap); + +/** + * @brief Checks if fragment represented by a fragment descriptor requested an + * ACK + */ +static inline bool _frag_ack_req(_frag_desc_t *frag); +/** + * @brief Returns sequence number for fragment represented by fragment + * descriptor + */ +static inline uint8_t _frag_seq(_frag_desc_t *frag); + +/** + * @brief Returns fragment size for fragment represented by fragment + * descriptor + */ +static inline uint16_t _frag_size(_frag_desc_t *frag); + +/** + * @brief Cleans up a fragmentation buffer entry and all state related to its + * datagram. + * + * @param[in] fbuf A fragmentation buffer entry + * @param[in] error An errno to provide to an upper layer as the reason for why + * gnrc_sixlowpan_frag_fb_t::pkt of @p fbuf was released. + */ +static void _clean_up_fbuf(gnrc_sixlowpan_frag_fb_t *fbuf, int error); + +/** + * @brief Send first fragment. + * + * @param[in] netif Network interface to send fragment over + * @param[in] fbuf Fragmentation buffer for the datagram to fragment + * @param[in] page Current 6Lo dispatch parsing page. + * + * @return Size of the fragment + */ +static uint16_t _send_1st_fragment(gnrc_netif_t *netif, + gnrc_sixlowpan_frag_fb_t *fbuf, + unsigned page); + +/** + * @brief Send subsequent fragment. + * + * @param[in] netif Network interface to send fragment over + * @param[in] fbuf Fragmentation buffer for the datagram to fragment + * @param[in] page Current 6Lo dispatch parsing page. + * + * @return Size of the fragment + */ +static uint16_t _send_nth_fragment(gnrc_netif_t *netif, + gnrc_sixlowpan_frag_fb_t *fbuf, + unsigned page); + +/** + * @brief Send a abort pseudo fragment for datagram identified by @p tag + * + * @param[in] pkt Datagram that is to be aborted. + * @param[in] tag Tag for @p pkt. + * @param[in] req_ack Request ACK for pseudo fragment from receive + * @param[in] page Current 6Lo dispatch parsing page. + * + * @return true, if abort pseudo fragment was sent. + * @return false, if abort pseudo fragment was unable to be sent. + */ +static bool _send_abort_frag(gnrc_pktsnip_t *pkt, uint8_t tag, bool req_ack, + unsigned page); + +/** + * @brief Re-send a fragment + * + * @param[in] node The fragment descriptor for the fragment to be + * resend + * @param[in] fbuf_ptr Fragmentation buffer for the datagram to fragment + * + * Used as a `clist_foreach()` iterator function + * + * return true when fragment was resent + * return false on error + */ +static int _resend_frag(clist_node_t *node, void *fbuf_ptr); + +/** + * @brief Retry to send the complete datagram + * + * @param[in] fbuf Fragmentation buffer for the datagram + */ +static void _retry_datagram(gnrc_sixlowpan_frag_fb_t *fbuf); + +/** + * @brief Cleans up state for the causing RFRAG and optionally also sends an + * abort ACK (NULL-bitmap ACK). + * + * @param[in] pkt The packet causing the abort. Will be released + * by this function. gnrc_pktsnip_t::data of @p pkt is + * expected to point to an RFRAG packet. + * @param[in] entry (Virtual) reassembly buffer entry to abort. + * @param[in] netif_hdr NETIF header of @p pkt. + * @param[in] send_ack Send an abort ACK. + */ +static void _abort_rb(gnrc_pktsnip_t *pkt, _generic_rb_entry_t *entry, + gnrc_netif_hdr_t *netif_hdr, bool send_ack); + +/** + * @brief Sends an RFRAG-ACK + * + * @param[in] netif Network interface to send ACK over + * @param[in] dst Destination address of ACK. + * @param[in] dst_len Length of @p dst. + * @param[in] rfrag The RFRAG to ACK + * @param[in] bitmap The bitmap for the ACK. + */ +static void _send_ack(gnrc_netif_t *netif, const uint8_t *dst, uint8_t dst_len, + const sixlowpan_sfr_t *rfrag, const uint8_t *bitmap); + +/** + * @brief Schedule next frame (RFRAG or RFRAG-ACK) with + * @ref GNRC_SIXLOWPAN_FRAG_SFR_INTER_FRAG_GAP_MSG + */ +static void _sched_next_frame(void); + +/** + * @brief Schedule ARQ timeout + * + * @param[in] fbuf A fragmentation buffer holding the state of the datagram + * and recoverable fragments. + * @param[in] offset Offset for the ARQ timeout in milliseconds. + */ +static void _sched_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf, uint32_t offset); + +/** + * @brief Schedule ARQ timeout for an abort fragment pseudo fragment + * + * @param[in,out] fbuf A fragmentation buffer. All state information will be + * cleared when called, except for identifying the ACK. + */ +static void _sched_abort_timeout(gnrc_sixlowpan_frag_fb_t *fbuf); + +/** + * @brief Handle a received RFRAG packet + * + * @param[in] netif_hdr NETIF header of @p pkt + * @param[in] pkt An RFRAG packet + * @param[in] page Current 6Lo dispatch parsing page. + */ +static void _handle_rfrag(gnrc_netif_hdr_t *netif_hdr, + gnrc_pktsnip_t *pkt, unsigned page); + +/** + * @brief Handle a received RFRAG-ACK + * + * @param[in] netif_hdr NETIF header of @p pkt + * @param[in] pkt An RFRAG-ACK + * @param[in] page Current 6Lo dispatch parsing page. + */ +static void _handle_ack(gnrc_netif_hdr_t *netif_hdr, + gnrc_pktsnip_t *pkt, unsigned page); + +/** + * @brief Forward a RFRAG + * + * @pre `entry->type == _VRB` + * + * @param[in] pkt The RFRAG to forward (without NETIF header) + * @param[in] entry The VRB entry to determine the route + * @param[in] offset Offset (from the incoming RFRAG's field) of the RFRAG. + * for offset > 0 this will be adapted for the offset + * difference from the first fragment due to recompression + * @param[in] page Current 6Lo dispatch parsing page. + * + * @return 0 on success, + * @return -ENOMEM, when packet buffer is too full to prepare packet for + * forwarding. @p pkt is released in that case. + */ +static int _forward_rfrag(gnrc_pktsnip_t *pkt, _generic_rb_entry_t *entry, + uint16_t offset, unsigned page); + +/* ====== PUBLIC FUNCTION DEFINITIONS ====== */ +void gnrc_sixlowpan_frag_sfr_init(void) +{ + if (CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US > 0) { + for (unsigned i = 0; i < FRAME_QUEUE_POOL_SIZE; i++) { + clist_rpush(&_frame_queue_free, &_frame_queue_pool[i].super); + } + } + for (unsigned i = 0; i < FRAG_DESCS_POOL_SIZE; i++) { + clist_rpush(&_frag_descs_free, &_frag_descs_pool[i].super); + } +} + +void gnrc_sixlowpan_frag_sfr_send(gnrc_pktsnip_t *pkt, void *ctx, + unsigned page) +{ + gnrc_sixlowpan_frag_fb_t *fbuf = ctx; + gnrc_netif_t *netif; + int error_no = GNRC_NETERR_SUCCESS; + uint16_t res; + + assert((fbuf != NULL) && ((fbuf->pkt == pkt) || (pkt == NULL))); + DEBUG("6lo sfr: (re-)sending fragmented datagram %u\n", fbuf->tag); + pkt = fbuf->pkt; + assert(pkt->type == GNRC_NETTYPE_NETIF); + netif = gnrc_netif_hdr_get_netif(pkt->data); + assert(netif != NULL); + + if (fbuf->offset == 0) { + DEBUG("6lo sfr: sending first fragment\n"); + res = _send_1st_fragment(netif, fbuf, page); + if (res == 0) { + DEBUG("6lo sfr: error sending first fragment\n"); + /* _send_1st_fragment only returns 0 if there is a memory problem */ + error_no = ENOMEM; + goto error; + } + } + else if (fbuf->sfr.frags_sent >= fbuf->sfr.window_size) { + DEBUG("6lo sfr: frags_sent >= fbuf->sfr.window_size: don't send more\n"); + return; + } + else if (fbuf->offset < fbuf->datagram_size) { + DEBUG("6lo sfr: sending subsequent fragment\n"); + res = _send_nth_fragment(netif, fbuf, page); + if (res == 0) { + DEBUG("6lo sfr: error sending subsequent fragment (offset = %u)\n", + fbuf->offset); + /* _send_nth_fragment only returns 0 if there is a memory problem */ + error_no = ENOMEM; + goto error; + } + } + else { + /* offset is greater or equal to datagram size + * => we are done sending fragments (not an error, but we can release + * the fragmentation buffer now) */ + goto error; + } + fbuf->offset += res; + + if ((fbuf->sfr.frags_sent < fbuf->sfr.window_size) && + (fbuf->offset < fbuf->datagram_size) && + !gnrc_sixlowpan_frag_fb_send(fbuf)) { + /* the queue of the 6LoWPAN thread is full */ + error_no = ENOMEM; + /* go back offset to not send abort on first fragment */ + fbuf->offset -= res; + goto error; + } + /* check if last fragment sent requested an ACK */ + _frag_desc_t *frag_desc = (_frag_desc_t *)clist_rpeek(&fbuf->sfr.window); + DEBUG("6lo sfr: last sent fragment (tag: %u, X: %i, seq: %u, " + "frag_size: %u, offset: %u)\n", + (uint8_t)fbuf->tag, _frag_ack_req(frag_desc), + _frag_seq(frag_desc), _frag_size(frag_desc), + frag_desc->offset); + if (_frag_ack_req(frag_desc)) { + /* initialize _arq_timer if not yet done */ + if (_arq_timer.callback == NULL) { + evtimer_init_msg(&_arq_timer); + } + _sched_arq_timeout(fbuf, fbuf->sfr.arq_timeout); + } + thread_yield(); + return; +error: + /* don't send abort for first fragment, the network does not know about + * the datagram */ + if ((fbuf->offset > 0) && + _send_abort_frag(fbuf->pkt, (uint8_t)fbuf->tag, true, 0)) { + /* wait for ACK before fbuf is deleted */ + _sched_abort_timeout(fbuf); + } + else { + _clean_up_fbuf(fbuf, error_no); + } +} + +void gnrc_sixlowpan_frag_sfr_recv(gnrc_pktsnip_t *pkt, void *ctx, + unsigned page) +{ + sixlowpan_sfr_t *hdr; + gnrc_netif_hdr_t *netif_hdr; + + (void)ctx; + DEBUG("6lo sfr: received selective fragment forwarding message\n"); + assert(pkt != NULL); + hdr = pkt->data; + assert(pkt->next != NULL); + netif_hdr = pkt->next->data; + assert(netif_hdr != NULL); + if (page != 0) { + DEBUG("6lo sfr: Invalid page %u\n", page); + gnrc_pktbuf_release(pkt); + } + else if (sixlowpan_sfr_rfrag_is(hdr)) { + _handle_rfrag(netif_hdr, pkt, page); + } + else if (sixlowpan_sfr_ack_is(hdr)) { + _handle_ack(netif_hdr, pkt, page); + } + else { + DEBUG("6lo sfr: Unknown dispatch: %02x\n", + hdr->disp_ecn & SIXLOWPAN_SFR_DISP_MASK); + gnrc_pktbuf_release(pkt); + } +} + +int gnrc_sixlowpan_frag_sfr_forward(gnrc_pktsnip_t *pkt, + sixlowpan_sfr_rfrag_t *rfrag, + gnrc_sixlowpan_frag_vrb_t *vrbe, + unsigned page) +{ + _generic_rb_entry_t entry = { .type = _VRB, .entry = { .vrb = vrbe } }; + gnrc_pktsnip_t *hdrsnip = gnrc_pktbuf_add(pkt, rfrag, sizeof(*rfrag), + GNRC_NETTYPE_SIXLOWPAN); + + /* free all intervals associated to the VRB entry, as we don't need them + * with SFR, so throw them out, to save this resource */ + while (vrbe->super.ints) { + vrbe->super.ints->end = 0U; + vrbe->super.ints = vrbe->super.ints->next; + } + if (hdrsnip == NULL) { + DEBUG("6lo sfr: Unable to allocate new rfrag header\n"); + gnrc_pktbuf_release(pkt); + return -ENOMEM; + } + DEBUG("6lo sfr: adapting old fragment size (%u) for forwarding to %u\n", + sixlowpan_sfr_rfrag_get_frag_size(hdrsnip->data), + (unsigned)gnrc_pkt_len(pkt)); + /* due to compression, packet length of the original fragment might have + * changed */ + sixlowpan_sfr_rfrag_set_frag_size(hdrsnip->data, gnrc_pkt_len(pkt)); + /* offset is adapted in `_forward_rfrag()` */ + return _forward_rfrag(hdrsnip, &entry, sixlowpan_sfr_rfrag_get_offset(rfrag), + page); +} + +void gnrc_sixlowpan_frag_sfr_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf) +{ + uint32_t now = xtimer_now_usec(); + _frag_desc_t * const head = (_frag_desc_t *)fbuf->sfr.window.next; + _frag_desc_t *frag_desc = head; + uint32_t next_arq_offset = fbuf->sfr.arq_timeout; + bool reschedule_arq_timeout = false; + int error_no = ETIMEDOUT; /* assume time out for fbuf->pkt */ + + DEBUG("6lo sfr: ARQ timeout for datagram %u\n", fbuf->tag); + fbuf->sfr.arq_timeout_event.msg.content.ptr = NULL; + /* copying clist_foreach because we can't work just in function context */ + if (frag_desc) { + do { + uint32_t diff; + + frag_desc = (_frag_desc_t *)frag_desc->super.next; + diff = now - frag_desc->last_sent; + if (diff < (fbuf->sfr.arq_timeout * US_PER_MS)) { + /* this fragment's last was last sent < fbuf->sfr.arq_timeout + * ago */ + uint32_t offset = fbuf->sfr.arq_timeout - (diff / US_PER_MS); + + DEBUG("6lo sfr: wait for fragment %u in next reschedule\n", + _frag_seq(frag_desc)); + if (offset < next_arq_offset) { + /* wait for this fragments ACK next */ + next_arq_offset = offset; + DEBUG(" (next ARQ timeout in %lu)\n", + (long unsigned)next_arq_offset); + } + /* this fragment is still waiting for its ACK, + * reschedule the next ACK timeout to the difference + * of the ACK timeout and the time of its last send */ + reschedule_arq_timeout = true; + } + else if (_frag_ack_req(frag_desc)) { + /* for this fragment we requested an ACK which was not received + * yet. Try to resend it */ + if ((frag_desc->retries++) < CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES) { + /* we have retries left for this fragment */ + DEBUG("6lo sfr: %u retries left for fragment (tag: %u, " + "X: %i, seq: %u, frag_size: %u, offset: %u)\n", + CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES - + (frag_desc->retries - 1), (uint8_t)fbuf->tag, + _frag_ack_req(frag_desc), _frag_seq(frag_desc), + _frag_size(frag_desc), frag_desc->offset); + if (_resend_frag(&frag_desc->super, fbuf) != 0) { + /* _resend_frag failed due to a memory resource + * problem */ + error_no = ENOMEM; + goto error; + } + /* fragment was resent successfully, schedule next ACK + * timeout */ + reschedule_arq_timeout = true; + } + else { + /* out of retries */ + DEBUG("6lo sfr: no retries left for fragment " + "(tag: %u, X: %i, seq: %u, frag_size: %u, " + "offset: %u)\n", + (uint8_t)fbuf->tag, _frag_ack_req(frag_desc), + _frag_seq(frag_desc), _frag_size(frag_desc), + frag_desc->offset); + /* we are out of retries on the fragment level, but we + * might be able to retry the datagram if retries for the + * datagram are configured. */ + _retry_datagram(fbuf); + return; + } + } + else { + /* Do not resend fragments that were not explicitly asking for + * an ACK from the reassembling endpoint on ACK timeout. + * If this is true for all fragments remaining in the fragment + * buffer, the datagram is to be considered timed out, so + * error_no should remain ETIMEDOUT */ + DEBUG("6lo sfr: nothing to do for fragment %u\n", + _frag_seq(frag_desc)); + } + } while (frag_desc != head); + } + else { + /* No fragments to resend, we can assume the packet was delivered + * successfully */ + error_no = GNRC_NETERR_SUCCESS; + } + assert(fbuf->sfr.frags_sent == clist_count(&fbuf->sfr.window)); + if (reschedule_arq_timeout) { + _sched_arq_timeout(fbuf, next_arq_offset); + return; + } +error: + /* don't check return value, as we don't want to wait for an ACK again ;-) */ + _send_abort_frag(fbuf->pkt, (uint8_t)fbuf->tag, false, 0); + _clean_up_fbuf(fbuf, error_no); +} + +void gnrc_sixlowpan_frag_sfr_inter_frame_gap(void) +{ + if (CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US > 0) { + _frame_queue_t *node = (_frame_queue_t *)clist_lpop(&_frame_queue); + + if (node != NULL) { + _last_frame_sent = xtimer_now_usec(); + gnrc_sixlowpan_dispatch_send(node->frame, NULL, node->page); + /* unset packet just to be safe */ + node->frame = NULL; + clist_rpush(&_frame_queue_free, &node->super); + } + if (clist_lpeek(&_frame_queue) != NULL) { + _sched_next_frame(); + } + } +} + +/* ====== INTERNAL FUNCTION DEFINITIONS ====== */ +static inline uint16_t _min(uint16_t a, size_t b) +{ + return (a < b) ? a : (uint16_t)b; +} + +static inline kernel_pid_t _getpid(void) +{ + /* in production, only the 6LoWPAN thread is supposed to call the API + * functions, so just get the current thread's PID for sending messages. + * When testing, those functions might however be called by the testing + * thread (usually the main thread), so indirect over the 6LoWPAN thread in + * that case */ + return IS_ACTIVE(TEST_SUITES) ? gnrc_sixlowpan_get_pid() : thread_getpid(); +} + +/* + * @brief Returns the datagram in @p fbuf to its original state + * + * This function can be both used to clean up the fragmentation buffer on + * failure without releasing @p fbuf's gnrc_sixlowpan_frag_fb_t::pkt and to + * reset a datagram for a datagram retry. + * + * @param[in] fbuf The fragmentation buffer entry to clean up + */ +static void _clean_slate_datagram(gnrc_sixlowpan_frag_fb_t *fbuf) +{ + clist_node_t new_queue = { .next = NULL }; + + fbuf->sfr.arq_timeout_event.msg.content.ptr = NULL; + /* remove potentially scheduled timers for this datagram */ + evtimer_del((evtimer_t *)(&_arq_timer), + &fbuf->sfr.arq_timeout_event.event); + fbuf->sfr.arq_timeout_event.event.next = NULL; + if (CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US > 0) { + for (clist_node_t *node = clist_lpop(&_frame_queue); + node != NULL; node = clist_lpop(&_frame_queue)) { + _frame_queue_t *entry = (_frame_queue_t *)node; + /* remove frames of this datagram from frame queue */ + if (entry->datagram_tag == fbuf->tag) { + gnrc_pktbuf_release(entry->frame); + /* unset packet just to be safe */ + entry->frame = NULL; + clist_rpush(&_frag_descs_free, node); + } + else { + clist_rpush(&new_queue, node); + } + } + /* reset frame queue with remaining frames */ + _frame_queue = new_queue; + } + fbuf->offset = 0U; + fbuf->sfr.cur_seq = 0U; + fbuf->sfr.frags_sent = 0U; + fbuf->sfr.window_size = CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE; + for (clist_node_t *node = clist_lpop(&fbuf->sfr.window); + node != NULL; node = clist_lpop(&fbuf->sfr.window)) { + clist_rpush(&_frag_descs_free, node); + } +} + +static gnrc_pktsnip_t *_build_rfrag(uint8_t tag, bool ack_req, uint16_t size, + uint8_t seq) +{ + sixlowpan_sfr_rfrag_t *hdr; + gnrc_pktsnip_t *frag = gnrc_pktbuf_add(NULL, NULL, + sizeof(sixlowpan_sfr_rfrag_t) + + size, GNRC_NETTYPE_SIXLOWPAN); + + if (frag == NULL) { + return NULL; + } + sixlowpan_sfr_rfrag_set_disp(frag->data); + + hdr = frag->data; + hdr->base.tag = tag; + if (ack_req) { + sixlowpan_sfr_rfrag_set_ack_req(hdr); + } + else { + sixlowpan_sfr_rfrag_clear_ack_req(hdr); + } + sixlowpan_sfr_rfrag_set_frag_size(hdr, size); + sixlowpan_sfr_rfrag_set_seq(hdr, seq); + /* set offset / datagram_size in callers */ + return frag; +} + +static gnrc_pktsnip_t *_build_frag_pkt(gnrc_netif_hdr_t *old_netif_hdr, + uint8_t tag, bool ack_req, uint16_t size, + uint8_t seq) +{ + gnrc_netif_hdr_t *new_netif_hdr; + gnrc_pktsnip_t *netif, *res; + + DEBUG("6lo sfr: building fragment (tag: %u, X: %i, seq: %u, frag_size: %u)\n", + tag, ack_req, seq, size); + netif = gnrc_netif_hdr_build(gnrc_netif_hdr_get_src_addr(old_netif_hdr), + old_netif_hdr->src_l2addr_len, + gnrc_netif_hdr_get_dst_addr(old_netif_hdr), + old_netif_hdr->dst_l2addr_len); + if (netif == NULL) { + return NULL; + } + + new_netif_hdr = netif->data; + *new_netif_hdr = *old_netif_hdr; + res = _build_rfrag(tag, ack_req, size, seq); + if (res == NULL) { + gnrc_pktbuf_release(netif); + return NULL; + } + return gnrc_pkt_prepend(res, netif); +} + +static gnrc_pktsnip_t *_build_frag_from_fbuf(gnrc_pktsnip_t *pkt, + gnrc_sixlowpan_frag_fb_t *fbuf, + uint16_t frag_size) +{ + return _build_frag_pkt(pkt->data, (uint8_t)fbuf->tag, + ((frag_size + fbuf->offset) >= fbuf->datagram_size) || + ((fbuf->sfr.frags_sent + 1) >= fbuf->sfr.window_size), + frag_size, fbuf->sfr.cur_seq); +} + +static uint16_t _copy_pkt_to_frag(uint8_t *data, const gnrc_pktsnip_t *pkt, + uint16_t frag_size, uint16_t init_offset) +{ + uint16_t offset = init_offset; + + while ((pkt != NULL) && (offset < frag_size)) { + uint16_t len = _min(frag_size - offset, pkt->size); + + memcpy(data + offset, pkt->data, len); + + offset += len; + pkt = pkt->next; + } + return offset; +} + +static uint16_t _find_offset_and_copy_rest(uint8_t *data, gnrc_pktsnip_t **pkt, + uint16_t frag_size, + uint16_t offset) +{ + uint16_t offset_count = 0, cur_frag_size = 0; + while ((*pkt != NULL) && (offset_count != offset)) { /* go to offset */ + uint16_t pkt_size = (uint16_t)(*pkt)->size; + offset_count += pkt_size; + + if (offset_count > offset) { /* we overshot */ + /* => copy rest of partly send packet snip */ + uint16_t pkt_offset = offset - (offset_count - pkt_size); + size_t clen = _min(frag_size, pkt_size - pkt_offset); + + memcpy(data, ((uint8_t *)(*pkt)->data) + pkt_offset, clen); + cur_frag_size = clen; + *pkt = (*pkt)->next; + break; + } + *pkt = (*pkt)->next; + } + return cur_frag_size; +} + +static bool _send_frame(gnrc_pktsnip_t *frame, void *ctx, unsigned page) +{ + uint32_t now = xtimer_now_usec(); + + if ((CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US == 0) || + ((now - _last_frame_sent) > CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US)) { + DEBUG("6lo sfr: dispatch frame to network interface\n"); + _last_frame_sent = now; + gnrc_sixlowpan_dispatch_send(frame, ctx, page); + return true; + } + else { + _frame_queue_t *node = (_frame_queue_t *)clist_lpop(&_frame_queue_free); + + if (node != NULL) { + sixlowpan_sfr_t *hdr = frame->next->data; + + assert(sixlowpan_sfr_is(hdr)); + node->frame = frame; + node->datagram_tag = hdr->tag; + node->page = page; + clist_rpush(&_frame_queue, &node->super); + _sched_next_frame(); + } + return (node != NULL); + } +} + +static bool _send_fragment(gnrc_pktsnip_t *frag, gnrc_sixlowpan_frag_fb_t *fbuf, + unsigned page, uint16_t offset) +{ + sixlowpan_sfr_rfrag_t *hdr = frag->next->data; + _frag_desc_t *frag_desc = (_frag_desc_t *)clist_lpop(&_frag_descs_free); + bool res; + + if (frag_desc == NULL) { + DEBUG("6lo sfr: could not remember fragment to send\n"); + gnrc_pktbuf_release(frag); + return false; + } + frag_desc->ar_seq_fs = byteorder_ntohs(hdr->ar_seq_fs); + frag_desc->offset = offset; + frag_desc->retries = 0; + clist_rpush(&fbuf->sfr.window, &frag_desc->super); + if ((res = _send_frame(frag, NULL, page))) { + frag_desc->last_sent = _last_frame_sent; + fbuf->sfr.cur_seq++; + fbuf->sfr.frags_sent++; + } + return res; +} + +static gnrc_pktsnip_t *_build_ack(gnrc_netif_t *netif, + const uint8_t *dst, uint8_t dst_len, + const sixlowpan_sfr_t *hdr, + const uint8_t *bitmap) +{ + gnrc_pktsnip_t *ack_snip, *ack_netif; + sixlowpan_sfr_ack_t *ack; + + ack_netif = gnrc_netif_hdr_build(NULL, 0, dst, dst_len); + if (ack_netif == NULL) { + DEBUG("6lo sfr: can't allocate netif header for ACK for (%s, %02x).\n", + gnrc_netif_addr_to_str(dst, dst_len, addr_str), hdr->tag); + return NULL; + } + gnrc_netif_hdr_set_netif(ack_netif->data, netif); + + ack_snip = gnrc_pktbuf_add(NULL, NULL, sizeof(sixlowpan_sfr_ack_t), + GNRC_NETTYPE_SIXLOWPAN); + + if (ack_snip == NULL) { + DEBUG("6lo sfr: can't allocate ACK for (%s, %02x).\n", + gnrc_netif_addr_to_str(dst, dst_len, addr_str), hdr->tag); + gnrc_pktbuf_release(ack_netif); + return NULL; + } + ack = ack_snip->data; + /* https://tools.ietf.org/html/rfc8931#section-6: + * The Datagram_Tag in the RFRAG_ACK is unique to the reassembling endpoint + * and is enough information for an intermediate hop to locate the VRB that + * contains the Datagram_Tag used by the previous hop and the Layer-2 + * information associated with it (interface and Link-Layer address).. + * [...] The reassembling endpoint of a fragment with the 'E' (ECN) flag set + * MUST echo that information at most once by setting the 'E' (ECN) flag in + * the next RFRAG_ACK. + * + * => base except dispatch are the same as ack'd RFRAG. + */ + ack->base = *hdr; + sixlowpan_sfr_ack_set_disp(&ack->base); + memcpy(ack->bitmap, bitmap, sizeof(ack->bitmap)); + ack_netif->next = ack_snip; + return ack_netif; +} + +static void _clean_up_rb_entry(_generic_rb_entry_t *entry) +{ + if (entry != NULL) { + switch (entry->type) { + case _RB: + gnrc_pktbuf_release(entry->entry.rb->pkt); + gnrc_sixlowpan_frag_rb_remove(entry->entry.rb); + break; + case _VRB: + gnrc_sixlowpan_frag_vrb_rm(entry->entry.vrb); + break; + default: + break; + } + } +} + +static void _try_reassembly(gnrc_netif_hdr_t *netif_hdr, + gnrc_pktsnip_t *rfrag, unsigned offset, + _generic_rb_entry_t *entry, + unsigned page) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + const gnrc_sixlowpan_frag_sfr_bitmap_t *bitmap; + sixlowpan_sfr_rfrag_t *hdr = rfrag->data; + gnrc_pktsnip_t *netif_snip = rfrag->next; + /* copy base for ACK */ + sixlowpan_sfr_t base = hdr->base; + int8_t ack_req = sixlowpan_sfr_rfrag_ack_req(hdr); + uint8_t seq = sixlowpan_sfr_rfrag_get_seq(hdr); + + assert(netif_snip->data == netif_hdr); + gnrc_pktbuf_hold(netif_snip, 1); /* hold netif header to use it with + * dispatch_when_complete() + * (rb_add() releases `pkt`) */ + entry->entry.rb = gnrc_sixlowpan_frag_rb_add(netif_hdr, rfrag, + offset, page); + /* check if VRB entry was created */ + vrbe = gnrc_sixlowpan_frag_vrb_get(gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, base.tag); + if ((entry->entry.rb == NULL) && (vrbe == NULL)) { + DEBUG("6lo sfr: can't allocate reassembly buffer or forward compressed " + "fragment\n"); + /* send abort */ + bitmap = &_null_bitmap; + } + else if (vrbe != NULL) { + DEBUG("6lo sfr: packet was forwarded\n"); + goto end; + } + else { + int res; + + DEBUG("6lo sfr: reassembling datagram (%s, %u)\n", + gnrc_netif_addr_to_str(gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, addr_str), + base.tag); + entry->type = _RB; + bf_set(entry->entry.rb->received, seq); + if ((res = gnrc_sixlowpan_frag_rb_dispatch_when_complete(entry->entry.rb, + netif_hdr)) < 0) { + DEBUG("6lo sfr: can not dispatch datagram to upper layer\n"); + _clean_up_rb_entry(entry); + /* send abort */ + bitmap = &_null_bitmap; + } + else { + if (res) { + DEBUG("6lo sfr: dispatched datagram to upper layer\n"); + bitmap = &_full_bitmap; + } + else if (ack_req) { + DEBUG("6lo sfr: ACKing received fragments %02X%02X%02X%02X " + "(%u of %u bytes received)\n", + entry->entry.rb->received[0], + entry->entry.rb->received[1], + entry->entry.rb->received[2], + entry->entry.rb->received[3], + entry->entry.base->current_size, + entry->entry.base->datagram_size); + bitmap = _to_bitmap(entry->entry.rb->received); + } + else { + /* no ACK was requested and no error was causing an abort ACK*/ + DEBUG("6lo sfr: no ACK requested by received fragment %u " + "(bitmap so far: %02X%02X%02X%02X)\n", seq, + entry->entry.rb->received[0], + entry->entry.rb->received[1], + entry->entry.rb->received[2], + entry->entry.rb->received[3]); + goto end; + } + } + } + _send_ack(gnrc_netif_hdr_get_netif(netif_hdr), + gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, &base, bitmap->bf); +end: + gnrc_pktbuf_release(netif_snip); /* release hold */ +} + +static void _forward_uncomp(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, + unsigned page, _generic_rb_entry_t *entry, + void *payload) +{ + sixlowpan_sfr_rfrag_t *hdr = pkt->data; + gnrc_sixlowpan_frag_rb_base_t vrb_base = { + .src_len = netif_hdr->src_l2addr_len, + .tag = hdr->base.tag, + .datagram_size = sixlowpan_sfr_rfrag_get_offset(hdr), + }; + gnrc_pktsnip_t tmp = { + .data = payload, + .size = pkt->size - sizeof(sixlowpan_sfr_rfrag_t) - 1, + .users = 1, + }; + + switch (page) { +#if defined(MODULE_GNRC_IPV6) + case 0: { + ipv6_hdr_t *ipv6_hdr = tmp.data; + + if (ipv6_hdr->hl <= 1) { + DEBUG("6lo sfr: minimal hop-limit reached\n"); + /* try to reassemble to hand to IPv6 module for error + * handling */ + _try_reassembly(netif_hdr, pkt, 0, entry, page); + return; + } + tmp.type = GNRC_NETTYPE_IPV6; + break; + } +#endif + default: + tmp.type = GNRC_NETTYPE_UNDEF; + break; + } + vrb_base.arrival = xtimer_now_usec(); + memcpy(vrb_base.src, gnrc_netif_hdr_get_src_addr(netif_hdr), + vrb_base.src_len); + entry->entry.vrb = gnrc_sixlowpan_frag_vrb_from_route(&vrb_base, + NULL, &tmp); + if (entry->entry.vrb == NULL) { + DEBUG("6lo sfr: no route found or no VRB space left, " + "trying reassembly\n"); + _try_reassembly(netif_hdr, pkt, 0, entry, page); + return; + } + /* only decrement hop-limit after check in case we reassemble */ + switch (page) { +#if defined(MODULE_GNRC_IPV6) + case 0: { + ipv6_hdr_t *ipv6_hdr = tmp.data; + + ipv6_hdr->hl--; + break; + } +#endif + default: + break; + } + entry->type = _VRB; + entry->entry.vrb->in_netif = gnrc_netif_hdr_get_netif(netif_hdr); + entry->entry.vrb->offset_diff = 0; /* packet is uncompressed so offset + * does not change */ + _forward_rfrag(pkt, entry, sixlowpan_sfr_rfrag_get_offset(hdr), page); +} + +static void _handle_1st_rfrag(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, + unsigned page, _generic_rb_entry_t *entry) +{ + sixlowpan_sfr_rfrag_t *hdr = pkt->data; + /* https://tools.ietf.org/html/rfc8931#section-5.1: + * + For a first fragment (i.e. with a Sequence of 0), this field + * indicates the datagram_size of the compressed datagram, [...] */ + uint16_t datagram_size = sixlowpan_sfr_rfrag_get_offset(hdr); + uint8_t fragment_size = sixlowpan_sfr_rfrag_get_frag_size(hdr); + uint8_t *payload; + + if ((datagram_size == 0) && (fragment_size == 0)) { + /* the received fragment is a pseudo-fragment that signals an abort + * condition by the fragmenting end-point, release state on the + * datagram */ + bool release_pkt = true; + + DEBUG("6lo sfr: Abort for datagram (%s, %u) received\n", + gnrc_netif_addr_to_str(gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, addr_str), + hdr->base.tag); + if ((entry->entry.vrb = gnrc_sixlowpan_frag_vrb_get( + gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, hdr->base.tag)) != NULL) { + /* we have a VRB on the aborted datagram. Release it */ + entry->type = _VRB; + _forward_rfrag(pkt, entry, 0, page); + gnrc_sixlowpan_frag_vrb_rm(entry->entry.vrb); + release_pkt = false; + } + if ((entry->entry.rb = gnrc_sixlowpan_frag_rb_get_by_datagram( + netif_hdr, hdr->base.tag)) != NULL) { + /* we have a reassembly buffer entry on the aborted datagram. + * Release it */ + entry->type = _RB; + _abort_rb(pkt, entry, netif_hdr, sixlowpan_sfr_rfrag_ack_req(hdr)); + release_pkt = false; + } + if (release_pkt) { + DEBUG("6lo sfr: received abort for unknown datagram\n"); + /* neither VRB or RB exists so we don't have any state on the + * aborted datagram left. Just release the abort pseudo fragment */ + gnrc_pktbuf_release(pkt); + } + return; + } + DEBUG("6lo sfr: First fragment (%s, %u) received\n", + gnrc_netif_addr_to_str(gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, addr_str), + hdr->base.tag); + + payload = (uint8_t *)(hdr + 1); + if (payload[0] == SIXLOWPAN_UNCOMP) { + _forward_uncomp(netif_hdr, pkt, page, entry, payload + 1); + } + else { + if (IS_USED(MODULE_GNRC_SIXLOWPAN_IPHC) && + sixlowpan_iphc_is(payload)) { + _try_reassembly(netif_hdr, pkt, 0, entry, page); + return; + } + DEBUG("6lo sfr: unable to parse next dispatch for forwarding " + "information. Abort\n"); + _abort_rb(pkt, entry, netif_hdr, false); + return; + } +} + +static void _handle_nth_rfrag(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, + unsigned page, _generic_rb_entry_t *entry) +{ + sixlowpan_sfr_rfrag_t *hdr = pkt->data; + uint16_t offset = sixlowpan_sfr_rfrag_get_offset(hdr); + + DEBUG("6lo sfr: Subsequent fragment (%s, %u) received\n", + gnrc_netif_addr_to_str(gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, addr_str), + hdr->base.tag); + if (gnrc_sixlowpan_frag_rb_exists(netif_hdr, hdr->base.tag)) { + DEBUG("6lo sfr: I am destination endpoint => adding to reassembly " + "buffer\n"); + _try_reassembly(netif_hdr, pkt, offset, entry, page); + } + else if ((entry->entry.vrb = gnrc_sixlowpan_frag_vrb_get( + gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, hdr->base.tag)) != NULL) { + entry->type = _VRB; + entry->entry.base->arrival = xtimer_now_usec(); + _forward_rfrag(pkt, entry, offset, page); + } + else { + DEBUG("6lo sfr: neither VRB nor RB found\n"); + /* always send abort ACK: + * https://tools.ietf.org/html/rfc8931#section-6.1.2 */ + _abort_rb(pkt, entry, netif_hdr, true); + } +} + +static void _check_failed_frags(sixlowpan_sfr_ack_t *ack, + gnrc_sixlowpan_frag_fb_t *fbuf) +{ + _frag_desc_t *frag_desc; + clist_node_t not_received = { .next = NULL }; + + DEBUG("6lo sfr: checking which fragments to resend for datagram %u\n", + fbuf->tag); + for (frag_desc = (_frag_desc_t *)clist_lpop(&fbuf->sfr.window); + frag_desc != NULL; + frag_desc = (_frag_desc_t *)clist_lpop(&fbuf->sfr.window)) { + uint8_t seq; + + seq = _frag_seq(frag_desc); + if (bf_isset(ack->bitmap, seq)) { + DEBUG("6lo sfr: fragment %u (offset: %u, frag_size: %u) " + "for datagram %u was received\n", seq, + frag_desc->offset, _frag_size(frag_desc), fbuf->tag); + fbuf->sfr.frags_sent--; + clist_rpush(&_frag_descs_free, &frag_desc->super); + } + else { + DEBUG("6lo sfr: fragment %u (offset: %u, frag_size: %u) " + "for datagram %u was not received\n", seq, + frag_desc->offset, _frag_size(frag_desc), fbuf->tag); + if ((frag_desc->retries++) < CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES) { + DEBUG("6lo sfr: %u retries left\n", + CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES - + (frag_desc->retries - 1)); + /* put fragment in "not received" list */ + clist_rpush(¬_received, &frag_desc->super); + frag_desc->ar_seq_fs &= ~(SIXLOWPAN_SFR_ACK_REQ << 8U); + } + else { + DEBUG("6lo sfr: no more retries for fragment %u\n", seq); + clist_rpush(&_frag_descs_free, &frag_desc->super); + /* retry to resend whole datagram */ + _retry_datagram(fbuf); + return; + } + } + } + /* all fragments were received of the current window were received and + * the datagram was transmitted completely */ + if ((clist_lpeek(¬_received) == NULL) && + (fbuf->offset == fbuf->datagram_size)) { + /* release fragmentation buffer */ + _clean_up_fbuf(fbuf, GNRC_NETERR_SUCCESS); + } + /* at least one fragment was not received */ + else { + fbuf->sfr.window = not_received; + assert(fbuf->sfr.frags_sent == clist_count(&fbuf->sfr.window)); + /* use _resend_frag here instead of loop above, so _resend_frag + * can know if the fragment is the last in the window by using + * clist_rpeek() on fbuf->sfr.window */ + if (clist_foreach(&fbuf->sfr.window, _resend_frag, fbuf) != NULL) { + /* XXX: it is unlikely that allocating an abort RFRAG will be + * successful since the resources missing to cause the abort are + * still in use, but we should at least try */ + if (_send_abort_frag(fbuf->pkt, (uint8_t)fbuf->tag, true, 0)) { + /* wait for ACK before fbuf is deleted */ + _sched_abort_timeout(fbuf); + } + else { + /* we have no memory resources left to send neither the + * resent fragment nor the abort ACK to signalize that fact to + * the reassembling endpoint */ + _clean_up_fbuf(fbuf, ENOMEM); + } + } + if ((fbuf->sfr.frags_sent < fbuf->sfr.window_size) && + (fbuf->offset < fbuf->datagram_size)) { + DEBUG("6lo sfr: trigger send of further fragments of datagram %u\n", + fbuf->tag); + gnrc_sixlowpan_frag_fb_send(fbuf); + } + } +} + +/* ====== INTERNAL FUNCTIONS USED BY PUBLIC FUNCTIONS ====== + * ====== AND TO MANIPULATE INTERNAL DATA STRUCTURES ====== */ +static inline gnrc_sixlowpan_frag_sfr_bitmap_t *_to_bitmap(uint8_t *bitmap) +{ + return (gnrc_sixlowpan_frag_sfr_bitmap_t *)bitmap; +} + +static inline bool _frag_ack_req(_frag_desc_t *frag) +{ + + return (frag->ar_seq_fs & (SIXLOWPAN_SFR_ACK_REQ << 8U)); +} + +static inline uint8_t _frag_seq(_frag_desc_t *frag) +{ + return (frag->ar_seq_fs & (SIXLOWPAN_SFR_SEQ_MASK << 8U)) >> + (SIXLOWPAN_SFR_SEQ_POS + 8U); +} + +static inline uint16_t _frag_size(_frag_desc_t *frag) +{ + return (frag->ar_seq_fs & SIXLOWPAN_SFR_FRAG_SIZE_MASK); +} + +static void _clean_up_fbuf(gnrc_sixlowpan_frag_fb_t *fbuf, int error) +{ + DEBUG("6lo sfr: removing fragmentation buffer entry for datagram %u\n", + fbuf->tag); + _clean_slate_datagram(fbuf); + gnrc_pktbuf_release_error(fbuf->pkt, error); + fbuf->pkt = NULL; +} + +static uint16_t _send_1st_fragment(gnrc_netif_t *netif, + gnrc_sixlowpan_frag_fb_t *fbuf, + unsigned page) +{ + gnrc_pktsnip_t *frag, *pkt = fbuf->pkt; + sixlowpan_sfr_rfrag_t *hdr; + uint8_t *data; + size_t comp_form_size = gnrc_pkt_len(pkt->next); + uint16_t frag_size = (uint16_t)netif->sixlo.max_frag_size - + sizeof(sixlowpan_sfr_rfrag_t); + + assert((fbuf->sfr.cur_seq == 0) && (fbuf->sfr.frags_sent == 0)); + assert(fbuf->sfr.window.next == NULL); + assert(comp_form_size <= UINT16_MAX); + /* restrict tag to value space of SFR, so that later RFRAG ACK can find + * it in reverse look-up */ + fbuf->tag &= UINT8_MAX; + DEBUG("6lo sfr: determined frag_size = %u\n", frag_size); + + /* packet was compressed */ + if (fbuf->datagram_size > comp_form_size) { + /* add slack to first fragment */ + frag_size -= (fbuf->datagram_size - comp_form_size); + /* use compressed form */ + fbuf->datagram_size = (uint16_t)gnrc_pkt_len(pkt->next); + } + else { + /* Add uncompressed datagram dispatch to "compressed form" + * datagram_size */ + fbuf->datagram_size++; + } + fbuf->sfr.arq_timeout = CONFIG_GNRC_SIXLOWPAN_SFR_OPT_ARQ_TIMEOUT_MS; + fbuf->sfr.window_size = CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE; + + frag = _build_frag_from_fbuf(pkt, fbuf, frag_size); + if (frag == NULL) { + DEBUG("6lo sfr: error allocating first fragment\n"); + return 0; + } + hdr = frag->next->data; + data = (uint8_t *)(hdr + 1); + sixlowpan_sfr_rfrag_set_offset(hdr, fbuf->datagram_size); + /* don't copy netif header of pkt => pkt->next */ + frag_size = _copy_pkt_to_frag(data, pkt->next, frag_size, 0); + + DEBUG("6lo sfr: send first fragment (tag: %u, X: %i, seq: %u, " + "frag_size: %u, datagram_size: %u)\n", + hdr->base.tag, sixlowpan_sfr_rfrag_ack_req(hdr), + sixlowpan_sfr_rfrag_get_seq(hdr), + sixlowpan_sfr_rfrag_get_frag_size(hdr), + sixlowpan_sfr_rfrag_get_offset(hdr)); + if (!_send_fragment(frag, fbuf, page, 0)) { + frag_size = 0; + } + return frag_size; +} + +static uint16_t _send_nth_fragment(gnrc_netif_t *netif, + gnrc_sixlowpan_frag_fb_t *fbuf, + unsigned page) +{ + gnrc_pktsnip_t *frag, *pkt = fbuf->pkt; + sixlowpan_sfr_rfrag_t *hdr; + uint8_t *data; + uint16_t frag_size = (uint16_t)netif->sixlo.max_frag_size - + sizeof(sixlowpan_sfr_rfrag_t); + uint16_t local_offset; + + assert((fbuf->sfr.cur_seq > 0) && + (fbuf->sfr.cur_seq <= SIXLOWPAN_SFR_SEQ_MAX)); + assert((fbuf->sfr.frags_sent == 0) || (fbuf->sfr.window.next != NULL)); + assert(fbuf->tag <= UINT8_MAX); + + DEBUG("6lo sfr: determined frag_size = %u\n", frag_size); + frag = _build_frag_from_fbuf(pkt, fbuf, + _min(frag_size, + fbuf->datagram_size - fbuf->offset)); + if (frag == NULL) { + DEBUG("6lo sfr: error allocating subsequent fragment\n"); + return 0; + } + hdr = frag->next->data; + data = (uint8_t *)(hdr + 1); + sixlowpan_sfr_rfrag_set_offset(hdr, fbuf->offset); + pkt = pkt->next; /* don't copy netif header */ + local_offset = _find_offset_and_copy_rest(data, &pkt, frag_size, + fbuf->offset); + /* copy remaining packet snips */ + local_offset = _copy_pkt_to_frag(data, pkt, frag_size, local_offset); + DEBUG("6lo sfr: send subsequent fragment (tag: %u, X: %i, seq: %u, " + "frag_size: %u, offset: %u)\n", + hdr->base.tag, sixlowpan_sfr_rfrag_ack_req(hdr), + sixlowpan_sfr_rfrag_get_seq(hdr), + sixlowpan_sfr_rfrag_get_frag_size(hdr), + sixlowpan_sfr_rfrag_get_offset(hdr)); + if (!_send_fragment(frag, fbuf, page, fbuf->offset)) { + local_offset = 0; + } + return local_offset; +} + +static bool _send_abort_frag(gnrc_pktsnip_t *pkt, uint8_t tag, bool req_ack, + unsigned page) +{ + gnrc_pktsnip_t *frag; + + frag = _build_frag_pkt(pkt->data, tag, req_ack, 0, 0); + if (frag != NULL) { + sixlowpan_sfr_rfrag_set_offset(frag->next->data, 0); + _send_frame(frag, NULL, page); + return true; + } + return false; +} + +static int _resend_frag(clist_node_t *node, void *fbuf_ptr) +{ + _frag_desc_t *frag_desc = (_frag_desc_t *)node; + gnrc_sixlowpan_frag_fb_t *fbuf = fbuf_ptr; + gnrc_pktsnip_t *frag, *pkt = fbuf->pkt; + sixlowpan_sfr_rfrag_t *hdr; + uint8_t *data; + uint16_t frag_size = _frag_size(frag_desc), cur_frag_size; + + frag = _build_frag_pkt(pkt->data, (uint8_t)fbuf->tag, false, + frag_size, 0); + if (frag == NULL) { + DEBUG("6lo sfr: error allocating fragment to resend\n"); + return 1; + } + hdr = frag->next->data; + /* is last fragment in window */ + if (((fbuf->sfr.frags_sent >= fbuf->sfr.window_size) || + (fbuf->offset >= fbuf->datagram_size)) && + (clist_node_t *)frag_desc == clist_rpeek(&fbuf->sfr.window)) { + frag_desc->ar_seq_fs |= (SIXLOWPAN_SFR_ACK_REQ << 8U); + _sched_arq_timeout(fbuf, fbuf->sfr.arq_timeout); + } + hdr->ar_seq_fs = byteorder_htons(frag_desc->ar_seq_fs); + if (frag_desc->offset > 0) { + sixlowpan_sfr_rfrag_set_offset(hdr, frag_desc->offset); + } + else { + sixlowpan_sfr_rfrag_set_offset(hdr, fbuf->datagram_size); + } + + data = (uint8_t *)(hdr + 1); + pkt = pkt->next; /* don't copy netif header */ + cur_frag_size = _find_offset_and_copy_rest(data, &pkt, frag_size, + frag_desc->offset); + /* copy remaining packet snips */ + cur_frag_size = _copy_pkt_to_frag(data, pkt, frag_size, cur_frag_size); + DEBUG("6lo sfr: resending fragment (retry: %u, tag: %u, X: %i, seq: %u, " + "frag_size: %u, %s: %u)\n", frag_desc->retries, + hdr->base.tag, sixlowpan_sfr_rfrag_ack_req(hdr), + sixlowpan_sfr_rfrag_get_seq(hdr), + sixlowpan_sfr_rfrag_get_frag_size(hdr), + (sixlowpan_sfr_rfrag_get_seq(hdr)) ? "offset" : "datagram_size", + sixlowpan_sfr_rfrag_get_offset(hdr)); + if (_send_frame(frag, NULL, 0)) { + frag_desc->last_sent = _last_frame_sent; + return 0; + } + else { + return 1; + } +} + +static void _retry_datagram(gnrc_sixlowpan_frag_fb_t *fbuf) +{ + if ((CONFIG_GNRC_SIXLOWPAN_SFR_DG_RETRIES == 0) || + (fbuf->sfr.retrans == 0)) { + DEBUG("6lo sfr: giving up to send datagram %u\n", + fbuf->tag); + _clean_up_fbuf(fbuf, ETIMEDOUT); + } + else { + DEBUG("6lo sfr: Retrying to send datagram %u completely\n", fbuf->tag); + fbuf->sfr.retrans--; + /* return fragmentation buffer to its original state to resend the whole + * datagram again */ + _clean_slate_datagram(fbuf); + gnrc_sixlowpan_frag_sfr_send(fbuf->pkt, fbuf, 0); + } +} + +static void _abort_rb(gnrc_pktsnip_t *pkt, _generic_rb_entry_t *entry, + gnrc_netif_hdr_t *netif_hdr, bool send_ack) +{ + sixlowpan_sfr_rfrag_t *hdr = pkt->data; + + DEBUG("6lo sfr: Aborting datagram (%s, %02x)\n", + gnrc_netif_addr_to_str(gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, addr_str), + hdr->base.tag); + if (send_ack) { + _send_ack(gnrc_netif_hdr_get_netif(netif_hdr), + gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, + &hdr->base, _null_bitmap.bf); + } + _clean_up_rb_entry(entry); + gnrc_pktbuf_release(pkt); +} + +static void _send_ack(gnrc_netif_t *netif, const uint8_t *dst, uint8_t dst_len, + const sixlowpan_sfr_t *hdr, const uint8_t *bitmap) +{ + gnrc_pktsnip_t *ack = _build_ack(netif, dst, dst_len, hdr, bitmap); + + DEBUG("6lo sfr: Sending ACK for (%s, %02x): %02X%02X%02X%02X\n", + gnrc_netif_addr_to_str(dst, dst_len, addr_str), + hdr->tag, bitmap[0], bitmap[1], bitmap[2], bitmap[3]); + if (ack != NULL) { + _send_frame(ack, NULL, 0); + } + else { + DEBUG("6lo sfr: unable to build ACK for sending\n"); + } +} + +static void _sched_next_frame(void) +{ + if (CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US > 0) { + int state = irq_disable(); /* make timer check atomic */ + bool already_set = (_if_gap_timer.offset || + _if_gap_timer.long_offset); + + irq_restore(state); + if (!already_set) { + uint32_t last_sent_since = (_last_frame_sent - xtimer_now_usec()); + + if (last_sent_since <= CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US) { + uint32_t offset = CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US - + last_sent_since; + DEBUG("6lo sfr: arming inter-frame timer in %" PRIu32 " us\n", + last_sent_since); + xtimer_set_msg(&_if_gap_timer, offset, &_if_gap_msg, _getpid()); + } + else { + DEBUG("6lo sfr: send frame immediately\n"); + gnrc_sixlowpan_frag_sfr_inter_frame_gap(); + } + } + else { + DEBUG("6lo sfr: inter-frame timer was already set\n"); + } + } +} + +static void _sched_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf, uint32_t offset) +{ + if (fbuf->sfr.arq_timeout_event.msg.content.ptr != NULL) { + DEBUG("6lo sfr: ARQ timeout for datagram %u already scheduled\n", + (uint8_t)fbuf->tag); + return; + } + DEBUG("6lo sfr: arming ACK timeout in %lums for datagram %u\n", + (long unsigned)offset, fbuf->tag); + fbuf->sfr.arq_timeout_event.event.offset = offset; + fbuf->sfr.arq_timeout_event.msg.content.ptr = fbuf; + fbuf->sfr.arq_timeout_event.msg.type = GNRC_SIXLOWPAN_FRAG_SFR_ARQ_TIMEOUT_MSG; + evtimer_add_msg(&_arq_timer, &fbuf->sfr.arq_timeout_event, + _getpid()); +} + +static void _sched_abort_timeout(gnrc_sixlowpan_frag_fb_t *fbuf) +{ + /* no fragments to wait for anymore as we aborted fragmentation and just + * wait for an ACK by the reassembling end point that they know. As such, + * clean-out the fragmentation buffer. */ + _clean_slate_datagram(fbuf); + fbuf->sfr.retrans = 0; + _sched_arq_timeout(fbuf, fbuf->sfr.arq_timeout); +} + +static void _handle_rfrag(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, + unsigned page) +{ + _generic_rb_entry_t entry = { .type = _UNDEF }; + + if (sixlowpan_sfr_rfrag_get_seq(pkt->data) == 0U) { + _handle_1st_rfrag(netif_hdr, pkt, page, &entry); + } + else { + _handle_nth_rfrag(netif_hdr, pkt, page, &entry); + } +} + +static void _handle_ack(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, + unsigned page) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + sixlowpan_sfr_ack_t *hdr = pkt->data; + + (void)page; + DEBUG("6lo sfr: received ACK for datagram (%s, %02x): %02X%02X%02X%02X\n", + gnrc_netif_addr_to_str(gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, + addr_str), hdr->base.tag, + hdr->bitmap[0], hdr->bitmap[1], hdr->bitmap[2], hdr->bitmap[3]); + if ((vrbe = gnrc_sixlowpan_frag_vrb_reverse( + gnrc_netif_hdr_get_netif(netif_hdr), + gnrc_netif_hdr_get_src_addr(netif_hdr), + netif_hdr->src_l2addr_len, hdr->base.tag)) != NULL) { + /* we found a VRB entry by reverse lookup, forward ACK further down. */ + sixlowpan_sfr_t mock_base = { .disp_ecn = hdr->base.disp_ecn, + .tag = vrbe->super.tag }; + DEBUG("6lo sfr: forward ACK to (%s, %02x)\n", + gnrc_netif_addr_to_str(vrbe->super.src, vrbe->super.src_len, + addr_str), vrbe->super.tag); + _send_ack(vrbe->in_netif, vrbe->super.src, vrbe->super.src_len, + &mock_base, hdr->bitmap); + if ((unaligned_get_u32(hdr->bitmap) == _full_bitmap.u32) || + (unaligned_get_u32(hdr->bitmap) == _null_bitmap.u32)) { + if (CONFIG_GNRC_SIXLOWPAN_FRAG_RBUF_DEL_TIMER > 0) { + /* garbage-collect entry after CONFIG_GNRC_SIXLOWPAN_FRAG_RBUF_DEL_TIMER + * microseconds */ + vrbe->super.arrival = xtimer_now_usec() - + (CONFIG_GNRC_SIXLOWPAN_FRAG_VRB_TIMEOUT_US - + CONFIG_GNRC_SIXLOWPAN_FRAG_RBUF_DEL_TIMER); + } + else { + gnrc_sixlowpan_frag_vrb_rm(vrbe); + } + } + else { + vrbe->super.arrival = xtimer_now_usec(); + } + } + else { + gnrc_sixlowpan_frag_fb_t *fbuf; + + if ((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(hdr->base.tag)) != NULL) { + /* ACK for pending ACK timeout received. removing ACK timeout */ + DEBUG("6lo sfr: cancelling ARQ timeout\n"); + evtimer_del((evtimer_t *)(&_arq_timer), + &fbuf->sfr.arq_timeout_event.event); + fbuf->sfr.arq_timeout_event.msg.content.ptr = NULL; + if ((unaligned_get_u32(hdr->bitmap) == _null_bitmap.u32)) { + /* ACK indicates the reassembling endpoint canceled reassembly + */ + DEBUG("6lo sfr: fragmentation canceled\n"); + /* Retry to send whole datagram if configured, otherwise + * cancel fragmentation */ + _retry_datagram(fbuf); + } + else { + /* Check and resent failed fragments within the current window + */ + _check_failed_frags(hdr, fbuf); + } + } + else { + DEBUG("6lo sfr: no VRB or fragmentation buffer found\n"); + } + } + gnrc_pktbuf_release(pkt); +} + +static int _forward_rfrag(gnrc_pktsnip_t *pkt, _generic_rb_entry_t *entry, + uint16_t offset, unsigned page) +{ + gnrc_pktsnip_t *old, *new = gnrc_netif_hdr_build( + NULL, 0, + entry->entry.base->dst, entry->entry.base->dst_len + ); + sixlowpan_sfr_rfrag_t *hdr; + + assert(entry->type == _VRB); + /* restrict out_tag to value space of SFR, so that later RFRAG ACK can find + * it in reverse look-up */ + entry->entry.vrb->out_tag &= UINT8_MAX; + DEBUG("6lo sfr: Forwarding to (%s, %u)\n", + gnrc_netif_addr_to_str(entry->entry.base->dst, + entry->entry.base->dst_len, addr_str), + entry->entry.vrb->out_tag); + if (new == NULL) { + DEBUG("6lo sfr: Unable to forward fragment, " + "packet buffer full\n"); + gnrc_pktbuf_release(pkt); + return -ENOMEM; + } + + hdr = pkt->data; + old = gnrc_pktsnip_search_type(pkt, GNRC_NETTYPE_NETIF); + if (old != NULL) { + /* remove original netif header */ + gnrc_pktbuf_remove_snip(pkt, old); + } + if (offset > 0) { + offset += entry->entry.vrb->offset_diff; + } + sixlowpan_sfr_rfrag_set_offset(hdr, offset); + hdr->base.tag = entry->entry.vrb->out_tag; + gnrc_netif_hdr_set_netif(new->data, entry->entry.vrb->out_netif); + new->next = pkt; + _send_frame(new, NULL, page); + return 0; +} + +/** @} */ diff --git a/sys/net/gnrc/network_layer/sixlowpan/gnrc_sixlowpan.c b/sys/net/gnrc/network_layer/sixlowpan/gnrc_sixlowpan.c index 7b2e69b18b42..1592bf9a4c42 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/gnrc_sixlowpan.c +++ b/sys/net/gnrc/network_layer/sixlowpan/gnrc_sixlowpan.c @@ -23,6 +23,9 @@ #include "net/gnrc/sixlowpan.h" #include "net/gnrc/sixlowpan/frag.h" #include "net/gnrc/sixlowpan/frag/rb.h" +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR +#include "net/gnrc/sixlowpan/frag/sfr.h" +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ #include "net/gnrc/sixlowpan/iphc.h" #include "net/gnrc/netif.h" #include "net/sixlowpan.h" @@ -114,11 +117,16 @@ void gnrc_sixlowpan_multiplex_by_size(gnrc_pktsnip_t *pkt, DEBUG("6lo: Dispatch for sending\n"); gnrc_sixlowpan_dispatch_send(pkt, NULL, page); } -#ifdef MODULE_GNRC_SIXLOWPAN_FRAG +#if defined(MODULE_GNRC_SIXLOWPAN_FRAG) || defined(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) else if (orig_datagram_size <= SIXLOWPAN_FRAG_MAX_LEN) { DEBUG("6lo: Send fragmented (%u > %u)\n", (unsigned int)datagram_size, netif->sixlo.max_frag_size); gnrc_sixlowpan_frag_fb_t *fbuf; +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR + bool sfr = gnrc_sixlowpan_frag_sfr_netif(netif); +#else /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ + bool sfr = false; +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ fbuf = gnrc_sixlowpan_frag_fb_get(); if (fbuf == NULL) { @@ -136,9 +144,20 @@ void gnrc_sixlowpan_multiplex_by_size(gnrc_pktsnip_t *pkt, fbuf->hint.fragsz = 0; #endif - gnrc_sixlowpan_frag_send(pkt, fbuf, page); - } + if (!sfr) { +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG + gnrc_sixlowpan_frag_send(pkt, fbuf, page); #endif + } +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR + else { + fbuf->sfr.cur_seq = 0U; + fbuf->sfr.frags_sent = 0U; + gnrc_sixlowpan_frag_sfr_send(pkt, fbuf, page); + } +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ + } +#endif /* defined(MODULE_GNRC_SIXLOWPAN_FRAG) || defined(MODULE_GNRC_SIXLOWPAN_FRAG_SFR) */ else { (void)orig_datagram_size; DEBUG("6lo: packet too big (%u > %u)\n", @@ -216,6 +235,13 @@ static void _receive(gnrc_pktsnip_t *pkt) return; } #endif +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR + else if (sixlowpan_sfr_is((sixlowpan_sfr_t *)dispatch)) { + DEBUG("6lo: received 6LoWPAN recoverable fragment\n"); + gnrc_sixlowpan_frag_sfr_recv(pkt, NULL, 0); + return; + } +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ #ifdef MODULE_GNRC_SIXLOWPAN_IPHC else if (sixlowpan_iphc_is(dispatch)) { DEBUG("6lo: received 6LoWPAN IPHC compressed datagram\n"); @@ -331,6 +357,32 @@ static void _send(gnrc_pktsnip_t *pkt) gnrc_sixlowpan_multiplex_by_size(pkt, datagram_size, netif, 0); } +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_FB +static void _continue_fragmenting(gnrc_sixlowpan_frag_fb_t *fbuf) +{ +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR + if (fbuf->pkt == NULL) { + /* In case the timer fired before the entry was removed */ + return; + } + + gnrc_netif_t *netif = gnrc_netif_hdr_get_netif(fbuf->pkt->data); + assert(netif != NULL); + if (gnrc_sixlowpan_frag_sfr_netif(netif)) { + gnrc_sixlowpan_frag_sfr_send(NULL, fbuf, 0); + return; + } +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG + gnrc_sixlowpan_frag_send(NULL, fbuf, 0); +#else /* MODULE_GNRC_SIXLOWPAN_FRAG */ + (void)fbuf; + DEBUG("6lo: No fragmentation implementation available to sent\n"); + assert(false); +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG */ +} +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_FB */ + static void *_event_loop(void *args) { msg_t msg, reply, msg_q[GNRC_SIXLOWPAN_MSG_QUEUE_SIZE]; @@ -346,6 +398,10 @@ static void *_event_loop(void *args) /* preinitialize ACK */ reply.type = GNRC_NETAPI_MSG_TYPE_ACK; +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR + gnrc_sixlowpan_frag_sfr_init(); +#endif + /* start event loop */ while (1) { DEBUG("6lo: waiting for incoming message.\n"); @@ -371,20 +427,25 @@ static void *_event_loop(void *args) #ifdef MODULE_GNRC_SIXLOWPAN_FRAG_FB case GNRC_SIXLOWPAN_FRAG_FB_SND_MSG: DEBUG("6lo: send fragmented event received\n"); -#ifdef MODULE_GNRC_SIXLOWPAN_FRAG - gnrc_sixlowpan_frag_send(NULL, msg.content.ptr, 0); -#else /* MODULE_GNRC_SIXLOWPAN_FRAG_FB */ - DEBUG("6lo: No fragmentation implementation available to sent\n"); - assert(false); -#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_FB */ + _continue_fragmenting(msg.content.ptr); break; -#endif +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_FB */ #ifdef MODULE_GNRC_SIXLOWPAN_FRAG_RB case GNRC_SIXLOWPAN_FRAG_RB_GC_MSG: DEBUG("6lo: garbage collect reassembly buffer event received\n"); gnrc_sixlowpan_frag_rb_gc(); break; #endif +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR + case GNRC_SIXLOWPAN_FRAG_SFR_ARQ_TIMEOUT_MSG: + DEBUG("6lo sfr: ARQ timeout received\n"); + gnrc_sixlowpan_frag_sfr_arq_timeout(msg.content.ptr); + break; + case GNRC_SIXLOWPAN_FRAG_SFR_INTER_FRAG_GAP_MSG: + DEBUG("6lo sfr: sending next scheduled frame\n"); + gnrc_sixlowpan_frag_sfr_inter_frame_gap(); + break; +#endif default: DEBUG("6lo: operation not supported\n"); diff --git a/sys/net/gnrc/network_layer/sixlowpan/iphc/gnrc_sixlowpan_iphc.c b/sys/net/gnrc/network_layer/sixlowpan/iphc/gnrc_sixlowpan_iphc.c index 159007e58cee..6545217ab5e9 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/iphc/gnrc_sixlowpan_iphc.c +++ b/sys/net/gnrc/network_layer/sixlowpan/iphc/gnrc_sixlowpan_iphc.c @@ -27,6 +27,9 @@ #include "net/gnrc/sixlowpan/ctx.h" #include "net/gnrc/sixlowpan/frag/rb.h" #include "net/gnrc/sixlowpan/frag/minfwd.h" +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR +#include "net/gnrc/sixlowpan/frag/sfr.h" +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ #ifdef MODULE_GNRC_SIXLOWPAN_FRAG_VRB #include "net/gnrc/sixlowpan/frag/vrb.h" #endif /* MODULE_GNRC_SIXLOWPAN_FRAG_VRB */ @@ -116,6 +119,18 @@ static char addr_str[IPV6_ADDR_MAX_STR_LEN]; #endif /* MODULE_GNRC_SIXLOWPAN_FRAG_VRB */ +static inline bool _is_rfrag(gnrc_pktsnip_t *sixlo) +{ +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR + assert((sixlo->next != NULL) && + (sixlo->next->type == GNRC_NETTYPE_SIXLOWPAN)); + return sixlowpan_sfr_rfrag_is(sixlo->next->data); +#else /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ + (void)sixlo; + return false; +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ +} + static inline bool _context_overlaps_iid(gnrc_sixlowpan_ctx_t *ctx, ipv6_addr_t *addr, eui64_t *iid) @@ -562,8 +577,14 @@ static size_t _iphc_nhc_ipv6_decode(gnrc_pktsnip_t *sixlo, size_t offset, /* might be needed to be overwritten by IPv6 reassembly after the IPv6 * packet was reassembled to get complete length */ if (rbuf != NULL) { - payload_len = rbuf->super.datagram_size - *uncomp_hdr_len- - sizeof(ipv6_hdr_t); + if (_is_rfrag(sixlo)) { + payload_len = (rbuf->super.datagram_size + *uncomp_hdr_len) - + (sizeof(ipv6_hdr_t) - offset); + } + else { + payload_len = rbuf->super.datagram_size - *uncomp_hdr_len - + sizeof(ipv6_hdr_t); + } } else { payload_len = (sixlo->size + *uncomp_hdr_len) - @@ -669,7 +690,13 @@ static size_t _iphc_nhc_udp_decode(gnrc_pktsnip_t *sixlo, size_t offset, /* might be needed to be overwritten by IPv6 reassembly after the IPv6 * packet was reassembled to get complete length */ if (rbuf != NULL) { - payload_len = rbuf->super.datagram_size - *uncomp_hdr_len; + if (_is_rfrag(sixlo)) { + payload_len = rbuf->super.datagram_size + sizeof(udp_hdr_t) - + offset; + } + else { + payload_len = rbuf->super.datagram_size - *uncomp_hdr_len; + } } else { payload_len = sixlo->size + sizeof(udp_hdr_t) - offset; @@ -781,7 +808,29 @@ void gnrc_sixlowpan_iphc_recv(gnrc_pktsnip_t *sixlo, void *rbuf_ptr, uint16_t payload_len; if (rbuf != NULL) { /* for a fragmented datagram we know the overall length already */ - payload_len = (uint16_t)(rbuf->super.datagram_size - sizeof(ipv6_hdr_t)); + if (_is_rfrag(sixlo)) { + DEBUG("6lo iphc: calculating payload length for SFR\n"); + DEBUG(" - rbuf->super.datagram_size: %u\n", + rbuf->super.datagram_size); + DEBUG(" - payload_offset: %u\n", (unsigned)payload_offset); + DEBUG(" - uncomp_hdr_len: %u\n", (unsigned)uncomp_hdr_len); + /* set IPv6 header payload length field to the length of whatever is + * left after removing the 6LoWPAN header and adding uncompressed + * headers */ + payload_len = (rbuf->super.datagram_size - payload_offset) + + (uncomp_hdr_len - sizeof(ipv6_hdr_t)); + DEBUG(" => %u\n", payload_len); + /* adapt datagram size for uncompressed datagram */ +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR + /* guard required because SFR-specific field of vrbe is accessed */ + rbuf->offset_diff += (uncomp_hdr_len - payload_offset); + rbuf->super.datagram_size += rbuf->offset_diff; +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_VRB */ + } + else { + /* for a fragmented datagram we know the overall length already */ + payload_len = (uint16_t)(rbuf->super.datagram_size - sizeof(ipv6_hdr_t)); + } #ifdef MODULE_GNRC_SIXLOWPAN_FRAG_VRB DEBUG("6lo iphc: VRB present, trying to create entry for dst %s\n", ipv6_addr_to_str(addr_str, &ipv6_hdr->dst, sizeof(addr_str))); @@ -845,6 +894,16 @@ void gnrc_sixlowpan_iphc_recv(gnrc_pktsnip_t *sixlo, void *rbuf_ptr, ipv6_hdr->hl--; vrbe->super.current_size = rbuf->super.current_size; if ((ipv6 = _encode_frag_for_forwarding(ipv6, vrbe))) { +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR + /* guard required because SFR-specific field of vrbe is + * accessed */ + if (_is_rfrag(sixlo)) { + vrbe->in_netif = iface; + /* calculate offset difference due to compression */ + vrbe->offset_diff = ((int)gnrc_pkt_len(ipv6->next)) - + sixlo->size; + } +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ if ((res = _forward_frag(ipv6, sixlo->next, vrbe, page)) == 0) { DEBUG("6lo iphc: successfully recompressed and forwarded " "1st fragment\n"); @@ -930,6 +989,11 @@ static int _forward_frag(gnrc_pktsnip_t *pkt, gnrc_pktsnip_t *frag_hdr, } /* the following is just debug output for testing without any forwarding * scheme */ +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR + if (sixlowpan_sfr_rfrag_is(frag_hdr->data)) { + return gnrc_sixlowpan_frag_sfr_forward(pkt, frag_hdr->data, vrbe, page); + } +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR */ DEBUG("6lo iphc: Do not know how to forward fragment from (%s, %u) ", gnrc_netif_addr_to_str(vrbe->super.src, vrbe->super.src_len, addr_str), vrbe->super.tag); From ed4ac70887f9e6db570db573f4cb1299bc59f5f0 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Fri, 15 Nov 2019 18:12:58 +0100 Subject: [PATCH 09/10] tests: provide tests for gnrc_sixlowpan_frag_sfr --- tests/gnrc_sixlowpan_frag_sfr/Makefile | 36 + tests/gnrc_sixlowpan_frag_sfr/Makefile.ci | 42 + tests/gnrc_sixlowpan_frag_sfr/app.config | 10 + tests/gnrc_sixlowpan_frag_sfr/common.h | 50 + tests/gnrc_sixlowpan_frag_sfr/main.c | 2836 +++++++++++++++++ tests/gnrc_sixlowpan_frag_sfr/mockup_netif.c | 123 + tests/gnrc_sixlowpan_frag_sfr/tests/01-run.py | 19 + 7 files changed, 3116 insertions(+) create mode 100644 tests/gnrc_sixlowpan_frag_sfr/Makefile create mode 100644 tests/gnrc_sixlowpan_frag_sfr/Makefile.ci create mode 100644 tests/gnrc_sixlowpan_frag_sfr/app.config create mode 100644 tests/gnrc_sixlowpan_frag_sfr/common.h create mode 100644 tests/gnrc_sixlowpan_frag_sfr/main.c create mode 100644 tests/gnrc_sixlowpan_frag_sfr/mockup_netif.c create mode 100755 tests/gnrc_sixlowpan_frag_sfr/tests/01-run.py diff --git a/tests/gnrc_sixlowpan_frag_sfr/Makefile b/tests/gnrc_sixlowpan_frag_sfr/Makefile new file mode 100644 index 000000000000..7302622270de --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr/Makefile @@ -0,0 +1,36 @@ +include ../Makefile.tests_common + +USEMODULE += gnrc_ipv6_router_default +USEMODULE += gnrc_sixlowpan_frag_sfr +USEMODULE += gnrc_sixlowpan_iphc +USEMODULE += gnrc_ipv6_nib +USEMODULE += gnrc_netif +USEMODULE += embunit +USEMODULE += netdev_ieee802154 +USEMODULE += netdev_test + +CFLAGS += -DTEST_SUITES + +include $(RIOTBASE)/Makefile.include + +ifndef CONFIG_GNRC_IPV6_NIB_NO_RTR_SOL + # disable router solicitations so they don't interfere with the tests + CFLAGS += -DCONFIG_GNRC_IPV6_NIB_NO_RTR_SOL=1 +endif +# SFR parameters +ifndef CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE + # fix window size + CFLAGS += -DCONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE=3U +endif +ifndef CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US + # decrease inter frame gap + CFLAGS += -DCONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US=5U +endif +ifndef CONFIG_GNRC_SIXLOWPAN_SFR_MIN_ARQ_TIMEOUT_MS + # decrease minimal ARQ timeout + CFLAGS += -DCONFIG_GNRC_SIXLOWPAN_SFR_MIN_ARQ_TIMEOUT_MS=100U +endif +ifndef CONFIG_GNRC_SIXLOWPAN_SFR_OPT_ARQ_TIMEOUT_MS + # decrease initial ARQ timeout + CFLAGS += -DCONFIG_GNRC_SIXLOWPAN_SFR_OPT_ARQ_TIMEOUT_MS=100U +endif diff --git a/tests/gnrc_sixlowpan_frag_sfr/Makefile.ci b/tests/gnrc_sixlowpan_frag_sfr/Makefile.ci new file mode 100644 index 000000000000..dad20b146896 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr/Makefile.ci @@ -0,0 +1,42 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega1284p \ + atmega328p \ + blackpill \ + bluepill \ + derfmega128 \ + hifive1 \ + hifive1b \ + i-nucleo-lrwan1 \ + im880b \ + mega-xplained \ + microduino-corerf \ + msb-430 \ + msb-430h \ + nucleo-f030r8 \ + nucleo-f031k6 \ + nucleo-f042k6 \ + nucleo-f070rb \ + nucleo-f072rb \ + nucleo-f302r8 \ + nucleo-f303k8 \ + nucleo-f334r8 \ + nucleo-l011k4 \ + nucleo-l031k6 \ + nucleo-l053r8 \ + samd10-xmini \ + saml10-xpro \ + saml11-xpro \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32l0538-disco \ + stm32mp157c-dk2 \ + telosb \ + waspmote-pro \ + z1 \ + # diff --git a/tests/gnrc_sixlowpan_frag_sfr/app.config b/tests/gnrc_sixlowpan_frag_sfr/app.config new file mode 100644 index 000000000000..1075ffab37d5 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr/app.config @@ -0,0 +1,10 @@ +CONFIG_KCONFIG_USEMODULE_GNRC_IPV6_NIB=y +CONFIG_KCONFIG_USEMODULE_GNRC_SIXLOWPAN=y +CONFIG_KCONFIG_USEMODULE_GNRC_SIXLOWPAN_FRAG_SFR=y +# disable router solicitations so they don't interfere with the tests +CONFIG_GNRC_IPV6_NIB_NO_RTR_SOL=y +# preconfigure SFR for tests +CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE=3 +CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US=5 +CONFIG_GNRC_SIXLOWPAN_SFR_MIN_ARQ_TIMEOUT_MS=100 +CONFIG_GNRC_SIXLOWPAN_SFR_OPT_ARQ_TIMEOUT_MS=100 diff --git a/tests/gnrc_sixlowpan_frag_sfr/common.h b/tests/gnrc_sixlowpan_frag_sfr/common.h new file mode 100644 index 000000000000..1c6bd3031bb7 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr/common.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2017 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 tests_gnrc_ipv6_nib Common header for GNRC's NIB tests + * @ingroup tests + * @brief Common definitions for GNRC's NIB tests + * @{ + * + * @file + * + * @author Martine Lenders + */ +#ifndef COMMON_H +#define COMMON_H + +#include + +#include "net/gnrc.h" +#include "net/gnrc/netif.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define _LL0 (0xb8) +#define _LL1 (0x8c) +#define _LL2 (0xcc) +#define _LL3 (0xba) +#define _LL4 (0xef) +#define _LL5 (0x9a) +#define _LL6 (0x67) +#define _LL7 (0x42) + +extern gnrc_netif_t *_mock_netif; + +void _tests_init(void); +void _common_set_up(void); + +#ifdef __cplusplus +} +#endif + +#endif /* COMMON_H */ +/** @} */ diff --git a/tests/gnrc_sixlowpan_frag_sfr/main.c b/tests/gnrc_sixlowpan_frag_sfr/main.c new file mode 100644 index 000000000000..8694fab11dde --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr/main.c @@ -0,0 +1,2836 @@ +/* + * Copyright (C) 2019 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 6LoWPAN minimal forwarding + * + * @author Martine Lenders + * + * @} + */ + +#include +#include + +#include "cib.h" +#include "common.h" +#include "embUnit.h" +#include "embUnit/embUnit.h" +#include "mutex.h" +#include "net/ipv6.h" +#include "net/gnrc.h" +#include "net/gnrc/ipv6/nib.h" +#include "net/gnrc/ipv6/nib/nc.h" +#include "net/gnrc/ipv6/nib/ft.h" +#include "net/gnrc/sixlowpan/frag.h" +#include "net/gnrc/sixlowpan/frag/rb.h" +#include "net/gnrc/sixlowpan/frag/sfr.h" +#include "net/gnrc/sixlowpan/iphc.h" +#include "net/netdev_test.h" +#ifdef MODULE_OD +/* for debugging _target_buf */ +#include "od.h" +#endif +#include "utlist.h" +#include "xtimer.h" + +#define SEND_PACKET_TIMEOUT (500U) + +#define LOC_L2 { _LL0, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 } +#define LOC_LL { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 \ + } +#define LOC_GB { 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, \ + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 \ + } +#define REM_L2 { _LL0, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1 } +#define REM_LL { 0xfe, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, \ + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1 \ + } +#define REM_GB { 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, \ + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1 \ + } + +#define LOC_GB_PFX_LEN (64U) +#define REM_GB_PFX_LEN (64U) +#define TEST_1ST_FRAG_UNCOMP_PAYLOAD_POS (6U) +#define TEST_1ST_FRAG_UNCOMP_IPV6_HDR_POS (7U) +#define TEST_1ST_FRAG_UNCOMP_IPV6_PAYLOAD_SIZE (40U) +#define TEST_1ST_FRAG_UNCOMP_IPV6_PAYLOAD_POS (47U) +#define TEST_1ST_FRAG_UNCOMP_UDP_PAYLOAD_SIZE (32U) +#define TEST_1ST_FRAG_UNCOMP_UDP_PAYLOAD_POS (55U) +#define TEST_1ST_FRAG_COMP_FRAG_SIZE (80U) +#define TEST_1ST_FRAG_COMP_ONLY_IPHC_FRAG_SIZE (48U) +#define TEST_1ST_FRAG_COMP_SIZE (38U) +#define TEST_1ST_FRAG_COMP_PAYLOAD_POS (6U) +#define TEST_1ST_FRAG_COMP_UDP_PAYLOAD_POS (44U) +#define TEST_1ST_FRAG_COMP_PREV_HOP_UDP_PAYLOAD_POS (45U) +#define TEST_NTH_FRAG_SIZE (32U) +#define TEST_NTH_FRAG_PAYLOAD_POS (6U) +#define TEST_FRAG_TAG (0xADU) +#define TEST_SEND_COMP_DATAGRAM_SIZE (193U) +#define TEST_SEND_DATAGRAM_TAG (0x25U) +#define TEST_SEND_FRAG1_PAYLOAD_POS (6U) +#define TEST_SEND_FRAG1_ICMPV6_PAYLOAD_POS (41U) +#define TEST_SEND_FRAG1_PAYLOAD_SIZE (35U) +#define TEST_OFFSET_DIFF (6U) + +static const uint8_t _test_1st_frag_uncomp[] = { + 0xe8, /* RFRAG | no ECN */ + TEST_FRAG_TAG, /* tag: TEST_FRAG_TAG */ + 0x00, 0x51, /* no ACK REQ | sequence: 0 | fragment_size: 81 */ + 0x04, 0xd1, /* compressed datagram size: 1233 */ + 0x41, /* uncompressed IPv6 */ + /* IPv6 header: payload length = 1192, + * next header = UDP (17), hop limit = 65 */ + 0x60, 0x00, 0x00, 0x00, 0x04, 0xa8, 0x11, 0x41, + /* Source: 2001:db8:d6c3:acf:dc71:2b85:82f:75fb */ + 0x20, 0x01, 0x0d, 0xb8, 0xd6, 0xc3, 0x0a, 0xcf, + 0xdc, 0x71, 0x2b, 0x85, 0x08, 0x2f, 0x75, 0xfb, + /* Destination: REM_GB */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1, + /* UDP source: 0xf0b4, UDP destination: 0xf0ba, + * length: 1192, (random) checksum: 0x47b8 */ + 0xf0, 0xb4, 0xf0, 0xba, 0x04, 0xa8, 0x47, 0xb8, + /* (random) payload of length 32 */ + 0xba, 0xb3, 0x6e, 0x4f, 0xd8, 0x23, 0x40, 0xf3, + 0xfb, 0xb9, 0x05, 0xbf, 0xbe, 0x19, 0xf6, 0xa2, + 0xc7, 0x6e, 0x09, 0xf9, 0xba, 0x70, 0x3a, 0x38, + 0xd5, 0x2f, 0x08, 0x85, 0xb8, 0xc1, 0x1a, 0x31, + }; +static const uint8_t _test_1st_frag_comp[] = { + 0xe8, /* RFRAG | no ECN */ + TEST_FRAG_TAG, /* tag: TEST_FRAG_TAG */ + 0x00, 0x46, /* no ACK REQ | sequence: 0 | fragment_size: 70 */ + 0x04, 0xc6, /* compressed datagram size: 1222 */ + /* IPHC: TF: 0b11, NH: 0b1 (NHC), HLIM: 0b10 (64), CID: 0b0, + * Source: uncompressed (SAC: 0b0, SAM: 0b00), + * Destination: uncompressed (M:0, DAC: 0b0, DAM: 0b00) */ + 0x7e, 0x00, + /* (uncompressed) Source: 2001:db8:d6c3:acf:dc71:2b85:82f:75fb */ + 0x20, 0x01, 0x0d, 0xb8, 0xd6, 0xc3, 0x0a, 0xcf, + 0xdc, 0x71, 0x2b, 0x85, 0x08, 0x2f, 0x75, 0xfb, + /* (uncompressed) Destination: REM_GB */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1, + /* NHC UDP: ports: 0b11 (12 bytes elided), (random) Checksum inline */ + 0xf3, 0x4a, 0x47, 0xb8, + /* (random) payload of length 32 */ + 0xba, 0xb3, 0x6e, 0x4f, 0xd8, 0x23, 0x40, 0xf3, + 0xfb, 0xb9, 0x05, 0xbf, 0xbe, 0x19, 0xf6, 0xa2, + 0xc7, 0x6e, 0x09, 0xf9, 0xba, 0x70, 0x3a, 0x38, + 0xd5, 0x2f, 0x08, 0x85, 0xb8, 0xc1, 0x1a, 0x31, + }; +static const uint8_t _test_1st_frag_comp_prev_hop[] = { + 0xe8, /* RFRAG | no ECN */ + TEST_FRAG_TAG, /* tag: TEST_FRAG_TAG */ + 0x00, 0x47, /* no ACK REQ | sequence: 0 | fragment_size: 71 */ + 0x04, 0xc7, /* compressed datagram size: 1223 */ + /* IPHC: TF: 0b11, NH: 0b1 (NHC), HLIM: 0b00 (inline), CID: 0b0, + * Source: uncompressed (SAC: 0b0, SAM: 0b00), + * Destination: uncompressed (M:0, DAC: 0b0, DAM: 0b00) */ + 0x7c, 0x00, + /* Hop Limit: 65 */ + 0x41, + /* (uncompressed) Source: 2001:db8:d6c3:acf:dc71:2b85:82f:75fb */ + 0x20, 0x01, 0x0d, 0xb8, 0xd6, 0xc3, 0x0a, 0xcf, + 0xdc, 0x71, 0x2b, 0x85, 0x08, 0x2f, 0x75, 0xfb, + /* (uncompressed) Destination: REM_GB */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1, + /* NHC UDP: ports: 0b11 (12 bytes elided), (random) Checksum inline */ + 0xf3, 0x4a, 0x47, 0xb8, + /* (random) payload of length 32 */ + 0xba, 0xb3, 0x6e, 0x4f, 0xd8, 0x23, 0x40, 0xf3, + 0xfb, 0xb9, 0x05, 0xbf, 0xbe, 0x19, 0xf6, 0xa2, + 0xc7, 0x6e, 0x09, 0xf9, 0xba, 0x70, 0x3a, 0x38, + 0xd5, 0x2f, 0x08, 0x85, 0xb8, 0xc1, 0x1a, 0x31, + }; +static const uint8_t _test_abort_frag[] = { + 0xe8, /* RFRAG | no ECN */ + TEST_FRAG_TAG, /* tag: TEST_FRAG_TAG */ + 0x00, 0x00, /* no ACK REQ | sequence: 0 | fragment_size: 0 */ + 0x00, 0x00, /* offset: 0 */ + }; +static const uint8_t _test_nth_frag[] = { + 0xe8, /* RFRAG | no ECN */ + TEST_FRAG_TAG, /* tag: TEST_FRAG_TAG */ + 0x06, 0x20, /* no ACK REQ | sequence: 6 | fragment_size: 32 */ + 0x04, 0xb0, /* offset: 1200 */ + /* payload of length 32 */ + 0x54, 0x26, 0x63, 0xab, 0x31, 0x0b, 0xa4, 0x4e, + 0x6e, 0xa9, 0x09, 0x02, 0x15, 0xbb, 0x24, 0xa9, + 0x56, 0x44, 0x4a, 0x84, 0xd1, 0x83, 0xb9, 0xdb, + 0x0e, 0x0d, 0xd6, 0x6a, 0x83, 0x31, 0x1d, 0x94, + }; +static const uint8_t _test_ack[] = { + 0xea, /* RFRAG-ACK | no ECN */ + 0xf1, /* tag: 0xf1 */ + /* randomly set bitmap */ + 0xbb, 0x6d, 0x5d, 0x94 + }; +static const uint8_t _test_send_ipv6[] = { + /* IPv6 header: payload length = 158, + * next header = ICMPv6 (58), hop limit = 64 */ + 0x60, 0x00, 0x00, 0x00, 0x00, 0x9e, 0x3a, 0x40, + /* Source: LOC_GB */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7, + /* Destination: REM_GB */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1, + }; +static const uint8_t _test_send_icmpv6[] = { + /* ICMPv6 Echo request (128), Code 0, (Random) checksum: 0x7269, + * random identifier: 0x59be, random sequence number: 15804 */ + 0x80, 0x00, 0x72, 0x69, 0x59, 0xbe, 0x3d, 0xbc, + /* random payload */ + 0x49, 0x19, 0xe8, 0x0b, 0x25, 0xbb, 0x00, 0x13, + 0x45, 0x85, 0xbd, 0x4a, 0xbb, 0xf1, 0x3d, 0xe3, + 0x36, 0xff, 0x52, 0xea, 0xe8, 0xec, 0xec, 0x82, + 0x94, 0x5f, 0xa4, 0x30, 0x1f, 0x46, 0x28, 0xc7, + 0x41, 0xff, 0x50, 0x84, 0x00, 0x41, 0xc7, 0x8d, + 0xb0, 0xdc, 0x18, 0xff, 0xcd, 0xfa, 0xa7, 0x72, + 0x4b, 0xcf, 0x7c, 0xf7, 0x7c, 0x8b, 0x65, 0x78, + 0xb0, 0xa8, 0xe7, 0x8f, 0xbc, 0x1e, 0xba, 0x4a, + 0x92, 0x13, 0x81, 0x5e, 0x23, 0xd1, 0xde, 0x09, + 0x84, 0x8a, 0xd0, 0xe2, 0xdd, 0x01, 0xc8, 0xd7, + 0x08, 0x4c, 0xd8, 0xc2, 0x21, 0x5c, 0x21, 0xb9, + 0x43, 0xea, 0x52, 0xbd, 0x6a, 0x9a, 0xac, 0x48, + 0x94, 0x98, 0xd1, 0x95, 0x6a, 0x0e, 0x10, 0xf9, + 0x2d, 0xe3, 0x53, 0xe4, 0x84, 0xb0, 0x8a, 0x92, + 0xaa, 0xe0, 0x5a, 0x63, 0x8b, 0x7d, 0x17, 0x51, + 0x22, 0x58, 0xa5, 0x6e, 0x87, 0x18, 0x32, 0x46, + 0x91, 0xd0, 0x59, 0xda, 0xc4, 0x9b, 0xa9, 0xde, + 0x20, 0xf4, 0xc8, 0xc4, 0xef, 0x1d, 0x9e, 0x13, + 0x6c, 0x28, 0x16, 0x59, 0xcc, 0x06 + }; +static const uint8_t _test_send_frag1_prev_hop[] = { + 0xe8, /* RFRAG | no ECN */ + TEST_SEND_DATAGRAM_TAG, /* tag: TEST_SEND_DATAGRAM_TAG */ + 0x00, 0x24, /* no ACK REQ | sequence: 0 | fragment_size: 36 */ + /* compressed datagram size: 144 */ + 0x00, TEST_SEND_COMP_DATAGRAM_SIZE + 1, + /* IPHC: TF: 0b11, NH: 0b0 (inline), HLIM: 0b00 (inline), CID: 0b0, + * Source: uncompressed (SAC: 0b0, SAM: 0b00), + * Destination: uncompressed (M:0, DAC: 0b0, DAM: 0b00) */ + 0x78, 0x00, + /* Next header: ICMPv6 (58), Hop Limit: 65 */ + 0x3a, 0x41, + /* (uncompressed) Source: LOC_GB */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7, + /* (uncompressed) Destination: REM_GB */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1, + }; +static const uint8_t _test_send_frag1[] = { + 0xe8, /* RFRAG | no ECN */ + TEST_SEND_DATAGRAM_TAG, /* tag: TEST_SEND_DATAGRAM_TAG */ + 0x00, 0x5b, /* no ACK REQ | sequence: 0 | fragment_size: 91 */ + /* compressed datagram size: 143 */ + 0x00, TEST_SEND_COMP_DATAGRAM_SIZE, + /* IPHC: TF: 0b11, NH: 0b0 (inline), HLIM: 0b10 (64), CID: 0b0, + * Source: uncompressed (SAC: 0b0, SAM: 0b00), + * Destination: uncompressed (M:0, DAC: 0b0, DAM: 0b00) */ + 0x7a, 0x00, + /* Next header: ICMPv6 (58) */ + 0x3a, + /* (uncompressed) Source: LOC_GB */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7, + /* (uncompressed) Destination: REM_GB */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1, + 0x80, 0x00, 0x72, 0x69, 0x59, 0xbe, 0x3d, 0xbc, + 0x49, 0x19, 0xe8, 0x0b, 0x25, 0xbb, 0x00, 0x13, + 0x45, 0x85, 0xbd, 0x4a, 0xbb, 0xf1, 0x3d, 0xe3, + 0x36, 0xff, 0x52, 0xea, 0xe8, 0xec, 0xec, 0x82, + 0x94, 0x5f, 0xa4, 0x30, 0x1f, 0x46, 0x28, 0xc7, + 0x41, 0xff, 0x50, 0x84, 0x00, 0x41, 0xc7, 0x8d, + 0xb0, 0xdc, 0x18, 0xff, 0xcd, 0xfa, 0xa7, 0x72, + }; +static const uint8_t _test_send_frag2[] = { + 0xe8, /* RFRAG | no ECN */ + TEST_SEND_DATAGRAM_TAG, /* tag: TEST_SEND_DATAGRAM_TAG */ + 0x04, 0x60, /* no ACK REQ | sequence: 1 | fragment_size: 96 */ + 0x00, 0x5b, /* offset: 91 */ + 0x4b, 0xcf, 0x7c, 0xf7, 0x7c, 0x8b, 0x65, 0x78, + 0xb0, 0xa8, 0xe7, 0x8f, 0xbc, 0x1e, 0xba, 0x4a, + 0x92, 0x13, 0x81, 0x5e, 0x23, 0xd1, 0xde, 0x09, + 0x84, 0x8a, 0xd0, 0xe2, 0xdd, 0x01, 0xc8, 0xd7, + 0x08, 0x4c, 0xd8, 0xc2, 0x21, 0x5c, 0x21, 0xb9, + 0x43, 0xea, 0x52, 0xbd, 0x6a, 0x9a, 0xac, 0x48, + 0x94, 0x98, 0xd1, 0x95, 0x6a, 0x0e, 0x10, 0xf9, + 0x2d, 0xe3, 0x53, 0xe4, 0x84, 0xb0, 0x8a, 0x92, + 0xaa, 0xe0, 0x5a, 0x63, 0x8b, 0x7d, 0x17, 0x51, + 0x22, 0x58, 0xa5, 0x6e, 0x87, 0x18, 0x32, 0x46, + 0x91, 0xd0, 0x59, 0xda, 0xc4, 0x9b, 0xa9, 0xde, + 0x20, 0xf4, 0xc8, 0xc4, 0xef, 0x1d, 0x9e, 0x13, + }; +static const uint8_t _test_send_frag3[] = { + 0xe8, /* RFRAG | no ECN */ + TEST_SEND_DATAGRAM_TAG, /* tag: TEST_SEND_DATAGRAM_TAG */ + 0x08, 0x06, /* no ACK REQ | sequence: 2 | fragment_size: 6 */ + 0x00, 0xbb, /* offset: 187 */ + 0x6c, 0x28, 0x16, 0x59, 0xcc, 0x06 + }; +static const uint8_t _loc_l2[] = LOC_L2; +static const ipv6_addr_t _rem_ll = { .u8 = REM_LL }; +static const ipv6_addr_t _rem_gb = { .u8 = REM_GB }; +static const uint8_t _rem_l2[] = REM_L2; +static const gnrc_sixlowpan_frag_rb_base_t _vrbe_base = { + .src = { 0xde, 0x71, 0x2b, 0x85, 0x08, 0x2f, 0x75, 0xfb }, + .src_len = IEEE802154_LONG_ADDRESS_LEN, + .dst = LOC_L2, + .dst_len = sizeof(_loc_l2), + .tag = TEST_FRAG_TAG, + .datagram_size = 1232U, + .current_size = 0U, + }; +static uint8_t _target_buf[128U]; +static uint8_t _target_buf_len; +/* to protect _target_buf and _target_buf_len */ +/* to wait for new data in _target_buf */ +static mutex_t _target_buf_filled = MUTEX_INIT_LOCKED; +static mutex_t _target_buf_barrier = MUTEX_INIT; +uint32_t _last_sent_frame; + +static gnrc_pktsnip_t *_create_recv_frag(const void *frag_data, + size_t frag_size); +static gnrc_pktsnip_t *_create_recv_ack(const void *ack_data, + size_t ack_size); +static int _set_route_and_nce(const ipv6_addr_t *route, unsigned pfx_len); +static int _add_dst(const ipv6_addr_t *dst, unsigned pfx_len); +static gnrc_pktsnip_t *_create_send_datagram(bool compressed, bool payload); +static size_t _wait_for_packet(size_t exp_size); +static inline void _wait_arq_timeout(gnrc_sixlowpan_frag_fb_t *fb); +static void _check_vrbe_values(gnrc_sixlowpan_frag_vrb_t *vrbe, + size_t mhr_len, bool check_offset_diff, + int16_t exp_offset_diff); +static void _check_ack(size_t mhr_len, uint8_t exp_tag, + const uint8_t *exp_bitmap); +static void _check_abort_ack(size_t mhr_len, uint8_t exp_tag); +static void _check_1st_frag_uncomp(size_t mhr_len, uint8_t exp_hl_diff); +static void _check_send_frag1(size_t mhr_len, bool ack_req); +static void _check_send_frag2(size_t mhr_len, bool ack_req); +static void _check_send_frag3(size_t mhr_len, bool ack_req); +static const gnrc_sixlowpan_frag_rb_t *_first_non_empty_rbuf(void); +static int _mock_netdev_send(netdev_t *dev, const iolist_t *iolist); + +static void _set_up(void) +{ + /* reset data-structures */ + _last_sent_frame = xtimer_now_usec() - CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US; + gnrc_sixlowpan_frag_rb_reset(); + gnrc_sixlowpan_frag_vrb_reset(); + gnrc_pktbuf_init(); + memset(_mock_netif->ipv6.addrs, 0, sizeof(_mock_netif->ipv6.addrs)); + memset(_mock_netif->ipv6.addrs_flags, 0, + sizeof(_mock_netif->ipv6.addrs_flags)); + gnrc_ipv6_nib_init(); + gnrc_ipv6_nib_init_iface(_mock_netif); + /* re-init for syncing */ + mutex_init(&_target_buf_filled); + mutex_lock(&_target_buf_filled); + mutex_init(&_target_buf_barrier); +} + +static void _tear_down(void) +{ + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, NULL); + mutex_unlock(&_target_buf_barrier); + /* wait in case mutex in _mock_netdev_send was already entered */ + mutex_lock(&_target_buf_barrier); + memset(_target_buf, 0, sizeof(_target_buf)); + _target_buf_len = 0; + mutex_unlock(&_target_buf_barrier); +} + +static void test_sfr_forward__success__1st_frag_sixlo(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe = gnrc_sixlowpan_frag_vrb_add( + &_vrbe_base, _mock_netif, _rem_l2, sizeof(_rem_l2) + ); + gnrc_pktsnip_t *pkt, *frag; + size_t mhr_len; + + vrbe->in_netif = _mock_netif; + TEST_ASSERT_NOT_NULL((pkt = gnrc_pktbuf_add(NULL, _test_1st_frag_uncomp, + sizeof(_test_1st_frag_uncomp), + GNRC_NETTYPE_SIXLOWPAN))); + /* separate fragment header from payload */ + TEST_ASSERT_NOT_NULL((frag = gnrc_pktbuf_mark(pkt, sizeof(sixlowpan_sfr_rfrag_t), + GNRC_NETTYPE_SIXLOWPAN))); + LL_DELETE(pkt, frag); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT_EQUAL_INT(0, gnrc_sixlowpan_frag_sfr_forward(pkt, + frag->data, + vrbe, + 0)); + gnrc_pktbuf_release(frag); /* delete separated fragment header */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_1st_frag_uncomp)))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + _check_vrbe_values(vrbe, mhr_len, false, 0); + _check_1st_frag_uncomp(mhr_len, 0U); + /* VRB entry should not have been removed */ + TEST_ASSERT_NOT_NULL(gnrc_sixlowpan_frag_vrb_get(_vrbe_base.src, + _vrbe_base.src_len, + _vrbe_base.tag)); +} + +static void test_sfr_forward__success__1st_frag_iphc(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe = gnrc_sixlowpan_frag_vrb_add( + &_vrbe_base, _mock_netif, _rem_l2, sizeof(_rem_l2) + ); + gnrc_pktsnip_t *pkt, *frag; + size_t mhr_len; + + vrbe->in_netif = _mock_netif; + TEST_ASSERT_NOT_NULL((pkt = gnrc_pktbuf_add(NULL, _test_1st_frag_comp, + sizeof(_test_1st_frag_comp), + GNRC_NETTYPE_SIXLOWPAN))); + /* separate fragment header from payload */ + TEST_ASSERT_NOT_NULL((frag = gnrc_pktbuf_mark(pkt, sizeof(sixlowpan_sfr_rfrag_t), + GNRC_NETTYPE_SIXLOWPAN))); + LL_DELETE(pkt, frag); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT_EQUAL_INT(0, gnrc_sixlowpan_frag_sfr_forward(pkt, + frag->data, + vrbe, + 0)); + gnrc_pktbuf_release(frag); /* delete separated fragment header */ + /* first wait and check IPHC part (we put some slack in the first fragment) */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_1st_frag_comp)))); + _check_vrbe_values(vrbe, mhr_len, false, 0); + TEST_ASSERT_MESSAGE( + memcmp(&_test_1st_frag_comp[TEST_1ST_FRAG_COMP_PAYLOAD_POS], + &_target_buf[mhr_len + sizeof(sixlowpan_sfr_rfrag_t)], + sizeof(_test_1st_frag_comp) - sizeof(sixlowpan_sfr_rfrag_t)) == 0, + "unexpected payload" + ); + /* VRB entry should not have been removed */ + TEST_ASSERT_NOT_NULL(gnrc_sixlowpan_frag_vrb_get(_vrbe_base.src, + _vrbe_base.src_len, + _vrbe_base.tag)); +} + +static void test_sfr_forward__success__nth_frag_incomplete(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe = gnrc_sixlowpan_frag_vrb_add( + &_vrbe_base, _mock_netif, _rem_l2, sizeof(_rem_l2) + ); + gnrc_pktsnip_t *pkt, *frag; + size_t mhr_len; + + vrbe->in_netif = _mock_netif; + TEST_ASSERT_NOT_NULL((pkt = gnrc_pktbuf_add(NULL, _test_nth_frag, + sizeof(_test_nth_frag), + GNRC_NETTYPE_SIXLOWPAN))); + /* separate fragment header from payload */ + TEST_ASSERT_NOT_NULL((frag = gnrc_pktbuf_mark(pkt, + sizeof(sixlowpan_sfr_rfrag_t), + GNRC_NETTYPE_SIXLOWPAN))); + LL_DELETE(pkt, frag); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT_EQUAL_INT(0, gnrc_sixlowpan_frag_sfr_forward(pkt, + frag->data, + vrbe, + 0)); + gnrc_pktbuf_release(frag); /* delete separated fragment header */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_nth_frag)))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + _check_vrbe_values(vrbe, mhr_len, false, 0); + TEST_ASSERT_MESSAGE( + memcmp(&_test_nth_frag[TEST_NTH_FRAG_PAYLOAD_POS], + &_target_buf[mhr_len + sizeof(sixlowpan_sfr_rfrag_t)], + TEST_NTH_FRAG_SIZE) == 0, + "unexpected forwarded packet payload" + ); + /* VRB entry should not have been removed */ + TEST_ASSERT_NOT_NULL(gnrc_sixlowpan_frag_vrb_get(_vrbe_base.src, + _vrbe_base.src_len, + _vrbe_base.tag)); +} + +static void test_sfr_forward__success__nth_frag_complete(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe = gnrc_sixlowpan_frag_vrb_add( + &_vrbe_base, _mock_netif, _rem_l2, sizeof(_rem_l2) + ); + gnrc_pktsnip_t *pkt, *frag; + + TEST_ASSERT_NOT_NULL((pkt = gnrc_pktbuf_add(NULL, _test_nth_frag, + sizeof(_test_nth_frag), + GNRC_NETTYPE_SIXLOWPAN))); + /* separate fragment header from payload */ + TEST_ASSERT_NOT_NULL((frag = gnrc_pktbuf_mark(pkt, + sizeof(sixlowpan_sfr_rfrag_t), + GNRC_NETTYPE_SIXLOWPAN))); + LL_DELETE(pkt, frag); + /* simulate current_size only missing the created fragment */ + vrbe->super.current_size = _vrbe_base.datagram_size; + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT_EQUAL_INT(0, gnrc_sixlowpan_frag_sfr_forward(pkt, + frag->data, + vrbe, + 0)); + gnrc_pktbuf_release(frag); /* delete separated fragment header */ + TEST_ASSERT(_wait_for_packet(sizeof(_test_nth_frag))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + /* VRB entry still exists, as with SFR it is removed by ACK or timeout */ + TEST_ASSERT_NOT_NULL(gnrc_sixlowpan_frag_vrb_get(_vrbe_base.src, + _vrbe_base.src_len, + _vrbe_base.tag)); +} + +static void test_sfr_forward__ENOMEM__netif_hdr_build_fail(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe = gnrc_sixlowpan_frag_vrb_add( + &_vrbe_base, _mock_netif, _rem_l2, sizeof(_rem_l2) + ); + gnrc_pktsnip_t *pkt, *frag, *filled_space; + + TEST_ASSERT_NOT_NULL((filled_space = gnrc_pktbuf_add( + NULL, NULL, + /* 115U == 2 * sizeof(gnrc_pktsnip_t) + movement due to mark */ + CONFIG_GNRC_PKTBUF_SIZE - sizeof(_test_nth_frag) - 115U, + GNRC_NETTYPE_UNDEF + ))); + TEST_ASSERT_NOT_NULL((pkt = gnrc_pktbuf_add(NULL, _test_nth_frag, + sizeof(_test_nth_frag), + GNRC_NETTYPE_SIXLOWPAN))); + /* separate fragment header from payload */ + TEST_ASSERT_NOT_NULL((frag = gnrc_pktbuf_mark(pkt, + sizeof(sixlowpan_sfr_rfrag_t), + GNRC_NETTYPE_SIXLOWPAN))); + LL_DELETE(pkt, frag); + + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT_EQUAL_INT(-ENOMEM, gnrc_sixlowpan_frag_sfr_forward(pkt, + frag->data, + vrbe, + 0)); + gnrc_pktbuf_release(frag); /* delete separated fragment header */ + gnrc_pktbuf_release(filled_space); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_uncomp(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_uncomp, + sizeof(_test_1st_frag_uncomp))) + ); + /* configure route to destination of IP header in frag */ + TEST_ASSERT_EQUAL_INT(0, _set_route_and_nce(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_1st_frag_uncomp)))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* but there was a VRB entry created */ + TEST_ASSERT_NOT_NULL((vrbe = gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + ))); + _check_vrbe_values(vrbe, mhr_len, true, 0); + _check_1st_frag_uncomp(mhr_len, 1U); +} + +static void test_sixlo_recv_fwd__1st_frag_uncomp__req_ack(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_uncomp, + sizeof(_test_1st_frag_uncomp))) + ); + /* set ACK Req flag in RFRAG header */ + sixlowpan_sfr_rfrag_set_ack_req(frag->data); + /* configure route to destination of IP header in frag */ + TEST_ASSERT_EQUAL_INT(0, _set_route_and_nce(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_1st_frag_uncomp)))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* but there was a VRB entry created */ + TEST_ASSERT_NOT_NULL((vrbe = gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + ))); + _check_vrbe_values(vrbe, mhr_len, true, 0); + _check_1st_frag_uncomp(mhr_len, 1U); + /* should time out as only the reassembling endpoint is supposed to send + * an abort ACK or if no VRB exists on a forwarding node. See + * - https://tools.ietf.org/html/rfc8931#section-6.1.2 + * - https://tools.ietf.org/html/rfc8931#section-6.3 + */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_uncomp__no_route(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_uncomp, + sizeof(_test_1st_frag_uncomp))) + ); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_uncomp))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_EQUAL_INT(_vrbe_base.datagram_size, rbuf->pkt->size); + gnrc_pktbuf_release(rbuf->pkt); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_uncomp__after_nth_frag(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_nth_frag, sizeof(_test_nth_frag))) + ); + /* configure route to destination of IP header in frag */ + TEST_ASSERT_EQUAL_INT(0, _set_route_and_nce(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + + /* expect abort ACK, see + * https://tools.ietf.org/html/rfc8931#section-6.1.2 */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_nth_frag)))); + /* no reassembly buffer entry */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + _check_abort_ack(mhr_len, _vrbe_base.tag); + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_uncomp, + sizeof(_test_1st_frag_uncomp))) + ); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_1st_frag_uncomp)))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* but there was a VRB entry created */ + TEST_ASSERT_NOT_NULL((vrbe = gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + ))); + _check_vrbe_values(vrbe, mhr_len, true, 0); + _check_1st_frag_uncomp(mhr_len, 1U); +} + +static void test_sixlo_recv_fwd__1st_frag_comp(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp_prev_hop, + sizeof(_test_1st_frag_comp_prev_hop))) + ); + /* configure route to destination of IP header in frag */ + TEST_ASSERT_EQUAL_INT(0, _set_route_and_nce(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* wait for recompressed fragment (hop-limit is now compressed) */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_1st_frag_comp)))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* but there was a VRB entry created */ + TEST_ASSERT_NOT_NULL((vrbe = gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + ))); + _check_vrbe_values(vrbe, mhr_len, true, + (int16_t)sizeof(_test_1st_frag_comp) - + (int16_t)sizeof(_test_1st_frag_comp_prev_hop)); + TEST_ASSERT_EQUAL_INT(TEST_1ST_FRAG_COMP_FRAG_SIZE, + vrbe->super.current_size); + /* frag_size changed to the size of the expected fragment */ + TEST_ASSERT_EQUAL_INT( + sixlowpan_sfr_rfrag_get_frag_size( + (sixlowpan_sfr_rfrag_t *)_test_1st_frag_comp + ), + sixlowpan_sfr_rfrag_get_frag_size( + (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len] + ) + ); + TEST_ASSERT_MESSAGE( + memcmp(&_test_1st_frag_comp[TEST_1ST_FRAG_COMP_PAYLOAD_POS], + &_target_buf[mhr_len + sizeof(sixlowpan_sfr_rfrag_t)], + TEST_1ST_FRAG_COMP_SIZE) == 0, + "unexpected IPHC header" + ); + TEST_ASSERT_MESSAGE( + memcmp(&_test_1st_frag_comp_prev_hop[TEST_1ST_FRAG_COMP_PREV_HOP_UDP_PAYLOAD_POS], + &_target_buf[mhr_len + TEST_1ST_FRAG_COMP_UDP_PAYLOAD_POS], + TEST_1ST_FRAG_UNCOMP_UDP_PAYLOAD_SIZE) == 0, + "unexpected forwarded packet payload" + ); +} + +/* a regression test to check for the bugs fixed in + * https://github.com/RIOT-OS/RIOT/pull/12848 */ +static void test_sixlo_recv_fwd__1st_frag_comp__resend(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp_prev_hop, + sizeof(_test_1st_frag_comp_prev_hop))) + ); + /* configure route to destination of IP header in frag */ + TEST_ASSERT_EQUAL_INT(0, _set_route_and_nce(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* wait for recompressed fragment (hop-limit is now compressed) */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_1st_frag_comp)))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* but there was a VRB entry created */ + TEST_ASSERT_NOT_NULL(gnrc_sixlowpan_frag_vrb_get(_vrbe_base.src, + _vrbe_base.src_len, + _vrbe_base.tag)); + /* receive fragment for a second time (e.g. resent) */ + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp_prev_hop, + sizeof(_test_1st_frag_comp_prev_hop))) + ); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* wait for recompressed fragment (hop-limit is now compressed) */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_1st_frag_comp)))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + gnrc_pktbuf_stats(); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB entry still exists */ + TEST_ASSERT_NOT_NULL((vrbe = gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + ))); + /* and if removed ... */ + gnrc_sixlowpan_frag_vrb_rm(vrbe); + /* the fragment interval pool is empty again */ + TEST_ASSERT(gnrc_sixlowpan_frag_rb_ints_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_comp__only_iphc(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp_prev_hop, + TEST_1ST_FRAG_COMP_PREV_HOP_UDP_PAYLOAD_POS)) + ); + sixlowpan_sfr_rfrag_set_frag_size(frag->data, + TEST_1ST_FRAG_COMP_PREV_HOP_UDP_PAYLOAD_POS - + sizeof(sixlowpan_sfr_rfrag_t)); + /* configure route to destination of IP header in frag */ + TEST_ASSERT_EQUAL_INT(0, _set_route_and_nce(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* wait for recompressed fragment (hop-limit is now compressed) */ + TEST_ASSERT((mhr_len = _wait_for_packet(TEST_1ST_FRAG_COMP_UDP_PAYLOAD_POS))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* but there was a VRB entry created */ + TEST_ASSERT_NOT_NULL((vrbe = gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + ))); + _check_vrbe_values(vrbe, mhr_len, true, + (int16_t)TEST_1ST_FRAG_COMP_UDP_PAYLOAD_POS - + (int16_t)TEST_1ST_FRAG_COMP_PREV_HOP_UDP_PAYLOAD_POS); + TEST_ASSERT_EQUAL_INT(TEST_1ST_FRAG_COMP_ONLY_IPHC_FRAG_SIZE, + vrbe->super.current_size); + TEST_ASSERT_MESSAGE( + memcmp(&_test_1st_frag_comp[TEST_1ST_FRAG_COMP_PAYLOAD_POS], + &_target_buf[mhr_len + sizeof(sixlowpan_sfr_rfrag_t)], + TEST_1ST_FRAG_COMP_SIZE) == 0, + "unexpected IPHC header" + ); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_comp__only_iphc_no_nhc(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_send_frag1_prev_hop, + sizeof(_test_send_frag1_prev_hop))) + ); + /* configure route to destination of IP header in frag */ + TEST_ASSERT_EQUAL_INT(0, _set_route_and_nce(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* wait for recompressed fragment (hop-limit is now compressed) */ + TEST_ASSERT((mhr_len = _wait_for_packet(TEST_SEND_FRAG1_ICMPV6_PAYLOAD_POS))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* but there was a VRB entry created */ + TEST_ASSERT_NOT_NULL((vrbe = gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, TEST_SEND_DATAGRAM_TAG + ))); + _check_vrbe_values(vrbe, mhr_len, true, + (int16_t)TEST_SEND_FRAG1_ICMPV6_PAYLOAD_POS - + (int16_t)sizeof(_test_send_frag1_prev_hop)); + TEST_ASSERT_MESSAGE( + memcmp(&_test_send_frag1[TEST_SEND_FRAG1_PAYLOAD_POS], + &_target_buf[mhr_len + sizeof(sixlowpan_sfr_rfrag_t)], + TEST_SEND_FRAG1_PAYLOAD_SIZE) == 0, + "unexpected IPHC header" + ); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_comp__no_route(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp_prev_hop, + sizeof(_test_1st_frag_comp_prev_hop))) + ); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_EQUAL_INT(_vrbe_base.datagram_size, rbuf->pkt->size); + gnrc_pktbuf_release(rbuf->pkt); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_comp__no_route_only_iphc(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp_prev_hop, + TEST_1ST_FRAG_COMP_PREV_HOP_UDP_PAYLOAD_POS)) + ); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_EQUAL_INT(_vrbe_base.datagram_size, rbuf->pkt->size); + gnrc_pktbuf_release(rbuf->pkt); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_comp__no_refrag(void) +{ + gnrc_sixlowpan_frag_fb_t *reserved[CONFIG_GNRC_SIXLOWPAN_FRAG_FB_SIZE]; + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp_prev_hop, + sizeof(_test_1st_frag_comp_prev_hop))) + ); + /* consume all available gnrc_sixlowpan_frag_fb_t instances so creating + * a fragment with extra slack is not possible */ + for (unsigned i = 0; i < CONFIG_GNRC_SIXLOWPAN_FRAG_FB_SIZE; i++) { + reserved[i] = gnrc_sixlowpan_frag_fb_get(); + reserved[i]->pkt = frag; + } + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_EQUAL_INT(_vrbe_base.datagram_size, rbuf->pkt->size); + gnrc_pktbuf_release(rbuf->pkt); + /* release all gnrc_sixlowpan_frag_fb_t instances again */ + for (unsigned i = 0; i < CONFIG_GNRC_SIXLOWPAN_FRAG_FB_SIZE; i++) { + reserved[i]->pkt = NULL; + } + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + /* This is normal reassembly so the rest should have be tested in the + * test for normal reassembly ;-) */ +} + +static void test_sixlo_recv_fwd__1st_frag_comp__after_nth_frag(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_nth_frag, + sizeof(_test_nth_frag))) + ); + /* configure route to destination of IP header in frag */ + TEST_ASSERT_EQUAL_INT(0, _set_route_and_nce(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + + /* expect abort ACK, see + * https://tools.ietf.org/html/rfc8931#section-6.1.2 */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_nth_frag)))); + /* no reassembly buffer entry */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + _check_abort_ack(mhr_len, _vrbe_base.tag); + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp_prev_hop, + sizeof(_test_1st_frag_comp_prev_hop))) + ); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* wait for recompressed fragment (hop-limit is now compressed) */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_1st_frag_comp)))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* but there was a VRB entry created */ + TEST_ASSERT_NOT_NULL((vrbe = gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + ))); + _check_vrbe_values(vrbe, mhr_len, true, + (int16_t)sizeof(_test_1st_frag_comp) - + (int16_t)sizeof(_test_1st_frag_comp_prev_hop)); + TEST_ASSERT_EQUAL_INT(TEST_1ST_FRAG_COMP_FRAG_SIZE, + vrbe->super.current_size); + TEST_ASSERT_MESSAGE( + memcmp(&_test_1st_frag_comp[TEST_1ST_FRAG_COMP_PAYLOAD_POS], + &_target_buf[mhr_len + sizeof(sixlowpan_sfr_rfrag_t)], + TEST_1ST_FRAG_COMP_SIZE) == 0, + "unexpected IPHC header" + ); + TEST_ASSERT_MESSAGE( + memcmp(&_test_1st_frag_comp_prev_hop[TEST_1ST_FRAG_COMP_PREV_HOP_UDP_PAYLOAD_POS], + &_target_buf[mhr_len + TEST_1ST_FRAG_COMP_UDP_PAYLOAD_POS], + TEST_1ST_FRAG_UNCOMP_UDP_PAYLOAD_SIZE) == 0, + "unexpected forwarded packet payload" + ); +} + +static void test_sixlo_recv_fwd__1st_frag_abort(void) +{ + uint8_t target_buf_dst[IEEE802154_LONG_ADDRESS_LEN]; + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + sixlowpan_sfr_rfrag_t *rfrag_hdr; + size_t mhr_len; + le_uint16_t tmp; + uint8_t exp_tag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_abort_frag, + sizeof(_test_abort_frag))) + ); + TEST_ASSERT_NOT_NULL( + (vrbe = gnrc_sixlowpan_frag_vrb_add(&_vrbe_base, _mock_netif, + _rem_l2, sizeof(_rem_l2))) + ); + vrbe->in_netif = _mock_netif; + exp_tag = vrbe->out_tag & 0xff; + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* should be forwarded */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_abort_frag)))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB entry was deleted */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_EQUAL_INT(sizeof(_rem_l2), ieee802154_get_dst(_target_buf, + target_buf_dst, + &tmp)); + TEST_ASSERT_MESSAGE(memcmp(_rem_l2, target_buf_dst, sizeof(_rem_l2)) == 0, + "Unexpected destination address"); + rfrag_hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT(sixlowpan_sfr_rfrag_is(&rfrag_hdr->base)); + TEST_ASSERT_EQUAL_INT(exp_tag, rfrag_hdr->base.tag); + TEST_ASSERT(!sixlowpan_sfr_rfrag_ack_req(rfrag_hdr)); + TEST_ASSERT_EQUAL_INT(0, sixlowpan_sfr_rfrag_get_seq(rfrag_hdr)); + TEST_ASSERT_EQUAL_INT(0, sixlowpan_sfr_rfrag_get_frag_size(rfrag_hdr)); + TEST_ASSERT_EQUAL_INT(0, sixlowpan_sfr_rfrag_get_offset(rfrag_hdr)); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_nalp(void) +{ + gnrc_pktsnip_t *frag; + uint8_t *data; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp, + sizeof(sixlowpan_sfr_rfrag_t) + 1)) + ); + data = frag->data; + /* mark dispatch after RFRAG header a non-6LoWPAN frame */ + data[sizeof(sixlowpan_sfr_rfrag_t)] &= ~(0xc0); + /* configure route to destination of IP header in frag */ + TEST_ASSERT_EQUAL_INT(0, _set_route_and_nce(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB entry was deleted */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_nalp__req_ack(void) +{ + gnrc_pktsnip_t *frag; + uint8_t *data; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp, + sizeof(sixlowpan_sfr_rfrag_t) + 1)) + ); + data = frag->data; + /* mark dispatch after RFRAG header a non-6LoWPAN frame */ + data[sizeof(sixlowpan_sfr_rfrag_t)] &= ~(0xc0); + /* set ACK request flag */ + sixlowpan_sfr_rfrag_set_ack_req(frag->data); + /* configure route to destination of IP header in frag */ + TEST_ASSERT_EQUAL_INT(0, _set_route_and_nce(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* should time out as only the reassembling endpoint is supposed to send + * an abort ACK or if no VRB exists on a forwarding node. See + * - https://tools.ietf.org/html/rfc8931#section-6.1.2 + * - https://tools.ietf.org/html/rfc8931#section-6.3 + */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB entry was deleted */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__1st_frag_nalp__no_vrb(void) +{ + gnrc_pktsnip_t *frag; + uint8_t *data; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp, + sizeof(sixlowpan_sfr_rfrag_t) + 1)) + ); + data = frag->data; + /* mark dispatch after RFRAG header a non-6LoWPAN frame */ + data[sizeof(sixlowpan_sfr_rfrag_t)] &= ~(0xc0); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB remains entry */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__nth_frag(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_nth_frag, sizeof(_test_nth_frag))) + ); + TEST_ASSERT_NOT_NULL( + (vrbe = gnrc_sixlowpan_frag_vrb_add(&_vrbe_base, _mock_netif, + _rem_l2, sizeof(_rem_l2))) + ); + vrbe->in_netif = _mock_netif; + /* set offset_diff to test it in the outgoing RFRAG */ + vrbe->offset_diff = TEST_OFFSET_DIFF; + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_nth_frag)))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB entry still exist */ + TEST_ASSERT_NOT_NULL((vrbe = gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + ))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + _check_vrbe_values(vrbe, mhr_len, false, 0); + /* offset changed according to offset_diff */ + TEST_ASSERT_EQUAL_INT( + sixlowpan_sfr_rfrag_get_offset((sixlowpan_sfr_rfrag_t *)&_test_nth_frag) + + TEST_OFFSET_DIFF, + sixlowpan_sfr_rfrag_get_offset((sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]) + ); + TEST_ASSERT_MESSAGE( + memcmp(&_test_nth_frag[TEST_NTH_FRAG_PAYLOAD_POS], + &_target_buf[mhr_len + sizeof(sixlowpan_sfr_rfrag_t)], + TEST_NTH_FRAG_SIZE) == 0, + "unexpected forwarded packet payload" + ); +} + +static void test_sixlo_recv_fwd__nth_frag__no_vrbe(void) +{ + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_nth_frag, sizeof(_test_nth_frag))) + ); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* expect abort ACK, see + * https://tools.ietf.org/html/rfc8931#section-6.1.2 */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_nth_frag)))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB too */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + _check_abort_ack(mhr_len, _vrbe_base.tag); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + +} + +static void test_sixlo_recv_fwd__nth_frag__duplicate(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_nth_frag, sizeof(_test_nth_frag))) + ); + TEST_ASSERT_NOT_NULL( + (vrbe = gnrc_sixlowpan_frag_vrb_add(&_vrbe_base, _mock_netif, + _rem_l2, sizeof(_rem_l2))) + ); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + TEST_ASSERT(_wait_for_packet(sizeof(_test_nth_frag))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* VRB entry should not have been removed */ + TEST_ASSERT_NOT_NULL(gnrc_sixlowpan_frag_vrb_get(_vrbe_base.src, + _vrbe_base.src_len, + _vrbe_base.tag)); + + /* generate and receive duplicate */ + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_nth_frag, sizeof(_test_nth_frag))) + ); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* with SFR the forwarder does not care about duplicates and just forwards + * them as well */ + _target_buf_len = 0; + TEST_ASSERT(_wait_for_packet(sizeof(_test_nth_frag))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* VRB entry should not have been removed */ + TEST_ASSERT_NOT_NULL(gnrc_sixlowpan_frag_vrb_get(_vrbe_base.src, + _vrbe_base.src_len, + _vrbe_base.tag)); +} + +static void test_sixlo_recv_fwd__nth_frag__overlap(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *frag; + sixlowpan_sfr_rfrag_t *frag_hdr; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_nth_frag, sizeof(_test_nth_frag))) + ); + TEST_ASSERT_NOT_NULL( + (vrbe = gnrc_sixlowpan_frag_vrb_add(&_vrbe_base, _mock_netif, + _rem_l2, sizeof(_rem_l2))) + ); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + TEST_ASSERT(_wait_for_packet(sizeof(_test_nth_frag))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* rest was already tested */ + + /* generate and receive overlapping fragment */ + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_nth_frag, sizeof(_test_nth_frag))) + ); + frag_hdr = frag->data; + /* move offset to simulate overlap*/ + sixlowpan_sfr_rfrag_set_offset(frag_hdr, + sixlowpan_sfr_rfrag_get_offset(frag_hdr) - 1); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + _target_buf_len = 0; + /* should time out */ + TEST_ASSERT(_wait_for_packet(sizeof(_test_nth_frag))); + /* with SFR the forwarder does not care about overlapping fragments and just + * forwards them as well */ + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* VRB entry should not have been removed */ + TEST_ASSERT_NOT_NULL(gnrc_sixlowpan_frag_vrb_get(_vrbe_base.src, + _vrbe_base.src_len, + _vrbe_base.tag)); + /* rest was already tested */ +} + +static void test_sixlo_recv_fwd__frag__too_short(void) +{ + gnrc_pktsnip_t *frag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp, + sizeof(sixlowpan_sfr_rfrag_t) - 1)) + ); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* should be forwarded */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_abort_frag))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__ack(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *ack; + sixlowpan_sfr_ack_t *ack_hdr; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (ack = _create_recv_ack(_test_ack, sizeof(_test_ack))) + ); + TEST_ASSERT_NOT_NULL( + (vrbe = gnrc_sixlowpan_frag_vrb_add(&_vrbe_base, _mock_netif, + _rem_l2, sizeof(_rem_l2))) + ); + vrbe->in_netif = _mock_netif; + ack_hdr = ack->data; + ack_hdr->base.tag = vrbe->out_tag; + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + ack)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_ack)))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB entry still exist */ + TEST_ASSERT_NOT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + _check_ack(mhr_len, _vrbe_base.tag, &_test_ack[2]); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__NULL_ack(void) +{ + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *ack; + sixlowpan_sfr_ack_t *ack_hdr; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (ack = _create_recv_ack(_test_ack, sizeof(_test_ack))) + ); + TEST_ASSERT_NOT_NULL( + (vrbe = gnrc_sixlowpan_frag_vrb_add(&_vrbe_base, _mock_netif, + _rem_l2, sizeof(_rem_l2))) + ); + vrbe->in_netif = _mock_netif; + ack_hdr = ack->data; + ack_hdr->base.tag = vrbe->out_tag; + /* set ACK bitmap to NULL */ + memset(ack_hdr->bitmap, 0, sizeof(ack_hdr->bitmap)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + ack)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_ack)))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB entry was removed */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + /* NULL bitmap == abort ACK */ + _check_abort_ack(mhr_len, _vrbe_base.tag); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__FULL_ack(void) +{ + const uint8_t full_bitmap[] = { 0xff, 0xff, 0xff, 0xff }; + gnrc_sixlowpan_frag_vrb_t *vrbe; + gnrc_pktsnip_t *ack; + sixlowpan_sfr_ack_t *ack_hdr; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (ack = _create_recv_ack(_test_ack, sizeof(_test_ack))) + ); + TEST_ASSERT_NOT_NULL( + (vrbe = gnrc_sixlowpan_frag_vrb_add(&_vrbe_base, _mock_netif, + _rem_l2, sizeof(_rem_l2))) + ); + vrbe->in_netif = _mock_netif; + ack_hdr = ack->data; + ack_hdr->base.tag = vrbe->out_tag; + /* set ACK bitmap to NULL */ + memcpy(ack_hdr->bitmap, full_bitmap, sizeof(ack_hdr->bitmap)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + ack)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_ack)))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB entry was removed */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + _check_ack(mhr_len, _vrbe_base.tag, full_bitmap); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__ack__no_vrbe(void) +{ + gnrc_pktsnip_t *ack; + + TEST_ASSERT_NOT_NULL( + (ack = _create_recv_ack(_test_ack, sizeof(_test_ack))) + ); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + ack)); + /* + * https://tools.ietf.org/html/rfc8931#section-6.2 + * > If the Reverse LSP is not found, the router MUST silently drop the + * > RFRAG-ACK message. + */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_ack))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_fwd__ack__too_short(void) +{ + gnrc_pktsnip_t *frag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_ack, + sizeof(sixlowpan_sfr_ack_t) - 1)) + ); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* should be forwarded */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_abort_frag))); + /* reassembly buffer remains empty */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_ep__1st_frag_uncomp(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_uncomp, + sizeof(_test_1st_frag_uncomp))) + ); + /* configure interface to be endpoint of route */ + TEST_ASSERT_EQUAL_INT(sizeof(ipv6_addr_t), + _add_dst(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* shouldn't trigger any sending */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_uncomp))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_EQUAL_INT(_vrbe_base.datagram_size, rbuf->pkt->size); + /* check if IPv6 packet is started in reassembly buffer */ + TEST_ASSERT_EQUAL_INT(0x60, ((uint8_t *)rbuf->pkt->data)[0] & 0xf0); + /* check `received` bitmap (used for ACKs) */ + TEST_ASSERT_EQUAL_INT(0x80, rbuf->received[0]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[1]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[2]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[3]); + gnrc_pktbuf_release(rbuf->pkt); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_ep__1st_frag_uncomp__req_ack(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + size_t mhr_len; + BITFIELD(exp_bitmap, 32U) = { 0 }; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_uncomp, + sizeof(_test_1st_frag_uncomp))) + ); + /* set ACK Req flag in RFRAG header */ + sixlowpan_sfr_rfrag_set_ack_req(frag->data); + /* configure interface to be endpoint of route */ + TEST_ASSERT_EQUAL_INT(sizeof(ipv6_addr_t), + _add_dst(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* expect RFRAG-ACK */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(sixlowpan_sfr_ack_t)))); + /* only sequence number 0 (first fragment) should be ACK'd */ + bf_set(exp_bitmap, 0); + _check_ack(mhr_len, _vrbe_base.tag, exp_bitmap); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_EQUAL_INT(_vrbe_base.datagram_size, rbuf->pkt->size); + /* check if IPv6 packet is started in reassembly buffer */ + TEST_ASSERT_EQUAL_INT(0x60, ((uint8_t *)rbuf->pkt->data)[0] & 0xf0); + /* check `received` bitmap (used for ACKs) */ + TEST_ASSERT_EQUAL_INT(0x80, rbuf->received[0]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[1]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[2]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[3]); + gnrc_pktbuf_release(rbuf->pkt); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_ep__1st_frag_uncomp__after_nth_frag(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_nth_frag, sizeof(_test_nth_frag))) + ); + /* configure interface to be endpoint of route */ + TEST_ASSERT_EQUAL_INT(sizeof(ipv6_addr_t), + _add_dst(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + + /* expect abort ACK, see + * https://tools.ietf.org/html/rfc8931#section-6.1.2 */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_nth_frag)))); + /* no reassembly buffer entry */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + _check_abort_ack(mhr_len, _vrbe_base.tag); + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_uncomp, + sizeof(_test_1st_frag_uncomp))) + ); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* shouldn't trigger any sending */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_uncomp))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_EQUAL_INT(_vrbe_base.datagram_size, rbuf->pkt->size); + /* check if IPv6 packet is started in reassembly buffer */ + TEST_ASSERT_EQUAL_INT(0x60, ((uint8_t *)rbuf->pkt->data)[0] & 0xf0); + /* check `received` bitmap (used for ACKs) */ + TEST_ASSERT_EQUAL_INT(0x80, rbuf->received[0]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[1]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[2]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[3]); + gnrc_pktbuf_release(rbuf->pkt); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_ep__1st_frag_comp(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp, + sizeof(_test_1st_frag_comp))) + ); + /* configure interface to be endpoint of route */ + TEST_ASSERT_EQUAL_INT(sizeof(ipv6_addr_t), + _add_dst(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* shouldn't trigger any sending */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_EQUAL_INT(_vrbe_base.datagram_size, rbuf->pkt->size); + /* check if IPv6 packet is started in reassembly buffer */ + TEST_ASSERT_EQUAL_INT(0x60, ((uint8_t *)rbuf->pkt->data)[0] & 0xf0); + /* check `received` bitmap (used for ACKs) */ + TEST_ASSERT_EQUAL_INT(0x80, rbuf->received[0]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[1]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[2]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[3]); + gnrc_pktbuf_release(rbuf->pkt); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_ep__1st_frag_comp__after_nth_frag(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_nth_frag, sizeof(_test_nth_frag))) + ); + /* configure interface to be endpoint of route */ + TEST_ASSERT_EQUAL_INT(sizeof(ipv6_addr_t), + _add_dst(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + + /* expect abort ACK, see + * https://tools.ietf.org/html/rfc8931#section-6.1.2 */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_nth_frag)))); + /* no reassembly buffer entry */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + _check_abort_ack(mhr_len, _vrbe_base.tag); + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp, + sizeof(_test_1st_frag_comp))) + ); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* shouldn't trigger any sending */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_EQUAL_INT(_vrbe_base.datagram_size, rbuf->pkt->size); + /* check if IPv6 packet is started in reassembly buffer */ + TEST_ASSERT_EQUAL_INT(0x60, ((uint8_t *)rbuf->pkt->data)[0] & 0xf0); + /* check `received` bitmap (used for ACKs) */ + TEST_ASSERT_EQUAL_INT(0x80, rbuf->received[0]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[1]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[2]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[3]); + gnrc_pktbuf_release(rbuf->pkt); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_ep__1st_frag_abort(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp, + sizeof(_test_1st_frag_comp))) + ); + /* configure interface to be endpoint of route */ + TEST_ASSERT_EQUAL_INT(sizeof(ipv6_addr_t), + _add_dst(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* shouldn't trigger any sending */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_abort_frag, + sizeof(_test_abort_frag))) + ); + /* expect abort ACK, see + * https://tools.ietf.org/html/rfc8931#section-6.3 */ + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + _target_buf_len = 0; + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(sixlowpan_sfr_ack_t))); + /* reassembly buffer entry was deleted */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_ep__1st_frag_abort__req_ack(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_pktsnip_t *frag; + size_t mhr_len; + + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_1st_frag_comp, + sizeof(_test_1st_frag_comp))) + ); + /* configure interface to be endpoint of route */ + TEST_ASSERT_EQUAL_INT(sizeof(ipv6_addr_t), + _add_dst(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* shouldn't trigger any sending */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_1st_frag_comp))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_abort_frag, + sizeof(_test_abort_frag))) + ); + /* set ACK request flag */ + sixlowpan_sfr_rfrag_set_ack_req(frag->data); + /* expect abort ACK, see + * https://tools.ietf.org/html/rfc8931#section-6.3 */ + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(sixlowpan_sfr_ack_t)))); + /* reassembly buffer entry was deleted */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, _vrbe_base.tag + )); + _check_abort_ack(mhr_len, _vrbe_base.tag); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_recv_ep__complete_datagram(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf; + gnrc_netreg_entry_t netreg = GNRC_NETREG_ENTRY_INIT_PID( + GNRC_NETREG_DEMUX_CTX_ALL, + thread_getpid() + ); + gnrc_pktsnip_t *frag; + size_t mhr_len; + BITFIELD(exp_bitmap, 32U) = { 0 }; + uint8_t exp_seq; + + /* configure interface to be endpoint of route */ + TEST_ASSERT_EQUAL_INT(sizeof(ipv6_addr_t), + _add_dst(&_rem_gb, REM_GB_PFX_LEN)); + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + /* ==== FIRST FRAGMENT ==== */ + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_send_frag1, + sizeof(_test_send_frag1))) + ); + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* shouldn't trigger any sending */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag1))); + /* normal reassembly should have started */ + /* reassembly buffer entry should have been created */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* and VRB remains empty */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_vrb_get( + _vrbe_base.src, _vrbe_base.src_len, TEST_SEND_DATAGRAM_TAG + )); + TEST_ASSERT_EQUAL_INT(sizeof(_test_send_ipv6) + sizeof(_test_send_icmpv6), + rbuf->pkt->size); + /* check if IPv6 packet is started in reassembly buffer */ + TEST_ASSERT_EQUAL_INT(0x60, ((uint8_t *)rbuf->pkt->data)[0] & 0xf0); + /* check `received` bitmap (used for ACKs) */ + TEST_ASSERT_EQUAL_INT(0x80, rbuf->received[0]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[1]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[2]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[3]); + + /* ==== SECOND FRAGMENT ==== */ + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_send_frag2, + sizeof(_test_send_frag2))) + ); + /* set ACK Req flag in RFRAG header */ + sixlowpan_sfr_rfrag_set_ack_req(frag->data); + exp_seq = sixlowpan_sfr_rfrag_get_seq(frag->data); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* expect RFRAG-ACK */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag2)))); + bf_set(exp_bitmap, 0); /* first fragment was received */ + bf_set(exp_bitmap, exp_seq); /* and second fragment */ + _check_ack(mhr_len, TEST_SEND_DATAGRAM_TAG, exp_bitmap); + /* reassembly buffer entry should still exist */ + TEST_ASSERT_NOT_NULL((rbuf = _first_non_empty_rbuf())); + /* check `received` bitmap (used for ACKs) */ + TEST_ASSERT_EQUAL_INT(0xc0, rbuf->received[0]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[1]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[2]); + TEST_ASSERT_EQUAL_INT(0x00, rbuf->received[3]); + + gnrc_netreg_register(GNRC_NETTYPE_IPV6, &netreg); + /* ==== THIRD FRAGMENT ==== */ + TEST_ASSERT_NOT_NULL( + (frag = _create_recv_frag(_test_send_frag3, + sizeof(_test_send_frag3))) + ); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + frag)); + /* expect RFRAG-ACK */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag3)))); + /* reassembly buffer should have been removed */ + TEST_ASSERT_NULL(_first_non_empty_rbuf()); + /* expect FULL bitmap, as the datagram is now complete */ + memset(exp_bitmap, 0xff, sizeof(exp_bitmap)); + _check_ack(mhr_len, TEST_SEND_DATAGRAM_TAG, exp_bitmap); + + /* retry 5 times to receive then give up (IPv6 thread might send NDP + * messages to itself) */ + for (unsigned i = 0; i < 5U; i++) { + msg_t msg; + + if (xtimer_msg_receive_timeout(&msg, 1000) < 0) { + continue; + } + if (msg.type == GNRC_NETAPI_MSG_TYPE_RCV) { + gnrc_pktsnip_t *pkt; + uint8_t *data; + + gnrc_netreg_unregister(GNRC_NETTYPE_IPV6, &netreg); + pkt = msg.content.ptr; + data = pkt->data; + TEST_ASSERT_EQUAL_INT(sizeof(_test_send_ipv6) + + sizeof(_test_send_icmpv6), pkt->size); + TEST_ASSERT_MESSAGE( + memcmp(data, _test_send_ipv6, sizeof(_test_send_ipv6)) == 0, + "Unexpected IPv6 header in reassembly" + ); + TEST_ASSERT_MESSAGE( + memcmp(&data[sizeof(_test_send_ipv6)], _test_send_icmpv6, + sizeof(_test_send_icmpv6)) == 0, + "Unexpected ICMPv6 packet in reassembly" + ); + gnrc_pktbuf_release(pkt); + break; + } + else if (msg.type == GNRC_NETAPI_MSG_TYPE_SND) { + /* release packet sent by network stack */ + gnrc_pktbuf_release(msg.content.ptr); + } + } + xtimer_usleep(1000); /* give GNRC time to clean up */ + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_send(void) +{ + gnrc_sixlowpan_frag_fb_t *fbuf; + gnrc_pktsnip_t *pkt; + sixlowpan_sfr_rfrag_t *hdr; + size_t mhr_len; + uint8_t tag; + + /* window is large enough to send all fragments at once */ + TEST_ASSERT(3 <= CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE); + TEST_ASSERT_NOT_NULL((pkt = _create_send_datagram(false, true))); + + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_send(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag1)))); + /* tags are generated by the stack so don't check */ + _check_send_frag1(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + tag = hdr->base.tag; + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag2)))); + _check_send_frag2(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag3)))); + /* last fragment should request an ACK */ + _check_send_frag3(mhr_len, true); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + for (unsigned i = 0; i < CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES; i++) { + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* three fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(3U, clist_count(&fbuf->sfr.window)); + _target_buf_len = 0; + _wait_arq_timeout(fbuf); + /* resend of fragment 3 */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag3)))); + /* last fragment should request an ACK */ + _check_send_frag3(mhr_len, true); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + } + _target_buf_len = 0; + _wait_arq_timeout(fbuf); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag3))); + /* fragmentation buffer should have been deleted after + * CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES retries */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_fb_get_by_tag(tag)); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_send__first_ackd(void) +{ + gnrc_sixlowpan_frag_fb_t *fbuf; + gnrc_pktsnip_t *pkt; + sixlowpan_sfr_rfrag_t *hdr; + sixlowpan_sfr_ack_t *ack_hdr; + size_t mhr_len; + uint8_t tag; + BITFIELD(ack_bitmap, 32U) = { 0 }; + + /* window is large enough to send all fragments at once */ + TEST_ASSERT(3 <= CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE); + TEST_ASSERT_NOT_NULL((pkt = _create_send_datagram(false, true))); + + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_send(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag1)))); + /* tags are generated by the stack so don't check */ + _check_send_frag1(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + tag = hdr->base.tag; + /* simulate successful reception for this fragment */ + bf_set(ack_bitmap, sixlowpan_sfr_rfrag_get_seq(hdr)); + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag2)))); + _check_send_frag2(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag3)))); + /* last fragment should request an ACK */ + _check_send_frag3(mhr_len, true); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* three fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(3U, clist_count(&fbuf->sfr.window)); + TEST_ASSERT_NOT_NULL( + (pkt = _create_recv_ack(_test_ack, sizeof(_test_ack))) + ); + ack_hdr = pkt->data; + ack_hdr->base.tag = tag; + memcpy(ack_hdr->bitmap, ack_bitmap, sizeof(ack_hdr->bitmap)); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* two fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&fbuf->sfr.window)); + /* resend of fragment 2 (fragment 1 is not resent) */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag2)))); + _check_send_frag2(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + for (unsigned i = 0; i < CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES; i++) { + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* two fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&fbuf->sfr.window)); + /* resend of fragment 3 */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag3)))); + /* last fragment should request an ACK */ + _check_send_frag3(mhr_len, true); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + _target_buf_len = 0; + _wait_arq_timeout(fbuf); + } + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag3))); + /* fragmentation buffer should have been deleted after + * CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES retries */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_fb_get_by_tag(tag)); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_send__middle_ackd(void) +{ + gnrc_sixlowpan_frag_fb_t *fbuf; + gnrc_pktsnip_t *pkt; + sixlowpan_sfr_rfrag_t *hdr; + sixlowpan_sfr_ack_t *ack_hdr; + size_t mhr_len; + uint8_t tag; + BITFIELD(ack_bitmap, 32U) = { 0 }; + + /* window is large enough to send all fragments at once */ + TEST_ASSERT(3 <= CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE); + TEST_ASSERT_NOT_NULL((pkt = _create_send_datagram(false, true))); + + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_send(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag1)))); + /* tags are generated by the stack so don't check */ + _check_send_frag1(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + tag = hdr->base.tag; + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag2)))); + _check_send_frag2(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + /* simulate successful reception for this fragment */ + bf_set(ack_bitmap, sixlowpan_sfr_rfrag_get_seq(hdr)); + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag3)))); + /* last fragment should request an ACK */ + _check_send_frag3(mhr_len, true); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* three fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(3U, clist_count(&fbuf->sfr.window)); + TEST_ASSERT_NOT_NULL( + (pkt = _create_recv_ack(_test_ack, sizeof(_test_ack))) + ); + ack_hdr = pkt->data; + ack_hdr->base.tag = tag; + memcpy(ack_hdr->bitmap, ack_bitmap, sizeof(ack_hdr->bitmap)); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* two fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&fbuf->sfr.window)); + /* resend of fragment 1 */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag1)))); + _check_send_frag1(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + /* should time out since fragment 2 is not resent */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag2))); + for (unsigned i = 0; i < CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES; i++) { + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* two fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&fbuf->sfr.window)); + /* resend of fragment 3 */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag3)))); + /* last fragment should request an ACK */ + _check_send_frag3(mhr_len, true); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + _target_buf_len = 0; + _wait_arq_timeout(fbuf); + } + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag3))); + /* fragmentation buffer should have been deleted after + * CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES retries */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_fb_get_by_tag(tag)); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_send__last_ackd(void) +{ + gnrc_sixlowpan_frag_fb_t *fbuf; + gnrc_pktsnip_t *pkt; + sixlowpan_sfr_rfrag_t *hdr; + sixlowpan_sfr_ack_t *ack_hdr; + size_t mhr_len; + uint8_t tag; + BITFIELD(ack_bitmap, 32U) = { 0 }; + + /* window is large enough to send all fragments at once */ + TEST_ASSERT(3 <= CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE); + TEST_ASSERT_NOT_NULL((pkt = _create_send_datagram(false, true))); + + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_send(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag1)))); + /* tags are generated by the stack so don't check */ + _check_send_frag1(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + tag = hdr->base.tag; + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag2)))); + _check_send_frag2(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag3)))); + /* last fragment should request an ACK */ + _check_send_frag3(mhr_len, true); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* three fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(3U, clist_count(&fbuf->sfr.window)); + /* simulate successful reception for this fragment */ + bf_set(ack_bitmap, sixlowpan_sfr_rfrag_get_seq(hdr)); + TEST_ASSERT_NOT_NULL( + (pkt = _create_recv_ack(_test_ack, sizeof(_test_ack))) + ); + ack_hdr = pkt->data; + ack_hdr->base.tag = tag; + memcpy(ack_hdr->bitmap, ack_bitmap, sizeof(ack_hdr->bitmap)); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* two fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&fbuf->sfr.window)); + /* resend of fragment 1 */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag1)))); + _check_send_frag1(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + for (unsigned i = 0; i < CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES; i++) { + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* two fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&fbuf->sfr.window)); + /* resend of fragment 2 (fragment 3 was ACK'd so it does not need + * to be resent) */ + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag2)))); + /* second fragment should now request the ACK */ + _check_send_frag2(mhr_len, true); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + TEST_ASSERT(sixlowpan_sfr_rfrag_ack_req(hdr)); + _target_buf_len = 0; + _wait_arq_timeout(fbuf); + } + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag3))); + /* fragmentation buffer should have been deleted after + * CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES retries */ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_fb_get_by_tag(tag)); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_send__all_ackd(void) +{ + gnrc_sixlowpan_frag_fb_t *fbuf; + gnrc_pktsnip_t *pkt; + sixlowpan_sfr_rfrag_t *hdr; + sixlowpan_sfr_ack_t *ack_hdr; + size_t mhr_len; + uint8_t tag; + BITFIELD(ack_bitmap, 32U) = { 0 }; + + /* window is large enough to send all fragments at once */ + TEST_ASSERT(3 <= CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE); + TEST_ASSERT_NOT_NULL((pkt = _create_send_datagram(false, true))); + + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_send(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag1)))); + /* tags are generated by the stack so don't check */ + _check_send_frag1(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + tag = hdr->base.tag; + /* simulate successful reception for this fragment */ + bf_set(ack_bitmap, sixlowpan_sfr_rfrag_get_seq(hdr)); + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag2)))); + _check_send_frag2(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + /* simulate successful reception for this fragment */ + bf_set(ack_bitmap, sixlowpan_sfr_rfrag_get_seq(hdr)); + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag3)))); + /* last fragment should request an ACK */ + _check_send_frag3(mhr_len, true); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + /* simulate successful reception for this fragment */ + bf_set(ack_bitmap, sixlowpan_sfr_rfrag_get_seq(hdr)); + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* three fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(3U, clist_count(&fbuf->sfr.window)); + TEST_ASSERT_NOT_NULL( + (pkt = _create_recv_ack(_test_ack, sizeof(_test_ack))) + ); + ack_hdr = pkt->data; + ack_hdr->base.tag = tag; + memcpy(ack_hdr->bitmap, ack_bitmap, sizeof(ack_hdr->bitmap)); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag3))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_send__FULL_ack_recv(void) +{ + gnrc_sixlowpan_frag_fb_t *fbuf; + gnrc_pktsnip_t *pkt; + sixlowpan_sfr_rfrag_t *hdr; + sixlowpan_sfr_ack_t *ack_hdr; + size_t mhr_len; + uint8_t tag; + + /* window is large enough to send all fragments at once */ + TEST_ASSERT(3 <= CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE); + TEST_ASSERT_NOT_NULL((pkt = _create_send_datagram(false, true))); + + netdev_test_set_send_cb((netdev_test_t *)_mock_netif->dev, + _mock_netdev_send); + TEST_ASSERT(0 < gnrc_netapi_dispatch_send(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag1)))); + /* tags are generated by the stack so don't check */ + _check_send_frag1(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + tag = hdr->base.tag; + /* simulate successful reception for this fragment */ + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag2)))); + _check_send_frag2(mhr_len, false); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + /* simulate successful reception for this fragment */ + _target_buf_len = 0; + TEST_ASSERT((mhr_len = _wait_for_packet(sizeof(_test_send_frag3)))); + /* last fragment should request an ACK */ + _check_send_frag3(mhr_len, true); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT_EQUAL_INT(tag, hdr->base.tag); + /* fragmentation buffer still exists for re-sending */ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); + /* three fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(3U, clist_count(&fbuf->sfr.window)); + /* simulate successful reception for this fragment */ + TEST_ASSERT_NOT_NULL( + (pkt = _create_recv_ack(_test_ack, sizeof(_test_ack))) + ); + ack_hdr = pkt->data; + ack_hdr->base.tag = tag; + /* FULL ACK == all ones */ + memset(ack_hdr->bitmap, 0xff, sizeof(ack_hdr->bitmap)); + _target_buf_len = 0; + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag3))); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static Test *tests_gnrc_sixlowpan_frag_sfr_api(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_sfr_forward__success__1st_frag_sixlo), + new_TestFixture(test_sfr_forward__success__1st_frag_iphc), + new_TestFixture(test_sfr_forward__success__nth_frag_incomplete), + new_TestFixture(test_sfr_forward__success__nth_frag_complete), + new_TestFixture(test_sfr_forward__ENOMEM__netif_hdr_build_fail), + }; + + EMB_UNIT_TESTCALLER(tests, _set_up, _tear_down, fixtures); + + return (Test *)&tests; +} + +static Test *tests_gnrc_sixlowpan_frag_sfr_integration(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_sixlo_recv_fwd__1st_frag_uncomp), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_uncomp__req_ack), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_uncomp__no_route), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_uncomp__after_nth_frag), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_comp), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_comp__resend), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_comp__only_iphc), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_comp__only_iphc_no_nhc), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_comp__no_route), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_comp__no_route_only_iphc), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_comp__no_refrag), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_comp__after_nth_frag), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_abort), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_nalp), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_nalp__req_ack), + new_TestFixture(test_sixlo_recv_fwd__1st_frag_nalp__no_vrb), + new_TestFixture(test_sixlo_recv_fwd__nth_frag), + new_TestFixture(test_sixlo_recv_fwd__nth_frag__no_vrbe), + new_TestFixture(test_sixlo_recv_fwd__nth_frag__duplicate), + new_TestFixture(test_sixlo_recv_fwd__nth_frag__overlap), + new_TestFixture(test_sixlo_recv_fwd__frag__too_short), + new_TestFixture(test_sixlo_recv_fwd__ack), + new_TestFixture(test_sixlo_recv_fwd__NULL_ack), + new_TestFixture(test_sixlo_recv_fwd__FULL_ack), + new_TestFixture(test_sixlo_recv_fwd__ack__no_vrbe), + new_TestFixture(test_sixlo_recv_fwd__ack__too_short), + new_TestFixture(test_sixlo_recv_ep__1st_frag_uncomp), + new_TestFixture(test_sixlo_recv_ep__1st_frag_uncomp__req_ack), + new_TestFixture(test_sixlo_recv_ep__1st_frag_uncomp__after_nth_frag), + new_TestFixture(test_sixlo_recv_ep__1st_frag_comp), + new_TestFixture(test_sixlo_recv_ep__1st_frag_comp__after_nth_frag), + new_TestFixture(test_sixlo_recv_ep__1st_frag_abort), + new_TestFixture(test_sixlo_recv_ep__1st_frag_abort__req_ack), + new_TestFixture(test_sixlo_recv_ep__complete_datagram), + new_TestFixture(test_sixlo_send), + new_TestFixture(test_sixlo_send__first_ackd), + new_TestFixture(test_sixlo_send__middle_ackd), + new_TestFixture(test_sixlo_send__last_ackd), + new_TestFixture(test_sixlo_send__all_ackd), + new_TestFixture(test_sixlo_send__FULL_ack_recv), + }; + + EMB_UNIT_TESTCALLER(tests, _set_up, _tear_down, fixtures); + + return (Test *)&tests; +} + +int main(void) +{ + _tests_init(); + + TESTS_START(); + TESTS_RUN(tests_gnrc_sixlowpan_frag_sfr_api()); + TESTS_RUN(tests_gnrc_sixlowpan_frag_sfr_integration()); + TESTS_END(); + return 0; +} + +static gnrc_pktsnip_t *_create_recv_frag(const void *frag_data, + size_t frag_size) +{ + gnrc_pktsnip_t *netif; + gnrc_netif_hdr_t *netif_hdr; + + netif = gnrc_netif_hdr_build(_vrbe_base.src, _vrbe_base.src_len, + _vrbe_base.dst, _vrbe_base.dst_len); + if (netif == NULL) { + return NULL; + } + netif_hdr = netif->data; + gnrc_netif_hdr_set_netif(netif_hdr, _mock_netif); + netif_hdr->flags = GNRC_NETIF_HDR_FLAGS_MORE_DATA; + return gnrc_pktbuf_add(netif, frag_data, frag_size, + GNRC_NETTYPE_SIXLOWPAN); +} + +static gnrc_pktsnip_t *_create_recv_ack(const void *ack_data, + size_t ack_size) +{ + gnrc_pktsnip_t *netif; + gnrc_netif_hdr_t *netif_hdr; + + netif = gnrc_netif_hdr_build(_rem_l2, sizeof(_rem_l2), + _vrbe_base.dst, _vrbe_base.dst_len); + if (netif == NULL) { + return NULL; + } + netif_hdr = netif->data; + gnrc_netif_hdr_set_netif(netif_hdr, _mock_netif); + return gnrc_pktbuf_add(netif, ack_data, ack_size, + GNRC_NETTYPE_SIXLOWPAN); +} + +static int _set_route_and_nce(const ipv6_addr_t *route, unsigned pfx_len) +{ + /* add neighbor cache entry */ + if (gnrc_ipv6_nib_nc_set(&_rem_ll, _mock_netif->pid, + _rem_l2, sizeof(_rem_l2)) < 0) { + return -1; + } + /* and route to neighbor */ + if (gnrc_ipv6_nib_ft_add(route, pfx_len, &_rem_ll, _mock_netif->pid, + 0) < 0) { + return -1; + } + return 0; +} + +static int _add_dst(const ipv6_addr_t *dst, unsigned pfx_len) +{ + /* discarding const qualifier should be safe */ + return gnrc_netif_ipv6_addr_add(_mock_netif, (ipv6_addr_t *)dst, pfx_len, + GNRC_NETIF_IPV6_ADDRS_FLAGS_STATE_VALID); +} + +static gnrc_pktsnip_t *_create_send_datagram(bool compressed, bool payload) +{ + gnrc_pktsnip_t *pkt1 = NULL, *pkt2; + gnrc_netif_hdr_t *netif_hdr; + + if (payload) { + pkt1 = gnrc_pktbuf_add(NULL, _test_send_icmpv6, sizeof(_test_send_icmpv6), + GNRC_NETTYPE_ICMPV6); + if (pkt1 == NULL) { + return NULL; + } + } + if (compressed) { + /* Use IPHC header from expected data */ + pkt2 = gnrc_pktbuf_add(pkt1, + &_test_send_frag1[TEST_SEND_FRAG1_PAYLOAD_POS], + TEST_SEND_FRAG1_PAYLOAD_SIZE, + GNRC_NETTYPE_SIXLOWPAN); + } + else { + pkt2 = gnrc_pktbuf_add(pkt1, _test_send_ipv6, sizeof(_test_send_ipv6), + GNRC_NETTYPE_IPV6); + } + if (pkt2 == NULL) { + return NULL; + } + pkt1 = gnrc_netif_hdr_build(_vrbe_base.src, _vrbe_base.src_len, + _vrbe_base.dst, _vrbe_base.dst_len); + if (pkt1 == NULL) { + return NULL; + } + netif_hdr = pkt1->data; + if (netif_hdr == NULL) { + return NULL; + } + gnrc_netif_hdr_set_netif(netif_hdr, _mock_netif); + LL_PREPEND(pkt2, pkt1); + return pkt2; +} + +static size_t _wait_for_packet(size_t exp_size) +{ + size_t mhr_len; + uint32_t now = 0U; + + xtimer_mutex_lock_timeout(&_target_buf_filled, + SEND_PACKET_TIMEOUT); + while ((mhr_len = ieee802154_get_frame_hdr_len(_target_buf))) { + now = xtimer_now_usec(); + size_t size = _target_buf_len - mhr_len; +#ifdef MODULE_OD + if (_target_buf_len > 0) { + puts("Sent packet: "); + od_hex_dump(_target_buf, _target_buf_len, OD_WIDTH_DEFAULT); + } +#endif /* MODULE_OD */ + if ((sizeof(sixlowpan_sfr_ack_t) == size) && + (sixlowpan_sfr_ack_is((sixlowpan_sfr_t *)&_target_buf[mhr_len]))) { + /* found ACK */ + break; + } + if (exp_size == size) { + /* found expected packet */ + break; + } + /* let packets in again at the device */ + mutex_unlock(&_target_buf_barrier); + /* wait for next packet */ + if (xtimer_mutex_lock_timeout(&_target_buf_filled, + SEND_PACKET_TIMEOUT) < 0) { + return 0; + } + } + if (mhr_len > 0) { + if ((CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US > 0U) && + (now - _last_sent_frame) < CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US) { + puts("(now - _last_sent_frame) < CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US"); + return 0; + } + _last_sent_frame = now; + } + return mhr_len; +} + +static inline void _wait_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf) +{ + /* wait 1.25 * arq_timeout of datagram fbuf->tag */ + xtimer_usleep((fbuf->sfr.arq_timeout + + (fbuf->sfr.arq_timeout >> 2)) * US_PER_MS); +} + +static void _check_vrbe_values(gnrc_sixlowpan_frag_vrb_t *vrbe, + size_t mhr_len, bool check_offset_diff, + int16_t exp_offset_diff) +{ + uint8_t target_buf_dst[IEEE802154_LONG_ADDRESS_LEN]; + sixlowpan_sfr_rfrag_t *frag_hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + le_uint16_t tmp; + + TEST_ASSERT(sixlowpan_sfr_rfrag_is(&frag_hdr->base)); + TEST_ASSERT_EQUAL_INT(vrbe->super.dst_len, + ieee802154_get_dst(_target_buf, + target_buf_dst, + &tmp)); + TEST_ASSERT_MESSAGE(memcmp(vrbe->super.dst, target_buf_dst, + vrbe->super.dst_len) == 0, + "vrbe->out_dst != target_buf_dst"); + + TEST_ASSERT_EQUAL_INT(vrbe->out_tag, + frag_hdr->base.tag); + TEST_ASSERT_NOT_NULL(vrbe->in_netif); + if (check_offset_diff) { + TEST_ASSERT_EQUAL_INT(exp_offset_diff, vrbe->offset_diff); + } +} + +static void _check_ack(size_t mhr_len, uint8_t exp_tag, + const uint8_t *exp_bitmap) +{ + sixlowpan_sfr_ack_t *ack = (sixlowpan_sfr_ack_t *)&_target_buf[mhr_len]; + + TEST_ASSERT(sixlowpan_sfr_ack_is(&ack->base)); + TEST_ASSERT_EQUAL_INT(exp_tag, ack->base.tag); + TEST_ASSERT_MESSAGE(memcmp(ack->bitmap, exp_bitmap, + sizeof(ack->bitmap)) == 0, + "Unexpected RFRAG-ACK bitmap"); +} + +static void _check_abort_ack(size_t mhr_len, uint8_t exp_tag) +{ + static const uint8_t _null_bitmap[] = { 0, 0, 0, 0 }; + + _check_ack(mhr_len, exp_tag, _null_bitmap); +} + +static void _check_1st_frag_uncomp(size_t mhr_len, uint8_t exp_hl_diff) +{ + static const ipv6_hdr_t *exp_ipv6_hdr = (ipv6_hdr_t *)&_test_1st_frag_uncomp[ + TEST_1ST_FRAG_UNCOMP_IPV6_HDR_POS + ]; + ipv6_hdr_t *ipv6_hdr; + + TEST_ASSERT_EQUAL_INT( + SIXLOWPAN_UNCOMP, + _target_buf[mhr_len + TEST_1ST_FRAG_UNCOMP_PAYLOAD_POS] + ); + ipv6_hdr = (ipv6_hdr_t *)&_target_buf[ + mhr_len + TEST_1ST_FRAG_UNCOMP_IPV6_HDR_POS + ]; + TEST_ASSERT_EQUAL_INT(exp_ipv6_hdr->v_tc_fl.u32, ipv6_hdr->v_tc_fl.u32); + TEST_ASSERT_EQUAL_INT(exp_ipv6_hdr->len.u16, ipv6_hdr->len.u16); + TEST_ASSERT_EQUAL_INT(exp_ipv6_hdr->nh, ipv6_hdr->nh); + /* hop-limit shall be decremented by 1 */ + TEST_ASSERT_EQUAL_INT(exp_ipv6_hdr->hl - exp_hl_diff, ipv6_hdr->hl); + TEST_ASSERT(ipv6_addr_equal(&exp_ipv6_hdr->src, &ipv6_hdr->src)); + TEST_ASSERT(ipv6_addr_equal(&exp_ipv6_hdr->dst, &ipv6_hdr->dst)); + TEST_ASSERT_MESSAGE( + memcmp(&_test_1st_frag_uncomp[TEST_1ST_FRAG_UNCOMP_IPV6_PAYLOAD_POS], + ipv6_hdr + 1, TEST_1ST_FRAG_UNCOMP_IPV6_PAYLOAD_SIZE) == 0, + "unexpected forwarded packet payload" + ); +} + +static void _check_send_frag_datagram_fields(size_t mhr_len) +{ + sixlowpan_sfr_rfrag_t *frag_hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + + if (sixlowpan_sfr_rfrag_get_seq(frag_hdr) == 0) { + TEST_ASSERT_EQUAL_INT(TEST_SEND_COMP_DATAGRAM_SIZE, + sixlowpan_sfr_rfrag_get_offset(frag_hdr)); + } +} + +static void _check_send_frag1(size_t mhr_len, bool ack_req) +{ + sixlowpan_sfr_rfrag_t *frag_hdr; + + frag_hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT(sixlowpan_sfr_rfrag_is(&frag_hdr->base)); + _check_send_frag_datagram_fields(mhr_len); + TEST_ASSERT_EQUAL_INT( + sizeof(_test_send_frag1) - sizeof(sixlowpan_sfr_rfrag_t), + sixlowpan_sfr_rfrag_get_frag_size(frag_hdr) + ); + TEST_ASSERT_EQUAL_INT(0, sixlowpan_sfr_rfrag_get_seq(frag_hdr)); + TEST_ASSERT_EQUAL_INT(ack_req, sixlowpan_sfr_rfrag_ack_req(frag_hdr)); + TEST_ASSERT_MESSAGE( + memcmp(&_test_send_frag1[TEST_SEND_FRAG1_PAYLOAD_POS], + &_target_buf[TEST_SEND_FRAG1_PAYLOAD_POS + mhr_len], + sixlowpan_sfr_rfrag_get_frag_size(frag_hdr)) == 0, + "unexpected IPHC header" + ); +} + +static void _check_send_frag2(size_t mhr_len, bool ack_req) +{ + sixlowpan_sfr_rfrag_t *frag_hdr; + + frag_hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT(sixlowpan_sfr_rfrag_is(&frag_hdr->base)); + _check_send_frag_datagram_fields(mhr_len); + TEST_ASSERT_EQUAL_INT( + sizeof(_test_send_frag2) - sizeof(sixlowpan_sfr_rfrag_t), + sixlowpan_sfr_rfrag_get_frag_size(frag_hdr) + ); + TEST_ASSERT_EQUAL_INT( + sixlowpan_sfr_rfrag_get_offset((sixlowpan_sfr_rfrag_t *)&_test_send_frag2), + sixlowpan_sfr_rfrag_get_offset(frag_hdr) + ); + TEST_ASSERT(sixlowpan_sfr_rfrag_get_seq(frag_hdr) > 0); + TEST_ASSERT_EQUAL_INT(ack_req, sixlowpan_sfr_rfrag_ack_req(frag_hdr)); + TEST_ASSERT_MESSAGE( + memcmp(&_test_send_frag2[sizeof(sixlowpan_sfr_rfrag_t)], + &_target_buf[sizeof(sixlowpan_sfr_rfrag_t) + mhr_len], + sixlowpan_sfr_rfrag_get_frag_size(frag_hdr)) == 0, + "unexpected send packet payload" + ); +} + +static void _check_send_frag3(size_t mhr_len, bool ack_req) +{ + sixlowpan_sfr_rfrag_t *frag_hdr; + + frag_hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + TEST_ASSERT(sixlowpan_sfr_rfrag_is(&frag_hdr->base)); + _check_send_frag_datagram_fields(mhr_len); + TEST_ASSERT_EQUAL_INT( + sizeof(_test_send_frag3) - sizeof(sixlowpan_sfr_rfrag_t), + sixlowpan_sfr_rfrag_get_frag_size(frag_hdr) + ); + TEST_ASSERT_EQUAL_INT( + sixlowpan_sfr_rfrag_get_offset((sixlowpan_sfr_rfrag_t *)&_test_send_frag3), + sixlowpan_sfr_rfrag_get_offset(frag_hdr) + ); + TEST_ASSERT(sixlowpan_sfr_rfrag_get_seq(frag_hdr) > 0); + TEST_ASSERT_EQUAL_INT(ack_req, sixlowpan_sfr_rfrag_ack_req(frag_hdr)); + TEST_ASSERT_MESSAGE( + memcmp(&_test_send_frag3[sizeof(sixlowpan_sfr_rfrag_t)], + &_target_buf[sizeof(sixlowpan_sfr_rfrag_t) + mhr_len], + sixlowpan_sfr_rfrag_get_frag_size(frag_hdr)) == 0, + "unexpected send packet payload" + ); +} + +static const gnrc_sixlowpan_frag_rb_t *_first_non_empty_rbuf(void) +{ + const gnrc_sixlowpan_frag_rb_t *rbuf = gnrc_sixlowpan_frag_rb_array(); + + for (unsigned i = 0; i < CONFIG_GNRC_SIXLOWPAN_FRAG_RBUF_SIZE; i++) { + if (!gnrc_sixlowpan_frag_rb_entry_empty(&rbuf[i])) { + return rbuf; + } + } + return NULL; +} + +static int _mock_netdev_send(netdev_t *dev, const iolist_t *iolist) +{ + (void)dev; + mutex_lock(&_target_buf_barrier); + _target_buf_len = 0; + for (const iolist_t *ptr = iolist; ptr != NULL; ptr = ptr->iol_next) { + if ((_target_buf_len + iolist->iol_len) > sizeof(_target_buf)) { + return -ENOBUFS; + } + memcpy(&_target_buf[_target_buf_len], ptr->iol_base, ptr->iol_len); + _target_buf_len += ptr->iol_len; + } + /* wake-up test thread */ + mutex_unlock(&_target_buf_filled); + return _target_buf_len; +} diff --git a/tests/gnrc_sixlowpan_frag_sfr/mockup_netif.c b/tests/gnrc_sixlowpan_frag_sfr/mockup_netif.c new file mode 100644 index 000000000000..2924ce08617d --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr/mockup_netif.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2017 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 Lenders + */ + +#include "common.h" +#include "msg.h" +#include "net/gnrc.h" +#include "net/ethernet.h" +#include "net/gnrc/ipv6/nib.h" +#include "net/gnrc/netif/ieee802154.h" +#include "net/gnrc/netif/internal.h" +#include "net/netdev_test.h" +#include "sched.h" +#include "thread.h" + +#define _MSG_QUEUE_SIZE (2) + +gnrc_netif_t *_mock_netif = NULL; + +static netdev_test_t _mock_netdev; +static char _mock_netif_stack[THREAD_STACKSIZE_DEFAULT]; +static msg_t _main_msg_queue[_MSG_QUEUE_SIZE]; +static gnrc_netif_t _netif; + +void _common_set_up(void) +{ + assert(_mock_netif != NULL); + gnrc_ipv6_nib_init(); + gnrc_netif_acquire(_mock_netif); + gnrc_ipv6_nib_init_iface(_mock_netif); + gnrc_netif_release(_mock_netif); +} + +int _get_device_type(netdev_t *dev, void *value, size_t max_len) +{ + (void)dev; + assert(max_len == sizeof(uint16_t)); + *((uint16_t *)value) = NETDEV_TYPE_IEEE802154; + return sizeof(uint16_t); +} + +static int _get_netdev_proto(netdev_t *netdev, void *value, size_t max_len) +{ + assert(max_len == sizeof(gnrc_nettype_t)); + (void)netdev; + + *((gnrc_nettype_t *)value) = GNRC_NETTYPE_SIXLOWPAN; + return sizeof(gnrc_nettype_t); +} + +int _get_max_packet_size(netdev_t *dev, void *value, size_t max_len) +{ + (void)dev; + assert(max_len == sizeof(uint16_t)); + *((uint16_t *)value) = 102U; + return sizeof(uint16_t); +} + +int _get_src_len(netdev_t *dev, void *value, size_t max_len) +{ + (void)dev; + assert(max_len == sizeof(uint16_t)); + *((uint16_t *)value) = IEEE802154_LONG_ADDRESS_LEN; + return sizeof(uint16_t); +} + +int _get_address_long(netdev_t *dev, void *value, size_t max_len) +{ + static const uint8_t addr[] = { _LL0, _LL1, _LL2, _LL3, + _LL4, _LL5, _LL6, _LL7 }; + + (void)dev; + assert(max_len >= sizeof(addr)); + memcpy(value, addr, sizeof(addr)); + return sizeof(addr); +} + +int _get_proto(netdev_t *dev, void *value, size_t max_len) +{ + (void)dev; + assert(max_len == sizeof(gnrc_nettype_t)); + *((gnrc_nettype_t *)value) = GNRC_NETTYPE_SIXLOWPAN; + return sizeof(gnrc_nettype_t); +} + +void _tests_init(void) +{ + int res; + + msg_init_queue(_main_msg_queue, _MSG_QUEUE_SIZE); + netdev_test_setup(&_mock_netdev, 0); + netdev_test_set_get_cb(&_mock_netdev, NETOPT_DEVICE_TYPE, + _get_device_type); + netdev_test_set_get_cb(&_mock_netdev, NETOPT_PROTO, + _get_netdev_proto); + netdev_test_set_get_cb(&_mock_netdev, NETOPT_MAX_PACKET_SIZE, + _get_max_packet_size); + netdev_test_set_get_cb(&_mock_netdev, NETOPT_SRC_LEN, + _get_src_len); + netdev_test_set_get_cb(&_mock_netdev, NETOPT_ADDRESS_LONG, + _get_address_long); + netdev_test_set_get_cb(&_mock_netdev, NETOPT_PROTO, + _get_proto); + res = gnrc_netif_ieee802154_create( + &_netif, _mock_netif_stack, THREAD_STACKSIZE_DEFAULT, + GNRC_NETIF_PRIO, "mockup_wpan", &_mock_netdev.netdev.netdev + ); + assert(res == 0); + _mock_netif = &_netif; +} + +/** @} */ diff --git a/tests/gnrc_sixlowpan_frag_sfr/tests/01-run.py b/tests/gnrc_sixlowpan_frag_sfr/tests/01-run.py new file mode 100755 index 000000000000..6d8b056e2d59 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr/tests/01-run.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 + +# Copyright (C) 2016 Kaspar Schleiser +# Copyright (C) 2016 Takuo Yonezawa +# +# 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 sys +from testrunner import run + + +def testfunc(child): + child.expect(r"OK \(\d+ tests\)") + + +if __name__ == "__main__": + sys.exit(run(testfunc)) From e980405cbce777ef7ff3832f4ed725891915d955 Mon Sep 17 00:00:00 2001 From: "Martine S. Lenders" Date: Tue, 3 Dec 2019 15:56:32 +0100 Subject: [PATCH 10/10] gnrc_sixlowpan_frag_sfr: provide statistics sub-module --- makefiles/pseudomodules.inc.mk | 1 + sys/Makefile.dep | 4 ++ sys/include/net/gnrc/sixlowpan/frag/sfr.h | 31 +++++++++ .../frag/sfr/gnrc_sixlowpan_frag_sfr.c | 63 +++++++++++++++++-- sys/shell/commands/sc_gnrc_6lo_frag_stats.c | 21 +++++++ 5 files changed, 116 insertions(+), 4 deletions(-) diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index ee617d964410..1ef1e6804a4e 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -56,6 +56,7 @@ PSEUDOMODULES += gnrc_sixloenc PSEUDOMODULES += gnrc_sixlowpan_border_router_default PSEUDOMODULES += gnrc_sixlowpan_default PSEUDOMODULES += gnrc_sixlowpan_frag_hint +PSEUDOMODULES += gnrc_sixlowpan_frag_sfr_stats PSEUDOMODULES += gnrc_sixlowpan_iphc_nhc PSEUDOMODULES += gnrc_sixlowpan_nd_border_router PSEUDOMODULES += gnrc_sixlowpan_router_default diff --git a/sys/Makefile.dep b/sys/Makefile.dep index 989e4b618fef..66ca09d64830 100644 --- a/sys/Makefile.dep +++ b/sys/Makefile.dep @@ -333,6 +333,10 @@ ifneq (,$(filter gnrc_sixlowpan_frag_sfr,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter gnrc_sixlowpan_frag_sfr_stats,$(USEMODULE))) + USEMODULE += gnrc_sixlowpan_frag_sfr +endif + ifneq (,$(filter gnrc_sixlowpan_frag_vrb,$(USEMODULE))) USEMODULE += xtimer USEMODULE += gnrc_sixlowpan_frag_fb diff --git a/sys/include/net/gnrc/sixlowpan/frag/sfr.h b/sys/include/net/gnrc/sixlowpan/frag/sfr.h index 3d5b9a5fcfbc..3a3a32e26053 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/sfr.h +++ b/sys/include/net/gnrc/sixlowpan/frag/sfr.h @@ -56,6 +56,28 @@ extern "C" { */ #define GNRC_SIXLOWPAN_FRAG_SFR_INTER_FRAG_GAP_MSG (0x0228) +/** + * @brief Stats on selective fragment recovery + */ +typedef struct { + uint32_t datagram_resends; /**< datagrams resent */ + struct { + uint32_t usual; /**< non-abort fragments sent */ + uint32_t aborts; /**< abort pseudo-fragments sent */ + uint32_t forwarded; /**< forwarded fragments */ + } fragments_sent; /**< RFRAG packets sent */ + struct { + uint32_t by_nack; /**< fragments resent due to a 0 in ACK's bitmap */ + uint32_t by_timeout; /**< fragments resent due to an ARQ timeout */ + } fragment_resends; /**< fragments resent */ + struct { + uint32_t full; /**< full RFRAGs ACKs sent */ + uint32_t partly; /**< partly ACKing RFRAGs sent */ + uint32_t aborts; /**< abort RFRAG ACKs sent */ + uint32_t forwarded; /**< forwarded ACKs */ + } acks; /**< ACKs stats */ +} gnrc_sixlowpan_frag_sfr_stats_t; + /** * @brief Initialize selective fragment recovery */ @@ -163,6 +185,15 @@ void gnrc_sixlowpan_frag_sfr_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf); */ void gnrc_sixlowpan_frag_sfr_inter_frame_gap(void); +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS) +/** + * @brief Fetch current stats for selective fragment recovery + * + * @param[out] stats The current stats. Must not be NULL. + */ +void gnrc_sixlowpan_frag_sfr_stats_get(gnrc_sixlowpan_frag_sfr_stats_t *stats); +#endif /* IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS) */ + #ifdef __cplusplus } #endif diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/gnrc_sixlowpan_frag_sfr.c b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/gnrc_sixlowpan_frag_sfr.c index 9b2119c8c448..8058a5b545c6 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/gnrc_sixlowpan_frag_sfr.c +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/gnrc_sixlowpan_frag_sfr.c @@ -96,6 +96,8 @@ static clist_node_t _frame_queue; static const gnrc_sixlowpan_frag_sfr_bitmap_t _full_bitmap = { .u32 = UINT32_MAX }; static const gnrc_sixlowpan_frag_sfr_bitmap_t _null_bitmap = { .u32 = 0U }; +static gnrc_sixlowpan_frag_sfr_stats_t _stats; + /** * @brief Converts a @ref sys_bitmap based bitmap to a * gnrc_sixlowpan_frag_sfr_bitmap_t @@ -495,6 +497,11 @@ void gnrc_sixlowpan_frag_sfr_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf) error_no = ENOMEM; goto error; } + else if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + /* fragment was resent successfully, note this done in + * the statistics */ + _stats.fragment_resends.by_timeout++; + } /* fragment was resent successfully, schedule next ACK * timeout */ reschedule_arq_timeout = true; @@ -559,6 +566,13 @@ void gnrc_sixlowpan_frag_sfr_inter_frame_gap(void) } } +void gnrc_sixlowpan_frag_sfr_stats_get(gnrc_sixlowpan_frag_sfr_stats_t *stats) +{ + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + *stats = _stats; + } +} + /* ====== INTERNAL FUNCTION DEFINITIONS ====== */ static inline uint16_t _min(uint16_t a, size_t b) { @@ -770,6 +784,9 @@ static bool _send_fragment(gnrc_pktsnip_t *frag, gnrc_sixlowpan_frag_fb_t *fbuf, frag_desc->retries = 0; clist_rpush(&fbuf->sfr.window, &frag_desc->super); if ((res = _send_frame(frag, NULL, page))) { + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.fragments_sent.usual++; + } frag_desc->last_sent = _last_frame_sent; fbuf->sfr.cur_seq++; fbuf->sfr.frags_sent++; @@ -866,6 +883,9 @@ static void _try_reassembly(gnrc_netif_hdr_t *netif_hdr, "fragment\n"); /* send abort */ bitmap = &_null_bitmap; + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.acks.aborts++; + } } else if (vrbe != NULL) { DEBUG("6lo sfr: packet was forwarded\n"); @@ -886,11 +906,17 @@ static void _try_reassembly(gnrc_netif_hdr_t *netif_hdr, _clean_up_rb_entry(entry); /* send abort */ bitmap = &_null_bitmap; + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.acks.aborts++; + } } else { if (res) { DEBUG("6lo sfr: dispatched datagram to upper layer\n"); bitmap = &_full_bitmap; + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.acks.full++; + } } else if (ack_req) { DEBUG("6lo sfr: ACKing received fragments %02X%02X%02X%02X " @@ -902,6 +928,9 @@ static void _try_reassembly(gnrc_netif_hdr_t *netif_hdr, entry->entry.base->current_size, entry->entry.base->datagram_size); bitmap = _to_bitmap(entry->entry.rb->received); + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.acks.partly++; + } } else { /* no ACK was requested and no error was causing an abort ACK*/ @@ -1087,6 +1116,17 @@ static void _handle_nth_rfrag(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, } } +static int _resend_failed_frag(clist_node_t *node, void *fbuf_ptr) +{ + int res; + + if (((res = _resend_frag(node, fbuf_ptr)) == 0) && + IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.fragment_resends.by_nack++; + } + return res; +} + static void _check_failed_frags(sixlowpan_sfr_ack_t *ack, gnrc_sixlowpan_frag_fb_t *fbuf) { @@ -1140,10 +1180,10 @@ static void _check_failed_frags(sixlowpan_sfr_ack_t *ack, else { fbuf->sfr.window = not_received; assert(fbuf->sfr.frags_sent == clist_count(&fbuf->sfr.window)); - /* use _resend_frag here instead of loop above, so _resend_frag - * can know if the fragment is the last in the window by using - * clist_rpeek() on fbuf->sfr.window */ - if (clist_foreach(&fbuf->sfr.window, _resend_frag, fbuf) != NULL) { + /* use _resend_failed_frag here instead of loop above, so + * _resend_frag can know if the fragment is the last in the window by + * using clist_rpeek() on fbuf->sfr.window */ + if (clist_foreach(&fbuf->sfr.window, _resend_failed_frag, fbuf) != NULL) { /* XXX: it is unlikely that allocating an abort RFRAG will be * successful since the resources missing to cause the abort are * still in use, but we should at least try */ @@ -1310,6 +1350,9 @@ static bool _send_abort_frag(gnrc_pktsnip_t *pkt, uint8_t tag, bool req_ack, if (frag != NULL) { sixlowpan_sfr_rfrag_set_offset(frag->next->data, 0); _send_frame(frag, NULL, page); + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.fragments_sent.aborts++; + } return true; } return false; @@ -1378,6 +1421,9 @@ static void _retry_datagram(gnrc_sixlowpan_frag_fb_t *fbuf) } else { DEBUG("6lo sfr: Retrying to send datagram %u completely\n", fbuf->tag); + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.datagram_resends++; + } fbuf->sfr.retrans--; /* return fragmentation buffer to its original state to resend the whole * datagram again */ @@ -1396,6 +1442,9 @@ static void _abort_rb(gnrc_pktsnip_t *pkt, _generic_rb_entry_t *entry, netif_hdr->src_l2addr_len, addr_str), hdr->base.tag); if (send_ack) { + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.acks.aborts++; + } _send_ack(gnrc_netif_hdr_get_netif(netif_hdr), gnrc_netif_hdr_get_src_addr(netif_hdr), netif_hdr->src_l2addr_len, @@ -1513,6 +1562,9 @@ static void _handle_ack(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, addr_str), vrbe->super.tag); _send_ack(vrbe->in_netif, vrbe->super.src, vrbe->super.src_len, &mock_base, hdr->bitmap); + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.acks.forwarded++; + } if ((unaligned_get_u32(hdr->bitmap) == _full_bitmap.u32) || (unaligned_get_u32(hdr->bitmap) == _null_bitmap.u32)) { if (CONFIG_GNRC_SIXLOWPAN_FRAG_RBUF_DEL_TIMER > 0) { @@ -1598,6 +1650,9 @@ static int _forward_rfrag(gnrc_pktsnip_t *pkt, _generic_rb_entry_t *entry, gnrc_netif_hdr_set_netif(new->data, entry->entry.vrb->out_netif); new->next = pkt; _send_frame(new, NULL, page); + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.fragments_sent.forwarded++; + } return 0; } diff --git a/sys/shell/commands/sc_gnrc_6lo_frag_stats.c b/sys/shell/commands/sc_gnrc_6lo_frag_stats.c index 53beec138447..6e256fc7cf45 100644 --- a/sys/shell/commands/sc_gnrc_6lo_frag_stats.c +++ b/sys/shell/commands/sc_gnrc_6lo_frag_stats.c @@ -16,6 +16,9 @@ #include #include "net/gnrc/sixlowpan/frag/stats.h" +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS +#include "net/gnrc/sixlowpan/frag/sfr.h" +#endif int _gnrc_6lo_frag_stats(int argc, char **argv) { @@ -28,6 +31,24 @@ int _gnrc_6lo_frag_stats(int argc, char **argv) #ifdef MODULE_GNRC_SIXLOWPAN_FRAG_VRB printf("VRB full: %u\n", stats->vrb_full); #endif +#ifdef MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS + gnrc_sixlowpan_frag_sfr_stats_t sfr; + + gnrc_sixlowpan_frag_sfr_stats_get(&sfr); + printf("DG resends: %lu\n", (long unsigned)sfr.datagram_resends); + printf("frags sent: usual: %lu, aborts: %lu, forwarded: %lu\n", + (long unsigned)sfr.fragments_sent.usual, + (long unsigned)sfr.fragments_sent.aborts, + (long unsigned)sfr.fragments_sent.forwarded); + printf("frag resends: NACK: %lu, timeout: %lu\n", + (long unsigned)sfr.fragment_resends.by_nack, + (long unsigned)sfr.fragment_resends.by_timeout); + printf("ACKs: full: %lu, partly: %lu, aborts: %lu, forwarded: %lu\n", + (long unsigned)sfr.acks.full, + (long unsigned)sfr.acks.partly, + (long unsigned)sfr.acks.aborts, + (long unsigned)sfr.acks.forwarded); +#endif /* MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS */ printf("frags complete: %u\n", stats->fragments); printf("dgs complete: %u\n", stats->datagrams); return 0;