zephyr/tests/unit/net_timeout/main.c

360 lines
9.9 KiB
C
Raw Permalink Normal View History

net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
/*
* Copyright (c) 2020 Nordic Semiconductor ASA
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/ztest.h>
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
#include <string.h>
#include <inttypes.h>
#include <zephyr/net/net_timeout.h>
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
#include "../../../subsys/net/ip/net_timeout.c"
#define HALFMAX_S (23U + 60U * (31U + 60U * (20U + 24U * 24U)))
#define NTO_MAX_S HALFMAX_S
#define FULLMAX_S (47U + 60U * (2U + 60U * (17U + 24U * 49U)))
#if 0
static void dump_nto(const struct net_timeout *nto)
{
uint64_t remaining = nto->timer_timeout;
uint64_t deadline = nto->timer_start;
remaining += NET_TIMEOUT_MAX_VALUE * nto->wrap_counter;
deadline += remaining;
printk("start %u, rem %u * %u + %u = %" PRIu64 ", ends %" PRIu64 "\n",
nto->timer_start, nto->wrap_counter,
NET_TIMEOUT_MAX_VALUE, nto->timer_timeout,
remaining, deadline);
}
#endif
ZTEST(net_timeout, test_basics)
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
{
zassert_equal(NET_TIMEOUT_MAX_VALUE, INT32_MAX,
"Max value not as expected");
zassert_equal(((uint32_t)(INT32_MAX / MSEC_PER_SEC)),
HALFMAX_S,
"Half-max constant is wrong");
zassert_equal((UINT32_MAX / MSEC_PER_SEC),
FULLMAX_S,
"Full-max constant is wrong");
}
ZTEST(net_timeout, test_set)
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
{
struct net_timeout nto;
uint32_t now = 4;
uint32_t lifetime = 0U;
/* Zero is a special case. */
memset(&nto, 0xa5, sizeof(nto));
net_timeout_set(&nto, 0, now);
zassert_equal(nto.timer_start, now);
zassert_equal(nto.wrap_counter, 0);
zassert_equal(nto.timer_timeout, 0);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
/* Less than the max is straightforward. */
lifetime = NTO_MAX_S / 2;
++now;
memset(&nto, 0xa5, sizeof(nto));
net_timeout_set(&nto, lifetime, now);
zassert_equal(nto.timer_start, now);
zassert_equal(nto.wrap_counter, 0);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
zassert_equal(nto.timer_timeout, lifetime * MSEC_PER_SEC,
NULL);
/* Max must not incur wrap, so fraction is not zero. */
lifetime = NTO_MAX_S;
++now;
memset(&nto, 0xa5, sizeof(nto));
net_timeout_set(&nto, lifetime, now);
zassert_equal(nto.timer_start, now);
zassert_equal(nto.wrap_counter, 0);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
zassert_equal(nto.timer_timeout, lifetime * MSEC_PER_SEC,
NULL);
/* Next after max does wrap. */
lifetime += 1U;
++now;
memset(&nto, 0xa5, sizeof(nto));
net_timeout_set(&nto, lifetime, now);
zassert_equal(nto.timer_start, now);
zassert_equal(nto.wrap_counter, 1U);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
zassert_equal(nto.timer_timeout,
(lifetime * MSEC_PER_SEC) % NET_TIMEOUT_MAX_VALUE,
NULL);
/* Fullmax should be one plus partial */
lifetime = FULLMAX_S;
++now;
memset(&nto, 0xa5, sizeof(nto));
net_timeout_set(&nto, lifetime, now);
zassert_equal(nto.timer_start, now);
zassert_equal(nto.wrap_counter, 1U);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
zassert_equal(nto.timer_timeout,
(lifetime * (uint64_t)MSEC_PER_SEC) % NET_TIMEOUT_MAX_VALUE,
NULL);
/* Multiples of max must also not have a zero fraction. */
lifetime = NET_TIMEOUT_MAX_VALUE;
++now;
memset(&nto, 0xa5, sizeof(nto));
net_timeout_set(&nto, lifetime, now);
zassert_equal(nto.timer_start, now);
zassert_equal(nto.wrap_counter, MSEC_PER_SEC - 1);
zassert_equal(nto.timer_timeout, NET_TIMEOUT_MAX_VALUE);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
}
ZTEST(net_timeout, test_deadline)
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
{
struct net_timeout nto;
uint64_t now = 1234;
uint64_t rollover31 = BIT64(31);
uint64_t rollover32 = BIT64(32);
uint32_t lifetime = 562;
net_timeout_set(&nto, lifetime, now);
uint64_t expected = now + lifetime * MSEC_PER_SEC;
zassert_equal(net_timeout_deadline(&nto, now),
expected,
NULL);
/* Advancing now has no effect until it wraps. */
zassert_equal(net_timeout_deadline(&nto, now + 23U),
expected,
NULL);
/* Advancing by 2^31 is not a wrap. */
now += rollover31;
zassert_equal(net_timeout_deadline(&nto, now),
expected,
NULL);
/* Advancing by 2^32 is a wrap, and should be reflected in the
* returned deadline
*/
now += rollover31;
expected += rollover32;
zassert_equal(net_timeout_deadline(&nto, now),
expected,
NULL);
zassert_equal(net_timeout_deadline(&nto, now + 52),
expected,
NULL);
}
ZTEST(net_timeout, test_remaining)
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
{
struct net_timeout nto;
uint32_t now = 4;
uint32_t lifetime = 0U;
/* Zero is a special case. */
memset(&nto, 0xa5, sizeof(nto));
net_timeout_set(&nto, 0, now);
zassert_equal(net_timeout_remaining(&nto, now), 0U,
NULL);
/* Without wrap is straightforward. */
lifetime = NTO_MAX_S / 2;
memset(&nto, 0xa5, sizeof(nto));
net_timeout_set(&nto, lifetime, now);
zassert_equal(nto.wrap_counter, 0);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
zassert_equal(net_timeout_remaining(&nto, now), lifetime,
NULL);
/* Estimate rounds down (legacy behavior). */
zassert_equal(net_timeout_remaining(&nto, now + 1U),
lifetime - 1U,
NULL);
zassert_equal(net_timeout_remaining(&nto, now + MSEC_PER_SEC - 1U),
lifetime - 1U,
NULL);
zassert_equal(net_timeout_remaining(&nto, now + MSEC_PER_SEC),
lifetime - 1U,
NULL);
zassert_equal(net_timeout_remaining(&nto, now + MSEC_PER_SEC + 1U),
lifetime - 2U,
NULL);
/* Works when wrap is involved */
lifetime = 4 * FULLMAX_S;
net_timeout_set(&nto, lifetime, now);
zassert_equal(nto.wrap_counter, 7);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
zassert_equal(net_timeout_remaining(&nto, now), lifetime,
NULL);
}
ZTEST(net_timeout, test_evaluate_basic)
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
{
struct net_timeout nto;
uint64_t now = 0;
uint32_t half_max = NET_TIMEOUT_MAX_VALUE / 2U;
uint32_t lifetime = FULLMAX_S + HALFMAX_S;
uint32_t remainder;
uint32_t delay;
uint64_t deadline;
net_timeout_set(&nto, lifetime, now);
zassert_equal(nto.timer_start, now,
NULL);
zassert_equal(nto.wrap_counter, 2,
NULL);
remainder = 2147482706;
zassert_equal(nto.timer_timeout, remainder,
NULL);
deadline = net_timeout_deadline(&nto, now);
/* Evaluation with wrap and no advance returns max value
* without changing anything. */
delay = net_timeout_evaluate(&nto, now);
zassert_equal(delay, NET_TIMEOUT_MAX_VALUE,
NULL);
zassert_equal(nto.timer_start, now,
NULL);
zassert_equal(nto.wrap_counter, 2,
NULL);
zassert_equal(nto.timer_timeout, remainder,
NULL);
zassert_equal(net_timeout_deadline(&nto, now), deadline,
NULL);
/* Advance now by half the delay should return the remainder,
* again without advancing anything.
*/
delay = net_timeout_evaluate(&nto, now + half_max);
zassert_equal(delay, NET_TIMEOUT_MAX_VALUE - half_max,
NULL);
zassert_equal(nto.timer_start, now,
NULL);
zassert_equal(nto.wrap_counter, 2,
NULL);
zassert_equal(nto.timer_timeout, remainder,
NULL);
zassert_equal(net_timeout_deadline(&nto, now), deadline,
NULL);
/* Advance now by just below delay still doesn't change
* anything.
*/
delay = net_timeout_evaluate(&nto, now + NET_TIMEOUT_MAX_VALUE - 1U);
zassert_equal(delay, 1U,
NULL);
zassert_equal(nto.timer_start, now,
NULL);
zassert_equal(nto.wrap_counter, 2,
NULL);
zassert_equal(nto.timer_timeout, remainder,
NULL);
zassert_equal(net_timeout_deadline(&nto, now), deadline,
NULL);
/* Advancing by the delay consumes the value of the delay. The
* deadline is unchanged.
*/
now += NET_TIMEOUT_MAX_VALUE;
delay = net_timeout_evaluate(&nto, now);
zassert_equal(delay, NET_TIMEOUT_MAX_VALUE,
NULL);
zassert_equal(nto.timer_start, now,
NULL);
zassert_equal(nto.wrap_counter, 1,
NULL);
zassert_equal(nto.timer_timeout, remainder,
"remainder %u", nto.timer_timeout);
zassert_equal(net_timeout_deadline(&nto, now), deadline,
NULL);
/* Advancing by more than the delay consumes the value of the delay,
* with the excess reducing the remainder. The deadline is
* unchanged.
*/
now += NET_TIMEOUT_MAX_VALUE + 1234;
remainder -= 1234;
delay = net_timeout_evaluate(&nto, now);
zassert_equal(delay, remainder,
NULL);
zassert_equal(nto.timer_start, (uint32_t)now,
NULL);
zassert_equal(nto.wrap_counter, 0,
NULL);
zassert_equal(nto.timer_timeout, remainder,
NULL);
zassert_equal(net_timeout_deadline(&nto, now), deadline,
NULL);
/* Final advance completes the timeout precisely */
now += delay;
delay = net_timeout_evaluate(&nto, now);
zassert_equal(delay, 0,
NULL);
zassert_equal(net_timeout_deadline(&nto, now), deadline,
NULL);
}
ZTEST(net_timeout, test_evaluate_whitebox)
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
{
/* This explicitly tests the path where subtracting the excess elapsed
* from the fractional timeout requires reducing the wrap count a
* second time.
*/
struct net_timeout nto;
uint64_t now = 0;
uint32_t lifetime = 3 * HALFMAX_S + 2;
net_timeout_set(&nto, lifetime, now);
zassert_equal(nto.timer_start, now);
zassert_equal(nto.wrap_counter, 3);
zassert_equal(nto.timer_timeout, 59);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
/* Preserve the deadline for validation */
uint64_t deadline = net_timeout_deadline(&nto, now);
uint32_t delay = net_timeout_evaluate(&nto, now);
zassert_equal(delay, NET_TIMEOUT_MAX_VALUE);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
zassert_equal(net_timeout_deadline(&nto, now),
deadline, NULL);
/* Simulate a late evaluation, far enough late that the counter gets
* wiped out.
*/
now += delay + 100U;
delay = net_timeout_evaluate(&nto, now);
zassert_equal(nto.timer_start, now);
zassert_equal(nto.wrap_counter, 1);
zassert_equal(nto.timer_timeout, 2147483606);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
zassert_equal(net_timeout_deadline(&nto, now),
deadline, NULL);
zassert_equal(delay, NET_TIMEOUT_MAX_VALUE);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
/* Another late evaluation finishes the wrap leaving some extra.
*/
now += delay + 123U;
delay = net_timeout_evaluate(&nto, now);
zassert_equal(nto.timer_start, (uint32_t)now);
zassert_equal(nto.wrap_counter, 0);
zassert_equal(nto.timer_timeout, 2147483483);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
zassert_equal(net_timeout_deadline(&nto, now),
deadline, NULL);
zassert_equal(delay, nto.timer_timeout);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
/* Complete the timeout. This does *not* adjust the internal
* state.
*/
now += delay + 234U;
delay = net_timeout_evaluate(&nto, now);
zassert_equal(delay, 0);
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
zassert_equal(net_timeout_deadline(&nto, now),
deadline, NULL);
}
ZTEST(net_timeout, test_nop)
net: timeout: refactor to fix multiple problems The net_timeout structure is documented to exist because of behavior that is no longer true, i.e. that `k_delayed_work_submit()` supports only delays up to INT32_MAX milliseconds. Nonetheless, use of 32-bit timestamps within the work handlers mean the restriction is still present. This infrastructure is currently used for two timers with long durations: * address for IPv6 addresses * prefix for IPv6 prefixes The handling of rollover was subtly different between these: address wraps reset the start time while prefix wraps did not. The calculation of remaining time in ipv6_nbr was incorrect when the original requested time in seconds was a multiple of NET_TIMEOUT_MAX_VALUE: the remainder value would be zero while the wrap counter was positive, causing the calculation to indicate no time remained. The maximum value was set to allow a 100 ms latency between elapse of the deadline and assessment of a given timer, but detection of rollover assumed that the captured time in the work handler was precisely the expected deadline, which is unlikely to be true. Use of the shared system work queue also risks observed latency exceeding 100 ms. These calculations could produce delays to next event that exceeded the maximum delay, which introduced special cases. Refactor so all operations that use this structure are encapsulated into API that is documented and has a full-coverage unit test. Switch to the standard mechanism of detecting completed deadlines by calculating the signed difference between the deadline and the current time, which eliminates some special cases. Uniformly rely on the scanning the set of timers to determine the next deadline, rather than assuming that the most recent update is always next. Signed-off-by: Peter Bigot <peter.bigot@nordicsemi.no>
2020-12-25 20:32:42 +01:00
{
}
ZTEST_SUITE(net_timeout, NULL, NULL, NULL, NULL, NULL);