Linux preempt-rt

Check our new training course

Real-Time Linux with PREEMPT_RT

Check our new training course
with Creative Commons CC-BY-SA
lecture and lab materials

Bootlin logo

Elixir Cross Referencer

/*
 * Copyright (c) 2020 Nordic Semiconductor ASA
 *
 * SPDX-License-Identifier: Apache-2.0
 */

#include <ztest.h>
#include <string.h>
#include <inttypes.h>
#include <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

static void test_basics(void)
{
	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");
}

static void test_set(void)
{
	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, NULL);
	zassert_equal(nto.wrap_counter, 0, NULL);
	zassert_equal(nto.timer_timeout, 0, NULL);

	/* 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, NULL);
	zassert_equal(nto.wrap_counter, 0, NULL);
	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, NULL);
	zassert_equal(nto.wrap_counter, 0, NULL);
	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, NULL);
	zassert_equal(nto.wrap_counter, 1U, NULL);
	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, NULL);
	zassert_equal(nto.wrap_counter, 1U, NULL);
	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, NULL);
	zassert_equal(nto.wrap_counter, MSEC_PER_SEC - 1, NULL);
	zassert_equal(nto.timer_timeout, NET_TIMEOUT_MAX_VALUE, NULL);
}

static void test_deadline(void)
{
	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);
}

static void test_remaining(void)
{
	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, NULL);
	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, NULL);
	zassert_equal(net_timeout_remaining(&nto, now), lifetime,
		      NULL);
}

static void test_evaluate_basic(void)
{
	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);
}

static void test_evaluate_whitebox(void)
{
	/* 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, NULL);
	zassert_equal(nto.wrap_counter, 3, NULL);
	zassert_equal(nto.timer_timeout, 59, NULL);

	/* 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, NULL);
	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, NULL);
	zassert_equal(nto.wrap_counter, 1, NULL);
	zassert_equal(nto.timer_timeout, 2147483606, NULL);
	zassert_equal(net_timeout_deadline(&nto, now),
		      deadline, NULL);
	zassert_equal(delay, NET_TIMEOUT_MAX_VALUE, NULL);

	/* 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, NULL);
	zassert_equal(nto.wrap_counter, 0, NULL);
	zassert_equal(nto.timer_timeout, 2147483483, NULL);
	zassert_equal(net_timeout_deadline(&nto, now),
		      deadline, NULL);
	zassert_equal(delay, nto.timer_timeout, NULL);

	/* Complete the timeout.  This does *not* adjust the internal
	 * state.
	 */
	now += delay + 234U;
	delay = net_timeout_evaluate(&nto, now);
	zassert_equal(delay, 0, NULL);
	zassert_equal(net_timeout_deadline(&nto, now),
		      deadline, NULL);
}

static void test_nop(void)
{
}

/*test case main entry*/
void test_main(void)
{

	ztest_test_suite(test_prf,
			 ztest_unit_test(test_basics),
			 ztest_unit_test(test_set),
			 ztest_unit_test(test_deadline),
			 ztest_unit_test(test_remaining),
			 ztest_unit_test(test_evaluate_basic),
			 ztest_unit_test(test_evaluate_whitebox),
			 ztest_unit_test(test_nop)
			 );
	ztest_run_test_suite(test_prf);
}