diff --git a/makefiles/pseudomodules.inc.mk b/makefiles/pseudomodules.inc.mk index 8d6b28661ae6..ca4b53aedc43 100644 --- a/makefiles/pseudomodules.inc.mk +++ b/makefiles/pseudomodules.inc.mk @@ -230,6 +230,12 @@ PSEUDOMODULES += gnrc_sixlowpan_frag_sfr_ecn_if_in PSEUDOMODULES += gnrc_sixlowpan_frag_sfr_ecn_if_out PSEUDOMODULES += gnrc_sixlowpan_frag_sfr_ecn_fqueue PSEUDOMODULES += gnrc_sixlowpan_frag_sfr_stats +## +## @addtogroup net_gnrc_sixlowpan_frag_sfr_congure +## @{ +## +PSEUDOMODULES += gnrc_sixlowpan_frag_sfr_congure +## @} PSEUDOMODULES += gnrc_sixlowpan_iphc_nhc PSEUDOMODULES += gnrc_sixlowpan_nd_border_router PSEUDOMODULES += gnrc_sixlowpan_router_default diff --git a/sys/include/net/gnrc/sixlowpan/config.h b/sys/include/net/gnrc/sixlowpan/config.h index fed8ab7f504a..d7a0a025e6a4 100644 --- a/sys/include/net/gnrc/sixlowpan/config.h +++ b/sys/include/net/gnrc/sixlowpan/config.h @@ -209,7 +209,13 @@ extern "C" { * size will vary between @ref CONFIG_GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE and @ref * CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE. */ +#ifdef CONFIG_GNRC_SIXLOWPAN_SFR_USE_ECN +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) +#define CONFIG_GNRC_SIXLOWPAN_SFR_USE_ECN 1U +#else #define CONFIG_GNRC_SIXLOWPAN_SFR_USE_ECN 0U +#endif +#endif /** * @brief Default minimum value of window size that the sender can use @@ -377,6 +383,17 @@ extern "C" { #ifndef CONFIG_GNRC_SIXLOWPAN_SFR_ECN_FQUEUE_DEN #define CONFIG_GNRC_SIXLOWPAN_SFR_ECN_FQUEUE_DEN 2U #endif + +/** + * @brief Deactivate automatic handling of ARQ timer + * + * This requires an external source (e.g. a test application) to call + * @ref gnrc_sixlowpan_frag_sfr_arq_timeout() for + * @ref net_gnrc_sixlowpan_frag_sfr to still work properly. + */ +#ifndef CONFIG_GNRC_SIXLOWPAN_SFR_MOCK_ARQ_TIMER +#define CONFIG_GNRC_SIXLOWPAN_SFR_MOCK_ARQ_TIMER 0U +#endif /** @} */ /** diff --git a/sys/include/net/gnrc/sixlowpan/frag/sfr.h b/sys/include/net/gnrc/sixlowpan/frag/sfr.h index 3a3a32e26053..d958f4721c93 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/sfr.h +++ b/sys/include/net/gnrc/sixlowpan/frag/sfr.h @@ -182,8 +182,11 @@ void gnrc_sixlowpan_frag_sfr_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf); /** * @brief Handles inter frame gap + * + * @param[in] fbuf The fragmentation buffer representing the datagram for which + * the next frame should uphold the inter frame gap */ -void gnrc_sixlowpan_frag_sfr_inter_frame_gap(void); +void gnrc_sixlowpan_frag_sfr_inter_frame_gap(gnrc_sixlowpan_frag_fb_t *fbuf); #if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS) /** diff --git a/sys/include/net/gnrc/sixlowpan/frag/sfr/congure.h b/sys/include/net/gnrc/sixlowpan/frag/sfr/congure.h new file mode 100644 index 000000000000..88627af58f54 --- /dev/null +++ b/sys/include/net/gnrc/sixlowpan/frag/sfr/congure.h @@ -0,0 +1,428 @@ +/* + * Copyright (C) 2021 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_congure Congestion control for 6LoWPAN SFR + * @ingroup net_gnrc_sixlowpan_frag_sfr + * + * @brief Congestion control for 6LoWPAN SFR using the @ref sys_congure + * + * When included, this module enables congestion control for 6LoWPAN Selective Fragment Recovery + * (SFR). The flavor of congestion control can be selected using the following sub-modules: + * + * - TBD + * @{ + * + * @file + * @brief Congure definitions for @ref net_gnrc_sixlowpan_frag_sfr + * + * @author Martine S. Lenders + */ +#ifndef NET_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_H +#define NET_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_H + +#include +#include + +#include "congure.h" +#include "kernel_defines.h" +#include "net/gnrc/sixlowpan/frag/sfr.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief The user-defined window unit for congure is one fragment with SFR + */ +#define GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_UNIT (1U) + +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) || DOXYGEN +/** + * @brief Retrieve CongURE state object from a pool of free objects + * + * Needs to be defined in for a each CongURE implementation `congure_x` e.g. as + * a sub-module `gnrc_sixlowpan_frag_sfr_congure_x` and call the respective + * `congure_x_snd_setup` function when a free object is available for that + * object. As such, congure_snd_t::driver == NULL can be used as an identifier + * if a state object is free. + * + * The pool of objects has to have an initial size of at least + * @ref CONFIG_GNRC_SIXLOWPAN_FRAG_FB_SIZE. + * + * The window unit is @ref GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_UNIT. + * + * @return A CongURE state object on success + * @return NULL, if no free CongURE state object is available (including when + * when module `gnrc_sixlowpan_frag_sfr_congure` is not included). + */ +congure_snd_t *gnrc_sixlowpan_frag_sfr_congure_snd_get(void); +#else +static inline congure_snd_t *gnrc_sixlowpan_frag_sfr_congure_snd_get(void) +{ + return NULL; +} +#endif + +/** + * @brief Frees the CongURE state object + * + * This makes a CongURE state object retrievable with + * @ref gnrc_sixlowpan_frag_sfr_congure_snd_get again. + * + * @pre CongURE object is not NULL when called with module + * `gnrc_sixlowpan_frag_sfr_congure` used. + * + * @param[in] c A CongURE state object + * + * @note Does not do anything without the module + * `gnrc_sixlowpan_frag_sfr_congure` + */ +static inline void gnrc_sixlowpan_frag_sfr_congure_snd_free(congure_snd_t *c) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + c->driver = NULL; +#else + (void)c; +#endif +} + +/** + * @brief Frees the CongURE state object of a fragmentation buffer and set's + * it to `NULL` + * + * @param[in] fb A fragmentation buffer entry + * + * @note Does not do anything without the module + * `gnrc_sixlowpan_frag_sfr_congure` + */ +static inline void gnrc_sixlowpan_frag_sfr_congure_snd_destroy(gnrc_sixlowpan_frag_fb_t *fb) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + gnrc_sixlowpan_frag_sfr_congure_snd_free(fb->sfr.congure); + fb->sfr.congure = NULL; +#else + (void)fb; +#endif +} + +/** + * @brief Initializes a CongURE state object in a fragmentation buffer entry + * + * @param[in] fb A fragmentation buffer entry + * + * @note Does not do anything without the module + * `gnrc_sixlowpan_frag_sfr_congure` + */ +static inline void gnrc_sixlowpan_frag_sfr_congure_snd_init(gnrc_sixlowpan_frag_fb_t *fb) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + fb->sfr.congure->driver->init(fb->sfr.congure, fb); +#else + (void)fb; +#endif +} + +/** + * @brief Retrieve CongURE state object when not retrieved and initialize it + * for a fragmentation buffer entry + * + * @param[in] fb A fragmentation buffer entry + * + * @note Does not do anything without the module + * `gnrc_sixlowpan_frag_sfr_congure` + */ +static inline void gnrc_sixlowpan_frag_sfr_congure_snd_setup(gnrc_sixlowpan_frag_fb_t *fb) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + if (fb->sfr.congure == NULL) { + fb->sfr.congure = gnrc_sixlowpan_frag_sfr_congure_snd_get(); + assert(fb->sfr.congure); + gnrc_sixlowpan_frag_sfr_congure_snd_init(fb); + if (fb->sfr.congure->cwnd > CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE) { + fb->sfr.congure->cwnd = CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE; + } + } +#else + (void)fb; +#endif +} + +/** + * @brief Checks if given fragmentation buffer entry is within congestion + * window + * + * @pre CongURE object of the fragmentation buffer entry is initialized when + * called with module `gnrc_sixlowpan_frag_sfr_congure` used. + * + * @param[in] fb A fragmentation buffer entry + * + * @note Without the module `gnrc_sixlowpan_frag_sfr_congure` the + * fragmentation buffer entry is checked against + * @ref CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE + * + * @retval true When @p fb is in congestion window + * @retval false When @p fb is not in congestion window + */ +static inline bool gnrc_sixlowpan_frag_sfr_congure_snd_in_cwnd(gnrc_sixlowpan_frag_fb_t *fb) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + return fb->sfr.frags_sent < fb->sfr.congure->cwnd; +#else + return fb->sfr.frags_sent < CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE; +#endif +} + +/** + * @brief Checks if given fragmentation buffer entry would still be within + * congestion window after next send + * + * @pre CongURE object of the fragmentation buffer entry is initialized when + * called with module `gnrc_sixlowpan_frag_sfr_congure` used. + * + * @note Without the module `gnrc_sixlowpan_frag_sfr_congure` the + * fragmentation buffer entry is checked against + * @ref CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE + * + * @retval true When @p fb can still send one fragment under the congestion + * window constraint. + * @retval false When @p fb can not still send one fragment under the + * congestion window constraint. + */ +static inline bool gnrc_sixlowpan_frag_sfr_congure_snd_next_in_cwnd(gnrc_sixlowpan_frag_fb_t *fb) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + return (fb->sfr.frags_sent + GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_UNIT) + < fb->sfr.congure->cwnd; +#else + return (fb->sfr.frags_sent + GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_UNIT) + < CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE; +#endif +} + +/** + * @brief Checks if inter-frame gap is provided + * + * Either because @ref CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US is greater + * 0 or module `gnrc_sixlowpan_frag_sfr_congure` is provided + * + * @retval true When an inter-frame gap can be provided + * @retval false When the inter-frame gap is supposed to be 0. + */ +static inline bool gnrc_sixlowpan_frag_sfr_congure_snd_has_inter_frame_gap(void) +{ + return (CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US > 0) || + IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE); +} + +/** + * @brief Returns inter-frame gap if provided by CongURE implementation + * + * When module `gnrc_sixlowpan_frag_sfr_congure` is provided it will provide + * congure_snd_driver_t::inter_message_interval() of the CongURE state object of + * the provided fragmentation buffer with @ref + * CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US as a lower bound. + * If congure_snd_driver_t::inter_message_interval returns -1, @p fb is NULL, or + * without the module `gnrc_sixlowpan_frag_sfr_congure` it will return + * @ref CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US + * + * @pre CongURE object of the fragmentation buffer entry is initialized when + * `fb` is `NULL` and when called with module + * `gnrc_sixlowpan_frag_sfr_congure` used. + * + * @param[in] fb A fragmentation buffer. May be NULL. + * + * @return The inter-frame gap for the given fragmentation buffer, but + */ +static inline uint32_t gnrc_sixlowpan_frag_sfr_congure_snd_inter_frame_gap( + gnrc_sixlowpan_frag_fb_t *fb + ) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + if (fb != NULL) { + congure_snd_t *c = fb->sfr.congure; + int32_t res = c->driver->inter_msg_interval( + c, GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_UNIT + ); + + if ((res >= 0) && + ((unsigned)res >= CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US)) { + return (uint32_t)res; + } + } +#else + (void)fb; +#endif + return CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US; +} + +/** + * @brief Report to CongURE that a fragment was sent. + * + * Calls congure_snd_driver_t::report_msg_sent for the CongURE state object of + * @p fb with `msg_size` @ref GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_UNIT. + * + * @pre CongURE object of the fragmentation buffer entry is initialized when + * called with module `gnrc_sixlowpan_frag_sfr_congure` used. + * + * @param[in] fb A fragmentation buffer. + */ +static inline void gnrc_sixlowpan_frag_sfr_congure_snd_report_frag_sent( + gnrc_sixlowpan_frag_fb_t *fb + ) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + congure_snd_t *c = fb->sfr.congure; + + c->driver->report_msg_sent(c, GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_UNIT); +#else + (void)fb; +#endif +} + +/** + * @brief Report to CongURE that a fragment as discarded. + * + * Calls congure_snd_driver_t::report_msg_discarded for the CongURE state object + * of @p fb with `msg_size` @ref GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_UNIT. + * + * @pre CongURE object of the fragmentation buffer entry is initialized when + * called with module `gnrc_sixlowpan_frag_sfr_congure` used. + * + * @param[in] fb A fragmentation buffer. + */ +static inline void gnrc_sixlowpan_frag_sfr_congure_snd_report_frag_discard( + gnrc_sixlowpan_frag_fb_t *fb + ) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + congure_snd_t *c = fb->sfr.congure; + + c->driver->report_msg_discarded(c, GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_UNIT); +#else + (void)fb; +#endif +} + +/** + * @brief Report to CongURE that the ACK for a fragment timed out. + * + * Calls congure_snd_driver_t::report_msgs_timeout for the CongURE state object + * of @p fb with gnrc_sixlowpan_frag_sfr_fb_t::window of @p fb as `msgs`. + * + * @pre CongURE object of the fragmentation buffer entry is initialized when + * called with module `gnrc_sixlowpan_frag_sfr_congure` used. + * + * @param[in] fb A fragmentation buffer. + */ +static inline void gnrc_sixlowpan_frag_sfr_congure_snd_report_frags_timeout( + gnrc_sixlowpan_frag_fb_t *fb + ) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + congure_snd_t *c = fb->sfr.congure; + + c->driver->report_msgs_timeout(c, (congure_snd_msg_t *)(&fb->sfr.window)); +#else + (void)fb; +#endif +} + +/** + * @brief Report to CongURE that a number of fragments are known to be lost. + * + * Calls congure_snd_driver_t::report_msgs_lost for the CongURE state object + * of @p fb with @p frags. + * + * @pre CongURE object of the fragmentation buffer entry is initialized when + * called with module `gnrc_sixlowpan_frag_sfr_congure` used. + * + * @param[in] fb A fragmentation buffer. + * @param[in] frags A collection of messages that are known to be lost. + * The list may be changed by the function. + */ +static inline void gnrc_sixlowpan_frag_sfr_congure_snd_report_frags_lost( + gnrc_sixlowpan_frag_fb_t *fb, congure_snd_msg_t *frags + ) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + congure_snd_t *c = fb->sfr.congure; + + c->driver->report_msgs_lost(c, frags); +#else + (void)fb; + (void)frags; +#endif +} + +/** + * @brief Report to CongURE that a number of fragments are known to be lost. + * + * Calls congure_snd_driver_t::report_msgs_acked for the CongURE state object + * of @p fb with @p frag and @p ack. + * + * @pre CongURE object of the fragmentation buffer entry is initialized when + * called with module `gnrc_sixlowpan_frag_sfr_congure` used. + * + * @param[in] fb A fragmentation buffer. + * @param[in] frag The ACK'd fragment. + * @param[in] ack The received ACK. + */ +static inline void gnrc_sixlowpan_frag_sfr_congure_snd_report_frag_acked( + gnrc_sixlowpan_frag_fb_t *fb, + congure_snd_msg_t *frag, + congure_snd_ack_t *ack + ) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + congure_snd_t *c = fb->sfr.congure; + + c->driver->report_msg_acked(c, frag, ack); + if (c->cwnd > CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE) { + c->cwnd = CONFIG_GNRC_SIXLOWPAN_SFR_MAX_WIN_SIZE; + } +#else + (void)fb; + (void)frag; + (void)ack; +#endif +} + +/** + * @brief Report to CongURE that ECN bit was set in an ACK. + * + * Calls congure_snd_driver_t::report_ecn_ce for the CongURE state object + * of @p fb with @p time. + * + * @pre CongURE object of the fragmentation buffer entry is initialized when + * called with module `gnrc_sixlowpan_frag_sfr_congure` used. + * + * @param[in] fb A fragmentation buffer. + * @param[in] time Timestamp in milliseconds of the earliest fragment + * for which the notified congestion occurred was sent. + */ +static inline void gnrc_sixlowpan_frag_sfr_congure_snd_report_ecn( + gnrc_sixlowpan_frag_fb_t *fb, uint32_t time + ) +{ +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + congure_snd_t *c = fb->sfr.congure; + + c->driver->report_ecn_ce(c, (ztimer_now_t)time); +#else + (void)fb; + (void)time; +#endif +} + +#ifdef __cplusplus +} +#endif + +#endif /* NET_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE_H */ +/** @} */ diff --git a/sys/include/net/gnrc/sixlowpan/frag/sfr_types.h b/sys/include/net/gnrc/sixlowpan/frag/sfr_types.h index 1894c0120848..d2f417ddd45e 100644 --- a/sys/include/net/gnrc/sixlowpan/frag/sfr_types.h +++ b/sys/include/net/gnrc/sixlowpan/frag/sfr_types.h @@ -22,7 +22,9 @@ #include "bitfield.h" #include "clist.h" +#include "congure.h" #include "evtimer_msg.h" +#include "kernel_defines.h" #include "msg.h" #include "xtimer.h" @@ -43,6 +45,9 @@ typedef union { * fragment recovery */ typedef struct gnrc_sixlowpan_frag_sfr_fb { +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) || DOXYGEN + congure_snd_t *congure; /**< state object for [CongURE](@ref sys_congure) */ +#endif /** * @brief Acknowledgment request timeout event */ @@ -51,8 +56,6 @@ typedef struct gnrc_sixlowpan_frag_sfr_fb { * 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; diff --git a/sys/net/gnrc/Makefile.dep b/sys/net/gnrc/Makefile.dep index b0f1d927c80c..4426854df4b2 100644 --- a/sys/net/gnrc/Makefile.dep +++ b/sys/net/gnrc/Makefile.dep @@ -240,6 +240,14 @@ ifneq (,$(filter gnrc_sixlowpan_frag_sfr,$(USEMODULE))) USEMODULE += xtimer endif +ifneq (,$(filter gnrc_sixlowpan_frag_sfr_congure_%,$(USEMODULE))) + USEMODULE += gnrc_sixlowpan_frag_sfr_congure +endif + +ifneq (,$(filter gnrc_sixlowpan_frag_sfr_congure,$(USEMODULE))) + USEMODULE += gnrc_sixlowpan_frag_sfr +endif + ifneq (,$(filter gnrc_sixlowpan_frag_sfr_ecn_%,$(USEMODULE))) USEMODULE += gnrc_sixlowpan_frag_sfr_ecn endif diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Kconfig b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Kconfig index 165b2982cf1b..e73248eab4db 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Kconfig +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Kconfig @@ -34,6 +34,7 @@ config GNRC_SIXLOWPAN_SFR_OPT_FRAG_SIZE config GNRC_SIXLOWPAN_SFR_USE_ECN bool "Indicates whether the sender should react to Explicit Content Notification (UseECN)" + default true if KCONFIG_USEMODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE default false help When the sender reacts to Explicit Congestion Notification (ECN) its @@ -41,8 +42,8 @@ config GNRC_SIXLOWPAN_SFR_USE_ECN 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" +if GNRC_SIXLOWPAN_SFR_USE_ECN && !KCONFIG_USEMODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE +comment "Warning: Reaction to ECN requires module `gnrc_sixlowpan_frag_sfr_congure`" endif config GNRC_SIXLOWPAN_SFR_MIN_WIN_SIZE @@ -168,4 +169,12 @@ config GNRC_SIXLOWPAN_SFR_ECN_FQUEUE_DEN @ref CONFIG_GNRC_SIXLOWPAN_SFR_ECN_FQUEUE_NUM / @ref CONFIG_GNRC_SIXLOWPAN_SFR_ECN_FQUEUE_DEN endif +config GNRC_SIXLOWPAN_SFR_MOCK_ARQ_TIMER + bool "Deactivate automatic handling of ARQ timer" + default n + help + This requires an external source (e.g. a test application) to call + @ref gnrc_sixlowpan_frag_sfr_arq_timeout() for + @ref net_gnrc_sixlowpan_frag_sfr to still work properly. + endif diff --git a/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Makefile b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Makefile index 70cae6bbeb49..2f2a7913ef6a 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Makefile +++ b/sys/net/gnrc/network_layer/sixlowpan/frag/sfr/Makefile @@ -1,3 +1,8 @@ MODULE := gnrc_sixlowpan_frag_sfr +SRC := $(MODULE).c + +# enable submodules +SUBMODULES := 1 + 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 index 2aff5233d313..a82c9e42e3db 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 @@ -39,6 +39,7 @@ #include "xtimer.h" #include "net/gnrc/sixlowpan/frag/sfr.h" +#include "net/gnrc/sixlowpan/frag/sfr/congure.h" #define ENABLE_DEBUG 0 #include "debug.h" @@ -49,14 +50,12 @@ CONFIG_GNRC_SIXLOWPAN_FRAG_VRB_SIZE) typedef struct { - clist_node_t super; /**< list parent instance */ - uint32_t last_sent; /**< last time sent in microseconds */ + congure_snd_msg_t super; /**< CongURE message parent */ /** * @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 */ + uint16_t offset; /**< offset of the fragment */ } _frag_desc_t; typedef struct { @@ -175,15 +174,25 @@ static uint16_t _send_nth_fragment(gnrc_netif_t *netif, * @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] fbuf Fragmentation buffer 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); +static bool _send_abort_frag(gnrc_pktsnip_t *pkt, + gnrc_sixlowpan_frag_fb_t *fbuf, + bool req_ack, unsigned page); + +/** + * @brief Adapts currently sent number of fragments to current window size + * + * Balances `fbuf->sfr.window` with `fbuf->sfr.congure->cwnd` + * + * @param[in] fbuf Fragmentation buffer to adapt window for + */ +static void _shrink_window(gnrc_sixlowpan_frag_fb_t *fbuf); /** * @brief Re-send a fragment @@ -235,8 +244,11 @@ static void _send_ack(gnrc_netif_t *netif, const uint8_t *dst, uint8_t dst_len, /** * @brief Schedule next frame (RFRAG or RFRAG-ACK) with * @ref GNRC_SIXLOWPAN_FRAG_SFR_INTER_FRAG_GAP_MSG + * + * @param[in] fbuf A fragmentation buffer holding the state of the datagram + * and recoverable fragments. */ -static void _sched_next_frame(void); +static void _sched_next_frame(gnrc_sixlowpan_frag_fb_t *fbuf); /** * @brief Schedule ARQ timeout @@ -297,13 +309,13 @@ static int _forward_rfrag(gnrc_pktsnip_t *pkt, _generic_rb_entry_t *entry, /* ====== PUBLIC FUNCTION DEFINITIONS ====== */ void gnrc_sixlowpan_frag_sfr_init(void) { - if (CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US > 0) { + if (gnrc_sixlowpan_frag_sfr_congure_snd_has_inter_frame_gap()) { 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); + clist_rpush(&_frag_descs_free, &_frag_descs_pool[i].super.super); } } @@ -329,6 +341,7 @@ void gnrc_sixlowpan_frag_sfr_send(gnrc_pktsnip_t *pkt, void *ctx, if (fbuf->offset == 0) { DEBUG("6lo sfr: sending first fragment\n"); + gnrc_sixlowpan_frag_sfr_congure_snd_setup(fbuf); res = _send_1st_fragment(netif, fbuf, page, &tx_sync); if (res == 0) { DEBUG("6lo sfr: error sending first fragment\n"); @@ -337,12 +350,17 @@ void gnrc_sixlowpan_frag_sfr_send(gnrc_pktsnip_t *pkt, void *ctx, 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"); + else if (!gnrc_sixlowpan_frag_sfr_congure_snd_in_cwnd(fbuf)) { + DEBUG("6lo sfr: frags_sent not within congestion window: " + "don't send more\n"); return; } else if (fbuf->offset < fbuf->datagram_size) { DEBUG("6lo sfr: sending subsequent fragment\n"); +#if IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) + assert(fbuf->sfr.congure); + assert(fbuf->sfr.congure->driver); +#endif res = _send_nth_fragment(netif, fbuf, page, &tx_sync); if (res == 0) { DEBUG("6lo sfr: error sending subsequent fragment (offset = %u)\n", @@ -358,15 +376,17 @@ void gnrc_sixlowpan_frag_sfr_send(gnrc_pktsnip_t *pkt, void *ctx, * the fragmentation buffer now) */ goto error; } + gnrc_sixlowpan_frag_sfr_congure_snd_report_frag_sent(fbuf); fbuf->offset += res; - if ((fbuf->sfr.frags_sent < fbuf->sfr.window_size) && + if (gnrc_sixlowpan_frag_sfr_congure_snd_in_cwnd(fbuf) && (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; + gnrc_sixlowpan_frag_sfr_congure_snd_report_frag_discard(fbuf); goto error; } /* check if last fragment sent requested an ACK */ @@ -395,7 +415,7 @@ void gnrc_sixlowpan_frag_sfr_send(gnrc_pktsnip_t *pkt, void *ctx, /* 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)) { + _send_abort_frag(fbuf->pkt, fbuf, true, 0)) { /* wait for ACK before fbuf is deleted */ _sched_abort_timeout(fbuf); } @@ -468,28 +488,48 @@ int gnrc_sixlowpan_frag_sfr_forward(gnrc_pktsnip_t *pkt, page); } +static int _report_non_ack_req_window_sent(clist_node_t *node, void *fbuf_ptr) +{ + _frag_desc_t *frag_desc = (_frag_desc_t *)node; + if (!_frag_ack_req(frag_desc)) { + gnrc_sixlowpan_frag_sfr_congure_snd_report_frag_sent(fbuf_ptr); + } + return 0; +} + 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 now = xtimer_now_usec() / US_PER_MS; + _frag_desc_t *frag_desc = (_frag_desc_t *)fbuf->sfr.window.next; 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; + if (IS_ACTIVE(CONFIG_GNRC_SIXLOWPAN_SFR_MOCK_ARQ_TIMER)) { + /* mock-up to emulate time having passed beyond (1us) the ARQ timeout */ + now -= (fbuf->sfr.arq_timeout * US_PER_MS) + 1; + } + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) && frag_desc) { + /* report timeout to CongURE state */ + gnrc_sixlowpan_frag_sfr_congure_snd_report_frags_timeout(fbuf); + _shrink_window(fbuf); /* potentially shrink window */ + /* reassign frag_desc, in case window head changed */ + frag_desc = (_frag_desc_t *)fbuf->sfr.window.next; + } /* copying clist_foreach because we can't work just in function context */ + _frag_desc_t * const head = frag_desc; 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)) { + frag_desc = (_frag_desc_t *)frag_desc->super.super.next; + diff = now - frag_desc->super.send_time; + if (diff < fbuf->sfr.arq_timeout) { /* this fragment's last was last sent < fbuf->sfr.arq_timeout * ago */ - uint32_t offset = fbuf->sfr.arq_timeout - (diff / US_PER_MS); + uint32_t offset = fbuf->sfr.arq_timeout - diff; DEBUG("6lo sfr: wait for fragment %u in next reschedule\n", _frag_seq(frag_desc)); @@ -507,24 +547,31 @@ void gnrc_sixlowpan_frag_sfr_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf) 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) { + if ((frag_desc->super.resends++) < 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_desc->super.resends - 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) { + if (_resend_frag(&frag_desc->super.super, fbuf) != 0) { /* _resend_frag failed due to a memory resource * problem */ 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++; + else { + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE)) { + /* fragment was resent successfully, report this to CongURE state + * object */ + gnrc_sixlowpan_frag_sfr_congure_snd_report_frag_sent(fbuf); + } + 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 */ @@ -555,6 +602,10 @@ void gnrc_sixlowpan_frag_sfr_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf) _frag_seq(frag_desc)); } } while (frag_desc != head); + /* report all non-ack_req fragments in window also as sent, since even + * the lost fragments are still in flight (even though they were + * previously marked as timed out) */ + clist_foreach(&fbuf->sfr.window, _report_non_ack_req_window_sent, fbuf); } else { /* No fragments to resend, we can assume the packet was delivered @@ -568,13 +619,13 @@ void gnrc_sixlowpan_frag_sfr_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf) } 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); + _send_abort_frag(fbuf->pkt, fbuf, false, 0); _clean_up_fbuf(fbuf, error_no); } -void gnrc_sixlowpan_frag_sfr_inter_frame_gap(void) +void gnrc_sixlowpan_frag_sfr_inter_frame_gap(gnrc_sixlowpan_frag_fb_t *fbuf) { - if (CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US > 0) { + if (gnrc_sixlowpan_frag_sfr_congure_snd_has_inter_frame_gap()) { _frame_queue_t *node = (_frame_queue_t *)clist_lpop(&_frame_queue); if (node != NULL) { @@ -585,7 +636,7 @@ void gnrc_sixlowpan_frag_sfr_inter_frame_gap(void) clist_rpush(&_frame_queue_free, &node->super); } if (clist_lpeek(&_frame_queue) != NULL) { - _sched_next_frame(); + _sched_next_frame(fbuf); } } } @@ -631,7 +682,7 @@ static void _clean_slate_datagram(gnrc_sixlowpan_frag_fb_t *fbuf) 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) { + if (gnrc_sixlowpan_frag_sfr_congure_snd_has_inter_frame_gap()) { for (clist_node_t *node = clist_lpop(&_frame_queue); node != NULL; node = clist_lpop(&_frame_queue)) { _frame_queue_t *entry = (_frame_queue_t *)node; @@ -652,7 +703,6 @@ static void _clean_slate_datagram(gnrc_sixlowpan_frag_fb_t *fbuf) 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); @@ -719,7 +769,9 @@ static gnrc_pktsnip_t *_build_frag_from_fbuf(gnrc_pktsnip_t *pkt, { 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), + /* we only can send the next fragment we build here, + * so request ACK for it */ + !gnrc_sixlowpan_frag_sfr_congure_snd_next_in_cwnd(fbuf), frag_size, fbuf->sfr.cur_seq); } @@ -807,14 +859,15 @@ static void _check_for_ecn(gnrc_pktsnip_t *frame) } } -static bool _send_frame(gnrc_pktsnip_t *frame, void *ctx, unsigned page) +static bool _send_frame(gnrc_pktsnip_t *frame, gnrc_sixlowpan_frag_fb_t *fbuf, + void *ctx, unsigned page) { uint32_t now; + uint32_t if_gap = gnrc_sixlowpan_frag_sfr_congure_snd_inter_frame_gap(fbuf); _check_for_ecn(frame); 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)) { + if ((if_gap == 0) || ((now - _last_frame_sent) > if_gap)) { DEBUG("6lo sfr: dispatch frame to network interface\n"); _last_frame_sent = now; gnrc_sixlowpan_dispatch_send(frame, ctx, page); @@ -831,7 +884,7 @@ static bool _send_frame(gnrc_pktsnip_t *frame, void *ctx, unsigned page) node->datagram_tag = hdr->tag; node->page = page; clist_rpush(&_frame_queue, &node->super); - _sched_next_frame(); + _sched_next_frame(fbuf); } return (node != NULL); } @@ -851,13 +904,14 @@ static bool _send_fragment(gnrc_pktsnip_t *frag, gnrc_sixlowpan_frag_fb_t *fbuf, } 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->super.size = 1; + frag_desc->super.resends = 0; + clist_rpush(&fbuf->sfr.window, &frag_desc->super.super); + if ((res = _send_frame(frag, fbuf, NULL, page))) { if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { _stats.fragments_sent.usual++; } - frag_desc->last_sent = _last_frame_sent; + frag_desc->super.send_time = _last_frame_sent / US_PER_MS; fbuf->sfr.cur_seq++; fbuf->sfr.frags_sent++; } @@ -1188,20 +1242,24 @@ 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; + int res = _resend_frag(node, fbuf_ptr); - if (((res = _resend_frag(node, fbuf_ptr)) == 0) && - IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { - _stats.fragment_resends.by_nack++; + if (res == 0) { + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { + _stats.fragment_resends.by_nack++; + } + gnrc_sixlowpan_frag_sfr_congure_snd_report_frag_sent(fbuf_ptr); } return res; } static void _check_failed_frags(sixlowpan_sfr_ack_t *ack, - gnrc_sixlowpan_frag_fb_t *fbuf) + gnrc_sixlowpan_frag_fb_t *fbuf, + uint32_t ack_recv_time) { _frag_desc_t *frag_desc; clist_node_t not_received = { .next = NULL }; + ztimer_now_t earliest_send = UINT32_MAX; DEBUG("6lo sfr: checking which fragments to resend for datagram %u\n", fbuf->tag); @@ -1211,34 +1269,59 @@ static void _check_failed_frags(sixlowpan_sfr_ack_t *ack, uint8_t seq; seq = _frag_seq(frag_desc); + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) && + (earliest_send > frag_desc->super.send_time)) { + earliest_send = frag_desc->super.send_time; + } 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); + clist_rpush(&_frag_descs_free, &frag_desc->super.super); + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE)) { + congure_snd_ack_t ack = { + .recv_time = ack_recv_time, + .id = seq, + .clean = 1U, + }; + gnrc_sixlowpan_frag_sfr_congure_snd_report_frag_acked( + fbuf, &frag_desc->super, &ack + ); + } } 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) { + if ((frag_desc->super.resends++) < CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES) { DEBUG("6lo sfr: %u retries left\n", CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES - - (frag_desc->retries - 1)); + (frag_desc->super.resends - 1)); /* put fragment in "not received" list */ - clist_rpush(¬_received, &frag_desc->super); + clist_rpush(¬_received, &frag_desc->super.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); + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) && + IS_ACTIVE(CONFIG_GNRC_SIXLOWPAN_SFR_USE_ECN) && + sixlowpan_sfr_ecn(&ack->base)) { + gnrc_sixlowpan_frag_sfr_congure_snd_report_ecn( + fbuf, earliest_send + ); + } + clist_rpush(&_frag_descs_free, &frag_desc->super.super); /* retry to resend whole datagram */ _retry_datagram(fbuf); return; } } } + if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE) && + sixlowpan_sfr_ecn(&ack->base)) { + gnrc_sixlowpan_frag_sfr_congure_snd_report_ecn(fbuf, earliest_send); + } /* all fragments were received of the current window were received and * the datagram was transmitted completely */ if ((clist_lpeek(¬_received) == NULL) && @@ -1248,7 +1331,12 @@ static void _check_failed_frags(sixlowpan_sfr_ack_t *ack, } /* at least one fragment was not received */ else { + gnrc_sixlowpan_frag_sfr_congure_snd_report_frags_lost( + fbuf, + (congure_snd_msg_t *)¬_received + ); fbuf->sfr.window = not_received; + _shrink_window(fbuf); assert(fbuf->sfr.frags_sent == clist_count(&fbuf->sfr.window)); /* use _resend_failed_frag here instead of loop above, so * _resend_frag can know if the fragment is the last in the window by @@ -1257,7 +1345,7 @@ static void _check_failed_frags(sixlowpan_sfr_ack_t *ack, /* 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)) { + if (_send_abort_frag(fbuf->pkt, fbuf, true, 0)) { /* wait for ACK before fbuf is deleted */ _sched_abort_timeout(fbuf); } @@ -1268,7 +1356,7 @@ static void _check_failed_frags(sixlowpan_sfr_ack_t *ack, _clean_up_fbuf(fbuf, ENOMEM); } } - if ((fbuf->sfr.frags_sent < fbuf->sfr.window_size) && + if (gnrc_sixlowpan_frag_sfr_congure_snd_in_cwnd(fbuf) && (fbuf->offset < fbuf->datagram_size)) { DEBUG("6lo sfr: trigger send of further fragments of datagram %u\n", fbuf->tag); @@ -1311,6 +1399,7 @@ 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_sixlowpan_frag_sfr_congure_snd_destroy(fbuf); gnrc_pktbuf_release_error(fbuf->pkt, error); fbuf->pkt = NULL; } @@ -1347,7 +1436,6 @@ static uint16_t _send_1st_fragment(gnrc_netif_t *netif, 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) { @@ -1425,15 +1513,16 @@ static uint16_t _send_nth_fragment(gnrc_netif_t *netif, return local_offset; } -static bool _send_abort_frag(gnrc_pktsnip_t *pkt, uint8_t tag, bool req_ack, - unsigned page) +static bool _send_abort_frag(gnrc_pktsnip_t *pkt, + gnrc_sixlowpan_frag_fb_t *fbuf, + bool req_ack, unsigned page) { gnrc_pktsnip_t *frag; - frag = _build_frag_pkt(pkt->data, tag, req_ack, 0, 0); + frag = _build_frag_pkt(pkt->data, fbuf->tag, req_ack, 0, 0); if (frag != NULL) { sixlowpan_sfr_rfrag_set_offset(frag->next->data, 0); - _send_frame(frag, NULL, page); + _send_frame(frag, fbuf, NULL, page); if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { _stats.fragments_sent.aborts++; } @@ -1442,6 +1531,37 @@ static bool _send_abort_frag(gnrc_pktsnip_t *pkt, uint8_t tag, bool req_ack, return false; } +static void _shrink_window(gnrc_sixlowpan_frag_fb_t *fbuf) +{ + if (!IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_CONGURE)) { + /* window does not shrink without congure */ + return; + } + if (!gnrc_sixlowpan_frag_sfr_congure_snd_in_cwnd(fbuf)) { + /* we are beyond the congestion window, so shrink it to the new size */ + clist_node_t new_window = { .next = NULL }; + _frag_desc_t *last; + + fbuf->sfr.frags_sent = 0; /* temporarily reset fragments sent to count them again*/ + /* move all fragments within congestion window into new, temporary list */ + while (gnrc_sixlowpan_frag_sfr_congure_snd_in_cwnd(fbuf)) { + clist_rpush(&new_window, clist_lpop(&fbuf->sfr.window)); + fbuf->sfr.frags_sent++; + } + /* free all remaining fragments from old congestion window that did not fit into + * the shrunk window */ + for (clist_node_t *node = clist_lpop(&fbuf->sfr.window); + node != NULL; node = clist_lpop(&fbuf->sfr.window)) { + clist_rpush(&_frag_descs_free, node); + } + /* the temporary list is now the new, shrunk window */ + fbuf->sfr.window.next = new_window.next; + /* recalculate offset for fragmentation header field */ + last = (_frag_desc_t *)clist_rpeek(&fbuf->sfr.window); + fbuf->offset = last->offset + _frag_size(last); + } +} + static int _resend_frag(clist_node_t *node, void *fbuf_ptr) { _frag_desc_t *frag_desc = (_frag_desc_t *)node; @@ -1459,7 +1579,7 @@ static int _resend_frag(clist_node_t *node, void *fbuf_ptr) } hdr = frag->next->data; /* is last fragment in window */ - if (((fbuf->sfr.frags_sent >= fbuf->sfr.window_size) || + if ((!gnrc_sixlowpan_frag_sfr_congure_snd_in_cwnd(fbuf) || (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); @@ -1480,14 +1600,14 @@ static int _resend_frag(clist_node_t *node, void *fbuf_ptr) /* copy remaining packet snips */ _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, + "frag_size: %u, %s: %u)\n", frag_desc->super.resends, 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; + if (_send_frame(frag, fbuf, NULL, 0)) { + frag_desc->super.send_time = _last_frame_sent / US_PER_MS; return 0; } else { @@ -1547,43 +1667,53 @@ static void _send_ack(gnrc_netif_t *netif, const uint8_t *dst, uint8_t dst_len, 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); + _send_frame(ack, NULL, NULL, 0); } else { DEBUG("6lo sfr: unable to build ACK for sending\n"); } } -static void _sched_next_frame(void) +static void _sched_next_frame(gnrc_sixlowpan_frag_fb_t *fbuf) { - if (CONFIG_GNRC_SIXLOWPAN_SFR_INTER_FRAME_GAP_US > 0) { - int state = irq_disable(); /* make timer check atomic */ - bool already_set = xtimer_is_set(&_if_gap_timer); - - 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"); - } + if (!gnrc_sixlowpan_frag_sfr_congure_snd_has_inter_frame_gap()) { + return; + } + int state = irq_disable(); /* make timer check atomic */ + bool already_set = xtimer_is_set(&_if_gap_timer); + + irq_restore(state); + if (already_set) { + DEBUG("6lo sfr: inter-frame timer was already set\n"); + return; + } + uint32_t last_sent_since = (_last_frame_sent - xtimer_now_usec()); + uint32_t if_gap = gnrc_sixlowpan_frag_sfr_congure_snd_inter_frame_gap(fbuf); + + if (last_sent_since <= if_gap) { + uint32_t offset = if_gap - last_sent_since; + DEBUG("6lo sfr: arming inter-frame timer in %" PRIu32 " us\n", + last_sent_since); + _if_gap_msg.content.ptr = fbuf; + xtimer_set_msg(&_if_gap_timer, offset, &_if_gap_msg, _getpid()); + } + else { + DEBUG("6lo sfr: send frame immediately\n"); + /* there is no risk of infinite recursion due to the call of `_sched_next_frame` since + * we only get here when (_last_frame_sent - now) > if_gap. + * Since gnrc_sixlowpan_frag_sfr_inter_frame_gap updates _last_frame_sent when the list is + * empty and only calls _sched_next_frame() when the list is still not empty after that this + * can not be the case if we came from there (except for misconfigured if_gap). */ + gnrc_sixlowpan_frag_sfr_inter_frame_gap(fbuf); } } static void _sched_arq_timeout(gnrc_sixlowpan_frag_fb_t *fbuf, uint32_t offset) { + if (IS_ACTIVE(CONFIG_GNRC_SIXLOWPAN_SFR_MOCK_ARQ_TIMER)) { + /* mock does not need to be scheduled */ + return; + } 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); @@ -1626,6 +1756,7 @@ static void _handle_ack(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, { gnrc_sixlowpan_frag_vrb_t *vrbe; sixlowpan_sfr_ack_t *hdr = pkt->data; + uint32_t recv_time = xtimer_now_usec(); (void)page; DEBUG("6lo sfr: received ACK for datagram (%s, %02x): %02X%02X%02X%02X\n", @@ -1653,7 +1784,7 @@ static void _handle_ack(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, 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() - + vrbe->super.arrival = recv_time - (CONFIG_GNRC_SIXLOWPAN_FRAG_VRB_TIMEOUT_US - CONFIG_GNRC_SIXLOWPAN_FRAG_RBUF_DEL_TIMER); } @@ -1662,7 +1793,7 @@ static void _handle_ack(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, } } else { - vrbe->super.arrival = xtimer_now_usec(); + vrbe->super.arrival = recv_time; } } else { @@ -1685,7 +1816,7 @@ static void _handle_ack(gnrc_netif_hdr_t *netif_hdr, gnrc_pktsnip_t *pkt, else { /* Check and resent failed fragments within the current window */ - _check_failed_frags(hdr, fbuf); + _check_failed_frags(hdr, fbuf, recv_time / US_PER_MS); } } else { @@ -1732,7 +1863,7 @@ static int _forward_rfrag(gnrc_pktsnip_t *pkt, _generic_rb_entry_t *entry, 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); + _send_frame(new, NULL, NULL, page); if (IS_USED(MODULE_GNRC_SIXLOWPAN_FRAG_SFR_STATS)) { _stats.fragments_sent.forwarded++; } diff --git a/sys/net/gnrc/network_layer/sixlowpan/gnrc_sixlowpan.c b/sys/net/gnrc/network_layer/sixlowpan/gnrc_sixlowpan.c index 1592bf9a4c42..6083f5adf18f 100644 --- a/sys/net/gnrc/network_layer/sixlowpan/gnrc_sixlowpan.c +++ b/sys/net/gnrc/network_layer/sixlowpan/gnrc_sixlowpan.c @@ -443,7 +443,7 @@ static void *_event_loop(void *args) 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(); + gnrc_sixlowpan_frag_sfr_inter_frame_gap(msg.content.ptr); break; #endif diff --git a/tests/gnrc_sixlowpan_frag_sfr_congure/Makefile b/tests/gnrc_sixlowpan_frag_sfr_congure/Makefile new file mode 100644 index 000000000000..eb5c1777d5e7 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure/Makefile @@ -0,0 +1,43 @@ +include ../Makefile.tests_common + +USEMODULE += congure_mock +USEMODULE += gnrc_ipv6_router_default +USEMODULE += gnrc_sixlowpan_frag_sfr +USEMODULE += gnrc_sixlowpan_frag_sfr_congure +USEMODULE += gnrc_sixlowpan_iphc +USEMODULE += gnrc_ipv6_nib +USEMODULE += gnrc_netif +USEMODULE += embunit +USEMODULE += netdev_ieee802154 +USEMODULE += netdev_test +USEMODULE += ztimer_msec ztimer_usec + +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 +ifndef CONFIG_GNRC_SIXLOWPAN_SFR_MOCK_ARQ_TIMER + # mock ARQ timer + CFLAGS += -DCONFIG_GNRC_SIXLOWPAN_SFR_MOCK_ARQ_TIMER=1U +endif diff --git a/tests/gnrc_sixlowpan_frag_sfr_congure/Makefile.ci b/tests/gnrc_sixlowpan_frag_sfr_congure/Makefile.ci new file mode 100644 index 000000000000..dc5ee39e2fcb --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure/Makefile.ci @@ -0,0 +1,51 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-mega2560 \ + arduino-nano \ + arduino-uno \ + atmega1284p \ + atmega328p \ + atmega328p-xplained-mini \ + atxmega-a1u-xpro \ + atxmega-a3bu-xplained \ + blackpill \ + blackpill-128kib \ + bluepill \ + bluepill-128kib \ + bluepill-stm32f030c8 \ + 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 \ + slstk3400a \ + stk3200 \ + stm32f030f4-demo \ + stm32f0discovery \ + stm32g0316-disco \ + stm32l0538-disco \ + stm32mp157c-dk2 \ + telosb \ + waspmote-pro \ + z1 \ + zigduino \ + # diff --git a/tests/gnrc_sixlowpan_frag_sfr_congure/app.config b/tests/gnrc_sixlowpan_frag_sfr_congure/app.config new file mode 100644 index 000000000000..9b940e371f50 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure/app.config @@ -0,0 +1,8 @@ +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_MOCK_ARQ_TIMER=y diff --git a/tests/gnrc_sixlowpan_frag_sfr_congure/common.h b/tests/gnrc_sixlowpan_frag_sfr_congure/common.h new file mode 100644 index 000000000000..1c6bd3031bb7 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure/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_congure/main.c b/tests/gnrc_sixlowpan_frag_sfr_congure/main.c new file mode 100644 index 000000000000..a3cd545d431c --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure/main.c @@ -0,0 +1,1074 @@ +/* + * Copyright (C) 2019-2021 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 Selective Fragment Recovery with Congestion + * Control + * + * @author Martine S. Lenders + * + * @} + */ + +#include +#include + +#include "cib.h" +#include "common.h" +#include "congure/mock.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/config.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 REM_L2 { _LL0, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7 + 1 } + +#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_PAYLOAD_SIZE (35U) + +enum { + TEST_SEND_FRAG_SEQ1 = 0, + TEST_SEND_FRAG_SEQ2, + TEST_SEND_FRAG_SEQ3, +}; + +#define TEST_NULL_ACK ((intptr_t)0x0) +#define TEST_FULL_ACK ((intptr_t)0xff) + +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: Global address generated from LOC_L2 */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7, + /* Destination: Global address generated from REM_L2 */ + 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[] = { + 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: Global address generated from LOC_L2 */ + 0x20, 0x01, 0x0d, 0xb8, 0xd3, 0x35, 0x91, 0x7e, + _LL0 ^ 0x2, _LL1, _LL2, _LL3, _LL4, _LL5, _LL6, _LL7, + /* (uncompressed) Destination: Global address generated from REM_L2 */ + 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 _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 = IEEE802154_LONG_ADDRESS_LEN, + .tag = TEST_FRAG_TAG, + .datagram_size = 1232U, + .current_size = 0U, + }; +static congure_mock_snd_t _sfr_congure_mocks[CONFIG_GNRC_SIXLOWPAN_FRAG_FB_SIZE]; +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; +unsigned _in_flight_frags; + +static void _congure_init(congure_snd_t *cong, void *ctx); +static void _congure_report_msg_sent(congure_snd_t *cong, unsigned msg_size); +static void _congure_report_msg_discarded(congure_snd_t *cong, + unsigned msg_size); +static void _congure_report_msgs_timeout_lost(congure_snd_t *cong, + congure_snd_msg_t *msgs); +static void _congure_report_msg_acked(congure_snd_t *cong, + congure_snd_msg_t *msg, + congure_snd_ack_t *ack); +static congure_mock_snd_t *_get_congure_by_fb(const gnrc_sixlowpan_frag_fb_t *fb); +static gnrc_pktsnip_t *_create_recv_ack(const void *ack_data, + size_t ack_size); +static gnrc_pktsnip_t *_create_send_datagram(bool compressed, bool payload); +static size_t _wait_for_packet(size_t exp_size); +static void _check_send_frag1(size_t mhr_len, bool ack_req, bool *finished); +static void _check_send_frag2(size_t mhr_len, bool ack_req, bool *finished); +static void _check_send_frag3(size_t mhr_len, bool ack_req, bool *finished); +static void _check_congure_snd_msg(congure_snd_msg_t *msg); +static int _mock_netdev_send(netdev_t *dev, const iolist_t *iolist); + +static const congure_snd_driver_t _congure_test_driver = { + .init = _congure_init, + .report_msg_sent = _congure_report_msg_sent, + .report_msg_discarded = _congure_report_msg_discarded, + .report_msgs_timeout = _congure_report_msgs_timeout_lost, + .report_msgs_lost = _congure_report_msgs_timeout_lost, + .report_msg_acked = _congure_report_msg_acked, +}; + +congure_snd_t *gnrc_sixlowpan_frag_sfr_congure_snd_get(void) +{ + for (unsigned i = 0; i < ARRAY_SIZE(_sfr_congure_mocks); i++) { + if (_sfr_congure_mocks[i].super.driver == NULL) { + congure_mock_snd_setup(&_sfr_congure_mocks[i], + &_congure_test_driver); + return &_sfr_congure_mocks[i].super; + } + } + return NULL; +} + +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)); + memset(_sfr_congure_mocks, 0, sizeof(_sfr_congure_mocks)); + _in_flight_frags = 0U; + 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 int _check_congure_snd_msgs_list(clist_node_t *node, void *arg) +{ + (void)arg; + _check_congure_snd_msg((congure_snd_msg_t *)node); + return 0; +} + +static void _test_send_frag(unsigned frag_seq, const uint8_t acked_frags, + const uint8_t ack_req, uint8_t *tag, + uint8_t *res_bitmap, bool *finished) +{ + static void (*const _check_send_func[])(size_t, bool, bool *) = { + _check_send_frag1, + _check_send_frag2, + _check_send_frag3, + }; + static const size_t exp_size[] = { + sizeof(_test_send_frag1), + sizeof(_test_send_frag2), + sizeof(_test_send_frag3), + }; + sixlowpan_sfr_rfrag_t *hdr; + bool send_func_finished; + size_t mhr_len; + + *finished = false; + TEST_ASSERT((mhr_len = _wait_for_packet(exp_size[frag_seq]))); + /* tags are generated by the stack so don't check */ + _check_send_func[frag_seq](mhr_len, ack_req & (1 << frag_seq), + &send_func_finished); + TEST_ASSERT(send_func_finished); + hdr = (sixlowpan_sfr_rfrag_t *)&_target_buf[mhr_len]; + if (frag_seq == 0) { + *tag = hdr->base.tag; + } + else { + TEST_ASSERT_EQUAL_INT(*tag, hdr->base.tag); + } + if (res_bitmap && (acked_frags & (1 << frag_seq))) { + /* simulate successful reception for this fragment */ + bf_set(res_bitmap, sixlowpan_sfr_rfrag_get_seq(hdr)); + } + _target_buf_len = 0; + *finished = true; +} + +static void _test_initial_send(const uint8_t acked_frags, const uint8_t ack_req, + uint8_t *tag, uint8_t *res_bitmap, + bool *finished) +{ + gnrc_pktsnip_t *pkt; + + *finished = false; + /* 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 send for fragments 1 to 3 */ + for (unsigned frag_seq = TEST_SEND_FRAG_SEQ1; + frag_seq <= TEST_SEND_FRAG_SEQ3; + frag_seq++) { + bool _frag_was_sent_correctly; + _test_send_frag(frag_seq, acked_frags, ack_req, tag, res_bitmap, + &_frag_was_sent_correctly); + TEST_ASSERT(_frag_was_sent_correctly); + } + *finished = true; +} + +static void _recv_ack(const uint8_t *ack_bitmap, uint8_t tag, bool ecn, + bool *finished) +{ + gnrc_pktsnip_t *pkt; + sixlowpan_sfr_ack_t *ack_hdr; + + *finished = false; + TEST_ASSERT_NOT_NULL( + (pkt = _create_recv_ack(_test_ack, sizeof(_test_ack))) + ); + ack_hdr = pkt->data; + ack_hdr->base.tag = tag; + if (ecn) { + sixlowpan_sfr_set_ecn(&ack_hdr->base); + } + switch ((intptr_t)ack_bitmap) { + case TEST_NULL_ACK: + case TEST_FULL_ACK: + memset(ack_hdr->bitmap, (intptr_t)ack_bitmap, sizeof(ack_hdr->bitmap)); + break; + default: + memcpy(ack_hdr->bitmap, ack_bitmap, sizeof(ack_hdr->bitmap)); + break; + } + TEST_ASSERT(0 < gnrc_netapi_dispatch_receive(GNRC_NETTYPE_SIXLOWPAN, + GNRC_NETREG_DEMUX_CTX_ALL, + pkt)); + *finished = true; +} + +#define TEST_ASSERT_FBUF_CONGURE_EXISTS(fbuf, cong) \ + TEST_ASSERT_NOT_NULL((fbuf = gnrc_sixlowpan_frag_fb_get_by_tag(tag))); \ + cong = _get_congure_by_fb(fbuf); \ + TEST_ASSERT(&cong->super == fbuf->sfr.congure) + +#define TEST_ASSERT_FBUF_CONGURE_DESTROYED(fbuf, cong) \ + TEST_ASSERT_NULL(gnrc_sixlowpan_frag_fb_get_by_tag(tag)); \ + TEST_ASSERT_NULL(fbuf->sfr.congure); \ + TEST_ASSERT_NULL(c->super.driver) + +#define TEST_ASSERT_REPORT_MSG_SENT(cong, num) \ + TEST_ASSERT_EQUAL_INT(num, (cong)->report_msg_sent_calls); \ + TEST_ASSERT(&(cong)->super == (cong)->report_msg_sent_args.c); \ + TEST_ASSERT_EQUAL_INT(1U, (cong)->report_msg_sent_args.msg_size) + +#define TEST_ASSERT_REPORT_MSG_DISCARDED(cong, num) \ + TEST_ASSERT_EQUAL_INT(num, (cong)->report_msg_discarded_calls); \ + TEST_ASSERT(&(cong)->super == (cong)->report_msg_discarded_args.c); \ + TEST_ASSERT_EQUAL_INT(1, (cong)->report_msg_discarded_args.msg_size) + +#define TEST_ASSERT_REPORT_MSGS_LOST_TIMEOUT(cong, num, m, method) \ + TEST_ASSERT_EQUAL_INT(num, (cong)->method ## _calls); \ + TEST_ASSERT(&(cong)->super == (cong)->method ## _args.c); \ + m = (cong)->method ## _args.msgs; \ + TEST_ASSERT_NOT_NULL(m) + +#define TEST_ASSERT_REPORT_MSGS_LOST(cong, num, m) \ + TEST_ASSERT_REPORT_MSGS_LOST_TIMEOUT(cong, num, m, report_msgs_lost) + +#define TEST_ASSERT_REPORT_MSGS_TIMEOUT(cong, num, m) \ + TEST_ASSERT_REPORT_MSGS_LOST_TIMEOUT(cong, num, m, report_msgs_timeout) + +#define TEST_ASSERT_REPORT_MSG_ACKED(cong, num) \ + TEST_ASSERT_EQUAL_INT(num, (cong)->report_msg_acked_calls); \ + TEST_ASSERT(&(cong)->super == (cong)->report_msg_acked_args.c); \ + TEST_ASSERT_NOT_NULL((cong)->report_msg_acked_args.msg); \ + TEST_ASSERT_NOT_NULL((cong)->report_msg_acked_args.ack) + +static void test_sixlo_send__no_ack(void) +{ + gnrc_sixlowpan_frag_fb_t *fbuf; + congure_mock_snd_t *c = NULL; + congure_snd_msg_t *msgs = NULL; + bool sent_frags_correct; + uint8_t tag; + static const uint8_t ack_req = 1 << TEST_SEND_FRAG_SEQ3; + + _test_initial_send(0, ack_req, &tag, NULL, &sent_frags_correct); + TEST_ASSERT(sent_frags_correct); + for (unsigned i = 0; i < CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES; i++) { + TEST_ASSERT_FBUF_CONGURE_EXISTS(fbuf, c); + /* three fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(3U, clist_count(&fbuf->sfr.window)); + /* report_msg_sent has been called 3 * (i + 1) times: + * - 3 for each initial send of the fragments + * - 3 each retry (while only fragment 3 is sent, 1 and 2 are also + * marked as in flight) + */ + TEST_ASSERT_REPORT_MSG_SENT(c, 3U * (i + 1)); + gnrc_sixlowpan_frag_sfr_arq_timeout(fbuf); + /* resend of fragment 3 */ + _test_send_frag(TEST_SEND_FRAG_SEQ3, 0, ack_req, &tag, + NULL, &sent_frags_correct); + TEST_ASSERT(sent_frags_correct); + /* report_msg_timeout was called i + 1 times; once for each ARQ + * timeout */ + TEST_ASSERT_REPORT_MSGS_TIMEOUT(c, i + 1U, msgs); + /* there is one message for each fragment */ + TEST_ASSERT_EQUAL_INT(3U, clist_count(&msgs->super)); + clist_foreach(&msgs->super, _check_congure_snd_msgs_list, NULL); + /* first fragment has no resends */ + TEST_ASSERT_EQUAL_INT( + 0, ((congure_snd_msg_t *)msgs->super.next->next)->resends + ); + /* second fragment has no resends */ + TEST_ASSERT_EQUAL_INT( + 0, ((congure_snd_msg_t *)msgs->super.next->next->next)->resends + ); + /* third fragment has i + 1 resends */ + TEST_ASSERT_EQUAL_INT( + i + 1, + ((congure_snd_msg_t *)msgs->super.next->next->next->next)->resends + ); + /* there are three fragments in flight */ + TEST_ASSERT_EQUAL_INT(3U, _in_flight_frags); + } + gnrc_sixlowpan_frag_sfr_arq_timeout(fbuf); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag3))); + /* fragmentation buffer and congure state should have been destroyed and + * freed after CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES retries */ + TEST_ASSERT_FBUF_CONGURE_DESTROYED(fbuf, c); + /* report_msg_sent has been called 3 * (RETRIES + 1) times: + * - 3 for each initial send of the fragments + * - 3 each retry (while only fragment 3 is sent, 1 and 2 are marked as + * in flight) + */ + TEST_ASSERT_REPORT_MSG_SENT( + c, 3U * (CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES + 1) + ); + /* report_msg_timeout was called CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES + 1 + * times; once for each ARQ timeout */ + TEST_ASSERT_REPORT_MSGS_TIMEOUT(c, + CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES + 1U, + msgs); + /* inter_msg_interval was called several times to calculate inter-frame gap + * TODO */ + TEST_ASSERT(0 < c->inter_msg_interval_calls); + /* none of the other CongURE report methods should have been called */ + TEST_ASSERT_EQUAL_INT(0, c->report_msg_discarded_calls); + TEST_ASSERT_EQUAL_INT(0, c->report_msgs_lost_calls); + TEST_ASSERT_EQUAL_INT(0, c->report_msg_acked_calls); + TEST_ASSERT_EQUAL_INT(0, c->report_ecn_ce_calls); + /* no fragments reported in flight anymore */ + TEST_ASSERT_EQUAL_INT(0, _in_flight_frags); + 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; + congure_mock_snd_t *c; + congure_snd_msg_t *msgs = NULL; + BITFIELD(ack_bitmap, 32U) = { 0 }; + bool sent_frags_correct, ack_received; + uint8_t tag; + static const uint8_t ack_req = 1 << TEST_SEND_FRAG_SEQ3; + static const uint8_t acked_frags = 1 << TEST_SEND_FRAG_SEQ1; + + _test_initial_send(acked_frags, ack_req, &tag, ack_bitmap, + &sent_frags_correct); + TEST_ASSERT(sent_frags_correct); + TEST_ASSERT_FBUF_CONGURE_EXISTS(fbuf, c); + /* three fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(3U, clist_count(&fbuf->sfr.window)); + _recv_ack(ack_bitmap, tag, false, &ack_received); + TEST_ASSERT(ack_received); + /* resend of fragment 2 (fragment 1 is ACK'd so not resent) */ + _test_send_frag(TEST_SEND_FRAG_SEQ2, acked_frags, ack_req, &tag, ack_bitmap, + &sent_frags_correct); + TEST_ASSERT(sent_frags_correct); + /* resend of fragment 3 */ + _test_send_frag(TEST_SEND_FRAG_SEQ3, 0, ack_req, &tag, + NULL, &sent_frags_correct); + TEST_ASSERT(sent_frags_correct); + TEST_ASSERT_FBUF_CONGURE_EXISTS(fbuf, c); + /* two fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&fbuf->sfr.window)); + /* fragments 2 and 3 were reported lost (in one call) */ + TEST_ASSERT_REPORT_MSGS_LOST(c, 1U, msgs); + /* msgs is `not_received` in SFR code which is allocated on stack, + * so we can't safely check the contents */ + /* fragment 1 was ACK'd */ + TEST_ASSERT_REPORT_MSG_ACKED(c, 1U); + /* report_msg_sent has been called 5 times: + * - 3 for each initial send of the fragments + * - 2 for resends of fragments 2 and 3 which were marked lost in the ACK + */ + TEST_ASSERT_REPORT_MSG_SENT(c, 5U); + TEST_ASSERT_FBUF_CONGURE_EXISTS(fbuf, c); + /* Due to the ACK, we only expect RETRIES - 1 retries so start iteration + * with 1 */ + for (unsigned i = 1U; i < CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES; i++) { + gnrc_sixlowpan_frag_sfr_arq_timeout(fbuf); + /* resend of fragment 3 */ + _test_send_frag(TEST_SEND_FRAG_SEQ3, 0, ack_req, &tag, + NULL, &sent_frags_correct); + TEST_ASSERT(sent_frags_correct); + TEST_ASSERT_FBUF_CONGURE_EXISTS(fbuf, c); + /* two fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&fbuf->sfr.window)); + /* nothing should have changed for report_msgs_lost */ + TEST_ASSERT_REPORT_MSGS_LOST(c, 1U, msgs); + /* nothing should have changed for report_msg_acked */ + TEST_ASSERT_REPORT_MSG_ACKED(c, 1U); + /* report_msg_sent has been called 5 + (2 * i) times: + * - 3 for each initial send of the fragments + * - 2 for resends of fragments 2 and 3 which were marked lost in the ACK + * - 3 each retry (while only fragment 3 is sent, 2 is also marked as + * in flight) + */ + TEST_ASSERT_REPORT_MSG_SENT(c, 5U + (2U * i)); + /* report_msg_timeout was called i times; once for each ARQ + * timeout */ + TEST_ASSERT_REPORT_MSGS_TIMEOUT(c, i, msgs); + /* fbuf and thus msgs will be deleted in second to last iteration due to + * fragment 3 having been resent too many times already. */ + if (i <= (CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES - 1)) { + /* there is one message for each fragment 2 and 3 */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&msgs->super)); + clist_foreach(&msgs->super, _check_congure_snd_msgs_list, NULL); + /* second fragment has 1 resends (due to the ACK) */ + TEST_ASSERT_EQUAL_INT( + 1, ((congure_snd_msg_t *)msgs->super.next->next)->resends + ); + /* third fragment has i + 1 resends (one due to the ACK, one due to + * the timeout) */ + TEST_ASSERT_EQUAL_INT( + i + 1, + ((congure_snd_msg_t *)msgs->super.next->next->next)->resends + ); + } + } + /* generate final timeout */ + gnrc_sixlowpan_frag_sfr_arq_timeout(fbuf); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag3))); + /* nothing should have changed for report_msgs_lost */ + TEST_ASSERT_REPORT_MSGS_LOST(c, 1U, msgs); + /* nothing should have changed for report_msg_acked */ + TEST_ASSERT_REPORT_MSG_ACKED(c, 1U); + /* report_msg_timeout was called CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES + * times; once for each ARQ timeout */ + TEST_ASSERT_REPORT_MSGS_TIMEOUT( + c, CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES, msgs + ); + /* report_msg_sent has been called 3 + (2 * RETRIES) times: + * - 3 for each initial send of the fragments + * - 2 each retry due to ACK or timeout (1 was ACK'd so is not sent anymore + * and while only fragment 3 is sent, 2 is marked as in flight) + */ + TEST_ASSERT_REPORT_MSG_SENT( + c, 3U + (2U * CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES) + ); + /* fragmentation buffer and congure state should have been destroyed and + * freed after CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES retries */ + TEST_ASSERT_FBUF_CONGURE_DESTROYED(fbuf, c); + /* inter_msg_interval was called several times to calculate inter-frame gap + */ + TEST_ASSERT(0 < c->inter_msg_interval_calls); + /* none of the other CongURE report methods should have been called */ + TEST_ASSERT_EQUAL_INT(0, c->report_msg_discarded_calls); + TEST_ASSERT_EQUAL_INT(0, c->report_ecn_ce_calls); + /* no fragments reported in flight anymore */ + TEST_ASSERT_EQUAL_INT(0, _in_flight_frags); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_send__last_ackd_ecn(void) +{ + gnrc_sixlowpan_frag_fb_t *fbuf; + congure_mock_snd_t *c; + congure_snd_msg_t *msgs = NULL; + BITFIELD(ack_bitmap, 32U) = { 0 }; + uint32_t pre_send_time; + bool sent_frags_correct, ack_received; + uint8_t tag; + static const uint8_t ack_req = 1 << TEST_SEND_FRAG_SEQ3; + static const uint8_t acked_frags = 1 << TEST_SEND_FRAG_SEQ3; + + pre_send_time = xtimer_now_usec() / US_PER_MS; + _test_initial_send(acked_frags, ack_req, &tag, ack_bitmap, + &sent_frags_correct); + TEST_ASSERT(sent_frags_correct); + TEST_ASSERT_FBUF_CONGURE_EXISTS(fbuf, c); + TEST_ASSERT_EQUAL_INT(3U, clist_count(&fbuf->sfr.window)); + _recv_ack(ack_bitmap, tag, true, &ack_received); + TEST_ASSERT(ack_received); + /* resend of fragment 1 */ + _test_send_frag(TEST_SEND_FRAG_SEQ1, acked_frags, ack_req, &tag, ack_bitmap, + &sent_frags_correct); + TEST_ASSERT(sent_frags_correct); + /* resend of fragment 2 (which now requests an ACK) */ + _test_send_frag(TEST_SEND_FRAG_SEQ2, 0, 1 << TEST_SEND_FRAG_SEQ2, &tag, + NULL, &sent_frags_correct); + TEST_ASSERT(sent_frags_correct); + /* (fragment 3 is ACK'd so not resent) */ + TEST_ASSERT_FBUF_CONGURE_EXISTS(fbuf, c); + /* two fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&fbuf->sfr.window)); + /* fragments 1 and 2 were reported lost (in one call) */ + TEST_ASSERT_REPORT_MSGS_LOST(c, 1U, msgs); + /* msgs is `not_received` in SFR code which is allocated on stack, + * so we can't safely check the contents */ + /* fragment 3 was ACK'd */ + TEST_ASSERT_REPORT_MSG_ACKED(c, 1U); + /* report_ecn_ce was called with a timestamp after the time we sent the + * original message */ + TEST_ASSERT_EQUAL_INT(1U, c->report_ecn_ce_calls); + TEST_ASSERT((xtimer_now_usec() / US_PER_MS) >= pre_send_time); + TEST_ASSERT(c->report_ecn_ce_args.time >= pre_send_time); + /* report_msg_sent has been called 5 times: + * - 3 for each initial send of the fragments + * - 2 for resends of fragments 2 and 3 which were marked lost in the ACK + */ + TEST_ASSERT_REPORT_MSG_SENT(c, 5U); + /* Due to the ACK, we only expect RETRIES - 1 retries so start iteration + * with 1 */ + for (unsigned i = 1U; i < CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES; i++) { + gnrc_sixlowpan_frag_sfr_arq_timeout(fbuf); + /* resend of fragment 2 */ + _test_send_frag(TEST_SEND_FRAG_SEQ2, 0, 1 << TEST_SEND_FRAG_SEQ2, &tag, + NULL, &sent_frags_correct); + TEST_ASSERT(sent_frags_correct); + TEST_ASSERT_FBUF_CONGURE_EXISTS(fbuf, c); + /* two fragments wait for potential re-sending */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&fbuf->sfr.window)); + /* nothing should have changed for report_msgs_lost */ + TEST_ASSERT_REPORT_MSGS_LOST(c, 1U, msgs); + /* nothing should have changed for report_msg_acked */ + TEST_ASSERT_REPORT_MSG_ACKED(c, 1U); + /* report_msg_sent has been called 5 + (2 * i) times: + * - 3 for each initial send of the fragments + * - 2 for resends of fragments 1 and 2 which were marked lost in the ACK + * - 3 each retry (while only fragment 3 is sent, 2 is also marked as + * in flight) + */ + TEST_ASSERT_REPORT_MSG_SENT(c, 5U + (2U * i)); + /* report_msg_timeout was called i times; once for each ARQ + * timeout */ + TEST_ASSERT_REPORT_MSGS_TIMEOUT(c, i, msgs); + /* fbuf and thus msgs will be deleted in second to last iteration due to + * fragment 3 having been resent too many times already. */ + if (i <= (CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES - 1)) { + /* there is one message for each fragment 2 and 3 */ + TEST_ASSERT_EQUAL_INT(2U, clist_count(&msgs->super)); + clist_foreach(&msgs->super, _check_congure_snd_msgs_list, NULL); + /* second fragment has 1 resends (due to the ACK) */ + TEST_ASSERT_EQUAL_INT( + 1, ((congure_snd_msg_t *)msgs->super.next->next)->resends + ); + /* third fragment has i + 1 resends (one due to the ACK, one due to + * the timeout) */ + TEST_ASSERT_EQUAL_INT( + i + 1, + ((congure_snd_msg_t *)msgs->super.next->next->next)->resends + ); + } + } + /* generate final timeout */ + gnrc_sixlowpan_frag_sfr_arq_timeout(fbuf); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag3))); + /* nothing should have changed for report_msgs_lost */ + TEST_ASSERT_REPORT_MSGS_LOST(c, 1U, msgs); + /* nothing should have changed for report_msg_acked */ + TEST_ASSERT_REPORT_MSG_ACKED(c, 1U); + /* report_msg_timeout was called CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES + * times; once for each ARQ timeout */ + TEST_ASSERT_REPORT_MSGS_TIMEOUT( + c, CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES, msgs + ); + /* report_msg_sent has been called 3 + (2 * RETRIES) times: + * - 3 for each initial send of the fragments + * - 2 each retry due to ACK or timeout (1 was ACK'd so is not sent anymore + * and while only fragment 2 is sent, 1 is marked as in flight) + */ + TEST_ASSERT_REPORT_MSG_SENT( + c, 3U + (2U * CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES) + ); + /* fragmentation buffer and congure state should have been destroyed and + * freed after CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES retries */ + TEST_ASSERT_FBUF_CONGURE_DESTROYED(fbuf, c); + /* inter_msg_interval was called several times to calculate inter-frame gap + */ + TEST_ASSERT(0 < c->inter_msg_interval_calls); + /* none of the other CongURE report methods should have been called */ + TEST_ASSERT_EQUAL_INT(0, c->report_msg_discarded_calls); + /* no fragments reported in flight anymore */ + TEST_ASSERT_EQUAL_INT(0, _in_flight_frags); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void _test_sixlo_send__all_or_FULL_ackd(const uint8_t acked_frags) +{ + gnrc_sixlowpan_frag_fb_t *fbuf; + BITFIELD(ack_bitmap, 32U) = { 0 }; + congure_mock_snd_t *c; + bool sent_frags_correct, ack_received; + uint8_t tag; + static const uint8_t ack_req = 1 << TEST_SEND_FRAG_SEQ3; + + if (acked_frags == TEST_FULL_ACK) { + _test_initial_send(0, ack_req, &tag, NULL, &sent_frags_correct); + } + else { + _test_initial_send(acked_frags, ack_req, &tag, ack_bitmap, + &sent_frags_correct); + } + TEST_ASSERT(sent_frags_correct); + TEST_ASSERT_FBUF_CONGURE_EXISTS(fbuf, c); + if (acked_frags == TEST_FULL_ACK) { + _recv_ack((uint8_t *)TEST_FULL_ACK, tag, false, &ack_received); + } + else { + _recv_ack(ack_bitmap, tag, false, &ack_received); + } + TEST_ASSERT(ack_received); + /* all three fragments were reported as sent exactly once */ + TEST_ASSERT_REPORT_MSG_SENT(c, 3U); + /* all three fragments were ACK'd */ + TEST_ASSERT_REPORT_MSG_ACKED(c, 3U); + /* should time out */ + TEST_ASSERT_EQUAL_INT(0, _wait_for_packet(sizeof(_test_send_frag3))); + /* fragmentation buffer and congure state should have been destroyed and + * freed after CONFIG_GNRC_SIXLOWPAN_SFR_FRAG_RETRIES retries */ + TEST_ASSERT_FBUF_CONGURE_DESTROYED(fbuf, c); + /* inter_msg_interval was called several times to calculate inter-frame gap + */ + TEST_ASSERT(0 < c->inter_msg_interval_calls); + /* none of the other CongURE report methods should have been called */ + TEST_ASSERT_EQUAL_INT(0, c->report_msg_discarded_calls); + TEST_ASSERT_EQUAL_INT(0, c->report_msgs_timeout_calls); + TEST_ASSERT_EQUAL_INT(0, c->report_msgs_lost_calls); + TEST_ASSERT_EQUAL_INT(0, c->report_msgs_timeout_calls); + /* no fragments reported in flight anymore */ + TEST_ASSERT_EQUAL_INT(0, _in_flight_frags); + TEST_ASSERT(gnrc_pktbuf_is_sane()); + TEST_ASSERT(gnrc_pktbuf_is_empty()); +} + +static void test_sixlo_send__all_ackd(void) +{ + _test_sixlo_send__all_or_FULL_ackd( + (1 << TEST_SEND_FRAG_SEQ1) | + (1 << TEST_SEND_FRAG_SEQ2) | + (1 << TEST_SEND_FRAG_SEQ3) + ); +} + +static void test_sixlo_send__FULL_ack_recv(void) +{ + _test_sixlo_send__all_or_FULL_ackd(TEST_FULL_ACK); +} + +static Test *tests_gnrc_sixlowpan_frag_sfr_congure_integration(void) +{ + EMB_UNIT_TESTFIXTURES(fixtures) { + new_TestFixture(test_sixlo_send__no_ack), + new_TestFixture(test_sixlo_send__first_ackd), + new_TestFixture(test_sixlo_send__last_ackd_ecn), + 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_congure_integration()); + TESTS_END(); + return 0; +} + +static void _congure_init(congure_snd_t *cong, void *ctx) +{ + cong->ctx = ctx; + cong->cwnd = CONFIG_GNRC_SIXLOWPAN_SFR_OPT_WIN_SIZE; +} + +static void _congure_report_msg_sent(congure_snd_t *cong, unsigned msg_size) +{ + (void)cong; + _in_flight_frags += msg_size; +} + +static void _congure_report_msg_discarded(congure_snd_t *cong, + unsigned msg_size) +{ + (void)cong; + _in_flight_frags -= msg_size; +} + +static int _report_msg_discarded(clist_node_t *node, void *arg) +{ + congure_snd_msg_t *msg = container_of(node, congure_snd_msg_t, super); + _congure_report_msg_discarded(arg, msg->size); + return 0; +} + +static void _congure_report_msgs_timeout_lost(congure_snd_t *cong, + congure_snd_msg_t *msgs) +{ + (void)cong; + clist_foreach(&msgs->super, _report_msg_discarded, cong); +} + +static void _congure_report_msg_acked(congure_snd_t *cong, + congure_snd_msg_t *msg, + congure_snd_ack_t *ack) +{ + (void)cong; + (void)ack; + _in_flight_frags -= msg->size; +} + +static congure_mock_snd_t *_get_congure_by_fb(const gnrc_sixlowpan_frag_fb_t *fb) +{ + for (unsigned i = 0; i < ARRAY_SIZE(_sfr_congure_mocks); i++) { + if ((_sfr_congure_mocks[i].super.driver != NULL) && + /* ctx is set to the fragment buffer */ + (_sfr_congure_mocks[i].super.ctx == fb)) { + return &_sfr_congure_mocks[i]; + } + } + return NULL; +} + +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 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 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, bool *finished) +{ + sixlowpan_sfr_rfrag_t *frag_hdr; + + *finished = false; + 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" + ); + *finished = true; +} + +static void _check_send_frag2(size_t mhr_len, bool ack_req, bool *finished) +{ + sixlowpan_sfr_rfrag_t *frag_hdr; + + *finished = false; + 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" + ); + *finished = true; +} + +static void _check_send_frag3(size_t mhr_len, bool ack_req, bool *finished) +{ + sixlowpan_sfr_rfrag_t *frag_hdr; + + *finished = false; + 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" + ); + *finished = true; +} + +static void _check_congure_snd_msg(congure_snd_msg_t *msg) +{ + TEST_ASSERT(msg->send_time > 0U); + TEST_ASSERT_EQUAL_INT(msg->size, 1); +} + +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_congure/mockup_netif.c b/tests/gnrc_sixlowpan_frag_sfr_congure/mockup_netif.c new file mode 100644 index 000000000000..4ea8d6533cc0 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure/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_PDU_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_congure/tests/01-run.py b/tests/gnrc_sixlowpan_frag_sfr_congure/tests/01-run.py new file mode 100755 index 000000000000..2eac21932f00 --- /dev/null +++ b/tests/gnrc_sixlowpan_frag_sfr_congure/tests/01-run.py @@ -0,0 +1,15 @@ +#!/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_check_unittests + + +if __name__ == "__main__": + sys.exit(run_check_unittests())