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