1 /*
2  * Copyright (c) 2020 Nordic Semiconductor ASA
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 #include <ztest.h>
8 #include <string.h>
9 #include <inttypes.h>
10 #include <net/net_timeout.h>
11 
12 #include "../../../subsys/net/ip/net_timeout.c"
13 
14 #define HALFMAX_S (23U + 60U * (31U + 60U * (20U + 24U * 24U)))
15 #define NTO_MAX_S HALFMAX_S
16 #define FULLMAX_S (47U + 60U * (2U + 60U * (17U + 24U * 49U)))
17 
18 #if 0
19 static void dump_nto(const struct net_timeout *nto)
20 {
21 	uint64_t remaining = nto->timer_timeout;
22 	uint64_t deadline = nto->timer_start;
23 
24 	remaining += NET_TIMEOUT_MAX_VALUE * nto->wrap_counter;
25 	deadline += remaining;
26 
27 	printk("start %u, rem %u * %u + %u = %" PRIu64 ", ends %" PRIu64 "\n",
28 	       nto->timer_start, nto->wrap_counter,
29 	       NET_TIMEOUT_MAX_VALUE, nto->timer_timeout,
30 	       remaining, deadline);
31 }
32 #endif
33 
test_basics(void)34 static void test_basics(void)
35 {
36 	zassert_equal(NET_TIMEOUT_MAX_VALUE, INT32_MAX,
37 		      "Max value not as expected");
38 	zassert_equal(((uint32_t)(INT32_MAX / MSEC_PER_SEC)),
39 		      HALFMAX_S,
40 		      "Half-max constant is wrong");
41 	zassert_equal((UINT32_MAX / MSEC_PER_SEC),
42 		      FULLMAX_S,
43 		      "Full-max constant is wrong");
44 }
45 
test_set(void)46 static void test_set(void)
47 {
48 	struct net_timeout nto;
49 	uint32_t now = 4;
50 	uint32_t lifetime = 0U;
51 
52 	/* Zero is a special case. */
53 	memset(&nto, 0xa5, sizeof(nto));
54 	net_timeout_set(&nto, 0, now);
55 	zassert_equal(nto.timer_start, now, NULL);
56 	zassert_equal(nto.wrap_counter, 0, NULL);
57 	zassert_equal(nto.timer_timeout, 0, NULL);
58 
59 	/* Less than the max is straightforward. */
60 	lifetime = NTO_MAX_S / 2;
61 	++now;
62 	memset(&nto, 0xa5, sizeof(nto));
63 	net_timeout_set(&nto, lifetime, now);
64 	zassert_equal(nto.timer_start, now, NULL);
65 	zassert_equal(nto.wrap_counter, 0, NULL);
66 	zassert_equal(nto.timer_timeout, lifetime * MSEC_PER_SEC,
67 		      NULL);
68 
69 	/* Max must not incur wrap, so fraction is not zero. */
70 	lifetime = NTO_MAX_S;
71 	++now;
72 	memset(&nto, 0xa5, sizeof(nto));
73 	net_timeout_set(&nto, lifetime, now);
74 	zassert_equal(nto.timer_start, now, NULL);
75 	zassert_equal(nto.wrap_counter, 0, NULL);
76 	zassert_equal(nto.timer_timeout, lifetime * MSEC_PER_SEC,
77 		      NULL);
78 
79 	/* Next after max does wrap. */
80 	lifetime += 1U;
81 	++now;
82 	memset(&nto, 0xa5, sizeof(nto));
83 	net_timeout_set(&nto, lifetime, now);
84 	zassert_equal(nto.timer_start, now, NULL);
85 	zassert_equal(nto.wrap_counter, 1U, NULL);
86 	zassert_equal(nto.timer_timeout,
87 		      (lifetime * MSEC_PER_SEC) % NET_TIMEOUT_MAX_VALUE,
88 		      NULL);
89 
90 	/* Fullmax should be one plus partial */
91 	lifetime = FULLMAX_S;
92 	++now;
93 	memset(&nto, 0xa5, sizeof(nto));
94 	net_timeout_set(&nto, lifetime, now);
95 	zassert_equal(nto.timer_start, now, NULL);
96 	zassert_equal(nto.wrap_counter, 1U, NULL);
97 	zassert_equal(nto.timer_timeout,
98 		      (lifetime * (uint64_t)MSEC_PER_SEC) % NET_TIMEOUT_MAX_VALUE,
99 		      NULL);
100 
101 	/* Multiples of max must also not have a zero fraction. */
102 	lifetime = NET_TIMEOUT_MAX_VALUE;
103 	++now;
104 	memset(&nto, 0xa5, sizeof(nto));
105 	net_timeout_set(&nto, lifetime, now);
106 	zassert_equal(nto.timer_start, now, NULL);
107 	zassert_equal(nto.wrap_counter, MSEC_PER_SEC - 1, NULL);
108 	zassert_equal(nto.timer_timeout, NET_TIMEOUT_MAX_VALUE, NULL);
109 }
110 
test_deadline(void)111 static void test_deadline(void)
112 {
113 	struct net_timeout nto;
114 	uint64_t now = 1234;
115 	uint64_t rollover31 = BIT64(31);
116 	uint64_t rollover32 = BIT64(32);
117 	uint32_t lifetime = 562;
118 
119 	net_timeout_set(&nto, lifetime, now);
120 	uint64_t expected = now + lifetime * MSEC_PER_SEC;
121 	zassert_equal(net_timeout_deadline(&nto, now),
122 		      expected,
123 		      NULL);
124 
125 	/* Advancing now has no effect until it wraps. */
126 	zassert_equal(net_timeout_deadline(&nto, now + 23U),
127 		      expected,
128 		      NULL);
129 
130 	/* Advancing by 2^31 is not a wrap. */
131 	now += rollover31;
132 	zassert_equal(net_timeout_deadline(&nto, now),
133 		      expected,
134 		      NULL);
135 
136 	/* Advancing by 2^32 is a wrap, and should be reflected in the
137 	 * returned deadline
138 	 */
139 	now += rollover31;
140 	expected += rollover32;
141 	zassert_equal(net_timeout_deadline(&nto, now),
142 		      expected,
143 		      NULL);
144 
145 	zassert_equal(net_timeout_deadline(&nto, now + 52),
146 		      expected,
147 		      NULL);
148 }
149 
test_remaining(void)150 static void test_remaining(void)
151 {
152 	struct net_timeout nto;
153 	uint32_t now = 4;
154 	uint32_t lifetime = 0U;
155 
156 	/* Zero is a special case. */
157 	memset(&nto, 0xa5, sizeof(nto));
158 	net_timeout_set(&nto, 0, now);
159 	zassert_equal(net_timeout_remaining(&nto, now), 0U,
160 		      NULL);
161 
162 	/* Without wrap is straightforward. */
163 	lifetime = NTO_MAX_S / 2;
164 	memset(&nto, 0xa5, sizeof(nto));
165 	net_timeout_set(&nto, lifetime, now);
166 	zassert_equal(nto.wrap_counter, 0, NULL);
167 	zassert_equal(net_timeout_remaining(&nto, now), lifetime,
168 		      NULL);
169 
170 	/* Estimate rounds down (legacy behavior). */
171 	zassert_equal(net_timeout_remaining(&nto, now + 1U),
172 		      lifetime - 1U,
173 		      NULL);
174 	zassert_equal(net_timeout_remaining(&nto, now + MSEC_PER_SEC - 1U),
175 		      lifetime - 1U,
176 		      NULL);
177 	zassert_equal(net_timeout_remaining(&nto, now + MSEC_PER_SEC),
178 		      lifetime - 1U,
179 		      NULL);
180 	zassert_equal(net_timeout_remaining(&nto, now + MSEC_PER_SEC + 1U),
181 		      lifetime - 2U,
182 		      NULL);
183 
184 	/* Works when wrap is involved */
185 	lifetime = 4 * FULLMAX_S;
186 	net_timeout_set(&nto, lifetime, now);
187 	zassert_equal(nto.wrap_counter, 7, NULL);
188 	zassert_equal(net_timeout_remaining(&nto, now), lifetime,
189 		      NULL);
190 }
191 
test_evaluate_basic(void)192 static void test_evaluate_basic(void)
193 {
194 	struct net_timeout nto;
195 	uint64_t now = 0;
196 	uint32_t half_max = NET_TIMEOUT_MAX_VALUE / 2U;
197 	uint32_t lifetime = FULLMAX_S + HALFMAX_S;
198 	uint32_t remainder;
199 	uint32_t delay;
200 	uint64_t deadline;
201 
202 	net_timeout_set(&nto, lifetime, now);
203 	zassert_equal(nto.timer_start, now,
204 		      NULL);
205 	zassert_equal(nto.wrap_counter, 2,
206 		      NULL);
207 	remainder = 2147482706;
208 	zassert_equal(nto.timer_timeout, remainder,
209 		      NULL);
210 	deadline = net_timeout_deadline(&nto, now);
211 
212 	/* Evaluation with wrap and no advance returns max value
213 	 * without changing anything. */
214 	delay = net_timeout_evaluate(&nto, now);
215 	zassert_equal(delay, NET_TIMEOUT_MAX_VALUE,
216 		       NULL);
217 	zassert_equal(nto.timer_start, now,
218 		      NULL);
219 	zassert_equal(nto.wrap_counter, 2,
220 		      NULL);
221 	zassert_equal(nto.timer_timeout, remainder,
222 		      NULL);
223 	zassert_equal(net_timeout_deadline(&nto, now), deadline,
224 		     NULL);
225 
226 	/* Advance now by half the delay should return the remainder,
227 	 * again without advancing anything.
228 	 */
229 	delay = net_timeout_evaluate(&nto, now + half_max);
230 	zassert_equal(delay, NET_TIMEOUT_MAX_VALUE - half_max,
231 		       NULL);
232 	zassert_equal(nto.timer_start, now,
233 		      NULL);
234 	zassert_equal(nto.wrap_counter, 2,
235 		      NULL);
236 	zassert_equal(nto.timer_timeout, remainder,
237 		      NULL);
238 	zassert_equal(net_timeout_deadline(&nto, now), deadline,
239 		     NULL);
240 
241 	/* Advance now by just below delay still doesn't change
242 	 * anything.
243 	 */
244 	delay = net_timeout_evaluate(&nto, now + NET_TIMEOUT_MAX_VALUE - 1U);
245 	zassert_equal(delay, 1U,
246 		       NULL);
247 	zassert_equal(nto.timer_start, now,
248 		      NULL);
249 	zassert_equal(nto.wrap_counter, 2,
250 		      NULL);
251 	zassert_equal(nto.timer_timeout, remainder,
252 		      NULL);
253 	zassert_equal(net_timeout_deadline(&nto, now), deadline,
254 		     NULL);
255 
256 	/* Advancing by the delay consumes the value of the delay. The
257 	 * deadline is unchanged.
258 	 */
259 	now += NET_TIMEOUT_MAX_VALUE;
260 	delay = net_timeout_evaluate(&nto, now);
261 	zassert_equal(delay, NET_TIMEOUT_MAX_VALUE,
262 		      NULL);
263 	zassert_equal(nto.timer_start, now,
264 		      NULL);
265 	zassert_equal(nto.wrap_counter, 1,
266 		      NULL);
267 	zassert_equal(nto.timer_timeout, remainder,
268 		      "remainder %u", nto.timer_timeout);
269 	zassert_equal(net_timeout_deadline(&nto, now), deadline,
270 		     NULL);
271 
272 	/* Advancing by more than the delay consumes the value of the delay,
273 	 * with the excess reducing the remainder.  The deadline is
274 	 * unchanged.
275 	 */
276 	now += NET_TIMEOUT_MAX_VALUE + 1234;
277 	remainder -= 1234;
278 	delay = net_timeout_evaluate(&nto, now);
279 	zassert_equal(delay, remainder,
280 		      NULL);
281 	zassert_equal(nto.timer_start, (uint32_t)now,
282 		      NULL);
283 	zassert_equal(nto.wrap_counter, 0,
284 		      NULL);
285 	zassert_equal(nto.timer_timeout, remainder,
286 		      NULL);
287 	zassert_equal(net_timeout_deadline(&nto, now), deadline,
288 		     NULL);
289 
290 	/* Final advance completes the timeout precisely */
291 	now += delay;
292 	delay = net_timeout_evaluate(&nto, now);
293 	zassert_equal(delay, 0,
294 		      NULL);
295 	zassert_equal(net_timeout_deadline(&nto, now), deadline,
296 		     NULL);
297 }
298 
test_evaluate_whitebox(void)299 static void test_evaluate_whitebox(void)
300 {
301 	/* This explicitly tests the path where subtracting the excess elapsed
302 	 * from the fractional timeout requires reducing the wrap count a
303 	 * second time.
304 	 */
305 	struct net_timeout nto;
306 	uint64_t now = 0;
307 	uint32_t lifetime = 3 * HALFMAX_S + 2;
308 
309 	net_timeout_set(&nto, lifetime, now);
310 	zassert_equal(nto.timer_start, now, NULL);
311 	zassert_equal(nto.wrap_counter, 3, NULL);
312 	zassert_equal(nto.timer_timeout, 59, NULL);
313 
314 	/* Preserve the deadline for validation */
315 	uint64_t deadline = net_timeout_deadline(&nto, now);
316 
317 	uint32_t delay = net_timeout_evaluate(&nto, now);
318 	zassert_equal(delay, NET_TIMEOUT_MAX_VALUE, NULL);
319 	zassert_equal(net_timeout_deadline(&nto, now),
320 		      deadline, NULL);
321 
322 	/* Simulate a late evaluation, far enough late that the counter gets
323 	 * wiped out.
324 	 */
325 	now += delay + 100U;
326 	delay = net_timeout_evaluate(&nto, now);
327 	zassert_equal(nto.timer_start, now, NULL);
328 	zassert_equal(nto.wrap_counter, 1, NULL);
329 	zassert_equal(nto.timer_timeout, 2147483606, NULL);
330 	zassert_equal(net_timeout_deadline(&nto, now),
331 		      deadline, NULL);
332 	zassert_equal(delay, NET_TIMEOUT_MAX_VALUE, NULL);
333 
334 	/* Another late evaluation finishes the wrap leaving some extra.
335 	 */
336 	now += delay + 123U;
337 	delay = net_timeout_evaluate(&nto, now);
338 	zassert_equal(nto.timer_start, (uint32_t)now, NULL);
339 	zassert_equal(nto.wrap_counter, 0, NULL);
340 	zassert_equal(nto.timer_timeout, 2147483483, NULL);
341 	zassert_equal(net_timeout_deadline(&nto, now),
342 		      deadline, NULL);
343 	zassert_equal(delay, nto.timer_timeout, NULL);
344 
345 	/* Complete the timeout.  This does *not* adjust the internal
346 	 * state.
347 	 */
348 	now += delay + 234U;
349 	delay = net_timeout_evaluate(&nto, now);
350 	zassert_equal(delay, 0, NULL);
351 	zassert_equal(net_timeout_deadline(&nto, now),
352 		      deadline, NULL);
353 }
354 
test_nop(void)355 static void test_nop(void)
356 {
357 }
358 
359 /*test case main entry*/
test_main(void)360 void test_main(void)
361 {
362 
363 	ztest_test_suite(test_prf,
364 			 ztest_unit_test(test_basics),
365 			 ztest_unit_test(test_set),
366 			 ztest_unit_test(test_deadline),
367 			 ztest_unit_test(test_remaining),
368 			 ztest_unit_test(test_evaluate_basic),
369 			 ztest_unit_test(test_evaluate_whitebox),
370 			 ztest_unit_test(test_nop)
371 			 );
372 	ztest_run_test_suite(test_prf);
373 }
374