diff --git a/clientlog.c b/clientlog.c index cec2e8e..09af04b 100644 --- a/clientlog.c +++ b/clientlog.c @@ -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; } /* ================================================== */ diff --git a/clientlog.h b/clientlog.h index 6349646..a400102 100644 --- a/clientlog.h +++ b/clientlog.h @@ -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); diff --git a/ntp_core.c b/ntp_core.c index 2fb5183..d113d22 100644 --- a/ntp_core.c +++ b/ntp_core.c @@ -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); } /* ================================================== */ diff --git a/test/simulation/122-xleave b/test/simulation/122-xleave index f137f19..5cb603d 100755 --- a/test/simulation/122-xleave +++ b/test/simulation/122-xleave @@ -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 diff --git a/test/unit/clientlog.c b/test/unit/clientlog.c index 850cedf..9d1066c 100644 --- a/test/unit/clientlog.c +++ b/test/unit/clientlog.c @@ -25,15 +25,24 @@ #include +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(); }