diff --git a/system_wrappers/include/ntp_time.h b/system_wrappers/include/ntp_time.h index 1c321841bd..1f57558b71 100644 --- a/system_wrappers/include/ntp_time.h +++ b/system_wrappers/include/ntp_time.h @@ -10,7 +10,9 @@ #ifndef SYSTEM_WRAPPERS_INCLUDE_NTP_TIME_H_ #define SYSTEM_WRAPPERS_INCLUDE_NTP_TIME_H_ -#include +#include +#include +#include #include "rtc_base/numerics/safe_conversions.h" @@ -60,5 +62,55 @@ inline bool operator!=(const NtpTime& n1, const NtpTime& n2) { return !(n1 == n2); } +// Converts |int64_t| milliseconds to Q32.32-formatted fixed-point seconds. +// Performs clamping if the result overflows or underflows. +inline int64_t Int64MsToQ32x32(int64_t milliseconds) { + // TODO(bugs.webrtc.org/10893): Change to use |rtc::saturated_cast| once the + // bug has been fixed. + double result = + std::round(milliseconds * (NtpTime::kFractionsPerSecond / 1000.0)); + + if (result <= std::numeric_limits::min()) { + return std::numeric_limits::min(); + } + + if (result >= std::numeric_limits::max()) { + return std::numeric_limits::max(); + } + + return rtc::dchecked_cast(result); +} + +// Converts |int64_t| milliseconds to UQ32.32-formatted fixed-point seconds. +// Performs clamping if the result overflows or underflows. +inline uint64_t Int64MsToUQ32x32(int64_t milliseconds) { + // TODO(bugs.webrtc.org/10893): Change to use |rtc::saturated_cast| once the + // bug has been fixed. + double result = + std::round(milliseconds * (NtpTime::kFractionsPerSecond / 1000.0)); + + if (result <= std::numeric_limits::min()) { + return std::numeric_limits::min(); + } + + if (result >= std::numeric_limits::max()) { + return std::numeric_limits::max(); + } + + return rtc::dchecked_cast(result); +} + +// Converts Q32.32-formatted fixed-point seconds to |int64_t| milliseconds. +inline int64_t Q32x32ToInt64Ms(int64_t q32x32) { + return rtc::dchecked_cast( + std::round(q32x32 * (1000.0 / NtpTime::kFractionsPerSecond))); +} + +// Converts UQ32.32-formatted fixed-point seconds to |int64_t| milliseconds. +inline int64_t UQ32x32ToInt64Ms(uint64_t q32x32) { + return rtc::dchecked_cast( + std::round(q32x32 * (1000.0 / NtpTime::kFractionsPerSecond))); +} + } // namespace webrtc #endif // SYSTEM_WRAPPERS_INCLUDE_NTP_TIME_H_ diff --git a/system_wrappers/source/ntp_time_unittest.cc b/system_wrappers/source/ntp_time_unittest.cc index f45273fb94..cdaca67fbe 100644 --- a/system_wrappers/source/ntp_time_unittest.cc +++ b/system_wrappers/source/ntp_time_unittest.cc @@ -10,14 +10,19 @@ #include "system_wrappers/include/ntp_time.h" +#include + #include "system_wrappers/include/clock.h" #include "test/gtest.h" namespace webrtc { namespace { -const uint32_t kNtpSec = 0x12345678; -const uint32_t kNtpFrac = 0x23456789; +constexpr uint32_t kNtpSec = 0x12345678; +constexpr uint32_t kNtpFrac = 0x23456789; + +constexpr int64_t kOneSecQ32x32 = uint64_t{1} << 32; +constexpr int64_t kOneMsQ32x32 = 4294967; TEST(NtpTimeTest, NoValueMeansInvalid) { NtpTime ntp; @@ -62,5 +67,215 @@ TEST(NtpTimeTest, CanExplicitlyConvertToAndFromUint64) { EXPECT_EQ(NtpTime(0x12345678, 0x90abcdef), NtpTime(0x1234567890abcdef)); } +TEST(NtpTimeTest, VerifyInt64MsToQ32x32NearZero) { + // Zero + EXPECT_EQ(Int64MsToQ32x32(0), 0); + + // Zero + 1 millisecond + EXPECT_EQ(Int64MsToQ32x32(1), kOneMsQ32x32); + + // Zero - 1 millisecond + EXPECT_EQ(Int64MsToQ32x32(-1), -kOneMsQ32x32); + + // Zero + 1 second + EXPECT_EQ(Int64MsToQ32x32(1000), kOneSecQ32x32); + + // Zero - 1 second + EXPECT_EQ(Int64MsToQ32x32(-1000), -kOneSecQ32x32); +} + +TEST(NtpTimeTest, VerifyInt64MsToUQ32x32NearZero) { + // Zero + EXPECT_EQ(Int64MsToUQ32x32(0), uint64_t{0}); + + // Zero + 1 millisecond + EXPECT_EQ(Int64MsToUQ32x32(1), uint64_t{kOneMsQ32x32}); + + // Zero - 1 millisecond + EXPECT_EQ(Int64MsToUQ32x32(-1), uint64_t{0}); // Clamped + + // Zero + 1 second + EXPECT_EQ(Int64MsToUQ32x32(1000), uint64_t{kOneSecQ32x32}); + + // Zero - 1 second + EXPECT_EQ(Int64MsToUQ32x32(-1000), uint64_t{0}); // Clamped +} + +TEST(NtpTimeTest, VerifyQ32x32ToInt64MsNearZero) { + // Zero + EXPECT_EQ(Q32x32ToInt64Ms(0), 0); + + // Zero + 1 millisecond + EXPECT_EQ(Q32x32ToInt64Ms(kOneMsQ32x32), 1); + + // Zero - 1 millisecond + EXPECT_EQ(Q32x32ToInt64Ms(-kOneMsQ32x32), -1); + + // Zero + 1 second + EXPECT_EQ(Q32x32ToInt64Ms(kOneSecQ32x32), 1000); + + // Zero - 1 second + EXPECT_EQ(Q32x32ToInt64Ms(-kOneSecQ32x32), -1000); +} + +TEST(NtpTimeTest, VerifyUQ32x32ToInt64MsNearZero) { + // Zero + EXPECT_EQ(UQ32x32ToInt64Ms(0), 0); + + // Zero + 1 millisecond + EXPECT_EQ(UQ32x32ToInt64Ms(kOneMsQ32x32), 1); + + // Zero + 1 second + EXPECT_EQ(UQ32x32ToInt64Ms(kOneSecQ32x32), 1000); +} + +TEST(NtpTimeTest, VerifyInt64MsToQ32x32NearMax) { + constexpr int64_t kMaxQ32x32 = std::numeric_limits::max(); + constexpr int64_t kBoundaryMs = (kMaxQ32x32 >> 32) * 1000 + 999; + + // Max + const int64_t boundary_q32x32 = Int64MsToQ32x32(kBoundaryMs); + EXPECT_LE(boundary_q32x32, kMaxQ32x32); + EXPECT_GT(boundary_q32x32, kMaxQ32x32 - kOneMsQ32x32); + + // Max + 1 millisecond + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs + 1), kMaxQ32x32); // Clamped + + // Max - 1 millisecond + EXPECT_LE(Int64MsToQ32x32(kBoundaryMs - 1), kMaxQ32x32 - kOneMsQ32x32); + + // Max + 1 second + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs + 1000), kMaxQ32x32); // Clamped + + // Max - 1 second + EXPECT_LE(Int64MsToQ32x32(kBoundaryMs - 1000), kMaxQ32x32 - kOneSecQ32x32); +} + +TEST(NtpTimeTest, VerifyInt64MsToUQ32x32NearMax) { + constexpr uint64_t kMaxUQ32x32 = std::numeric_limits::max(); + constexpr int64_t kBoundaryMs = (kMaxUQ32x32 >> 32) * 1000 + 999; + + // Max + const uint64_t boundary_uq32x32 = Int64MsToUQ32x32(kBoundaryMs); + EXPECT_LE(boundary_uq32x32, kMaxUQ32x32); + EXPECT_GT(boundary_uq32x32, kMaxUQ32x32 - kOneMsQ32x32); + + // Max + 1 millisecond + EXPECT_EQ(Int64MsToUQ32x32(kBoundaryMs + 1), kMaxUQ32x32); // Clamped + + // Max - 1 millisecond + EXPECT_LE(Int64MsToUQ32x32(kBoundaryMs - 1), kMaxUQ32x32 - kOneMsQ32x32); + + // Max + 1 second + EXPECT_EQ(Int64MsToUQ32x32(kBoundaryMs + 1000), kMaxUQ32x32); // Clamped + + // Max - 1 second + EXPECT_LE(Int64MsToUQ32x32(kBoundaryMs - 1000), kMaxUQ32x32 - kOneSecQ32x32); +} + +TEST(NtpTimeTest, VerifyQ32x32ToInt64MsNearMax) { + constexpr int64_t kMaxQ32x32 = std::numeric_limits::max(); + constexpr int64_t kBoundaryMs = (kMaxQ32x32 >> 32) * 1000 + 1000; + + // Max + EXPECT_EQ(Q32x32ToInt64Ms(kMaxQ32x32), kBoundaryMs); + + // Max - 1 millisecond + EXPECT_EQ(Q32x32ToInt64Ms(kMaxQ32x32 - kOneMsQ32x32), kBoundaryMs - 1); + + // Max - 1 second + EXPECT_EQ(Q32x32ToInt64Ms(kMaxQ32x32 - kOneSecQ32x32), kBoundaryMs - 1000); +} + +TEST(NtpTimeTest, VerifyUQ32x32ToInt64MsNearMax) { + constexpr uint64_t kMaxUQ32x32 = std::numeric_limits::max(); + constexpr int64_t kBoundaryMs = (kMaxUQ32x32 >> 32) * 1000 + 1000; + + // Max + EXPECT_EQ(UQ32x32ToInt64Ms(kMaxUQ32x32), kBoundaryMs); + + // Max - 1 millisecond + EXPECT_EQ(UQ32x32ToInt64Ms(kMaxUQ32x32 - kOneMsQ32x32), kBoundaryMs - 1); + + // Max - 1 second + EXPECT_EQ(UQ32x32ToInt64Ms(kMaxUQ32x32 - kOneSecQ32x32), kBoundaryMs - 1000); +} + +TEST(NtpTimeTest, VerifyInt64MsToQ32x32NearMin) { + constexpr int64_t kBoundaryQ32x32 = 0x8000000000000000; + constexpr int64_t kBoundaryMs = -int64_t{0x80000000} * 1000; + + // Min + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs), kBoundaryQ32x32); + + // Min + 1 millisecond + EXPECT_EQ(Q32x32ToInt64Ms(Int64MsToQ32x32(kBoundaryMs + 1)), kBoundaryMs + 1); + + // Min - 1 millisecond + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs - 1), kBoundaryQ32x32); // Clamped + + // Min + 1 second + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs + 1000), + kBoundaryQ32x32 + kOneSecQ32x32); + + // Min - 1 second + EXPECT_EQ(Int64MsToQ32x32(kBoundaryMs - 1000), kBoundaryQ32x32); // Clamped +} + +TEST(NtpTimeTest, VerifyQ32x32ToInt64MsNearMin) { + constexpr int64_t kBoundaryQ32x32 = 0x8000000000000000; + constexpr int64_t kBoundaryMs = -int64_t{0x80000000} * 1000; + + // Min + EXPECT_EQ(Q32x32ToInt64Ms(kBoundaryQ32x32), kBoundaryMs); + + // Min + 1 millisecond + EXPECT_EQ(Q32x32ToInt64Ms(kBoundaryQ32x32 + kOneMsQ32x32), kBoundaryMs + 1); + + // Min + 1 second + EXPECT_EQ(Q32x32ToInt64Ms(kBoundaryQ32x32 + kOneSecQ32x32), + kBoundaryMs + 1000); +} + +TEST(NtpTimeTest, VerifyInt64MsToQ32x32RoundTrip) { + constexpr int kIterations = 50000; + + std::mt19937 generator(123456789); + std::uniform_int_distribution distribution( + Q32x32ToInt64Ms(std::numeric_limits::min()), + Q32x32ToInt64Ms(std::numeric_limits::max())); + + for (int iteration = 0; iteration < kIterations; ++iteration) { + int64_t input_ms = distribution(generator); + int64_t transit_q32x32 = Int64MsToQ32x32(input_ms); + int64_t output_ms = Q32x32ToInt64Ms(transit_q32x32); + + ASSERT_EQ(input_ms, output_ms) + << "iteration = " << iteration << ", input_ms = " << input_ms + << ", transit_q32x32 = " << transit_q32x32 + << ", output_ms = " << output_ms; + } +} + +TEST(NtpTimeTest, VerifyInt64MsToUQ32x32RoundTrip) { + constexpr int kIterations = 50000; + + std::mt19937 generator(123456789); + std::uniform_int_distribution distribution( + UQ32x32ToInt64Ms(std::numeric_limits::min()), + UQ32x32ToInt64Ms(std::numeric_limits::max())); + + for (int iteration = 0; iteration < kIterations; ++iteration) { + uint64_t input_ms = distribution(generator); + uint64_t transit_uq32x32 = Int64MsToUQ32x32(input_ms); + uint64_t output_ms = UQ32x32ToInt64Ms(transit_uq32x32); + + ASSERT_EQ(input_ms, output_ms) + << "iteration = " << iteration << ", input_ms = " << input_ms + << ", transit_uq32x32 = " << transit_uq32x32 + << ", output_ms = " << output_ms; + } +} + } // namespace } // namespace webrtc