+* [Bug 2250] Rework of leap second handling machine
(4.2.7p388) 2013/09/19 Released by Harlan Stenn <stenn@ntp.org>
* [Bug 2473] NTPD exits after clock is stepped backwards externally
(4.2.7p387) 2013/09/16 Released by Harlan Stenn <stenn@ntp.org>
AC_CONFIG_FILES([scripts/summary], [chmod +x scripts/summary])
AC_CONFIG_FILES([tests/Makefile])
AC_CONFIG_FILES([tests/libntp/Makefile])
+AC_CONFIG_FILES([tests/ntpd/Makefile])
AC_CONFIG_FILES([util/Makefile])
AC_CONFIG_SUBDIRS([sntp])
extern void process_packet (struct peer *, struct pkt *, u_int);
extern void clock_select (void);
-extern int leap_tai; /* TAI at next leap */
-extern u_long leap_sec; /* next scheduled leap from file */
-extern u_long leap_peers; /* next scheduled leap from peers */
-extern u_long leapsec; /* seconds to next leap */
-extern u_long leap_expire; /* leap information expiration */
+extern u_long leapsec; /* seconds to next leap (proximity class) */
extern int sys_orphan;
extern double sys_mindisp;
extern double sys_maxdist;
NTP_PRINTF(2, 3);
extern void record_raw_stats (sockaddr_u *srcadr, sockaddr_u *dstadr, l_fp *t1, l_fp *t2, l_fp *t3, l_fp *t4, int leap, int version, int mode, int stratum, int poll, int precision, double root_delay, double root_dispersion, u_int32 refid);
extern void check_leap_file (void);
-extern u_long leap_month(u_long);
extern void record_crypto_stats (sockaddr_u *, const char *);
#ifdef DEBUG
extern void record_timing_stats (const char *);
$(srcdir)/ntpd.mdoc.in \
$(NULL)
-noinst_HEADERS = declcond.h
+noinst_HEADERS = \
+ declcond.h \
+ ntp_leapsec.h \
+ $(NULL)
install-data-local: install-html
ntp_control.c \
ntp_crypto.c \
ntp_filegen.c \
+ ntp_leapsec.c \
ntp_loopfilter.c \
ntp_monitor.c \
ntp_peer.c \
#include "ntp_config.h"
#include "ntp_crypto.h"
#include "ntp_assert.h"
+#include "ntp_leapsec.h"
#include "ntp_md5.h" /* provides OpenSSL digest API */
#include "lib_strbuf.h"
#ifdef KERNEL_PLL
#ifdef KERNEL_PLL
static struct timex ntx;
static u_long ntp_adjtime_time;
- const double tscale =
+ static const double tscale =
# ifdef STA_NANO
1e-9;
# else
1e-6;
# endif
- const double to_ms = 1e3 * tscale;
+ static const double to_ms = 1e3 * tscale;
/*
* CS_K_* variables depend on up-to-date output of ntp_adjtime()
if (sys_tai > 0)
ctl_putuint(sys_var[CS_TAI].text, sys_tai);
break;
-
+
case CS_LEAPTAB:
- if (leap_sec > 0)
- ctl_putfs(sys_var[CS_LEAPTAB].text,
- leap_sec);
+ {
+ leap_signature_t lsig;
+ leapsec_getsig(&lsig);
+ if (lsig.ttime > 0)
+ ctl_putfs(sys_var[CS_LEAPTAB].text, lsig.ttime);
break;
-
+ }
+
case CS_LEAPEND:
- if (leap_expire > 0)
- ctl_putfs(sys_var[CS_LEAPEND].text,
- leap_expire);
+ {
+ leap_signature_t lsig;
+ leapsec_getsig(&lsig);
+ if (lsig.etime > 0)
+ ctl_putfs(sys_var[CS_LEAPEND].text, lsig.etime);
break;
+ }
case CS_RATE:
ctl_putuint(sys_var[CS_RATE].text, ntp_minpoll);
#include "ntp_random.h"
#include "ntp_assert.h"
#include "ntp_calendar.h"
+#include "ntp_leapsec.h"
#include "openssl/asn1_mac.h"
#include "openssl/bn.h"
* via the kernel to other applications.
*/
case CRYPTO_LEAP | CRYPTO_RESP:
-
/*
* Discard the message if invalid. We can't
* compare the value timestamps here, as they
* than the stored ones, install the new leap
* values and recompute the signatures.
*/
- if (ntohl(ep->pkt[2]) > leap_expire) {
+ if (leapsec_add_fix(ntohl(ep->pkt[1]),
+ ntohl(ep->pkt[2]),
+ ntohl(ep->pkt[0]),
+ NULL))
+ {
+ leap_signature_t lsig;
+
+ leapsec_getsig(&lsig);
tai_leap.tstamp = ep->tstamp;
tai_leap.fstamp = ep->fstamp;
tai_leap.vallen = ep->vallen;
- leap_tai = ntohl(ep->pkt[0]);
- leap_sec = ntohl(ep->pkt[1]);
- leap_expire = ntohl(ep->pkt[2]);
crypto_update();
mprintf_event(EVNT_TAI, peer,
- "%d leap %s expire %s", leap_tai,
- fstostr(leap_sec),
- fstostr(leap_expire));
+ "%d leap %s expire %s", lsig.taiof,
+ fstostr(lsig.ttime),
+ fstostr(lsig.etime));
}
peer->crypto |= CRYPTO_FLAG_LEAP;
peer->flash &= ~TEST8;
char statstr[NTP_MAXSTRLEN]; /* statistics for filegen */
u_int32 *ptr;
u_int len;
+ leap_signature_t lsig;
hostval.tstamp = htonl(crypto_time());
if (hostval.tstamp == 0)
tai_leap.ptr = emalloc(len);
tai_leap.vallen = htonl(len);
ptr = (u_int32 *)tai_leap.ptr;
- ptr[0] = htonl(leap_tai);
- ptr[1] = htonl(leap_sec);
- ptr[2] = htonl(leap_expire);
+ leapsec_getsig(&lsig);
+ ptr[0] = htonl(lsig.taiof);
+ ptr[1] = htonl(lsig.ttime);
+ ptr[2] = htonl(lsig.etime);
if (tai_leap.sig == NULL)
tai_leap.sig = emalloc(sign_siglen);
EVP_SignInit(&ctx, sign_digest);
EVP_SignUpdate(&ctx, tai_leap.ptr, len);
if (EVP_SignFinal(&ctx, tai_leap.sig, &len, sign_pkey))
tai_leap.siglen = htonl(sign_siglen);
- if (leap_sec > 0)
+ if (lsig.ttime > 0)
crypto_flags |= CRYPTO_FLAG_TAI;
snprintf(statstr, sizeof(statstr), "signature update ts %u",
ntohl(hostval.tstamp));
--- /dev/null
+/*
+ * ntp_leapsec.c - leap second processing for NTPD
+ *
+ * Written by Juergen Perlinger (perlinger@ntp.org) for the NTP project.
+ * The contents of 'html/copyright.html' apply.
+ * ----------------------------------------------------------------------
+ * This is an attempt to get the leap second handling into a dedicated
+ * module to make the somewhat convoluted logic testable.
+ */
+
+#include <config.h>
+#include <sys/types.h>
+#include <ctype.h>
+
+#include "ntp_types.h"
+#include "ntp_fp.h"
+#include "ntp_stdlib.h"
+#include "ntp_calendar.h"
+#include "ntp_leapsec.h"
+#include "ntp.h"
+
+
+/* ---------------------------------------------------------------------
+ * GCC is rather sticky with its 'const' attribute. We have to it more
+ * explicit than with a cast...
+ */
+static void* noconst(const void* ptr)
+{
+ union {
+ const void * cp;
+ void * vp;
+ } tmp;
+ tmp.cp = ptr;
+ return tmp.vp;
+}
+
+/* ---------------------------------------------------------------------
+ * Things to put into libntp...
+ */
+
+vint64
+strtouv64(
+ const char * begp,
+ char ** endp,
+ int base)
+{
+ vint64 res;
+ u_char digit;
+ int sig, num;
+ const u_char *src;
+
+ num = 0;
+ src = (const u_char*)begp;
+ while (*src && isspace(*src))
+ src++;
+
+ if (*src == '-') {
+ src++;
+ sig = 1;
+ } else {
+ sig = 0;
+ src += (*src == '+');
+ }
+
+ if (base == 0) {
+ base = 10;
+ if (*src == '0') {
+ src++;
+ base = 8;
+ if (toupper(*src) == 'X') {
+ src++;
+ base = 16;
+ }
+ }
+ } else if (base == 16) { /* remove optional leading '0x' or '0X' */
+ if (src[0] == '0' && toupper(src[1]) == 'X')
+ src += 2;
+ } else if (base <= 2 || base > 36) {
+ memset(&res, 0xFF, sizeof(res));
+ errno = ERANGE;
+ return res;
+ }
+
+ memset(&res, 0, sizeof(res));
+ while (*src) {
+ if (isdigit(*src))
+ digit = (u_char)*src - '0';
+ else if (isupper(*src))
+ digit = (u_char)*src - 'A' + 10;
+ else if (islower(*src))
+ digit = (u_char)*src - 'a' + 10;
+ else
+ break;
+ if (digit >= base)
+ break;
+ num = 1;
+#if defined(HAVE_INT64)
+ res.Q_s = res.Q_s * base + digit;
+#else
+ /* res *= base, using 16x16->32 bit
+ * multiplication. Slow but portable.
+ */
+ {
+ u_int32 accu;
+ accu = (u_int32)res.W_s.ll * base;
+ res.W_s.ll = (u_short)accu;
+ accu = (accu >> 16)
+ + (u_int32)res.W_s.lh * base;
+ res.W_s.lh = (u_short)accu;
+ /* the upper bits can be done in one step: */
+ res.D_s.hi = res.D_s.hi * base + (accu >> 16);
+ }
+ M_ADD(res.D_s.hi, res.D_s.lo, 0, digit);
+#endif
+ src++;
+ }
+ if (!num)
+ errno = EINVAL;
+ if (endp)
+ *endp = (char*)noconst(src);
+ if (sig)
+ M_NEG(res.D_s.hi, res.D_s.lo);
+ return res;
+}
+
+int icmpv64(
+ const vint64 * lhs,
+ const vint64 * rhs)
+{
+ int res;
+
+#if defined(HAVE_INT64)
+ res = (lhs->q_s > rhs->q_s)
+ - (lhs->q_s < rhs->q_s);
+#else
+ res = (lhs->d_s.hi > rhs->d_s.hi)
+ - (lhs->d_s.hi < rhs->d_s.hi);
+ if ( ! res )
+ res = (lhs->D_s.lo > rhs->D_s.lo)
+ - (lhs->D_s.lo < rhs->D_s.lo);
+#endif
+
+ return res;
+}
+
+
+int ucmpv64(
+ const vint64 * lhs,
+ const vint64 * rhs)
+{
+ int res;
+
+#if defined(HAVE_INT64)
+ res = (lhs->Q_s > rhs->Q_s)
+ - (lhs->Q_s < rhs->Q_s);
+#else
+ res = (lhs->D_s.hi > rhs->D_s.hi)
+ - (lhs->D_s.hi < rhs->D_s.hi);
+ if ( ! res )
+ res = (lhs->D_s.lo > rhs->D_s.lo)
+ - (lhs->D_s.lo < rhs->D_s.lo);
+#endif
+ return res;
+}
+
+static vint64
+addv64i32(
+ const vint64 * lhs,
+ int32 rhs)
+{
+ vint64 res;
+
+ res = *lhs;
+#if defined(HAVE_INT64)
+ res.q_s += rhs;
+#else
+ M_ADD(res.D_s.hi, res.D_s.lo, -(rhs < 0), rhs);
+#endif
+ return res;
+}
+
+#if 0
+static vint64
+subv64i32(
+ const vint64 * lhs,
+ int32 rhs)
+{
+ vint64 res;
+
+ res = *lhs;
+#if defined(HAVE_INT64)
+ res.q_s -= rhs;
+#else
+ M_SUB(res.D_s.hi, res.D_s.lo, -(rhs < 0), rhs);
+#endif
+ return res;
+}
+#endif
+
+#if 0
+static vint64
+addv64u32(
+ const vint64 * lhs,
+ u_int32 rhs)
+{
+ vint64 res;
+
+ res = *lhs;
+#if defined(HAVE_INT64)
+ res.Q_s += rhs;
+#else
+ M_ADD(res.D_s.hi, res.D_s.lo, 0, rhs);
+#endif
+ return res;
+}
+#endif
+
+static vint64
+subv64u32(
+ const vint64 * lhs,
+ u_int32 rhs)
+{
+ vint64 res;
+
+ res = *lhs;
+#if defined(HAVE_INT64)
+ res.Q_s -= rhs;
+#else
+ M_SUB(res.D_s.hi, res.D_s.lo, 0, rhs);
+#endif
+ return res;
+}
+
+/* ---------------------------------------------------------------------
+ * Things to put into ntp_calendar... (and consequently into libntp...)
+ */
+
+/* ------------------------------------------------------------------ */
+static int
+ntpcal_ntp64_to_date(
+ struct calendar *jd,
+ const vint64 *ntp)
+{
+ ntpcal_split ds;
+
+ ds = ntpcal_daysplit(ntp);
+ ds.hi += ntpcal_daysec_to_date(jd, ds.lo);
+
+ return ntpcal_rd_to_date(jd, ds.hi + DAY_NTP_STARTS);
+}
+
+/* ------------------------------------------------------------------ */
+static vint64
+ntpcal_date_to_ntp64(
+ const struct calendar *jd)
+{
+ return ntpcal_dayjoin(ntpcal_date_to_rd(jd) - DAY_NTP_STARTS,
+ ntpcal_date_to_daysec(jd));
+}
+
+
+/* ---------------------------------------------------------------------
+ * Our internal data structure
+ */
+#define MAX_HIST 10 /* history of leap seconds */
+
+struct leap_info {
+ vint64 ttime; /* transition time (after the step, ntp scale) */
+ u_int32 stime; /* schedule limit (a month before transition) */
+ short total; /* accumulated leap seconds from that point */
+ u_short dynls; /* dynamic: inserted on peer/clock request */
+};
+typedef struct leap_info leap_info_t;
+
+struct leap_head {
+ vint64 expire; /* table expiration time */
+ u_short size; /* number of infos in table */
+ int32 total; /* total leaps before first entry */
+ vint64 when; /* begin of next leap era */
+ vint64 ttime; /* nominal transition time */
+ vint64 stime; /* announce leapsec 1 month ahead */
+ vint64 base; /* base of this leap era */
+ short this_tai; /* current TAI offset */
+ short next_tai; /* TAI offset after 'when'*/
+ short dynls; /* next leap is dynamic */
+};
+typedef struct leap_head leap_head_t;
+
+struct leap_table {
+ leap_signature_t lsig;
+ leap_head_t head;
+ leap_info_t info[MAX_HIST];
+};
+
+/* Where we store our tables */
+static leap_table_t _ltab[2], *_lptr;
+static int/*BOOL*/ _electric;
+
+/* Forward decls of local helpers */
+static int add_range(leap_table_t*, const leap_info_t*);
+static char * get_line(leapsec_reader, void*, char*, size_t);
+static char * skipws(const char*);
+static void reload_limits(leap_table_t*, const vint64*);
+static int betweenu32(u_int32, u_int32, u_int32);
+static void reset_times(leap_table_t*);
+static int leapsec_add(leap_table_t*, const vint64*, int);
+static int leapsec_raw(leap_table_t*, const vint64 *, int, int);
+
+/* =====================================================================
+ * Get & Set the current leap table
+ */
+
+/* ------------------------------------------------------------------ */
+leap_table_t *
+leapsec_get_table(
+ int alternate)
+{
+ leap_table_t *p1, *p2;
+
+ p1 = _lptr;
+ p1 = &_ltab[p1 == &_ltab[1]];
+ p2 = &_ltab[p1 == &_ltab[0]];
+ if (alternate) {
+ memcpy(p2, p1, sizeof(leap_table_t));
+ p1 = p2;
+ }
+
+ return p1;
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_set_table(
+ leap_table_t * pt)
+{
+ if (pt == &_ltab[0] || pt == &_ltab[1])
+ _lptr = pt;
+ return _lptr == pt;
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_electric(
+ int/*BOOL*/ on)
+{
+ int res = _electric;
+ if (on < 0)
+ return res;
+
+ _electric = on != 0;
+ if (_electric == res)
+ return res;
+
+ if (_lptr == &_ltab[0] || _lptr == &_ltab[1])
+ reset_times(_lptr);
+
+ return res;
+}
+
+/* =====================================================================
+ * API functions that operate on tables
+ */
+
+/* ---------------------------------------------------------------------
+ * Clear all leap second data. Use it for init & cleanup
+ */
+void
+leapsec_clear(
+ leap_table_t * pt)
+{
+ memset(&pt->lsig, 0, sizeof(pt->lsig));
+ memset(&pt->head, 0, sizeof(pt->head));
+ reset_times(pt);
+}
+
+/* ---------------------------------------------------------------------
+ * Check if expired at a given time
+ */
+int/*BOOL*/
+leapsec_is_expired(
+ leap_table_t * pt ,
+ u_int32 when,
+ const time_t * tpiv)
+{
+ vint64 limit;
+
+ limit = ntpcal_ntp_to_ntp(when, tpiv);
+ return ucmpv64(&limit, &pt->head.expire) >= 0;
+}
+
+/* ---------------------------------------------------------------------
+ * Load a leap second file and check expiration on the go
+ */
+int/*BOOL*/
+leapsec_load(
+ leap_table_t * pt ,
+ leapsec_reader func,
+ void * farg,
+ int use_build_limit)
+{
+ char *cp, *ep, linebuf[50];
+ vint64 tt, limit;
+ int al;
+ struct calendar build;
+
+ leapsec_clear(pt);
+ if (use_build_limit && ntpcal_get_build_date(&build))
+ limit = ntpcal_date_to_ntp64(&build);
+ else
+ memset(&limit, 0, sizeof(limit));
+
+ while (get_line(func, farg, linebuf, sizeof(linebuf))) {
+ cp = skipws(linebuf);
+ if (*cp == '#') {
+ cp++;
+ if (*cp == '@' || *cp == '$') {
+ cp = skipws(cp+1);
+ pt->head.expire = strtouv64(cp, &ep, 10);
+ if (ep == cp || *ep > ' ')
+ goto fail_read;
+ pt->lsig.etime = pt->head.expire.D_s.lo;
+ }
+ } else if (isdigit(*cp)) {
+ tt = strtouv64(cp, &ep, 10);
+ if (ep == cp || *ep > ' ')
+ goto fail_read;
+ cp = skipws(ep);
+ al = strtol(cp, &ep, 10);
+ if (ep == cp || *ep > ' ')
+ goto fail_read;
+ cp = skipws(ep);
+ if (ucmpv64(&tt, &limit) >= 0) {
+ if (!leapsec_raw(pt, &tt, al, FALSE))
+ goto fail_insn;
+ } else {
+ pt->head.total = al;
+ }
+ pt->lsig.ttime = tt.D_s.lo;
+ pt->lsig.taiof = al;
+ }
+ }
+ return TRUE;
+
+fail_read:
+ errno = EILSEQ;
+fail_insn:
+ leapsec_clear(pt);
+ return FALSE;
+}
+
+/* ---------------------------------------------------------------------
+ * Dump a table in human-readable format. Use 'fprintf' and a FILE
+ * pointer if you want to get it printed into a stream.
+ */
+void
+leapsec_dump(
+ const leap_table_t * pt ,
+ leapsec_dumper func,
+ void * farg)
+{
+ int idx;
+ vint64 ts;
+ struct calendar atb, ttb;
+
+ ntpcal_ntp64_to_date(&ttb, &pt->head.expire);
+ (*func)(farg, "leap table (%u entries) expires at %04u-%02u-%02u:\n",
+ pt->head.size,
+ ttb.year, ttb.month, ttb.monthday);
+ idx = pt->head.size;
+ while (idx-- != 0) {
+ ts = pt->info[idx].ttime;
+ ntpcal_ntp64_to_date(&ttb, &ts);
+ ts = subv64u32(&ts, pt->info[idx].stime);
+ ntpcal_ntp64_to_date(&atb, &ts);
+
+ (*func)(farg, "%04u-%02u-%02u [%c] (%04u-%02u-%02u) - %d\n",
+ ttb.year, ttb.month, ttb.monthday,
+ "-*"[pt->info[idx].dynls != 0],
+ atb.year, atb.month, atb.monthday,
+ pt->info[idx].total);
+ }
+}
+
+/* =====================================================================
+ * usecase driven API functions
+ */
+
+int/*BOOL*/
+leapsec_query(
+ leap_result_t * qr ,
+ u_int32 ts32 ,
+ const time_t * pivot)
+{
+ leap_table_t * pt;
+ vint64 ts64, last;
+ u_int32 when32;
+ int fired;
+
+ /* preset things we use later on... */
+ fired = FALSE;
+ ts64 = ntpcal_ntp_to_ntp(ts32, pivot);
+ pt = leapsec_get_table(FALSE);
+ memset(qr, 0, sizeof(leap_result_t));
+
+ if (ucmpv64(&ts64, &pt->head.base) < 0) {
+ /* Ooops? Clock step backward? Oh, well... */
+ reload_limits(pt, &ts64);
+ } else if (ucmpv64(&ts64, &pt->head.when) >= 0) {
+ /* Boundary crossed in forward direction. This might
+ * indicate a leap transition, so we prepare for that
+ * case.
+ */
+ last = pt->head.ttime;
+ qr->warped = last.D_s.lo - pt->head.when.D_s.lo;
+ reload_limits(pt, &ts64);
+ if (ucmpv64(&pt->head.base, &last) == 0)
+ fired = TRUE;
+ else
+ qr->warped = 0;
+ }
+
+ qr->tai_offs = pt->head.this_tai;
+
+ /* If before the next schedulung alert, we're done. */
+ if (ucmpv64(&ts64, &pt->head.stime) < 0)
+ return fired;
+
+ /* now start to collect the remaing data */
+ when32 = pt->head.when.D_s.lo;
+
+ qr->tai_diff = pt->head.next_tai - pt->head.this_tai;
+ qr->when = pt->head.when;
+ qr->dist = when32 - ts32;
+ qr->dynamic = pt->head.dynls;
+ qr->proximity= LSPROX_SCHEDULE;
+
+ /* if not in the last day before transition, we're done. */
+ if (!betweenu32(when32-SECSPERDAY, ts32, when32))
+ return fired;
+
+ qr->proximity = LSPROX_ANNOUNCE;
+ if (!betweenu32(when32-10, ts32, when32))
+ return fired;
+
+ qr->proximity = LSPROX_ALERT;
+ return fired;
+}
+
+/* ------------------------------------------------------------------ */
+/* Reset the current leap frame */
+void
+leapsec_reset_frame(void)
+{
+ reset_times(leapsec_get_table(FALSE));
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_load_file(
+ FILE * ifp ,
+ int blimit)
+{
+ leap_table_t * pt;
+
+ pt = leapsec_get_table(TRUE);
+ return leapsec_load(pt, (leapsec_reader)getc, ifp, blimit)
+ && leapsec_set_table(pt);
+}
+
+/* ------------------------------------------------------------------ */
+void
+leapsec_getsig(
+ leap_signature_t * psig)
+{
+ const leap_table_t * pt;
+
+ pt = leapsec_get_table(FALSE);
+ memcpy(psig, &pt->lsig, sizeof(leap_signature_t));
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_expired(
+ u_int32 when,
+ const time_t * tpiv)
+{
+ return leapsec_is_expired(leapsec_get_table(FALSE), when, tpiv);
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_add_fix(
+ u_int32 ttime,
+ u_int32 etime,
+ int total,
+ const time_t * pivot)
+{
+ time_t tpiv;
+ leap_table_t * pt;
+ vint64 tt64, et64;
+
+ if (pivot == NULL) {
+ time(&tpiv);
+ pivot = &tpiv;
+ }
+
+ et64 = ntpcal_ntp_to_ntp(etime, pivot);
+ tt64 = ntpcal_ntp_to_ntp(ttime, pivot);
+ pt = leapsec_get_table(TRUE);
+
+ if (ucmpv64(&et64, &pt->head.expire) <= 0)
+ return FALSE;
+ if ( ! leapsec_raw(pt, &tt64, total, FALSE))
+ return FALSE;
+
+ pt->lsig.etime = etime;
+ pt->lsig.ttime = ttime;
+ pt->lsig.taiof = total;
+
+ pt->head.expire = et64;
+
+ return leapsec_set_table(pt);
+}
+
+/* ------------------------------------------------------------------ */
+int/*BOOL*/
+leapsec_add_dyn(
+ u_int32 ntpnow,
+ int insert,
+ const time_t * pivot )
+{
+ leap_table_t * pt;
+ vint64 now64;
+
+ pt = leapsec_get_table(TRUE);
+ now64 = ntpcal_ntp_to_ntp(ntpnow, pivot);
+ return leapsec_add(pt, &now64, (insert != 0))
+ && leapsec_set_table(pt);
+}
+
+/* =====================================================================
+ * internal helpers
+ */
+
+/* Reset / init the time window in the leap processor to force reload on
+ * next query.
+ */
+static void
+reset_times(
+ leap_table_t * pt)
+{
+ pt->head.base.D_s.hi = 0;
+ pt->head.base.D_s.lo = 1;
+ pt->head.stime = pt->head.base;
+ pt->head.ttime = pt->head.base;
+ pt->head.when = pt->head.base;
+}
+
+/* [internal] Add raw data to the table, removing old entries on the
+ * fly. This cannot fail currently.
+ */
+static int/*BOOL*/
+add_range(
+ leap_table_t * pt,
+ const leap_info_t * pi)
+{
+ /* If the table is full, make room by throwing out the oldest
+ * entry. But remember the accumulated leap seconds!
+ */
+ if (pt->head.size >= MAX_HIST) {
+ pt->head.size = MAX_HIST - 1;
+ pt->head.total = pt->info[pt->head.size].total;
+ }
+
+ /* make room in lower end and insert item */
+ memmove(pt->info+1, pt->info, pt->head.size*sizeof(*pt->info));
+ pt->info[0] = *pi;
+ pt->head.size++;
+
+ /* invalidate the cached limit data -- we might have news ;-)
+ *
+ * This blocks a spurious transition detection. OTOH, if you add
+ * a value after the last query before a leap transition was
+ * expected to occur, this transition trigger is lost. But we
+ * can probably live with that.
+ */
+ reset_times(pt);
+ return TRUE;
+}
+
+/* [internal] given a reader function, read characters into a buffer
+ * until either EOL or EOF is reached. Makes sure that the buffer is
+ * always NUL terminated, but silently truncates excessive data. The
+ * EOL-marker ('\n') is *not* stored in the buffer.
+ *
+ * Returns the pointer to the buffer, unless EOF was reached when trying
+ * to read the first character of a line.
+ */
+static char *
+get_line(
+ leapsec_reader func,
+ void * farg,
+ char * buff,
+ size_t size)
+{
+ int ch;
+ char *ptr = buff;
+
+ while (EOF != (ch = (*func)(farg)) && '\n' != ch)
+ if (size > 1) {
+ size--;
+ *ptr++ = (char)ch;
+ }
+ if (size)
+ *ptr = '\0';
+ return (ptr == buff && ch == EOF) ? NULL : buff;
+}
+
+/* [internal] skips whitespace characters from a character buffer. */
+static char *
+skipws(
+ const char *ptr)
+{
+ const u_char * src;
+
+ src = (const u_char*)ptr;
+ while (isspace(*src))
+ src++;
+ return (char*)noconst(src);
+}
+
+/* [internal] reload the table limits around the given time stamp. This
+ * is where the real work is done when it comes to table lookup and
+ * evaluation. Some care has been taken to have correct code for dealing
+ * with boundary conditions and empty tables.
+ */
+static void
+reload_limits(
+ leap_table_t * pt,
+ const vint64 * ts)
+{
+ int idx;
+
+ /* Get full time and search the true lower bound. Use a
+ * simple loop here, since the number of entries does
+ * not warrant a binary search. This also works for an empty
+ * table, so there is no shortcut for that case.
+ */
+ for (idx = 0; idx != pt->head.size; idx++)
+ if (ucmpv64(ts, &pt->info[idx].ttime) >= 0)
+ break;
+
+ /* get time limits with proper bound conditions. Note that the
+ * bounds of the table will be observed even if the table is
+ * empty -- no undefined condition must arise from this code.
+ */
+ if (idx >= pt->head.size) {
+ memset(&pt->head.base, 0x00, sizeof(vint64));
+ pt->head.this_tai = pt->head.total;
+ } else {
+ pt->head.base = pt->info[idx].ttime;
+ pt->head.this_tai = pt->info[idx].total;
+ }
+ if (--idx >= 0) {
+ pt->head.next_tai = pt->info[idx].total;
+ pt->head.dynls = pt->info[idx].dynls;
+ pt->head.ttime = pt->info[idx].ttime;
+
+ if (!_electric)
+ pt->head.when = addv64i32(
+ &pt->head.ttime,
+ pt->head.next_tai - pt->head.this_tai);
+ else
+ pt->head.when = pt->head.ttime;
+
+ pt->head.stime = subv64u32(
+ &pt->head.ttime, pt->info[idx].stime);
+
+ } else {
+ memset(&pt->head.ttime, 0xFF, sizeof(vint64));
+ pt->head.stime = pt->head.ttime;
+ pt->head.when = pt->head.ttime;
+ pt->head.next_tai = pt->head.this_tai;
+ pt->head.dynls = 0;
+ }
+}
+
+/* [internal] Take a time stamp and create a leap second frame for
+ * it. This will schedule a leap second for the beginning of the next
+ * month, midnight UTC. The 'insert' argument tells if a leap second is
+ * added (!=0) or removed (==0). We do not handle multiple inserts
+ * (yet?)
+ *
+ * Returns 1 if the insert worked, 0 otherwise. (It's not possible to
+ * insert a leap second into the current history -- only appending
+ * towards the future is allowed!)
+ */
+static int/*BOOL*/
+leapsec_add(
+ leap_table_t* pt ,
+ const vint64 * now64 ,
+ int insert)
+{
+ vint64 ttime, stime;
+ struct calendar fts;
+ leap_info_t li;
+
+ /* Check against the table expiration and the lates available
+ * leap entry. Do not permit inserts, only appends, and only if
+ * the extend the table beyond the expiration!
+ */
+ if ( ucmpv64(now64, &pt->head.expire) < 0
+ || (pt->head.size && ucmpv64(now64, &pt->info[0].ttime) <= 0)) {
+ errno = ERANGE;
+ return FALSE;
+ }
+
+ ntpcal_ntp64_to_date(&fts, now64);
+ /* To guard against dangling leap flags: do not accept leap
+ * second request on the 1st hour of the 1st day of the month.
+ */
+ if (fts.monthday == 1 && fts.hour == 0) {
+ errno = EINVAL;
+ return FALSE;
+ }
+
+ /* Ok, do the remaining calculations */
+ fts.monthday = 1;
+ fts.hour = 0;
+ fts.minute = 0;
+ fts.second = 0;
+ stime = ntpcal_date_to_ntp64(&fts);
+ fts.month++;
+ ttime = ntpcal_date_to_ntp64(&fts);
+
+ li.ttime = ttime;
+ li.stime = ttime.D_s.lo - stime.D_s.lo;
+ li.total = (pt->head.size ? pt->info[0].total : pt->head.total)
+ + (insert ? 1 : -1);
+ li.dynls = 1;
+ return add_range(pt, &li);
+}
+
+/* [internal] Given a time stamp for a leap insertion (the exact begin
+ * of the new leap era), create new leap frame and put it into the
+ * table. This is the work horse for reading a leap file and getting a
+ * leap second update via authenticated network packet.
+ */
+int/*BOOL*/
+leapsec_raw(
+ leap_table_t * pt,
+ const vint64 * ttime,
+ int total,
+ int dynls)
+{
+ vint64 stime;
+ struct calendar fts;
+ leap_info_t li;
+
+ /* Check that we only extend the table. Paranoia rulez! */
+ if (pt->head.size && ucmpv64(ttime, &pt->info[0].ttime) <= 0) {
+ errno = ERANGE;
+ return FALSE;
+ }
+
+ ntpcal_ntp64_to_date(&fts, ttime);
+ /* If this does not match the exact month start, bail out. */
+ if (fts.monthday != 1 || fts.hour || fts.minute || fts.second) {
+ errno = EINVAL;
+ return FALSE;
+ }
+ fts.month--; /* was in range 1..12, no overflow here! */
+ stime = ntpcal_date_to_ntp64(&fts);
+ li.ttime = *ttime;
+ li.stime = ttime->D_s.lo - stime.D_s.lo;
+ li.total = (short)total;
+ li.dynls = (dynls != 0);
+ return add_range(pt, &li);
+}
+
+/* [internal] Do a wrap-around save range inclusion check.
+ * Returns TRUE if x in [lo,hi[ (intervall open on right side) with full
+ * handling of an overflow / wrap-around.
+ */
+static int/*BOOL*/
+betweenu32(
+ u_int32 lo,
+ u_int32 x,
+ u_int32 hi)
+{
+ int rc;
+ if (lo <= hi)
+ rc = (lo <= x) && (x < hi);
+ else
+ rc = (lo <= x) || (x < hi);
+ return rc;
+}
+
+/* -*- that's all folks! -*- */
--- /dev/null
+/*
+ * ntp_leapsec.h - leap second processing for NTPD
+ *
+ * Written by Juergen Perlinger (perlinger@ntp.org) for the NTP project.
+ * The contents of 'html/copyright.html' apply.
+ * ----------------------------------------------------------------------
+ * This is an attempt to get the leap second handling into a dedicated
+ * module to make the somewhat convoluted logic testable.
+ */
+
+#ifndef NTP_LEAPSEC_H
+#define NTP_LEAPSEC_H
+
+/* these should probably go to libntp... */
+extern vint64 strtouv64(const char * src, char ** endp, int base);
+extern int icmpv64(const vint64 * lhs, const vint64 * rhs);
+extern int ucmpv64(const vint64 * lhs, const vint64 * rhs);
+
+/* function pointer types. Note that 'fprintf' and 'getc' can be casted
+ * to the dumper resp. reader type, provided the auxiliary argument is a
+ * valid FILE pointer in hat case.
+ */
+typedef void (*leapsec_dumper)(void*, const char *fmt, ...);
+typedef int (*leapsec_reader)(void*);
+
+struct leap_table;
+typedef struct leap_table leap_table_t;
+
+/* Set/get electric mode
+ * Electric mode is defined as the operation mode where the system clock
+ * automagically manages the leap second, so we don't have to care about
+ * stepping the clock. (This should be the case with most systems,
+ * including the current implementation of the Win32 timekeeping.)
+ *
+ * The consequence of electric mode is that we do not 'see' the leap
+ * second, and no client actions are needed when crossing the leap era
+ * boundary. In manual (aka non-electric) mode the clock will simply
+ * step forward untill *we* (that is, this module) tells the client app
+ * to step at the right time. This needs a slightly different type of
+ * processing, so switching between those two modes should not be done
+ * close to a leap second. The transition might be lost in that case.
+ *
+ * OTOH, this is a system characteristic, so it's expected to be set
+ * properly somewhere after system start and retain the value.
+ *
+ * Simply querying the state or setting it to the same value as before
+ * does not have any unwanted side effects. You can query by giving a
+ * negative value for the switch.
+ */
+extern int/*BOOL*/ leapsec_electric(int/*BOOL*/ on);
+
+
+/* Query result for a leap second schedule
+ * 'when' tells the transition point in full time scale, but only if
+ * 'tai_diff' is not zero.
+ * 'dist' is the distance to the transition, in clock seconds.
+ * Only valid if 'tai_diff' not zero. To get the true (elapsed
+ * time) difference, add 'tai_diff' to that value.
+ * 'tai_offs' is the CURRENT distance from clock (UTC) to TAI.
+ * 'tai_diff' is the change in TAI offset after the next leap
+ * transition. Zero if nothing is pending.
+ * 'warped' is set only once, when the the leap second occurred between
+ * two queries.
+ * 'proximity' is a proximity warning. See definitions below. This might
+ * be more useful than an absolute difference to the leap second.
+ * 'dynamic' != 0 if entry was requested by clock/peer
+ */
+struct leap_result {
+ vint64 when;
+ u_int32 dist;
+ short tai_offs;
+ short tai_diff;
+ short warped;
+ u_short proximity;
+ short dynamic;
+};
+typedef struct leap_result leap_result_t;
+
+struct leap_signature {
+ u_int32 etime; /* expiration time */
+ u_int32 ttime; /* transition time */
+ short taiof; /* total offset to TAI */
+};
+typedef struct leap_signature leap_signature_t;
+
+
+#define LSPROX_NOWARN 0 /* clear radar screen */
+#define LSPROX_SCHEDULE 1 /* less than 1 month to target*/
+#define LSPROX_ANNOUNCE 2 /* less than 1 day to target */
+#define LSPROX_ALERT 3 /* less than 10 sec to target */
+
+/* Get the current or alternate table ponter. Getting the alternate
+ * pointer will automatically copy the primary table, so it can be
+ * subsequently modified.
+ */
+extern leap_table_t *leapsec_get_table(int alternate);
+/* Set the current leap table. Accepts only return values from
+ * 'leapsec_get_table()', so it's hard to do something wrong. Returns
+ * TRUE if the table was exchanged.
+ */
+extern int/*BOOL*/ leapsec_set_table(leap_table_t*);
+
+/* Clear all leap second data. Use it for init & cleanup */
+extern void leapsec_clear(leap_table_t*);
+
+/* Check if a table is expired at a given point in time. 'when' is
+ * subject to NTP era unfolding.
+ */
+extern int/*BOOL*/ leapsec_is_expired(leap_table_t*, u_int32 when,
+ const time_t * pivot);
+
+/* Load a leap second file. If 'blimit' is set, do not store (but
+ * register with their TAI offset) leap entries before the build date.
+ */
+extern int/*BOOL*/ leapsec_load(leap_table_t*, leapsec_reader,
+ void*, int blimit);
+
+
+/* Dump the current leap table in readable format, using the provided
+ * dump formatter function.
+ */
+extern void leapsec_dump(const leap_table_t*, leapsec_dumper func, void *farg);
+
+/* Glue to integrate this easier into existing code */
+extern int/*BOOL*/ leapsec_load_file(FILE*, int blimit);
+extern void leapsec_getsig(leap_signature_t * psig);
+extern int/*BOOL*/ leapsec_expired(u_int32 when, const time_t * pivot);
+
+/* Reset the current leap frame */
+extern void leapsec_reset_frame(void);
+
+/* Given a transition time, the TAI offset valid after that and an
+ * expiration time, try to establish a system leap transition. Only
+ * works if the existing table is extended. On success, updates the
+ * signature data.
+ */
+extern int/*BOOL*/ leapsec_add_fix(u_int32 ttime, u_int32 etime, int total,
+ const time_t * pivot);
+
+/* Take a time stamp and create a leap second frame for it. This will
+ * schedule a leap second for the beginning of the next month, midnight
+ * UTC. The 'insert' argument tells if a leap second is added (!=0) or
+ * removed (==0). We do not handle multiple inserts (yet?)
+ *
+ * Returns 1 if the insert worked, 0 otherwise. (It's not possible to
+ * insert a leap second into the current history -- only appending
+ * towards the future is allowed!)
+ *
+ * 'ntp_now' is subject to era unfolding. The entry is marked
+ * dynamic. The leap signature is NOT updated.
+ */
+extern int/*BOOL*/ leapsec_add_dyn(u_int32 ntp_now, int insert,
+ const time_t * pivot);
+
+/* Take a time stamp and get the associated leap information. The time
+ * stamp is subject to era unfolding around the pivot or the current
+ * system time if pivot is NULL. Sets the information in '*qr' and
+ * returns TRUE if a leap second era boundary was crossed between the
+ * last and the current query. In that case, qr.warped contains the
+ * required clock stepping. (Which is zero in electric mode...)
+ */
+extern int/*BOOL*/ leapsec_query(leap_result_t *qr, u_int32 ntpts,
+ const time_t * pivot);
+
+#endif /* !defined(NTP_LEAPSEC_H) */
#include "ntp_unixtime.h"
#include "ntp_control.h"
#include "ntp_string.h"
+#include "ntp_leapsec.h"
#include <stdio.h>
#ifdef HAVE_LIBSCF_H
double sys_jitter; /* system jitter */
u_long sys_epoch; /* last clock update time */
static double sys_clockhop; /* clockhop threshold */
-int leap_tai; /* TAI at next next leap */
-u_long leap_sec; /* next scheduled leap from file */
-u_long leap_peers; /* next scheduled leap from peers */
-u_long leap_expire; /* leap information expiration */
-static int leap_vote; /* leap consensus */
+static int leap_vote_ins; /* leap consensus for insert */
+static int leap_vote_del; /* leap consensus for delete */
keyid_t sys_private; /* private value for session seed */
int sys_manycastserver; /* respond to manycast client pkts */
int ntp_mode7; /* respond to ntpdc (mode7) */
}
/*
- * If the leapseconds values are from file or network
- * and the leap is in the future, schedule a leap at the
- * given epoch. Otherwise, if the number of survivor
- * leap bits is greater than half the number of
- * survivors, schedule a leap for the end of the current
- * month.
+ * If there is no leap second pending and the number of
+ * survivor leap bits is greater than half the number of
+ * survivors, try to schedule a leap for the end of the
+ * current month. (This only works if no leap second for
+ * that range is in the table, so doing this more than
+ * once is mostly harmless.)
*/
- get_systime(&now);
- if (leap_sec > 0) {
- if (leap_sec > now.l_ui) {
- sys_tai = leap_tai - 1;
- if (leapsec == 0)
- report_event(EVNT_ARMED, NULL,
- NULL);
- leapsec = leap_sec - now.l_ui;
- } else {
- sys_tai = leap_tai;
+ if (leapsec == LSPROX_NOWARN) {
+ if (leap_vote_ins > leap_vote_del
+ && leap_vote_ins > sys_survivors / 2) {
+ get_systime(&now);
+ leapsec_add_dyn(now.l_ui, TRUE, NULL);
}
- break;
-
- } else if (leap_vote > sys_survivors / 2) {
- leap_peers = now.l_ui + leap_month(now.l_ui);
- if (leap_peers > now.l_ui) {
- if (leapsec == 0)
- report_event(PEVNT_ARMED, peer,
- NULL);
- leapsec = leap_peers - now.l_ui;
+ if (leap_vote_del > leap_vote_ins
+ && leap_vote_del > sys_survivors / 2) {
+ get_systime(&now);
+ leapsec_add_dyn(now.l_ui, FALSE, NULL);
}
- } else if (leapsec > 0) {
- report_event(EVNT_DISARMED, NULL, NULL);
- leapsec = 0;
- sys_leap = LEAP_NOWARNING;
}
break;
*/
e = 1e9;
speer = 0;
- leap_vote = 0;
+ leap_vote_ins = 0;
+ leap_vote_del = 0;
for (i = 0; i < nlist; i++) {
peer = peers[i].peer;
peer->unreach = 0;
sys_survivors++;
if (peer->leap == LEAP_ADDSECOND) {
if (peer->flags & FLAG_REFCLOCK)
- leap_vote = nlist;
- else
- leap_vote++;
+ leap_vote_ins = nlist;
+ else if (leap_vote_ins < nlist)
+ leap_vote_ins++;
+ }
+ if (peer->leap == LEAP_DELSECOND) {
+ if (peer->flags & FLAG_REFCLOCK)
+ leap_vote_del = nlist;
+ else if (leap_vote_del < nlist)
+ leap_vote_del++;
}
if (peer->flags & FLAG_PREFER)
sys_prefer = peer;
#include "ntp_machine.h"
#include "ntpd.h"
#include "ntp_stdlib.h"
+#include "ntp_leapsec.h"
#if defined(HAVE_IO_COMPLETION_PORT)
# include "ntp_iocompletionport.h"
#define TC_ERR (-1)
#endif
+static void check_leapsec(u_int32, const time_t*);
+
/*
* These routines provide support for the event timer. The timer is
* implemented by an interrupt routine which sets a flag once every
struct peer * p;
struct peer * next_peer;
l_fp now;
+ time_t tnow;
static int leap_warn_log = FALSE;
/*
sys_rootdisp = 0;
}
+ get_systime(&now);
+ time(&tnow);
+
/*
- * Leapseconds. If a leap is pending, decrement the time
- * remaining. If less than one day remains, set the leap bits.
- * When no time remains, clear the leap bits and increment the
- * TAI. If kernel suppport is not available, do the leap
- * crudely. Note a leap cannot be pending unless the clock is
- * set.
+ * Leapseconds. Get time and defer to worker if either something
+ * is imminent or every 8th second.
*/
- if (leapsec > 0) {
- leapsec--;
- if (leapsec == 0) {
- sys_leap = LEAP_NOWARNING;
- sys_tai = leap_tai;
-#ifndef SYS_WINNT /* WinNT port has its own leap second handling */
-# ifdef KERNEL_PLL
- if (!(pll_control && kern_enable))
-# endif /* KERNEL_PLL */
- {
- step_systime(-1.0);
- msyslog(LOG_NOTICE, "Inserting positive leap second.");
- }
-#endif /* SYS_WINNT */
- report_event(EVNT_LEAP, NULL, NULL);
- } else {
- if (leapsec < DAY)
- sys_leap = LEAP_ADDSECOND;
- if (leap_tai > 0)
- sys_tai = leap_tai - 1;
- }
+ if (leapsec > LSPROX_NOWARN || 0 == (now.l_ui & 7)) {
+ check_leapsec(now.l_ui, &tnow);
}
/*
if (stats_timer <= current_time) {
stats_timer += HOUR;
write_stats();
- if (sys_tai != 0) {
- get_systime(&now);
- if (now.l_ui > leap_expire) {
- report_event(EVNT_LEAPVAL, NULL, NULL);
- if (leap_warn_log == FALSE) {
- msyslog(LOG_WARNING,
- "leapseconds data file has expired.");
- leap_warn_log = TRUE;
- }
- /* If a new file was installed between
- * the previous 24 hour check and the
- * expiration of this one, we'll squawk
- * once. Better than checking for a
- * new file every hour...
- */
- check_leap_file();
- } else
- leap_warn_log = FALSE;
- }
+ if (sys_tai != 0 && leapsec_expired(now.l_ui, &tnow)) {
+ report_event(EVNT_LEAPVAL, NULL, NULL);
+ if (leap_warn_log == FALSE) {
+ msyslog(LOG_WARNING,
+ "leapseconds data file has expired.");
+ leap_warn_log = TRUE;
+ }
+ /* If a new file was installed between
+ * the previous 24 hour check and the
+ * expiration of this one, we'll squawk
+ * once. Better than checking for a
+ * new file every hour...
+ */
+ check_leap_file();
+ } else
+ leap_warn_log = FALSE;
}
}
timer_timereset = current_time;
}
+static void
+check_leapsec(
+ u_int32 now ,
+ const time_t * tpiv)
+{
+ leap_result_t lsdata;
+ u_int32 lsprox;
+
+#if defined(SYS_WINNT)
+ leapsec_electric(1); /* WinNT port has its own leap second handling */
+#elif defined(KERNEL_PLL)
+ leapsec_electric(pll_control && kern_enable);
+#else
+ leapsec_electric(0);
+#endif
+
+ if (sys_leap == LEAP_NOTINSYNC) {
+ lsprox = LSPROX_NOWARN;
+ leapsec_reset_frame();
+ memset(&lsdata, 0, sizeof(lsdata));
+
+ } else if (leapsec_query(&lsdata, now, tpiv)) {
+ /* Full hit. Eventually step the clock, but always
+ * announce the leap event has happened.
+ */
+ if (lsdata.warped < 0) {
+ step_systime(lsdata.warped);
+ msyslog(LOG_NOTICE, "Inserting positive leap second.");
+ } else if (lsdata.warped > 0) {
+ step_systime(lsdata.warped);
+ msyslog(LOG_NOTICE, "Inserting negative leap second.");
+ }
+ report_event(EVNT_LEAP, NULL, NULL);
+ lsprox = LSPROX_NOWARN;
+ leapsec = LSPROX_NOWARN;
+ sys_tai = lsdata.tai_offs;
+ } else {
+ lsprox = lsdata.proximity;
+ sys_tai = lsdata.tai_offs;
+ }
+
+ /* We guard against panic alarming during the red alert phase.
+ * Strange and evil things might happen if we go from stone cold
+ * to piping hot in one step. If things are already that wobbly,
+ * we let the normal clock correction take over, even if a jump
+ * is involved.
+ */
+ if ( (leapsec > 0 || lsprox < LSPROX_ALERT)
+ && leapsec < lsprox ) {
+ if (lsprox >= LSPROX_SCHEDULE) {
+ if (lsdata.dynamic)
+ report_event(PEVNT_ARMED, sys_peer, NULL);
+ else
+ report_event(EVNT_ARMED, NULL, NULL);
+ }
+ leapsec = lsprox;
+ }
+ if (leapsec > lsprox) {
+ if (lsprox < LSPROX_SCHEDULE) {
+ report_event(EVNT_DISARMED, NULL, NULL);
+ }
+ leapsec = lsprox;
+ }
+
+ if (sys_leap != LEAP_NOTINSYNC) {
+ if (leapsec < LSPROX_ANNOUNCE)
+ lsdata.tai_diff = 0;
+ if (lsdata.tai_diff > 0)
+ sys_leap = LEAP_ADDSECOND;
+ else if (lsdata.tai_diff < 0)
+ sys_leap = LEAP_DELSECOND;
+ else
+ sys_leap = LEAP_NOWARNING;
+ }
+}
#include "ntp_stdlib.h"
#include "ntp_assert.h"
#include "ntp_calendar.h"
+#include "ntp_leapsec.h"
#include "lib_strbuf.h"
#include <stdio.h>
/*
* Function prototypes
*/
-static int leap_file(FILE *);
static void record_sys_stats(void);
void ntpd_time_stepped(void);
msyslog(LOG_ERR,
"leapseconds: stat(%s) failed: %m",
leapseconds_file);
- } else if (leap_file(fp) < 0) {
+ } else if (!leapsec_load_file(fp, TRUE)) {
msyslog(LOG_ERR,
- "format error leapseconds file %s",
- leapseconds_file);
+ "format error leapseconds file %s",
+ leapseconds_file);
} else {
+ leap_signature_t lsig;
+
+ leapsec_getsig(&lsig);
get_systime(&now);
mprintf_event(EVNT_TAI, NULL,
"%d leap %s %s %s",
- leap_tai,
- fstostr(leap_sec),
- (now.l_ui > leap_expire)
+ lsig.taiof,
+ fstostr(lsig.ttime),
+ leapsec_expired(now.l_ui, NULL)
? "expired"
: "expires",
- fstostr(leap_expire));
+ fstostr(lsig.etime));
}
fclose(fp);
break;
FILE *fp;
struct stat *sp1 = &leapseconds_file_sb1;
struct stat *sp2 = &leapseconds_file_sb2;
+ leap_table_t * plt;
if (leapseconds_file) {
if ((fp = fopen(leapseconds_file, "r")) == NULL) {
if ( (sp1->st_mtime != sp2->st_mtime)
|| (sp1->st_ctime != sp2->st_ctime)) {
leapseconds_file_sb1 = leapseconds_file_sb2;
- if (leap_file(fp) < 0) {
+ plt = leapsec_get_table(TRUE);
+ if (!leapsec_load(plt, (leapsec_reader)getc, fp, TRUE)) {
msyslog(LOG_ERR,
"format error leapseconds file %s",
leapseconds_file);
+ } else {
+ leapsec_set_table(plt);
}
}
fclose(fp);
}
-/*
- * leap_file - read leapseconds file
- *
- * Read the ERTS leapsecond file in NIST text format and extract the
- * NTP seconds of the latest leap and TAI offset after the leap.
- */
-static int
-leap_file(
- FILE *fp /* file handle */
- )
-{
- char buf[NTP_MAXSTRLEN]; /* file line buffer */
- u_long leap; /* NTP time at leap */
- u_long expire; /* NTP time when file expires */
- int offset; /* TAI offset at leap (s) */
- int i;
-
- /*
- * Read and parse the leapseconds file. Empty lines and comments
- * are ignored. A line beginning with #@ contains the file
- * expiration time in NTP seconds. Other lines begin with two
- * integers followed by junk or comments. The first integer is
- * the NTP seconds at the leap, the second is the TAI offset
- * after the leap.
- */
- offset = 0;
- leap = 0;
- expire = 0;
- i = 10;
- while (fgets(buf, NTP_MAXSTRLEN - 1, fp) != NULL) {
- if (strlen(buf) < 1)
- continue;
-
- if (buf[0] == '#') {
- if (strlen(buf) < 3)
- continue;
-
- /*
- * Note the '@' flag was used only in the 2006
- * table; previious to that the flag was '$'.
- */
- if (buf[1] == '@' || buf[1] == '$') {
- if (sscanf(&buf[2], "%lu", &expire) !=
- 1)
- return (-1);
-
- continue;
- }
- }
- if (sscanf(buf, "%lu %d", &leap, &offset) == 2) {
-
- /*
- * Valid offsets must increase by one for each
- * leap.
- */
- if (i++ != offset)
- return (-1);
- }
- }
-
- /*
- * There must be at least one leap.
- */
- if (i == 10)
- return (-1);
-
- leap_tai = offset;
- leap_sec = leap;
- leap_expire = expire;
- return (0);
-}
-
-
-/*
- * leap_month - returns seconds until the end of the month.
- */
-u_long
-leap_month(
- u_long sec /* current NTP second */
- )
-{
- int leap;
- int32 year, month;
- u_int32 ndays;
- ntpcal_split tmp;
- vint64 tvl;
-
- /* --*-- expand time and split to days */
- tvl = ntpcal_ntp_to_ntp(sec, NULL);
- tmp = ntpcal_daysplit(&tvl);
- /* --*-- split to years and days in year */
- tmp = ntpcal_split_eradays(tmp.hi + DAY_NTP_STARTS - 1, &leap);
- year = tmp.hi;
- /* --*-- split days of year to month */
- tmp = ntpcal_split_yeardays(tmp.lo, leap);
- month = tmp.hi;
- /* --*-- get nominal start of next month */
- ndays = ntpcal_edate_to_eradays(year, month+1, 0) + 1 - DAY_NTP_STARTS;
-
- return (u_int32)(ndays*SECSPERDAY - sec);
-}
-
-
/*
* getauthkeys - read the authentication keys from the specified file
*/
time_t ntp_stamp
)
{
- char * buf;
- struct tm * tm;
- time_t unix_stamp;
+ char * buf;
+ struct calendar tm;
LIB_GETBUF(buf);
- unix_stamp = ntp_stamp - JAN_1970;
- tm = gmtime(&unix_stamp);
- if (NULL == tm)
- msnprintf(buf, LIB_BUFLENGTH, "gmtime(%ld): %m",
- (long)unix_stamp);
+ if (ntpcal_ntp_to_date(&tm, (u_int32)ntp_stamp, NULL) < 0)
+ snprintf(buf, LIB_BUFLENGTH, "ntpcal_ntp_to_date: %ld: range error",
+ (long)ntp_stamp);
else
snprintf(buf, LIB_BUFLENGTH, "%04d%02d%02d%02d%02d",
- tm->tm_year + 1900, tm->tm_mon + 1,
- tm->tm_mday, tm->tm_hour, tm->tm_min);
-
+ tm.year, tm.month, tm.monthday,
+ tm.hour, tm.minute);
return buf;
}
+NULL =
SUBDIRS =
if GTEST_AVAILABLE
-SUBDIRS += libntp
+SUBDIRS += libntp \
+ ntpd \
+ $(NULL)
endif
--- /dev/null
+NULL =
+BUILT_SOURCES =
+CLEANFILES =
+
+check_PROGRAMS = tests
+
+LDADD = \
+ $(top_builddir)/libntp/libntp.a \
+ $(LDADD_LIBNTP) \
+ $(PTHREAD_LIBS) \
+ $(LDADD_NTP) \
+ $(GTEST_LDFLAGS) \
+ $(GTEST_LIBS) \
+ $(NULL)
+
+AM_CFLAGS = $(CFLAGS_NTP)
+AM_CXXFLAGS = $(GTEST_CXXFLAGS)
+
+AM_CPPFLAGS = $(NTP_INCS)
+AM_CPPFLAGS += -I$(top_srcdir)/sntp
+AM_CPPFLAGS += -I$(top_srcdir)/ntpd
+AM_CPPFLAGS += $(GTEST_CPPFLAGS)
+AM_CPPFLAGS += $(CPPFLAGS_NTP)
+
+AM_LDFLAGS = $(LDFLAGS_NTP)
+
+tests_SOURCES = $(top_srcdir)/sntp/tests_main.cpp \
+ ntpdtest.cpp \
+ $(top_srcdir)/ntpd/ntp_leapsec.c \
+ leapsec.cpp \
+ $(NULL)
+
+noinst_HEADERS = ntpdtest.h \
+ $(NULL)
+
+TESTS =
+
+if !NTP_CROSSCOMPILE
+TESTS += tests
+endif
+
+## check-libntp.mf - automake fragment
+## slightly adapted for deeper directory
+
+BUILT_SOURCES += check-libntp
+CLEANFILES += check-libntp
+
+check-libntp: ../../libntp/libntp.a
+ @echo stamp > $@
+
+../../libntp/libntp.a:
+ cd ../../libntp && $(MAKE) $(AM_MAKEFLAGS) libntp.a
+
+include $(top_srcdir)/depsver.mf
+include $(top_srcdir)/includes.mf
--- /dev/null
+#include "ntpdtest.h"
+
+extern "C" {
+#include "ntp.h"
+#include "ntp_calendar.h"
+#include "ntp_leapsec.h"
+}
+
+#include <string>
+#include <sstream>
+
+static const char leap1 [] =
+ "#\n"
+ "#@ 3610569600\n"
+ "#\n"
+ "2272060800 10 # 1 Jan 1972\n"
+ "2287785600 11 # 1 Jul 1972\n"
+ "2303683200 12 # 1 Jan 1973\n"
+ "2335219200 13 # 1 Jan 1974\n"
+ "2366755200 14 # 1 Jan 1975\n"
+ "2398291200 15 # 1 Jan 1976\n"
+ "2429913600 16 # 1 Jan 1977\n"
+ "2461449600 17 # 1 Jan 1978\n"
+ "2492985600 18 # 1 Jan 1979\n"
+ "2524521600 19 # 1 Jan 1980\n"
+ " \t \n"
+ "2571782400 20 # 1 Jul 1981\n"
+ "2603318400 21 # 1 Jul 1982\n"
+ "2634854400 22 # 1 Jul 1983\n"
+ "2698012800 23 # 1 Jul 1985\n"
+ "2776982400 24 # 1 Jan 1988\n"
+ "2840140800 25 # 1 Jan 1990\n"
+ "2871676800 26 # 1 Jan 1991\n"
+ "2918937600 27 # 1 Jul 1992\n"
+ "2950473600 28 # 1 Jul 1993\n"
+ "2982009600 29 # 1 Jul 1994\n"
+ "3029443200 30 # 1 Jan 1996\n"
+ "3076704000 31 # 1 Jul 1997\n"
+ "3124137600 32 # 1 Jan 1999\n"
+ "3345062400 33 # 1 Jan 2006\n"
+ "3439756800 34 # 1 Jan 2009\n"
+ "3550089600 35 # 1 Jul 2012\n"
+ "#\n"
+ "#h dc2e6b0b 5aade95d a0587abd 4e0dacb4 e4d5049e\n"
+ "#\n";
+
+static const char leap2 [] =
+ "#\n"
+ "#@ 2950473700\n"
+ "#\n"
+ "2272060800 10 # 1 Jan 1972\n"
+ "2287785600 11 # 1 Jul 1972\n"
+ "2303683200 12 # 1 Jan 1973\n"
+ "2335219200 13 # 1 Jan 1974\n"
+ "2366755200 14 # 1 Jan 1975\n"
+ "2398291200 15 # 1 Jan 1976\n"
+ "2429913600 16 # 1 Jan 1977\n"
+ "2461449600 17 # 1 Jan 1978\n"
+ "2492985600 18 # 1 Jan 1979\n"
+ "2524521600 19 # 1 Jan 1980\n"
+ "2571782400 20 # 1 Jul 1981\n"
+ "2603318400 21 # 1 Jul 1982\n"
+ "2634854400 22 # 1 Jul 1983\n"
+ "2698012800 23 # 1 Jul 1985\n"
+ "2776982400 24 # 1 Jan 1988\n"
+ "2840140800 25 # 1 Jan 1990\n"
+ "2871676800 26 # 1 Jan 1991\n"
+ "2918937600 27 # 1 Jul 1992\n"
+ "2950473600 28 # 1 Jul 1993\n"
+ "#\n";
+
+static u_int32 lsec2009 = 3439756800u; // 1 Jan 2009, 00:00:00 utc
+static u_int32 lsec2012 = 3550089600u; // 1 Jul 2012, 00:00:00 utc
+
+int stringreader(void* farg)
+{
+ const char ** cpp = (const char**)farg;
+ if (**cpp)
+ return *(*cpp)++;
+ else
+ return EOF;
+}
+
+static int/*BOOL*/
+setup_load_table(
+ const char * cp,
+ int blim=FALSE)
+{
+ int rc;
+ leap_table_t * pt = leapsec_get_table(0);
+ rc = (pt != NULL) && leapsec_load(pt, stringreader, &cp, blim);
+ rc = rc && leapsec_set_table(pt);
+ return rc;
+}
+
+static int/*BOOL*/
+setup_clear_table()
+{
+ int rc;
+ leap_table_t * pt = leapsec_get_table(0);
+ if (pt)
+ leapsec_clear(pt);
+ rc = leapsec_set_table(pt);
+ return rc;
+}
+
+
+class leapsecTest : public ntpdtest {
+protected:
+ virtual void SetUp();
+ virtual void TearDown();
+
+ std::string CalendarToString(const calendar &cal) {
+ std::ostringstream ss;
+ ss << cal.year << "-" << (u_int)cal.month << "-" << (u_int)cal.monthday
+ << " (" << cal.yearday << ") " << (u_int)cal.hour << ":"
+ << (u_int)cal.minute << ":" << (u_int)cal.second;
+ return ss.str();
+ }
+
+ ::testing::AssertionResult IsEqual(const calendar &expected, const calendar &actual) {
+ if (expected.year == actual.year &&
+ (expected.yearday == actual.yearday ||
+ (expected.month == actual.month &&
+ expected.monthday == actual.monthday)) &&
+ expected.hour == actual.hour &&
+ expected.minute == actual.minute &&
+ expected.second == actual.second) {
+ return ::testing::AssertionSuccess();
+ } else {
+ return ::testing::AssertionFailure()
+ << "expected: " << CalendarToString(expected) << " but was "
+ << CalendarToString(actual);
+ }
+ }
+ ::testing::AssertionResult IsEqual(const vint64 &expected, const vint64 &actual) {
+ if (0 == memcmp(&expected, &actual, sizeof(vint64))) {
+ return ::testing::AssertionSuccess();
+ } else {
+ return ::testing::AssertionFailure()
+ << "expected: "
+ << std::hex << expected.D_s.hi << '.'
+ << std::hex << expected.D_s.lo
+ << " but was "
+ << std::hex << actual.D_s.hi << '.'
+ << std::hex << actual.D_s.lo;
+ }
+ }
+};
+
+void leapsecTest::SetUp()
+{
+ ntpcal_set_timefunc(timefunc);
+ settime(1970, 1, 1, 0, 0, 0);
+ leapsec_electric(1);
+}
+
+void leapsecTest::TearDown()
+{
+ ntpcal_set_timefunc(NULL);
+}
+
+TEST_F(leapsecTest, ParseVUI64) {
+ vint64 act, exp;
+ const char *sp;
+ char *ep;
+
+ sp = "1234x";
+ exp.D_s.hi = 0;
+ exp.D_s.lo = 1234;
+ act = strtouv64(sp, &ep, 0);
+ EXPECT_TRUE(IsEqual(exp, act));
+ EXPECT_EQ(*ep, 'x');
+
+ sp = "-1234x";
+ exp.D_s.hi = ~0;
+ exp.D_s.lo = -1234;
+ act = strtouv64(sp, &ep, 0);
+ EXPECT_TRUE(IsEqual(exp, act));
+ EXPECT_EQ(*ep, 'x');
+
+ sp = "0123456789AbCdEf";
+ exp.D_s.hi = 0x01234567;
+ exp.D_s.lo = 0x89ABCDEF;
+ act = strtouv64(sp, &ep, 16);
+ EXPECT_TRUE(IsEqual(exp, act));
+ EXPECT_EQ(*ep, '\0');
+}
+
+TEST_F(leapsecTest, tableSelect) {
+ leap_table_t *pt1, *pt2, *pt3, *pt4;
+
+ pt1 = leapsec_get_table(0);
+ pt2 = leapsec_get_table(0);
+ EXPECT_EQ(pt1, pt2);
+
+ pt1 = leapsec_get_table(1);
+ pt2 = leapsec_get_table(1);
+ EXPECT_EQ(pt1, pt2);
+
+ pt1 = leapsec_get_table(1);
+ pt2 = leapsec_get_table(0);
+ EXPECT_NE(pt1, pt2);
+
+ pt1 = leapsec_get_table(0);
+ pt2 = leapsec_get_table(1);
+ EXPECT_NE(pt1, pt2);
+
+ leapsec_set_table(pt1);
+ pt2 = leapsec_get_table(0);
+ pt3 = leapsec_get_table(1);
+ EXPECT_EQ(pt1, pt2);
+ EXPECT_NE(pt2, pt3);
+
+ pt1 = pt3;
+ leapsec_set_table(pt1);
+ pt2 = leapsec_get_table(0);
+ pt3 = leapsec_get_table(1);
+ EXPECT_EQ(pt1, pt2);
+ EXPECT_NE(pt2, pt3);
+}
+
+TEST_F(leapsecTest, loadFileOk) {
+ const char *cp = leap1;
+ int rc;
+ leap_table_t * pt = leapsec_get_table(0);
+
+ rc = leapsec_load(pt, stringreader, &cp, FALSE)
+ && leapsec_set_table(pt);
+ EXPECT_EQ(1, rc);
+ rc = leapsec_expired(3439756800, NULL);
+ EXPECT_EQ(0, rc);
+ //leapsec_dump(pt, (leapsec_dumper)fprintf, stdout);
+}
+
+TEST_F(leapsecTest, loadFileExpire) {
+ const char *cp = leap1;
+ int rc;
+ leap_table_t * pt = leapsec_get_table(0);
+
+ rc = leapsec_load(pt, stringreader, &cp, FALSE)
+ && leapsec_set_table(pt);
+ EXPECT_EQ(1, rc);
+ rc = leapsec_expired(3610569601, NULL);
+ EXPECT_EQ(1, rc);
+}
+
+/* test handling of the leap second at 2009.01.01 */
+TEST_F(leapsecTest, ls2009faraway) {
+ int rc;
+ leap_result_t qr;
+
+ rc = setup_load_table(leap1);
+ EXPECT_EQ(1, rc);
+
+ // test 60 days before leap. Nothing scheduled or indicated.
+ rc = leapsec_query(&qr, lsec2009 - 60*SECSPERDAY, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(33, qr.tai_offs);
+ EXPECT_EQ(0, qr.tai_diff);
+ EXPECT_EQ(0, qr.dist);
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+}
+
+/* test handling of the leap second at 2009.01.01 */
+TEST_F(leapsecTest, ls2009weekaway) {
+ int rc;
+ leap_result_t qr;
+
+ rc = setup_load_table(leap1);
+ EXPECT_EQ(1, rc);
+
+ // test 7 days before leap. Leap scheduled, but not yet indicated.
+ rc = leapsec_query(&qr, lsec2009 - 7*SECSPERDAY, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(33, qr.tai_offs);
+ EXPECT_EQ(1, qr.tai_diff);
+ EXPECT_EQ(7*SECSPERDAY, qr.dist);
+ EXPECT_EQ(LSPROX_SCHEDULE, qr.proximity);
+}
+
+/* test handling of the leap second at 2009.01.01 */
+TEST_F(leapsecTest, ls2009houraway) {
+ int rc;
+ leap_result_t qr;
+
+ rc = setup_load_table(leap1);
+ EXPECT_EQ(1, rc);
+
+ // test 1 hour before leap. 61 true seconds to go.
+ rc = leapsec_query(&qr, lsec2009 - SECSPERHR, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(33, qr.tai_offs);
+ EXPECT_EQ(1, qr.tai_diff);
+ EXPECT_EQ(SECSPERHR, qr.dist);
+ EXPECT_EQ(LSPROX_ANNOUNCE, qr.proximity);
+}
+
+/* test handling of the leap second at 2009.01.01 */
+TEST_F(leapsecTest, ls2009secaway) {
+ int rc;
+ leap_result_t qr;
+
+ rc = setup_load_table(leap1);
+ EXPECT_EQ(1, rc);
+
+ // test 1 second before leap (last boundary...) 2 true seconds to go.
+ rc = leapsec_query(&qr, lsec2009 - 1, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(33, qr.tai_offs);
+ EXPECT_EQ(1, qr.tai_diff);
+ EXPECT_EQ(1, qr.dist);
+ EXPECT_EQ(LSPROX_ALERT, qr.proximity);
+}
+
+/* test handling of the leap second at 2009.01.01 */
+TEST_F(leapsecTest, ls2009onspot) {
+ int rc;
+ leap_result_t qr;
+
+ rc = setup_load_table(leap1);
+ EXPECT_EQ(1, rc);
+
+ // test on-spot: treat leap second as already gone.
+ rc = leapsec_query(&qr, lsec2009, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(34, qr.tai_offs);
+ EXPECT_EQ(0, qr.tai_diff);
+ EXPECT_EQ(0, qr.dist);
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+}
+
+/* test handling of the leap second at 2009.01.01 */
+TEST_F(leapsecTest, ls2009sequenceElectric) {
+ int rc;
+ leap_result_t qr;
+
+ rc = setup_load_table(leap1);
+ EXPECT_EQ(1, rc);
+
+ rc = leapsec_query(&qr, lsec2009 - 60*SECSPERDAY, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2009 - 7*SECSPERDAY, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_SCHEDULE, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2009 - SECSPERHR, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_ANNOUNCE, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2009 - 1, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_ALERT, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2009, NULL);
+ EXPECT_EQ(TRUE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+
+ // second call, same time frame: no trigger!
+ rc = leapsec_query(&qr, lsec2009, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+}
+
+/* test handling of the leap second at 2009.01.01 */
+TEST_F(leapsecTest, ls2009nodata) {
+ int rc;
+ leap_result_t qr;
+
+ rc = setup_clear_table();
+ EXPECT_EQ(1, rc);
+
+ // test on-spot with empty table
+ rc = leapsec_query(&qr, lsec2009, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.tai_offs);
+ EXPECT_EQ(0, qr.tai_diff);
+ EXPECT_EQ(0, qr.dist);
+}
+
+/* test handling of the leap second at 2009.01.01 */
+TEST_F(leapsecTest, ls2009limdata) {
+ int rc;
+ leap_result_t qr;
+
+ rc = setup_load_table(leap1, TRUE);
+ EXPECT_EQ(1, rc);
+
+ // test on-spot with limted table - does not work if build before 2013!
+ rc = leapsec_query(&qr, lsec2009, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(35, qr.tai_offs);
+ EXPECT_EQ(0, qr.tai_diff);
+ EXPECT_EQ(0, qr.dist);
+}
+
+TEST_F(leapsecTest, addManual) {
+ int rc;
+ leap_result_t qr;
+
+ static const u_int32 insns[] = {
+ 2982009600, // 29 # 1 Jul 1994
+ 3029443200, // 30 # 1 Jan 1996
+ 3076704000, // 31 # 1 Jul 1997
+ 3124137600, // 32 # 1 Jan 1999
+ 3345062400, // 33 # 1 Jan 2006
+ 3439756800, // 34 # 1 Jan 2009
+ 3550089600, // 35 # 1 Jul 2012
+ 0 // sentinel
+ };
+
+ rc = setup_load_table(leap2, FALSE);
+ EXPECT_EQ(1, rc);
+
+ leap_table_t * pt = leapsec_get_table(0);
+ for (int idx=1; insns[idx]; ++idx) {
+ rc = leapsec_add_dyn(insns[idx] - 20*SECSPERDAY - 100, 1, NULL);
+ EXPECT_EQ(TRUE, rc);
+ }
+ // try to slip in a previous entry
+ rc = leapsec_add_dyn(insns[0] - 20*SECSPERDAY - 100, 1, NULL);
+ EXPECT_EQ(FALSE, rc);
+ //leapsec_dump(pt, (leapsec_dumper)fprintf, stdout);
+}
+
+TEST_F(leapsecTest, addFixed) {
+ int rc;
+ leap_result_t qr;
+
+ static const struct { u_int32 tt; int of; } insns[] = {
+ {2982009600, 29},// # 1 Jul 1994
+ {3029443200, 30},// # 1 Jan 1996
+ {3076704000, 31},// # 1 Jul 1997
+ {3124137600, 32},// # 1 Jan 1999
+ {3345062400, 33},// # 1 Jan 2006
+ {3439756800, 34},// # 1 Jan 2009
+ {3550089600, 35},// # 1 Jul 2012
+ {0,0} // sentinel
+ };
+
+ rc = setup_load_table(leap2, FALSE);
+ EXPECT_EQ(1, rc);
+
+ leap_table_t * pt = leapsec_get_table(0);
+ // try to get in BAD time stamps...
+ for (int idx=0; insns[idx].tt; ++idx) {
+ rc = leapsec_add_fix(insns[idx].tt - 20*SECSPERDAY - 100,
+ insns[idx].tt + SECSPERDAY,
+ insns[idx].of,
+ NULL);
+ EXPECT_EQ(FALSE, rc);
+ }
+ // no do it right
+ for (int idx=0; insns[idx].tt; ++idx) {
+ rc = leapsec_add_fix(insns[idx].tt,
+ insns[idx].tt + SECSPERDAY,
+ insns[idx].of,
+ NULL);
+ EXPECT_EQ(TRUE, rc);
+ }
+ // try to slip in a previous entry
+ rc = leapsec_add_fix(insns[0].tt,
+ insns[0].tt + SECSPERDAY,
+ insns[0].of,
+ NULL);
+ EXPECT_EQ(FALSE, rc);
+ //leapsec_dump(pt, (leapsec_dumper)fprintf, stdout);
+}
+
+/* test handling of the leap second at 2012.07.01 */
+TEST_F(leapsecTest, ls2012sequenceElectric) {
+ int rc;
+ leap_result_t qr;
+
+ rc = setup_load_table(leap1);
+ EXPECT_EQ(1, rc);
+
+ rc = leapsec_query(&qr, lsec2012 - 60*SECSPERDAY, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2012 - 7*SECSPERDAY, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_SCHEDULE, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2012 - SECSPERHR, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_ANNOUNCE, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2012 - 1, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_ALERT, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2012, NULL);
+ EXPECT_EQ(TRUE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+
+ // second call, same time frame: no trigger!
+ rc = leapsec_query(&qr, lsec2012, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+}
+
+/* test handling of the leap second at 2012.07.01 */
+TEST_F(leapsecTest, ls2012sequenceDumb) {
+ int rc;
+ leap_result_t qr;
+
+ leapsec_electric(0);
+ EXPECT_EQ(0, leapsec_electric(-1));
+ EXPECT_EQ(0, leapsec_electric(-1));
+
+ rc = setup_load_table(leap1);
+ EXPECT_EQ(1, rc);
+
+ rc = leapsec_query(&qr, lsec2012 - 60*SECSPERDAY, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2012 - 7*SECSPERDAY, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_SCHEDULE, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2012 - SECSPERHR, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_ANNOUNCE, qr.proximity);
+
+ rc = leapsec_query(&qr, lsec2012 - 1, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_ALERT, qr.proximity);
+
+ // This is just 1 sec before transition!
+ rc = leapsec_query(&qr, lsec2012, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_ALERT, qr.proximity);
+
+ // NOW the insert/backwarp must happen
+ rc = leapsec_query(&qr, lsec2012+1, NULL);
+ EXPECT_EQ(TRUE, rc);
+ EXPECT_EQ(-1, qr.warped );
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+
+ // second call with transition time: no trigger!
+ rc = leapsec_query(&qr, lsec2012, NULL);
+ EXPECT_EQ(FALSE, rc);
+ EXPECT_EQ(0, qr.warped );
+ EXPECT_EQ(LSPROX_NOWARN, qr.proximity);
+}
+
--- /dev/null
+#include "ntpdtest.h"
+
+/* This file contains various constants that libntp needs to be set
+ * and that is normally defined in ntpd/ntpq/...
+ */
+
+u_long current_time = 4; // needed by authkeys. Used only in to calculate lifetime.
+const char *progname = "ntpdtest";
+
+time_t ntpdtest::nowtime = 0;
+
+time_t ntpdtest::timefunc(time_t *ptr)
+{
+ if (ptr)
+ *ptr = nowtime;
+ return nowtime;
+}
+
+void ntpdtest::settime(int y, int m, int d, int H, int M, int S)
+{
+
+ time_t days(ntpcal_edate_to_eradays(y-1, m-1, d-1) + 1 - DAY_UNIX_STARTS);
+ time_t secs(ntpcal_etime_to_seconds(H, M, S));
+
+ nowtime = days * SECSPERDAY + secs;
+}
+
--- /dev/null
+#include "tests_main.h"
+
+extern "C" {
+#include "ntp_stdlib.h"
+#include "ntp_calendar.h"
+};
+
+class ntpdtest : public ntptest {
+
+protected:
+ static time_t timefunc(time_t*);
+ static time_t nowtime;
+ static void settime(int y, int m, int d, int H, int M, int S);
+
+};