]> git.ipfire.org Git - thirdparty/ntp.git/commitdiff
[Bug 2250] Rework of leap second handling machine
authorJuergen Perlinger <perlinger@ntp.org>
Fri, 27 Sep 2013 18:37:04 +0000 (20:37 +0200)
committerJuergen Perlinger <perlinger@ntp.org>
Fri, 27 Sep 2013 18:37:04 +0000 (20:37 +0200)
bk: 5245d050iy4vYHPZe2kzKMeejbm0Gw

16 files changed:
ChangeLog
configure.ac
include/ntpd.h
ntpd/Makefile.am
ntpd/ntp_control.c
ntpd/ntp_crypto.c
ntpd/ntp_leapsec.c [new file with mode: 0644]
ntpd/ntp_leapsec.h [new file with mode: 0644]
ntpd/ntp_proto.c
ntpd/ntp_timer.c
ntpd/ntp_util.c
tests/Makefile.am
tests/ntpd/Makefile.am [new file with mode: 0644]
tests/ntpd/leapsec.cpp [new file with mode: 0644]
tests/ntpd/ntpdtest.cpp [new file with mode: 0644]
tests/ntpd/ntpdtest.h [new file with mode: 0644]

index 10684a8789df611eb8ae6e443872247ab7fc50a8..8ff2221b6e42a9f7ac91ee225b12db4c010deac8 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,4 @@
+* [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>
index 0d6b47e81ca8fd501ed18227e5e13c5db90c38cb..8d1294e9685aa008b7fc816146a688cb2d3b30ee 100644 (file)
@@ -4261,6 +4261,7 @@ AC_CONFIG_FILES([scripts/plot_summary],   [chmod +x scripts/plot_summary])
 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])
index 1d49a9961b12d618ed097167f7c133efecbf3668..d62b5f0d557a47d5e73e8e8ae405cb3498af320a 100644 (file)
@@ -222,11 +222,7 @@ extern     void    peer_clear      (struct peer *, char *);
 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;
@@ -290,7 +286,6 @@ extern      int     mprintf_clock_stats(sockaddr_u *, const char *, ...)
                        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 *);
index f97e49b545898bee15b0eb3260a9d4cba7eec37e..4c4ca2db36146acf57e241412d490653a3a64db8 100644 (file)
@@ -173,7 +173,10 @@ noinst_DATA =                              \
        $(srcdir)/ntpd.mdoc.in          \
        $(NULL)
 
-noinst_HEADERS = declcond.h
+noinst_HEADERS =                       \
+       declcond.h                      \
+       ntp_leapsec.h                   \
+       $(NULL)
 
 install-data-local: install-html
 
@@ -217,6 +220,7 @@ libntpd_a_SOURCES =         \
        ntp_control.c           \
        ntp_crypto.c            \
        ntp_filegen.c           \
+       ntp_leapsec.c           \
        ntp_loopfilter.c        \
        ntp_monitor.c           \
        ntp_peer.c              \
index 4dea934af8ecbec4f5cad7ec66538c4650b4f8cc..a4724ccdd3a8e744ec3272a3a2a6d80d9d20a186 100644 (file)
@@ -25,6 +25,7 @@
 #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
@@ -1707,13 +1708,13 @@ ctl_putsys(
 #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()
@@ -1907,18 +1908,24 @@ ctl_putsys(
                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);
index b36de3e77988c6837e6a14aace53ff28ffb76869..b7e503a4577532ca3bbdeaf106c63f13bdec3d7b 100644 (file)
@@ -19,6 +19,7 @@
 #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"
@@ -938,7 +939,6 @@ crypto_recv(
                 * 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
@@ -953,18 +953,22 @@ crypto_recv(
                         * 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;
@@ -1789,6 +1793,7 @@ crypto_update(void)
        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)
@@ -1842,9 +1847,10 @@ crypto_update(void)
                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);
@@ -1852,7 +1858,7 @@ crypto_update(void)
        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)); 
diff --git a/ntpd/ntp_leapsec.c b/ntpd/ntp_leapsec.c
new file mode 100644 (file)
index 0000000..db46faa
--- /dev/null
@@ -0,0 +1,899 @@
+/*
+ * 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! -*- */
diff --git a/ntpd/ntp_leapsec.h b/ntpd/ntp_leapsec.h
new file mode 100644 (file)
index 0000000..fd76d90
--- /dev/null
@@ -0,0 +1,165 @@
+/*
+ * 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) */
index f2d6970ec92a17caef0f80503018e6ea8923dc89..d61be35828d7be1ffcf4365b85ecefdc265f840e 100644 (file)
@@ -13,6 +13,7 @@
 #include "ntp_unixtime.h"
 #include "ntp_control.h"
 #include "ntp_string.h"
+#include "ntp_leapsec.h"
 
 #include <stdio.h>
 #ifdef HAVE_LIBSCF_H
@@ -92,11 +93,8 @@ double       sys_maxdist = MAXDISTANCE; /* selection threshold */
 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) */
@@ -1941,38 +1939,24 @@ clock_update(
                }
 
                /*
-                * 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;
 
@@ -2782,7 +2766,8 @@ clock_select(void)
         */
        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;
@@ -2790,9 +2775,15 @@ clock_select(void)
                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;
index aacec6496a9dc61fe986acf42ba0ec99007c7089..14af57c3f9cf2695317017a132ca76c1be7d22d2 100644 (file)
@@ -8,6 +8,7 @@
 #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"
@@ -39,6 +40,8 @@
 #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
@@ -279,6 +282,7 @@ timer(void)
        struct peer *   p;
        struct peer *   next_peer;
        l_fp            now;
+       time_t          tnow;
        static int      leap_warn_log = FALSE;
 
        /*
@@ -350,35 +354,15 @@ timer(void)
                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);
        }
 
        /*
@@ -427,25 +411,22 @@ timer(void)
        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;
        }
 }
 
@@ -511,3 +492,78 @@ timer_clr_stats(void)
        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;
+       }
+}
index 7224d43217dacc29bbe07523d83958a0b20e3ee2..1e95f0dadc5dd931c7ef11b73e1b396256df3115 100644 (file)
@@ -12,6 +12,7 @@
 #include "ntp_stdlib.h"
 #include "ntp_assert.h"
 #include "ntp_calendar.h"
+#include "ntp_leapsec.h"
 #include "lib_strbuf.h"
 
 #include <stdio.h>
@@ -98,7 +99,6 @@ static double prev_drift_comp;                /* last frequency update */
 /*
  * Function prototypes
  */
-static int     leap_file(FILE *);
 static void    record_sys_stats(void);
        void    ntpd_time_stepped(void);
 
@@ -489,20 +489,23 @@ stats_config(
                        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;
@@ -867,6 +870,7 @@ check_leap_file(
        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) {
@@ -885,10 +889,13 @@ check_leap_file(
                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);
@@ -898,109 +905,6 @@ check_leap_file(
 }
 
 
-/*
- * 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
  */
@@ -1066,21 +970,17 @@ char * fstostr(
        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;
 }
 
index 5b02c6e0d5a8219fc4bb89c8dbeccab1e310d80d..a7d7c3c7e8d38ae8200b4321f9f8ba6e999dd9ca 100644 (file)
@@ -1,6 +1,9 @@
+NULL =
 SUBDIRS = 
 
 if GTEST_AVAILABLE
-SUBDIRS += libntp
+SUBDIRS +=     libntp  \
+               ntpd    \
+               $(NULL)
 endif
 
diff --git a/tests/ntpd/Makefile.am b/tests/ntpd/Makefile.am
new file mode 100644 (file)
index 0000000..c5df6e4
--- /dev/null
@@ -0,0 +1,55 @@
+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
diff --git a/tests/ntpd/leapsec.cpp b/tests/ntpd/leapsec.cpp
new file mode 100644 (file)
index 0000000..4596d8e
--- /dev/null
@@ -0,0 +1,568 @@
+#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);
+}
+
diff --git a/tests/ntpd/ntpdtest.cpp b/tests/ntpd/ntpdtest.cpp
new file mode 100644 (file)
index 0000000..76b3b1a
--- /dev/null
@@ -0,0 +1,27 @@
+#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;
+}
+
diff --git a/tests/ntpd/ntpdtest.h b/tests/ntpd/ntpdtest.h
new file mode 100644 (file)
index 0000000..cc3172a
--- /dev/null
@@ -0,0 +1,15 @@
+#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);
+
+};