Browse Source

Merge branch 'trac61'

JINMEI Tatuya 14 years ago
parent
commit
09ece8cdd4

+ 123 - 28
src/lib/dns/dnssectime.cc

@@ -12,6 +12,10 @@
 // OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 // PERFORMANCE OF THIS SOFTWARE.
 
+#include <stdint.h>
+
+#include <sys/time.h>
+
 #include <string>
 #include <iomanip>
 #include <iostream>
@@ -26,30 +30,121 @@
 
 using namespace std;
 
+namespace {
+int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
+
+inline bool
+isLeap(const int y) {
+    return ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0);
+}
+
+unsigned int
+yearSecs(const int year) {
+    return ((isLeap(year) ? 366 : 365 ) * 86400);
+}
+
+unsigned int
+monthSecs(const int month, const int year) {
+    return ((days[month] + ((month == 1 && isLeap(year)) ? 1 : 0 )) * 86400);
+}
+}
+
 namespace isc {
 namespace dns {
 
 string
-timeToText(const time_t timeval) {
-    struct tm* const t = gmtime(&timeval);
-
-    // gmtime() will keep most values within range, but it can
-    // produce a five-digit year; check for this.
-    if ((t->tm_year + 1900) > 9999) {
-        isc_throw(InvalidTime, "Time value out of range: year > 9999");
+timeToText64(uint64_t value) {
+    struct tm tm;
+    unsigned int secs;
+
+    // We cannot rely on gmtime() because time_t may not be of 64 bit
+    // integer.  The following conversion logic is borrowed from BIND 9.
+    tm.tm_year = 70;
+    while ((secs = yearSecs(tm.tm_year + 1900)) <= value) {
+        value -= secs;
+        ++tm.tm_year;
+        if (tm.tm_year + 1900 > 9999) {
+            isc_throw(InvalidTime,
+                      "Time value out of range (year > 9999): " <<
+                      tm.tm_year + 1900);
+        }
+    }
+    tm.tm_mon = 0;
+    while ((secs = monthSecs(tm.tm_mon, tm.tm_year + 1900)) <= value) {
+        value -= secs;
+        tm.tm_mon++;
     }
+    tm.tm_mday = 1;
+    while (86400 <= value) {
+        value -= 86400;
+        ++tm.tm_mday;
+    }
+    tm.tm_hour = 0;
+    while (3600 <= value) {
+        value -= 3600;
+        ++tm.tm_hour;
+    }
+    tm.tm_min = 0;
+    while (60 <= value) {
+        value -= 60;
+        ++tm.tm_min;
+    }
+    tm.tm_sec = value;    // now t < 60, so this substitution is safe.
 
     ostringstream oss;
     oss << setfill('0')
-        << setw(4) << t->tm_year + 1900
-        << setw(2) << t->tm_mon + 1
-        << setw(2) << t->tm_mday 
-        << setw(2) << t->tm_hour
-        << setw(2) << t->tm_min
-        << setw(2) << t->tm_sec;
+        << setw(4) << tm.tm_year + 1900
+        << setw(2) << tm.tm_mon + 1
+        << setw(2) << tm.tm_mday
+        << setw(2) << tm.tm_hour
+        << setw(2) << tm.tm_min
+        << setw(2) << tm.tm_sec;
     return (oss.str());
 }
 
+// timeToText32() below uses the current system time.  To test it with
+// unusual current time values we introduce the following function pointer;
+// when it's non NULL, we call it to get the (normally faked) current time.
+// Otherwise we use the standard gettimeofday(2).  This hook is specifically
+// intended for testing purposes, so, even if it's visible outside of this
+// library, it's not even declared in a header file.
+namespace dnssectime {
+namespace detail {
+int64_t (*gettimeFunction)() = NULL;
+}
+}
+
+namespace {
+int64_t
+gettimeofdayWrapper() {
+    using namespace dnssectime::detail;
+    if (gettimeFunction != NULL) {
+        return (gettimeFunction());
+    }
+
+    struct timeval now;
+    gettimeofday(&now, NULL);
+
+    return (static_cast<int64_t>(now.tv_sec));
+}
+}
+
+string
+timeToText32(const uint32_t value) {
+    // We first adjust the time to the closest epoch based on the current time.
+    // Note that the following variables must be signed in order to handle
+    // time until year 2038 correctly.
+    const int64_t start = gettimeofdayWrapper() - 0x7fffffff;
+    int64_t base = 0;
+    int64_t t;
+    while ((t = (base + value)) < start) {
+        base += 0x100000000LL;
+    }
+
+    // Then convert it to text.
+    return (timeToText64(t));
+}
+
 namespace {
 const size_t DATE_LEN = 14;      // YYYYMMDDHHmmSS
 
@@ -62,27 +157,20 @@ checkRange(const int min, const int max, const int value,
     }
     isc_throw(InvalidTime, "Invalid " << valname << "value: " << value);
 }
-
-int days[12] = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
-
-inline bool
-isLeap(const int y) {
-    return ((((y) % 4) == 0 && ((y) % 100) != 0) || ((y) % 400) == 0);
-}
 }
 
-time_t
-timeFromText(const string& time_txt) {
-    // first try reading YYYYMMDDHHmmSS format
-    int year, month, day, hour, minute, second;
-
+uint64_t
+timeFromText64(const string& time_txt) {
+    // Confirm the source only consists digits.  sscanf() allows some
+    // minor exceptions.
     for (int i = 0; i < time_txt.length(); ++i) {
         if (!isdigit(time_txt.at(i))) {
-            isc_throw(InvalidTime,
-                      "Couldn't convert non-numeric time value: " << time_txt); 
+            isc_throw(InvalidTime, "Couldn't convert non-numeric time value: "
+                      << time_txt);
         }
     }
 
+    int year, month, day, hour, minute, second;
     if (time_txt.length() != DATE_LEN ||
         sscanf(time_txt.c_str(), "%4d%2d%2d%2d%2d%2d",
                &year, &month, &day, &hour, &minute, &second) != 6)
@@ -100,7 +188,7 @@ timeFromText(const string& time_txt) {
 
     time_t timeval = second + (60 * minute) + (3600 * hour) +
         ((day - 1) * 86400);
-    for (int m = 0; m < (month - 1); m++) {
+    for (int m = 0; m < (month - 1); ++m) {
             timeval += days[m] * 86400;
     }
     if (isLeap(year) && month > 2) {
@@ -112,5 +200,12 @@ timeFromText(const string& time_txt) {
 
     return (timeval);
 }
+
+uint32_t
+timeFromText32(const string& time_txt) {
+    // The implicit conversion from uint64_t to uint32_t should just work here,
+    // because we only need to drop higher 32 bits.
+    return (timeFromText64(time_txt));
+}
 }
 }

+ 94 - 4
src/lib/dns/dnssectime.h

@@ -17,7 +17,6 @@
 
 #include <sys/types.h>
 #include <stdint.h>
-#include <time.h>
 
 #include <exceptions/exceptions.h>
 
@@ -40,11 +39,102 @@ public:
         isc::Exception(file, line, what) {}
 };
 
-time_t
-timeFromText(const std::string& time_txt);
+///
+/// \name DNSSEC time conversion functions.
+///
+/// These functions convert between times represented in seconds (in integer)
+/// since epoch and those in the textual form used in the RRSIG records.
+/// For integers we provide both 32-bit and 64-bit versions.
+/// The RRSIG expiration and inception fields are both 32-bit unsigned
+/// integers, so 32-bit versions would be more useful for protocol operations.
+/// However, with 32-bit integers we need to take into account wrap-around
+/// points and compare values using the serial number arithmetic as specified
+/// in RFC4034, which would be more error prone.  We therefore provide 64-bit
+/// versions, too.
+///
+/// The timezone is always UTC for these functions.
+//@{
+/// Convert textual DNSSEC time to integer, 64-bit version.
+///
+/// The textual form must only consist of digits and be in the form of
+/// YYYYMMDDHHmmSS, where:
+/// - YYYY must be between 1970 and 9999
+/// - MM must be between 01 and 12
+/// - DD must be between 01 and 31 and must be a valid day for the month
+///   represented in 'MM'.  For example, if MM is 04, DD cannot be 31.
+///   DD can be 29 when MM is 02 only when YYYY is a leap year.
+/// - HH must be between 00 and 23
+/// - mm must be between 00 and 59
+/// - SS must be between 00 and 60
+///
+/// For all fields the range includes the begin and end values.  Note that
+/// 60 is allowed for 'SS', intending a leap second, although in real operation
+/// it's unlikely to be specified.
+///
+/// If the given text is valid, this function converts it to an unsigned
+/// 64-bit number of seconds since epoch (1 January 1970 00:00:00) and returns
+/// the converted value.  64 bits are sufficient to represent all possible
+/// values for the valid format uniquely, so there is no overflow.
+///
+/// \note RFC4034 also defines the textual form of an unsigned decimal integer
+/// for the corresponding time in seconds.  This function doesn't support
+/// this form, and if given it throws an exception of class \c InvalidTime.
+///
+/// \exception InvalidTime The given textual representation is invalid.
+///
+/// \param time_txt Textual time in the form of YYYYMMDDHHmmSS
+/// \return Seconds since epoch corresponding to \c time_txt
+uint64_t
+timeFromText64(const std::string& time_txt);
 
+/// Convert textual DNSSEC time to integer, 32-bit version.
+///
+/// This version is the same as \c timeFromText64() except that the return
+/// value is wrapped around to an unsigned 32-bit integer, simply dropping
+/// the upper 32 bits.
+uint32_t
+timeFromText32(const std::string& time_txt);
+
+/// Convert integral DNSSEC time to textual form, 64-bit version.
+///
+/// This function takes an integer that would be seconds since epoch and
+/// converts it in the form of YYYYMMDDHHmmSS.  For example, if \c value is
+/// 0, it returns "19700101000000".  If the value corresponds to a point
+/// of time on and after year 10,000, which cannot be represented in the
+/// YYYY... form, an exception of class \c InvalidTime will be thrown.
+///
+/// \exception InvalidTime The given time specifies on or after year 10,000.
+/// \exception Other A standard exception, if resource allocation for the
+/// returned text fails.
+///
+/// \param value Seconds since epoch to be converted.
+/// \return Textual representation of \c value in the form of YYYYMMDDHHmmSS.
 std::string
-timeToText(const time_t timeval);
+timeToText64(uint64_t value);
+
+/// Convert integral DNSSEC time to textual form, 32-bit version.
+///
+/// This version is the same as \c timeToText64(), but the time value
+/// is expected to be the lower 32 bits of the full 64-bit value.
+/// These two will be different on and after a certain point of time
+/// in year 2106, so this function internally resolves the ambiguity
+/// using the current system time at the time of function call;
+/// it first identifies the range of [N*2^32 - 2^31, N*2^32 + 2^31)
+/// that contains the current time, and interprets \c value in the context
+/// of that range.  It then applies the same process as \c timeToText64().
+///
+/// There is one important exception in this processing, however.
+/// Until 19 Jan 2038 03:14:08 (2^31 seconds since epoch), this range
+/// would contain time before epoch.  In order to ensure the returned
+/// value is also a valid input to \c timeFromText, this function uses
+/// a special range [0, 2^32) until that time.  As a result, all upper
+/// half of the 32-bit values are treated as a future time.  For example,
+/// 2^32-1 (the highest value in 32-bit unsigned integers) will be converted
+/// to "21060207062815", instead of "19691231235959".
+std::string
+timeToText32(uint32_t value);
+
+//@}
 }
 }
 

+ 4 - 7
src/lib/dns/rdata/generic/rrsig_46.cc

@@ -93,8 +93,8 @@ RRSIG::RRSIG(const string& rrsig_str) :
         isc_throw(InvalidRdataText, "RRSIG labels out of range");
     }
 
-    uint32_t timeexpire = timeFromText(expire_txt);
-    uint32_t timeinception = timeFromText(inception_txt);
+    const uint32_t timeexpire = timeFromText32(expire_txt);
+    const uint32_t timeinception = timeFromText32(inception_txt);
 
     vector<uint8_t> signature;
     decodeBase64(signaturebuf.str(), signature);
@@ -157,15 +157,12 @@ RRSIG::~RRSIG() {
 
 string
 RRSIG::toText() const {
-    string expire = timeToText(impl_->timeexpire_);
-    string inception = timeToText(impl_->timeinception_);
-
     return (impl_->covered_.toText() +
             " " + boost::lexical_cast<string>(static_cast<int>(impl_->algorithm_))
             + " " + boost::lexical_cast<string>(static_cast<int>(impl_->labels_))
             + " " + boost::lexical_cast<string>(impl_->originalttl_)
-            + " " + expire
-            + " " + inception
+            + " " + timeToText32(impl_->timeexpire_)
+            + " " + timeToText32(impl_->timeinception_)
             + " " + boost::lexical_cast<string>(impl_->tag_)
             + " " + impl_->signer_.toText()
             + " " + encodeBase64(impl_->signature_));

+ 120 - 27
src/lib/dns/tests/dnssectime_unittest.cc

@@ -23,48 +23,141 @@
 using namespace std;
 using namespace isc::dns;
 
+// See dnssectime.cc
+namespace isc {
+namespace dns {
+namespace dnssectime {
+namespace detail {
+extern int64_t (*gettimeFunction)();
+}
+}
+}
+}
+
 namespace {
 
-TEST(DNSSECTimeTest, fromText) {
+class DNSSECTimeTest : public ::testing::Test {
+protected:
+    ~DNSSECTimeTest() {
+        dnssectime::detail::gettimeFunction = NULL;
+    }
+};
+
+TEST_F(DNSSECTimeTest, fromText) {
+    // In most cases (in practice) the 32-bit and 64-bit versions should
+    // behave identically, so we'll mainly test the 32-bit version, which
+    // will be more commonly used in actual code (because many of the wire
+    // format time field are 32-bit).  The subtle cases where these two
+    // return different values will be tested at the end of this test case.
+
     // These are bogus and should be rejected
-    EXPECT_THROW(timeFromText("2011 101120000"), InvalidTime);
-    EXPECT_THROW(timeFromText("201101011200-0"), InvalidTime);
+    EXPECT_THROW(timeFromText32("2011 101120000"), InvalidTime);
+    EXPECT_THROW(timeFromText32("201101011200-0"), InvalidTime);
 
-    // Short length
-    EXPECT_THROW(timeFromText("20100223"), InvalidTime);
+    // Short length (or "decimal integer" version of representation;
+    // it's valid per RFC4034, but is not supported in this implementation)
+    EXPECT_THROW(timeFromText32("20100223"), InvalidTime);
 
     // Leap year checks
-    EXPECT_THROW(timeFromText("20110229120000"), InvalidTime);
-    EXPECT_THROW(timeFromText("21000229120000"), InvalidTime);
-    EXPECT_NO_THROW(timeFromText("20000229120000"));
-    EXPECT_NO_THROW(timeFromText("20120229120000"));
+    EXPECT_THROW(timeFromText32("20110229120000"), InvalidTime);
+    EXPECT_THROW(timeFromText32("21000229120000"), InvalidTime);
+    EXPECT_NO_THROW(timeFromText32("20000229120000"));
+    EXPECT_NO_THROW(timeFromText32("20120229120000"));
 
     // unusual case: this implementation allows SS=60 for "leap seconds"
-    EXPECT_NO_THROW(timeFromText("20110101120060"));
+    EXPECT_NO_THROW(timeFromText32("20110101120060"));
 
     // Out of range parameters
-    EXPECT_THROW(timeFromText("19100223214617"), InvalidTime); // YY<1970
-    EXPECT_THROW(timeFromText("20110001120000"), InvalidTime); // MM=00
-    EXPECT_THROW(timeFromText("20111301120000"), InvalidTime); // MM=13
-    EXPECT_THROW(timeFromText("20110100120000"), InvalidTime); // DD=00
-    EXPECT_THROW(timeFromText("20110132120000"), InvalidTime); // DD=32
-    EXPECT_THROW(timeFromText("20110431120000"), InvalidTime); // 'Apr31'
-    EXPECT_THROW(timeFromText("20110101250000"), InvalidTime); // HH=25
-    EXPECT_THROW(timeFromText("20110101126000"), InvalidTime); // mm=60
-    EXPECT_THROW(timeFromText("20110101120061"), InvalidTime); // SS=61
+    EXPECT_THROW(timeFromText32("19100223214617"), InvalidTime); // YY<1970
+    EXPECT_THROW(timeFromText32("20110001120000"), InvalidTime); // MM=00
+    EXPECT_THROW(timeFromText32("20111301120000"), InvalidTime); // MM=13
+    EXPECT_THROW(timeFromText32("20110100120000"), InvalidTime); // DD=00
+    EXPECT_THROW(timeFromText32("20110132120000"), InvalidTime); // DD=32
+    EXPECT_THROW(timeFromText32("20110431120000"), InvalidTime); // 'Apr31'
+    EXPECT_THROW(timeFromText32("20110101250000"), InvalidTime); // HH=25
+    EXPECT_THROW(timeFromText32("20110101126000"), InvalidTime); // mm=60
+    EXPECT_THROW(timeFromText32("20110101120061"), InvalidTime); // SS=61
+
+    // Feb 7, 06:28:15 UTC 2106 is the possible maximum time that can be
+    // represented as an unsigned 32bit integer without overflow.
+    EXPECT_EQ(4294967295L, timeFromText32("21060207062815"));
+
+    // After that, timeFromText32() should start returning the second count
+    // modulo 2^32.
+    EXPECT_EQ(0, timeFromText32("21060207062816"));
+    EXPECT_EQ(10, timeFromText32("21060207062826"));
+
+    // On the other hand, the 64-bit version should return monotonically
+    // increasing counters.
+    EXPECT_EQ(4294967296LL, timeFromText64("21060207062816"));
+    EXPECT_EQ(4294967306LL, timeFromText64("21060207062826"));
 }
 
-TEST(DNSSECTimeTest, toText) {
-    EXPECT_EQ("19700101000000", timeToText(0));
-    EXPECT_EQ("20100311233000", timeToText(1268350200));
+// This helper templated function tells timeToText32 a faked current time.
+// The template parameter is that faked time in the form of int64_t seconds
+// since epoch.
+template <int64_t NOW>
+int64_t
+testGetTime() {
+    return (NOW);
 }
 
-TEST(DNSSECTimeTest, overflow) {
+// Seconds since epoch for the year 10K eve.  Commonly used in some tests
+// below.
+const uint64_t YEAR10K_EVE = 253402300799LL;
+
+TEST_F(DNSSECTimeTest, toText) {
+    // Check a basic case with the default (normal) gettimeFunction
+    // based on the "real current time".
+    // Note: this will fail after year 2078, but at that point we won't use
+    // this program anyway:-)
+    EXPECT_EQ("20100311233000", timeToText32(1268350200));
+
+    // Set the current time to: Feb 18 09:04:14 UTC 2012 (an arbitrary choice
+    // in the range of the first half of uint32 since epoch).
+    dnssectime::detail::gettimeFunction = testGetTime<1329555854LL>;
+
+    // Test the "year 2038" problem.
+    // Check the result of toText() for "INT_MIN" in int32_t.  It's in the
+    // 68-year range from the faked current time, so the result should be
+    // in year 2038, instead of 1901.
+    EXPECT_EQ("20380119031408", timeToText64(0x80000000L));
+    EXPECT_EQ("20380119031408", timeToText32(0x80000000L));
+
+    // A controversial case: what should we do with "-1"?  It's out of range
+    // in future, but according to RFC time before epoch doesn't seem to be
+    // considered "in-range" either.  Our toText() implementation handles
+    // this range as a special case and always treats them as future time
+    // until year 2038.  This won't be a real issue in practice, though,
+    // since such too large values won't be used in actual deployment by then.
+    EXPECT_EQ("21060207062815", timeToText32(0xffffffffL));
+
+    // After the singular point of year 2038, the first half of uint32 can
+    // point to a future time.
+    // Set the current time to: Apr 1 00:00:00 UTC 2038:
+    dnssectime::detail::gettimeFunction = testGetTime<2153692800LL>;
+    // then time "10" is Feb 7 06:28:26 UTC 2106
+    EXPECT_EQ("21060207062826", timeToText32(10));
+    // in 64-bit, it's 2^32 + 10
+    EXPECT_EQ("21060207062826", timeToText64(0x10000000aLL));
+
+    // After year 2106, the upper half of uint32 can point to past time
+    // (as it should).
+    dnssectime::detail::gettimeFunction = testGetTime<0x10000000aLL>;
+    EXPECT_EQ("21060207062815", timeToText32(0xffffffffL));
+
+    // Try very large time value.  Actually it's the possible farthest time
+    // that can be represented in the form of YYYYMMDDHHmmSS.
+    EXPECT_EQ("99991231235959", timeToText64(YEAR10K_EVE));
+    dnssectime::detail::gettimeFunction = testGetTime<YEAR10K_EVE - 10>;
+    EXPECT_EQ("99991231235959", timeToText32(4294197631L));
+}
+
+TEST_F(DNSSECTimeTest, overflow) {
     // Jan 1, Year 10,000.
-    if (sizeof(time_t) > 4) {
-        EXPECT_THROW(timeToText(static_cast<time_t>(253402300800LL)),
-                     InvalidTime);
-    }
+    EXPECT_THROW(timeToText64(253402300800LL), InvalidTime);
+    dnssectime::detail::gettimeFunction = testGetTime<YEAR10K_EVE - 10>;
+    EXPECT_THROW(timeToText32(4294197632L), InvalidTime);
 }
 
 }