From: Juergen Perlinger Date: Fri, 27 Sep 2013 18:37:04 +0000 (+0200) Subject: [Bug 2250] Rework of leap second handling machine X-Git-Tag: NTP_4_2_7P391~4^2 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=b92e4b7ce9c8397c4307801b611b3b48a835a687;p=thirdparty%2Fntp.git [Bug 2250] Rework of leap second handling machine bk: 5245d050iy4vYHPZe2kzKMeejbm0Gw --- diff --git a/ChangeLog b/ChangeLog index 10684a878..8ff2221b6 100644 --- 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 * [Bug 2473] NTPD exits after clock is stepped backwards externally (4.2.7p387) 2013/09/16 Released by Harlan Stenn diff --git a/configure.ac b/configure.ac index 0d6b47e81..8d1294e96 100644 --- a/configure.ac +++ b/configure.ac @@ -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]) diff --git a/include/ntpd.h b/include/ntpd.h index 1d49a9961..d62b5f0d5 100644 --- a/include/ntpd.h +++ b/include/ntpd.h @@ -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 *); diff --git a/ntpd/Makefile.am b/ntpd/Makefile.am index f97e49b54..4c4ca2db3 100644 --- a/ntpd/Makefile.am +++ b/ntpd/Makefile.am @@ -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 \ diff --git a/ntpd/ntp_control.c b/ntpd/ntp_control.c index 4dea934af..a4724ccdd 100644 --- a/ntpd/ntp_control.c +++ b/ntpd/ntp_control.c @@ -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); diff --git a/ntpd/ntp_crypto.c b/ntpd/ntp_crypto.c index b36de3e77..b7e503a45 100644 --- a/ntpd/ntp_crypto.c +++ b/ntpd/ntp_crypto.c @@ -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 index 000000000..db46faacd --- /dev/null +++ b/ntpd/ntp_leapsec.c @@ -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 +#include +#include + +#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 index 000000000..fd76d9087 --- /dev/null +++ b/ntpd/ntp_leapsec.h @@ -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) */ diff --git a/ntpd/ntp_proto.c b/ntpd/ntp_proto.c index f2d6970ec..d61be3582 100644 --- a/ntpd/ntp_proto.c +++ b/ntpd/ntp_proto.c @@ -13,6 +13,7 @@ #include "ntp_unixtime.h" #include "ntp_control.h" #include "ntp_string.h" +#include "ntp_leapsec.h" #include #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; diff --git a/ntpd/ntp_timer.c b/ntpd/ntp_timer.c index aacec6496..14af57c3f 100644 --- a/ntpd/ntp_timer.c +++ b/ntpd/ntp_timer.c @@ -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; + } +} diff --git a/ntpd/ntp_util.c b/ntpd/ntp_util.c index 7224d4321..1e95f0dad 100644 --- a/ntpd/ntp_util.c +++ b/ntpd/ntp_util.c @@ -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 @@ -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; } diff --git a/tests/Makefile.am b/tests/Makefile.am index 5b02c6e0d..a7d7c3c7e 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -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 index 000000000..c5df6e4a3 --- /dev/null +++ b/tests/ntpd/Makefile.am @@ -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 index 000000000..4596d8e84 --- /dev/null +++ b/tests/ntpd/leapsec.cpp @@ -0,0 +1,568 @@ +#include "ntpdtest.h" + +extern "C" { +#include "ntp.h" +#include "ntp_calendar.h" +#include "ntp_leapsec.h" +} + +#include +#include + +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 index 000000000..76b3b1a25 --- /dev/null +++ b/tests/ntpd/ntpdtest.cpp @@ -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 index 000000000..cc3172a83 --- /dev/null +++ b/tests/ntpd/ntpdtest.h @@ -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); + +};