1 /*
2  * Copyright 2020 Peter Bigot Consulting
3  *
4  * SPDX-License-Identifier: Apache-2.0
5  */
6 
7 /* Tests for the time_sync data structures */
8 
9 #include <string.h>
10 #include <zephyr/ztest.h>
11 #include "timeutil_test.h"
12 
13 static const struct timeutil_sync_config cfg1 = {
14 	.ref_Hz = USEC_PER_SEC,
15 	.local_Hz = 32768,
16 };
17 
18 static const struct timeutil_sync_config cfg2 = {
19 	.ref_Hz = NSEC_PER_SEC,
20 	.local_Hz = 100,
21 };
22 
scale_ref(uint32_t factor,const struct timeutil_sync_config * cfg)23 static inline uint64_t scale_ref(uint32_t factor,
24 			      const struct timeutil_sync_config *cfg)
25 {
26 	return (uint64_t)factor * (uint64_t)cfg->ref_Hz;
27 }
28 
scale_local(uint32_t factor,const struct timeutil_sync_config * cfg)29 static inline uint64_t scale_local(uint32_t factor,
30 				   const struct timeutil_sync_config *cfg)
31 {
32 	return (uint64_t)factor * (uint64_t)cfg->local_Hz;
33 }
34 
scale_local_signed(int32_t factor,const struct timeutil_sync_config * cfg)35 static inline int64_t scale_local_signed(int32_t factor,
36 					 const struct timeutil_sync_config *cfg)
37 {
38 	return (int64_t)factor * (int64_t)cfg->local_Hz;
39 }
40 
41 
test_state_update(void)42 static void test_state_update(void)
43 {
44 	struct timeutil_sync_instant si = { 0 };
45 	struct timeutil_sync_state ss = { 0 };
46 	int rv = timeutil_sync_state_update(&ss, &si);
47 
48 	zassert_equal(rv, -EINVAL,
49 		      "invalid init got: %d", rv);
50 	zassert_equal(ss.base.ref, 0,
51 		      "unexpected base ref");
52 	zassert_equal(ss.skew, 0,
53 		      "unexpected skew");
54 
55 	si.ref = 1;
56 	rv = timeutil_sync_state_update(&ss, &si);
57 	zassert_equal(rv, 0,
58 		      "valid first init got: %d", rv);
59 	zassert_equal(ss.base.ref, 1,
60 		      "base not updated");
61 	zassert_equal(ss.latest.ref, 0,
62 		      "unexpected latest ref");
63 	zassert_equal(ss.skew, 1.0,
64 		      "unexpected skew");
65 
66 	rv = timeutil_sync_state_update(&ss, &si);
67 	zassert_equal(rv, -EINVAL,
68 		      "non-increasing ref got: %d", rv);
69 	zassert_equal(ss.base.ref, 1,
70 		      "unexpected base ref");
71 	zassert_equal(ss.base.local, 0,
72 		      "unexpected base local");
73 	zassert_equal(ss.latest.ref, 0,
74 		      "unexpected latest ref");
75 
76 	si.ref += 1;
77 	rv = timeutil_sync_state_update(&ss, &si);
78 	zassert_equal(rv, -EINVAL,
79 		      "non-increasing local got: %d", rv);
80 	zassert_equal(ss.latest.ref, 0,
81 		      "unexpected latest ref");
82 
83 	si.local += 20;
84 	rv = timeutil_sync_state_update(&ss, &si);
85 	zassert_equal(rv, 1,
86 		      "increasing got: %d", rv);
87 	zassert_equal(ss.base.ref, 1,
88 		      "unexpected base ref");
89 	zassert_equal(ss.base.local, 0,
90 		      "unexpected base local");
91 	zassert_equal(ss.latest.ref, si.ref,
92 		      "unexpected latest ref");
93 	zassert_equal(ss.latest.local, si.local,
94 		      "unexpected latest local");
95 }
96 
test_state_set_skew(void)97 static void test_state_set_skew(void)
98 {
99 	struct timeutil_sync_instant si = {
100 		.ref = 1,
101 	};
102 	struct timeutil_sync_state ss = {
103 		.cfg = &cfg1,
104 	};
105 	float skew = 0.99;
106 	int rv = timeutil_sync_state_update(&ss, &si);
107 
108 	zassert_equal(rv, 0,
109 		      "valid first init got: %d", rv);
110 	zassert_equal(ss.skew, 1.0,
111 		      "unexpected skew");
112 
113 	rv = timeutil_sync_state_set_skew(&ss, -1.0, NULL);
114 	zassert_equal(rv, -EINVAL,
115 		      "negative skew set got: %d", rv);
116 	zassert_equal(ss.skew, 1.0,
117 		      "unexpected skew");
118 
119 	rv = timeutil_sync_state_set_skew(&ss, 0.0, NULL);
120 	zassert_equal(rv, -EINVAL,
121 		      "zero skew set got: %d", rv);
122 	zassert_equal(ss.skew, 1.0,
123 		      "unexpected skew");
124 
125 	rv = timeutil_sync_state_set_skew(&ss, skew, NULL);
126 	zassert_equal(rv, 0,
127 		      "valid skew set got: %d", rv);
128 	zassert_equal(ss.skew, skew,
129 		      "unexpected skew");
130 	zassert_equal(ss.base.ref, si.ref,
131 		      "unexpected base ref");
132 	zassert_equal(ss.base.local, si.local,
133 		      "unexpected base ref");
134 
135 	skew = 1.01;
136 	si.ref += 5;
137 	si.local += 3;
138 
139 	rv = timeutil_sync_state_set_skew(&ss, skew, &si);
140 	zassert_equal(rv, 0,
141 		      "valid skew set got: %d", rv);
142 	zassert_equal(ss.skew, skew,
143 		      "unexpected skew");
144 	zassert_equal(ss.base.ref, si.ref,
145 		      "unexpected base ref");
146 	zassert_equal(ss.base.local, si.local,
147 		      "unexpected base ref");
148 	zassert_equal(ss.latest.ref, 0,
149 		      "uncleared latest ref");
150 	zassert_equal(ss.latest.local, 0,
151 		      "uncleared latest local");
152 }
153 
test_estimate_skew(void)154 static void test_estimate_skew(void)
155 {
156 	struct timeutil_sync_state ss = {
157 		.cfg = &cfg1,
158 	};
159 	struct timeutil_sync_instant si0 = {
160 		.ref = cfg1.ref_Hz,
161 	};
162 	struct timeutil_sync_instant si1 = {
163 		.ref = si0.ref + cfg1.ref_Hz,
164 		.local = si0.local + cfg1.local_Hz,
165 	};
166 	float skew = 0.0;
167 
168 	skew = timeutil_sync_estimate_skew(&ss);
169 	zassert_equal(skew, 0,
170 		      "unexpected uninit skew: %f", skew);
171 
172 	int rv = timeutil_sync_state_update(&ss, &si0);
173 
174 	zassert_equal(rv, 0,
175 		      "valid init got: %d", rv);
176 
177 	skew = timeutil_sync_estimate_skew(&ss);
178 	zassert_equal(skew, 0,
179 		      "unexpected base-only skew: %f", skew);
180 
181 	rv = timeutil_sync_state_update(&ss, &si1);
182 	zassert_equal(rv, 1,
183 		      "valid update got: %d", rv);
184 
185 	zassert_equal(ss.base.ref, si0.ref,
186 		      "unexpected base ref");
187 	zassert_equal(ss.base.local, si0.local,
188 		      "unexpected base local");
189 	zassert_equal(ss.latest.ref, si1.ref,
190 		      "unexpected latest ref");
191 	zassert_equal(ss.latest.local, si1.local,
192 		      "unexpected latest local");
193 
194 	skew = timeutil_sync_estimate_skew(&ss);
195 	zassert_equal(skew, 1.0,
196 		      "unexpected linear skew: %f", skew);
197 
198 	/* Local advanced half as far as it should: scale by 2 to
199 	 * correct.
200 	 */
201 	ss.latest.local = scale_local(1, ss.cfg) / 2;
202 	skew = timeutil_sync_estimate_skew(&ss);
203 	zassert_equal(skew, 2.0,
204 		      "unexpected half skew: %f", skew);
205 
206 	/* Local advanced twice as far as it should: scale by 1/2 to
207 	 * correct.
208 	 */
209 	ss.latest.local = scale_local(2, ss.cfg);
210 	skew = timeutil_sync_estimate_skew(&ss);
211 	zassert_equal(skew, 0.5,
212 		      "unexpected double skew: %f", skew);
213 }
214 
tref_from_local(const char * tag,const struct timeutil_sync_config * cfg)215 static void tref_from_local(const char *tag,
216 			    const struct timeutil_sync_config *cfg)
217 {
218 	struct timeutil_sync_state ss = {
219 		.cfg = cfg,
220 	};
221 	struct timeutil_sync_instant si0 = {
222 		/* Absolute local 0 is 5 s ref */
223 		.ref = scale_ref(10, cfg),
224 		.local = scale_local(5, cfg),
225 	};
226 	uint64_t ref = 0;
227 	int rv = timeutil_sync_ref_from_local(&ss, 0, &ref);
228 
229 	zassert_equal(rv, -EINVAL,
230 		      "%s: unexpected uninit convert: %d", tag, rv);
231 
232 	rv = timeutil_sync_state_update(&ss, &si0);
233 	zassert_equal(rv, 0,
234 		      "%s: unexpected init: %d", tag, rv);
235 	zassert_equal(ss.skew, 1.0,
236 		      "%s: unexpected skew");
237 
238 	rv = timeutil_sync_ref_from_local(&ss, ss.base.local, NULL);
239 	zassert_equal(rv, -EINVAL,
240 		      "%s: unexpected missing dest: %d", tag, rv);
241 
242 	rv = timeutil_sync_ref_from_local(&ss, ss.base.local, &ref);
243 	zassert_equal(rv, 0,
244 		      "%s: unexpected fail", tag, rv);
245 	zassert_equal(ref, ss.base.ref,
246 		      "%s: unexpected base convert", tag);
247 
248 	rv = timeutil_sync_ref_from_local(&ss, 0, &ref);
249 	zassert_equal(rv, 0,
250 		      "%s: unexpected local=0 fail", tag, rv);
251 	zassert_equal(ref, scale_ref(5, cfg),
252 		      "%s: unexpected local=0 ref", tag);
253 
254 	rv = timeutil_sync_ref_from_local(&ss, ss.base.local, &ref);
255 	zassert_equal(rv, 0,
256 		      "%s: unexpected local=base fail", tag, rv);
257 	zassert_equal(ref, ss.base.ref,
258 		      "%s: unexpected local=base ref", tag);
259 
260 	rv = timeutil_sync_ref_from_local(&ss, ss.base.local
261 					  + scale_local(2, cfg), &ref);
262 	zassert_equal(rv, 0,
263 		      "%s: unexpected local=base+2s fail %d", tag, rv);
264 	zassert_equal(ref, ss.base.ref + scale_ref(2, cfg),
265 		      "%s: unexpected local=base+2s ref", tag);
266 
267 	rv = timeutil_sync_ref_from_local(&ss, (int64_t)ss.base.local
268 					  - scale_local(12, cfg), &ref);
269 	zassert_equal(rv, -ERANGE,
270 		      "%s: unexpected local=base-12s res %u", tag, rv);
271 
272 	/* Skew of 0.5 means local runs at double speed */
273 	rv = timeutil_sync_state_set_skew(&ss, 0.5, NULL);
274 	zassert_equal(rv, 0,
275 		      "%s: failed set skew", tag);
276 
277 	/* Local at double speed corresponds to half advance in ref */
278 	rv = timeutil_sync_ref_from_local(&ss, ss.base.local
279 					  + scale_local(2, cfg), &ref);
280 	zassert_equal(rv, 1,
281 		      "%s: unexpected skew adj fail", tag);
282 	zassert_equal(ref, ss.base.ref + cfg->ref_Hz,
283 		      "%s: unexpected skew adj convert", tag);
284 }
285 
test_ref_from_local(void)286 static void test_ref_from_local(void)
287 {
288 	tref_from_local("std", &cfg1);
289 	tref_from_local("ext", &cfg2);
290 }
291 
tlocal_from_ref(const char * tag,const struct timeutil_sync_config * cfg)292 static void tlocal_from_ref(const char *tag,
293 			    const struct timeutil_sync_config *cfg)
294 {
295 	struct timeutil_sync_state ss = {
296 		.cfg = cfg,
297 	};
298 	struct timeutil_sync_instant si0 = {
299 		/* Absolute local 0 is 5 s ref */
300 		.ref = scale_ref(10, cfg),
301 		.local = scale_local(5, cfg),
302 	};
303 	int64_t local = 0;
304 	int rv = timeutil_sync_local_from_ref(&ss, 0, &local);
305 
306 	zassert_equal(rv, -EINVAL,
307 		      "%s: unexpected uninit convert: %d", tag, rv);
308 
309 	rv = timeutil_sync_state_update(&ss, &si0);
310 	zassert_equal(rv, 0,
311 		      "%s: unexpected init: %d", tag, rv);
312 	zassert_equal(ss.skew, 1.0,
313 		      "%s: unexpected skew", tag);
314 
315 	rv = timeutil_sync_local_from_ref(&ss, ss.base.ref, NULL);
316 	zassert_equal(rv, -EINVAL,
317 		      "%s: unexpected missing dest", tag, rv);
318 
319 	rv = timeutil_sync_local_from_ref(&ss, ss.base.ref, &local);
320 	zassert_equal(rv, 0,
321 		      "%s: unexpected fail", tag, rv);
322 	zassert_equal(local, ss.base.local,
323 		      "%s: unexpected base convert", tag);
324 
325 	rv = timeutil_sync_local_from_ref(&ss, ss.base.ref
326 					  + scale_ref(2, cfg), &local);
327 	zassert_equal(rv, 0,
328 		      "%s: unexpected base+2s fail", tag);
329 	zassert_equal(local, ss.base.local + scale_local(2, cfg),
330 		      "%s: unexpected base+2s convert", tag);
331 
332 	rv = timeutil_sync_local_from_ref(&ss, ss.base.ref
333 					  - scale_ref(7, cfg), &local);
334 	zassert_equal(rv, 0,
335 		      "%s: unexpected base-7s fail", tag);
336 	zassert_equal(local, scale_local_signed(-2, cfg),
337 		      "%s: unexpected base-7s convert", tag);
338 
339 
340 	/* Skew of 0.5 means local runs at double speed */
341 	rv = timeutil_sync_state_set_skew(&ss, 0.5, NULL);
342 	zassert_equal(rv, 0,
343 		      "%s: failed set skew", tag);
344 
345 	/* Local at double speed corresponds to half advance in ref */
346 	rv = timeutil_sync_local_from_ref(&ss, ss.base.ref
347 					  + scale_ref(1, cfg) / 2, &local);
348 	zassert_equal(rv, 1,
349 		      "%s: unexpected skew adj fail", tag);
350 	zassert_equal(local, ss.base.local + scale_local(1, cfg),
351 		      "%s: unexpected skew adj convert", tag);
352 }
353 
test_local_from_ref(void)354 static void test_local_from_ref(void)
355 {
356 	tlocal_from_ref("std", &cfg1);
357 	tlocal_from_ref("ext", &cfg2);
358 }
359 
test_large_linearity(void)360 static void test_large_linearity(void)
361 {
362 	uint64_t inputs[] = {
363 		1000ULL,
364 		3999999999ULL,
365 		4000000000ULL,
366 		4000000001ULL,
367 		UINT64_MAX / 10000000
368 	};
369 	uint64_t ref_out;
370 	int64_t loc_out;
371 	int rv;
372 
373 	const struct timeutil_sync_config unity = {
374 		.ref_Hz = 1000,
375 		.local_Hz = 1000,
376 	};
377 	struct timeutil_sync_instant inst = {
378 		.ref = 200,
379 		.local = 100
380 	};
381 	struct timeutil_sync_state ss = {
382 		.cfg = &unity,
383 	};
384 	uint64_t offset = inst.ref - inst.local;
385 
386 	timeutil_sync_state_set_skew(&ss, 1.0f, &inst);
387 
388 	for (int i = 0; i < ARRAY_SIZE(inputs); i++) {
389 		rv = timeutil_sync_ref_from_local(&ss, inputs[i], &ref_out);
390 		zassert_equal(rv, 0, "Unexpected conversion fail");
391 		zassert_equal(ref_out, inputs[i] + offset,
392 			      "Large unity local->ref conversion fail");
393 
394 		rv = timeutil_sync_local_from_ref(&ss, inputs[i], &loc_out);
395 		zassert_equal(rv, 0, "Unexpected conversion fail");
396 		zassert_equal(loc_out, inputs[i] - offset,
397 			      "Large unity ref->local conversion fail");
398 	}
399 }
400 
test_skew_to_ppb(void)401 static void test_skew_to_ppb(void)
402 {
403 	float skew = 1.0;
404 	int32_t ppb = timeutil_sync_skew_to_ppb(skew);
405 
406 	zassert_equal(ppb, 0,
407 		      "unexpected perfect: %d", ppb);
408 
409 	skew = 0.999976;
410 	ppb = timeutil_sync_skew_to_ppb(skew);
411 	zassert_equal(ppb, 24020,
412 		      "unexpected fast: %d", ppb);
413 
414 	skew = 1.000022;
415 	ppb = timeutil_sync_skew_to_ppb(skew);
416 	zassert_equal(ppb, -22053,
417 		      "unexpected slow: %d", ppb);
418 
419 	skew = 3.147483587;
420 	ppb = timeutil_sync_skew_to_ppb(skew);
421 	zassert_equal(ppb, -2147483587,
422 		      "unexpected near limit: %.10g %d", skew, ppb);
423 	skew = 3.147483826;
424 	ppb = timeutil_sync_skew_to_ppb(skew);
425 	zassert_equal(ppb, INT32_MIN,
426 		      "unexpected above limit: %.10g %d", skew, ppb);
427 }
428 
ZTEST(timeutil_api,test_sync)429 ZTEST(timeutil_api, test_sync)
430 {
431 	test_state_update();
432 	test_state_set_skew();
433 	test_estimate_skew();
434 	test_ref_from_local();
435 	test_local_from_ref();
436 	test_large_linearity();
437 	test_skew_to_ppb();
438 }
439