clientlog: separate NTP timestamps from IP addresses

Instead of keeping one pair of RX and TX timestamp for each address, add
a separate RX->TX map using an ordered circular buffer. Save the RX
timestamps as 64-bit integers and search them with a combined linear
interpolation and binary algorithm.

This enables the server to support multiple interleaved clients sharing
the same IP address (e.g. NAT) and it will allow other improvements to
be implemented later. A drawback is that a single broken client sending
interleaved requests at a high rate (without spoofing the source
address) can now prevent clients on other addresses from getting
interleaved responses.

The total number of saved timestamps does not change. It's still
determined by the clientloglimit directive. A new option may be added
later if needed. The whole buffer is allocated at once, but only on
first use to not waste memory on client-only configurations.
This commit is contained in:
Miroslav Lichvar 2021-10-11 13:27:35 +02:00
parent 5cb469b204
commit 14b8df3702
5 changed files with 519 additions and 40 deletions

View file

@ -55,8 +55,6 @@ typedef struct {
int8_t rate[MAX_SERVICES];
int8_t ntp_timeout_rate;
uint8_t drop_flags;
NTP_int64 ntp_rx_ts;
NTP_int64 ntp_tx_ts;
} Record;
/* Hash table of records, there is a fixed number of records per slot */
@ -124,6 +122,35 @@ static int limit_interval[MAX_SERVICES];
/* Flag indicating whether facility is turned on or not */
static int active;
/* RX and TX timestamp saved for clients using interleaved mode */
typedef struct {
uint64_t rx_ts;
uint32_t flags;
int32_t tx_ts_offset;
} NtpTimestamps;
/* Flags for NTP timestamps */
#define NTPTS_DISABLED 1
#define NTPTS_VALID_TX 2
/* RX->TX map using a circular buffer with ordered timestamps */
typedef struct {
ARR_Instance timestamps;
uint32_t first;
uint32_t size;
uint32_t max_size;
uint32_t cached_index;
uint64_t cached_rx_ts;
} NtpTimestampMap;
static NtpTimestampMap ntp_ts_map;
/* Maximum interval of NTP timestamps in future after a backward step */
#define NTPTS_FUTURE_LIMIT (1LL << 32) /* 1 second */
/* Maximum number of timestamps moved in the array to insert a new timestamp */
#define NTPTS_INSERT_LIMIT 64
/* Global statistics */
static uint32_t total_hits[MAX_SERVICES];
static uint32_t total_drops[MAX_SERVICES];
@ -229,8 +256,6 @@ get_record(IPAddr *ip)
record->rate[i] = INVALID_RATE;
record->ntp_timeout_rate = INVALID_RATE;
record->drop_flags = 0;
UTI_ZeroNtp64(&record->ntp_rx_ts);
UTI_ZeroNtp64(&record->ntp_tx_ts);
return record;
}
@ -359,7 +384,8 @@ CLG_Initialise(void)
/* Calculate the maximum number of slots that can be allocated in the
configured memory limit. Take into account expanding of the hash
table where two copies exist at the same time. */
max_slots = CNF_GetClientLogLimit() / (sizeof (Record) * SLOT_SIZE * 3 / 2);
max_slots = CNF_GetClientLogLimit() /
((sizeof (Record) + sizeof (NtpTimestamps)) * SLOT_SIZE * 3 / 2);
max_slots = CLAMP(MIN_SLOTS, max_slots, MAX_SLOTS);
for (slots2 = 0; 1U << (slots2 + 1) <= max_slots; slots2++)
;
@ -373,6 +399,13 @@ CLG_Initialise(void)
UTI_GetRandomBytes(&ts_offset, sizeof (ts_offset));
ts_offset %= NSEC_PER_SEC / (1U << TS_FRAC);
ntp_ts_map.timestamps = NULL;
ntp_ts_map.first = 0;
ntp_ts_map.size = 0;
ntp_ts_map.max_size = 1U << (slots2 + SLOT_BITS);
ntp_ts_map.cached_index = 0;
ntp_ts_map.cached_rx_ts = 0ULL;
}
/* ================================================== */
@ -384,6 +417,8 @@ CLG_Finalise(void)
return;
ARR_DestroyInstance(records);
if (ntp_ts_map.timestamps)
ARR_DestroyInstance(ntp_ts_map.timestamps);
}
/* ================================================== */
@ -598,22 +633,294 @@ CLG_LogAuthNtpRequest(void)
/* ================================================== */
void CLG_GetNtpTimestamps(int index, NTP_int64 **rx_ts, NTP_int64 **tx_ts)
int
CLG_GetNtpMinPoll(void)
{
Record *record;
return limit_interval[CLG_NTP];
}
record = ARR_GetElement(records, index);
/* ================================================== */
*rx_ts = &record->ntp_rx_ts;
*tx_ts = &record->ntp_tx_ts;
static NtpTimestamps *
get_ntp_tss(uint32_t index)
{
return ARR_GetElement(ntp_ts_map.timestamps,
(ntp_ts_map.first + index) & (ntp_ts_map.max_size - 1));
}
/* ================================================== */
static int
find_ntp_rx_ts(uint64_t rx_ts, uint32_t *index)
{
uint64_t rx_x, rx_lo, rx_hi, step;
uint32_t i, x, lo, hi;
if (ntp_ts_map.cached_rx_ts == rx_ts && rx_ts != 0ULL) {
*index = ntp_ts_map.cached_index;
return 1;
}
if (ntp_ts_map.size == 0) {
*index = 0;
return 0;
}
lo = 0;
hi = ntp_ts_map.size - 1;
rx_lo = get_ntp_tss(lo)->rx_ts;
rx_hi = get_ntp_tss(hi)->rx_ts;
/* Check for ts < lo before ts > hi to trim timestamps from "future" later
if both conditions are true to not break the order of the endpoints.
Compare timestamps by their difference to allow adjacent NTP eras. */
if ((int64_t)(rx_ts - rx_lo) < 0) {
*index = 0;
return 0;
} else if ((int64_t)(rx_ts - rx_hi) > 0) {
*index = ntp_ts_map.size;
return 0;
}
/* Perform a combined linear interpolation and binary search */
for (i = 0; ; i++) {
if (rx_ts == rx_hi) {
*index = ntp_ts_map.cached_index = hi;
ntp_ts_map.cached_rx_ts = rx_ts;
return 1;
} else if (rx_ts == rx_lo) {
*index = ntp_ts_map.cached_index = lo;
ntp_ts_map.cached_rx_ts = rx_ts;
return 1;
} else if (lo + 1 == hi) {
*index = hi;
return 0;
}
if (hi - lo > 3 && i % 2 == 0) {
step = (rx_hi - rx_lo) / (hi - lo);
if (step == 0)
step = 1;
x = lo + (rx_ts - rx_lo) / step;
} else {
x = lo + (hi - lo) / 2;
}
if (x <= lo)
x = lo + 1;
else if (x >= hi)
x = hi - 1;
rx_x = get_ntp_tss(x)->rx_ts;
if ((int64_t)(rx_x - rx_ts) <= 0) {
lo = x;
rx_lo = rx_x;
} else {
hi = x;
rx_hi = rx_x;
}
}
}
/* ================================================== */
static uint64_t
ntp64_to_int64(NTP_int64 *ts)
{
return (uint64_t)ntohl(ts->hi) << 32 | ntohl(ts->lo);
}
/* ================================================== */
static void
int64_to_ntp64(uint64_t ts, NTP_int64 *ntp_ts)
{
ntp_ts->hi = htonl(ts >> 32);
ntp_ts->lo = htonl(ts & ((1ULL << 32) - 1));
}
/* ================================================== */
static uint32_t
push_ntp_tss(uint32_t index)
{
if (ntp_ts_map.size < ntp_ts_map.max_size) {
ntp_ts_map.size++;
} else {
ntp_ts_map.first = (ntp_ts_map.first + 1) % (ntp_ts_map.max_size);
if (index > 0)
index--;
}
return index;
}
/* ================================================== */
static void
set_ntp_tx_offset(NtpTimestamps *tss, NTP_int64 *rx_ts, struct timespec *tx_ts)
{
struct timespec ts;
if (!tx_ts) {
tss->flags &= ~NTPTS_VALID_TX;
return;
}
UTI_Ntp64ToTimespec(rx_ts, &ts);
UTI_DiffTimespecs(&ts, tx_ts, &ts);
if (ts.tv_sec < -2 || ts.tv_sec > 1) {
tss->flags &= ~NTPTS_VALID_TX;
return;
}
tss->tx_ts_offset = (int32_t)ts.tv_nsec + (int32_t)ts.tv_sec * (int32_t)NSEC_PER_SEC;
tss->flags |= NTPTS_VALID_TX;
}
/* ================================================== */
static void
get_ntp_tx(NtpTimestamps *tss, struct timespec *tx_ts)
{
int32_t offset = tss->tx_ts_offset;
NTP_int64 ntp_ts;
if (tss->flags & NTPTS_VALID_TX) {
int64_to_ntp64(tss->rx_ts, &ntp_ts);
UTI_Ntp64ToTimespec(&ntp_ts, tx_ts);
if (offset >= (int32_t)NSEC_PER_SEC) {
offset -= NSEC_PER_SEC;
tx_ts->tv_sec++;
}
tx_ts->tv_nsec += offset;
UTI_NormaliseTimespec(tx_ts);
} else {
UTI_ZeroTimespec(tx_ts);
}
}
/* ================================================== */
void
CLG_SaveNtpTimestamps(NTP_int64 *rx_ts, struct timespec *tx_ts)
{
NtpTimestamps *tss;
uint32_t i, index;
uint64_t rx;
if (!active)
return;
/* Allocate the array on first use */
if (!ntp_ts_map.timestamps) {
ntp_ts_map.timestamps = ARR_CreateInstance(sizeof (NtpTimestamps));
ARR_SetSize(ntp_ts_map.timestamps, ntp_ts_map.max_size);
}
rx = ntp64_to_int64(rx_ts);
if (rx == 0ULL)
return;
/* Disable the RX timestamp if it already exists to avoid responding
with a wrong TX timestamp */
if (find_ntp_rx_ts(rx, &index)) {
get_ntp_tss(index)->flags |= NTPTS_DISABLED;
return;
}
assert(index <= ntp_ts_map.size);
if (index == ntp_ts_map.size) {
/* Increase the size or drop the oldest timestamp to make room for
the new timestamp */
index = push_ntp_tss(index);
} else {
/* Trim timestamps in distant future after backward step */
while (index < ntp_ts_map.size &&
get_ntp_tss(ntp_ts_map.size - 1)->rx_ts - rx > NTPTS_FUTURE_LIMIT)
ntp_ts_map.size--;
/* Insert the timestamp if it is close to the latest timestamp.
Otherwise, replace the closest older or the oldest timestamp. */
if (index + NTPTS_INSERT_LIMIT >= ntp_ts_map.size) {
index = push_ntp_tss(index);
for (i = ntp_ts_map.size - 1; i > index; i--)
*get_ntp_tss(i) = *get_ntp_tss(i - 1);
} else {
if (index > 0)
index--;
}
}
ntp_ts_map.cached_index = index;
ntp_ts_map.cached_rx_ts = rx;
tss = get_ntp_tss(index);
tss->rx_ts = rx;
tss->flags = 0;
set_ntp_tx_offset(tss, rx_ts, tx_ts);
DEBUG_LOG("Saved RX+TX index=%"PRIu32" first=%"PRIu32" size=%"PRIu32,
index, ntp_ts_map.first, ntp_ts_map.size);
}
/* ================================================== */
void
CLG_UpdateNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts)
{
uint32_t index;
if (!ntp_ts_map.timestamps)
return;
if (!find_ntp_rx_ts(ntp64_to_int64(rx_ts), &index))
return;
set_ntp_tx_offset(get_ntp_tss(index), rx_ts, tx_ts);
}
/* ================================================== */
int
CLG_GetNtpMinPoll(void)
CLG_GetNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts)
{
return limit_interval[CLG_NTP];
NtpTimestamps *tss;
uint32_t index;
if (!ntp_ts_map.timestamps)
return 0;
if (!find_ntp_rx_ts(ntp64_to_int64(rx_ts), &index))
return 0;
tss = get_ntp_tss(index);
if (tss->flags & NTPTS_DISABLED)
return 0;
get_ntp_tx(tss, tx_ts);
return 1;
}
/* ================================================== */
void
CLG_DisableNtpTimestamps(NTP_int64 *rx_ts)
{
uint32_t index;
if (!ntp_ts_map.timestamps)
return;
if (find_ntp_rx_ts(ntp64_to_int64(rx_ts), &index))
get_ntp_tss(index)->flags |= NTPTS_DISABLED;
}
/* ================================================== */

View file

@ -43,9 +43,14 @@ extern int CLG_GetClientIndex(IPAddr *client);
extern int CLG_LogServiceAccess(CLG_Service service, IPAddr *client, struct timespec *now);
extern int CLG_LimitServiceRate(CLG_Service service, int index);
extern void CLG_LogAuthNtpRequest(void);
extern void CLG_GetNtpTimestamps(int index, NTP_int64 **rx_ts, NTP_int64 **tx_ts);
extern int CLG_GetNtpMinPoll(void);
/* Functions to save and retrieve timestamps for server interleaved mode */
extern void CLG_SaveNtpTimestamps(NTP_int64 *rx_ts, struct timespec *tx_ts);
extern void CLG_UpdateNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts);
extern int CLG_GetNtpTxTimestamp(NTP_int64 *rx_ts, struct timespec *tx_ts);
extern void CLG_DisableNtpTimestamps(NTP_int64 *rx_ts);
/* And some reporting functions, for use by chronyc. */
extern int CLG_GetNumberOfIndices(void);

View file

@ -2042,8 +2042,8 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
{
NTP_PacketInfo info;
NTP_Mode my_mode;
NTP_int64 *local_ntp_rx, *local_ntp_tx;
NTP_Local_Timestamp local_tx, *tx_ts;
NTP_int64 ntp_rx, *local_ntp_rx;
int log_index, interleaved, poll, version;
uint32_t kod;
@ -2106,7 +2106,7 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
CLG_LogAuthNtpRequest();
}
local_ntp_rx = local_ntp_tx = NULL;
local_ntp_rx = NULL;
tx_ts = NULL;
interleaved = 0;
@ -2115,18 +2115,15 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
in the interleaved mode. This means the third reply to a new client is
the earliest one that can be interleaved. We don't want to waste time
on clients that are not using the interleaved mode. */
if (kod == 0 && log_index >= 0) {
CLG_GetNtpTimestamps(log_index, &local_ntp_rx, &local_ntp_tx);
interleaved = !UTI_IsZeroNtp64(local_ntp_rx) &&
!UTI_CompareNtp64(&message->originate_ts, local_ntp_rx) &&
UTI_CompareNtp64(&message->receive_ts, &message->transmit_ts);
if (kod == 0 &&
UTI_CompareNtp64(&message->receive_ts, &message->transmit_ts) != 0) {
ntp_rx = message->originate_ts;
local_ntp_rx = &ntp_rx;
interleaved = CLG_GetNtpTxTimestamp(&ntp_rx, &local_tx.ts);
if (interleaved) {
UTI_Ntp64ToTimespec(local_ntp_tx, &local_tx.ts);
tx_ts = &local_tx;
} else {
UTI_ZeroNtp64(local_ntp_tx);
local_ntp_tx = NULL;
CLG_DisableNtpTimestamps(&ntp_rx);
}
}
@ -2144,9 +2141,8 @@ NCR_ProcessRxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
rx_ts, tx_ts, local_ntp_rx, NULL, remote_addr, local_addr,
message, &info);
/* Save the transmit timestamp */
if (tx_ts)
UTI_TimespecToNtp64(&tx_ts->ts, local_ntp_tx, NULL);
if (local_ntp_rx)
CLG_SaveNtpTimestamps(local_ntp_rx, tx_ts ? &tx_ts->ts : NULL);
}
/* ================================================== */
@ -2208,10 +2204,9 @@ void
NCR_ProcessTxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_addr,
NTP_Local_Timestamp *tx_ts, NTP_Packet *message, int length)
{
NTP_int64 *local_ntp_rx, *local_ntp_tx;
NTP_Local_Timestamp local_tx;
NTP_int64 *local_ntp_rx;
NTP_PacketInfo info;
int log_index;
if (!parse_packet(message, length, &info))
return;
@ -2219,18 +2214,17 @@ NCR_ProcessTxUnknown(NTP_Remote_Address *remote_addr, NTP_Local_Address *local_a
if (info.mode == MODE_BROADCAST)
return;
log_index = CLG_GetClientIndex(&remote_addr->ip_addr);
if (log_index < 0)
return;
if (SMT_IsEnabled() && info.mode == MODE_SERVER)
UTI_AddDoubleToTimespec(&tx_ts->ts, SMT_GetOffset(&tx_ts->ts), &tx_ts->ts);
CLG_GetNtpTimestamps(log_index, &local_ntp_rx, &local_ntp_tx);
local_ntp_rx = &message->receive_ts;
if (!CLG_GetNtpTxTimestamp(local_ntp_rx, &local_tx.ts))
return;
UTI_Ntp64ToTimespec(local_ntp_tx, &local_tx.ts);
update_tx_timestamp(&local_tx, tx_ts, local_ntp_rx, NULL, message);
UTI_TimespecToNtp64(&local_tx.ts, local_ntp_tx, NULL);
CLG_UpdateNtpTxTimestamp(local_ntp_rx, &local_tx.ts);
}
/* ================================================== */

View file

@ -8,6 +8,18 @@ client_conf="
logdir tmp
log rawmeasurements"
server_conf="noclientlog"
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail
check_sync || test_fail
check_file_messages "111 111 1111.* 4I [DKH] [DKH]\$" 0 0 measurements.log || test_fail
rm -f tmp/measurements.log
server_conf=""
run_test || test_fail
check_chronyd_exit || test_fail
check_source_selection || test_fail

View file

@ -25,15 +25,24 @@
#include <clientlog.c>
static uint64_t
get_random64(void)
{
return ((uint64_t)random() << 40) ^ ((uint64_t)random() << 20) ^ random();
}
void
test_unit(void)
{
int i, j, index;
uint64_t ts64, prev_first_ts64, prev_last_ts64, max_step;
uint32_t index2, prev_first, prev_size;
struct timespec ts, ts2;
int i, j, k, index, shift;
CLG_Service s;
struct timespec ts;
NTP_int64 ntp_ts;
IPAddr ip;
char conf[][100] = {
"clientloglimit 10000",
"clientloglimit 20000",
"ratelimit interval 3 burst 4 leak 3",
"cmdratelimit interval 3 burst 4 leak 3",
"ntsratelimit interval 6 burst 8 leak 3",
@ -67,7 +76,7 @@ test_unit(void)
}
DEBUG_LOG("records %u", ARR_GetSize(records));
TEST_CHECK(ARR_GetSize(records) == 64);
TEST_CHECK(ARR_GetSize(records) == 128);
s = CLG_NTP;
@ -82,6 +91,158 @@ test_unit(void)
DEBUG_LOG("requests %d responses %d", i, j);
TEST_CHECK(j * 4 < i && j * 6 > i);
TEST_CHECK(!ntp_ts_map.timestamps);
UTI_ZeroNtp64(&ntp_ts);
CLG_SaveNtpTimestamps(&ntp_ts, NULL);
TEST_CHECK(ntp_ts_map.timestamps);
TEST_CHECK(ntp_ts_map.first == 0);
TEST_CHECK(ntp_ts_map.size == 0);
TEST_CHECK(ntp_ts_map.max_size == 128);
TEST_CHECK(ARR_GetSize(ntp_ts_map.timestamps) == ntp_ts_map.max_size);
TEST_CHECK(ntp_ts_map.max_size > NTPTS_INSERT_LIMIT);
for (i = 0; i < 200; i++) {
DEBUG_LOG("iteration %d", i);
max_step = (1ULL << (i % 50));
ts64 = 0ULL - 100 * max_step;
ntp_ts_map.first = i % ntp_ts_map.max_size;
ntp_ts_map.size = 0;
ntp_ts_map.cached_rx_ts = 0ULL;
for (j = 0; j < 500; j++) {
do {
ts64 += get_random64() % max_step + 1;
} while (ts64 == 0ULL);
int64_to_ntp64(ts64, &ntp_ts);
if (random() % 10) {
UTI_Ntp64ToTimespec(&ntp_ts, &ts);
UTI_AddDoubleToTimespec(&ts, TST_GetRandomDouble(-1.999, 1.999), &ts);
} else {
UTI_ZeroTimespec(&ts);
}
CLG_SaveNtpTimestamps(&ntp_ts,
UTI_IsZeroTimespec(&ts) ? (random() % 2 ? &ts : NULL) : &ts);
if (j < ntp_ts_map.max_size) {
TEST_CHECK(ntp_ts_map.size == j + 1);
TEST_CHECK(ntp_ts_map.first == i % ntp_ts_map.max_size);
} else {
TEST_CHECK(ntp_ts_map.size == ntp_ts_map.max_size);
TEST_CHECK(ntp_ts_map.first == (i + j + ntp_ts_map.size + 1) % ntp_ts_map.max_size);
}
TEST_CHECK(CLG_GetNtpTxTimestamp(&ntp_ts, &ts2));
TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) == 0);
for (k = random() % 4; k > 0; k--) {
int64_to_ntp64(get_ntp_tss(random() % ntp_ts_map.size)->rx_ts, &ntp_ts);
if (random() % 2)
TEST_CHECK(CLG_GetNtpTxTimestamp(&ntp_ts, &ts));
UTI_Ntp64ToTimespec(&ntp_ts, &ts);
UTI_AddDoubleToTimespec(&ts, TST_GetRandomDouble(-1.999, 1.999), &ts);
CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts);
TEST_CHECK(CLG_GetNtpTxTimestamp(&ntp_ts, &ts2));
TEST_CHECK(UTI_CompareTimespecs(&ts, &ts2) == 0);
if (ntp_ts_map.size > 1) {
index = random() % (ntp_ts_map.size - 1);
if (get_ntp_tss(index)->rx_ts + 1 != get_ntp_tss(index + 1)->rx_ts) {
int64_to_ntp64(get_ntp_tss(index)->rx_ts + 1, &ntp_ts);
TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts));
int64_to_ntp64(get_ntp_tss(index + 1)->rx_ts - 1, &ntp_ts);
TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts));
CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts);
}
}
if (random() % 2) {
int64_to_ntp64(get_ntp_tss(0)->rx_ts - 1, &ntp_ts);
TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts));
int64_to_ntp64(get_ntp_tss(ntp_ts_map.size - 1)->rx_ts + 1, &ntp_ts);
TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts));
CLG_UpdateNtpTxTimestamp(&ntp_ts, &ts);
}
}
}
for (j = 0; j < 500; j++) {
shift = (i % 3) * 26;
if (i % 7 == 0) {
while (ntp_ts_map.size < ntp_ts_map.max_size) {
ts64 += get_random64() >> (shift + 8);
int64_to_ntp64(ts64, &ntp_ts);
CLG_SaveNtpTimestamps(&ntp_ts, NULL);
if (ntp_ts_map.cached_index + NTPTS_INSERT_LIMIT < ntp_ts_map.size)
ts64 = get_ntp_tss(ntp_ts_map.size - 1)->rx_ts;
}
}
do {
if (ntp_ts_map.size > 1 && random() % 2) {
k = random() % (ntp_ts_map.size - 1);
ts64 = get_ntp_tss(k)->rx_ts +
(get_ntp_tss(k + 1)->rx_ts - get_ntp_tss(k)->rx_ts) / 2;
} else {
ts64 = get_random64() >> shift;
}
} while (ts64 == 0ULL);
int64_to_ntp64(ts64, &ntp_ts);
prev_first = ntp_ts_map.first;
prev_size = ntp_ts_map.size;
prev_first_ts64 = get_ntp_tss(0)->rx_ts;
prev_last_ts64 = get_ntp_tss(prev_size - 1)->rx_ts;
CLG_SaveNtpTimestamps(&ntp_ts, NULL);
TEST_CHECK(find_ntp_rx_ts(ts64, &index2));
if (ntp_ts_map.size > 1) {
TEST_CHECK(ntp_ts_map.size > 0 && ntp_ts_map.size <= ntp_ts_map.max_size);
if (get_ntp_tss(index2)->flags & NTPTS_DISABLED)
continue;
TEST_CHECK(get_ntp_tss(ntp_ts_map.size - 1)->rx_ts - ts64 <= NTPTS_FUTURE_LIMIT);
if ((int64_t)(prev_last_ts64 - ts64) <= NTPTS_FUTURE_LIMIT) {
TEST_CHECK(prev_size + 1 >= ntp_ts_map.size);
if (index2 + NTPTS_INSERT_LIMIT + 1 >= ntp_ts_map.size &&
!(index2 == 0 &&
((NTPTS_INSERT_LIMIT == prev_size && (int64_t)(ts64 - prev_first_ts64) > 0) ||
(NTPTS_INSERT_LIMIT + 1 == prev_size && (int64_t)(ts64 - prev_first_ts64) < 0))))
TEST_CHECK((prev_first + prev_size + 1) % ntp_ts_map.max_size ==
(ntp_ts_map.first + ntp_ts_map.size) % ntp_ts_map.max_size);
else
TEST_CHECK(prev_first + prev_size == ntp_ts_map.first + ntp_ts_map.size);
}
TEST_CHECK((int64_t)(get_ntp_tss(ntp_ts_map.size - 1)->rx_ts -
get_ntp_tss(0)->rx_ts) > 0);
for (k = 0; k + 1 < ntp_ts_map.size; k++)
TEST_CHECK((int64_t)(get_ntp_tss(k + 1)->rx_ts - get_ntp_tss(k)->rx_ts) > 0);
}
if (random() % 10 == 0) {
CLG_DisableNtpTimestamps(&ntp_ts);
TEST_CHECK(!CLG_GetNtpTxTimestamp(&ntp_ts, &ts));
}
for (k = random() % 10; k > 0; k--) {
ts64 = get_random64() >> shift;
int64_to_ntp64(ts64, &ntp_ts);
CLG_GetNtpTxTimestamp(&ntp_ts, &ts);
}
}
}
CLG_Finalise();
CNF_Finalise();
}