-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathbyztime.h
449 lines (335 loc) · 15.7 KB
/
byztime.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
/* Copyright 2021, Akamai Technologies, Inc.
SPDX-License-Identifier: Apache-2.0
*/
#ifndef BYZTIME_H_
#define BYZTIME_H_
#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <stdatomic.h>
#if ATOMIC_INT_LOCK_FREE < 2
#error \
"This platform does not support lock-free atomic stores on ints, which libbyztime relies upon"
#endif
#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L
#include <signal.h>
#endif
/** \file byztime.h */
/** \defgroup stamp Timestamp manipulation
@{
*/
/** The type of timestamps. On modern systems this is isomorphic to
`struct timespec`, but fields are always 64 bits even where
`time_t` is 32 bits.*/
typedef struct byztime_stamp_s {
int64_t seconds;
int64_t nanoseconds;
} byztime_stamp;
/** Normalizes a timestamp so that its `nanoseconds` field is in the range
[0,1000000000).
\param[in,out] stamp The timestamp to be normalized.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
\exception EOVERFLOW The operation caused an integer overflow/underflow,
and was completed with 2-complement wraparound semantics.
*/
int byztime_stamp_normalize(byztime_stamp *stamp);
/** Adds two timestamps.
\param[out] sum The sum of `stamp1` and `stamp2`.
\param[in] stamp1 The first summand.
\param[in] stamp2 The second summand.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
\exception EOVERFLOW The operation caused an integer overflow/underflow,
and was completed with 2-complement wraparound semantics.
*/
int byztime_stamp_add(byztime_stamp *sum, byztime_stamp const *stamp1,
byztime_stamp const *stamp2);
/** Substracts two timestamps.
\param[out] diff The difference of `stamp1` and `stamp2`.
\param[in] stamp1 The minuend.
\param[in] stamp2 The subtrahend.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
\exception EOVERFLOW The operation caused an integer overflow/underflow,
and was completed with 2-complement wraparound semantics.
*/
int byztime_stamp_sub(byztime_stamp *diff, byztime_stamp const *stamp1,
byztime_stamp const *stamp2);
/** Compares the normalized form two timestamps.
\param[in] stamp1 The first timestamp to be compared.
\param[out] stamp2 The second timestamp to be compared.
This function has no means of signaling whether normalizing the inputs
resulted in an overflow. In case of concern, pass inputs that are already
normalized.
\returns -1 if stamp1 < stamp2
\returns 0 if stamp1 == stamp2
\returns 1 if stamp1 > stamp2
*/
int byztime_stamp_cmp(byztime_stamp const *stamp1, byztime_stamp const *stamp2);
/** Scales (i.e. multiplies) a timestamp.
\param[out] prod The scaled timestamp.
\param[in] stamp The timestamp to be scaled.
\param[ppb] ppb The amount by which to scale `stamp`, in parts per billion.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
\exception EOVERFLOW The operation caused an integer overflow/underflow,
and was completed with 2-complement wraparound semantics.
*/
int byztime_stamp_scale(byztime_stamp *prod, byztime_stamp const *stamp,
int64_t ppb);
/** Halves a timestamp.
\param[out] prod The scaled timestamp.
\param[in] stamp The timestamp to be scaled.
This function is much faster than calling byztime_stamp_scale() with
ppb=500_000_000.
If `stamp` is non-normalized then `prod` may be non-normalized as well.
\returns void. This function always succeeds.
*/
void byztime_stamp_halve(byztime_stamp *prod, byztime_stamp const *stamp);
/** The maximum number of bytes written by byztime_stamp_fmt(). */
#define BYZTIME_STAMP_MAX_FMT_LEN 32
/** Formats a timestamp.
\param[out] str Pointer to the output buffer.
\param[in] size Size of the output buffer.
\param[in] stamp The timestamp to be formatted.
A buffer of size at least `BYZTIME_STAMP_MAX_FMT_LEN` will always be able
to contain the formatted timestamp without truncation.
\return The number of bytes that were needed to represent `stamp`, excluding
the terminating null. If the return value is greater than `size-1`, the
output was truncated.
*/
size_t byztime_stamp_fmt(char *str, size_t size, byztime_stamp const *stamp);
/** @} */
/** \defgroup common Common API for consumers and providers
@{
*/
/** The type of context objects used for provider-consumer communication. */
typedef struct byztime_ctx_s byztime_ctx;
#define BYZTIME_ERA_LEN 16
/** Gets the current clock era.
This is a random 16-byte string that changes after a reboot but otherwise
remains constant.
\param era 16-byte buffer into which to return the era.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
*/
int byztime_get_clock_era(unsigned char era[BYZTIME_ERA_LEN]);
/** Gets the current local time.
This is a clock value that advances monotonically from some
arbitrary epoch. It is comparable only with other local clock
values obtained from the same machine with no intervening reboot.
\param[out] local_time The current local time.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
*/
int byztime_get_local_time(byztime_stamp *local_time);
/** Get the current real time relative to the POSIX epoch.
\param[out] real_time The current real time.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
*/
int byztime_get_real_time(byztime_stamp *real_time);
/** Closes a context object.
\param[in] ctx Pointer to the context object to be closed.
Using a context object after closing it will result in undefined behavior.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
*/
int byztime_close(byztime_ctx *ctx);
/** @} */
/** \defgroup consumer Consumer API
@{
*/
/** Opens a timedata file for read-only access.
\param[in] pathname The path to the timedata file.
\return A pointer to a newly-allocated context object, or `NULL` on failure
and sets `errno`.
In addition to any `errno` values set by the operating system, this function
may set `errno` as follows.
\exception EPROTO `pathname` is not a correctly-formatted timedata file.
\exception ECONNREFUSED The timedata file's era does not match the current
boot. This usually indicates that byztimed is not running.
*/
byztime_ctx *byztime_open_ro(char const *pathname);
/** Gets bounds and an estimate of time offset `(global time - local time)`.
\param[in] ctx Pointer to context object.
\param[out] min Minimum possible offset.
\param[out] est Estimated offset.
\param[out] max Maximum possible offset.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
\exception EPROTO The timedata file is improperly formatted.
\exception EOVERFLOW An integer underflow/overflow occurred during
offset or error computation. The resulting output values are
undefined.
In addition to the above `errno` values, any error set by `clock_gettime()`
may be returned. `clock_gettime()` will never be called if `error` is `NULL`
or the drift rate is set to 0.
*/
int byztime_get_offset(byztime_ctx *ctx, byztime_stamp *min, byztime_stamp *est,
byztime_stamp *max);
/** Gets bounds and an estimate of the global time.
\param[in] ctx Pointer to context object.
\param[out] min Minimum possible global time.
\param[out] est Estimated global time.
\param[out] max Maximum possible global time.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
\exception EPROTO The timedata file is improperly formatted.
\exception EOVERFLOW An integer underflow/overflow occurred during
time or error computation. The resulting output values are
undefined.
In addition to the above `errno` values, any error set by `clock_gettime()`
may be returned.
It is important to be aware that `min` and `max` are bounds on the
*actual* global time, not on other nodes' estimation thereof. It
is guranteed that other correct nodes' ranges will overlap ours,
that is, their `min` will be less than our `max`, and their `max`
will be greater than our `min`. However, it is *not* guaranteed
that other correct nodes' `est` will be between our `min` and our
`max`.
*/
int byztime_get_global_time(byztime_ctx *ctx, byztime_stamp *min,
byztime_stamp *est, byztime_stamp *max);
/** Sets the drift rate used in error calculations.
\param[in] ctx Pointer to context object.
\param[in] drift_ppb Drift rate in parts per billion.
*/
void byztime_set_drift(byztime_ctx *ctx, int64_t drift_ppb);
/** Returns the drift rate used in error calculations.
\param[in] ctx Pointer to context object.
*/
int64_t byztime_get_drift(byztime_ctx const *ctx);
/** Begin slewing time estimates.
This function changes how `est` is calcuated in future calls to
`byztime_get_offset()` and `byztime_get_global_time()`. When a context
is first opened, time estimation is in "step" mode where the estimate
is always the midpoint of the min and max. Such an estimate changes
discontinuously every time a new data point is obtained, and can move
backward.
Calling this function causes future estimates to be clamped such
that they will be more consistent with each other. Specifically,
if `byztime_get_global_time()` returns an estimate of `g_1` at
local time `l_1` and an estimate of `g_2` at local time `l_2`, `g_2`
will be clamped such that
`min_rate <= (g_2 - g_1)/(l_2 - l_1) <= max_rate`.
It is unwise to enter slew mode until the clock is known to be at
least reasonably accurate: otherwise it may take a very long time
to catch up with a large future correction. For this reason, this
function accepts a `maxerror` parameter which will cause it to
return an error and remain in step mode if `(max-min)/2 >= maxerror`.
Calling this function while already in slew mode is equivalent to
switching to step mode and then immediately back into slew mode:
it will cause the estimate to catch up to the current midpoint by
a one-time step.
A maximum rate of `INT64_MAX` is treated as infinity. A call of
`byztime_slew(ctx, 0, INT64_MAX, maxerror)` will allow the estimate
to advance at arbitrarily high or low speed but never to move
backward.
When in slew mode, it becomes possible for `byztime_get_offset()`
and `byztime_get_global_time()` to return `(min,est,max)` tuples
such that `est < min` or `est > max`. This can happen a previous
estimate with wide error bounds is superceded by a new estimate
with narrower ones which do not include the previous estimate.
\param[in] ctx Pointer to context object.
\param[in] min_rate_ppb Minimum clock rate in parts per billion.
\param[in] max_rate_ppb Maximum clock rate in parts per billion.
\param[in] maxerror Maximum error bound for slew mode to be
allowed to take effect. May be `NULL`.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
\exception ERANGE The current time is not known to within `maxerror`.
*/
int byztime_slew(byztime_ctx *ctx, int64_t min_rate_ppb, int64_t max_rate_ppb,
byztime_stamp const *maxerror);
/** Stop slewing time estimates.
This function puts time estimation back into step mode after a previous
call to `byztime_slew()`.
\param[in] ctx Pointer to context object.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
*/
int byztime_step(byztime_ctx *ctx);
#if defined(_POSIX_C_SOURCE) && _POSIX_C_SOURCE >= 199309L
/** Install a signal handler for graceful recovery from page faults in the
timedata file.
If the timedata file gets truncated after it has been opened,
future accesses to it will raise SIGBUS. This function installs a
signal handler that will allow whatever function was trying to
access the truncated file to gracefully error out with EPROTO
rather than crashing the program. If the SIGBUS was caused for any
other reason, this handler will reraise SIGBUS with the kernel
default signal handler, which will cause the program to crash and
dump core just as it normally would.
A timedata file getting truncated while open is not something that
should ever ordinarily happen; it would indicate that the byztime
daemon or some or other process that has write permissions to the
file is buggy or malicious. Benign mistakes such as the user
specifying a path that does not point to a valid timedata file are
detected and handled without relying on SIGBUS. Nonetheless,
libbyztime is designed such that even a malicious byztime provider
should not ever be able to cause a consumer to crash or hang, and it
is necessary to be able to trap and recover from SIGBUS in order
to uphold that guarantee.
Calling this function will replace whatever SIGBUS handler was
previously installed, so use it only if nothing else in your
program needs to handle SIGBUS. Otherwise, instead call
`byztime_handle_sigbus()` from within your custom signal handler.
\param[in] oact If non-NULL, the action previously associated with
SIGBUS will be stored in the pointed location.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
This function is declared conditionally upon `defined(_POSIX_C_SOURCE) &&
_POSIX_C_SOURCE >= 199309L`.
*/
int byztime_install_sigbus_handler(struct sigaction *oact);
/** Handle SIGBUS signals caused by page faults in the timedata file.
This function must be called only from within a signal handler. If
`signo` is `SIGBUS` and the signal was raised when a libbyztime
function attempted to access a truncated timedata file, this function
will not return and will `siglongjmp()` into the sigjmp
environment which that function set up. Otherwise, it will return
without doing anything.
This function is declared conditionally upon `defined(_POSIX_C_SOURCE) &&
_POSIX_C_SOURCE >= 199309L`.
*/
void byztime_handle_sigbus(int signo, siginfo_t *info, void *context);
#endif
/** @} */
/** \defgroup provider Provider API
@{
*/
/** Opens a timedata file for read/write access, initializing it if necessary.
\param[in] pathname The path to the timedata file.
\return A pointer to a newly-allocated context object, or `NULL` on failure
and sets `errno`.
*/
byztime_ctx *byztime_open_rw(char const *pathname);
/** Sets the time offset `(global time - local time)` and error bound.
\param[in] ctx Pointer to context object.
\param[in] offset The offset.
\param[in] error Maximum error bound on `offset`.
\param[in] as_of Local time as of which `error` was computed. If `NULL`,
treated as "now".
\returns 0 on success.
\returns -1 on failure and sets `errno`.
*/
int byztime_set_offset(byztime_ctx *ctx, byztime_stamp const *offset,
byztime_stamp const *error, byztime_stamp const *as_of);
/** Gets the offset without any slewing or error calculation. */
void byztime_get_offset_quick(byztime_ctx const *ctx, byztime_stamp *offset);
/** Gets the data that was stored by the last call to `byztime_set_offset`,
without any recomputation of the error bounds. */
void byztime_get_offset_raw(byztime_ctx const *ctx, byztime_stamp *offset,
byztime_stamp *error, byztime_stamp *as_of);
/** Recompute and record the difference between global time and real time.
This is used to recover a best-guess `(global_time - local_time)` offset
after the next reboot.
\param[in] ctx Pointer to the context object.
\returns 0 on success.
\returns -1 on failure and sets `errno`.
*/
int byztime_update_real_offset(byztime_ctx *ctx);
/** @} */
#endif