File: time_clamper.h

package info (click to toggle)
chromium 139.0.7258.127-1
  • links: PTS, VCS
  • area: main
  • in suites:
  • size: 6,122,068 kB
  • sloc: cpp: 35,100,771; ansic: 7,163,530; javascript: 4,103,002; python: 1,436,920; asm: 946,517; xml: 746,709; pascal: 187,653; perl: 88,691; sh: 88,436; objc: 79,953; sql: 51,488; cs: 44,583; fortran: 24,137; makefile: 22,147; tcl: 15,277; php: 13,980; yacc: 8,984; ruby: 7,485; awk: 3,720; lisp: 3,096; lex: 1,327; ada: 727; jsp: 228; sed: 36
file content (116 lines) | stat: -rw-r--r-- 4,636 bytes parent folder | download | duplicates (11)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
// 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 <algorithm>

#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<double>(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<int64_t>(random_value /
                                         (std::numeric_limits<uint64_t>::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_