From a4e7c93d8fc3bb878a834ec6802ccce3b8903caf Mon Sep 17 00:00:00 2001 From: Martine Lenders Date: Tue, 9 Feb 2021 18:26:43 +0100 Subject: [PATCH] tests: Initial import of `congure_abe` tests --- tests/congure_abe/Makefile | 18 ++ tests/congure_abe/Makefile.ci | 8 + tests/congure_abe/README.md | 30 +++ tests/congure_abe/app.config | 4 + tests/congure_abe/app.config.test | 9 + tests/congure_abe/congure_impl.c | 76 +++++++ tests/congure_abe/congure_impl.h | 36 ++++ tests/congure_abe/main.c | 151 ++++++++++++++ tests/congure_abe/tests/01-run.py | 332 ++++++++++++++++++++++++++++++ 9 files changed, 664 insertions(+) create mode 100644 tests/congure_abe/Makefile create mode 100644 tests/congure_abe/Makefile.ci create mode 100644 tests/congure_abe/README.md create mode 100644 tests/congure_abe/app.config create mode 100644 tests/congure_abe/app.config.test create mode 100644 tests/congure_abe/congure_impl.c create mode 100644 tests/congure_abe/congure_impl.h create mode 100644 tests/congure_abe/main.c create mode 100755 tests/congure_abe/tests/01-run.py diff --git a/tests/congure_abe/Makefile b/tests/congure_abe/Makefile new file mode 100644 index 0000000000..4744deaf04 --- /dev/null +++ b/tests/congure_abe/Makefile @@ -0,0 +1,18 @@ +include ../Makefile.tests_common + +USEMODULE += congure_abe +USEMODULE += congure_test +USEMODULE += fmt +USEMODULE += shell + +INCLUDES += -I$(CURDIR) + +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=6 +endif diff --git a/tests/congure_abe/Makefile.ci b/tests/congure_abe/Makefile.ci new file mode 100644 index 0000000000..1152ca53bc --- /dev/null +++ b/tests/congure_abe/Makefile.ci @@ -0,0 +1,8 @@ +BOARD_INSUFFICIENT_MEMORY := \ + arduino-duemilanove \ + arduino-leonardo \ + arduino-nano \ + arduino-uno \ + atmega328p \ + atmega328p-xplained-mini \ + # diff --git a/tests/congure_abe/README.md b/tests/congure_abe/README.md new file mode 100644 index 0000000000..e690918536 --- /dev/null +++ b/tests/congure_abe/README.md @@ -0,0 +1,30 @@ +Tests for the CongURE TCP ABE implementation +============================================= + +This test tests the `congure_abe` implementation. + +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="" make flash test +``` + +It can also executed with pytest: + +```console +$ pytest tests/01-run.py +``` + +Expected result +--------------- + +The application's test script passes without error code. diff --git a/tests/congure_abe/app.config b/tests/congure_abe/app.config new file mode 100644 index 0000000000..1a812b8b04 --- /dev/null +++ b/tests/congure_abe/app.config @@ -0,0 +1,4 @@ +CONFIG_KCONFIG_USEMODULE_CONGURE_TEST=y +CONFIG_KCONFIG_USEMODULE_SHELL=y +CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE=6 +CONFIG_SHELL_NO_ECHO=y diff --git a/tests/congure_abe/app.config.test b/tests/congure_abe/app.config.test new file mode 100644 index 0000000000..ca5263607c --- /dev/null +++ b/tests/congure_abe/app.config.test @@ -0,0 +1,9 @@ +CONFIG_MODULE_CONGURE=y +CONFIG_MODULE_CONGURE_ABE=y +CONFIG_MODULE_CONGURE_TEST=y +CONFIG_MODULE_FMT=y +CONFIG_MODULE_SEQ=y +CONFIG_MODULE_SHELL=y + +CONFIG_CONGURE_TEST_LOST_MSG_POOL_SIZE=6 +CONFIG_SHELL_NO_ECHO=y diff --git a/tests/congure_abe/congure_impl.c b/tests/congure_abe/congure_impl.c new file mode 100644 index 0000000000..44eaaefdc0 --- /dev/null +++ b/tests/congure_abe/congure_impl.c @@ -0,0 +1,76 @@ +/* + * 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 + */ + +#include +#include "kernel_defines.h" + +#include "congure_impl.h" + +static unsigned _fr_calls; +static bool _same_wnd_adv_res; + +static void _fr(congure_abe_snd_t *c); +static bool _same_wnd_adv(congure_abe_snd_t *c, congure_snd_ack_t *ack); + +static const congure_abe_snd_consts_t _consts[] = { + { + .reno = { + .fr = _fr, + .same_wnd_adv = _same_wnd_adv, + .init_mss = 1460, + .cwnd_lower = 1095, + .cwnd_upper = 2190, + .init_ssthresh = CONGURE_WND_SIZE_MAX, + .frthresh = 3, + }, + .abe_multiplier_numerator = CONFIG_CONGURE_ABE_MULTIPLIER_NUMERATOR_DEFAULT, + .abe_multiplier_denominator = CONFIG_CONGURE_ABE_MULTIPLIER_DENOMINATOR_DEFAULT, + } +}; + +int congure_test_snd_setup(congure_test_snd_t *c, unsigned id) +{ + if (id >= ARRAY_SIZE(_consts)) { + return -1; + } + _fr_calls = 0; + congure_abe_snd_setup(c, &_consts[id]); + return 0; +} + +unsigned congure_abe_test_get_fr_calls(void) +{ + return _fr_calls; +} + +void congure_abe_test_set_same_wnd_adv_res(bool value) +{ + _same_wnd_adv_res = value; +} + +static void _fr(congure_abe_snd_t *c) +{ + (void)c; + _fr_calls++; +} + +static bool _same_wnd_adv(congure_abe_snd_t *c, congure_snd_ack_t *ack) +{ + (void)c; + (void)ack; + return _same_wnd_adv_res; +} + +/** @} */ diff --git a/tests/congure_abe/congure_impl.h b/tests/congure_abe/congure_impl.h new file mode 100644 index 0000000000..bb9df42a8c --- /dev/null +++ b/tests/congure_abe/congure_impl.h @@ -0,0 +1,36 @@ +/* + * 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 + */ +#ifndef CONGURE_IMPL_H +#define CONGURE_IMPL_H + +#include "congure/abe.h" + +#ifdef __cplusplus +extern "C" { +#endif + +typedef congure_abe_snd_t congure_test_snd_t; + +int congure_test_snd_setup(congure_test_snd_t *c, unsigned id); +unsigned congure_abe_test_get_fr_calls(void); +void congure_abe_test_set_same_wnd_adv_res(bool value); + +#ifdef __cplusplus +} +#endif + +#endif /* CONGURE_IMPL_H */ +/** @} */ diff --git a/tests/congure_abe/main.c b/tests/congure_abe/main.c new file mode 100644 index 0000000000..8180c6bbb2 --- /dev/null +++ b/tests/congure_abe/main.c @@ -0,0 +1,151 @@ +/* + * 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 + */ + +#include +#include +#include + +#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 int _set_cwnd(int argc, char **argv); +static int _get_fr_calls(int argc, char **argv); +static int _set_same_wnd_adv_res(int argc, char **argv); + +static congure_abe_snd_t _congure_state; +static const shell_command_t shell_commands[] = { + { "state", "Prints current CongURE state object as JSON", _json_statham }, + { "set_cwnd", "Set cwnd member for CongURE state object", _set_cwnd }, + { "get_ff_calls", + "Get the number of calls to fast_retransmit callback of CongURE state " + "object", _get_fr_calls }, + { "set_same_wnd_adv", + "Set the result for the same_window_advertised callback of CongURE state " + "object", _set_same_wnd_adv_res }, + { 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; +} + +#define PRINT_FIELD_PTR(obj_ptr, field) \ + print_str("\"" #field "\":\"0x"); \ + print_u32_hex((intptr_t)((obj_ptr)->field)); \ + print_str("\",") + +#define PRINT_FIELD_UINT(obj, field) \ + print_str("\"" #field "\":"); \ + print_u32_dec((obj).field); \ + print_str(",") + +static void _print_congure_abe_consts(const congure_abe_snd_consts_t *consts) +{ + print_str("\"consts\":"); + + if (consts) { + print_str("{"); + PRINT_FIELD_PTR(&consts->reno, fr); + PRINT_FIELD_PTR(&consts->reno, same_wnd_adv); + PRINT_FIELD_PTR(&consts->reno, ss_cwnd_inc); + PRINT_FIELD_PTR(&consts->reno, ca_cwnd_inc); + PRINT_FIELD_PTR(&consts->reno, fr_cwnd_dec); + PRINT_FIELD_UINT(consts->reno, init_mss); + PRINT_FIELD_UINT(consts->reno, cwnd_upper); + PRINT_FIELD_UINT(consts->reno, cwnd_lower); + PRINT_FIELD_UINT(consts->reno, init_ssthresh); + PRINT_FIELD_UINT(consts->reno, frthresh); + PRINT_FIELD_UINT(*consts, abe_multiplier_numerator); + PRINT_FIELD_UINT(*consts, abe_multiplier_denominator); + print_str("},"); + } + else { + print_str("null,"); + } +} + +static int _json_statham(int argc, char **argv) +{ + (void)argc; + (void)argv; + print_str("{"); + + PRINT_FIELD_UINT(_congure_state.super, cwnd); + _print_congure_abe_consts( + (congure_abe_snd_consts_t *)_congure_state.consts + ); + PRINT_FIELD_UINT(_congure_state, mss); + PRINT_FIELD_UINT(_congure_state, last_ack); + PRINT_FIELD_UINT(_congure_state, ssthresh); + PRINT_FIELD_UINT(_congure_state, in_flight_size); + PRINT_FIELD_UINT(_congure_state, dup_acks); + + print_str("}\n"); + return 0; +} + +static int _set_cwnd(int argc, char **argv) +{ + uint32_t tmp; + + if (argc < 2) { + print_str("{\"error\":\"`cwnd` argument expected\"}"); + return 1; + } + tmp = scn_u32_dec(argv[1], strlen(argv[1])); + if (tmp > CONGURE_WND_SIZE_MAX) { + print_str("{\"error\":\"`ssthresh` not 16 bit wide\"}\n"); + } + _congure_state.super.cwnd = (congure_wnd_size_t)tmp; + return 0; +} + +static int _get_fr_calls(int argc, char **argv) +{ + (void)argc; + (void)argv; + + print_str("{\"fr_calls\":"); + print_u32_dec(congure_abe_test_get_fr_calls()); + print_str("}\n"); + return 0; +} + +static int _set_same_wnd_adv_res(int argc, char **argv) +{ + if (argc < 2) { + print_str("{\"error\":\"`value` argument expected\"}"); + return 1; + } + congure_abe_test_set_same_wnd_adv_res( + (bool)scn_u32_dec(argv[1], strlen(argv[1])) + ); + return 0; +} + +/** @} */ diff --git a/tests/congure_abe/tests/01-run.py b/tests/congure_abe/tests/01-run.py new file mode 100755 index 0000000000..e9e287974d --- /dev/null +++ b/tests/congure_abe/tests/01-run.py @@ -0,0 +1,332 @@ +#! /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 sys +import unittest + +from riotctrl.ctrl import RIOTCtrl +from riotctrl.shell.json import RapidJSONShellInteractionParser, rapidjson + +from riotctrl_shell.congure_test import CongureTest + + +class TestCongUREBase(unittest.TestCase): + # pylint: disable=too-many-public-methods + # it's just one more ... + 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 = CongureTest(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.clear() + + def tearDown(self): + self.shell.msgs_reset() + + def _parse(self, res): + self.logger.debug(res) + if res.strip(): + return self.json_parser.parse(res) + return None + + def exec_cmd(self, cmd, timeout=-1, async_=False): + res = self.shell.cmd(cmd, timeout, async_) + return self._parse(res) + + def assertSlowStart(self, state): + # pylint: disable=invalid-name + # trying to be in line with `unittest` + """ + > The slow start algorithm is used when cwnd < ssthresh, while the + > congestion avoidance algorithm is used when cwnd > ssthresh. When + > cwnd and ssthresh are equal, the sender may use either slow start or + > congestion avoidance. + """ + self.assertLess(state['cwnd'], state['ssthresh']) + + def assertInFastRetransmit(self, state): + # pylint: disable=invalid-name + # trying to be in line with `unittest` + """ + > The TCP sender SHOULD use the "fast retransmit" algorithm to detect + > and repair loss, based on incoming duplicate ACKs. The fast + > retransmit algorithm uses the arrival of 3 duplicate ACKs [...] as + > an indication that a segment has been lost. + """ + self.assertGreaterEqual(state['dup_acks'], state['consts']['frthresh']) + + def assertNotInFastRetransmit(self, state): + # pylint: disable=invalid-name + # trying to be in line with `unittest` + """Reverse of self.assertInFastRetransmit()""" + self.assertLess(state['dup_acks'], state['consts']['frthresh']) + + def get_ff_calls(self): + res = self.exec_cmd('get_ff_calls') + return res['fr_calls'] + + def set_same_wnd_adv(self, value): + self.exec_cmd(f'set_same_wnd_adv {value:d}') + + def set_cwnd(self, cwnd): + self.exec_cmd(f'set_cwnd {cwnd}') + + def cong_state(self): + return self.exec_cmd('state') + + def cong_init(self, ctx=0): + res = self.shell.init(ctx) + return self._parse(res) + + def cong_report_msg_sent(self, msg_size): + res = self.shell.report_msg_sent(msg_size) + return self._parse(res) + + def cong_report_msg_acked(self, msg, ack): + res = self.shell.report_msg_acked(msg, ack) + return self._parse(res) + + def cong_report_ecn_ce(self, time): + res = self.shell.report_ecn_ce(time) + return self._parse(res) + + +class TestCongUREABEWithoutSetup(TestCongUREBase): + def test_no_setup(self): + state = self.exec_cmd('state') + self.assertEqual(state, { + 'cwnd': 0, + 'consts': None, + 'mss': 0, + 'last_ack': 0, + 'ssthresh': 0, + 'in_flight_size': 0, + 'dup_acks': 0, + }) + + +class TestCongUREABEDefaultInitTests(TestCongUREBase): + def setUp(self): + super().setUp() + res = self.shell.setup(0) + self.assertIn('success', res) + + def test_setup(self): + state = self.cong_state() + self.assertIsNotNone(state['consts']) + # fast_retransmit and same_window_advertised need to be set to a + # function pointer + self.assertNotEqual(int(state['consts']['fr'], base=16), 0) + self.assertNotEqual(int(state['consts']['same_wnd_adv'], base=16), 0) + # ss_cwnd_inc, ca_cwnd_inc, fr_cwnd_dec are optional and setup 0 need + # to be set to a function pointer + self.assertEqual(int(state['consts']['ss_cwnd_inc'], base=16), 0) + self.assertEqual(int(state['consts']['ca_cwnd_inc'], base=16), 0) + self.assertEqual(int(state['consts']['fr_cwnd_dec'], base=16), 0) + self.assertEqual(state['consts']['init_mss'], 1460) + self.assertEqual(state['consts']['cwnd_lower'], 1095) + self.assertEqual(state['consts']['cwnd_upper'], 2190) + self.assertEqual(state['consts']['init_ssthresh'], 0xffff) + self.assertEqual(state['consts']['frthresh'], 3) + # https://tools.ietf.org/html/rfc8511#section-3.1 + beta_ecn = (state['consts']['abe_multiplier_numerator'] / + state['consts']['abe_multiplier_denominator']) + self.assertAlmostEqual(beta_ecn, 0.8) + self.assertEqual(state['consts']['frthresh'], 3) + + def test_init(self): + """ + This is inherited from `congure_reno`, so it should be the same as + in `tests/congure_reno`. + + https://tools.ietf.org/html/rfc5681#section-3.1 + + > IW, the initial value of cwnd, MUST be set using the following + > guidelines as an upper bound. + > + > If SMSS > 2190 bytes: + > IW = 2 * SMSS bytes and MUST NOT be more than 2 segments + > If (SMSS > 1095 bytes) and (SMSS <= 2190 bytes): + > IW = 3 * SMSS bytes and MUST NOT be more than 3 segments + > if SMSS <= 1095 bytes: + > IW = 4 * SMSS bytes and MUST NOT be more than 4 segments + """ + res = self.cong_init() + self.assertIn('success', res) + state = self.cong_state() + self.assertEqual(state['consts']['init_mss'], state['mss']) + # (SMSS > 1095 bytes) + self.assertGreater(state['mss'], state['consts']['cwnd_lower']) + # (SMSS <= 2190 bytes) + self.assertLessEqual(state['mss'], state['consts']['cwnd_upper']) + # as such, IW = 3 * SMSS bytes + self.assertEqual(state['cwnd'], 3 * state['mss']) + # We start with slow start + self.assertSlowStart(state) + self.assertNotInFastRetransmit(state) + + +class TestCongUREABE(TestCongUREBase): + """ + Most functionality should be the same as for `congure_reno`, except + for the behavior of `report_ecn_ce`. So only test some basics from + `tests/congure_reno` and focus testing on `report_ecn_ce` + """ + def setUp(self): + super().setUp() + res = self.shell.setup(0) + self.assertIn('success', res) + res = self.cong_init() + self.assertIn('success', res) + + def _send_msg_and_recv_ack(self, msg_size, msg_resends=0, + ack_id=15, ack_size=None, ack_clean=True): + # pylint: disable=too-many-arguments + # already reduced number of arguments, cong_report_msg_acked would + # need... + if ack_size is None: + # set ack_size to arbitrary value + ack_size = msg_size + res = self.cong_report_msg_sent(msg_size=msg_size) + self.assertIn('success', res) + state = self.cong_state() + self.assertEqual(state['in_flight_size'], msg_size) + res = self.cong_report_msg_acked( + msg={'send_time': 1000, 'size': msg_size, 'resends': msg_resends}, + ack={'recv_time': 1100, 'id': ack_id, 'size': ack_size, + 'clean': ack_clean, 'wnd': 1234, 'delay': 0}, + ) + self.assertIn('success', res) + + def test_slow_start_increase(self): + # pylint: disable=invalid-name + # name chosen to be in line with RFC + """ + See test_slow_start_increase_large_N() from `tests/congure_reno` + """ + state = self.cong_state() + init_cwnd = state['cwnd'] + init_mss = state['mss'] + init_ssthresh = state['ssthresh'] + self.assertEqual(state['in_flight_size'], 0) + # pylint: disable=invalid-name + # name chosen to be in line with RFC + # set N to larger than SMSS + N = state['mss'] + 1337 + self._send_msg_and_recv_ack(N) + state = self.cong_state() + # MSS did not change + self.assertEqual(state['mss'], init_mss) + self.assertEqual(state['cwnd'], init_cwnd + state['mss']) + self.assertEqual(state['in_flight_size'], 0) + self.assertEqual(state['ssthresh'], init_ssthresh) + self.assertNotInFastRetransmit(state) + + def test_enter_fast_retransmit(self): + """ + See self.test_enter_fast_retransmit_all_check_true() from + `tests/congure_reno` + """ + state = self.cong_state() + self.assertEqual(0, self.get_ff_calls()) + self.assertNotInFastRetransmit(state) + self.assertEqual(state['in_flight_size'], 0) + self._send_msg_and_recv_ack(42, ack_id=15, ack_size=0, ack_clean=True) + # make condition (a) true + self.cong_report_msg_sent(52) + state = self.cong_state() + self.assertEqual(state['in_flight_size'], 52) + # make condition (e) not true + self.set_same_wnd_adv(True) + # condition (b) ack['size'] == 0, (c) ack['clean'] == True, + # (d) ack['id'] == 15 + for _ in range(3): + res = self.cong_report_msg_acked( + msg={'send_time': 1000, 'size': 42, 'resends': 0}, + ack={'recv_time': 1100, 'id': 15, 'size': 0, + 'clean': True, 'wnd': 1234, 'delay': 0}, + ) + self.assertIn('success', res) + self.assertEqual(1, self.get_ff_calls()) + self.assertInFastRetransmit(self.cong_state()) + + def test_ecn_ce_small_flight_size(self): + """ + https://tools.ietf.org/html/rfc8511#section-3 + + > As permitted by RFC 8311, this document specifies a sender-side + > change to TCP where receipt of a packet with the ECN-Echo flag SHOULD + > trigger the TCP source to set the slow start threshold (ssthresh) to + > 0.8 times the FlightSize, with a lower bound of 2 * SMSS applied to + > the result (where SMSS stands for Sender Maximum Segment Size)). As + > in [RFC5681], the TCP sender also reduces the cwnd value to no more + > than the new ssthresh value. Section 6.1.2 of RFC 3168 provides + > guidance on setting a cwnd less than 2 * SMSS. + """ + state = self.cong_state() + beta_ecn = (state['consts']['abe_multiplier_numerator'] / + state['consts']['abe_multiplier_denominator']) + self.assertAlmostEqual(beta_ecn, 0.8) + flight_size = 150 + # put some bytes in the air + self.cong_report_msg_sent(flight_size) + self.cong_report_ecn_ce(1204) + state = self.cong_state() + # [...] set the slow start threshold (ssthresh) to 0.8 times the + # FlightSize, with a lower bound of 2 * SMSS applied to the result + self.assertEqual(state['ssthresh'], 2 * state['mss']) + # the TCP sender also reduces the cwnd value to no more + # than the new ssthresh value + self.assertLessEqual(state['cwnd'], state['ssthresh']) + + def test_ecn_ce_large_flight_size(self): + """ + Same as test_ecn_ce_small_flight_size, but with flight size + larger than (2 * SMSS / 0.8) + """ + state = self.cong_state() + beta_ecn = (state['consts']['abe_multiplier_numerator'] / + state['consts']['abe_multiplier_denominator']) + self.assertAlmostEqual(beta_ecn, 0.8) + flight_size = 3 * state['mss'] + # increase congestion window large enough to send all those bytes + self.set_cwnd(flight_size) + # put some bytes in the air + for _ in range(3): + self.cong_report_msg_sent(state['mss']) + self.cong_report_ecn_ce(1204) + state = self.cong_state() + # [...] set the slow start threshold (ssthresh) to 0.8 times the + # FlightSize, with a lower bound of 2 * SMSS applied to the result + self.assertEqual(state['ssthresh'], beta_ecn * flight_size) + # the TCP sender also reduces the cwnd value to no more + # than the new ssthresh value + self.assertLessEqual(state['cwnd'], state['ssthresh']) + + +if __name__ == '__main__': + unittest.main()