+* [Bug 1771] algorithmic error in 'clocktime()' fixed;
+ Unit test extended for fixed system time
(4.2.7p109) 2011/01/02 Released by Harlan Stenn <stenn@ntp.org>
* Remove nearly all strcpy() and most strcat() from NTP distribution.
One major pocket remains in ntp_crypto.c. libopts & libisc also have
int32 lo;
} ntpcal_split;
+typedef time_t (*systime_func_ptr)(time_t*);
+
+/*
+ * set the function for getting the system time. This is mostly used for
+ * unit testing to provide a fixed / shifted time stamp. Setting the
+ * value to NULL restores the original function, that is, 'time()',
+ * which is also the automatic default.
+ */
+extern systime_func_ptr ntpcal_set_timefunc(systime_func_ptr);
/*
* days-of-week
#include "ntp_stdlib.h"
#include "ntp_calendar.h"
-/*
- * Hacks to avoid excercising the multiplier. I have no pride.
- */
-#define MULBY10(x) (((x)<<3) + ((x)<<1))
-#define MULBY60(x) (((x)<<6) - ((x)<<2)) /* watch overflow */
-#define MULBY24(x) (((x)<<4) + ((x)<<3))
-
/*
* We check that the time be within CLOSETIME seconds of the receive
* time stamp. This is about 4 hours, which hopefully should be wide
*/
#define NEARTIME (182u * SECSPERDAY)
-/*
- * For a final check, we use a limit that is exactly half of a leap year
- */
-#define NEARLIMIT (183u * SECSPERDAY)
-
/*
* local calendar helpers
*/
* Compute the offset into the year in seconds. Note that
* this could come out to be a negative number.
*/
- tmp = yday-1;
- tmp = MULBY24(tmp) + hour + tzoff;
- tmp = MULBY60(tmp) + (long)minute;
- tmp = MULBY60(tmp) + (long)second;
-
+ tmp = ((int32)second +
+ SECSPERMIN * ((int32)minute +
+ MINSPERHR * ((int32)hour + (int32)tzoff +
+ HRSPERDAY * ((int32)yday - 1))));
/*
* Based on the cached year start, do a first attempt. Be
* happy and return if this gets us better than NEARTIME to
* the receive time stamp. Do this only if the cached year
- * start is not zero, which will no happen after 1900 for the
+ * start is not zero, which will not happen after 1900 for the
* next few thousand years.
*/
if (*yearstart) {
/* -- get time stamp of potential solution */
- test[0] = (u_int32)*yearstart + tmp;
+ test[0] = (u_int32)(*yearstart) + tmp;
/* -- calc absolute difference to receive time */
diff[0] = test[0] - rec_ui;
if (diff[0] >= 0x80000000u)
* around the guess and select the entry with the minimum
* absolute difference to the receive time stamp.
*/
- y = ntp_to_year(rec_ui)
- + (tmp / SECSPERAVGYEAR)
- - (tmp < 0); /* get close */
-
+ y = ntp_to_year(rec_ui - tmp);
for (idx = 0; idx < 3; idx++) {
/* -- get year start of potential solution */
ystt[idx] = year_to_ntp(y + idx - 1);
for (min = 1, idx = 0; idx < 3; idx++)
if (diff[idx] < diff[min])
min = idx;
- if (diff[min] <= NEARLIMIT) {
- /* -*- store results and update year start */
- *ts_ui = test[min];
- *yearstart = ystt[min];
- } else
- *ts_ui = rec_ui; /* emergency fallback */
+ /* -*- store results and update year start */
+ *ts_ui = test[min];
+ *yearstart = ystt[min];
/* -*- tell if we could get into CLOSETIME*/
return diff[min] < CLOSETIME;
#include "ntp_fp.h"
#include "ntp_unixtime.h"
+/*
+ *---------------------------------------------------------------------
+ * replacing the 'time()' function
+ * --------------------------------------------------------------------
+ */
+
+static systime_func_ptr systime_func = time;
+
+systime_func_ptr
+ntpcal_set_timefunc(
+ systime_func_ptr nfunc)
+{
+ systime_func_ptr res = systime_func;
+
+ if (!nfunc)
+ nfunc = time;
+ systime_func = nfunc;
+
+ return res;
+}
+
+
+
+static time_t
+now()
+{
+ if (systime_func)
+ return (*systime_func)(NULL);
+ else
+ return time(NULL);
+}
+
/*
*---------------------------------------------------------------------
* basic calendar stuff
#ifdef HAVE_INT64
- res.q_s = pivot ? *pivot : time(NULL);
+ res.q_s = pivot ? *pivot : now();
res.Q_s -= 0x80000000u; /* unshift of half range */
ntp -= (u_int32)JAN_1970; /* warp into UN*X domain */
#else /* no 64bit scalars */
- time_t tmp = pivot ? *pivot : time(NULL);
+ time_t tmp = pivot ? *pivot : now();
/*
* shifting negative signed quantities is compiler-dependent, so
#ifdef HAVE_INT64
- res.q_s = pivot ? *pivot : time(NULL);
+ res.q_s = pivot ? *pivot : now();
res.Q_s -= 0x80000000u; /* unshift of half range */
res.Q_s += (u_int32)JAN_1970; /* warp into NTP domain */
#else /* no 64bit scalars */
- time_t tmp = pivot ? *pivot : time(NULL);
+ time_t tmp = pivot ? *pivot : now();
/*
* shifting negative signed quantities is compiler-dependent, so
class caljulianTest : public libntptest {
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
}
};
+void caljulianTest::SetUp()
+{
+ ntpcal_set_timefunc(timefunc);
+ settime(1970, 1, 1, 0, 0, 0);
+}
+
+void caljulianTest::TearDown()
+{
+ ntpcal_set_timefunc(NULL);
+}
+
+
TEST_F(caljulianTest, RegularTime) {
u_long testDate = 3485080800UL; // 2010-06-09 14:00:00
calendar expected = {2010,160,6,9,14,0,0};
#include "libntptest.h"
-/*
- * calyearstart uses a pivot time, which defaults to the current system
- * time if not given. Since this is not a good idea in an regression
- * test, we use 2020-01-01 for the pivot.
- */
-
-static const time_t hold = 1577836800; // 2020-01-01 00:00:00
-
class calyearstartTest : public libntptest {
+protected:
+ virtual void SetUp();
+ virtual void TearDown();
};
+void calyearstartTest::SetUp()
+{
+ ntpcal_set_timefunc(timefunc);
+ settime(1970, 1, 1, 0, 0, 0);
+}
+
+void calyearstartTest::TearDown()
+{
+ ntpcal_set_timefunc(NULL);
+}
+
+
TEST_F(calyearstartTest, NoWrapInDateRange) {
const u_int32 input = 3486372600UL; // 2010-06-24 12:50:00.
const u_int32 expected = 3471292800UL; // 2010-01-01 00:00:00
- EXPECT_EQ(expected, calyearstart(input, &hold));
+ EXPECT_EQ(expected, calyearstart(input, &nowtime));
+ EXPECT_EQ(expected, calyearstart(input, NULL));
}
TEST_F(calyearstartTest, NoWrapInDateRangeLeapYear) {
const u_int32 input = 3549528000UL; // 2012-06-24 12:00:00
const u_int32 expected = 3534364800UL; // 2012-01-01 00:00:00
- EXPECT_EQ(expected, calyearstart(input, &hold));
+ EXPECT_EQ(expected, calyearstart(input, &nowtime));
+ EXPECT_EQ(expected, calyearstart(input, NULL));
}
TEST_F(calyearstartTest, WrapInDateRange) {
const u_int32 input = 19904UL; // 2036-02-07 12:00:00
const u_int32 expected = 4291747200UL; // 2036-01-01 00:00:00
- EXPECT_EQ(expected, calyearstart(input, &hold));
+ EXPECT_EQ(expected, calyearstart(input, &nowtime));
+ EXPECT_EQ(expected, calyearstart(input, NULL));
}
#include "libntptest.h"
-/*
- * clocktime() uses calyearstart(), which in uses caljulian().
- * caljulian() should be ideally be mocked, because it uses
- * the current system time.
- */
+// ---------------------------------------------------------------------
+// test fixture
+//
+// The clocktimeTest uses the NTP calendar feature to use a mockup
+// function for getting the current system time, so the tests are not
+// dependent on the actual system time.
class clocktimeTest : public libntptest {
+ virtual void SetUp();
+ virtual void TearDown();
};
+void clocktimeTest::SetUp()
+{
+ ntpcal_set_timefunc(timefunc);
+ settime(2000, 1, 1, 0, 0, 0);
+}
+
+void clocktimeTest::TearDown()
+{
+ ntpcal_set_timefunc(NULL);
+}
+
+// ---------------------------------------------------------------------
+// test cases
+
TEST_F(clocktimeTest, CurrentYear) {
// Timestamp: 2010-06-24 12:50:00Z
- const u_long timestamp = 3486372600UL;
- const u_int32 expected = timestamp; // exactly the same.
+ const u_int32 timestamp = 3486372600UL;
+ const u_int32 expected = timestamp; // exactly the same.
const int yday=175, hour=12, minute=50, second=0, tzoff=0;
* timestamp for the 12:00:00 time.
*/
- const u_long timestamp = 3486372600UL; // 2010-06-24 12:50:00Z
- const u_int32 expected = 3486369600UL; // 2010-06-24 12:00:00Z
+ const u_int32 timestamp = 3486372600UL; // 2010-06-24 12:50:00Z
+ const u_int32 expected = 3486369600UL; // 2010-06-24 12:00:00Z
const int yday=175, hour=12, minute=0, second=0, tzoff=0;
*
* Time sent into function is 04:00:00 +0800
*/
- const u_long timestamp = 3486369600UL;
- const u_int32 expected = timestamp;
+ const u_int32 timestamp = 3486369600UL;
+ const u_int32 expected = timestamp;
const int yday=175, hour=4, minute=0, second=0, tzoff=8;
* Time sent into function is 11:00:00.
* Yearstart sent into function is the yearstart of 2009!
*/
- const u_long timestamp = 3471418800UL;
- const u_int32 expected = timestamp;
+ const u_int32 timestamp = 3471418800UL;
+ const u_int32 expected = timestamp;
const int yday=2, hour=11, minute=0, second=0, tzoff=0;
* Time sent into function is 23:00:00
* (which is meant to be 2009-12-31 23:00:00Z)
*/
- const u_long timestamp = 3471296400UL;
- const u_int32 expected = 3471289200UL;
+ const u_int32 timestamp = 3471296400UL;
+ const u_int32 expected = 3471289200UL;
const int yday=365, hour=23, minute=0, second=0, tzoff=0;
* Time sent into function is 01:00:00
* (which is meant to be 2010-01-01 01:00:00Z)
*/
- const u_long timestamp = 3471289200UL;
- const u_int32 expected = 3471296400UL;
+ const u_int32 timestamp = 3471289200UL;
+ const u_int32 expected = 3471296400UL;
const int yday=1, hour=1, minute=0, second=0, tzoff=0;
u_long yearstart = 0;
TEST_F(clocktimeTest, NoReasonableConversion) {
/* Timestamp is: 2010-01-02 11:00:00Z */
- const u_long timestamp = 3471418800UL;
+ const u_int32 timestamp = 3471418800UL;
const int yday=100, hour=12, minute=0, second=0, tzoff=0;
u_long yearstart = 0;
ASSERT_FALSE(clocktime(yday, hour, minute, second, tzoff, timestamp,
&yearstart, &actual));
}
+
+TEST_F(clocktimeTest, AlwaysInLimit) {
+ /* Timestamp is: 2010-01-02 11:00:00Z */
+ const u_int32 timestamp = 3471418800UL;
+
+ int cyc, yday, hour, minute, second;
+ u_long yearstart = 0;
+ u_int32 actual, diff;
+
+ for (cyc = 0; cyc < 5; cyc++) {
+ settime(1900+cyc*65, 1, 1, 0, 0, 0);
+ for (yday = -26000; yday < 26000; yday += 17)
+ for (hour = -204; hour < 204; hour+=2)
+ for (minute = -60; minute < 60; minute++) {
+ clocktime(yday, hour, minute, 30, 0,
+ timestamp, &yearstart, &actual);
+ diff = actual - timestamp;
+ if (diff >= 0x80000000UL)
+ diff = ~diff + 1;
+ ASSERT_LE(diff, (183u * SECSPERDAY));
+ }
+ }
+}
u_long current_time = 4; // needed by authkeys. Used only in to calculate lifetime.
volatile int debug = 0;
const char *progname = "libntptest";
+
+time_t libntptest::nowtime = 0;
+
+time_t libntptest::timefunc(time_t *ptr)
+{
+ if (ptr)
+ *ptr = nowtime;
+ return nowtime;
+}
+
+void libntptest::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;
+}
+
extern "C" {
#include "ntp_stdlib.h"
+#include "ntp_calendar.h"
};
class libntptest : 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);
+
};