// Copyright 2023 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #ifndef GIN_TIME_CLAMPER_H_ #define GIN_TIME_CLAMPER_H_ #include #include "base/rand_util.h" #include "base/time/time.h" #include "gin/gin_export.h" namespace gin { // This class adds some amount of jitter to time. That is, for every // `kResolutionMicros` microseconds it calculates a threshold (using a hash) // that once exceeded advances to the next threshold. This is done so that // time jumps slightly and does not move smoothly. // // NOTE: the implementation assumes it's used for servicing calls from JS, // which uses the unix-epoch at time 0. // TODO(skyostil): Deduplicate this with the clamper in Blink. class GIN_EXPORT TimeClamper { public: // Public for tests. static const int64_t kResolutionMicros; TimeClamper() : secret_(base::RandUint64()) {} // This constructor should only be used in tests. explicit TimeClamper(uint64_t secret) : secret_(secret) {} TimeClamper(const TimeClamper&) = delete; TimeClamper& operator=(const TimeClamper&) = delete; ~TimeClamper() = default; // Clamps a time to millisecond precision. The return value is in milliseconds // relative to unix-epoch (which is what JS uses). inline int64_t ClampToMillis(base::Time time) const { // Adding jitter is non-trivial, only use it if necessary. // ClampTimeResolution() adjusts the time to land on `kResolutionMicros` // boundaries, and either uses the current `kResolutionMicros` boundary, or // the next one. Because `kResolutionMicros` is smaller than 1ms, and this // function returns millisecond accuracy, ClampTimeResolution() is only // necessary when within `kResolutionMicros` of the next millisecond. const int64_t now_micros = (time - base::Time::UnixEpoch()).InMicroseconds(); const int64_t micros = now_micros % 1000; // abs() is necessary for devices with times before unix-epoch (most likely // configured incorrectly). if (abs(micros) + kResolutionMicros < 1000) { return now_micros / 1000; } return ClampTimeResolution(now_micros) / 1000; } // Clamps the time, giving microsecond precision. The return value is in // milliseconds relative to unix-epoch (which is what JS uses). inline double ClampToMillisHighResolution(base::Time now) const { const int64_t clamped_time = ClampTimeResolution((now - base::Time::UnixEpoch()).InMicroseconds()); return static_cast(clamped_time) / 1000.0; } private: inline int64_t ClampTimeResolution(int64_t time_micros) const { if (time_micros < 0) { return -ClampTimeResolutionPositiveValue(-time_micros); } return ClampTimeResolutionPositiveValue(time_micros); } inline int64_t ClampTimeResolutionPositiveValue(int64_t time_micros) const { DCHECK_GE(time_micros, 0u); // For each clamped time interval, compute a pseudorandom transition // threshold. The reported time will either be the start of that interval or // the next one depending on which side of the threshold |time_seconds| is. const int64_t interval = time_micros / kResolutionMicros; const int64_t clamped_time_micros = interval * kResolutionMicros; const int64_t tick_threshold = ThresholdFor(clamped_time_micros); if (time_micros - clamped_time_micros < tick_threshold) { return clamped_time_micros; } return clamped_time_micros + kResolutionMicros; } inline int64_t ThresholdFor(int64_t clamped_time) const { // Returns a random value between 0 and kResolutionMicros. The distribution // is not necessarily equal, but for a random value it's good enough. // Avoid floating-point math by rewriting: // (random_value * 1.0 / UINT64_MAX) * kResolutionMicros // into: // random_value / (UINT64_MAX / kResolutionMicros) // where we avoid integer overflow by dividing instead of multiplying. const uint64_t random_value = MurmurHash3(clamped_time ^ secret_); return std::min(static_cast(random_value / (std::numeric_limits::max() / kResolutionMicros)), kResolutionMicros); } static inline uint64_t MurmurHash3(uint64_t value) { value ^= value >> 33; value *= uint64_t{0xFF51AFD7ED558CCD}; value ^= value >> 33; value *= uint64_t{0xC4CEB9FE1A85EC53}; value ^= value >> 33; return value; } const uint64_t secret_; }; } // namespace gin #endif // GIN_TIME_CLAMPER_H_