1
0
mirror of https://github.com/RIOT-OS/RIOT.git synced 2024-12-29 04:50:03 +01:00

Merge pull request #15951 from miri64/congure/feat/initial

congure: initial import of a congestion control framework
This commit is contained in:
Martine Lenders 2021-02-26 12:46:50 +01:00 committed by GitHub
commit fc6627606c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
27 changed files with 2284 additions and 4 deletions

View File

@ -8,7 +8,7 @@
: ${TEST_BOARDS_LLVM_COMPILE:=""}
: ${TEST_KCONFIG_samr21_xpro:="examples/hello-world tests/periph_*
tests/test_tools tests/xtimer_* tests/ztimer_*
tests/test_tools tests/congure_* tests/xtimer_* tests/ztimer_*
tests/driver_ad7746 tests/driver_adcxx1c tests/driver_ads101x tests/driver_adt101x
tests/driver_adt7310 tests/driver_adxl345 tests/driver_aip31068 tests/driver_apa102
tests/driver_apds99xx tests/driver_apds99xx_full tests/driver_at tests/driver_at24cxxx
@ -25,9 +25,9 @@ tests/mtd_mapper tests/driver_o* tests/driver_p* tests/driver_q*
tests/driver_r* tests/driver_s* tests/driver_t* tests/driver_u*
tests/driver_v*"}
: ${TEST_KCONFIG_native:="examples/hello-world tests/periph_* tests/sys_crypto
tests/test_tools tests/prng_* tests/xtimer_* tests/ztimer_* tests/driver_ws281x
tests/posix_sleep tests/pkg_umorse tests/cb_mux* tests/eepreg tests/shell
tests/struct_tm_utility"}
tests/test_tools tests/congure_* tests/prng_* tests/xtimer_* tests/ztimer_*
tests/driver_ws281x tests/posix_sleep tests/pkg_umorse tests/cb_mux* tests/eepreg
tests/shell tests/struct_tm_utility"}
: ${TEST_WITH_CONFIG_SUPPORTED:="examples/suit_update tests/driver_at86rf2xx_aes"}

View File

@ -8,5 +8,7 @@ cpu/riscv_common/include/.*\.h
cpu/riscv_common/periph/.*\.c
sys/riotboot/.*\.h
sys/riotboot/.*\.c
sys/congure.*\.c
sys/include/congure.*\.h
sys/ztimer/.*\.c
sys/include/ztimer.*\.h

View File

@ -18,6 +18,7 @@ rsource "cb_mux/Kconfig"
rsource "checksum/Kconfig"
rsource "color/Kconfig"
rsource "crypto/Kconfig"
rsource "congure/Kconfig"
rsource "div/Kconfig"
rsource "embunit/Kconfig"
rsource "entropy_source/Kconfig"

View File

@ -21,6 +21,14 @@ ifneq (,$(filter arduino_pwm,$(FEATURES_USED)))
FEATURES_REQUIRED += periph_pwm
endif
ifneq (,$(filter congure_%,$(USEMODULE)))
USEMODULE += congure
endif
ifneq (,$(filter congure_test,$(USEMODULE)))
USEMODULE += fmt
endif
ifneq (,$(filter eepreg,$(USEMODULE)))
FEATURES_REQUIRED += periph_eeprom
endif

28
sys/congure/Kconfig Normal file
View File

@ -0,0 +1,28 @@
# 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.
if !TEST_KCONFIG
menu "CongURE congestion control abstraction"
depends on USEMODULE_CONGURE
rsource "mock/Kconfig"
rsource "test/Kconfig"
endmenu # CongURE congestion control abstraction
endif # !TEST_KCONFIG
if TEST_KCONFIG
menuconfig MODULE_CONGURE
bool "CongURE congestion control abstraction"
depends on TEST_KCONFIG
if MODULE_CONGURE
rsource "mock/Kconfig"
rsource "test/Kconfig"
endif # MODULE_CONGURE
endif # TEST_KCONFIG

8
sys/congure/Makefile Normal file
View File

@ -0,0 +1,8 @@
ifneq (,$(filter congure_mock,$(USEMODULE)))
DIRS += mock
endif
ifneq (,$(filter congure_test,$(USEMODULE)))
DIRS += test
endif
include $(RIOTBASE)/Makefile.base

3
sys/congure/mock/Kconfig Normal file
View File

@ -0,0 +1,3 @@
config MODULE_CONGURE_MOCK
bool "CongURE mock implementation for testing"
depends on MODULE_CONGURE

View File

@ -0,0 +1,3 @@
MODULE := congure_mock
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,121 @@
/*
* 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.
*/
/**
* @{
*
* @file
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include "congure/mock.h"
static void _snd_init(congure_snd_t *cong, void *ctx);
static int32_t _snd_inter_msg_interval(congure_snd_t *cong, unsigned msg_size);
static void _snd_report_msg_sent(congure_snd_t *cong, unsigned msg_size);
static void _snd_report_msg_discarded(congure_snd_t *cong, unsigned msg_size);
static void _snd_report_msgs_lost(congure_snd_t *cong, congure_snd_msg_t *msgs);
static void _snd_report_msgs_timeout(congure_snd_t *cong,
congure_snd_msg_t *msgs);
static void _snd_report_msg_acked(congure_snd_t *cong, congure_snd_msg_t *msg,
congure_snd_ack_t *ack);
static void _snd_report_ecn_ce(congure_snd_t *cong, ztimer_now_t time);
static const congure_snd_driver_t _driver = {
.init = _snd_init,
.inter_msg_interval = _snd_inter_msg_interval,
.report_msg_sent = _snd_report_msg_sent,
.report_msg_discarded = _snd_report_msg_discarded,
.report_msgs_timeout = _snd_report_msgs_timeout,
.report_msgs_lost = _snd_report_msgs_lost,
.report_msg_acked = _snd_report_msg_acked,
.report_ecn_ce = _snd_report_ecn_ce,
};
void congure_mock_snd_setup(congure_mock_snd_t *c)
{
c->super.driver = &_driver;
}
static void _snd_init(congure_snd_t *cong, void *ctx)
{
congure_mock_snd_t *c = (congure_mock_snd_t *)cong;
c->init_calls++;
c->init_args.c = &c->super;
c->init_args.ctx = ctx;
}
static int32_t _snd_inter_msg_interval(congure_snd_t *cong, unsigned msg_size)
{
congure_mock_snd_t *c = (congure_mock_snd_t *)cong;
c->inter_msg_interval_calls++;
c->inter_msg_interval_args.c = &c->super;
c->inter_msg_interval_args.msg_size = msg_size;
return -1;
}
static void _snd_report_msg_sent(congure_snd_t *cong, unsigned msg_size)
{
congure_mock_snd_t *c = (congure_mock_snd_t *)cong;
c->report_msg_sent_calls++;
c->report_msg_sent_args.c = &c->super;
c->report_msg_sent_args.msg_size = msg_size;
}
static void _snd_report_msg_discarded(congure_snd_t *cong, unsigned msg_size)
{
congure_mock_snd_t *c = (congure_mock_snd_t *)cong;
c->report_msg_discarded_calls++;
c->report_msg_discarded_args.c = &c->super;
c->report_msg_discarded_args.msg_size = msg_size;
}
static void _snd_report_msgs_lost(congure_snd_t *cong, congure_snd_msg_t *msgs)
{
congure_mock_snd_t *c = (congure_mock_snd_t *)cong;
c->report_msgs_lost_calls++;
c->report_msgs_lost_args.c = &c->super;
c->report_msgs_lost_args.msgs = msgs;
}
static void _snd_report_msgs_timeout(congure_snd_t *cong,
congure_snd_msg_t *msgs)
{
congure_mock_snd_t *c = (congure_mock_snd_t *)cong;
c->report_msgs_timeout_calls++;
c->report_msgs_timeout_args.c = &c->super;
c->report_msgs_timeout_args.msgs = msgs;
}
static void _snd_report_msg_acked(congure_snd_t *cong, congure_snd_msg_t *msg,
congure_snd_ack_t *ack)
{
congure_mock_snd_t *c = (congure_mock_snd_t *)cong;
c->report_msg_acked_calls++;
c->report_msg_acked_args.c = &c->super;
c->report_msg_acked_args.msg = msg;
c->report_msg_acked_args.ack = ack;
}
static void _snd_report_ecn_ce(congure_snd_t *cong, ztimer_now_t time)
{
congure_mock_snd_t *c = (congure_mock_snd_t *)cong;
c->report_ecn_ce_calls++;
c->report_ecn_ce_args.c = &c->super;
c->report_ecn_ce_args.time = time;
}
/** @} */

30
sys/congure/test/Kconfig Normal file
View File

@ -0,0 +1,30 @@
# 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.
if !TEST_KCONFIG
menuconfig KCONFIG_USEMODULE_CONGURE_TEST
bool "Configure CongURE test framework"
depends on USEMODULE_CONGURE_TEST
help
Configure CongURE test framework via Kconfig.
if KCONFIG_USEMODULE_CONGURE_TEST
rsource "Kconfig.config"
endif # KCONFIG_USEMODULE_CONGURE_TEST
endif # !TEST_KCONFIG
if TEST_KCONFIG
menuconfig MODULE_CONGURE_TEST
bool "CongURE test framework"
depends on TEST_KCONFIG
select MODULE_FMT
if MODULE_CONGURE_TEST
rsource "Kconfig.config"
endif # MODULE_CONGURE_TEST
endif # TEST_KCONFIG

View File

@ -0,0 +1,19 @@
# 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.
#
# XXX This file only is required since there is no easy way to use these config
# options with both the final MODULE_SHELL and KCONFIG_USEMODULE_SHELL in a
# nicely looking and easy to migrate way. After migration, the content of this
# file can be folded back into `sys/shell/Kconfig`
config CONGURE_TEST_LOST_MSG_POOL_SIZE
int "Pool size for the list elements for a lost message report"
default 4
help
@see congure_snd_driver_t::report_msg_lost
This defines the maximum number of 3-tuples you can use with
@ref congure_test_call_report() when argv[1] is msg_lost.

View File

@ -0,0 +1,3 @@
MODULE := congure_test
include $(RIOTBASE)/Makefile.base

View File

@ -0,0 +1,369 @@
/*
* 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.
*/
/**
* @{
*
* @file
* @author Martine S. Lenders <m.lenders@fu-berlin.de>
*/
#include <limits.h>
#include <stdbool.h>
#include <string.h>
#include <stdlib.h>
#include "fmt.h"
#include "congure/test.h"
static bool _scn_u32_dec_with_zero(const char *str, size_t n, uint32_t *res)
{
if ((n == 1) && str[0] == '0') {
*res = 0;
}
else if ((*res = scn_u32_dec(str, n)) == 0) {
return false;
}
return true;
}
int congure_test_clear_state(int argc, char **argv)
{
(void)argc;
(void)argv;
memset(congure_test_get_state(), 0, sizeof(congure_test_snd_t));
print_str("{\"success\":null}\n");
return 0;
}
int congure_test_call_setup(int argc, char **argv)
{
congure_test_snd_t *c = congure_test_get_state();
uint32_t id = 0;
if (argc > 1) {
if (!_scn_u32_dec_with_zero(argv[1], strlen(argv[1]), &id)) {
print_str("{\"error\":\"`id` expected to be integer\"}\n");
return 1;
}
}
if (congure_test_snd_setup(c, (unsigned)id) < 0) {
print_str("{\"error\":\"`id` is invalid\"}");
return 1;
}
print_str("{");
print_str("\"success\":\"0x");
print_u32_hex((intptr_t)c);
print_str("\"}\n");
return 0;
}
static inline bool _check_driver(congure_test_snd_t *c)
{
if (c->super.driver == NULL) {
print_str("{\"error\":\"State object not set up\"}\n");
return false;
}
return true;
}
int congure_test_call_init(int argc, char **argv)
{
congure_test_snd_t *c = congure_test_get_state();
uint32_t ctx;
size_t arglen;
if (!_check_driver(c)) {
return 1;
}
if (argc < 2) {
print_str("{\"error\":\"`ctx` argument expected\"}\n");
return 1;
}
arglen = strlen(argv[1]);
if ((arglen < 3) || ((argv[1][0] != '0') && (argv[1][1] != 'x'))) {
print_str("{\"error\":\"`ctx` expected to be hex\"}\n");
return 1;
}
ctx = scn_u32_hex(&argv[1][2], arglen - 2);
c->super.driver->init(&c->super, (void *)((intptr_t)ctx));
print_str("{\"success\":null}\n");
return 0;
}
int congure_test_call_inter_msg_interval(int argc, char **argv)
{
congure_test_snd_t *c = congure_test_get_state();
uint32_t msg_size;
int32_t res;
(void)argc;
(void)argv;
if (!_check_driver(c)) {
return 1;
}
if (argc < 2) {
print_str("{\"error\":\"`msg_size` argument expected\"}\n");
return 1;
}
if (!_scn_u32_dec_with_zero(argv[1], strlen(argv[1]), &msg_size)) {
print_str("{\"error\":\"`msg_size` expected to be integer\"}\n");
return 1;
}
res = c->super.driver->inter_msg_interval(&c->super, msg_size);
print_str("{\"success\":");
print_s32_dec(res);
print_str("}\n");
return 0;
}
static int _call_report_msg_sent(int argc, char **argv)
{
congure_test_snd_t *c = congure_test_get_state();
uint32_t msg_size;
if (argc < 2) {
print_str("{\"error\":\"`msg_size` argument expected\"}\n");
return 1;
}
if (!_scn_u32_dec_with_zero(argv[1], strlen(argv[1]), &msg_size)) {
print_str("{\"error\":\"`msg_size` expected to be integer\"}\n");
return 1;
}
c->super.driver->report_msg_sent(&c->super, (unsigned)msg_size);
print_str("{\"success\":null}\n");
return 0;
}
static int _call_report_msg_discarded(int argc, char **argv)
{
congure_test_snd_t *c = congure_test_get_state();
uint32_t msg_size;
if (argc < 2) {
print_str("{\"error\":\"`msg_size` argument expected\"}\n");
return 1;
}
if (!_scn_u32_dec_with_zero(argv[1], strlen(argv[1]), &msg_size)) {
print_str("{\"error\":\"`msg_size` expected to be integer\"}\n");
return 1;
}
c->super.driver->report_msg_discarded(&c->super, (unsigned)msg_size);
print_str("{\"success\":null}\n");
return 0;
}
static int _call_report_msgs_timeout_lost(void (*method)(congure_snd_t *,
congure_snd_msg_t *),
int argc, char **argv)
{
static congure_snd_msg_t list_pool[CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE];
clist_node_t msgs = { .next = NULL };
congure_test_snd_t *c = congure_test_get_state();
if (argc < 4) {
print_str("{\"error\":\"At least 3 arguments `msg_send_time`, "
"`msg_size`, `msg_resends` expected\"}\n");
return 1;
}
if ((argc - 1) % 3) {
print_str("{\"error\":\"Number of arguments must be divisible "
"by 3\"}\n");
return 1;
}
if ((unsigned)((argc - 1) / 3) >= ARRAY_SIZE(list_pool)) {
print_str("{\"error\":\"List element pool depleted\"}");
return 1;
}
for (int i = 1; i < argc; i += 3) {
uint32_t tmp;
unsigned pool_idx = ((i - 1) / 3);
list_pool[pool_idx].super.next = NULL;
if (!_scn_u32_dec_with_zero(argv[i], strlen(argv[i]), &tmp)) {
print_str("{\"error\":\"`msg_send_time` expected to be "
"integer\"}\n");
return 1;
}
list_pool[pool_idx].send_time = tmp;
if (!_scn_u32_dec_with_zero(argv[i + 1], strlen(argv[i + 1]), &tmp)) {
print_str("{\"error\":\"`msg_size` expected to be integer\"}\n");
return 1;
}
list_pool[pool_idx].size = tmp;
if (!_scn_u32_dec_with_zero(argv[i + 2], strlen(argv[i + 2]), &tmp)) {
print_str("{\"error\":\"`msg_resends` expected to be "
"integer\"}\n");
return 1;
}
list_pool[pool_idx].resends = tmp;
clist_rpush(&msgs, &list_pool[pool_idx].super);
}
method(&c->super, (congure_snd_msg_t *)msgs.next);
print_str("{\"success\":null}\n");
return 0;
}
static int _call_report_msgs_timeout(int argc, char **argv)
{
congure_test_snd_t *c = congure_test_get_state();
return _call_report_msgs_timeout_lost(c->super.driver->report_msgs_timeout,
argc, argv);
}
static int _call_report_msgs_lost(int argc, char **argv)
{
congure_test_snd_t *c = congure_test_get_state();
return _call_report_msgs_timeout_lost(c->super.driver->report_msgs_lost,
argc, argv);
}
static int _call_report_msg_acked(int argc, char **argv)
{
static congure_snd_msg_t msg = { .size = 0 };
static congure_snd_ack_t ack = { .size = 0 };
congure_test_snd_t *c = congure_test_get_state();
uint32_t tmp;
if (argc < 10) {
print_str("{\"error\":\"At least 9 arguments `msg_send_time`, "
"`msg_size`, `msg_resends`, `ack_recv_time`, `ack_id`, "
"`ack_size`, `ack_clean`, `ack_wnd`, `ack_delay` "
"expected\"}\n");
return 1;
}
if (!_scn_u32_dec_with_zero(argv[1], strlen(argv[1]), &tmp)) {
print_str("{\"error\":\"`msg_send_time` expected to be "
"integer\"}\n");
return 1;
}
msg.send_time = tmp;
if (!_scn_u32_dec_with_zero(argv[2], strlen(argv[2]), &tmp)) {
print_str("{\"error\":\"`msg_size` expected to be integer\"}\n");
return 1;
}
msg.size = tmp;
if (!_scn_u32_dec_with_zero(argv[3], strlen(argv[3]), &tmp)) {
print_str("{\"error\":\"`msg_resends` expected to be integer\"}\n");
return 1;
}
msg.resends = tmp;
if (!_scn_u32_dec_with_zero(argv[4], strlen(argv[4]), &tmp)) {
print_str("{\"error\":\"`ack_recv_time` expected to be integer\"}\n");
return 1;
}
ack.recv_time = tmp;
if (!_scn_u32_dec_with_zero(argv[5], strlen(argv[5]), &tmp)) {
print_str("{\"error\":\"`ack_id` expected to be integer\"}\n");
return 1;
}
ack.id = tmp;
if (!_scn_u32_dec_with_zero(argv[6], strlen(argv[6]), &tmp)) {
print_str("{\"error\":\"`ack_size` expected to be integer\"}\n");
return 1;
}
ack.size = tmp;
if (!_scn_u32_dec_with_zero(argv[7], strlen(argv[7]), &tmp)) {
print_str("{\"error\":\"`ack_clean` expected to be integer\"}\n");
return 1;
}
ack.clean = (bool)tmp;
if (!_scn_u32_dec_with_zero(argv[8], strlen(argv[8]), &tmp)) {
print_str("{\"error\":\"`ack_wnd` expected to be integer\"}\n");
return 1;
}
if (tmp > CONGURE_WND_SIZE_MAX) {
print_str("{\"error\":\"`ack_wnd` not 16 bit wide\"}\n");
return 1;
}
ack.wnd = (uint16_t)tmp;
if (!_scn_u32_dec_with_zero(argv[9], strlen(argv[9]), &tmp)) {
print_str("{\"error\":\"`ack_delay` expected to be integer\"}\n");
return 1;
}
if (tmp > UINT16_MAX) {
print_str("{\"error\":\"`ack_delay` not 16 bit wide\"}\n");
return 1;
}
ack.delay = (uint16_t)tmp;
c->super.driver->report_msg_acked(&c->super, &msg, &ack);
print_str("{\"success\":null}\n");
return 0;
}
static int _call_report_ecn_ce(int argc, char **argv)
{
congure_test_snd_t *c = congure_test_get_state();
uint32_t time;
if (argc < 2) {
print_str("{\"error\":\"`time` argument expected\"}\n");
return 1;
}
if (!_scn_u32_dec_with_zero(argv[1], strlen(argv[1]), &time)) {
print_str("{\"error\":\"`time` expected to be integer\"}\n");
return 1;
}
c->super.driver->report_ecn_ce(&c->super, time);
print_str("{\"success\":null}\n");
return 0;
}
int congure_test_call_report(int argc, char **argv)
{
if (!_check_driver(congure_test_get_state())) {
return 1;
}
if (argc < 2) {
print_str("{\"error\":\"No report command provided\"}\n");
return 1;
}
if (strcmp(argv[1], "msg_sent") == 0) {
return _call_report_msg_sent(argc - 1, &argv[1]);
}
else if (strcmp(argv[1], "msg_discarded") == 0) {
return _call_report_msg_discarded(argc - 1, &argv[1]);
}
else if (strcmp(argv[1], "msgs_timeout") == 0) {
return _call_report_msgs_timeout(argc - 1, &argv[1]);
}
else if (strcmp(argv[1], "msgs_lost") == 0) {
return _call_report_msgs_lost(argc - 1, &argv[1]);
}
else if (strcmp(argv[1], "msg_acked") == 0) {
return _call_report_msg_acked(argc - 1, &argv[1]);
}
else if (strcmp(argv[1], "ecn_ce") == 0) {
return _call_report_ecn_ce(argc - 1, &argv[1]);
}
print_str("{\"error\":\"Unknown command `");
print_str(argv[1]);
print_str("`\"}\n");
return 1;
}
/** @} */

225
sys/include/congure.h Normal file
View File

@ -0,0 +1,225 @@
/*
* 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 sys_congure CongURE - A Congestion control framework
* @ingroup sys
* @brief <b>Cong</b>estion control <b>u</b>tilizing <b>r</b>e-usable
* <b>e</b>lements
* @{
*
* @file
*
* @author Martine S. Lenders <m.lenders@fu-berlin.de>
*/
#ifndef CONGURE_H
#define CONGURE_H
#include <stdint.h>
#include "clist.h"
#include "ztimer.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief Maximum value the window size can take
*/
#define CONGURE_WND_SIZE_MAX (UINT16_MAX)
/**
* @brief Define type for window size to allow for possible window size
* scaling
*/
typedef uint16_t congure_wnd_size_t;
/**
* @brief Forward-declariton for the driver struct
*/
typedef struct congure_snd_driver congure_snd_driver_t;
/**
* @brief Base state object for CongURE implementations
*/
typedef struct {
/**
* @brief Driver for the state object. See @ref congure_snd_driver_t.
*/
const congure_snd_driver_t *driver;
/**
* @brief Context for callbacks specific to the congestion control
*
* E.g. A TCP PCB.
*/
void *ctx;
congure_wnd_size_t cwnd; /**< Congestion window size */
} congure_snd_t;
/**
* @brief Object to represent a collection of sent messages.
*/
typedef struct {
clist_node_t super; /**< see @ref clist_node_t */
/**
* @brief timestamp in milliseconds of when the message was sent.
*/
ztimer_now_t send_time;
congure_wnd_size_t size; /**< size in initiator-defined units */
/**
* @brief number of times the message has already been resent.
*
* This does not include the first send.
*/
uint8_t resends;
} congure_snd_msg_t;
/**
* @brief Object to represent an ACK to a message
*/
typedef struct {
/**
* @brief Timestamp in milliseconds of when the ACK was received.
*/
ztimer_now_t recv_time;
uint32_t id; /**< ID of the message the ACK is for */
/**
* @brief size of the ACK in initiator-defined units
*/
congure_wnd_size_t size;
/**
* @brief the peer-reported window size in caller defined units. Leave
* 0 if not supplied by the protocol.
*/
congure_wnd_size_t wnd;
/**
* @brief true, if ACK only contains an ACK, false if not
*
* This e.g. can be used to tell the congestion control mechanism that the
* SYN or FIN tag are cleared in the ACK with TCP.
*/
uint16_t clean;
/**
* @brief the peer-reported time in milliseconds the ACK was delayed
* since message reception. Leave 0 if not supplied by the
* protocol.
*/
uint16_t delay;
} congure_snd_ack_t;
/**
* @brief Driver for CongURE objects.
*/
struct congure_snd_driver {
/**
* @brief Initializes a CongURE object.
*
* @param[in,out] c The CongURE object to initialize.
* @param[in] ctx Context for callbacks specific to the
* congestion control (such as a TCP PCB).
* May be NULL.
*/
void (*init)(congure_snd_t *c, void *ctx);
/**
* @brief Get current interval between messages for pacing.
*
* @param[in] c The CongURE state object.
* @param[in] msg_size The size of the next message to send in
* caller-defined unit.
*
* @return The current interval between sent messages in microseconds
* when pacing supported by congestion control implementation.
* @return -1, if pacing is not supported by the congestion control
* implementation.
*/
int32_t (*inter_msg_interval)(congure_snd_t *c, unsigned msg_size);
/**
* @brief Report that a message was sent.
*
* @param[in] c The CongURE state object.
* @param[in] msg_size Size of the message in caller-defined unit.
*/
void (*report_msg_sent)(congure_snd_t *c, unsigned msg_size);
/**
* @brief Report message as discarded.
*
* Discarded messages are not further taken into account for congestion
* control.
*
* @param[in] c The CongURE state object.
* @param[in] msg_size Size of the discarded message in caller-defined
* unit.
*/
void (*report_msg_discarded)(congure_snd_t *c, unsigned msg_size);
/**
* @brief Report that the ACKs for a collection of messages timed out.
*
* @note As many congestion control algorithms do not distinguish loss
* and ACK timeout, this method and
* congure_snd_t::report_msgs_lost need to have the same
* signature so the same function can be used here
*
* @param[in] c The CongURE state object.
* @param[in] msgs A collection of messages for which the ACK
* timed out. The list must not be changed by the
* method.
*/
void (*report_msgs_timeout)(congure_snd_t *c, congure_snd_msg_t *msgs);
/**
* @brief Report that a collection of messages that is known to be lost.
*
* One indicator for a lost message may e.g. be the reception of an ACK of a
* later sent packet, but not a ACK timeout (see
* congure_snd_driver_t::report_msgs_timeout() for that).
*
* @note As many congestion control algorithms do not distinguish loss
* and ACK timeout, this method and
* congure_snd_t::report_msgs_timeout need to have the same
* signature so the same function can be used here
*
* @param[in] c The CongURE state object.
* @param[in] msgs A collection of messages that are known to
* be lost. The list must not be be changed by the
* method.
*/
void (*report_msgs_lost)(congure_snd_t *c, congure_snd_msg_t *msgs);
/**
* @brief Report that the ACK for a message was received.
*
* @param[in] c The CongURE state object.
* @param[in] msg The ACK'd message.
* @param[in] ack The received ACK.
*/
void (*report_msg_acked)(congure_snd_t *c, congure_snd_msg_t *msg,
congure_snd_ack_t *ack);
/**
* @brief Report that "congestion encountered" CE signals were received
* for a message by means of explicit congestion notification
* (ECN).
*
* @param[in] c The CongURE state object.
* @param[in] time Timestamp in milliseconds of the message the CE
* event occurred for was sent.
*/
void (*report_ecn_ce)(congure_snd_t *c, ztimer_now_t time);
};
#ifdef __cplusplus
}
#endif
#endif /* CONGURE_H */
/** @} */

View File

@ -0,0 +1,32 @@
/*
* 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 sys_congure_config CongURE compile time configuration
* @ingroup sys_congure
* @ingroup config
* @brief Configuration for congestion control using @ref sys_congure
* @{
*
* @file
*
* @author Martine S. Lenders <m.lenders@fu-berlin.de>
*/
#ifndef CONGURE_CONFIG_H
#define CONGURE_CONFIG_H
#ifdef __cplusplus
extern "C" {
#endif
#ifdef __cplusplus
}
#endif
#endif /* CONGURE_CONFIG_H */
/** @} */

164
sys/include/congure/mock.h Normal file
View File

@ -0,0 +1,164 @@
/*
* 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 sys_congure_mock CongURE mock implementation
* @ingroup sys_congure
* @brief A mock for testing @ref sys_congure
* @{
*
* @file
*
* @author Martine S. Lenders <m.lenders@fu-berlin.de>
*/
#ifndef CONGURE_MOCK_H
#define CONGURE_MOCK_H
#include <stdint.h>
#include "congure.h"
#ifdef __cplusplus
extern "C" {
#endif
/**
* @brief A mock CongURE state object
*
* @extends congure_snd_t
*/
typedef struct {
congure_snd_t super; /**< see @ref congure_snd_t */
/**
* @brief How often was the congure_snd_driver_t::init() method called?
*/
uint8_t init_calls;
/**
* @brief How often was the congure_snd_driver_t::inter_msg_interval()
* method called?
*/
uint8_t inter_msg_interval_calls;
/**
* @brief How often was the congure_snd_driver_t::report_msg_sent()
* method called?
*/
uint8_t report_msg_sent_calls;
/**
* @brief How often was the congure_snd_driver_t::report_msg_discarded()
* method called?
*/
uint8_t report_msg_discarded_calls;
/**
* @brief How often was the congure_snd_driver_t::report_msgs_timeout()
* method called?
*/
uint8_t report_msgs_timeout_calls;
/**
* @brief How often was the congure_snd_driver_t::report_msgs_lost()
* method called?
*/
uint8_t report_msgs_lost_calls;
/**
* @brief How often was the congure_snd_driver_t::report_msg_acked()
* method called?
*/
uint8_t report_msg_acked_calls;
/**
* @brief How often was the congure_snd_driver_t::report_ecn_ce()
* method called?
*/
uint8_t report_ecn_ce_calls;
/**
* @brief What were the arguments for the last
* congure_snd_driver_t::init() call?
*/
struct {
congure_snd_t *c; /**< The CongURE object to initialize */
void *ctx; /**< Context for callbacks specific to CC */
} init_args;
/**
* @brief What were the arguments for the last
* congure_snd_driver_t::inter_msg_interval() call?
*/
struct {
congure_snd_t *c; /**< The CongURE state object */
unsigned msg_size; /**< The size of the next message to send */
} inter_msg_interval_args;
/**
* @brief What were the arguments for the last
* congure_snd_driver_t::report_msg_sent() call?
*/
struct {
congure_snd_t *c; /**< The CongURE state object */
unsigned msg_size; /**< Size of the message */
} report_msg_sent_args;
/**
* @brief What were the arguments for the last
* congure_snd_driver_t::report_msg_discarded() call?
*/
struct {
congure_snd_t *c; /**< The CongURE state object */
unsigned msg_size; /**< Size of the message */
} report_msg_discarded_args;
/**
* @brief What were the arguments for the last
* congure_snd_driver_t::report_msgs_timeout() call?
*/
struct {
congure_snd_t *c; /**< The CongURE state object */
/**
* @brief A collection of messages for which the ACK timed out
*/
congure_snd_msg_t *msgs;
} report_msgs_timeout_args;
/**
* @brief What were the arguments for the last
* congure_snd_driver_t::report_msgs_lost() call?
*/
struct {
congure_snd_t *c; /**< The CongURE state object */
/**
* @brief A collection of messages that are known to be lost
*/
congure_snd_msg_t *msgs;
} report_msgs_lost_args;
/**
* @brief What were the arguments for the last
* congure_snd_driver_t::report_msg_acked() call?
*/
struct {
congure_snd_t *c; /**< The CongURE state object */
congure_snd_msg_t *msg; /**< The ACK'd message */
congure_snd_ack_t *ack; /**< The received ACK */
} report_msg_acked_args;
/**
* @brief What were the arguments for the last
* congure_snd_driver_t::report_ecn_ce() call?
*/
struct {
congure_snd_t *c; /**< The CongURE state object */
/**
* @brief Timestamp of the message the CE event occurred for was sent
*/
ztimer_now_t time;
} report_ecn_ce_args;
} congure_mock_snd_t;
/**
* @brief Sets up the driver for CongURE mock object
*
* @param[in] c A CongURE mock object
*/
void congure_mock_snd_setup(congure_mock_snd_t *c);
#ifdef __cplusplus
}
#endif
#endif /* CONGURE_MOCK_H */
/** @} */

319
sys/include/congure/test.h Normal file
View File

@ -0,0 +1,319 @@
/*
* 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 sys_congure_test CongURE test framework shell commands
* @ingroup sys_congure
* @brief Shell commands to test a CongURE implementation
*
* This module requires an application defined `congure_impl.h` which defines
* the congure_snd_t extension of the CongURE implementation as
* @ref congure_test_snd_t and provides a function declaration
* @ref congure_test_snd_setup() setup said type. E.g.
*
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ {.c}
* typedef congure_reno_snd_t congure_test_snd_t;
*
* void congure_test_snd_setup(congure_test_snd_t *c, int id);
* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
*
* All constants and initial values can then be set within the application
* specific definition of @ref congure_test_snd_setup().
*
* @{
*
* @file
* @brief Definitions for the CongURE test framework
*
* @author Martine S Lenders <m.lenders@fu-berlin.de>
*/
#ifndef CONGURE_TEST_H
#define CONGURE_TEST_H
#include "congure_impl.h"
#ifdef __cplusplus
extern "C" {
#endif
#ifdef DOXYGEN
/**
* @brief Application-defined type for the CongURE state object under test
*
* @extends congure_snd_t
*
* @note Needs to be set within an application-provided `congure_impl.h` to
* the state object of the CongURE implementation you want to test.
*/
typedef congure_snd_t congure_test_snd_t;
/**
* @brief Setup the application-defined CongURE state object under test
*
* @note Needs to be defined by the application and declare it within
* an application-provided `congure_impl.h`
*
* @param[in,out] c The CongURE state object under test. May not be NULL.
* @param[in] id And application-specific ID that may identify different
* setup parameters, e.g. a set of different constants to
* use when setting up @p c. If not applicable to your
* application just ignore @p id.
*
* @retval 0 on success
* @retval -1 when @p id is not ignored and is an unknown value to the
* application.
*/
int congure_test_snd_setup(void congure_test_snd_t *c, unsigned id);
#endif /* DOXYGEN */
/**
* @brief Pool size for the message list elements for a lost message report
*
* @see congure_snd_driver_t::report_msg_lost
*
* This defines the maximum number of 3-tuples you can use with
* @ref congure_test_call_report() when `argv[1] = "msg_lost"`.
*/
#ifndef CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE
#define CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE (4U)
#endif
/**
* @brief Get the application-defined CongURE state object
*
* @note Needs to be defined by the application
*
* @return The CongURE state object.
*/
congure_test_snd_t *congure_test_get_state(void);
/**
* @brief Clears the CongURE state object
*
* Every byte in the state object is set to 0.
*
* @param[in] argc Number of @p argv. Needs to be at least 1.
* @param[in] argv Command line arguments. No extra arguments are required
* except for the command name in `argv[0]`.
*
* Always generates the following JSON object in STDOUT:
* @code {"success": null} @endcode
*
* @return Always 0.
*/
int congure_test_clear_state(int argc, char **argv);
/**
* @brief Setup the CongURE state object
*
* Calls application-defined @ref congure_test_snd_setup()
*
* @param[in] argc Number of @p argv. Needs to be at least 1.
* @param[in] argv Command line arguments. The command name is expected in
* `argv[0]`. If @p argc > 1, a integer is expected in
* `argv[1]` for the `id` parameter of
* @ref congure_test_snd_setup().
*
* This function will generate the following JSON objects in STDOUT:
* - @code {"success": "0x12345678"} @endcode
* On success, with `0x12345678` being replaced with the memory address of the
* state object.
* - @code {"error":"`id` expected to be integer"} @endcode
* On error, when `argv[1]` is not parsable to an unsigned integer.
* - @code {"error":"`id` is invalid"} @endcode
* On error, when `argv[1]` is a valid unsigned integer but is an unknown
* value to the application.
*
* @retval 0 on success.
* @retval 1 on error. Only can happen if `argv[1]` is provided and an invalid
* or unexpected value.
*/
int congure_test_call_setup(int argc, char **argv);
/**
* @brief Calls `init()` method for CongURE state object.
*
* @see congure_snd_driver_t::init()
*
* @param[in] argc Number of @p argv. Needs to be at least 2.
* @param[in] argv Command line arguments. `argv[0]` needs to be the command
* name and `argv[1]` needs to be a hexadecimal integer of
* format 0xXXXX, represending a pointer to the object used as
* the `ctx` parameter for `init()`.
*
* This function will generate the following JSON objects in STDOUT on error:
* - @code {"error":"State object not set up"} @endcode
* When @ref congure_test_snd_setup() was not called before calling this
* command (i.e. the `driver` member of the state object is `NULL`).
* - @code {"error":"`ctx` argument expected"} @endcode
* When @p argc < 2.
* - @code {"error":"`ctx` expected to be hex"} @endcode
* When `argv[1]` is not parseable as a hexadecimal integer.
*
* Always generates the following JSON object in STDOUT:
* @code {"success": null} @endcode
*
* @retval 0 on success.
* @retval 1 on error.
*/
int congure_test_call_init(int argc, char **argv);
/**
* @brief Calls `inter_msg_interval()` method for CongURE state object.
*
* @see congure_snd_driver_t::inter_msg_interval()
*
* @param[in] argc Number of @p argv. Needs to be at least 1.
* @param[in] argv Command line arguments. No extra arguments are required
* except for the command name in `argv[0]`.
*
* This function will generate the following JSON objects in STDOUT:
* - @code {"success":X} @endcode
* On success, with `X` being replaced with the return value of
* congure_snd_driver_t::inter_msg_interval().
* - @code {"error":"State object not set up"} @endcode
* When @ref congure_test_snd_setup() was not called before calling this
* command (i.e. the `driver` member of the state object is `NULL`).
*
*
* @retval 0 on success.
* @retval 1 on error.
*/
int congure_test_call_inter_msg_interval(int argc, char **argv);
/**
* @brief Calls one of the `report_*()` methods for CongURE state object.
*
* @see congure_snd_driver_t::report_msg_sent()
* @see congure_snd_driver_t::report_msg_discarded()
* @see congure_snd_driver_t::report_msg_timeout()
* @see congure_snd_driver_t::report_msg_lost()
* @see congure_snd_driver_t::report_msg_acked()
* @see congure_snd_driver_t::report_ecn_ce()
*
* @param[in] argc Number of @p argv. Needs to be at least 3.
* @param[in] argv Command line arguments. `argv[0]` needs to be the command
* name and `argv[1]` needs to one of the following
* sub-commands that may require at least one extra arguments:
* - `msg_sent`: `argv[2]` is expected to be an integer for the
* `msg_size` parameter of
* congure_snd_driver_t::report_msg_sent()
* - `msg_sent`: `argv[2]` is expected to be an integer for the
* `msg_size` parameter of
* congure_snd_driver_t::report_msg_discarded()
* - `msg_timeout`: `argv` is expected to have a number of
* parameters divisible by 3 after `argv[1]` (i.e.
* `(argc - 2) % 3 == 0` must hold). Each group of 3
* `argv[2+i]`, `argv[3+i]`, argv[4+i] (with `i` being the
* offset of the group) represents an element in the `msgs`
* list parameter of
* congure_snd_driver_t::report_msg_timeout():
* - `argv[2+i]` (`msg_send_time`) is expected to be an
* integer for the `send_time` member of
* @ref congure_snd_msg_t,
* - `argv[3+i]` (`msg_size`) is expected to be a an integer
* for the `size` member of @ref congure_snd_msg_t, and
* - `argv[4+i]` (`msg_resends`) is expected to be an integer
* integer for the `resends` member of
* @ref congure_snd_msg_t.
* - `msg_lost`: `argv` is expected to have a number of
* parameters divisible by 3 after `argv[1]` (i.e.
* `(argc - 2) % 3 == 0` must hold. Each group of 3
* `argv[2+i]`, `argv[3+i]`, argv[4+i] (with `i` being the
* offset of the group) represents an element in the `msgs`
* list parameter of
* congure_snd_driver_t::report_msg_lost():
* - `argv[2+i]` (`msg_send_time`) is expected to be an
* integer for the `send_time` member of
* @ref congure_snd_msg_t,
* - `argv[3+i]` (`msg_size`) is expected to be an integer
* for the `size` member of @ref congure_snd_msg_t, and
* - `argv[4+i]` (`msg_resends`) is expected to be a an integer
* integer for the `resends` member of
* @ref congure_snd_msg_t.
* - `msg_acked`: `argc` must be 11. The first three arguments
* after `argv[1]` represent members of the `msg` parameter
* of congure_snd_driver_t::report_msg_acked():
* - `argv[2]` (`msg_send_time`) is expected to be an
* integer for the `send_time` member of
* @ref congure_snd_msg_t,
* - `argv[3]` (`msg_size`) is expected to be an integer
* for the `size` member of @ref congure_snd_msg_t, and
* - `argv[4]` (`msg_resends`) is expected to be an integer
* for the `resends` member of @ref congure_snd_msg_t.
*
* The `next` member of @ref congure_snd_msg_t will be
* initialized with `NULL`.
*
* The remaining 6 arguments represent members of the `ack`
* parameter of congure_snd_driver_t::report_msg_acked():
* - `argv[5]` (`ack_recv_time`) is expected to be a an
* integer for the `recv_time` member of
* @ref congure_snd_ack_t,
* - `argv[6]` (`ack_id`) is expected to be a an integer
* for the `ack_id` member of @ref congure_snd_ack_t, and
* - `argv[7]` (`ack_size`) is expected to be a an integer
* integer for the `size` member of @ref congure_snd_ack_t.
* - `argv[8]` (`ack_clean`) is expected to be a an integer
* for the `clean` member of @ref congure_snd_ack_t. If
* `argv[8]` is `"0"`, `clean` will be set to `false` and to
* `true` otherwise.
* - `argv[9]` (`ack_wnd`) is expected to be a 16-bit integer
* for the `wnd` member of @ref congure_snd_ack_t.
* - `argv[10]` (`ack_delay`) is expected to be a 16-bit
* integer for the `delay` member of
* @ref congure_snd_ack_t.
* - `ecn_ce`: `argv[2]` is expected to be an integer for the
* `time` parameter of congure_snd_driver_t::report_ecn_ce()
*
* This function will generate the following JSON objects in STDOUT:
* - @code {"success":null} @endcode
* On success
* - @code {"error":"State object not set up"} @endcode
* When @ref congure_test_snd_setup() was not called before calling this
* command (i.e. the `driver` member of the state object is `NULL`).
* - @code {"error":"No report command provided"} @endcode
* When @p argc < 2.
* - @code {"error":"Unknown command `<command>`"} @endcode
* When `argv[1] = "<command>"` is not a known sub-command.
* - @code {"error":"`<arg_name>` argument expected"} @endcode
* When `argv[i] = "<arg_name>"` is expected but `argc <= i`.
* - @code {"error":"`<arg_name>` expected to be integer"} @endcode
* When `argv[i] = "<arg_name>"` is expected to be an integer but is not
* parseable
* - @code {"error":"`<arg_name>` expected not 16 bit wide"} @endcode
* When `argv[i] = "<arg_name>"` is expected to be an 16-bit unsigned integer
* but is is greater than or equal to $2^{16}$
* - @code {"error":"At least `<arg_num>` arguments <arglist> expecdted"}
* @endcode
* When `argc` is smaller than expected. `<arg_num>` provides the number of
* arguments beyond the sub-command (i.e. `argc` needs at least to be
* `<arg_num> + 2`), with the names of the arguments expected provided in
* `<arg_list>` as a comma-seperated list of <tt>`<arg_name>`</tt>.
* - @code {"error":"Number of arguments must be divisible by 3"} @endcode
* When `argv[1] == "msg_timeout"` or `argv[1] == "msg_lost"` but
* the length of the argument list after `argv[1]` is not divisible by 3 (i.e.
* `(argc - 2) % 3 != 0`).
* - @code {"error":"List element pool depleted"} @endcode
* When `argv[1] == "msg_timeout"` or `argv[1] == "msg_lost"` and
* `(argc - 2) / 3` >= @ref CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE).
*
* Provides no output on success.
*
* @retval 0 on success.
* @retval 1 on error.
*/
int congure_test_call_report(int argc, char **argv);
#ifdef __cplusplus
}
#endif
#endif /* CONGURE_TEST_H */
/** @} */

View File

@ -22,6 +22,9 @@
#include <stdlib.h>
#include "shell_commands.h"
#ifdef MODULE_CONGURE_TEST
#include "congure/test.h"
#endif
extern int _reboot_handler(int argc, char **argv);
extern int _version_handler(int argc, char **argv);
@ -338,6 +341,17 @@ const shell_command_t _shell_command_list[] = {
#endif
#ifdef MODULE_DFPLAYER
{"dfplayer", "Control a DFPlayer Mini MP3 player", _sc_dfplayer},
#endif
#ifdef MODULE_CONGURE_TEST
{ "cong_clear", "Clears CongURE state object", congure_test_clear_state },
{ "cong_setup", "Calls the setup function for the CongURE state object",
congure_test_call_setup },
{ "cong_init", "Calls init method of the CongURE state object",
congure_test_call_init },
{ "cong_imi", "Calls inter_message_interval method of the CongURE state object",
congure_test_call_inter_msg_interval },
{ "cong_report", "Calls a report_* method of the CongURE state object",
congure_test_call_report },
#endif
{NULL, NULL, NULL}
};

View File

@ -0,0 +1,29 @@
include ../Makefile.tests_common
USEMODULE += congure_mock
USEMODULE += congure_test
USEMODULE += fmt
USEMODULE += shell
USEMODULE += shell_commands
INCLUDES += -I$(CURDIR)
# Use a terminal that does not introduce extra characters into the stream.
RIOT_TERMINAL ?= socat
# As there is an 'app.config' we want to explicitly disable Kconfig by setting
# the variable to empty
SHOULD_RUN_KCONFIG ?=
include $(RIOTBASE)/Makefile.include
ifndef CONFIG_SHELL_NO_ECHO
CFLAGS += -DCONFIG_SHELL_NO_ECHO=1
endif
ifndef CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE
CFLAGS += -DCONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE=4
export LOST_MSG_POOL_SIZE=4
else
export LOST_MSG_POOL_SIZE=$(CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE)
endif

View File

@ -0,0 +1,7 @@
BOARD_INSUFFICIENT_MEMORY := \
arduino-duemilanove \
arduino-leonardo \
arduino-nano \
arduino-uno \
atmega328p \
#

View File

@ -0,0 +1,40 @@
Basic tests for the CongURE API
===============================
This test tests the `congure_test` test framework used for the other CongURE
tests.
Usage
-----
The test requires an up-to-date version of `riotctrl` with `rapidjson` support:
```console
$ pip install --upgrade riotctrl[rapidjson]
```
Then simply run the application using:
```console
$ BOARD="<board>" make flash test
```
It can also executed with pytest:
```console
$ pytest tests/01-run.py
```
Note that this only works from within the directory of the test, so if you are
somewhere else, use
```console
$ cd tests/congure_test
```
first to change into that.
Expected result
---------------
The application's test script passes without error code.

View File

@ -0,0 +1,2 @@
CONFIG_KCONFIG_USEMODULE_SHELL=y
CONFIG_SHELL_NO_ECHO=y

View File

@ -0,0 +1,7 @@
CONFIG_MODULE_CONGURE=y
CONFIG_MODULE_CONGURE_MOCK=y
CONFIG_MODULE_CONGURE_TEST=y
CONFIG_MODULE_FMT=y
CONFIG_MODULE_SHELL=y
CONFIG_MODULE_SHELL_COMMANDS=y
CONFIG_SHELL_NO_ECHO=y

View File

@ -0,0 +1,29 @@
/*
* 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.
*/
/**
* @{
*
* @file
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#include "congure/mock.h"
#include "congure_impl.h"
int congure_test_snd_setup(congure_test_snd_t *c, unsigned id)
{
if (id > 0) {
return -1;
}
congure_mock_snd_setup(c);
return 0;
}
/** @} */

View File

@ -0,0 +1,34 @@
/*
* 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.
*/
/**
* @{
*
* @file
*
* @author Martine Lenders <m.lenders@fu-berlin.de>
*/
#ifndef CONGURE_IMPL_H
#define CONGURE_IMPL_H
#include "congure/mock.h"
#ifdef __cplusplus
extern "C" {
#endif
typedef congure_mock_snd_t congure_test_snd_t;
int congure_test_snd_setup(congure_test_snd_t *c, unsigned id);
#ifdef __cplusplus
}
#endif
#endif /* CONGURE_IMPL_H */
/** @} */

333
tests/congure_test/main.c Normal file
View File

@ -0,0 +1,333 @@
/*
* 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.
*/
/**
* @{
*
* @file
* @author Martine S. Lenders <m.lenders@fu-berlin.de>
*/
#include <stdlib.h>
#include "clist.h"
#include "congure/test.h"
#include "fmt.h"
#include "shell.h"
#include "congure_impl.h"
static int _json_statham(int argc, char **argv);
static congure_test_snd_t _congure_state;
static const shell_command_t shell_commands[] = {
{ "state", "Prints current CongURE state object as JSON", _json_statham },
{ NULL, NULL, NULL }
};
int main(void)
{
char line_buf[SHELL_DEFAULT_BUFSIZE];
shell_run(shell_commands, line_buf, SHELL_DEFAULT_BUFSIZE);
return 0;
}
congure_test_snd_t *congure_test_get_state(void)
{
return &_congure_state;
}
static int _print_congure_snd_msg(clist_node_t *node, void *arg)
{
congure_snd_msg_t *msg = (congure_snd_msg_t *)node;
(void)arg;
print_str("{");
print_str("\"send_time\":");
print_u64_dec(msg->send_time);
print_str(",");
print_str("\"size\":");
print_u32_dec(msg->size);
print_str(",");
print_str("\"resends\":");
print_u32_dec(msg->resends);
print_str("},");
return 0;
}
static int _print_congure_snd_ack(congure_snd_ack_t *ack)
{
print_str("{");
print_str("\"recv_time\":");
print_u64_dec(ack->recv_time);
print_str(",");
print_str("\"id\":");
print_u32_dec(ack->id);
print_str(",");
print_str("\"size\":");
print_u32_dec(ack->size);
print_str(",");
print_str("\"clean\":");
print_str(ack->clean ? "true" : "false");
print_str(",");
print_str("\"wnd\":");
print_u32_dec(ack->wnd);
print_str(",");
print_str("\"delay\":");
print_u32_dec(ack->delay);
print_str(",");
print_str("},");
return 0;
}
static void _print_init_state(void)
{
print_str("\"init\":{");
print_str("\"calls\":");
print_u32_dec(_congure_state.init_calls);
print_str(",");
print_str("\"last_args\":{");
print_str("\"c\":\"0x");
print_u32_hex((intptr_t)_congure_state.init_args.c);
print_str("\",");
print_str("\"ctx\":\"0x");
print_u32_hex((intptr_t)_congure_state.init_args.ctx);
print_str("\"");
print_str("}");
print_str("},");
}
static void _print_inter_msg_interval_state(void)
{
print_str("\"inter_msg_interval\":{");
print_str("\"calls\":");
print_u32_dec(_congure_state.inter_msg_interval_calls);
print_str(",");
print_str("\"last_args\":{");
print_str("\"c\":\"0x");
print_u32_hex((intptr_t)_congure_state.inter_msg_interval_args.c);
print_str("\",");
print_str("\"msg_size\":");
print_u32_dec(_congure_state.inter_msg_interval_args.msg_size);
print_str("");
print_str("}");
print_str("},");
}
static void _print_report_msg_sent_state(void)
{
print_str("\"report_msg_sent\":{");
print_str("\"calls\":");
print_u32_dec(_congure_state.report_msg_sent_calls);
print_str(",");
print_str("\"last_args\":{");
print_str("\"c\":\"0x");
print_u32_hex((intptr_t)_congure_state.report_msg_sent_args.c);
print_str("\",");
print_str("\"msg_size\":");
print_u32_dec(_congure_state.report_msg_sent_args.msg_size);
print_str("");
print_str("}");
print_str("},");
}
static void _print_report_msg_discarded_state(void)
{
print_str("\"report_msg_discarded\":{");
print_str("\"calls\":");
print_u32_dec(_congure_state.report_msg_discarded_calls);
print_str(",");
print_str("\"last_args\":{");
print_str("\"c\":\"0x");
print_u32_hex((intptr_t)_congure_state.report_msg_discarded_args.c);
print_str("\",");
print_str("\"msg_size\":");
print_u32_dec(_congure_state.report_msg_discarded_args.msg_size);
print_str("");
print_str("}");
print_str("},");
}
static void _print_report_msgs_timeout_state(void)
{
print_str("\"report_msgs_timeout\":{");
print_str("\"calls\":");
print_u32_dec(_congure_state.report_msgs_timeout_calls);
print_str(",");
print_str("\"last_args\":{");
print_str("\"c\":\"0x");
print_u32_hex((intptr_t)_congure_state.report_msgs_timeout_args.c);
print_str("\",");
print_str("\"msgs\":[");
clist_foreach((clist_node_t *)&_congure_state.report_msgs_timeout_args.msgs,
_print_congure_snd_msg, NULL);
print_str("]");
print_str("}");
print_str("},");
}
static void _print_report_msgs_lost_state(void)
{
print_str("\"report_msgs_lost\":{");
print_str("\"calls\":");
print_u32_dec(_congure_state.report_msgs_lost_calls);
print_str(",");
print_str("\"last_args\":{");
print_str("\"c\":\"0x");
print_u32_hex((intptr_t)_congure_state.report_msgs_lost_args.c);
print_str("\",");
print_str("\"msgs\":[");
clist_foreach((clist_node_t *)&_congure_state.report_msgs_lost_args.msgs,
_print_congure_snd_msg, NULL);
print_str("]");
print_str("}");
print_str("},");
}
static void _print_report_msg_acked_state(void)
{
print_str("\"report_msg_acked\":{");
print_str("\"calls\":");
print_u32_dec(_congure_state.report_msg_acked_calls);
print_str(",");
print_str("\"last_args\":{");
print_str("\"c\":\"0x");
print_u32_hex((intptr_t)_congure_state.report_msg_acked_args.c);
print_str("\",");
assert((_congure_state.report_msg_acked_args.msg == NULL) ||
(_congure_state.report_msg_acked_args.msg->super.next == NULL));
print_str("\"msg\":");
if (_congure_state.report_msg_acked_args.msg) {
_print_congure_snd_msg(
(clist_node_t *)_congure_state.report_msg_acked_args.msg, NULL
);
}
else {
print_str("null,");
}
print_str("\"ack\":");
if (_congure_state.report_msg_acked_args.ack) {
_print_congure_snd_ack(_congure_state.report_msg_acked_args.ack);
}
else {
print_str("null");
}
print_str("}");
print_str("},");
}
static void _print_report_ecn_ce_state(void)
{
print_str("\"report_ecn_ce\":{");
print_str("\"calls\":");
print_u32_dec(_congure_state.report_ecn_ce_calls);
print_str(",");
print_str("\"last_args\":{");
print_str("\"c\":\"0x");
print_u32_hex((intptr_t)_congure_state.report_ecn_ce_args.c);
print_str("\",");
print_str("\"time\":");
print_u64_dec((intptr_t)_congure_state.report_ecn_ce_args.time);
print_str(",");
print_str("}");
print_str("},");
}
static int _json_statham(int argc, char **argv)
{
(void)argc;
(void)argv;
print_str("{");
print_str("\"driver\":\"0x");
print_u32_hex((intptr_t)_congure_state.super.driver);
print_str("\",");
print_str("\"ctx\":\"0x");
print_u32_hex((intptr_t)_congure_state.super.ctx);
print_str("\",");
print_str("\"cwnd\":");
print_u32_dec(_congure_state.super.cwnd);
print_str(",");
_print_init_state();
_print_inter_msg_interval_state();
_print_report_msg_sent_state();
_print_report_msg_discarded_state();
_print_report_msgs_timeout_state();
_print_report_msgs_lost_state();
_print_report_msg_acked_state();
_print_report_ecn_ce_state();
print_str("}\n");
return 0;
}
/** @} */

View File

@ -0,0 +1,450 @@
#! /usr/bin/env python3
# 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.
import logging
import os
import sys
import unittest
from riotctrl.ctrl import RIOTCtrl
from riotctrl.shell import ShellInteraction
from riotctrl.shell.json import RapidJSONShellInteractionParser, rapidjson
class TestCongUREBase(unittest.TestCase):
DEBUG = False
@classmethod
def setUpClass(cls):
cls.ctrl = RIOTCtrl()
cls.ctrl.start_term()
if cls.DEBUG:
cls.ctrl.term.logfile = sys.stdout
cls.ctrl.reset()
cls.shell = ShellInteraction(cls.ctrl)
cls.json_parser = RapidJSONShellInteractionParser()
cls.json_parser.set_parser_args(
parse_mode=rapidjson.PM_TRAILING_COMMAS
)
cls.logger = logging.getLogger(cls.__name__)
if cls.DEBUG:
cls.logger.setLevel(logging.DEBUG)
@classmethod
def tearDownClass(cls):
cls.ctrl.stop_term()
def setUp(self):
self.shell.cmd('cong_clear')
def exec_cmd(self, cmd, timeout=-1, async_=False):
res = self.shell.cmd(cmd, timeout, async_)
self.logger.debug(repr(res))
if res.strip():
return self.json_parser.parse(res)
return None
class TestCongUREWithoutSetup(TestCongUREBase):
def test_no_setup(self):
state = self.exec_cmd('state')
self.assertEqual(state, {
'driver': '0x00000000',
'ctx': '0x00000000',
'cwnd': 0,
'init': {
'calls': 0,
'last_args': {
'c': '0x00000000',
'ctx': '0x00000000',
},
},
'inter_msg_interval': {
'calls': 0,
'last_args': {
'c': '0x00000000',
'msg_size': 0,
},
},
'report_msg_sent': {
'calls': 0,
'last_args': {
'c': '0x00000000',
'msg_size': 0,
},
},
'report_msg_discarded': {
'calls': 0,
'last_args': {
'c': '0x00000000',
'msg_size': 0,
},
},
'report_msgs_timeout': {
'calls': 0,
'last_args': {
'c': '0x00000000',
'msgs': [],
},
},
'report_msgs_lost': {
'calls': 0,
'last_args': {
'c': '0x00000000',
'msgs': [],
},
},
'report_msg_acked': {
'calls': 0,
'last_args': {
'c': '0x00000000',
'msg': None,
'ack': None,
},
},
'report_ecn_ce': {
'calls': 0,
'last_args': {
'c': '0x00000000',
'time': 0,
},
},
})
def test_setup_invalid_id(self):
self.test_no_setup()
res = self.exec_cmd('cong_setup abcd')
self.assertEqual(res, {'error': "`id` expected to be integer"})
res = self.exec_cmd('cong_setup 1')
self.assertEqual(res, {'error': "`id` is invalid"})
def test_setup(self):
self.test_no_setup()
res = self.exec_cmd('cong_setup')
# res['success'] is 32-bit hexadecimal number greater zero, representing
# the pointer to the congure state object
self.assertGreater(int(res['success'], base=16), 0)
self.assertEqual(len(res['success']), len('0x00000000'))
res = self.exec_cmd('cong_setup 0')
# res['success'] is 32-bit hexadecimal number greater zero, representing
# the pointer to the congure state object
self.assertGreater(int(res['success'], base=16), 0)
self.assertEqual(len(res['success']), len('0x00000000'))
def test_init_wo_setup(self):
res = self.exec_cmd('cong_init 0x12345')
self.assertEqual(res, {'error': "State object not set up"})
def test_inter_msg_interval_wo_setup(self):
res = self.exec_cmd('cong_imi 689')
self.assertEqual(res, {'error': "State object not set up"})
def test_report_wo_setup(self):
res = self.exec_cmd('cong_report')
self.assertEqual(res, {'error': "State object not set up"})
class TestCongUREWithSetup(TestCongUREBase):
def setUp(self):
super().setUp()
res = self.exec_cmd('cong_setup')
self.congure_state_ptr = int(res['success'], base=16)
def test_init_no_args(self):
res = self.exec_cmd('cong_init')
self.assertEqual(res, {'error': "`ctx` argument expected"})
def test_init_arg_not_hex(self):
res = self.exec_cmd('cong_init foobar')
self.assertEqual(res, {'error': "`ctx` expected to be hex"})
res = self.exec_cmd('cong_init 123456')
self.assertEqual(res, {'error': "`ctx` expected to be hex"})
def test_init_success(self):
ctx = 0x12345
res = self.exec_cmd('cong_init 0x{ctx:x}'.format(ctx=ctx))
self.assertIsNone(res['success'])
res = self.exec_cmd('state')
self.assertEqual(res['init']['calls'], 1)
self.assertEqual(int(res['init']['last_args']['c'], base=16),
self.congure_state_ptr)
self.assertEqual(int(res['init']['last_args']['ctx'], base=16),
ctx)
def test_inter_msg_interval_no_args(self):
res = self.exec_cmd('cong_imi')
self.assertEqual(res, {'error': '`msg_size` argument expected'})
def test_inter_msg_interval_not_int(self):
res = self.exec_cmd('cong_imi foobar')
self.assertEqual(res, {'error': '`msg_size` expected to be integer'})
def test_inter_msg_interval_success(self):
msg_size = 521
res = self.exec_cmd('cong_imi {msg_size}'.format(msg_size=msg_size))
assert res == {'success': -1}
res = self.exec_cmd('state')
self.assertEqual(res['inter_msg_interval']['calls'], 1)
self.assertEqual(int(res['inter_msg_interval']['last_args']['c'],
base=16),
self.congure_state_ptr)
self.assertEqual(res['inter_msg_interval']['last_args']['msg_size'],
msg_size)
def test_report_no_args(self):
res = self.exec_cmd('cong_report foobar')
self.assertEqual(res, {'error': 'Unknown command `foobar`'})
def test_report_unknown_command(self):
res = self.exec_cmd('cong_report')
self.assertEqual(res, {'error': 'No report command provided'})
def test_report_msg_sent_no_args(self):
res = self.exec_cmd('cong_report msg_sent')
self.assertEqual(res, {'error': '`msg_size` argument expected'})
def test_report_msg_sent_not_int(self):
res = self.exec_cmd('cong_report msg_sent this one')
self.assertEqual(res, {'error': '`msg_size` expected to be integer'})
def test_report_msg_sent_success(self):
msg_size = 1234
res = self.exec_cmd('cong_report msg_sent {msg_size}'
.format(msg_size=msg_size))
self.assertIsNone(res['success'])
res = self.exec_cmd('state')
self.assertEqual(res['report_msg_sent']['calls'], 1)
self.assertEqual(int(res['report_msg_sent']['last_args']['c'],
base=16),
self.congure_state_ptr)
self.assertEqual(res['report_msg_sent']['last_args']['msg_size'],
msg_size)
def test_report_msg_discarded_no_args(self):
res = self.exec_cmd('cong_report msg_discarded')
self.assertEqual(res, {'error': "`msg_size` argument expected"})
def test_report_msg_discarded_not_int(self):
res = self.exec_cmd('cong_report msg_discarded this one')
self.assertEqual(res, {'error': "`msg_size` expected to be integer"})
def test_report_msg_discarded_success(self):
msg_size = 1234
res = self.exec_cmd('cong_report msg_discarded {msg_size}'
.format(msg_size=msg_size))
self.assertIsNone(res['success'])
res = self.exec_cmd('state')
self.assertEqual(res['report_msg_discarded']['calls'], 1)
self.assertEqual(int(res['report_msg_discarded']['last_args']['c'],
base=16),
self.congure_state_ptr)
self.assertEqual(res['report_msg_discarded']['last_args']['msg_size'],
msg_size)
def _report_msgs_timeout_lost_acked_not_enough_args(self, cmd, exp_params):
args = ""
# gradually append more arguments but never get full set
for i in range(len(exp_params) - 1):
args += ' {}'.format(i + 1)
res = self.exec_cmd('cong_report {cmd} {args}'
.format(cmd=cmd, args=args))
self.assertEqual(res, {
'error': 'At least {} arguments {} expected'
.format(len(exp_params),
', '.join('`{}`'.format(p)
for p in exp_params))
})
def _report_msgs_timeout_lost_argc_not_mod_3(self, cmd):
res = self.exec_cmd('cong_report {cmd} 1 2 3 4'.format(cmd=cmd))
self.assertEqual(res, {
'error': 'Number of arguments must be divisible by 3'
})
res = self.exec_cmd('cong_report {cmd} 1 2 3 4 5'.format(cmd=cmd))
self.assertEqual(res, {
'error': 'Number of arguments must be divisible by 3'
})
def _report_msgs_timeout_lost_acked_args_not_int(self, cmd, exp_params):
# generate list of arguments that are exp_params string parameters
args = [chr(i + ord('a')) for i in range(len(exp_params))]
if cmd != 'msg_acked':
# and exp_params integer parameters for msgs_timeout and msgs_lost
args += [str(i + len(exp_params)) for i in range(len(exp_params))]
res = self.exec_cmd('cong_report {} {}'.format(cmd, ' '.join(args)))
self.assertEqual(res, {
'error': '`{}` expected to be integer'.format(exp_params[0])
})
# gradually transform all but the last string to integer and test again
for i in range(len(exp_params) - 1):
args[i] = str(i + 1)
res = self.exec_cmd(
'cong_report {} {}'.format(cmd, ' '.join(args))
)
self.assertEqual(res, {
'error': '`{}` expected to be integer'
.format(exp_params[i + 1])
})
def _report_msgs_timeout_lost_exceed_msg_pool_size(self, cmd):
# expected to be set by Makefile
pool_size = int(os.environ.get('LOST_MSG_POOL_SIZE', 4))
args = ' '.join('1' for _ in range(3 * pool_size))
args += ' 1 1 1'
res = self.exec_cmd('cong_report {cmd} {args}'
.format(cmd=cmd, args=args))
self.assertEqual(res, {
'error': 'List element pool depleted'
})
def _report_msgs_timeout_lost_success(self, cmd):
msgs = [{'send_time': 76543, 'size': 1234, 'resends': 2},
{'send_time': 5432, 'size': 987, 'resends': 32}]
res = self.exec_cmd(
'cong_report {cmd} '
'{msgs[0][send_time]} {msgs[0][size]} {msgs[0][resends]} '
'{msgs[1][send_time]} {msgs[1][size]} {msgs[1][resends]}'
.format(cmd=cmd, msgs=msgs)
)
self.assertIsNone(res['success'])
res = self.exec_cmd('state')
self.assertEqual(res['report_{}'.format(cmd)]['calls'], 1)
self.assertEqual(int(res['report_{}'.format(cmd)]['last_args']['c'],
base=16),
self.congure_state_ptr)
self.assertEqual(res['report_{}'.format(cmd)]['last_args']['msgs'],
msgs)
def test_report_msgs_timeout_not_enough_args(self):
self._report_msgs_timeout_lost_acked_not_enough_args(
'msgs_timeout', ['msg_send_time', 'msg_size', 'msg_resends']
)
def test_report_msgs_timeout_argc_not_mod_3(self):
self._report_msgs_timeout_lost_argc_not_mod_3('msgs_timeout')
def test_report_msgs_timeout_args_not_int(self):
self._report_msgs_timeout_lost_acked_args_not_int(
'msgs_timeout', ['msg_send_time', 'msg_size', 'msg_resends']
)
def test_report_msgs_timeout_exceed_msg_pool_size(self):
self._report_msgs_timeout_lost_exceed_msg_pool_size('msgs_timeout')
def test_report_msgs_timeout_success(self):
self._report_msgs_timeout_lost_success('msgs_timeout')
def test_report_msgs_lost_not_enough_args(self):
self._report_msgs_timeout_lost_acked_not_enough_args(
'msgs_lost', ['msg_send_time', 'msg_size', 'msg_resends']
)
def test_report_msgs_lost_argc_not_mod_3(self):
self._report_msgs_timeout_lost_argc_not_mod_3('msgs_lost')
def test_report_msgs_lost_msg_args_not_int(self):
self._report_msgs_timeout_lost_acked_args_not_int(
'msgs_lost', ['msg_send_time', 'msg_size', 'msg_resends']
)
def test_report_msgs_lost_exceed_msg_pool_size(self):
self._report_msgs_timeout_lost_exceed_msg_pool_size('msgs_lost')
def test_report_msgs_lost_success(self):
self._report_msgs_timeout_lost_success('msgs_lost')
def test_report_msg_acked_not_enough_args(self):
self._report_msgs_timeout_lost_acked_not_enough_args(
'msg_acked', [
'msg_send_time', 'msg_size', 'msg_resends', 'ack_recv_time',
'ack_id', 'ack_size', 'ack_clean', 'ack_wnd', 'ack_delay',
]
)
def test_report_msg_acked_msg_args_not_int(self):
self._report_msgs_timeout_lost_acked_args_not_int(
'msg_acked', [
'msg_send_time', 'msg_size', 'msg_resends', 'ack_recv_time',
'ack_id', 'ack_size', 'ack_clean', 'ack_wnd', 'ack_delay',
]
)
def test_report_msg_acked_wnd_not_wnd_size(self):
msg = {'send_time': 12345, 'size': 456, 'resends': 0}
ack = {'recv_time': 12432, 'id': 18846, 'size': 12,
'clean': 1, 'wnd': (1 << 16) + 7642, 'delay': 1235}
res = self.exec_cmd(
'cong_report msg_acked '
'{msg[send_time]} {msg[size]} {msg[resends]} '
'{ack[recv_time]} {ack[id]} {ack[size]} {ack[clean]} '
'{ack[wnd]} {ack[delay]}'
.format(msg=msg, ack=ack)
)
self.assertEqual(res, {
'error': '`ack_wnd` not 16 bit wide'
})
def test_report_msg_acked_delay_not_uint16(self):
msg = {'send_time': 12345, 'size': 456, 'resends': 0}
ack = {'recv_time': 12432, 'id': 18846, 'size': 12,
'clean': 1, 'wnd': 7642, 'delay': (1 << 16) + 1235}
res = self.exec_cmd(
'cong_report msg_acked '
'{msg[send_time]} {msg[size]} {msg[resends]} '
'{ack[recv_time]} {ack[id]} {ack[size]} {ack[clean]} '
'{ack[wnd]} {ack[delay]}'
.format(msg=msg, ack=ack)
)
self.assertEqual(res, {
'error': '`ack_delay` not 16 bit wide'
})
def test_report_msg_acked_success(self):
msg = {'send_time': 12345, 'size': 456, 'resends': 0}
ack = {'recv_time': 12432, 'id': 18846, 'size': 12,
'clean': 1, 'wnd': 742, 'delay': 1235}
res = self.exec_cmd(
'cong_report msg_acked '
'{msg[send_time]} {msg[size]} {msg[resends]} '
'{ack[recv_time]} {ack[id]} {ack[size]} {ack[clean]} '
'{ack[wnd]} {ack[delay]}'
.format(msg=msg, ack=ack)
)
self.assertIsNone(res['success'])
res = self.exec_cmd('state')
self.assertEqual(res['report_msg_acked']['calls'], 1)
self.assertEqual(int(res['report_msg_acked']['last_args']['c'],
base=16),
self.congure_state_ptr)
self.assertEqual(res['report_msg_acked']['last_args']['msg'], msg)
self.assertEqual(res['report_msg_acked']['last_args']['ack'], ack)
def test_report_ecn_ce_no_args(self):
res = self.exec_cmd('cong_report ecn_ce')
self.assertEqual(res, {'error': '`time` argument expected'})
def test_report_ecn_ce_not_int(self):
res = self.exec_cmd('cong_report ecn_ce this one')
self.assertEqual(res, {'error': '`time` expected to be integer'})
def test_report_ecn_ce_success(self):
time = 64352
res = self.exec_cmd('cong_report ecn_ce {time}'.format(time=time))
self.assertIsNone(res['success'])
res = self.exec_cmd('state')
self.assertEqual(res['report_ecn_ce']['calls'], 1)
self.assertEqual(int(res['report_ecn_ce']['last_args']['c'],
base=16),
self.congure_state_ptr)
self.assertEqual(res['report_ecn_ce']['last_args']['time'],
time)
if __name__ == '__main__':
unittest.main()