Commit 09ece8cd authored by JINMEI Tatuya's avatar JINMEI Tatuya
Browse files

Merge branch 'trac61'

parents 296d6b7b bd8392ff
......@@ -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));
}
}
}
......@@ -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);
//@}
}
}
......
......@@ -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_));
......
......@@ -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);
}
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment