| /* |
| * Copyright (c) 2020 Nordic Semiconductor ASA |
| * |
| * SPDX-License-Identifier: Apache-2.0 |
| */ |
| |
| #include <zephyr/ztest.h> |
| #include <string.h> |
| #include <inttypes.h> |
| #include <zephyr/net/net_timeout.h> |
| |
| #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) |
| { |
| 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) |
| { |
| 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); |
| |
| /* 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); |
| 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); |
| 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); |
| 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); |
| 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); |
| } |
| |
| ZTEST(net_timeout, test_deadline) |
| { |
| 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) |
| { |
| 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); |
| 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); |
| zassert_equal(net_timeout_remaining(&nto, now), lifetime, |
| NULL); |
| } |
| |
| ZTEST(net_timeout, test_evaluate_basic) |
| { |
| 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) |
| { |
| /* 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); |
| |
| /* 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); |
| 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); |
| zassert_equal(net_timeout_deadline(&nto, now), |
| deadline, NULL); |
| zassert_equal(delay, NET_TIMEOUT_MAX_VALUE); |
| |
| /* 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); |
| zassert_equal(net_timeout_deadline(&nto, now), |
| deadline, NULL); |
| zassert_equal(delay, nto.timer_timeout); |
| |
| /* Complete the timeout. This does *not* adjust the internal |
| * state. |
| */ |
| now += delay + 234U; |
| delay = net_timeout_evaluate(&nto, now); |
| zassert_equal(delay, 0); |
| zassert_equal(net_timeout_deadline(&nto, now), |
| deadline, NULL); |
| } |
| |
| ZTEST(net_timeout, test_nop) |
| { |
| } |
| |
| ZTEST_SUITE(net_timeout, NULL, NULL, NULL, NULL, NULL); |