Commit 70883d6e authored by Yuri Wiitala's avatar Yuri Wiitala Committed by Commit Bot

Make base::Time::UTC/LocalExploded() mostly Y10K compliant.

Fixes issues with UTCExploded() on certain 32-bit systems, where Time's
in the near and distant future would result in exploded calendar fields
in "random years past." This change solves the Y2038 problem for those
systems.

Hardens the UTC/LocalExploded() API, to guarantee all platforms will
explode from the years 1601 to 30828 (at least), and clarifies how
callers should check for a valid conversion. For platforms whose
standard libraries are insufficient, the icu::Calendar implementation is
used to provide the needed functionality.

Fixes/Simplification to ensure base::Time::ToRoundedDownMillis...() will
never do math that overflows int64_t's. Minor other tweaks in touched
files.

Fixes to a couple of unit tests in net/der and net/cert/internal.

Bug: 1104442
Change-Id: I5f0e45879b8ee18cc7c253fa4a204443fdc27ebf
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2317864
Commit-Queue: Yuri Wiitala <miu@chromium.org>
Reviewed-by: default avatarRoss McIlroy <rmcilroy@chromium.org>
Reviewed-by: default avatarMatt Mueller <mattm@chromium.org>
Reviewed-by: default avatarMaksim Ivanov <emaxx@chromium.org>
Reviewed-by: default avatarPeter Kasting <pkasting@chromium.org>
Cr-Commit-Position: refs/heads/master@{#802050}
parent 60ce09ac
...@@ -2043,9 +2043,18 @@ component("base") { ...@@ -2043,9 +2043,18 @@ component("base") {
if (is_posix && !is_mac && !is_ios) { if (is_posix && !is_mac && !is_ios) {
sources += [ sources += [
"time/time_conversion_posix.cc", "time/time_conversion_posix.cc",
"time/time_exploded_icu.cc", # See note below.
"time/time_exploded_posix.cc", "time/time_exploded_posix.cc",
"time/time_now_posix.cc", "time/time_now_posix.cc",
] ]
# The ICU dependency is only needed on systems with a 32-bit time_t.
# However, that cannot be determined from build variables, like
# |current_cpu|, since some 32-bit systems have a 64-bit time_t (and vice
# versa). Thus, the dependency is taken here for all POSIX platforms and the
# compiler+linker should be able to easily detect when the ICU routines will
# not be called and delete them in the final linking.
deps += [ "//third_party/icu:icui18n" ]
} }
if (is_posix && !is_mac && !is_ios && !is_nacl) { if (is_posix && !is_mac && !is_ios && !is_nacl) {
......
...@@ -440,12 +440,20 @@ bool Time::FromMillisecondsSinceUnixEpoch(int64_t unix_milliseconds, ...@@ -440,12 +440,20 @@ bool Time::FromMillisecondsSinceUnixEpoch(int64_t unix_milliseconds,
} }
int64_t Time::ToRoundedDownMillisecondsSinceUnixEpoch() const { int64_t Time::ToRoundedDownMillisecondsSinceUnixEpoch() const {
// Adjust from Windows epoch (1601) to Unix epoch (1970). constexpr int64_t kEpochOffsetMillis =
const int64_t ms = (*this - UnixEpoch()).InMicroseconds(); kTimeTToMicrosecondsOffset / kMicrosecondsPerMillisecond;
static_assert(kTimeTToMicrosecondsOffset % kMicrosecondsPerMillisecond == 0,
// Floor rather than truncating. "assumption: no epoch offset sub-milliseconds");
return (ms >= 0) ? (ms / kMicrosecondsPerMillisecond)
: ((ms + 1) / kMicrosecondsPerMillisecond - 1); // Compute the milliseconds since UNIX epoch without the possibility of
// under/overflow. Round the result towards -infinity.
//
// If |us_| is negative and includes fractions of a millisecond, subtract one
// more to effect the round towards -infinity. C-style integer truncation
// takes care of all other cases.
const int64_t millis = us_ / kMicrosecondsPerMillisecond;
const int64_t submillis = us_ % kMicrosecondsPerMillisecond;
return millis - kEpochOffsetMillis - (submillis < 0);
} }
std::ostream& operator<<(std::ostream& os, Time time) { std::ostream& operator<<(std::ostream& os, Time time) {
......
...@@ -541,6 +541,9 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> { ...@@ -541,6 +541,9 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> {
// functions will return false if passed values outside these limits. The limits // functions will return false if passed values outside these limits. The limits
// are inclusive, meaning that the API should support all dates within a given // are inclusive, meaning that the API should support all dates within a given
// limit year. // limit year.
//
// WARNING: These are not the same limits for the inverse functionality,
// UTCExplode() and LocalExplode(). See method comments for further details.
#if defined(OS_WIN) #if defined(OS_WIN)
static constexpr int kExplodedMinYear = 1601; static constexpr int kExplodedMinYear = 1601;
static constexpr int kExplodedMaxYear = 30827; static constexpr int kExplodedMaxYear = 30827;
...@@ -741,14 +744,16 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> { ...@@ -741,14 +744,16 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> {
return FromStringInternal(time_string, false, parsed_time); return FromStringInternal(time_string, false, parsed_time);
} }
// Fills the given exploded structure with either the local time or UTC from // Fills the given |exploded| structure with either the local time or UTC from
// this time structure (containing UTC). // this Time instance. If the conversion cannot be made, the output will be
void UTCExplode(Exploded* exploded) const { // assigned invalid values. Use Exploded::HasValidValues() to confirm a
return Explode(false, exploded); // successful conversion.
} //
void LocalExplode(Exploded* exploded) const { // Y10K compliance: This method will successfully convert all Times that
return Explode(true, exploded); // represent dates on/after the start of the year 1601 and on/before the start
} // of the year 30828. Some platforms might convert over a wider input range.
void UTCExplode(Exploded* exploded) const { Explode(false, exploded); }
void LocalExplode(Exploded* exploded) const { Explode(true, exploded); }
// The following two functions round down the time to the nearest day in // The following two functions round down the time to the nearest day in
// either UTC or local time. It will represent midnight on that day. // either UTC or local time. It will represent midnight on that day.
...@@ -782,6 +787,16 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> { ...@@ -782,6 +787,16 @@ class BASE_EXPORT Time : public time_internal::TimeBase<Time> {
const Exploded& exploded, const Exploded& exploded,
Time* time) WARN_UNUSED_RESULT; Time* time) WARN_UNUSED_RESULT;
// Some platforms use the ICU library to provide To/FromExploded, when their
// native library implementations are insufficient in some way.
static void ExplodeUsingIcu(int64_t millis_since_unix_epoch,
bool is_local,
Exploded* exploded);
static bool FromExplodedUsingIcu(bool is_local,
const Exploded& exploded,
int64_t* millis_since_unix_epoch)
WARN_UNUSED_RESULT;
// Rounds down the time to the nearest day in either local time // Rounds down the time to the nearest day in either local time
// |is_local = true| or UTC |is_local = false|. // |is_local = true| or UTC |is_local = false|.
Time Midnight(bool is_local) const; Time Midnight(bool is_local) const;
......
...@@ -8,15 +8,12 @@ ...@@ -8,15 +8,12 @@
#include "base/check.h" #include "base/check.h"
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "third_party/icu/source/i18n/unicode/calendar.h" #include "third_party/icu/source/i18n/unicode/calendar.h"
#include "third_party/icu/source/i18n/unicode/timezone.h" #include "third_party/icu/source/i18n/unicode/timezone.h"
namespace base { namespace base {
static_assert(
sizeof(Time::Exploded::year) == sizeof(int32_t),
"The sizes of Time::Exploded members and ICU date fields do not match.");
namespace { namespace {
// Returns a new icu::Calendar instance for the local time zone if |is_local| // Returns a new icu::Calendar instance for the local time zone if |is_local|
...@@ -32,45 +29,87 @@ std::unique_ptr<icu::Calendar> CreateCalendar(bool is_local) { ...@@ -32,45 +29,87 @@ std::unique_ptr<icu::Calendar> CreateCalendar(bool is_local) {
return calendar; return calendar;
} }
} // namespace // Explodes the |millis_since_unix_epoch| using an icu::Calendar, and returns
// true if the conversion was successful.
bool ExplodeUsingIcuCalendar(int64_t millis_since_unix_epoch,
bool is_local,
Time::Exploded* exploded) {
// ICU's year calculation is wrong for years too far in the past (though
// other fields seem to be correct). Given that the Time::Explode() for
// Windows only works for values on/after 1601-01-01 00:00:00 UTC, just use
// that as a reasonable lower-bound here as well.
constexpr int64_t kInputLowerBound =
-Time::kTimeTToMicrosecondsOffset / Time::kMicrosecondsPerMillisecond;
static_assert(
Time::kTimeTToMicrosecondsOffset % Time::kMicrosecondsPerMillisecond == 0,
"assumption: no epoch offset sub-milliseconds");
// The input to icu::Calendar is a double-typed value. To ensure no loss of
// precision when converting int64_t to double, an upper-bound must also be
// imposed.
static_assert(std::numeric_limits<double>::radix == 2, "");
constexpr int64_t kInputUpperBound = uint64_t{1}
<< std::numeric_limits<double>::digits;
if (millis_since_unix_epoch < kInputLowerBound ||
millis_since_unix_epoch > kInputUpperBound) {
return false;
}
void Time::Explode(bool is_local, Exploded* exploded) const {
std::unique_ptr<icu::Calendar> calendar = CreateCalendar(is_local); std::unique_ptr<icu::Calendar> calendar = CreateCalendar(is_local);
UErrorCode status = U_ZERO_ERROR; UErrorCode status = U_ZERO_ERROR;
calendar->setTime(ToRoundedDownMillisecondsSinceUnixEpoch(), status); calendar->setTime(millis_since_unix_epoch, status);
DCHECK(U_SUCCESS(status)); if (!U_SUCCESS(status))
return false;
exploded->year = calendar->get(UCAL_YEAR, status); using CalendarField = decltype(calendar->get(UCAL_YEAR, status));
DCHECK(U_SUCCESS(status)); static_assert(sizeof(Time::Exploded::year) >= sizeof(CalendarField),
"Time::Exploded members are not large enough to hold ICU "
"calendar fields.");
bool got_all_fields = true;
exploded->year = calendar->get(UCAL_YEAR, status);
got_all_fields &= !!U_SUCCESS(status);
// ICU's UCalendarMonths is 0-based. E.g., 0 for January. // ICU's UCalendarMonths is 0-based. E.g., 0 for January.
exploded->month = calendar->get(UCAL_MONTH, status) + 1; exploded->month = calendar->get(UCAL_MONTH, status) + 1;
DCHECK(U_SUCCESS(status)); got_all_fields &= !!U_SUCCESS(status);
// ICU's UCalendarDaysOfWeek is 1-based. E.g., 1 for Sunday. // ICU's UCalendarDaysOfWeek is 1-based. E.g., 1 for Sunday.
exploded->day_of_week = calendar->get(UCAL_DAY_OF_WEEK, status) - 1; exploded->day_of_week = calendar->get(UCAL_DAY_OF_WEEK, status) - 1;
DCHECK(U_SUCCESS(status)); got_all_fields &= !!U_SUCCESS(status);
exploded->day_of_month = calendar->get(UCAL_DAY_OF_MONTH, status); exploded->day_of_month = calendar->get(UCAL_DAY_OF_MONTH, status);
DCHECK(U_SUCCESS(status)); got_all_fields &= !!U_SUCCESS(status);
exploded->hour = calendar->get(UCAL_HOUR_OF_DAY, status); exploded->hour = calendar->get(UCAL_HOUR_OF_DAY, status);
DCHECK(U_SUCCESS(status)); got_all_fields &= !!U_SUCCESS(status);
exploded->minute = calendar->get(UCAL_MINUTE, status); exploded->minute = calendar->get(UCAL_MINUTE, status);
DCHECK(U_SUCCESS(status)); got_all_fields &= !!U_SUCCESS(status);
exploded->second = calendar->get(UCAL_SECOND, status); exploded->second = calendar->get(UCAL_SECOND, status);
DCHECK(U_SUCCESS(status)); got_all_fields &= !!U_SUCCESS(status);
exploded->millisecond = calendar->get(UCAL_MILLISECOND, status); exploded->millisecond = calendar->get(UCAL_MILLISECOND, status);
DCHECK(U_SUCCESS(status)); got_all_fields &= !!U_SUCCESS(status);
return got_all_fields;
} }
} // namespace
// static // static
bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) { void Time::ExplodeUsingIcu(int64_t millis_since_unix_epoch,
bool is_local,
Exploded* exploded) {
if (!ExplodeUsingIcuCalendar(millis_since_unix_epoch, is_local, exploded)) {
// Error: Return an invalid Exploded.
*exploded = {};
}
}
// static
bool Time::FromExplodedUsingIcu(bool is_local,
const Exploded& exploded,
int64_t* millis_since_unix_epoch) {
// ICU's UCalendarMonths is 0-based. E.g., 0 for January. // ICU's UCalendarMonths is 0-based. E.g., 0 for January.
CheckedNumeric<int> month = exploded.month; CheckedNumeric<int> month = exploded.month;
month--; month--;
if (!month.IsValid()) { if (!month.IsValid())
*time = Time(0);
return false; return false;
}
std::unique_ptr<icu::Calendar> calendar = CreateCalendar(is_local); std::unique_ptr<icu::Calendar> calendar = CreateCalendar(is_local);
...@@ -85,12 +124,29 @@ bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) { ...@@ -85,12 +124,29 @@ bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) {
UErrorCode status = U_ZERO_ERROR; UErrorCode status = U_ZERO_ERROR;
UDate date = calendar->getTime(status); UDate date = calendar->getTime(status);
if (U_FAILURE(status)) { if (U_FAILURE(status))
*time = Time(0);
return false; return false;
}
return FromMillisecondsSinceUnixEpoch(date, time); *millis_since_unix_epoch = saturated_cast<int64_t>(date);
return true;
} }
#if defined(OS_FUCHSIA)
void Time::Explode(bool is_local, Exploded* exploded) const {
return ExplodeUsingIcu(ToRoundedDownMillisecondsSinceUnixEpoch(), is_local,
exploded);
}
// static
bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) {
int64_t millis_since_unix_epoch;
if (FromExplodedUsingIcu(is_local, exploded, &millis_since_unix_epoch))
return FromMillisecondsSinceUnixEpoch(millis_since_unix_epoch, time);
*time = Time(0);
return false;
}
#endif // defined(OS_FUCHSIA)
} // namespace base } // namespace base
...@@ -14,33 +14,29 @@ ...@@ -14,33 +14,29 @@
#include <limits> #include <limits>
#include "base/no_destructor.h"
#include "base/numerics/safe_math.h" #include "base/numerics/safe_math.h"
#include "base/synchronization/lock.h" #include "base/synchronization/lock.h"
#include "build/build_config.h" #include "build/build_config.h"
#if defined(OS_ANDROID) #if defined(OS_NACL)
#include "base/os_compat_android.h"
#elif defined(OS_NACL)
#include "base/os_compat_nacl.h" #include "base/os_compat_nacl.h"
#endif #endif
#if defined(OS_APPLE)
static_assert(sizeof(time_t) >= 8, "Y2038 problem!");
#endif
namespace { namespace {
// This prevents a crash on traversing the environment global and looking up // This prevents a crash on traversing the environment global and looking up
// the 'TZ' variable in libc. See: crbug.com/390567. // the 'TZ' variable in libc. See: crbug.com/390567.
base::Lock* GetSysTimeToTimeStructLock() { base::Lock* GetSysTimeToTimeStructLock() {
static auto* lock = new base::Lock(); static base::NoDestructor<base::Lock> lock;
return lock; return lock.get();
} }
// Define a system-specific SysTime that wraps either to a time_t or // Define a system-specific SysTime that wraps either to a time_t or
// a time64_t depending on the host system, and associated convertion. // a time64_t depending on the host system, and associated convertion.
// See crbug.com/162007 // See crbug.com/162007
#if defined(OS_ANDROID) && !defined(__LP64__) #if defined(OS_ANDROID) && !defined(__LP64__)
typedef time64_t SysTime; typedef time64_t SysTime;
SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) { SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) {
...@@ -58,7 +54,9 @@ void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) { ...@@ -58,7 +54,9 @@ void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
else else
gmtime64_r(&t, timestruct); gmtime64_r(&t, timestruct);
} }
#elif defined(OS_AIX) #elif defined(OS_AIX)
// The function timegm is not available on AIX. // The function timegm is not available on AIX.
time_t aix_timegm(struct tm* tm) { time_t aix_timegm(struct tm* tm) {
time_t ret; time_t ret;
...@@ -99,7 +97,8 @@ void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) { ...@@ -99,7 +97,8 @@ void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
gmtime_r(&t, timestruct); gmtime_r(&t, timestruct);
} }
#else #else // MacOS (and iOS 64-bit), Linux/ChromeOS, or any other POSIX-compliant.
typedef time_t SysTime; typedef time_t SysTime;
SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) { SysTime SysTimeFromTimeStruct(struct tm* timestruct, bool is_local) {
...@@ -114,6 +113,7 @@ void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) { ...@@ -114,6 +113,7 @@ void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
else else
gmtime_r(&t, timestruct); gmtime_r(&t, timestruct);
} }
#endif // defined(OS_ANDROID) && !defined(__LP64__) #endif // defined(OS_ANDROID) && !defined(__LP64__)
} // namespace } // namespace
...@@ -121,24 +121,25 @@ void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) { ...@@ -121,24 +121,25 @@ void SysTimeToTimeStruct(SysTime t, struct tm* timestruct, bool is_local) {
namespace base { namespace base {
void Time::Explode(bool is_local, Exploded* exploded) const { void Time::Explode(bool is_local, Exploded* exploded) const {
// The following values are all rounded towards -infinity. const int64_t millis_since_unix_epoch =
int64_t milliseconds = ToRoundedDownMillisecondsSinceUnixEpoch(); ToRoundedDownMillisecondsSinceUnixEpoch();
SysTime seconds; // Seconds since epoch.
int millisecond; // Exploded millisecond value (0-999). // For systems with a Y2038 problem, use ICU as the Explode() implementation.
if (sizeof(SysTime) < 8) {
// If the microseconds were negative, the rounded down milliseconds will also ExplodeUsingIcu(millis_since_unix_epoch, is_local, exploded);
// be negative. For example, -1 us becomes -1 ms. return;
if (milliseconds >= 0) { }
// Rounding towards -infinity <=> rounding towards 0, in this case.
seconds = milliseconds / kMillisecondsPerSecond; // Split the |millis_since_unix_epoch| into separate seconds and millisecond
millisecond = milliseconds % kMillisecondsPerSecond; // components because the platform calendar-explode operates at one-second
} else { // granularity.
// Round these *down* (towards -infinity). SysTime seconds = millis_since_unix_epoch / Time::kMillisecondsPerSecond;
seconds = (milliseconds + 1) / kMillisecondsPerSecond - 1; int64_t millisecond = millis_since_unix_epoch % Time::kMillisecondsPerSecond;
// Make this nonnegative (and between 0 and 999 inclusive). if (millisecond < 0) {
millisecond = milliseconds % kMillisecondsPerSecond; // Make the the |millisecond| component positive, within the range [0,999],
if (millisecond < 0) // by transferring 1000 ms from |seconds|.
millisecond += kMillisecondsPerSecond; --seconds;
millisecond += Time::kMillisecondsPerSecond;
} }
struct tm timestruct; struct tm timestruct;
......
...@@ -958,6 +958,134 @@ TEST_F(TimeTest, FromLocalExplodedCrashOnAndroid) { ...@@ -958,6 +958,134 @@ TEST_F(TimeTest, FromLocalExplodedCrashOnAndroid) {
} }
#endif // OS_ANDROID #endif // OS_ANDROID
// Regression test for https://crbug.com/1104442
TEST_F(TimeTest, Explode_Y10KCompliance) {
constexpr int kDaysPerYear = 365;
constexpr int64_t kHalfYearInMicros =
TimeDelta::FromDays(kDaysPerYear / 2).InMicroseconds();
// The Y2038 issue occurs when a 32-bit signed integer overflows.
constexpr int64_t kYear2038MicrosOffset =
Time::kTimeTToMicrosecondsOffset +
(std::numeric_limits<int32_t>::max() * Time::kMicrosecondsPerSecond);
// 1 March 10000 at noon.
constexpr int64_t kYear10000YearsOffset = 10000 - 1970;
constexpr int kExtraLeapDaysOverThoseYears = 1947;
constexpr int kDaysFromJanToMar10000 = 31 + 29;
constexpr int64_t kMarch10000MicrosOffset =
Time::kTimeTToMicrosecondsOffset +
TimeDelta::FromDays(kYear10000YearsOffset * kDaysPerYear +
kExtraLeapDaysOverThoseYears + kDaysFromJanToMar10000)
.InMicroseconds() +
TimeDelta::FromHours(12).InMicroseconds();
// Windows uses a 64-bit signed integer type that reperesents the number of
// 1/10 microsecond ticks.
constexpr int64_t kWindowsMaxMicrosOffset =
std::numeric_limits<int64_t>::max() / 10;
// ICU's Calendar API uses double values. Thus, the maximum supported value is
// the maximum integer that can be represented by a double.
static_assert(std::numeric_limits<double>::radix == 2, "");
constexpr int64_t kMaxIntegerAsDoubleMillis =
int64_t{1} << std::numeric_limits<double>::digits;
constexpr int64_t kIcuMaxMicrosOffset =
Time::kTimeTToMicrosecondsOffset +
(kMaxIntegerAsDoubleMillis * Time::kMicrosecondsPerMillisecond + 999);
const auto make_time = [](int64_t micros) {
return Time::FromDeltaSinceWindowsEpoch(
TimeDelta::FromMicroseconds(micros));
};
const struct TestCase {
Time time;
Time::Exploded expected;
} kTestCases[] = {
// A very long time ago.
{Time::Min(), Time::Exploded{-290677, 12, 4, 23, 19, 59, 5, 224}},
// Before/On/After 1 Jan 1601.
{make_time(-kHalfYearInMicros),
Time::Exploded{1600, 7, 1, 3, 0, 0, 0, 0}},
{make_time(0), Time::Exploded{1601, 1, 1, 1, 0, 0, 0, 0}},
{make_time(kHalfYearInMicros), Time::Exploded{1601, 7, 1, 2, 0, 0, 0, 0}},
// Before/On/After 1 Jan 1970.
{make_time(Time::kTimeTToMicrosecondsOffset - kHalfYearInMicros),
Time::Exploded{1969, 7, 4, 3, 0, 0, 0, 0}},
{make_time(Time::kTimeTToMicrosecondsOffset),
Time::Exploded{1970, 1, 4, 1, 0, 0, 0, 0}},
{make_time(Time::kTimeTToMicrosecondsOffset + kHalfYearInMicros),
Time::Exploded{1970, 7, 4, 2, 0, 0, 0, 0}},
// Before/On/After 19 January 2038.
{make_time(kYear2038MicrosOffset - kHalfYearInMicros),
Time::Exploded{2037, 7, 2, 21, 3, 14, 7, 0}},
{make_time(kYear2038MicrosOffset),
Time::Exploded{2038, 1, 2, 19, 3, 14, 7, 0}},
{make_time(kYear2038MicrosOffset + kHalfYearInMicros),
Time::Exploded{2038, 7, 2, 20, 3, 14, 7, 0}},
// Before/On/After 1 March 10000 at noon.
{make_time(kMarch10000MicrosOffset - kHalfYearInMicros),
Time::Exploded{9999, 9, 3, 1, 12, 0, 0, 0}},
{make_time(kMarch10000MicrosOffset),
Time::Exploded{10000, 3, 3, 1, 12, 0, 0, 0}},
{make_time(kMarch10000MicrosOffset + kHalfYearInMicros),
Time::Exploded{10000, 8, 3, 30, 12, 0, 0, 0}},
// Before/On/After Windows Max (14 September 30828).
{make_time(kWindowsMaxMicrosOffset - kHalfYearInMicros),
Time::Exploded{30828, 3, 4, 16, 2, 48, 5, 477}},
{make_time(kWindowsMaxMicrosOffset),
Time::Exploded{30828, 9, 4, 14, 2, 48, 5, 477}},
{make_time(kWindowsMaxMicrosOffset + kHalfYearInMicros),
Time::Exploded{30829, 3, 4, 15, 2, 48, 5, 477}},
// Before/On/After ICU Max.
{make_time(kIcuMaxMicrosOffset - kHalfYearInMicros),
Time::Exploded{287396, 4, 3, 13, 8, 59, 0, 992}},
{make_time(kIcuMaxMicrosOffset),
Time::Exploded{287396, 10, 3, 12, 8, 59, 0, 992}},
{make_time(kIcuMaxMicrosOffset + kHalfYearInMicros),
Time::Exploded{287397, 4, 3, 12, 8, 59, 0, 992}},
// A very long time from now.
{Time::Max(), Time::Exploded{293878, 1, 4, 10, 4, 0, 54, 775}},
};
for (const TestCase& test_case : kTestCases) {
SCOPED_TRACE(testing::Message() << "Time: " << test_case.time);
Time::Exploded exploded = {};
test_case.time.UTCExplode(&exploded);
// Confirm the implementation provides a correct conversion for all inputs
// within the guaranteed range (as discussed in the header comments). If an
// implementation provides a result for inputs outside the guaranteed range,
// the result must still be correct.
if (exploded.HasValidValues()) {
EXPECT_EQ(test_case.expected.year, exploded.year);
EXPECT_EQ(test_case.expected.month, exploded.month);
EXPECT_EQ(test_case.expected.day_of_week, exploded.day_of_week);
EXPECT_EQ(test_case.expected.day_of_month, exploded.day_of_month);
EXPECT_EQ(test_case.expected.hour, exploded.hour);
EXPECT_EQ(test_case.expected.minute, exploded.minute);
EXPECT_EQ(test_case.expected.second, exploded.second);
EXPECT_EQ(test_case.expected.millisecond, exploded.millisecond);
} else {
// The implementation could not provide a conversion. That is only allowed
// for inputs outside the guaranteed range.
const bool is_in_range =
test_case.time >= make_time(0) &&
test_case.time <= make_time(kWindowsMaxMicrosOffset);
EXPECT_FALSE(is_in_range);
}
}
}
TEST_F(TimeTest, FromExploded_MinMax) { TEST_F(TimeTest, FromExploded_MinMax) {
Time::Exploded exploded = {0}; Time::Exploded exploded = {0};
exploded.month = 1; exploded.month = 1;
......
...@@ -62,13 +62,17 @@ int64_t FileTimeToMicroseconds(const FILETIME& ft) { ...@@ -62,13 +62,17 @@ int64_t FileTimeToMicroseconds(const FILETIME& ft) {
return bit_cast<int64_t, FILETIME>(ft) / 10; return bit_cast<int64_t, FILETIME>(ft) / 10;
} }
void MicrosecondsToFileTime(int64_t us, FILETIME* ft) { bool CanConvertToFileTime(int64_t us) {
DCHECK_GE(us, 0LL) << "Time is less than 0, negative values are not " return us >= 0 && us <= (std::numeric_limits<int64_t>::max() / 10);
"representable in FILETIME"; }
FILETIME MicrosecondsToFileTime(int64_t us) {
DCHECK(CanConvertToFileTime(us)) << "Out-of-range: Cannot convert " << us
<< " microseconds to FILETIME units.";
// Multiply by 10 to convert microseconds to 100-nanoseconds. Bit_cast will // Multiply by 10 to convert microseconds to 100-nanoseconds. Bit_cast will
// handle alignment problems. This only works on little-endian machines. // handle alignment problems. This only works on little-endian machines.
*ft = bit_cast<FILETIME, int64_t>(us * 10); return bit_cast<FILETIME, int64_t>(us * 10);
} }
int64_t CurrentWallclockMicroseconds() { int64_t CurrentWallclockMicroseconds() {
...@@ -235,9 +239,7 @@ FILETIME Time::ToFileTime() const { ...@@ -235,9 +239,7 @@ FILETIME Time::ToFileTime() const {
result.dwLowDateTime = std::numeric_limits<DWORD>::max(); result.dwLowDateTime = std::numeric_limits<DWORD>::max();
return result; return result;
} }
FILETIME utc_ft; return MicrosecondsToFileTime(us_);
MicrosecondsToFileTime(us_, &utc_ft);
return utc_ft;
} }
// static // static
...@@ -348,15 +350,13 @@ bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) { ...@@ -348,15 +350,13 @@ bool Time::FromExploded(bool is_local, const Exploded& exploded, Time* time) {
} }
void Time::Explode(bool is_local, Exploded* exploded) const { void Time::Explode(bool is_local, Exploded* exploded) const {
if (us_ < 0LL) { if (!CanConvertToFileTime(us_)) {
// We are not able to convert it to FILETIME. // We are not able to convert it to FILETIME.
ZeroMemory(exploded, sizeof(*exploded)); ZeroMemory(exploded, sizeof(*exploded));
return; return;
} }
// FILETIME in UTC. const FILETIME utc_ft = MicrosecondsToFileTime(us_);
FILETIME utc_ft;
MicrosecondsToFileTime(us_, &utc_ft);
// FILETIME in local time if necessary. // FILETIME in local time if necessary.
bool success = true; bool success = true;
......
...@@ -5,7 +5,6 @@ ...@@ -5,7 +5,6 @@
#include "net/cert/internal/revocation_util.h" #include "net/cert/internal/revocation_util.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "build/build_config.h"
#include "net/der/encode_values.h" #include "net/der/encode_values.h"
#include "net/der/parse_values.h" #include "net/der/parse_values.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
...@@ -138,13 +137,15 @@ TEST(CheckRevocationDateTest, VerifyTimeMinusAgeFromBeforeWindowsEpoch) { ...@@ -138,13 +137,15 @@ TEST(CheckRevocationDateTest, VerifyTimeMinusAgeFromBeforeWindowsEpoch) {
der::GeneralizedTime encoded_this_update; der::GeneralizedTime encoded_this_update;
ASSERT_TRUE( ASSERT_TRUE(
der::EncodeTimeAsGeneralizedTime(this_update, &encoded_this_update)); der::EncodeTimeAsGeneralizedTime(this_update, &encoded_this_update));
#if defined(OS_WIN) // Note: Not all platforms can explode Time before the Windows Epoch. So,
EXPECT_FALSE(CheckRevocationDateValid(encoded_this_update, nullptr, // CheckRevocationDateValid() should succeed iff UTCExplode() will also
verify_time, kOneWeek)); // succeed for a Time 6 days before the Windows Epoch.
#else base::Time::Exploded exploded;
EXPECT_TRUE(CheckRevocationDateValid(encoded_this_update, nullptr, (verify_time - kOneWeek).UTCExplode(&exploded);
verify_time, kOneWeek)); const bool can_encode_before_windows_epoch = exploded.HasValidValues();
#endif EXPECT_EQ(can_encode_before_windows_epoch,
CheckRevocationDateValid(encoded_this_update, nullptr, verify_time,
kOneWeek));
} }
} // namespace net } // namespace net
...@@ -41,33 +41,25 @@ TEST(EncodeValuesTest, EncodeTimeAsGeneralizedTime) { ...@@ -41,33 +41,25 @@ TEST(EncodeValuesTest, EncodeTimeAsGeneralizedTime) {
// after the 32-bit time_t maximum, the conversion between base::Time and // after the 32-bit time_t maximum, the conversion between base::Time and
// der::GeneralizedTime goes through the time representation of the underlying // der::GeneralizedTime goes through the time representation of the underlying
// platform, which might not be able to handle the full GeneralizedTime date // platform, which might not be able to handle the full GeneralizedTime date
// range. Out-of-range times should not be converted to der::GeneralizedTime. In // range. Out-of-range times should not be converted to der::GeneralizedTime.
// tests, possibly-out-of-range test times are specified as a
// base::Time::Exploded, and then converted to a base::Time. If the conversion
// fails, this signals the underlying platform cannot handle the time, and the
// test aborts early. If the underlying platform can represent the time, then
// the conversion is successful, and the encoded GeneralizedTime can should
// match the test time.
// //
// Thu, 1 Jan 1570 00:00:00 GMT. This time is unrepresentable by the Windows // Thus, this test focuses on an input date 31 years before the Windows epoch,
// native time libraries. // and confirms that EncodeTimeAsGeneralizedTime() produces the correct result
// on platforms where it returns true. As of this writing, it will return false
// on Windows.
TEST(EncodeValuesTest, EncodeTimeFromBeforeWindowsEpoch) { TEST(EncodeValuesTest, EncodeTimeFromBeforeWindowsEpoch) {
base::Time::Exploded exploded; constexpr int kYearsBeforeWindowsEpoch = 1601 - 1570;
exploded.year = 1570; constexpr int kDaysPerYear = 365;
exploded.month = 1; constexpr int kExtraLeapDaysOverThoseYears = 8;
exploded.day_of_week = 5; constexpr base::Time kStartOfYear1570 =
exploded.day_of_month = 1; base::Time() -
exploded.hour = 0; base::TimeDelta::FromDays(kYearsBeforeWindowsEpoch * kDaysPerYear +
exploded.minute = 0; kExtraLeapDaysOverThoseYears);
exploded.second = 0;
exploded.millisecond = 0;
base::Time time; GeneralizedTime generalized_time;
if (!base::Time::FromUTCExploded(exploded, &time)) if (!EncodeTimeAsGeneralizedTime(kStartOfYear1570, &generalized_time))
return; return;
GeneralizedTime generalized_time;
ASSERT_TRUE(EncodeTimeAsGeneralizedTime(time, &generalized_time));
EXPECT_EQ(1570, generalized_time.year); EXPECT_EQ(1570, generalized_time.year);
EXPECT_EQ(1, generalized_time.month); EXPECT_EQ(1, generalized_time.month);
EXPECT_EQ(1, generalized_time.day); EXPECT_EQ(1, generalized_time.day);
......
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