Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 70 additions & 22 deletions include/msgpack/v1/adaptor/cpp11/chrono.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,54 @@ Target integral_cast(Source source) {

return static_cast<Target>(source);
}

// Adds d to tp without overflowing Duration's integer rep. The seconds field of
// a timestamp comes straight off the wire, so scaling it to Duration's ticks
// (1e9 for nanoseconds, 1e12 for picoseconds) can exceed the rep's range.
template <typename Clock, typename Duration, typename Rep, typename Period>
void checked_add(
std::chrono::time_point<Clock, Duration>& tp,
std::chrono::duration<Rep, Period> d,
std::false_type /* floating point rep */) {
typedef typename Duration::rep rep;
typedef std::ratio_divide<Period, typename Duration::period> ratio;
static_assert(ratio::den == 1, "d must convert to Duration exactly");
rep const max = std::chrono::duration_values<rep>::max();
rep const min = std::chrono::duration_values<rep>::min();
rep const factor = static_cast<rep>(ratio::num);
rep v = integral_cast<rep>(d.count());
if (v > max / factor) {
throw std::overflow_error{"timestamp does not fit the time_point"};
}
if (v < min / factor) {
throw std::underflow_error{"timestamp does not fit the time_point"};
}
v *= factor;
rep const cur = tp.time_since_epoch().count();
if (v > 0 && cur > max - v) {
throw std::overflow_error{"timestamp does not fit the time_point"};
}
if (v < 0 && cur < min - v) {
throw std::underflow_error{"timestamp does not fit the time_point"};
}
tp += Duration(v);
}

template <typename Clock, typename Duration, typename Rep, typename Period>
void checked_add(
std::chrono::time_point<Clock, Duration>& tp,
std::chrono::duration<Rep, Period> d,
std::true_type /* floating point rep */) {
tp += d;
}

template <typename Clock, typename Duration, typename Rep, typename Period>
void checked_add(
std::chrono::time_point<Clock, Duration>& tp,
std::chrono::duration<Rep, Period> d) {
checked_add(tp, d, std::integral_constant<bool,
std::chrono::treat_as_floating_point<typename Duration::rep>::value>());
}
} // namespace detail

template <typename Clock, typename Duration>
Expand All @@ -144,16 +192,16 @@ struct as<std::chrono::time_point<Clock, Duration>> {
case 4: {
uint32_t sec;
_msgpack_load32(uint32_t, o.via.ext.data(), &sec);
tp += std::chrono::seconds(sec);
detail::checked_add(tp, std::chrono::seconds(sec));
} break;
case 8: {
uint64_t value;
_msgpack_load64(uint64_t, o.via.ext.data(), &value);
uint32_t nanosec = detail::integral_cast<uint32_t>(value >> 34);
uint64_t sec = value & 0x00000003ffffffffLL;
tp += std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(nanosec));
tp += std::chrono::seconds(sec);
detail::checked_add(tp, std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(nanosec)));
detail::checked_add(tp, std::chrono::seconds(sec));
} break;
case 12: {
uint32_t nanosec;
Expand All @@ -162,20 +210,20 @@ struct as<std::chrono::time_point<Clock, Duration>> {
_msgpack_load64(int64_t, o.via.ext.data() + 4, &sec);

if (sec >= 0) {
tp += std::chrono::seconds(sec);
tp += std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(nanosec));
detail::checked_add(tp, std::chrono::seconds(sec));
detail::checked_add(tp, std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(nanosec)));
}
else {
if (nanosec == 0) {
tp += std::chrono::seconds(sec);
detail::checked_add(tp, std::chrono::seconds(sec));
}
else {
++sec;
tp += std::chrono::seconds(sec);
detail::checked_add(tp, std::chrono::seconds(sec));
int64_t ns = detail::integral_cast<int64_t>(nanosec) - 1000000000L;
tp += std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(ns));
detail::checked_add(tp, std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(ns)));
}
}
} break;
Expand All @@ -196,17 +244,17 @@ struct convert<std::chrono::time_point<Clock, Duration>> {
case 4: {
uint32_t sec;
_msgpack_load32(uint32_t, o.via.ext.data(), &sec);
tp += std::chrono::seconds(sec);
detail::checked_add(tp, std::chrono::seconds(sec));
v = tp;
} break;
case 8: {
uint64_t value;
_msgpack_load64(uint64_t, o.via.ext.data(), &value);
uint32_t nanosec = detail::integral_cast<uint32_t>(value >> 34);
uint64_t sec = value & 0x00000003ffffffffLL;
tp += std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(nanosec));
tp += std::chrono::seconds(sec);
detail::checked_add(tp, std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(nanosec)));
detail::checked_add(tp, std::chrono::seconds(sec));
v = tp;
} break;
case 12: {
Expand All @@ -216,20 +264,20 @@ struct convert<std::chrono::time_point<Clock, Duration>> {
_msgpack_load64(int64_t, o.via.ext.data() + 4, &sec);

if (sec >= 0) {
tp += std::chrono::seconds(sec);
tp += std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(nanosec));
detail::checked_add(tp, std::chrono::seconds(sec));
detail::checked_add(tp, std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(nanosec)));
}
else {
if (nanosec == 0) {
tp += std::chrono::seconds(sec);
detail::checked_add(tp, std::chrono::seconds(sec));
}
else {
++sec;
tp += std::chrono::seconds(sec);
detail::checked_add(tp, std::chrono::seconds(sec));
int64_t ns = detail::integral_cast<int64_t>(nanosec) - 1000000000L;
tp += std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(ns));
detail::checked_add(tp, std::chrono::duration_cast<Duration>(
std::chrono::nanoseconds(ns)));
}
}

Expand Down
44 changes: 44 additions & 0 deletions test/msgpack_cpp11.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,50 @@ BOOST_AUTO_TEST_CASE(system_clock_picoseconds)
BOOST_CHECK(val1 == val2);
}

BOOST_AUTO_TEST_CASE(system_clock_out_of_range)
{
typedef std::chrono::system_clock::time_point time_point;
// timestamp96 with tv_sec = INT64_MAX
char packed_max[] = {
static_cast<char>(0xc7u), 12, static_cast<char>(-1),
0, 0, 0, 0,
0x7f, static_cast<char>(0xffu), static_cast<char>(0xffu), static_cast<char>(0xffu),
static_cast<char>(0xffu), static_cast<char>(0xffu), static_cast<char>(0xffu), static_cast<char>(0xffu)
};
msgpack::object_handle oh =
msgpack::unpack(packed_max, sizeof(packed_max));
BOOST_CHECK_THROW(oh.get().as<time_point>(), std::overflow_error);
time_point val;
BOOST_CHECK_THROW(oh.get().convert(val), std::overflow_error);

// timestamp96 with tv_sec = INT64_MIN
char packed_min[] = {
static_cast<char>(0xc7u), 12, static_cast<char>(-1),
0, 0, 0, 0,
static_cast<char>(0x80u), 0, 0, 0, 0, 0, 0, 0
};
oh = msgpack::unpack(packed_min, sizeof(packed_min));
BOOST_CHECK_THROW(oh.get().as<time_point>(), std::underflow_error);
BOOST_CHECK_THROW(oh.get().convert(val), std::underflow_error);
}

BOOST_AUTO_TEST_CASE(system_clock_picoseconds_out_of_range)
{
typedef std::chrono::duration<int64_t, std::pico> picoseconds;
typedef std::chrono::time_point<std::chrono::system_clock, picoseconds> time_point;
// timestamp32 with tv_sec = UINT32_MAX, about 4.3e21 ps
char packed[] = {
static_cast<char>(0xd6u), static_cast<char>(-1),
static_cast<char>(0xffu), static_cast<char>(0xffu),
static_cast<char>(0xffu), static_cast<char>(0xffu)
};
msgpack::object_handle oh =
msgpack::unpack(packed, sizeof(packed));
BOOST_CHECK_THROW(oh.get().as<time_point>(), std::overflow_error);
time_point val;
BOOST_CHECK_THROW(oh.get().convert(val), std::overflow_error);
}

BOOST_AUTO_TEST_CASE(system_clock_impl_min)
{
std::chrono::system_clock::time_point val1(std::chrono::system_clock::time_point::min());
Expand Down
Loading